navigation: state the bar's geometry instead of collapsing a margin for it

Hoisting the glass bar out of the pages (8017c0d) dropped the artist page's
hero 24px, so the artwork began below the bar instead of running under it.

The bar used to be each page's FIRST CHILD, and its `margin-top: -24px`
collapsed through the page root -- pulling `.artist-page` itself up to the
top of the pane, where the hero could sit behind the bar. As a sibling it
cannot do that, so the root fell back to the pane's padding edge and took
the artwork with it: `.artist-page` is `position: relative` and the whole
`.hero-bg` stack is positioned against its box.

Diffing every element in the page against the previous build, 16 moved and
all by exactly +24: .hero-bg, .hero-seed, .gpu-hero, canvas.banner (the
visible one), the twins, .seam-clip/.seam-tail and .sheetclip. The other 94
did not move at all -- the flow content was never the problem, which is why
this did not show up as content shifting.

That geometry is now stated rather than emergent. The bar's margin-bottom
is -bar-h, so its net flow contribution is -pane-pad: it consumes no height
of its own AND cancels the pane's top padding, leaving the page's box flush
with the top of the pane. Each page under the bar hands that inset back with
`padding-top: var(--pane-pad)`, which is a no-op for the hero (padding does
not move a positioned ancestor's padding-box top) and restores every flow
child. The pane's 24px is now a named custom property rather than a number
repeated in four rules with a comment warning they are tied.

Verified against the pre-hoist build element by element: 87 artist-page
elements compared, ZERO moved, including canvas.banner back at 84 and the
Fanart wordmark at 883. Album page root 38, hero 62, tracklist 514 --
identical. The bar still paints above the hero (elementFromPoint at its
centre returns the bar), arrival still animates only the arriving crumb,
Back still animates nothing, and a page with no bar is untouched at the
pane's padding edge. 85 JS tests pass.

The verification that let this through checked the page's top against my own
model of the layout rather than against the previous build, and the model
omitted margin collapsing. The before/after element diff above is the check
that would have caught it.
This commit is contained in:
KeelrLeek committed 2026-07-28 17:40:22 -05:00
1 parent 8017c0d877
commit 35729c1f9e
4 files changed
+59 -15

No files matched your search

+14 -4
View File
@@ -44,7 +44,12 @@
here and lives above whichever view is current — see the note at the top
of GlassBar.svelte for what a per-view instance cost. Views that do not
show it yet hide it; the component is never unmounted, so its crumb
memory survives every navigation, including out to Home and back. */
memory survives every navigation, including out to Home and back.
A page added here MUST also take `padding-top: var(--pane-pad)`: the bar
cancels the pane's top padding so a page's artwork can run underneath it,
and the page restores that inset for its own content. Without it the page
renders 24px too high; with it but not listed here, 24px too low. */
const BAR_VIEWS = new Set(["catalogArtist", "catalogAlbum"]);
initEvents();
@@ -72,8 +77,9 @@
<main class="content" use:floatScroll inert={previewUi.open || undefined}>
<!-- First child of the scroll pane: the bar's sticky pin and its
negative bleed margins are measured against THIS element's
padding, and its net flow height is zero, so the page below still
starts exactly where it did when each view carried its own. -->
padding. It consumes no flow height of its own AND cancels the
pane's top padding, so the page after it begins flush with the top
of the pane and its artwork runs under the bar. -->
<GlassBar hidden={!BAR_VIEWS.has(ui.view)} />
<CurrentView />
</main>
@@ -137,12 +143,16 @@
away with the content, keeping the seam colors identical at every
scroll position. */
background-attachment: local;
/* The pane's top padding, named because three other rules are tied to
it: the glass bar bleeds up over it, cancels it for what follows, and
each page under the bar restores it as its own padding-top. */
--pane-pad: 24px;
border-radius: var(--radius);
overflow-y: auto;
overflow-anchor: none;
/* Main scroll pane: no elastic rubber-band at its edges. Inner regions
deliberately do NOT set this, so their wheel chains here. */
overscroll-behavior: none;
padding: 24px 28px;
padding: var(--pane-pad) 28px;
}
</style>
+31 -11
View File
@@ -125,23 +125,43 @@
<style>
.glassbar {
--bar-h: 46px;
/* Sticky rather than absolute, so the bar pins to the top of the pane
and stays there as the page scrolls. The negative margins reproduce
an absolute bleed while keeping the bar in normal flow: -24/-28 bleed
it up over the pane's 24px top padding and out to its left/right
edges, and margin-bottom -22 makes its net flow height ZERO
(-24 + 46 - 22 = 0) so the hero beneath it is not pushed down.
`top: -24px` pins it flush against the very top of the pane, over
that padding. All four numbers are tied to the pane's own 24/28px
padding — change one and the seam opens. */
and stays there as the page scrolls, and `top` pins it flush against
the very top of the pane over that padding.
The negative margins reproduce an absolute bleed while keeping the
bar in normal flow, and they do TWO jobs:
margin-top -pane-pad : bleed up over the pane's top padding
(and -28 out to its left/right edges)
margin-bottom -bar-h : consume no flow height of its own, and
cancel the pane's top padding for what follows
so the net flow contribution is -pane-pad, and the PAGE's box after
it starts flush with the top of the pane — which is what lets a hero
run underneath the bar rather than beginning below it. Each page
under the bar restores its own content inset with
`padding-top: var(--pane-pad)`; see the note by BAR_VIEWS in
App.svelte.
This used to come from margin collapsing instead: the bar was each
page's first child, and its negative top margin collapsed through the
page root and pulled it up. Hoisting the bar out ended the collapse
and dropped every artist-page hero layer 24px (the `.hero-bg` stack
is absolutely positioned against `.artist-page`), so the geometry is
stated here rather than emerging from where the bar happens to sit.
All of these are tied to the pane's own padding — change one and the
seam opens. */
position: sticky;
top: -24px;
margin: -24px -28px -22px;
top: calc(-1 * var(--pane-pad));
margin: calc(-1 * var(--pane-pad)) -28px calc(-1 * var(--bar-h));
/* Above the artist page's pinned identity (30) and hero (20): the bar
keeps the top of the pane to itself and the parked wordmark slides
under it. */
z-index: 40;
height: 46px;
height: var(--bar-h);
display: flex;
align-items: center;
gap: 12px;
+8
View File
@@ -758,6 +758,14 @@
</div>
<style>
/* The glass bar cancels the pane's top padding so a page's box starts
flush with the top of the pane and its artwork can run under the bar.
This page's hero is in normal flow rather than positioned against the
root, so it only needs that inset handed back. */
.album-page {
padding-top: var(--pane-pad);
}
.hint {
color: var(--text-secondary);
padding-top: 10vh;
+6
View File
@@ -4932,6 +4932,12 @@
.artist-page {
position: relative;
isolation: isolate;
/* The glass bar cancels the pane's top padding so this page's box
starts flush with the top of the pane — that is what lets the hero
run UNDER the bar, and the `.hero-bg` stack is positioned against
this box. The inset the pane would have given us comes back here, so
the flow content below the hero is unmoved. */
padding-top: var(--pane-pad);
/* Written by heroScroll(). --p is the ride-up progress, 0 at the top
of the page; the three lengths are measured from the DOM and are all