// DiveDex — App root with screen routing + XP toast function App({ initialScreen = 'welcome', initialId }) { const { state } = useDiveDex(); // Auth gate: if no user is selected, or the app is explicitly locked, // render the LockScreen instead of the routed screens. All hooks below // still run unconditionally, so this early-return is safe. const showLock = state.locked || !state.activeUserId; const expedition = state.expeditions.find(e => e.id === state.activeExpeditionId) || state.expeditions[0]; const expRegion = expedition ? expedition.region : null; // Background expedition music — controlled here at the App root so it // survives screen navigation (the audio engine de-dupes same-music calls, // so re-firing this effect is a no-op while the same track is playing). // Stops cleanly when the user locks the app or switches off Japan. React.useEffect(() => { if (!window.DiveDexAudio) return; if (!showLock && expRegion === 'japan') { window.DiveDexAudio.playMusic('music.japanExpedition'); } else { window.DiveDexAudio.stopMusic(); } }, [showLock, expRegion]); // Returning user: if they've logged dives already, skip the Welcome intro // and land on the Dashboard. Computed at mount time so there's no flash. const computedInitial = (initialScreen === 'welcome' && state.dives.length > 0) ? 'dashboard' : initialScreen; const [stack, setStack] = React.useState([{ s: computedInitial, id: initialId }]); const cur = stack[stack.length - 1]; const [toast, setToast] = React.useState(null); const nav = (s, id) => { if (s === 'back') return setStack(stack.length > 1 ? stack.slice(0, -1) : stack); setStack([...stack, { s, id }]); }; const setTab = (s) => setStack([{ s }]); // Clear any in-flight toast on screen change so a stale message can't bleed // into another surface. (Toasts are reserved for real, state-driven events; // there's no longer a fake demo toast on Dashboard mount.) React.useEffect(() => { setToast(null); }, [cur.s]); const tabFor = (s) => ({ welcome: 'levels', levels: 'levels', dashboard: 'levels', log: 'log', sightings: 'log', 'edit-dive': 'log', 'log-similar': 'log', dex: 'dex', creature: 'dex', team: 'team', achievements: 'team', sidequests: 'team', memories: 'memories', story: 'memories', recap: 'levels', settings: 'levels', trends: 'levels', boutique: 'gear', item: 'gear', locker: 'gear', prestige: 'gear', }[s] || 'levels'); const tab = tabFor(cur.s); const onTab = (id) => { if (id === 'levels') setTab('dashboard'); else if (id === 'log') setTab('log'); else if (id === 'dex') setTab('dex'); else if (id === 'team') setTab('team'); else if (id === 'memories') setTab('memories'); else if (id === 'gear') setTab('boutique'); }; const hideNav = cur.s === 'welcome'; let screen = null; switch (cur.s) { case 'welcome': screen = ; break; case 'levels': screen = ; break; case 'dashboard': screen = ; break; case 'log': screen = ; break; case 'edit-dive': screen = ; break; case 'log-similar': screen = ; break; case 'recap': screen = ; break; case 'dex': screen = ; break; case 'creature': screen = ; break; case 'team': screen = ; break; case 'achievements': screen = ; break; case 'sidequests': screen = ; break; case 'memories': screen = ; break; case 'story': screen = ; break; case 'settings': screen = ; break; case 'trends': screen = ; break; case 'boutique': screen = ; break; case 'item': screen = ; break; case 'locker': screen = ; break; case 'prestige': screen = ; break; default: screen = ; } if (showLock) { return
; } return (
{screen} {!hideNav && } {toast && }
); } function XpToast({ xp, msg, kind = 'aqua' }) { const c = kind === 'gold' ? '#D7B46A' : '#82ECFF'; return (
{msg} +{xp}
); } Object.assign(window, { App, XpToast });