feat(v0.4.0-prep): first-run UX hint — pulse + bouncing arrow

The 'pick a station to begin' state was too subtle on first install
(David's own 30-second panic moment when he uninstalled the dev build
and reinstalled from the Web Store, 2026-06-09 evening).

Two layered cues, both pure CSS driven by a body.is-first-run class:

1. Subtle accent-green glow pulses around:
   - popup: the station list section
   - newtab: the Quick Stations chip row
   (rgba alpha 0.18-0.25, 2.4s ease-in-out infinite — visible but not noisy)

2. Bouncing ↓ arrow appended to the 'Pick a station to begin' text in
   both surfaces (after-pseudo with translateY animation).

The is-first-run class is toggled in popup.js + newtab.js via a new
reflectFirstRunHint() function called from:
  - init() once stations + currentStation are resolved
  - onPickStation() the moment a user picks
  - the chrome.storage.onChanged listener when another surface picks
    (so the hint disappears on both surfaces simultaneously)

Existing users with stored currentStationId never see either cue —
the class only attaches when currentStation is null.

Working on branch v0.4.0-prep so the live main (= what shipped to
Web Store v0.3.0) is unchanged. Merge to main when ready to bump the
manifest version + readme.txt + CHANGELOG.md for v0.4.0 release.
This commit is contained in:
2026-06-09 22:05:55 +01:00
parent 9695d9d341
commit b82f14ee7b
4 changed files with 92 additions and 0 deletions
+35
View File
@@ -310,3 +310,38 @@ html, body {
.station-list::-webkit-scrollbar-track {
background: transparent;
}
/* ──────────────────────────────────────────────────────────────────────
First-run UX hint (v0.4.0)
When body.is-first-run is set (no station picked), the station list
subtly pulses with an accent-green glow and the now-playing area gets
a bouncing ↓ arrow pointing the user at the list. Both removed the
moment a station is picked, so existing users never see them again.
────────────────────────────────────────────────────────────────────── */
@keyframes tuner-pulse-glow {
0%, 100% { box-shadow: 0 0 0 0 rgba(109, 191, 122, 0); }
50% { box-shadow: 0 0 0 4px rgba(109, 191, 122, 0.25); }
}
@keyframes tuner-bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(3px); }
}
body.is-first-run .stations {
animation: tuner-pulse-glow 2.4s ease-in-out infinite;
border-radius: 6px;
}
body.is-first-run .np-track {
color: var(--accent);
font-weight: 500;
}
body.is-first-run .np-track::after {
content: " ↓";
display: inline-block;
animation: tuner-bounce 1.8s ease-in-out infinite;
margin-left: 4px;
}