Play: make Back step through the game screens (URL-driven views)

The Hub → Game Selection → Game screens were internal $state with no history
entries, so the device/browser Back button skipped straight out of /play. Now
the screen is derived from the URL (?game=…&v=…) and forward moves use goto, so
each screen is a real history entry: Back goes Game → Selection → Hub → site,
matching the rest of the app. The in-app Back button uses history.back() so it
mirrors the device button. Statuses refresh on every navigation (incl. Back).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-11 08:50:48 -04:00
parent dc2e382dea
commit d6015dd44e
+18 -10
View File
@@ -1,13 +1,20 @@
<script>
import { onMount } from 'svelte';
import { goto, afterNavigate } from '$app/navigation';
import { page } from '$app/stores';
import { getJSON } from '$lib/api.js';
import WordGame from '$lib/components/WordGame.svelte';
import WordSearchGame from '$lib/components/WordSearchGame.svelte';
let view = $state('hub'); // 'hub' | 'select' | 'play'
let game = $state('word'); // 'word' | 'wordsearch'
let variant = $state('5');
let wsSize = $state('med');
// Screen is derived from the URL so the device/browser Back button steps through
// Hub → Game Selection → Game (each screen is its own history entry), instead of
// jumping straight out of /play.
let sp = $derived($page.url.searchParams);
let game = $derived(sp.get('game') === 'wordsearch' ? 'wordsearch' : 'word');
let view = $derived(!sp.get('game') ? 'hub' : (sp.get('v') ? 'play' : 'select'));
let variant = $derived(sp.get('v') || '5');
let wsSize = $derived(sp.get('v') || 'med');
let date = $state('');
let wordStatus = $state({ 5: null, 6: null });
let wsStatus = $state(null);
@@ -71,8 +78,11 @@
return 'Play';
}
function openGame(g) { game = g; view = 'select'; refreshStatus(); }
function pick(v) { if (game === 'word') variant = v; else wsSize = v; view = 'play'; }
// Forward navigations push a history entry (via goto); the in-app Back button
// pops it (history.back), so it mirrors the device Back button exactly.
function openGame(g) { goto('/play?game=' + g); }
function pick(v) { goto('/play?game=' + game + '&v=' + v); }
function back() { history.back(); }
// Daily Word on mobile = a focused viewport: lock scroll + hide footer. Cleanup
// ALWAYS removes the class (re-run or unmount), so leaving /play can't strand it.
@@ -83,10 +93,6 @@
return () => document.documentElement.classList.remove('playing-word');
}
});
function back() {
view = view === 'play' ? 'select' : 'hub';
refreshStatus();
}
const WS_OPTS = [
['small', 'Small', 'cosy · 8×8'],
@@ -98,6 +104,8 @@
try { date = (await getJSON('/api/puzzle/word?variant=5')).date; } catch { /* offline */ }
refreshStatus();
});
// Refresh hub/selection statuses whenever we land on a screen (incl. Back).
afterNavigate(() => refreshStatus());
</script>
<svelte:head><title>Play · Upbeat Bytes</title></svelte:head>