Files
yellowjacket/frontend/index.html
T
logan f59490b113 feat(player): show progress on the phone's bar border
#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
2026-08-21 14:38:12 +00:00

113 lines
5.5 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<link href="./index.css" rel="stylesheet" />
<title>yellowjacket</title>
<script src="/index.ts" type="module"></script>
</head>
<body>
<!-- a11y.30. First focusable thing in the document, so a keyboard
user is not walked through the header, the library filter, the
search box and eleven nav items on every navigation. -->
<a class="skip-link" href="#main-content">Skip to content</a>
<!-- Below 600px this bar is not in the layout at all (#57): index.css
takes its grid row away and leaves the element visually hidden,
carrying nothing but the `h1` below. Every control in it has
somewhere else to be there -- `nav-history` is the platform's
own back gesture, `job-indicator` is `<job-band>`, `search-bar`
is `<search-dialog>` opened from the view's own header, and
`library-filter` is Settings -> Libraries (#148). -->
<header class="top-bar">
<hgroup>
<h1 class="title">YellowJacket</h1>
<!-- a11y.29: a heading level was being used for type size. -->
<p class="subtitle">Music how it was meant to bee.</p>
</hgroup>
<!-- Global back/forward (#6). Before the library filter so the
two navigation controls in this bar are adjacent, and after
the brand because that is where a window's chrome ends and
the app's begins. Hidden below 600px by index.css: the
phone has a system back, and this bar has no room. -->
<nav-history></nav-history>
<library-filter></library-filter>
<search-bar></search-bar>
<job-indicator></job-indicator>
</header>
<!-- The phone's view of background work (#62): below 600px the
indicator above stands down and its rows appear here instead,
in the layout rather than over it. `display: none` above that
width in index.css, which is also what keeps it out of the
desktop grid -- an in-flow child with no named area is
auto-placed into one of the shell's rows, which is the trap the
skip link is absolutely positioned to avoid. -->
<job-band></job-band>
<div class="sidebar">
<app-sidebar></app-sidebar>
</div>
<div class="content-area">
<main class="main-panel" data-active-view="tracks" data-testid="main-content" id="main-content"
tabindex="-1">
<track-list></track-list>
</main>
<queue-panel id="queue-panel"></queue-panel>
</div>
<!-- Three columns, and the outer two are the same width, which is
what makes the middle one *centred* rather than merely in the
middle of what is left (#23). The transport used to sit in a
`320px 1fr auto` grid, so its centre was ~140px right of the
window's.
That is also why the volume moved out of `audio-player` and
into the bar (#42): the transport column has to contain the
transport and nothing else, or "centred" means centred with a
slider bolted to one side. It joins the queue button in
`.bar-end`, whose width is what the left column is matched
against. -->
<footer class="bottom-bar">
<now-playing></now-playing>
<audio-player></audio-player>
<div class="bar-end">
<volume-control></volume-control>
<button aria-label="Toggle queue" aria-controls="queue-panel" aria-expanded="false"
id="queue-button">
<!-- ICON_QUEUE in src/utils/icon-language.ts, written out
because this file has no module scope. It was `list`,
which is the Playlists destination's icon. -->
<wa-icon name="bars-staggered"></wa-icon>
</button>
</div>
</footer>
<!-- How far through the song we are, on the border between the two
bars (#58). The shell's element rather than either bar's:
they are separate components stacked in this grid, so a line
on the border between them is a row of it, and neither one has
to reach into the other's box for two pixels. It renders
nothing above 600px and nothing with no track, is `aria-hidden`
(Now Playing's seek bar is what announces the position) and
takes no pointer events at all -- a thin line that sometimes
seeks is worse than one that never does. -->
<player-progress-line></player-progress-line>
<!-- The phone's primary navigation, hidden above 600px by
index.css. Eager rather than a chunk, for the reason
notification-host is: it is the only way to move around the
app on a phone. After the footer, because that is where it
renders -- the tab bar sits below the transport, and DOM order
is what a screen reader and the tab sequence follow. -->
<bottom-nav></bottom-nav>
<first-run-wizard></first-run-wizard>
<notification-host></notification-host>
<shortcuts-overlay></shortcuts-overlay>
<!-- The phone's search surface (#57). A singleton here for the
reason shortcuts-overlay is one: one instance, one document
listener, and no `data-testid="search-input"` resolving to two
elements. It renders nothing while shut, so the header's box
is still the only one on a desktop. -->
<search-dialog></search-dialog>
</body>
</html>