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.
6.6 KiB
Scrabble Game — UI design system
Visual and interaction conventions for the ui client. Behaviour lives in
FUNCTIONAL.md; cross-service architecture (including the global
points this doc references) lives in 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; notransform, which broke scrolling differently in Safari/Chrome). Labels are sized incqwagainst 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):beginnershows a split3×/word(localized слово/буква),classica single3W/3С,nonenothing. 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
.gcgfile 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).