Repository navigation
fix(docsite): pin the desktop hero with sticky so overscroll can bounce again - #5542
imdreamrunner wants to merge 1 commit into
Conversation
…unce again Step 2 of #5392; closes #5470. #5415 restored pull-to-refresh on mobile and left the suppression rule gated to desktop widths: @media (min-width: 1024px) { html { overscroll-behavior-y: none } } That rule exists because three home-hero layers were `position: fixed` — `heroContent`, `HeroFloatingCards.stage` and `HeroThemeReel.backdropGlow`. A fixed layer is glued to the viewport and is not part of the document, so when the document rubber-bands past its own bottom edge the layer does not lift with it. At max scroll the fixed glow occupies the entire viewport, so any bottom overscroll necessarily paints hero art in the exposed gap. `position: sticky` pins the same way but stays in the document: it lifts with the page and cannot paint below the document's own bottom edge. It is clamped to its containing block, though, and the pinned layers sit inside the 760px hero band — on their own they would unpin as soon as the band scrolled past. `HeroPinRail` is that containing block: `absolute; inset: 0` against `heroScope` (hero + showcase), so a sticky child stays pinned for exactly as long as the pin-and-cover effect runs and releases where the opaque showcase already covers it. Being absolute it contributes no height, so `heroSpacer` goes on reserving the band and the document is the same size. Below 1024px the rail is `display: contents` — no box at all — so the narrow hero lays out exactly as if it were not in the tree. The glow now shares the cards' pin layer instead of pinning itself, which drops both of its media-query branches, and `stage` becomes `absolute` inside that layer. `navBackdrop` stays `fixed`: it covers the header strip, which sits above `heroScope` in flow, so there is no box inside the hero for a sticky band to stick to — and at a header-height tall it never reaches the bottom-overscroll gap. With nothing fixed left in the hero's art, `overscroll-behavior-y: none` is deleted outright rather than narrowed, and desktop gets its native rubber-band back. Test plan: Production builds of `main` and this branch served side by side (`NEXT_PUBLIC_DOCS_TARGET=latest`, so no canary banner), driven with Playwright at a device scale factor of 1 with transitions disabled. Pixel-identical to `fixed` at scroll 0 / 100 / 200 / 400 / 760 / 1400 / 2200 / max, at 1280x900, 1024x768, 1440x1400, 1280x600, 768x1024 and 390x844, in light and dark mode — 0 differing pixels in every frame bar a handful of 4/255 antialiasing pixels that also appear at 390px, where the change applies to nothing. Document height, `scrollWidth` and CTA hit-testing unchanged at every sample. Overscroll gap emulated by appending an N-px transparent spacer and scrolling N further, which reproduces the rubber-band frame exactly (the scrolled content lifts, `fixed` layers stay put). At 1280x900 the gap went from 74% hero art — glow, a floating card and the theme dots — to 0 differing pixels of anything: no `fixed` layer intersects the gap on any of /, /docs/getting-started, /components, /components/Button, /templates, /themes, /blog, /changelog, /community or /playground. `getComputedStyle(html).overscrollBehaviorY` goes `none` -> `auto` at desktop widths and stays `auto` at 390/768. Theme swap mid-scroll, resize while scrolled, `prefers-reduced-motion`, auto-advance and hover-to-pause all match `main` exactly. Gates: `pnpm -F @astryxdesign/docsite test` (403 passing), `typecheck`, `build`, eslint and prettier on every changed file. Docsite-only, so CI's `check-scope` skips the heavy jobs and no changeset is required. Not covered: Safari, and a real trackpad. The emulation matches Chromium's model (fixed layers stay, scrolled content lifts) and proves layout equivalence; the *feel* of the restored bounce wants a look on a Mac.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
AI review status for this pull request.
|
There was a problem hiding this comment.
Thanks for the careful write-up: the sticky rail keeps the hero identical at rest, and the bottom overscroll gap is clean on this branch. Approval waits on a maintainer to provide or identify the governing spec for restoring native overscroll at desktop widths on every Docsite route, since no current spec covers page-level overscroll. Separately, a top bounce on the home page leaves the fixed navBackdrop band at the top of the gap and drops the nav onto the plain page background (on the dark slide its light text sits on white), so the nav tint needs to stay attached to the nav during overscroll. The branch also needs a rebase, because page.tsx conflicts with main.
[Automated review]
| // hero for a sticky band to stick to. It is only a header-height tall, so it | ||
| // never reaches the bottom-overscroll gap the pinned art used to bleed into. |
There was a problem hiding this comment.
True for the bottom gap, but with the overscroll rule gone a top bounce moves the transparent nav down with the document while this fixed band stays at the top: the slide tint is left in the gap and the nav sits on the plain page background (on the Gothic slide its light ink lands on white). Could the tint travel with the header during overscroll, for example from a layer that scrolls with it?
| * `position: sticky` pins the same way but stays in the document, so it lifts | ||
| * with the page and cannot paint below the document's own bottom edge — no | ||
| * suppression rule needed. The catch is that sticky is clamped to its |
There was a problem hiding this comment.
At the bottom of the page the pinned glow (1,050px tall, stuck 48px into a 900px viewport) reaches about 198px past the document's bottom edge; what keeps it out of the bounce gap is .astryx-layout-content's overflow: clip. Switching that clip to visible brings the bleed back with no error, so it is worth naming here as load-bearing next to the hidden/auto/scroll warning below.
Summary
Step 2 of #5392 — closes #5470. Desktop gets its native overscroll bounce back, and
overscroll-behavior-y: noneis deleted rather than narrowed.#5415 fixed the mobile half and left the suppression gated to desktop:
That rule exists because three home-hero layers were
position: fixed—heroContent,HeroFloatingCards.stageandHeroThemeReel.backdropGlow. A fixed layer is glued to the viewport and is not part of the document, so when the document rubber-bands past its own bottom edge the layer does not lift with it. At max scroll the fixed glow occupies the whole viewport, so any bottom overscroll paints hero art in the exposed gap.The fix
position: stickypins identically but stays in the document: it lifts with the page and cannot paint below the document's own bottom edge. That is structural, not empirical, which is why the rule can go entirely.Sticky is clamped to its containing block, and the pinned layers live inside the 760px hero band, so on their own they would unpin the moment the band scrolled past. The new
HeroPinRailis that containing block:absolute; inset: 0againstheroScope(hero + showcase), so a sticky child stays pinned for exactly as long as the pin-and-cover effect runs and releases where the opaque showcase already covers it. Being absolute it contributes no height —heroSpacerstill reserves the band and the document is the same size.Below 1024px the rail is
display: contents: no box at all, so the narrow hero lays out exactly as if it were not in the tree. Nothing about mobile changes.Two simplifications fall out:
positionandtopbecome unconditional);stagebecomesabsoluteinside that layer instead offixed.navBackdropstaysfixedon purpose: it covers the header strip, and the header sits aboveheroScopein flow, so there is no box inside the hero for a sticky band to stick to. At a header-height tall it never reaches the bottom gap.Verification
Production builds of
mainand this branch served side by side (NEXT_PUBLIC_DOCS_TARGET=latest, so no canary banner), driven with Playwright at DPR 1 with transitions disabled.Pixel-identical to
fixedat every scroll position and viewport:(differing pixels; repeated in dark mode with the same result. A handful of 4/255 antialiasing pixels turn up nondeterministically, including at 390px where the change applies to nothing.) Every layer's rect matches to the tenth of a pixel — only the
positionkeyword differs. Document height,scrollWidthand CTA hit-testing are unchanged at every sample.The gap. A bottom rubber-band is emulated by appending an N-px transparent spacer and scrolling N further, which reproduces the frame exactly: the scrolled content lifts,
fixedlayers stay put.htmloverscroll-behavior-y≥1024pxnoneautoBefore, the exposed strip shows the aurora glow, a floating card and the theme dots below the footer. After, it is the page background and nothing else.
Nothing else on the site bleeds. The rule was app-global, so I checked every route for
fixedchrome that would now show in the gap:/,/docs/getting-started,/components,/components/Button,/templates,/themes,/blog,/changelog,/community,/playground— 0 painters on all ten. The docs sidebar is sticky, not fixed.Live behaviour matches
main— theme swap mid-scroll (layers hold at 33,48 through the swap), resize while scrolled and back,prefers-reduced-motion, auto-advance, hover-to-pause over both the hero column and the gutters, and the CTA hit test.Gates:
pnpm -F @astryxdesign/docsite test(403 passing),typecheck,build, eslint and prettier on every changed file. Docsite-only, socheck-scopeskips the heavy jobs and no changeset is required.The trap this leaves behind, and where it is written down
fixedbroke if an ancestor gainedtransformorfilter.stickybreaks additionally on an ancestoroverflow: hidden|auto|scroll— silently, with no error: the hero just stops pinning and nobody notices until they look at the home page..astryx-layout-content(AppShell) already carriesoverflow: clip, which is safe (clip creates no scroll container) but is one property value away from un-pinning the whole landing page. That warning is inHeroPinRail's file header, which is where anyone touching this will be.Not covered
Chromium only, and no real trackpad. The emulation matches Chromium's model and proves layout equivalence; the feel of the restored bounce, and Safari, want a look on a Mac — the preview deploy is the place to do it.