From 52a0e3160dcb74776cec7768cbaf6c8199eee1cd Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Wed, 3 Jun 2026 14:54:41 +0200 Subject: [PATCH] Stage 7 polish (round 2): layout/zoom/tab-bar/hint/check fixes - nav bar grows ONLY in game (other screens: minimal nav, content fills); tab bar always bottom - tab bar: tighter icon/label spacing, bigger icons, hint badge on the icon corner - board zoom reworked to width-based (real native scroll, fixes Safari/Chrome) + constant cqw labels; pinch & swipe-to-history dropped (conflict), double-tap kept, history via menu - beginner bonus labels shrunk to fit cells - Draw opens exchange directly (no confirm); confirm popovers restyled like the hamburger dropdown (vertical); removed the floating direction toggle - pending tiles darker bg (no outline); last-word dark-tile highlight (static / 1s flash) - check button disabled for <2/>15 chars, already-checked, or 5s cooldown - global user-select:none (inputs exempt); docs updated; TODO-4 alphabet-on-wire --- PLAN.md | 13 ++- docs/UI_DESIGN.md | 29 +++--- ui/src/app.css | 17 +++- ui/src/components/Header.svelte | 14 ++- ui/src/components/HoldConfirm.svelte | 9 +- ui/src/components/Screen.svelte | 9 +- ui/src/game/Board.svelte | 143 +++++++++++---------------- ui/src/game/Game.svelte | 109 ++++++++------------ 8 files changed, 161 insertions(+), 182 deletions(-) diff --git a/PLAN.md b/PLAN.md index f6fc884..75d4766 100644 --- a/PLAN.md +++ b/PLAN.md @@ -529,8 +529,10 @@ Open details: deployment target/host; dashboards; load expectations. pinned to the bottom) + a one-line **announcement banner** (client-side mock rotation now; server-driven channel later β€” Β§10); a mobile-OS **tab bar** and a reusable **HoldConfirm** press-and-hold control (MakeMove 🏁 + game-action confirms); - board **zoom reworked** to a fixed viewport with counter-scaled labels, corner-letter - tiles, contrasting grid lines, and a Settings **bonus-label style** (beginner/ + board **zoom reworked** to a width-based zoom in a fixed viewport (real native + scroll, double-tap; pinch/swipe dropped) with constant `cqw` labels, corner-letter + tiles, contrasting grid lines, last-word dark-tile highlight, and a Settings + **bonus-label style** (beginner/ classic/none); **hint lays its tiles on the board** (no spend when no move β€” a new `no_hint_available` result code); the history opens as an in-place **slide-down** (not a modal); word-check is alphabet/length-limited, cached and throttled. Design @@ -561,3 +563,10 @@ Open details: deployment target/host; dashboards; load expectations. row behind. Add a periodic reaper (or a finished-and-idle sweep) that deletes guest accounts with no active games once their last session is gone; the `ON DELETE CASCADE` foreign keys clean up the dependent rows. +- **TODO-4 β€” put the per-game alphabet on the wire (owner's idea, Stage 7).** Today the + client hardcodes each variant's letters/values (ported into `ui/src/lib/premiums.ts` + from `scrabble-solver/rules/rules.go`) and the edge exchanges plays/hints by concrete + letters. Consider extending `game.state` to carry the variant's `(letter, index, + value)` table so the UI stops duplicating it, and optionally moving tile exchange to + letter **indices** end-to-end. Caveat (as for the dictionaries, TODO-2): the wire table + must stay pinned to the same `rules.Alphabet` the engine uses, or indices drift. diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md index 78ba6a3..719a251 100644 --- a/docs/UI_DESIGN.md +++ b/docs/UI_DESIGN.md @@ -10,11 +10,12 @@ emoji glyphs. Tokens are CSS custom properties (`ui/src/app.css`), light/dark vi ## Layout shell (`components/Screen.svelte`) -A mobile-app feel: the screen is a full-height flex column where the **nav bar grows** -to absorb spare vertical space (its buttons stay top-aligned) and everything else β€” -the announcement strip, the content, and the optional bottom tab bar β€” **pins to the -bottom**, the strip directly above the content. Tall content scrolls within the content -region. Every screen except Login uses `Screen`. +A full-height flex column: the nav bar, the announcement strip, the content, and an +optional bottom tab bar (the tab bar always sits at the screen bottom). On most screens +the nav is minimal and the **content fills** between nav and tab bar. **Only in the +game** (`growNav`) does the nav bar grow to absorb spare height (buttons top-aligned), +pinning the board and controls to the **bottom** for thumb reach. Every screen except +Login uses `Screen`. ## Navigation @@ -31,12 +32,18 @@ region. Every screen except Login uses `Screen`. - **Tiles**: the letter sits in the **top-left** corner (offset a touch more than the value), the point value bottom-right; blanks show no value. -- **Board zoom** (`Board.svelte`): a two-state zoom (full 15Γ—15 ↔ ~9 cells) via - `transform: scale()` on an inner layer inside a **fixed-size viewport** (the page never - reflows; the viewport scrolls when zoomed), with a smooth transition. Cell/tile **text - lives in a counter-scaled layer** (`scale(1/z)`) sized in `cqw`, so labels stay a - constant size (relatively smaller at higher zoom). On touch, attempting to place a tile - auto-zooms in centred on the target; double-tap and pinch toggle. +- **Board zoom** (`Board.svelte`): a two-state zoom (full 15Γ—15 ↔ ~9 cells) by **growing + the board's width** inside a fixed-size viewport (a real layout change β†’ native scroll + that works consistently across browsers; no `transform`, which broke scrolling + differently in Safari/Chrome). Labels are sized in `cqw` against the fixed viewport, so + they stay a constant size as the cells grow (relatively smaller at higher zoom). + **Double-tap** toggles zoom and, on touch, placing a tile auto-zooms in centred on the + target; the custom pinch and swipe-to-open-history gestures were dropped because they + fight native scroll β€” history opens from the menu. +- **Highlights**: pending tiles use a slightly darker tile background (no outline). The + last completed word gets a dark tile background β€” static while it is the opponent's + turn (our word), and a 1 s flash when it is our turn (their word). While placing, only + the pending tiles are highlighted. - **Bonus-square labels** β€” a Settings choice (`boardlabels.ts`): `beginner` shows a split `3Γ—` / `word` (localized слово/Π±ΡƒΠΊΠ²Π°), `classic` a single `3W` / `3Π‘`, `none` nothing. Default **beginner**. diff --git a/ui/src/app.css b/ui/src/app.css index 79623ef..e796d77 100644 --- a/ui/src/app.css +++ b/ui/src/app.css @@ -27,8 +27,8 @@ --tile-bg: #f4e2b8; --tile-edge: #d8c190; --tile-text: #2a2113; - --tile-pending: #ffe7a3; - --tile-recent: #fff6d8; + --tile-pending: #f2cf73; + --tile-recent: #c8a85c; --prem-tw: #e06a5b; /* triple word */ --prem-dw: #efa6a0; /* double word + centre */ --prem-tl: #4f8fd6; /* triple letter */ @@ -66,8 +66,8 @@ --tile-bg: #d9c79a; --tile-edge: #b6a473; --tile-text: #20190d; - --tile-pending: #f0d98f; - --tile-recent: #4a4636; + --tile-pending: #d8b75e; + --tile-recent: #7a6638; --prem-tw: #b1493d; --prem-dw: #8c5450; --prem-tl: #34608f; @@ -130,6 +130,15 @@ body { #app { height: 100%; + /* No text selection anywhere by default; inputs opt back in below. */ + user-select: none; + -webkit-user-select: none; +} + +input, +textarea { + user-select: text; + -webkit-user-select: text; } button { diff --git a/ui/src/components/Header.svelte b/ui/src/components/Header.svelte index 28b55c3..f95ff0c 100644 --- a/ui/src/components/Header.svelte +++ b/ui/src/components/Header.svelte @@ -2,10 +2,11 @@ import type { Snippet } from 'svelte'; import { navigate } from '../lib/router.svelte'; - let { title, back, menu }: { title: string; back?: string; menu?: Snippet } = $props(); + let { title, back, menu, grow = false }: { title: string; back?: string; menu?: Snippet; grow?: boolean } = + $props(); -