import { useState, useRef, useCallback } from "react";
const CARD_TYPES = ["Creature", "Instant", "Sorcery", "Enchantment", "Artifact", "Land", "Planeswalker", "Battle"];
const COLORS = [
{ id: "W", label: "White", symbol: "โ", bg: "#f9f6d5", border: "#c8b87a", text: "#333" },
{ id: "U", label: "Blue", symbol: "๐ง", bg: "#c6d9e8", border: "#3a6b9c", text: "#fff" },
{ id: "B", label: "Black", symbol: "๐", bg: "#1a1a1a", border: "#5a4a6e", text: "#d0c8e0" },
{ id: "R", label: "Red", symbol: "๐ฅ", bg: "#e87040", border: "#8b2500", text: "#fff" },
{ id: "G", label: "Green", symbol: "๐ฒ", bg: "#3a7a48", border: "#1a4025", text: "#d4f5d0" },
{ id: "C", label: "Colorless", symbol: "โ", bg: "#c0bdb5", border: "#7a7068", text: "#333" },
{ id: "M", label: "Multi", symbol: "โ
", bg: "linear-gradient(135deg,#f9f6d5,#c6d9e8,#1a1a1a,#e87040,#3a7a48)", border: "#b8960c", text: "#fff" },
];
const RARITY_COLORS = { Common: "#aaa", Uncommon: "#a0c0d0", Rare: "#d4af37", "Mythic Rare": "#e07020" };
const DEFAULT_CARD = {
name: "Blazing Champion",
manaCost: "{2}{R}{R}",
type: "Creature",
subtype: "Human Warrior",
color: "R",
rarity: "Rare",
power: "4",
toughness: "3",
rulesText: "Haste\nWhen Blazing Champion enters the battlefield, it deals 2 damage to any target.",
flavorText: "\"No retreat. No mercy. Only fire.\"",
artist: "Proxy Artist",
setSymbol: "โ
",
loyalty: "",
};
function CardPreview({ card, imageUrl }) {
const colorObj = COLORS.find(c => c.id === card.color) || COLORS[5];
const isMulti = card.color === "M";
const isLand = card.type === "Land";
const isPW = card.type === "Planeswalker";
const frameBg = isMulti
? "linear-gradient(160deg,#f9f6d5 0%,#c6d9e8 25%,#1a1a1a 50%,#e87040 75%,#3a7a48 100%)"
: colorObj.bg;
const rarityColor = RARITY_COLORS[card.rarity] || "#aaa";
return (
{/* Header */}
{card.name || "Card Name"}
{card.manaCost || ""}
{/* Art Frame */}
{imageUrl ? (

) : (
{colorObj.symbol}
)}
{/* Set & rarity */}
{card.setSymbol || "โ
"} {card.rarity ? card.rarity[0] : ""}
{/* Type Line */}
{[card.type, card.subtype].filter(Boolean).join(" โ ") || "Type"}
{card.setSymbol || "โ
"}
{/* Text Box */}
{card.rulesText || ""}
{card.flavorText && (
{card.flavorText}
)}
{/* Footer */}
โฆ {card.artist || "Unknown"}
{(card.type === "Creature" || card.type === "Battle") && (
{card.power}/{card.toughness}
)}
{isPW && card.loyalty && (
{card.loyalty}
)}
);
}
function Field({ label, children }) {
return (
{children}
);
}
const inputStyle = {
background: "#1e1a14",
border: "1px solid #3a3020",
borderRadius: 6,
color: "#e8dfc8",
padding: "7px 10px",
fontSize: 13,
fontFamily: "inherit",
outline: "none",
width: "100%",
boxSizing: "border-box",
transition: "border-color 0.2s",
};
export default function App() {
const [card, setCard] = useState(DEFAULT_CARD);
const [imageUrl, setImageUrl] = useState("");
const [imageInput, setImageInput] = useState("");
const [tab, setTab] = useState("basic");
const previewRef = useRef();
const fileInputRef = useRef();
const update = useCallback((field, val) => setCard(c => ({ ...c, [field]: val })), []);
const handleImageFile = (e) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (ev) => setImageUrl(ev.target.result);
reader.readAsDataURL(file);
};
const handleImageUrl = () => {
setImageUrl(imageInput.trim());
};
const handlePrint = () => window.print();
const isPW = card.type === "Planeswalker";
const isCreature = card.type === "Creature" || card.type === "Battle";
return (
{/* Header */}
๐
MTG Proxy Maker
Design ยท Print ยท Play
{/* Editor Panel */}
{/* Tabs */}
{["basic", "art", "stats"].map(t => (
))}
{tab === "basic" && <>
update("name", e.target.value)} placeholder="e.g. Blazing Champion" />
update("manaCost", e.target.value)} placeholder="{2}{R}{R}" />
update("subtype", e.target.value)} placeholder="e.g. Wizard" />
{COLORS.map(c => (
))}
update("artist", e.target.value)} placeholder="Artist name" />
>}
{tab === "art" && <>
โ or paste an image URL โ
setImageInput(e.target.value)} placeholder="https://..." />
{imageUrl && (
)}
update("setSymbol", e.target.value)} placeholder="โ
or set code" maxLength={4} />
>}
{tab === "stats" && <>
{isCreature && <>
update("power", e.target.value)} placeholder="4" />
update("toughness", e.target.value)} placeholder="3" />
>}
{isPW && (
update("loyalty", e.target.value)} placeholder="4" />
)}
{!isCreature && !isPW && (
No stats for this card type. Switch to Creature or Planeswalker to edit stats.
)}
Quick Reset
>}
{/* Preview */}
Preview
Use the Print button to print on card stock.
Cut to 2.5" ร 3.5" for standard size.
);
}