From db81d7dafb8a81f5fcc4b8963fb03aea91b5f826 Mon Sep 17 00:00:00 2001 From: "K. O. A." Date: Mon, 29 Jun 2026 17:44:09 -0400 Subject: [PATCH] Add progress bar to v3 "Up Next" pill (#649) The persistent top-right "Up Next" pill showed the upcoming section name and a countdown ("in 12.3s") but no at-a-glance sense of how far through the current section the song is. Add a thin progress bar directly under the existing text that fills as the current section elapses toward the next, reaching full when the section flips. The text row is wrapped unchanged in a flex row and the pill stacks the bar beneath it; nothing else about the pill's content or styling changes. Progress is computed in updateUpNext() as the fraction elapsed between the previous section boundary (last section at/before now, else song start) and the next section. The fill uses the same gradient as the section name for visual cohesion. Signed-off-by: topkoa Co-authored-by: Claude Opus 4.8 --- static/v3/index.html | 9 ++++++--- static/v3/player-chrome.js | 13 +++++++++++++ static/v3/v3.css | 25 +++++++++++++++++++++++-- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/static/v3/index.html b/static/v3/index.html index d25f14f..18bbbe3 100644 --- a/static/v3/index.html +++ b/static/v3/index.html @@ -855,9 +855,12 @@ diff --git a/static/v3/player-chrome.js b/static/v3/player-chrome.js index 0657711..ce13c57 100644 --- a/static/v3/player-chrome.js +++ b/static/v3/player-chrome.js @@ -187,6 +187,19 @@ const nm = $('v3-upnext-name'), eta = $('v3-upnext-eta'); if (nm) nm.textContent = next.name || '—'; if (eta) eta.textContent = 'in ' + dt.toFixed(1) + 's'; + // Progress bar: fraction of the current section elapsed toward `next`. + // Previous boundary is the last section at/before now (else song start). + const fill = $('v3-upnext-bar-fill'); + if (fill) { + let prevT = 0; + for (let i = 0; i < secs.length; i++) { + if (typeof secs[i].time === 'number' && secs[i].time <= t) prevT = secs[i].time; + else break; + } + const span = next.time - prevT; + const prog = span > 0 ? Math.max(0, Math.min(1, (t - prevT) / span)) : 0; + fill.style.width = (prog * 100).toFixed(1) + '%'; + } pill.classList.remove('hidden'); } diff --git a/static/v3/v3.css b/static/v3/v3.css index e9caf26..8cc2336 100644 --- a/static/v3/v3.css +++ b/static/v3/v3.css @@ -340,8 +340,9 @@ input, textarea, select, /* — Up Next pill (top-right, persistent) — */ #player-hud .v3-upnext { display: flex; - align-items: center; - gap: .5rem; + flex-direction: column; + align-items: stretch; + gap: .35rem; padding: .45rem .9rem; border-radius: .75rem; background: rgba(15, 23, 42, .7); @@ -351,6 +352,26 @@ input, textarea, select, pointer-events: auto; } #player-hud .v3-upnext.hidden { display: none; } +/* Text row keeps the original inline layout untouched. */ +#player-hud .v3-upnext .v3-upnext-row { + display: flex; + align-items: center; + gap: .5rem; +} +/* Progress bar under the text — fills as the current section elapses. */ +#player-hud .v3-upnext .v3-upnext-bar { + height: 4px; + border-radius: 999px; + background: rgba(148, 163, 184, .25); + overflow: hidden; +} +#player-hud .v3-upnext .v3-upnext-bar-fill { + height: 100%; + width: 0%; + border-radius: inherit; + background: linear-gradient(90deg, #22d3ee, #a855f7, #f472b6); + transition: width .12s linear; +} /* — Live performance HUD (top-right, read-only) — */ .v3-live-performance-hud {