// =============================================================
// 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 (
);
})}
{filtered.length === 0 && (
∅ NO HAY RESULTADOS · ajusta los filtros
)}
{/* DETAIL MODAL */}
{selected && (
setSelected(null)}
/>
)}
);
}
function FilterGroup({ label, children }) {
return (
);
}
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;