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:
@@ -1111,7 +1111,7 @@
|
||||
{@const badge = badgeKind(app.chatUnread[id] ?? false, app.messageUnread[id] ?? false)}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div class="scoreboard" class:flat={landscape} onclick={landscape ? undefined : toggleHistory}>
|
||||
<div class="scoreboard" class:flat={landscape} data-coach="game-header" onclick={landscape ? undefined : toggleHistory}>
|
||||
{#if badge}<span class="unread-dot sbadge-dot" class:nudge={badge === 'nudge'}></span>{/if}
|
||||
{#each view.game.seats as s (s.seat)}
|
||||
<div class="seat" class:turn={view.game.toMove === s.seat && !gameOver} class:win={s.isWinner}>
|
||||
@@ -1250,7 +1250,7 @@
|
||||
{#snippet rackRow()}
|
||||
<!-- The footer is drawn even when the game is over (rack + controls), but inert:
|
||||
a finished game shows the final rack greyed out and the controls disabled. -->
|
||||
<div class="rack-row" class:inert={gameOver}>
|
||||
<div class="rack-row" class:inert={gameOver} data-coach="game-rack">
|
||||
<div class="rack-wrap">
|
||||
<Rack
|
||||
slots={rackSlots}
|
||||
@@ -1271,7 +1271,7 @@
|
||||
|
||||
{#snippet controlButtons()}
|
||||
<button class="tab" disabled={busy || !isMyTurn || !connection.online} onclick={openExchange}>
|
||||
<span class="sq">🔄</span><span class="lbl">{t('game.draw')}</span>
|
||||
<span class="sq" data-coach="game-turn">🔄</span><span class="lbl">{t('game.draw')}</span>
|
||||
</button>
|
||||
<TapConfirm
|
||||
triggerClass="tab"
|
||||
@@ -1279,7 +1279,7 @@
|
||||
disabled={busy || !isMyTurn || !connection.online || hintCount <= 0}
|
||||
onconfirm={doHint}
|
||||
>
|
||||
<span class="sq">🛟{#if hintCount > 0}<span class="badge">{hintCount}</span>{/if}</span>
|
||||
<span class="sq" data-coach="game-hints">🛟{#if hintCount > 0}<span class="badge">{hintCount}</span>{/if}</span>
|
||||
<span class="lbl">{t('game.hint')}</span>
|
||||
</TapConfirm>
|
||||
{#if placement.pending.length > 0}
|
||||
@@ -1288,7 +1288,7 @@
|
||||
</button>
|
||||
{:else}
|
||||
<button class="tab" disabled={busy || gameOver} onclick={shuffle}>
|
||||
<span class="sq">🔀</span>
|
||||
<span class="sq" data-coach="game-shuffle">🔀</span>
|
||||
</button>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
Reference in New Issue
Block a user