// ============================================================= // COLLECTION / POKÉDEX — full grid with filters & detail // ============================================================= const { useState: useColState, useMemo: useColMemo } = React; function Collection({ state, onBack }) { const [filterType, setFilterType] = useColState("all"); const [filterRarity, setFilterRarity] = useColState("all"); const [filterGen, setFilterGen] = useColState("all"); const [showOnlyOwned, setShowOnlyOwned] = useColState(false); const [sortBy, setSortBy] = useColState("dex"); const [search, setSearch] = useColState(""); const [searchFocused, setSearchFocused] = useColState(false); const [selected, setSelected] = useColState(null); // Live suggestions for the hero search const suggestions = useColMemo(() => { if (!search.trim()) return []; const q = search.toLowerCase(); return POKEDEX .filter(p => p.name.toLowerCase().includes(q) || String(p.id).includes(q)) .slice(0, 8); }, [search]); const stats = GameEngine.collectionStats(state); const filtered = useColMemo(() => { let list = [...POKEDEX]; if (filterType !== "all") list = list.filter(p => p.types.includes(filterType)); if (filterRarity !== "all") list = list.filter(p => p.rarity === filterRarity); if (filterGen !== "all") list = list.filter(p => filterGen === "1" ? p.id <= 151 : p.id > 151); if (showOnlyOwned) list = list.filter(p => state.collection[p.id]); if (search) { const q = search.toLowerCase(); list = list.filter(p => p.name.toLowerCase().includes(q) || String(p.id).includes(q)); } if (sortBy === "hp") list.sort((a,b) => b.hp - a.hp); else if (sortBy === "rarity") { const order = ["legendary","holo","rare","uncommon","common"]; list.sort((a,b) => order.indexOf(a.rarity) - order.indexOf(b.rarity)); } else list.sort((a,b) => a.id - b.id); return list; }, [filterType, filterRarity, filterGen, showOnlyOwned, sortBy, search, state.collection]); return (
{/* TOP BAR */}
POKÉDEX · BASE DE DATOS
{stats.owned} / {stats.total} REGISTRADOS · {stats.pct}%
{Object.entries(stats.byRarity).map(([k,v]) => (
0 ? RARITY[k].color : "var(--fg-4)", letterSpacing:"0.05em", }}> {RARITY[k].symbol} {v.owned}/{v.total}
))}
{/* FILTER BAR */}
setFilterType("all")} color="cyan">TODOS {Object.entries(TYPE_INFO).map(([k,info]) => ( setFilterType(filterType===k?"all":k)} typeKey={k}> {info.name} ))}
setFilterRarity("all")} color="cyan">TODAS {Object.entries(RARITY).map(([k,r]) => ( setFilterRarity(filterRarity===k?"all":k)} customColor={r.color}> {r.symbol} {r.name} ))}
setFilterGen("all")} color="cyan">I+II setFilterGen("1")} color="cyan">GEN I setFilterGen("2")} color="cyan">GEN II
setShowOnlyOwned(!showOnlyOwned)} color="lime"> {showOnlyOwned ? "✓" : "○"} SOLO MIS CARTAS
{/* HERO SEARCH */}
setSearch(e.target.value)} onFocus={()=>setSearchFocused(true)} onBlur={()=>setTimeout(()=>setSearchFocused(false), 180)} placeholder="Buscar carta por nombre o número de Pokédex..." style={{ fontFamily:"var(--font-sans)", fontSize: 18, fontWeight: 500, padding:"16px 48px 16px 48px", width: "100%", background: search ? "var(--bg-surface)" : "var(--bg-inset)", border:`1px solid ${search ? "var(--accent-cyan)" : "var(--border-solid)"}`, color:"#fff", borderRadius:2, outline:"none", letterSpacing: "0.01em", boxShadow: search ? "0 0 16px var(--accent-cyan-glow), inset 0 0 0 1px rgba(0,240,255,0.1)" : "none", transition: "all 180ms", }} /> {search && ( )} {/* Live suggestions dropdown */} {searchFocused && suggestions.length > 0 && (
{suggestions.map(p => { const owned = state.collection[p.id] || 0; const info = TYPE_INFO[p.types[0]]; return ( ); })}
)} {searchFocused && search.trim() && suggestions.length === 0 && (
∅ NINGUNA CARTA COINCIDE CON "{search}"
)}
{/* GRID */}
MOSTRANDO {filtered.length} ENTRADAS · {filtered.filter(p => state.collection[p.id]).length} EN POSESIÓN
{filtered.map(p => { const count = state.collection[p.id] || 0; return (
setSelected(p)} />
); })} {filtered.length === 0 && (
∅ NO HAY RESULTADOS · ajusta los filtros
)}
{/* DETAIL MODAL */} {selected && ( setSelected(null)} /> )}
); } function FilterGroup({ label, children }) { return (
{label}
{children}
); } function FilterChip({ active, onClick, children, color, typeKey, customColor }) { const c = customColor || (typeKey ? TYPE_INFO[typeKey].color : (color === "cyan" ? "var(--accent-cyan)" : color === "lime" ? "var(--accent-lime)" : "var(--accent-orange)")); return ( ); } function DetailModal({ pokemon, count, onClose }) { const info = TYPE_INFO[pokemon.types[0]]; return (
e.stopPropagation()} data-r="detail-modal" style={{ background:"var(--bg-base)", border:`1px solid ${info.color}`, borderRadius: 4, padding: 32, maxWidth: 900, width:"100%", display:"grid", gridTemplateColumns:"auto 1fr", gap:32, position:"relative", boxShadow: `0 0 60px ${info.glow}, var(--elev-offset-2)`, }}>
#{String(pokemon.id).padStart(3,"0")} · {pokemon.types.map(t=>TYPE_INFO[t].name.toUpperCase()).join(" / ")} · GEN {pokemon.id <= 151 ? "I" : "II"}

{pokemon.name}

"{pokemon.flavor}"
0 ? "var(--accent-lime)" : "var(--fg-4)"}/>
ATAQUES
{pokemon.attacks.map((atk, i) => (
{atk.cost.map((c,j) => )}
{atk.name}
{atk.note &&
{atk.note}
}
{atk.dmg > 0 ? atk.dmg : "—"}
))}
DEBILIDAD ×2 {TYPE_INFO[pokemon.weak].name}
); } function StatBox({ label, value, sub, accent }) { return (
{label}
{value}
{sub &&
{sub}
}
); } window.Collection = Collection;