Word Search mobile: focused viewport, theme placement, unique-per-size words
Per field feedback. * Each day is now THREE distinct puzzles: the three sizes draw DISJOINT word slices from a date-shuffled pool (small/med/large = 6/9/13, sum 28 unique). Curated fallback themes expanded to 30 words each; LLM proposals accepted only if they supply >= 28 unique words, else fall back. No more repeats across sizes. * Word Search is now a focused game screen on mobile (same as Daily Word): body scroll locked + footer hidden (generalized .playing-game), and the grid sizes to the largest square that fits between the theme and the palette (container query) — the whole puzzle is on screen, no page scroll. * Theme placement: full "Today's theme · <name>" on the size-selection screen; just the theme name on the puzzle itself, saving vertical space for Large. * cosy → cozy. 🇺🇸 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -101,24 +101,27 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Daily Word on mobile = a focused viewport: lock scroll + hide footer. Cleanup
|
||||
// Any puzzle 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.
|
||||
$effect(() => {
|
||||
if (typeof document === 'undefined') return;
|
||||
if (view === 'play' && game === 'word') {
|
||||
document.documentElement.classList.add('playing-word');
|
||||
return () => document.documentElement.classList.remove('playing-word');
|
||||
if (view === 'play') {
|
||||
document.documentElement.classList.add('playing-game');
|
||||
return () => document.documentElement.classList.remove('playing-game');
|
||||
}
|
||||
});
|
||||
|
||||
const WS_OPTS = [
|
||||
['small', 'Small', 'cosy · 8×8'],
|
||||
['small', 'Small', 'cozy · 8×8'],
|
||||
['med', 'Medium', 'balanced · 11×11'],
|
||||
['large', 'Large', 'a longer sit · 14×14'],
|
||||
];
|
||||
|
||||
let wsTheme = $state('');
|
||||
|
||||
onMount(async () => {
|
||||
try { date = (await getJSON('/api/puzzle/word?variant=5')).date; } catch { /* offline */ }
|
||||
try { wsTheme = (await getJSON('/api/puzzle/wordsearch?variant=med')).theme; } catch { /* offline */ }
|
||||
refreshStatus();
|
||||
});
|
||||
// Refresh hub/selection statuses whenever we land on a screen (incl. Back).
|
||||
@@ -140,7 +143,7 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container page" class:gameview={view === 'play' && game === 'word'}>
|
||||
<main class="container page" class:gameview={view === 'play'}>
|
||||
{#if view === 'hub'}
|
||||
<h1>Play</h1>
|
||||
<p class="sub">A small calm thing after the brief. One of each a day — no rush, no score to beat but your own.</p>
|
||||
@@ -165,6 +168,9 @@
|
||||
|
||||
{:else if view === 'select'}
|
||||
<h1 class="seltitle">{game === 'word' ? 'Daily Word' : 'Word Search'}</h1>
|
||||
{#if game === 'wordsearch' && wsTheme}
|
||||
<p class="wstheme"><span>Today’s theme</span>{wsTheme}</p>
|
||||
{/if}
|
||||
<p class="sub">{game === 'word' ? 'Pick your length.' : 'Pick your size.'}</p>
|
||||
<div class="opts">
|
||||
{#if game === 'word'}
|
||||
@@ -206,6 +212,9 @@
|
||||
h1 { font-size: clamp(2rem, 5vw, 2.6rem); margin: 6px 0 6px; }
|
||||
.seltitle { font-size: clamp(1.7rem, 4.5vw, 2.2rem); }
|
||||
.sub { color: var(--muted); margin: 0 0 24px; max-width: 540px; }
|
||||
.wstheme { font-family: var(--serif); font-size: 1.6rem; color: var(--accent-deep); margin: 4px 0 18px; }
|
||||
.wstheme span { display: block; text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.66rem;
|
||||
font-family: var(--label); color: var(--muted); margin-bottom: 3px; }
|
||||
|
||||
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; }
|
||||
.gamecard {
|
||||
|
||||
Reference in New Issue
Block a user