diff --git a/CLAUDE.md b/CLAUDE.md index 9cad34b..10e9cf0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,6 +13,7 @@ conversation memory — is the source of continuity. Keep it that way. - [`docs/FUNCTIONAL.md`](docs/FUNCTIONAL.md) (+ [`_ru`](docs/FUNCTIONAL_ru.md) mirror) — per-domain user stories. English authoritative. - [`docs/TESTING.md`](docs/TESTING.md) — test layers + the per-stage CI gate. +- [`docs/UI_DESIGN.md`](docs/UI_DESIGN.md) — the `ui` visual/interaction design system. ## Mandatory per-stage workflow diff --git a/PLAN.md b/PLAN.md index dcd43a5..f6fc884 100644 --- a/PLAN.md +++ b/PLAN.md @@ -40,7 +40,7 @@ independent (see ARCHITECTURE §9.1). | 4 | Lobby & social (matchmaking, friends, block, chat, profile, nudge) | **done** | | 5 | Robot opponent | **done** | | 6 | Gateway edge (Connect/FB, platform auth, sessions, push bridge, admin) | **done** | -| 7 | UI — playable slice (Svelte+Vite, board, lobby, chat, hint/word-check, i18n) | todo | +| 7 | UI — playable slice + UX polish (Svelte+Vite, board, lobby, chat, hint/word-check, i18n) | **done** | | 8 | UI — social/account/history (friends, blocks, invitations, profile edit, stats, history/GCG) | todo | | 9 | Telegram integration (bot side-service, deep-link, push) | todo | | 10 | Admin & dictionary ops (complaint review, version reload) | todo | @@ -525,6 +525,16 @@ Open details: deployment target/host; dashboards; load expectations. **bundle-size budget** — prod is ~67 KB gzip JS — and a chromium e2e). The Go workflows already cover the new backend/gateway/pkg code; a `game.ListForAccount` integration test and gateway transcode tests for the new ops were added. + - **UX polish** (follow-up PR): a mobile-app **app shell** (growing nav bar, content + 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/ + 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 + details live in the new [`docs/UI_DESIGN.md`](docs/UI_DESIGN.md). ## Deferred TODOs (cross-stage) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 81ae482..bc9ca4e 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -38,6 +38,11 @@ Three executables plus per-platform side-services: is a hash router and the session token is held in memory + IndexedDB. A build-flagged in-memory mock transport (`pnpm start`) runs the whole slice with no backend. Embeddable in platform webviews; packageable to native (iOS/Android) via Capacitor. + The client uses a mobile-app shell (a growing nav bar; content pinned to the bottom), + a one-line **announcement banner** under the nav (a client-side mock rotation today — + a server-driven channel later, §10), and a client **board-style** setting (bonus-label + mode). The visual/interaction design system is documented in + [`UI_DESIGN.md`](UI_DESIGN.md). - **`platform/`** *(planned)* — per-platform side-services (Telegram bot first): deep-link invites and platform-native push notifications. They talk to `backend` over an internal API. @@ -209,7 +214,11 @@ Key points: (`hint_balance`, spent after the allowance; top-ups are a later feature). A hint reveals the top-1 ranked move (`GenerateMoves[0]`). The lobby/tournament caller picks the per-game defaults (e.g. one in casual random games, none in - tournaments). + tournaments). The client **lays the hinted tiles onto the board** as a pending + placement and leaves the commit to the player. When the rack has no legal move the + service spends **nothing** and returns `ErrNoHintAvailable` — surfaced as the distinct + result code `no_hint_available` (separate from `hint_unavailable`) so the UI can say + "no options" rather than "no hints left". - **Word-check tool**: unlimited dictionary lookups against the game's pinned dictionary; each result offers a **complaint** (complainant, game, variant, dict_version, word, the disputed result, an optional note) that lands in an @@ -363,6 +372,10 @@ match-found. Out-of-app platform push (your-turn, nudge) is wired in Stage 9; session-revocation events and cursor-based stream resume are deferred (single-instance MVP). +A separate **announcements channel** feeds the client's one-line banner (UI_DESIGN.md). +It is a client-side **mock** rotation today; a server-driven source (operational notices, +promotions) is future work and would deliver short markdown messages (text + links). + ## 11. Observability - Structured logging with `go.uber.org/zap` (JSON). OpenTelemetry tracer and diff --git a/docs/FUNCTIONAL.md b/docs/FUNCTIONAL.md index 261e473..1dc59ed 100644 --- a/docs/FUNCTIONAL.md +++ b/docs/FUNCTIONAL.md @@ -17,7 +17,10 @@ the top-1 hint, the unlimited word-check with complaint, per-game chat and nudge real-time in-app updates, switching interface language (en/ru) and theme, and a read-only profile. Managing friends and blocks, creating friend games (invitations), editing the profile, the statistics screen and the history/GCG viewer arrive in -Stage 8. +Stage 8. Settings also pick the board's bonus-label style (beginner / classic / +none). A hint **lays the suggested tiles on the board** for the player to confirm and +costs nothing when the rack has no legal move. The word-check accepts only the +variant's alphabet, remembers answers within the session and rate-limits repeats. ### Identity & sessions *(Stage 1 / 6)* A player arrives from a platform (Telegram first), via email login, or as an diff --git a/docs/FUNCTIONAL_ru.md b/docs/FUNCTIONAL_ru.md index ee1cd09..ec4efd1 100644 --- a/docs/FUNCTIONAL_ru.md +++ b/docs/FUNCTIONAL_ru.md @@ -16,7 +16,11 @@ top-1 подсказку, безлимитную проверку слова с обновления в реальном времени, переключение языка интерфейса (en/ru) и темы и профиль только для чтения. Управление друзьями и блоками, создание дружеских игр (приглашения), редактирование профиля, экран статистики и просмотр истории/GCG -появятся в Stage 8. +появятся в Stage 8. В настройках также выбирается стиль подписей бонус-клеток +(новичок / классика / без текста). Подсказка **выставляет предложенные фишки на +доску** — игрок сам решает сделать ход, и подсказка не тратится, если ходов нет. +Проверка слова принимает только алфавит варианта, запоминает ответы в рамках сессии +и ограничивает частоту повторов. ### Личность и сессии *(Stage 1 / 6)* Игрок приходит с платформы (сначала Telegram), через email-вход или как diff --git a/docs/UI_DESIGN.md b/docs/UI_DESIGN.md new file mode 100644 index 0000000..78ba6a3 --- /dev/null +++ b/docs/UI_DESIGN.md @@ -0,0 +1,77 @@ +# Scrabble Game — UI design system + +Visual and interaction conventions for the `ui` client. Behaviour lives in +[`FUNCTIONAL.md`](FUNCTIONAL.md); cross-service architecture (including the global +points this doc references) lives in [`ARCHITECTURE.md`](ARCHITECTURE.md). The client +is **pure HTML5/CSS + Unicode** — no image/font/SVG assets; icons are CSS shapes or +emoji glyphs. Tokens are CSS custom properties (`ui/src/app.css`), light/dark via +`prefers-color-scheme` or an explicit Settings choice, and **Telegram-themeParams-ready** +(the tokens can be overridden at runtime). + +## 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`. + +## Navigation + +- **Back**: a thin, compact `<` drawn from two rotated CSS borders (`Header.svelte` + `.chev`) — lighter than a glyph. +- **Hamburger**: a CSS three-bar (`Menu.svelte`), deliberately larger; opens a dropdown + of items (lobby: Profile/Settings/About; game: History/Chat/Check word/Drop game). +- **Tab bar** (`TabBar.svelte`): square, borderless, evenly distributed buttons — a large + emoji icon over a tiny truncated label. A press highlights a rounded **square** behind + the icon (slightly larger than it) until release; spacing keeps adjacent labels from + touching. No text selection on nav / tab-bar / buttons (`user-select: none`). + +## Tiles & board + +- **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. +- **Bonus-square labels** — a Settings choice (`boardlabels.ts`): `beginner` shows a + split `3×` / `word` (localized слово/буква), `classic` a single `3W` / `3С`, `none` + nothing. Default **beginner**. +- **Grid lines**: the inter-cell gap shows a contrasting `--cell-line` (darker in light, + lighter in dark) to avoid a wavy-line optical illusion. + +## Controls + +- **HoldConfirm** (`components/HoldConfirm.svelte`): the shared press-and-hold control. A + short tap opens a small popover above the button; a ~0.7 s hold runs the primary action + immediately. Reused by: + - **MakeMove** (appears when ≥1 tile is pending; the rack collapses its used slots and + shifts left to free room): a **🏁** button whose popover offers **Make move ✅** / + **Reset ❌**. + - **Game tab bar**: 🔄 Draw (disabled when the bag is empty), 🥺 Skip, 🛟 Hint (with a + remaining-count badge) — each confirmed by an **Ok ✅** popover; 🔀 Shuffle has no + label and no confirm. The under-board slot shows the **Scores: N** preview. + +## Announcement banner (`components/AdBanner.svelte`, `lib/banner.ts`) + +A one-line inset strip under the nav bar. Content is minimal markdown (text + links, +escaped + linkified). A parameterised **rotator** drives messages: a fitting message +holds `holdMs` (default 60 s) then cross-fades to the next; a message wider than the strip +pauses (`edgePauseMs`), scrolls to its right edge at `scrollPxPerSec`, pauses, and repeats +until the cycle exceeds `holdMs`. Today a **mock** provider rotates a long and a short +message; the source becomes a server-driven channel later (see ARCHITECTURE). + +## Result / status iconography (`lib/result.ts`) + +Lobby rows show two lines (opponents, then result + score) with a large place-based emoji +on the right: Victory 🏆 / Defeat 🥈 / Draw 🏅, and for 3–4-player games II 🥈 / III 🥉 / +IV 🏅; active games show Your move 🟢 / Opponent's move ⏳; invitations use 💌. + +## Caveat + +Emoji are rendered by the platform's system emoji font, so their exact look varies across +OSes — acceptable for the MVP, and consistent with the no-asset rule (no glyphs are +downloaded).