d733ce3119
Wire the deferred Stage 7 surfaces end-to-end (UI -> gateway transcode -> backend REST -> existing domain services): friends (incl. one-time friend codes), per-user blocks, friend-game invitations, profile editing + email binding, the statistics screen, and the in-game history + GCG export. Friends gain two add paths (interview decision, a deliberate plan change): one-time 6-digit codes (friend_codes table, 12h TTL, single-use, rate-limited redeem); and play-gated requests (shared game required) where an explicit decline is permanent, an ignored request lapses after 30 days, and a code bypasses a decline. Migration 00006 widens friendships_status_chk and adds friend_codes. Lobby notification badge is poll + push: a new generic `notify` event drives it live; the client polls on open/focus. Language stays a single Settings control that writes through to the durable account's preferred_language. GCG export is finished-only (game.ErrGameActive) and shares/downloads the .gcg file. Tests: backend unit + inttest (friend gate/decline/code, ListInvitations, GetStats, GCG gate), gateway transcode round-trips + notify constructor, UI vitest (codecs, win-rate, share choice) + Playwright social specs. Docs: PLAN (Stage 8 done + refinements + TODO-5), ARCHITECTURE, FUNCTIONAL(+ru), UI_DESIGN, TESTING, module READMEs.
111 lines
6.6 KiB
Markdown
111 lines
6.6 KiB
Markdown
# 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 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
|
||
|
||
- **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: Friends/Profile/Settings/About; game: History/Chat/Check word, plus
|
||
*Export GCG* on a finished game and *Add to friends* per opponent, then Drop game). A
|
||
red count **badge** rides the hamburger (and the lobby *Friends* item) for pending
|
||
incoming friend requests + invitations; the same dot style serves any future
|
||
notification count.
|
||
- **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) 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**.
|
||
- **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 💌.
|
||
|
||
## Social, account & history surfaces (Stage 8)
|
||
|
||
- **Friends** (`screens/Friends.svelte`, from the lobby menu): an "add a friend" block
|
||
pairing a code **input** with a **Show my code** action that reveals a large 6-digit
|
||
code + its expiry; then the incoming **requests** (Accept / Decline), the **friends**
|
||
list (Remove / Block), and a **blocked** list (Unblock). Durable accounts only — a
|
||
guest sees a sign-in prompt.
|
||
- **Invitations**: a lobby **section** (a 💌 row per open invitation) with Accept /
|
||
Decline for an invitee and a waiting/Cancel state for the inviter; creating one is the
|
||
**"Play with friends"** mode in `NewGame.svelte` (pick invitees, then variant / move
|
||
time / hints).
|
||
- **Statistics** (`screens/Stats.svelte`, the lobby 📊 tab): a 2-column grid of stat
|
||
cards (wins / losses / draws / games / win-rate / best game / best move) — pure
|
||
numbers, no charts.
|
||
- **Profile editing** (`screens/Profile.svelte`): an inline form (display name, timezone,
|
||
the away-window time pickers, block toggles) and an email-binding sub-flow (enter email
|
||
→ enter the confirm code). Interface language stays in **Settings** (it writes through
|
||
to the account for durable users).
|
||
- **History / GCG**: the in-game slide-down history gains the running total per move;
|
||
*Export GCG* shares or downloads the `.gcg` file and appears only once the game is
|
||
finished.
|
||
|
||
## 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).
|