// =============================================================================
// HUB — DASHBOARD HOME
// =============================================================================
const { useState: hubUseState, useEffect: hubUseEffect } = React;
const useState = hubUseState, useEffect = hubUseEffect;
const { PROVIDERS:HP, RARITIES:HR, CARDS:HC, CHAMPIONS:HCH, COLLECTIONS:HCL } = window.GAME_DATA;
function Hub({ state, actions }) {
const ownedSet = state.collection;
const packsLeft = state.packsToday;
const totalCards = HC.length;
const ownedCount = Object.keys(ownedSet).filter(k => k.indexOf('myth-') !== 0 && HC.find(c=>c.id===k)).length;
const mythicsOwned = Object.keys(ownedSet).filter(k => window.GAME_DATA.MYTHIC_CARDS[k]).length;
const champsDefeated = state.defeatedChamps.length;
// Collection completion %
const collProgress = HCL.map(coll => {
const total = coll.total;
const have = HC.filter(c => c.provider === coll.provider && ownedSet[c.id]).length;
return { ...coll, have, pct: total ? Math.round(100*have/total) : 0, complete: have === total };
});
return (
{/* HERO ROW */}
◈ SLOT LEGENDS / EDICIÓN ARQUETIPOS · v1.0
COLECCIONA.
DESAFÍA.
DOMINA.
50 cartas arquetípicas. 5 colecciones por estudio. 5 campeones.
Abre sobres cada día, completa las series y derrota a los maestros
para obtener cartas MÍTICAS que ningún sobre te dará.
actions.go('packs')}>
{packsLeft>0 ? `▸ ABRIR SOBRE (${packsLeft} disponibles)` : 'SIN SOBRES — VUELVE EN 24H'}
actions.go('champions')}>
◆ DESAFIAR CAMPEÓN
DAILY · SOBRES
{[...Array(2)].map((_, i) => (
))}
{/* KPI ROW */}
Cartas en colección
{ownedCount}/ {totalCards}
{Math.round(100*ownedCount/totalCards)}% completada
Colecciones completas
{collProgress.filter(c=>c.complete).length}/ 5
proveedores dominados
Campeones derrotados
{champsDefeated}/ 5
desafíos completados
Cartas míticas
{mythicsOwned}/ 5
solo vía desafío
{/* COLLECTIONS PREVIEW */}
02 / COLECCIONES
Progreso por proveedor
{collProgress.map(coll => {
const p = HP[coll.provider];
return (
actions.go('collection')}>
{coll.complete ? '✓ COMPLETA' : `${coll.have}/${coll.total}`}
{p.name}
{p.tagline}
{coll.pct}%
{coll.complete ? '◆ +1500 CRD reclamado' : `${coll.total - coll.have} restantes`}
);
})}
{/* CHAMPIONS PREVIEW */}
03 / CAMPEONES
Maestros · Duelo de cartas
{HCH.map((champ, i) => {
const defeated = state.defeatedChamps.includes(champ.id);
const required = i; // each requires previous
const locked = i > 0 && !state.defeatedChamps.includes(HCH[i-1].id);
return (
!locked && !defeated && actions.battle(champ.id)} />
);
})}
);
}
function ChampionCard({ champion, defeated, locked, onClick }) {
const c = champion;
return (
BOSS · {String(c.difficulty).padStart(2,'0')}/05
{defeated ? '✓ CLEAR' : locked ? '◇ LOCK' : '◆ OPEN'}
[{c.sigil}]
{c.name}
{c.title}
"{c.quote}"
{[...Array(5)].map((_, j) => (
))}
+{c.reward.credits.toLocaleString()} CRD · 1× MÍTICA
);
}
function NextReset({ resetAt, onReset }) {
const [now, setNow] = useState(Date.now());
useEffect(() => {
const id = setInterval(() => setNow(Date.now()), 1000);
return () => clearInterval(id);
}, []);
if (!resetAt) return recarga al recargar
;
const remain = Math.max(0, resetAt - now);
if (remain === 0) {
return ◆ RECARGAR SOBRES ;
}
const h = Math.floor(remain / 3.6e6);
const m = Math.floor((remain % 3.6e6) / 6e4);
const s = Math.floor((remain % 6e4) / 1000);
return (
{String(h).padStart(2,'0')}:{String(m).padStart(2,'0')}:{String(s).padStart(2,'0')}
SKIP
);
}
window.Hub = Hub;