artist page: end the pane's scroll at the content (clip the 300vh sheet out of the scroll range)

The pane could scroll ~850px (1971px at a tall window) past the last content
pixel: at max scroll ZERO of 11 release rows were on screen, the whole viewport
empty page.

The sheet is 300vh of absolutely positioned decoration, and an abs-positioned
descendant extends its scroller's scrollable overflow. .content's scrollHeight
tracked the sheet's box — pin flow origin + the sheet's live translate + 300vh —
instead of the page. Because that translate is scroll-driven, the range also
breathed with scroll position and depended on scroll history: the same scrollTop
measured 4167, 4329 and 4368 depending on the route taken. Hiding the sheet gave
exactly 1797, the page plus the pane's padding-top.

Wrap the sheet in a .sheetclip box with overflow: clip, sized to the pane's
client box, so the sheet paints exactly the region the pane can show and
contributes nothing to the scroll range.

- The clip box is ABSOLUTE and the pin stays height: 0. Sizing the pin itself
  and cancelling with a negative margin-bottom leaves +24px behind — the margin
  collapses against the neighbouring height-0 pins and walks the hero zone from
  24 to 48. Out of flow there is no margin to collapse, and the sticky
  constraint is untouched.
- overflow-clip-margin is not an option for the sheet's 28px bleed: it
  re-expands the overflow region right back to 2440. The bleed moves onto the
  clip box instead.
- The sheet's offsets are now clip-box relative. --band-from-pin is
  pin-relative and the box sits --pin-origin above the pin, so the two cancel:
  --band-from-pin + --pin-origin is --band-top.
- --frame-h (measure()'s scroller.clientHeight) becomes unconditional. It fed
  the scroll-driven-only .fade-frame; .sheetclip needs it on both branches.
  Setting it on the fallback path is inert — .fade-frame's rule lives inside
  the @supports block and never matches there.

Verified live against the pre-change rendering at identical scrollTop, each
comparison bracketed by two baseline captures so the hero's 20s rotation could
not be mistaken for a difference: 0 differing pixels at four of five stops (the
fifth is one chip's hover ring, 83px), hero zone and scrollbody unmoved at every
stop. Scroll range is now content-exact with the clip box flush to the pane at
1280x800, 1600x1100, 1100x620 and 1920x1400, and across artists with 4, 10, 11
and 22 rows. At max scroll all 11 rows are on screen instead of none.
This commit is contained in:
KeelrLeek committed 2026-07-24 17:51:24 -05:00
1 parent 2eb1cf57e3
commit 0878659f6a
1 file changed
+51 -5
+51 -5
View File
@@ -1166,8 +1166,13 @@
const hostFlowTop = zone.offsetTop + zone.offsetHeight - 18;
const fadeS = Math.max(0, hostFlowTop - cutScreen);
page.style.setProperty("--fade-s", `${fadeS.toFixed(1)}px`);
page.style.setProperty("--frame-h", `${scroller.clientHeight}px`);
}
// Unconditional: .fade-frame is scroll-driven-only, but .sheetclip
// sizes itself from --frame-h on BOTH branches (it is what keeps the
// 300vh sheet out of the pane's scroll range). Setting it on the
// fallback path is otherwise inert — .fade-frame's rule lives inside
// the @supports block and never matches there.
page.style.setProperty("--frame-h", `${scroller.clientHeight}px`);
// The extras' hit-gate sentinel parks at the scrollTop where
// p crosses 0.42. Probed live (2026-07-24, this WebView2): the
// observers' rootBounds top coincides with the page's box top
@@ -3409,7 +3414,9 @@
the wash of rounds 7-10: fewer layers, nothing to hand over
between them, and a baked gradient moved as a whole cannot
seam. -->
<div class="sheetpin" aria-hidden="true"><div class="sheet"></div></div>
<div class="sheetpin" aria-hidden="true">
<div class="sheetclip"><div class="sheet"></div></div>
</div>
<!-- LAYER 30 — THE IDENTITY. The block's bottom IS --edge at every
scroll position, so the wordmark and the Play/chips row are
@@ -4084,13 +4091,52 @@
seams of rounds 7-10 all came from recomputing stops against the
scroll. This one element replaces the scrim, the veil, the eraser and
the wash. */
.sheet {
/* THE SHEET'S CLIP BOX — the reason the pane's scroll range equals its
content. The sheet is 300vh of absolutely-positioned decoration, and an
abs-positioned descendant extends its scroller's SCROLLABLE OVERFLOW:
measured, .content's scrollHeight tracked the sheet's box (pin flow
origin + the sheet's live translate + 300vh) instead of the page, so the
pane scrolled ~850px past the last content pixel — at max scroll ZERO of
11 release rows were on screen, the whole viewport empty page. Worse, the
sheet's translate is scroll-driven, so the range BREATHED with scroll
position and depended on scroll history (same scrollTop measured 4167,
4329 and 4368 by route; hidden .sheet gave exactly 1797 = content).
`overflow: clip` here stops that propagation at the clip box's own
geometry — the region the pane can actually show — so the sheet paints
identically and contributes nothing.
ABSOLUTE, and the pin stays height:0: sizing the PIN itself and cancelling
with margin-bottom:-100% leaves +24px behind (the negative margin
collapses against the neighbouring height-0 pins and walks the hero zone
24 → 48). Out of flow, there is no margin to collapse and the sticky
constraint is untouched.
The box IS the pane's client box: --pin-origin up to undo the pane's
padding-top (the pins stick that far below the pane's visible top), and
one pane tall. --frame-h is measure()'s scroller.clientHeight; the 100vh
fallback only covers the pre-measure first paint, where a too-tall clip
is harmless (it just clips less). */
.sheetclip {
position: absolute;
top: var(--band-from-pin);
top: calc(-1 * var(--pin-origin));
/* The sheet's full-bleed lives here now — the clip edge has to sit on
the pane's padding edge, or it would shave the bleed off instead. */
left: -28px;
right: -28px;
height: var(--frame-h, 100vh);
overflow: clip;
pointer-events: none;
}
.sheet {
position: absolute;
/* In clip-box coordinates. --band-from-pin is pin-relative and the box
sits --pin-origin above the pin, so the two terms cancel to the
band's own offset: --band-from-pin + --pin-origin ≡ --band-top. */
top: var(--band-top);
left: 0;
right: 0;
/* Far past any scrollport bottom; the gradient's tail is opaque page
colour, so extra height is just more page. */
colour, so extra height is just more page. Free now — the clip box
keeps it out of the scrollable overflow however tall it is. */
height: 300vh;
pointer-events: none;
/* The glide is EASED (p²), not linear — the user could see the