Add a mobile section rail that follows the scroll, jumps between cards and folds the one you are on

This commit is contained in:
Jonathan Sykes
2026-09-22 13:47:26 +08:00
parent 987697e759
commit 530d07d920
4 changed files with 333 additions and 3 deletions

View File

@@ -375,6 +375,26 @@ Local DB file: `server/data/ytplayer.db` (gitignored). `BUILD_TAG` is computed f
the fetch resolves to `null` offline and `respondWith(null)` throws. It ends
with `|| Response.error()`.
## Section rail + foldable cards (phones)
`SectionRail` in app.js draws a floating right-hand rail on phones with one
icon per visible card (player · now playing · lyrics · related · list). It
follows the scroll, a tap jumps to that card, and a tap on the card you are
**already** on folds/unfolds it (`data.settings.foldedCards`, per device).
Folding hides a card's body but never its header, so it stays a landmark you
can scroll to and reopen; the Related card's own +/− button routes through
`SectionRail.setFolded` so the two can never disagree.
Two things about this page that break naive implementations:
- **The document does not scroll.** `.player-pane` is the scroll container in
the browser, but the installed PWA in portrait scrolls `.body` instead
(`.player-pane` becomes `overflow-y: visible` there). `scrollerOf()` walks up
to whichever ancestor actually scrolls, and `viewBox()` measures against it.
- **Scroll events do not bubble**, so a `window` scroll listener never fires
for an element scroller — the listener is on `document` with `capture: true`.
Cards also appear outside `render()` (Related arrives with the video's related
list), so `onScroll` re-runs `build()`, which is a no-op unless the visible set
changed; without that the rail could highlight nothing at all.
## First paint vs. the network (launch)
`boot()` used to `await` the profile pull and the shared-playlist inbox before
the first `render()`, so on a slow link the sidebar stayed empty for as long as