feedBack/static/tour-engine.css
Byron Gamatos c7fb074111
feat(onboarding): first-run home tour (spotlight coach marks) (#528)
* style(tour): align tour engine + Shepherd bubbles to the v3 fb-* palette

The tour/help engine shipped its own indigo/blue dark palette (#181830 / #4080e0)
that predates the v3 fee[dB]ack tokens, and the spotlight bubbles themselves used
the vendored Shepherd LIGHT default (white card, black text) — both clashed with
the navy/sky v3 UI behind them.

- Recolor the "?" menu button, popover and first-visit toast to the fb-* tokens
  (card #1e293b, primary #0ea5e9, border #334155, text #f8fafc/#94a3b8, gold
  #e8c040 unchanged).
- Add a dark .shepherd-* override block (loads after the vendored shepherd.css,
  which is left pristine for upgrades): dark bubble + arrow, fb-primary Next/Done
  button, slate secondary button, fb text scale, and bump the modal dim to 0.6 to
  match the onboarding overlay.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(tour-engine): let client/core tours register into the consolidated menu

The tour engine only listed server-discovered plugins (those with a tour.json,
populated from /api/plugins) in the "?" menu, and always prompted unseen relevant
tours via the toast + button pulse. Generalize register() so a core/client-owned
tour can participate:
- `name` registers the tour into the menu catalog (_tourPlugins) so it shows in
  the "?" menu even without a server plugin; never clobbers a real plugin entry.
- `autoPrompt:false` opts the tour OUT of the unseen toast + pulse (for tours
  driven programmatically by their owner), while still listing + running on
  demand. _unseenRelevant honours it.
Both options are additive and default to the prior behaviour.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(v3): add stable tour anchors to home cards + instrument badge

Give the first-run home tour stable spotlight targets: #v3-hero on the hero
panel and data-tour="continue" on the three continue/pick/browse card variants
(dashboard.js), and #v3-instrument-wrap on the topbar instrument selector
(badges.js, mirroring the existing #v3-tuner-wrap). The other targets (audio
routing, tuner, profile, sidebar nav) already had stable ids.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(onboarding): first-run home tour (spotlight coach marks)

After a genuine onboarding completion, dim the home page and spotlight one card
at a time with an explanatory bubble + Next, reusing the shared tour engine
(Shepherd). 7 stops: Hero/Start Playing → Continue/Pick → Instrument selector →
Tuner → Audio Routing → Profile → Sidebar nav. Auto-runs once; replayable
forever from the "?" tour menu as "Welcome tour".

- New static/v3/onboarding-tour.js: registers the spotlight tour (screens:
  ['v3-home'], name "Welcome tour", autoPrompt:false) and exposes startFirstRun(),
  gated on the engine's seen/dismissed state so it never repeats; loaded after
  tour-engine.js + dashboard.js.
- profile.js finish(): trigger startFirstRun() only on a real onboarding
  completion (!editing) — a later profile edit must not relaunch it.

Verified headlessly (native core + Playwright): all 7 anchors resolve, the
spotlight advances one bubble at a time in the v3 dark theme, completion marks
seen, startFirstRun is once-only, and the "?" menu lists "Welcome tour".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(capabilities): clear the handler timeout timer once the race settles

Codex round-6: _withTimeout raced the handler promise against a bare setTimeout
but never cleared it, so a handler that resolves first leaves the timer alive
until it fires. Harmless at 250ms, but the new 15s MIDI permission-command
overrides (discover/open-source) kept the event loop alive ~15s after every
successful call (and the test process hung that long) and could accumulate
delayed callbacks across repeated scans. Capture the timer and clearTimeout it in
a .finally on the race. (Domain/capabilities tests now finish in ~0.1s, not 15s.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(midi-input): give the built-in Web-MIDI provider a distinct participant id

Codex round-7: the built-in Web-MIDI provider registered with participantId
'core.midi-input' — the same id as the domain owner. unregisterProvider()
unregisters the provider's participant, so a provider swap/hot-reload would tear
down the domain OWNER too, leaving midi-input with no owner for later commands.
Register the provider as 'core.midi-input.web-midi'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(onboarding): don't start the home tour when launching the diagnostic

Codex round-7: on the final onboarding step, "Play it now" calls finish() (which
started the home tour) and THEN playSong(target). startFirstRun() navigated to
v3-home and scheduled the tour, then playSong switched to the player — so the
tour spotlighted hidden home elements / stole focus from the diagnostic. Gate the
tour on a launchingSong flag (passed by the "Play it now" path); the Skip path
stays on home, so the tour still runs there.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 16:32:33 +02:00

272 lines
7.5 KiB
CSS

/* ── Consolidated tour menu — one floating button + popover for all tours ── */
/* Palette aligned with the v3 fee[dB]ack design tokens (tailwind.config.js):
fb-card #1e293b surfaces, fb-cardMuted #0b1220 wells, fb-primary #0ea5e9
accent, fb-border #334155 hairlines, fb-text #f8fafc / fb-textDim #94a3b8
text, fb-gold #e8c040 badge. Plain CSS (no Tailwind classes) so it needs no
stylesheet rebuild. Per CLAUDE.md → Frontend Conventions. */
.slopsmith-tour-menu-btn {
position: fixed;
bottom: 12px;
right: 12px;
/* #player is z-index:100 and fixed inset:0; the menu must sit above
it (and above the controls bar at z:10 inside #player), but below
transient modals like Shepherd tooltips. */
z-index: 200;
width: 32px;
height: 32px;
border-radius: 50%;
background: #1e293b;
border: 1.5px solid #0ea5e9;
color: #f8fafc;
font-size: 15px;
font-weight: bold;
cursor: pointer;
box-shadow: 0 0 6px #0ea5e966;
display: flex;
align-items: center;
justify-content: center;
transition: box-shadow 0.2s, transform 0.15s;
}
.slopsmith-tour-menu-btn:hover {
box-shadow: 0 0 12px #0ea5e9aa;
transform: translateY(-1px);
}
.slopsmith-tour-menu-btn.has-unseen {
animation: tour-pulse 2s ease-in-out infinite;
}
.slopsmith-tour-menu-btn.has-unseen::after {
content: '';
position: absolute;
top: -2px;
right: -2px;
width: 10px;
height: 10px;
background: #e8c040;
border: 2px solid #1e293b;
border-radius: 50%;
}
@keyframes tour-pulse {
0%, 100% { box-shadow: 0 0 6px #0ea5e966; }
50% { box-shadow: 0 0 16px #0ea5e9cc; }
}
.slopsmith-tour-menu-popover {
position: fixed;
bottom: 56px;
right: 12px;
z-index: 201;
min-width: 220px;
max-width: 320px;
/* Keep the list scrollable when many tour-enabled plugins are
relevant — without this the popover would grow past the
viewport top as the plugin ecosystem expands. 80px reserves
room for the trigger button + its bottom inset. */
max-height: calc(100vh - 80px);
overflow-y: auto;
background: #1e293b;
border: 1px solid #0ea5e944;
border-radius: 8px;
padding: 6px;
font-size: 13px;
color: #f8fafc;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
}
.slopsmith-tour-menu-popover .tour-menu-header {
padding: 4px 10px 6px;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #94a3b8;
border-bottom: 1px solid #334155;
margin-bottom: 4px;
}
.slopsmith-tour-menu-popover .tour-menu-empty {
padding: 10px;
color: #94a3b8;
font-style: italic;
text-align: center;
}
.slopsmith-tour-menu-popover .tour-menu-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
width: 100%;
padding: 7px 10px;
background: transparent;
border: none;
border-radius: 4px;
color: #f8fafc;
font-size: 13px;
text-align: left;
cursor: pointer;
transition: background 0.12s;
}
.slopsmith-tour-menu-popover .tour-menu-item:hover {
background: #334155;
color: #f8fafc;
}
/* Keyboard focus gets an explicit ring instead of relying on the hover
background — matches the :focus-visible treatment elsewhere in the
app (style.css). Pointer focus is left alone. */
.slopsmith-tour-menu-popover .tour-menu-item:focus-visible {
background: #334155;
color: #f8fafc;
outline: 2px solid #0ea5e9;
outline-offset: -2px;
}
.slopsmith-tour-menu-popover .tour-menu-item-label {
flex: 1;
}
.slopsmith-tour-menu-popover .tour-menu-item-status {
font-size: 10px;
font-weight: 600;
padding: 2px 6px;
border-radius: 10px;
min-width: 14px;
text-align: center;
}
.slopsmith-tour-menu-popover .tour-menu-item-status.is-new {
background: #e8c040;
color: #0f172a;
}
.slopsmith-tour-menu-popover .tour-menu-item-status.is-seen {
background: #0b1220;
color: #94a3b8;
}
/* ── First-visit toast — anchored above the menu button ── */
.slopsmith-tour-prompt {
position: fixed;
bottom: 56px;
right: 12px;
z-index: 202;
background: #1e293b;
border: 1px solid #0ea5e944;
border-radius: 8px;
padding: 10px 14px;
font-size: 13px;
color: #f8fafc;
max-width: 240px;
line-height: 1.4;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
transition: opacity 0.5s;
}
.slopsmith-tour-prompt.fading {
opacity: 0;
pointer-events: none;
}
.slopsmith-tour-prompt .tour-prompt-more {
margin-top: 4px;
font-size: 11px;
color: #94a3b8;
}
.slopsmith-tour-prompt .tour-prompt-buttons {
display: flex;
gap: 6px;
margin-top: 8px;
}
.slopsmith-tour-prompt button {
padding: 3px 10px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
border: none;
transition: opacity 0.15s;
}
.slopsmith-tour-prompt button:hover {
opacity: 0.85;
}
.slopsmith-tour-prompt button[data-action="start"] {
background: #0ea5e9;
color: #fff;
font-weight: 600;
}
.slopsmith-tour-prompt button[data-action="dismiss"] {
background: #334155;
color: #94a3b8;
}
/* ── Shepherd bubble theme — override the vendored light default ──────────── */
/* The vendored static/vendor/shepherd.css ships Shepherd's stock LIGHT theme
(white bubble, black text, blue buttons), which clashes with the dark v3 UI.
These overrides load after it (index.html order) and recolor the spotlight
bubbles to the fb-* tokens. The vendored file is left untouched so it stays
upgradable. */
.shepherd-element {
background: #1e293b;
border: 1px solid #334155;
border-radius: 10px;
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.55);
max-width: 360px;
}
.shepherd-content {
background: #1e293b;
border-radius: 10px;
}
.shepherd-text {
color: #cbd5e1;
font-size: 0.9rem;
line-height: 1.45;
padding: 0.85em 0.9em;
}
.shepherd-title {
color: #f8fafc;
font-size: 0.95rem;
font-weight: 700;
}
/* Title row: drop the light-grey header fill the stock theme paints behind a
titled step, so the header blends into the card. */
.shepherd-has-title .shepherd-content .shepherd-header {
background: transparent;
padding: 0.85em 0.9em 0;
}
/* Arrow must match the bubble surface (stock paints it white, and grey behind
a bottom-placed titled step). */
.shepherd-arrow:before {
background: #1e293b;
}
.shepherd-element.shepherd-has-title[data-popper-placement^="bottom"] > .shepherd-arrow:before {
background-color: #1e293b;
}
.shepherd-footer {
padding: 0 0.75rem 0.75rem;
}
/* Primary button (Next / Done) → fb-primary. */
.shepherd-button {
background: #0ea5e9;
color: #fff;
border-radius: 6px;
font-weight: 600;
padding: 0.4rem 1.1rem;
}
.shepherd-button:not(:disabled):hover {
background: #38bdf8;
color: #fff;
}
/* Secondary button (Back / Skip) → muted slate. */
.shepherd-button.shepherd-button-secondary {
background: #334155;
color: #f8fafc;
}
.shepherd-button.shepherd-button-secondary:not(:disabled):hover {
background: #475569;
color: #f8fafc;
}
.shepherd-cancel-icon {
color: #94a3b8;
}
.shepherd-cancel-icon:hover,
.shepherd-has-title .shepherd-content .shepherd-cancel-icon:hover {
color: #f8fafc;
}
.shepherd-has-title .shepherd-content .shepherd-cancel-icon {
color: #94a3b8;
}
/* Dim the page a touch more, matching the onboarding overlay (bg-black/60). */
.shepherd-modal-overlay-container.shepherd-modal-is-visible {
opacity: 0.6;
}