Play: Game-Selection step + mobile sticky on-brand keyboard

Mobile polish for the games.
* New flow: Play Hub → Game Selection → Game. The 5/6 (word) and S/M/L (search)
  pickers move OFF the game screen onto a selection screen that shows each
  option's today-status. Back-button reads "Game Selection" in a game and
  "Play Hub" on the selection screen — buys vertical room for the keyboard.
* Daily Word on mobile now fills the height: the board scrolls in the middle and
  the keyboard is pinned at the bottom, always reachable (no scrolling down to
  type). Desktop stays inline.
* Keyboard restyled on-brand: warm cream keys (was cool generic grey), the label
  font, an accent-tinted Enter, and the same green/gold/grey feedback as the
  tiles; full-bleed, tactile press, safe-area aware.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-10 21:58:44 -04:00
parent 98441fae15
commit a52226ce61
2 changed files with 171 additions and 98 deletions
+56 -37
View File
@@ -153,40 +153,44 @@
{#if loading}
<p class="muted">Loading todays puzzle…</p>
{:else}
<div class="board" style="--len:{length}">
{#each Array(maxGuesses) as _, r (r)}
{@const g = guesses[r]}
{@const cs = cols[r] || null}
<div class="row">
{#each Array(length) as _, c (c)}
{@const ch = g ? g[c] : (r === guesses.length ? current[c] : '')}
<div class="tile {cs ? cs[c] : ''}" class:filled={!!ch}>{(ch || '').toUpperCase()}</div>
{/each}
<div class="play-area">
<div class="board" style="--len:{length}">
{#each Array(maxGuesses) as _, r (r)}
{@const g = guesses[r]}
{@const cs = cols[r] || null}
<div class="row">
{#each Array(length) as _, c (c)}
{@const ch = g ? g[c] : (r === guesses.length ? current[c] : '')}
<div class="tile {cs ? cs[c] : ''}" class:filled={!!ch}>{(ch || '').toUpperCase()}</div>
{/each}
</div>
{/each}
</div>
{#if message}<p class="flash">{message}</p>{/if}
{#if status !== 'playing'}
<div class="result rise">
<p class="rmark">{status === 'won' ? '✦ nicely done ✦' : 'todays word:'}
{#if status === 'lost' && answer}<strong>{answer.toUpperCase()}</strong>{/if}</p>
{#if why}<p class="why"><span class="lbl">Why this word</span>{why}</p>{/if}
{#if stats}
<div class="stats">
<div><span class="n">{stats.played}</span><span class="l">played</span></div>
<div><span class="n">{stats.played ? Math.round(100 * stats.won / stats.played) : 0}%</span><span class="l">solved</span></div>
<div><span class="n">{stats.streak}</span><span class="l">streak</span></div>
</div>
{/if}
<button class="share" onclick={share}>{copied ? 'Copied!' : 'Share result'}</button>
</div>
{/each}
{/if}
</div>
{#if message}<p class="flash">{message}</p>{/if}
{#if status !== 'playing'}
<div class="result rise">
<p class="rmark">{status === 'won' ? '✦ nicely done ✦' : 'todays word:'}
{#if status === 'lost' && answer}<strong>{answer.toUpperCase()}</strong>{/if}</p>
{#if why}<p class="why"><span class="lbl">Why this word</span>{why}</p>{/if}
{#if stats}
<div class="stats">
<div><span class="n">{stats.played}</span><span class="l">played</span></div>
<div><span class="n">{stats.played ? Math.round(100 * stats.won / stats.played) : 0}%</span><span class="l">solved</span></div>
<div><span class="n">{stats.streak}</span><span class="l">streak</span></div>
</div>
{/if}
<button class="share" onclick={share}>{copied ? 'Copied!' : 'Share result'}</button>
</div>
{:else}
{#if status === 'playing'}
<div class="keyboard">
{#each ROWS as row, ri (ri)}
<div class="krow">
{#if ri === 2}<button class="key wide" onclick={() => key('enter')}>Enter</button>{/if}
{#if ri === 2}<button class="key wide enter" onclick={() => key('enter')}>Enter</button>{/if}
{#each row as k (k)}
<button class="key {keyState[k] || ''}" onclick={() => key(k)}>{k.toUpperCase()}</button>
{/each}
@@ -217,19 +221,34 @@
text-align: center; background: var(--ink); color: #fff; border-radius: 8px;
padding: 7px 14px; width: fit-content; margin: 0 auto 12px; font-size: 0.86rem;
}
.keyboard { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
.keyboard { display: flex; flex-direction: column; gap: 7px; margin-top: 8px; }
.krow { display: flex; gap: 5px; justify-content: center; }
.key {
flex: 1; min-width: 0; max-width: 42px; height: 52px; border: none; border-radius: 7px;
background: #e4e7ea; color: var(--ink); font-family: var(--label); font-weight: 600;
font-size: 0.92rem; cursor: pointer; text-transform: uppercase;
flex: 1; min-width: 0; max-width: 44px; height: 54px; border: none; border-radius: 8px;
background: #e9e2d4; color: var(--ink); font-family: var(--label); font-weight: 600;
font-size: 1rem; cursor: pointer; text-transform: uppercase;
box-shadow: 0 1.5px 0 rgba(70, 58, 36, 0.16); transition: background 0.12s ease, transform 0.05s ease;
}
.key.wide { max-width: 62px; font-size: 0.7rem; }
.key.correct { background: #4a9d6e; color: #fff; }
.key.present { background: #d8b24a; color: #fff; }
.key.absent { background: #9aa6b2; color: #fff; }
.key:hover { filter: brightness(0.96); }
.key:active { transform: translateY(1px); box-shadow: none; }
.key.wide { max-width: 66px; font-size: 0.68rem; letter-spacing: 0.03em; }
.key.enter { background: var(--accent-soft); color: var(--accent-deep); }
.key.correct { background: #4a9d6e; color: #fff; box-shadow: 0 1.5px 0 rgba(40, 90, 60, 0.32); }
.key.present { background: #d8b24a; color: #fff; box-shadow: 0 1.5px 0 rgba(150, 110, 20, 0.32); }
.key.absent { background: #9aa6b2; color: #fff; box-shadow: 0 1.5px 0 rgba(80, 90, 100, 0.32); }
.key:hover { filter: brightness(0.97); }
.muted { color: var(--muted); text-align: center; }
/* Mobile: fill the height — board scrolls in the middle, keyboard pinned at the
bottom and always reachable (full-bleed, on-brand, safe-area aware). */
@media (max-width: 720px) {
.wordgame { display: flex; flex-direction: column; height: 100%; max-width: 100%; }
.play-area { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column;
justify-content: center; padding: 4px 0; }
.board { margin-bottom: 12px; }
.keyboard { flex-shrink: 0; margin: 6px -16px 0; gap: 6px; background: var(--bg);
border-top: 1px solid var(--line); padding: 9px 6px calc(9px + env(safe-area-inset-bottom)); }
.key { height: 50px; }
}
.result { text-align: center; }
.rmark { font-family: var(--serif); font-style: italic; color: var(--accent-deep); font-size: 1.2rem; margin: 0 0 10px; }
.rmark strong { font-style: normal; letter-spacing: 0.06em; }