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:
jay
2026-06-11 09:15:06 -04:00
parent 1dda91fd96
commit 52a8bc5326
5 changed files with 101 additions and 57 deletions
@@ -147,18 +147,20 @@
{:else if !words.length}
<p class="muted">Could not load todays word search.</p>
{:else}
<p class="theme"><span class="lbl">Todays theme</span>{theme}</p>
<p class="theme">{theme}</p>
<div class="grid" class:ok={okFlash} class:done={status === 'done'} bind:this={gridEl} style="--n:{n}"
role="application" aria-label="Word search grid — drag across letters to find words"
onpointerdown={down} onpointermove={move} onpointerup={up} onpointercancel={up}>
{#each grid as rowStr, r (r)}
{#each rowStr.split('') as ch, c (c)}
{@const key = r + ',' + c}
<div class="cell" class:sel={selSet.has(key)}
style={cellColor.has(key) && !selSet.has(key) ? `background:${PALETTE[cellColor.get(key)]};color:#2a2f36` : ''}>{ch}</div>
<div class="gridwrap">
<div class="grid" class:ok={okFlash} class:done={status === 'done'} bind:this={gridEl} style="--n:{n}"
role="application" aria-label="Word search grid — drag across letters to find words"
onpointerdown={down} onpointermove={move} onpointerup={up} onpointercancel={up}>
{#each grid as rowStr, r (r)}
{#each rowStr.split('') as ch, c (c)}
{@const key = r + ',' + c}
<div class="cell" class:sel={selSet.has(key)}
style={cellColor.has(key) && !selSet.has(key) ? `background:${PALETTE[cellColor.get(key)]};color:#2a2f36` : ''}>{ch}</div>
{/each}
{/each}
{/each}
</div>
</div>
<div class="palette">
@@ -189,9 +191,8 @@
<style>
.wordsearch { max-width: 520px; margin: 0 auto; opacity: 0; transform: translateY(6px); }
.wordsearch.ready { opacity: 1; transform: none; transition: opacity 0.3s ease, transform 0.3s ease; }
.theme { text-align: center; font-family: var(--serif); font-size: 1.3rem; color: var(--accent-deep); margin: 0 0 14px; }
.theme .lbl { display: block; text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.64rem;
font-family: var(--label); color: var(--muted); margin-bottom: 2px; }
.theme { text-align: center; font-family: var(--serif); font-size: 1.4rem; color: var(--accent-deep); margin: 0 0 12px; }
.gridwrap { display: flex; justify-content: center; }
.grid {
/* Cells are ~32px on a wide screen (board grows with the grid) and shrink via
1fr to fit a narrow phone — capped by max-width so it can never overflow. */
@@ -224,4 +225,21 @@
padding: 11px 26px; font: inherit; font-weight: 600; cursor: pointer; }
.share:hover { background: var(--accent-deep); }
.muted { color: var(--muted); text-align: center; }
/* Mobile: a focused game screen (matches Daily Word). The grid sizes to the
largest square that fits the space left between the theme and the palette,
so the whole puzzle is on screen with no page scroll. */
@media (max-width: 720px) {
.wordsearch { display: flex; flex-direction: column; height: 100%; max-width: 100%; }
.theme { flex-shrink: 0; font-size: 1.2rem; margin: 4px 0 8px; }
.gridwrap { flex: 1; min-height: 0; container-type: size; align-items: center; padding: 2px 0; }
.grid { width: min(100%, 100cqh, calc(var(--n) * 40px)); max-width: none; margin: 0; }
.palette { flex-shrink: 0; margin: 10px auto 0; padding: 9px 12px 10px; max-width: 100%;
width: 100%; box-sizing: border-box; }
.plabel { margin-bottom: 7px; }
.words { gap: 6px; }
.words li { font-size: 0.78rem; padding: 3px 9px; }
.hint { flex-shrink: 0; padding: 9px 0 calc(env(safe-area-inset-bottom) + 6px); }
.result { flex-shrink: 0; padding-bottom: calc(env(safe-area-inset-bottom) + 6px); }
}
</style>