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 ? ( card art ) : (
{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 => ( ))}