// ============================================================= // CARD COMPONENT — beautiful Pokémon TCG card // Used everywhere: pack reveal, collection grid, battle field // ============================================================= const { useState, useRef, useEffect } = React; // Energy chip — small circle showing energy type function EnergyChip({ type, size = 18 }) { const info = TYPE_INFO[type] || TYPE_INFO.normal; return ( {info.icon} ); } // Type ribbon at top of card function TypeRibbon({ types }) { return (
{types.map((t,i) => )}
); } // Rarity badge bottom-right function RarityMark({ rarity }) { const r = RARITY[rarity]; return ( {r.symbol} {r.name} ); } // Holographic foil overlay for rare cards function HoloFoil({ rarity, hover }) { if (rarity !== "holo" && rarity !== "legendary") return null; const isLeg = rarity === "legendary"; return (
); } // Main PokemonCard function PokemonCard({ pokemon, size = "md", showBack = false, onClick, glow = false, dim = false, count = 0 }) { const [hover, setHover] = useState(false); const [tilt, setTilt] = useState({ rx: 0, ry: 0 }); const ref = useRef(null); const dims = { xs: { w: 110, h: 154, scale: 0.45 }, sm: { w: 160, h: 224, scale: 0.65 }, md: { w: 220, h: 308, scale: 0.9 }, lg: { w: 280, h: 392, scale: 1.15 }, xl: { w: 340, h: 476, scale: 1.4 }, }[size]; const primaryType = pokemon.types[0]; const info = TYPE_INFO[primaryType]; function onMove(e) { if (!ref.current) return; const rect = ref.current.getBoundingClientRect(); const x = (e.clientX - rect.left) / rect.width; const y = (e.clientY - rect.top) / rect.height; setTilt({ ry: (x - 0.5) * 14, rx: -(y - 0.5) * 14 }); } function onLeave() { setHover(false); setTilt({ rx:0, ry:0 }); } if (showBack) { return (
Reverso
); } const isRareGlow = pokemon.rarity === "holo" || pokemon.rarity === "legendary"; return (
setHover(true)} onMouseLeave={onLeave} onClick={onClick} style={{ width: dims.w, height: dims.h, position:"relative", cursor: onClick ? "pointer" : "default", transform: `perspective(1000px) rotateX(${tilt.rx}deg) rotateY(${tilt.ry}deg) ${hover ? "translateY(-6px)" : ""}`, transformStyle: "preserve-3d", transition: "transform 220ms cubic-bezier(0.16,1,0.3,1)", filter: dim ? "grayscale(1) brightness(0.5)" : "none", opacity: dim ? 0.65 : 1, }} > {/* Outer frame */}
{/* Inner card body */}
{/* Header row: name + HP */}
{pokemon.name}
#{String(pokemon.id).padStart(3,"0")} · GEN {pokemon.id <= 151 ? "I" : "II"}
{pokemon.hp}PS
{/* Image well */}
{pokemon.name}{ e.target.style.display='none'; }} /> {/* Hologram scanlines */}
{/* Attacks */} {size !== "xs" && (
{pokemon.attacks.map((atk, i) => (
{atk.cost.map((c,j) => )}
{atk.name}
0 ? "#fff" : "var(--fg-3)", }}>{atk.dmg > 0 ? atk.dmg : "—"}
))}
)} {/* Footer: weakness / retreat / rarity */} {size === "md" || size === "lg" || size === "xl" ? (
DÉBIL
RETIR {Array.from({length: pokemon.retreat}).map((_,i)=>( ))} {pokemon.retreat === 0 && }
) : null}
{/* Count badge */} {count > 1 && (
×{count}
)}
); } window.PokemonCard = PokemonCard; window.EnergyChip = EnergyChip; window.TypeRibbon = TypeRibbon; window.RarityMark = RarityMark;