feat(player): show progress on the phone's bar border
CI / check (push) Skipped
CI / e2e (push) Skipped
CI / check (pull_request) Successful in 2m28s
CI / e2e (pull_request) Successful in 8m55s

#59 took the seek bar off the phone's transport, so the one thing a
mini player is expected to say without being opened -- how far through
the song it is -- had nowhere left to be said.

It is the shell's element and its own 2px grid row between `bottom-bar`
and `bottom-nav`, because those two are separate components and either
one drawing the line means reaching into the other's box. The fill is
`scaleX()` off the same `PlaybackPositionChanged` the seek bar renders,
with the same `trackChangeId`/`seq` guards and an interval that only
interpolates *between* reports -- never its own clock, which is the
rule that exists because a local counter drifted 30 s away from the
backend across four keyboard seeks.

It is `aria-hidden` and takes no pointer events at any depth: Now
Playing's seek bar is what announces the position, and a 2px strip on
the top edge of the tab bar is exactly where a thumb aiming at a tab
lands. It renders nothing above 600px, from `matchMedia` rather than a
media query, because a stylesheet cannot stop a 1 Hz interval running
for the life of every desktop session about a line nobody can see.

Its phone rule is at the foot of index.css beside `job-band`'s, not in
the phone block above: a media query adds no specificity, so a
`display: block` written before the `display: none` that takes it out
of the desktop grid loses to it and the line never appears at all.

Closes #58
This commit is contained in:
2026-08-21 03:43:37 -04:00
parent 14e3ab574c
commit bd45e5d595
8 changed files with 660 additions and 3 deletions
+27 -3
View File
@@ -426,6 +426,7 @@ body div.sidebar {
"jobs-band" auto
"main-panel" 1fr
"bottom-bar" auto
"progress-line" auto
"bottom-nav" auto
/ 1fr;
/* Nothing may scroll sideways here. On a desktop the shell is
@@ -501,7 +502,8 @@ body div.sidebar {
expression of the same fact is a second thing to keep in step.
The view carries its own queue button, because this is where
that one lived. */
body:has(#main-content[data-active-view="now-playing"]) .bottom-bar {
body:has(#main-content[data-active-view="now-playing"]) .bottom-bar,
body:has(#main-content[data-active-view="now-playing"]) player-progress-line {
display: none;
}
}
@@ -568,8 +570,12 @@ body div.sidebar {
/* Out of the desktop grid entirely. `job-band` renders nothing above
600px anyway, but an in-flow grid child with no named area is
auto-placed into a row of the shell -- the same trap the skip link is
absolutely positioned to avoid. */
body job-band {
absolutely positioned to avoid. `player-progress-line` (#58) is the
same element in the same position for the same reason: below 600px it
has a named row, and above it there is no border for it to sit on --
the desktop bar carries a real, interactive seek bar. */
body job-band,
body player-progress-line {
display: none;
}
@@ -602,3 +608,21 @@ body job-band {
background-color: var(--yj-bg-elevated, #343a40);
}
}
/* #58. How far through the song we are, in its own grid row between
the two bars -- so the line is *on* the border rather than inside
either of them, and in flow rather than over it. The row is `auto`
and the element renders nothing while no track is loaded, so it costs
no height at all until there is something to say.
**This block is below the `display: none` above and has to be**, for
the reason the band's rule is: a media query adds no specificity, so
`body player-progress-line { display: block }` written before that
rule loses to it at equal specificity and the line never appears at
any width. Nothing fails; it is simply not there. */
@media (max-width: 599px) {
body player-progress-line {
display: block;
grid-area: progress-line;
}
}