feat(ui): first-run onboarding coachmarks
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 57s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m20s
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 57s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m20s
A one-time coachmark overlay walks a new player through the lobby and their first game board: a light dimmed layer draws one tail-pointed hint bubble at a time, advancing on a tap anywhere and removing itself for good after the last hint. Two independent series (lobby: settings/stats/new game; game: header/pass-exchange/hints/shuffle/rack), gated by a per-device persisted flag and marked done only after the last hint, so an interrupted run replays from the start. A deep-link into Settings -> Friends still triggers the lobby series on the first trip back to the lobby. Targets carry a data-coach attribute, so one positioning engine anchors the bubble in both portrait and landscape, re-measuring each frame until the geometry settles (route slide, hidden-banner reflow, fonts). The promo banner hides while the overlay is up (app.coachActive); a hidden DebugPanel "Reset visited" control replays the walk-through. Off by default in the mock build so the Playwright smoke is unaffected; ?coach forces it on for the dedicated e2e. Pure geometry (step lists, nextVisibleStep, placeBubble) in lib/coachmark.ts (unit-tested); Coachmark.svelte renders. Docs: FUNCTIONAL(+ru) onboarding story, UI_DESIGN coachmark section.
This commit is contained in:
@@ -25,6 +25,20 @@ theme, and links to the matching per-language Telegram channel; the game itself
|
||||
`/app/` (web), `/telegram/` (the Telegram Mini App) and `/vk/` (the VK Mini App). The landing's theme is ephemeral
|
||||
(it follows the system scheme, not the saved preference); its language choice is saved.
|
||||
|
||||
### First-run onboarding
|
||||
The first time a player opens the app it walks them through the interface with a light
|
||||
**coachmark overlay**: a dimmed layer draws one hint bubble at a time, each pointing — with a
|
||||
tail — at a real control, and a **tap anywhere** advances to the next; after the last hint the
|
||||
overlay is gone for good. Two independent series run. The **lobby** series points at the
|
||||
⚙️ settings, ✏️ statistics and 🎲 new-game tabs. The **game** series, on the player's first game
|
||||
board, points at the scoreboard header (move history / chat / word-check), the 🔄 pass-and-exchange,
|
||||
🛟 hints and 🔀 shuffle controls, and the rack. A series counts as seen only **after its last hint**,
|
||||
so leaving mid-way replays it from the start next time; it is remembered per device. Arriving at the
|
||||
lobby is the lobby trigger, so a player who deep-links straight into Settings → Friends still gets the
|
||||
lobby walk-through on their first trip back. Both series work the same in portrait and landscape (the
|
||||
bubbles re-anchor to wherever the controls move) and the hint texts are localized (en/ru). The
|
||||
advertising banner hides while the overlay is up and returns when it closes.
|
||||
|
||||
### Identity & sessions
|
||||
A player arrives from a platform (Telegram first), via email login, or as an
|
||||
ephemeral guest. The gateway validates the credential once and mints a thin
|
||||
|
||||
@@ -26,6 +26,21 @@ top-1 подсказку, безлимитную проверку слова с
|
||||
`/app/` (веб), `/telegram/` (Telegram Mini App) и `/vk/` (VK Mini App). Тема на странице эфемерна (берётся из
|
||||
системной настройки, а не из сохранённой), выбор языка сохраняется.
|
||||
|
||||
### Первый запуск: онбординг
|
||||
При первом открытии приложения игрока один раз проводят по интерфейсу лёгким
|
||||
**coachmark-оверлеем**: затемнённый слой показывает по одной подсказке-«облачку» за раз, каждое
|
||||
«хвостиком» указывает на реальный элемент управления, а **тап в любом месте** переходит к
|
||||
следующей; после последней подсказки оверлей убирается навсегда. Серий две. Серия **лобби**
|
||||
указывает на вкладки ⚙️ настроек, ✏️ статистики и 🎲 новой игры. Серия **игры**, на первой партии
|
||||
игрока, указывает на шапку со счётом (история ходов / чат / проверка слова), кнопки 🔄 пас-и-обмен,
|
||||
🛟 подсказок и 🔀 перемешивания, и на стойку с фишками. Серия считается просмотренной только
|
||||
**после последней подсказки**, поэтому выход на середине в следующий раз покажет её с начала;
|
||||
запоминается она по устройству. Триггер серии лобби — попадание в лобби, так что игрок, пришедший
|
||||
по deeplink сразу в Настройки → Друзья, всё равно получит проводку по лобби при первом возврате.
|
||||
Обе серии одинаково работают в portrait и landscape (облачка переустанавливаются туда, куда
|
||||
переезжают элементы), тексты подсказок локализованы (en/ru). Рекламный баннер скрывается, пока
|
||||
оверлей показан, и возвращается по закрытию.
|
||||
|
||||
### Личность и сессии
|
||||
Игрок приходит с платформы (сначала Telegram), через email-вход или как
|
||||
эфемерный гость. Gateway один раз валидирует доступ и выдаёт тонкий
|
||||
|
||||
+28
-1
@@ -49,6 +49,32 @@ fast load shows it for ~1.25 s. Each tile **drops in** with a brief scale + fade
|
||||
dismisses as soon as the lobby is ready. The pure layout and timing live in `lib/splash.ts`
|
||||
(unit-tested); `Splash.svelte` is the renderer.
|
||||
|
||||
## First-run onboarding coachmarks (`components/Coachmark.svelte`, `lib/coachmark.ts`)
|
||||
|
||||
A one-time **coachmark overlay** introduces the interface to a new player. Like the splash it is an
|
||||
**App-level overlay**; it runs two independent series chosen by the route — **lobby** (⚙️ settings →
|
||||
✏️ stats → 🎲 new game) and **game** (scoreboard header → 🔄 pass/exchange → 🛟 hints → 🔀 shuffle →
|
||||
rack). It draws **one bubble at a time** over a light scrim (`rgba(0,0,0,0.32)`); the whole layer
|
||||
captures pointer events, so a **tap anywhere advances** and the UI underneath stays inert. After the
|
||||
last hint the series is recorded done (`session.ts` `onboarding` key) and the overlay removes itself;
|
||||
a series is marked only after its **last** hint, so an interrupted player replays it from the start.
|
||||
The lobby series gates on `app.splashDone`, the game series on its first target laying out; both defer
|
||||
while a modal (`welcomeRedeem` / `staleInvite`) is open.
|
||||
|
||||
Each target carries a **`data-coach` attribute**, so the **same positioning engine anchors it in both
|
||||
orientations** wherever the layout moves it (the tab bar to the landscape left panel, etc.). The
|
||||
bubble points at the live `getBoundingClientRect()` of its target, **re-measuring each frame until the
|
||||
geometry settles** (the route-change slide, the hidden-banner reflow, async fonts) and on resize /
|
||||
rotation; a target absent in the current state is skipped. The bubble matches the in-game counter text
|
||||
size (`0.85rem`, larger in landscape), wraps by word and never fills the width; its **tail** sits on
|
||||
the edge facing the target, centred on it (clamped near a corner). The pure geometry (step lists,
|
||||
`nextVisibleStep`, `placeBubble`) lives in `lib/coachmark.ts` (unit-tested); `Coachmark.svelte` is the
|
||||
renderer. While the overlay is up the **advertising banner is hidden** (`app.coachActive`) so its
|
||||
scroll does not run behind the scrim. The hidden DebugPanel offers a **Reset visited** control that
|
||||
clears the flags so the walk-through replays on the next launch. In the **mock build** the overlay
|
||||
does not auto-show (so it cannot block the Playwright smoke); `?coach` forces it on for the dedicated
|
||||
e2e and the screenshots.
|
||||
|
||||
## Navigation
|
||||
|
||||
- **Back**: a thin, compact `<` drawn from two rotated CSS borders (`Header.svelte`
|
||||
@@ -253,7 +279,8 @@ the surroundings in the light theme and a touch lighter in the dark theme, mappe
|
||||
linkified). It is **server-driven**: the campaigns and display timings ride the `profile.get`
|
||||
response (`app.profile.banner`, present only for an eligible viewer — see ARCHITECTURE §10), so
|
||||
`Header` renders the strip only when that block is present, and a `notify` `banner` event re-fetches
|
||||
the profile to show or hide it in place.
|
||||
the profile to show or hide it in place. It is also hidden while a first-run onboarding overlay is up
|
||||
(`app.coachActive`), reappearing by this same condition once the overlay closes.
|
||||
|
||||
The rotation runs in a **persistent module engine** (`lib/bannerEngine`): the scheduler and timer
|
||||
live outside the components, so navigating between screens (which remounts the view) **continues the
|
||||
|
||||
Reference in New Issue
Block a user