// =============================================================================
// APP — Router, state, top bar, toasts, tweaks
// =============================================================================
const { useState, useEffect, useCallback } = React;
const STORAGE_KEY = 'slot-legends-v1';
const DEFAULT_STATE = {
credits: 500,
collection: {}, // cardId → { count, firstSeen }
packsToday: 2,
nextReset: null,
defeatedChamps: [],
claimedCollections: [],
unlockedBadges: [],
};
function loadState() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return { ...DEFAULT_STATE };
return { ...DEFAULT_STATE, ...JSON.parse(raw) };
} catch { return { ...DEFAULT_STATE }; }
}
function saveState(s) {
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(s)); } catch {}
}
function App() {
// --- Tweaks ---
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"packsPerDay": 2,
"packSize": 5,
"showRarityWeights": false,
"bestOf": 5,
"primaryAccent": "cyan",
"demoSeed": false
}/*EDITMODE-END*/;
const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
// --- Routing ---
const [route, setRoute] = useState('hub');
const [routeParam, setRouteParam] = useState(null);
// --- Game state ---
const [state, setState] = useState(() => loadState());
const [toast, setToast] = useState(null);
const [modal, setModal] = useState(null);
// Persist whenever state changes
useEffect(() => { saveState(state); }, [state]);
// First-run demo seed → unlock 12 cards so battle/collection is interesting
useEffect(() => {
if (Object.keys(state.collection).length === 0 && t.demoSeed) {
const seed = ['gates-olympus','sweet-bonanza','starburst','book-of-dead','wanted-dead-wild',
'reactoonz','san-quentin','le-bandit','fortune-tiger','wolf-gold','gemix','beam-boys'];
const coll = {};
seed.forEach(id => { coll[id] = { count: 1, firstSeen: Date.now() }; });
setState(s => ({ ...s, collection: coll }));
}
}, [t.demoSeed]);
const showToast = useCallback((msg, kind='info') => {
setToast({ msg, kind, id: Date.now() });
setTimeout(() => setToast(null), 3000);
}, []);
// --- Actions ---
const actions = {
go: (r, param=null) => { setRoute(r); setRouteParam(param); window.scrollTo(0,0); },
battle: (champId) => { setRoute('battle'); setRouteParam(champId); window.scrollTo(0,0); },
openedPack: (cardIds) => {
setState(s => {
const coll = { ...s.collection };
let newCount = 0;
cardIds.forEach(id => {
if (!coll[id]) { coll[id] = { count: 1, firstSeen: Date.now() }; newCount++;}
else { coll[id] = { ...coll[id], count: coll[id].count + 1 }; }
});
const nextPacks = s.packsToday - 1;
// Check for completed collections
const nextClaimed = [...s.claimedCollections];
const nextCredits = s.credits;
let creditsDelta = 0;
window.GAME_DATA.COLLECTIONS.forEach(col => {
if (s.claimedCollections.includes(col.id)) return;
const have = window.GAME_DATA.CARDS.filter(c => c.provider === col.provider && coll[c.id]).length;
if (have === col.total) {
nextClaimed.push(col.id);
creditsDelta += col.prize.credits;
setTimeout(() => showToast(`◆ COLECCIÓN ${window.GAME_DATA.PROVIDERS[col.provider].name} COMPLETA · +${col.prize.credits} CRD`, 'success'), 200);
}
});
showToast(`+${cardIds.length} cartas (${newCount} nuevas)`, 'info');
const nextReset = nextPacks <= 0 ? (Date.now() + 24*3600*1000) : s.nextReset;
return { ...s, collection: coll, packsToday: nextPacks, credits: nextCredits + creditsDelta, claimedCollections: nextClaimed, nextReset };
});
// Return to hub
setTimeout(() => actions.go('hub'), 200);
},
resetDaily: () => {
setState(s => ({ ...s, packsToday: t.packsPerDay || 2, nextReset: null }));
showToast('◆ SOBRES RECARGADOS', 'success');
},
championDefeated: (champId, reward) => {
setState(s => {
if (s.defeatedChamps.includes(champId)) return s;
const coll = { ...s.collection };
if (reward.card && !coll[reward.card]) {
coll[reward.card] = { count: 1, firstSeen: Date.now() };
}
return {
...s,
defeatedChamps: [...s.defeatedChamps, champId],
credits: s.credits + (reward.credits || 0),
collection: coll,
};
});
showToast(`◆ ¡CAMPEÓN DERROTADO! +${reward.credits} CRD · 1× MÍTICA`, 'success');
},
resetGame: () => {
if (confirm('¿Reiniciar todo el progreso? Se borrarán cartas, créditos y campeones.')) {
setState({ ...DEFAULT_STATE });
showToast('◈ Progreso reiniciado', 'info');
}
},
};
// Render route
let screen = null;
if (route === 'hub') screen =