Games batch: neutral words/themes, Word Search sizes + per-word colours

From playtesting findings:
* Pools nearly doubled (115/104 → 228/201) with calm/neutral everyday words
  (claps, dance, drench, beach…), not just strictly-upbeat ones — more variety,
  ~7-month runway. The post-solve "why" prompt reworded to fit neutral words.
* Word Search now stores one theme + word list per day; the grid is built per
  request for three SIZE tiers — Small (8×8, 6 words), Medium (11×11, 9),
  Large (14×14, 13). Large packs more words = a longer sit ("too fast" fix).
  All sizes share the day's theme; every size still code-placed + solvable.
* Word Search themes can now be neutral everyday scenes ("Around the house",
  "At the beach", "In the kitchen", "A walk outdoors", "Making music"…), not
  only hopeful — same shape as the articles.
* Each found word gets its own colour from a calm palette, in the grid and its
  word-list chip. Per-size local progress + best time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-10 20:32:53 -04:00
parent 33d5d55c33
commit f43f645d69
6 changed files with 376 additions and 112 deletions
+20 -7
View File
@@ -6,9 +6,10 @@
let view = $state('hub'); // 'hub' | 'word' | 'wordsearch'
let variant = $state('5');
let wsSize = $state('med');
let date = $state('');
let wordStatus = $state({ 5: null, 6: null }); // null | {status, tries, max}
let wsStatus = $state(null); // null | {status, found, total, ms}
let wsStatus = $state(null); // null | {status, found, ms}
function readStatus(v) {
try {
@@ -20,11 +21,18 @@
return null;
}
function readWs() {
try {
const s = JSON.parse(localStorage.getItem(`goodnews:wordsearch:${date}`) || 'null');
if (s && Array.isArray(s.found)) return { status: s.status, found: s.found.length, ms: s.ms };
} catch { /* ignore */ }
return null;
let inProgress = null;
for (const sz of ['small', 'med', 'large']) {
try {
const s = JSON.parse(localStorage.getItem(`goodnews:wordsearch:${sz}:${date}`) || 'null');
if (s && Array.isArray(s.foundWords)) {
const st = { status: s.status, found: s.foundWords.length, ms: s.ms };
if (st.status === 'done') return st; // prefer a finished one
if (st.found > 0 && !inProgress) inProgress = st;
}
} catch { /* ignore */ }
}
return inProgress;
}
function refreshStatus() {
wordStatus = { 5: readStatus('5'), 6: readStatus('6') };
@@ -104,7 +112,12 @@
</div>
<WordGame {variant} onstatus={refreshStatus} />
{:else if view === 'wordsearch'}
<WordSearchGame onstatus={refreshStatus} />
<div class="variant">
<button class="vchip" class:on={wsSize === 'small'} onclick={() => (wsSize = 'small')}>Small<span>cosy · 6 words</span></button>
<button class="vchip" class:on={wsSize === 'med'} onclick={() => (wsSize = 'med')}>Medium<span>9 words</span></button>
<button class="vchip" class:on={wsSize === 'large'} onclick={() => (wsSize = 'large')}>Large<span>a longer sit · 13 words</span></button>
</div>
<WordSearchGame size={wsSize} onstatus={refreshStatus} />
{/if}
</main>