feat(ui): move in-game status to the board — highlight, score badge, full-width rack
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Failing after 13s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Failing after 0s
CI / deploy (pull_request) Has been skipped

Remove the under-board status strip and relocate its signals:
- bag count -> a badge on the exchange/pass control + the foot of the move table
- whose-turn / win-lose -> a thin strip above the score plaques
- the tentative-move caption -> the board itself: staged tiles tint green (legal) or
  pink (illegal), the board tiles a formed word runs through go a shade darker, and an
  orange score badge sits on the main word (digit sized like a tile value, clamped on-board)

The word geometry (covered cells + badge anchor) is a new pure client-side helper
(ui/src/lib/formed.ts), independent of the move evaluator, so it works on the local or
network preview path alike; the badge's number still comes from the preview score.

Rack: a seven-column grid filling the tray width in both layouts — square, full-width
tiles — with the confirm control in the fixed 7th slot.

Settings: a touch-only "Zoom the board" toggle (default on, device-local) gates the
tile-placement auto-zoom; taking a hint while zoomed in now zooms out so the highlighted
hint word is never left off-screen.

Docs (FUNCTIONAL +_ru, UI_DESIGN, ARCHITECTURE) and e2e/unit tests updated.
This commit is contained in:
Ilia Denisov
2026-07-10 14:58:32 +02:00
parent 2683103fc1
commit 77a690fcf6
21 changed files with 635 additions and 198 deletions
+11
View File
@@ -118,6 +118,9 @@ export const app = $state<{
locale: Locale;
reduceMotion: boolean;
boardLabels: BoardLabelMode;
/** Auto-zoom the board toward a tile when it is placed (touch/mobile only; the wide desktop and
* landscape layouts already fit the whole board and never auto-zoom). On by default. */
zoomBoard: boolean;
/** Completion flags for the two first-run coachmark series (components/Coachmark.svelte). */
onboarding: OnboardingState;
/** Whether a first-run coachmark overlay is currently on screen. While set, the scrolling promo
@@ -172,6 +175,7 @@ export const app = $state<{
locale: 'en',
reduceMotion: false,
boardLabels: 'beginner',
zoomBoard: true,
onboarding: { lobbyDone: false, gameDone: false },
coachActive: false,
notifications: 0,
@@ -813,6 +817,7 @@ export async function bootstrap(): Promise<void> {
app.theme = prefs.theme ?? 'auto';
app.reduceMotion = prefs.reduceMotion ?? false;
app.boardLabels = prefs.boardLabels ?? 'beginner';
app.zoomBoard = prefs.zoomBoard ?? true;
app.onboarding = await loadOnboarding();
applyTheme(app.theme);
applyReduceMotion(app.reduceMotion);
@@ -1232,6 +1237,7 @@ function persistPrefs(): void {
locale: app.locale,
reduceMotion: app.reduceMotion,
boardLabels: app.boardLabels,
zoomBoard: app.zoomBoard,
});
// Mirror the device-independent display prefs to Telegram CloudStorage so they follow the user
// across devices (no-op outside Telegram / on a client predating it). Locale is excluded — it
@@ -1321,6 +1327,11 @@ export function setBoardLabels(mode: BoardLabelMode): void {
persistPrefs();
}
export function setZoomBoard(on: boolean): void {
app.zoomBoard = on;
persistPrefs();
}
// Background/foreground lifecycle: silence the reconnect banner during a suspend and
// reconnect quietly on return (and refresh the lobby badge for any push missed while
// hidden, §10). Several signals cover the platforms: the page Visibility API, the