/* global React */ // Pixel-art card — canvas background + chamfered SVG panels + bitmap fonts. // Card is 400×512 final, painted at 100×128 native and upscaled 4× pixelated. const { useRef, useEffect, useMemo } = React; // ============ Palettes (limited, per-rarity) ============ const RARITIES = { comum: { label: "COMUM", bgImage: "assets/bg-comum.png", frameOuter: "#2e2e34", frameGold: "#c8c8d0", frameInner: "#5a5a64", namePlateBg:"#f4ecd8", namePlateEdge:"#2e2e34", nameText: "#2a2a32", statPanelBg:"#22222a", statPanelEdge:"#0e0e14", panelLine: "#c8c8d0", statLabel: "#c8c8d0", value: "#f4f4f8", icon: "#f4ecd8", divider: "#0e0e14", badgeBg: "#22222a", badgeBorder:"#c8c8d0", badgeText: "#f4ecd8", panelBg: "#22222a", panelEdge: "#0e0e14", }, raro: { label: "RARO", bgImage: "assets/bg-raro.png", frameOuter: "#062448", frameGold: "#f8d860", frameInner: "#1a52b8", namePlateBg:"#f4ecd8", namePlateEdge:"#062448", nameText: "#0a2068", statPanelBg:"#0c2a78", statPanelEdge:"#04143a", panelLine: "#f8d860", statLabel: "#f8d860", value: "#fff8d0", icon: "#f8d860", divider: "#04143a", badgeBg: "#0c2a78", badgeBorder:"#f8d860", badgeText: "#fff8d0", panelBg: "#0c2a78", panelEdge: "#04143a", }, epico: { label: "ÉPICO", bgImage: "assets/bg-epico.png", frameOuter: "#1e0438", frameGold: "#f4d060", frameInner: "#5a1c92", namePlateBg:"#f4ecd8", namePlateEdge:"#1e0438", nameText: "#2a0a55", statPanelBg:"#2a0a55", statPanelEdge:"#0a0218", panelLine: "#f4d060", statLabel: "#f4d060", value: "#fff8e0", icon: "#f4d060", divider: "#0a0218", badgeBg: "#2a0a55", badgeBorder:"#f4d060", badgeText: "#fff8e0", panelBg: "#2a0a55", panelEdge: "#0a0218", }, lendario: { label: "LENDÁRIO", bgImage: "assets/bg-lendario.png", frameOuter: "#2a1404", frameGold: "#f8d860", frameInner: "#a06820", namePlateBg:"#f4ecd8", namePlateEdge:"#2a1404", nameText: "#3a2008", statPanelBg:"#3a2008", statPanelEdge:"#150804", panelLine: "#f8d860", statLabel: "#f8d860", value: "#fff8d0", icon: "#f8d860", divider: "#150804", badgeBg: "#3a2008", badgeBorder:"#f8d860", badgeText: "#fff8d0", panelBg: "#3a2008", panelEdge: "#150804", }, }; // ============ Background (uses static images) ============ function PixelBackground({ rarity }) { const p = RARITIES[rarity]; return ( ); } // ============ Pixel-art icons (12×12 grid) ============ // Each icon is a bitmap as array of strings. '.' = transparent, '#' = filled const ICON_BITMAPS = { trophy: [ "............", "..########..", "..#......#..", ".##......##.", ".#.######.#.", ".#.######.#.", ".##......##.", "..#.####.#..", "...#....#...", "...######...", "..########..", "............", ], star: [ "............", ".....##.....", ".....##.....", "....####....", ".##########.", "..########..", "...######...", "..##....##..", ".##......##.", "............", "............", "............", ], ball: [ "............", "...######...", "..#.####.#..", ".##.####.##.", ".#..####..#.", ".########.#.", ".#.######.#.", ".##.####.##.", "..#.####.#..", "...######...", "............", "............", ], crown: [ "............", ".#..........", ".##....##..#", ".#.#..#..#.#", ".#..##.#..##", ".#######..#.", ".##########.", ".##.####.##.", ".##########.", "............", "............", "............", ], people: [ "............", "...##....##.", "..####..####", "..####..####", "...##....##.", "............", ".########..#", "##......####", "##......##.#", "##......##.#", "############", "............", ], shield: [ "............", "..########..", ".##########.", ".##########.", ".##.....###.", ".###...###..", ".####.####..", "..########..", "...######...", "....####....", ".....##.....", "............", ], calendar: [ "............", "..##....##..", "..##....##..", "############", "#..........#", "############", "#..#..#..#.#", "#..#..#..#.#", "#..........#", "############", "............", "............", ], flag: [ "............", ".#..........", ".#######....", ".#.....##...", ".#......#...", ".#.....##...", ".#######....", ".#..........", ".#..........", ".#..........", "............", "............", ], boot: [ "............", "............", "....#####...", "...##...##..", "..##.....##.", "..#.......##", "..#.......##", "############", ".##########.", "............", "............", "............", ], whistle: [ "............", "............", "...####.....", "..######.###", ".#####.#.###", ".#####.#####", "..######.###", "...####.....", "............", "............", "............", "............", ], position: [ "............", "....####....", "..########..", ".##......##.", ".#...##...#.", ".#..####..#.", ".#...##...#.", ".##......##.", "..########..", "....####....", "............", "............", ], bolt: [ "............", "......###...", ".....####...", "....####....", "...####.....", "..########..", "...#####....", "...####.....", "..####......", ".####.......", "############", "............", ], goal: [ "............", "############", "#..#..#..#.#", "#..#..#..#.#", "############", "#..#..#..#.#", "#..#..#..#.#", "############", "#..........#", "############", "............", "............", ], cards: [ "............", "..######....", ".#......#...", ".#......#...", ".#......#.##", ".#......##.#", ".#######.#.#", "..####...#.#", ".......#..##", ".......####.", "............", "............", ], }; const ICON_NAMES = Object.keys(ICON_BITMAPS); // Renders a pixel icon at a given pixel size (each '#' becomes a SIZE×SIZE square) function PixelIcon({ name, color, pixelSize = 2 }) { const map = ICON_BITMAPS[name] || ICON_BITMAPS.star; const w = map[0].length * pixelSize; const h = map.length * pixelSize; return ( {map.map((row, y) => row.split("").map((c, x) => c === "#" ? : null ) )} ); } // ============ Pixel frame (chunky gold, double-line) ============ function PixelFrame({ rarity }) { const p = RARITIES[rarity]; // Native 100×128 viewBox, pixel-perfect rings. // Frame layers from outermost to innermost: // 0-1: outer dark line (1 native px) // 1-5: thick gold band (4 native px) // 5-6: inner dark line (1 native px) // Total: 6 native px = 24 upscaled px const outer = "M0,0 H100 V128 H0 Z"; const r1 = "M1,1 H99 V127 H1 Z"; const r5 = "M5,5 H95 V123 H5 Z"; const r6 = "M6,6 H94 V122 H6 Z"; return ( {/* outer dark line */} {/* gold band */} {/* gold subtle shading — a darker stripe inside the band gives bevel */} {/* inner dark line */} ); } // ============ Pixel corner brackets (simple L, like reference) ============ function PixelCornerOrnaments({ rarity }) { const p = RARITIES[rarity]; // Simple 4×4 native L-bracket: just a corner accent const Bracket = () => ( {/* horizontal bar */} {/* vertical bar */} ); return ( ); } // ============ Chamfered pixel panel ============ function PixelPanel({ x, y, w, h, bg, line, edge, children, style }) { // x,y,w,h in upscaled px (400×512 space). // Renders an SVG chamfered rect behind the children, then absolute children on top. const cx = 6; // chamfer in px (= 1.5 native, but visually 6px reads well) return (
{/* dark edge */} {/* gold line */} {/* inner fill */}
{children}
); } // ============ Rarity badge (small chamfered panel top-left) ============ function RarityBadge({ rarity }) { const p = RARITIES[rarity]; const label = p.label; // Approximate badge width based on label length (Press Start 2P ~10px per char at 10px size) const w = 24 + label.length * 12; return (
{label}
); } // ============ Stat cell ============ function StatCell({ icon, label, value, rarity, isLast }) { const p = RARITIES[rarity]; return (
{label}
{value}
{!isLast && (
)}
); } // ============ Flag thumbnail (with chamfered border) ============ function FlagThumb({ src, rarity }) { const p = RARITIES[rarity]; return (
{src ? ( flag ) : (
FLAG
)}
); } // ============ Stat row (vertical list mode — flowing text) ============ function StatRow({ icon, label, value, rarity, isLast, rowHeight }) { const p = RARITIES[rarity]; return (
{label}
{value && (
{value}
)}
); } // ============ Main Card ============ function Figurinha({ data, ariaId }) { const { rarity = "comum", name = "BRASIL", subtitle = "", image = null, flag = null, stats = [], photoLayout = false, hideFlag = false, } = data || {}; const p = RARITIES[rarity]; // Layout dimensions (in 400×512 space) // PHOTO MODE: full-bleed image (352×240), bigger name plate with subtitle, vertical-list stats // CLASSIC MODE: centered illustration with margins, horizontal stat cells const layout = photoLayout ? { imgX: 24, imgY: 24, imgW: 352, imgH: 240, nameY: 272, nameH: 64, statsY: 344, statsH: 144, listStats: true } : { imgX: 24, imgY: 64, imgW: 352, imgH: 248, nameY: 320, nameH: 56, statsY: 388, statsH: 92, listStats: false }; const hasSubtitle = !!(subtitle && subtitle.trim()); // Name font sizing — accounts for subtitle line + flag overhang const reservedRight = hideFlag ? 0 : 60; const reservedLeft = hideFlag ? 0 : 0; const usableW = 360 - 24 - reservedRight - reservedLeft; const nameFontSize = hasSubtitle ? (name.length <= 14 ? 17 : name.length <= 20 ? 14 : name.length <= 26 ? 11 : 9) : (name.length <= 6 ? 24 : name.length <= 9 ? 20 : name.length <= 14 ? 16 : 13); const rowH = stats.length > 0 ? Math.floor(layout.statsH / stats.length) : layout.statsH; return (
{/* canvas background */}
{/* image area (full-bleed in photo mode, contained in classic) */}
{image ? ( {name} ) : (
{photoLayout ? ( UPLOAD
352 × 240
(ou 1056 × 720)
) : ( ARTE
CENTRAL
)}
)}
{/* frame */} {/* badge */} {/* name plate (cream/beige with dark text) */}
{!hideFlag && (
)}
{name}
{hasSubtitle && (
{subtitle}
)}
{/* stats panel */}
{stats.length === 0 ? (
SEM ESTATÍSTICAS
) : layout.listStats ? ( stats.map((s, i) => ( )) ) : ( stats.map((s, i) => ( )) )}
); } // Exports window.Figurinha = Figurinha; window.RARITIES = RARITIES; window.ICON_NAMES = ICON_NAMES; window.Icons = ICON_BITMAPS; // for the editor preview window.PixelIcon = PixelIcon;