facebook / facebook/astryx

docsite: contain the desktop-pinned hero layers so overscroll suppression can be removed entirely

Open
#5,470 0 comments 0 reactions 0 assignees View on GitHub
area:docs
Dominant language
TypeScript
Stars
13.1k
Forks
1.1k
Avg merge
1d 14h
Merged PRs (30d)
669

Description

## Summary

Step 2 of #5392. #5415 restored pull-to-refresh on mobile by bounding the hero's aurora glow below 1024px and scoping the overscroll suppression to desktop:

```css
@media (min-width: 1024px) {
html { overscroll-behavior-y: none; }
}
```

That was always meant to be a stepping stone, not the destination. At ≥1024px three layers are still `position: fixed` — `heroContent` (`(site)/page.tsx`), `HeroFloatingCards.stage`, and `HeroThemeReel.backdropGlow` — so the suppression has to stay, and macOS trackpads still get no native rubber-band in Safari, Chrome or Firefox. That is the case #3032 was actually filed for.

This issue is to finish the job: contain the desktop-pinned layers so `overscroll-behavior-y: none` can be **deleted outright** rather than gated.

## Why `sticky` is the answer

A `fixed` layer is glued to the viewport and is not part of the document. When the document rubber-bands past its own bottom edge, the fixed layer does not lift with it — so it is sitting in the exposed gap. That is the bleed.

A `sticky` layer *is* in the document. It lifts with it, and **cannot paint below the document's own bottom edge**. That is structural rather than empirical, which is why it lets the rule be removed instead of narrowed.

Concretely, at max scroll today the fixed glow occupies viewport y=48–1098 — the entire viewport — so any bottom overscroll necessarily paints it.

## Prototype: the effect survives

DOM surgery on a dev build, canary banner hidden to match production, one `position: absolute; inset: 0` rail per layer as a direct child of `heroScope`, with `backdropGlow` + `stage` + `heroContent` all `position: sticky; top: var(--appshell-header-height)`:

| scroll | 0 | 100 | 200 | 400 | 760 | 1400 | 2200 | max |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| pixels differing from `fixed` | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

Document height unchanged (3540px). Verified at 1280×900, 1280×600, 1440×1400 and 1024×768. Layer geometry matches `fixed` everywhere except max scroll, where sticky releases — invisible, because the opaque `showcaseOverlay` already covers the hero there. Hero CTA hit-testing unaffected (`elementFromPoint` returns the button before and after).

So the tuned pin-and-cover effect is reproducible with `sticky`. The work is in the plumbing, not the design.

## Two traps, both paid for already

**1. The rails must go in the right place.** The pinned layers are not children of `heroScope`. Its only two children are `swipeArea` (`HeroReelProvider`'s root, 760px, holding every hero layer) and `showcaseOverlay`. A sticky layer inside `swipeArea` releases after ~48px, so each layer needs a containing block spanning hero + showcase.

Insert each rail **immediately before `showcaseOverlay`**, not at the front of `heroScope`. Inserting at the front puts the layers behind `themeFill` (opaque, still inside `swipeArea`, later in tree order) and produces a 29%-of-pixels difference at scroll 0 that looks exactly like sticky failing but is pure paint order.

**2. `display: contents` on `swipeArea` is NOT a shortcut — I tried it.** `` already renders `display: contents`, so `swipeArea` is the only real box between the layers and `heroScope`, which makes "just make `swipeArea` contents and flip the three layers to sticky" look like a two-line fix. Measured, it is not:

- document height 3540 → **5302px** (+1762): `heroContent` becomes in-flow and adds its own height on top of `heroSpacer`, which still reserves 760px
- `heroContent` lands at viewport y=1858 at scroll 0 — below the fold, after the spacer
- glow `x` shifts 40 → **−520**: `left: 50%` + `translateX(-50%)` no longer resolves the way it did

Anyone taking this will reach for that shortcut. It needs the rails, plus `heroSpacer` bookkeeping and a centering change.

## Scope

- Three `absolute; inset: 0` rails as direct children of `heroScope`, inserted before `showcaseOverlay`; the pinned layers move into them. This means the layers currently rendered by `HeroReelCards` inside `HeroReelProvider` have to be reachable as direct children of `heroScope` while keeping reel context and ``.
- `heroContent`, `stage`, `backdropGlow`: `fixed` → `sticky` at ≥1024px.
- Centering: `left: 50%` + `transform: translateX(-50%)` → `margin-inline: auto` inside a full-width rail (verified equivalent: x=40, w=1200 either way).
- `heroSpacer` height bookkeeping once the hero is sticky rather than fixed.
- Delete `@media (min-width: 1024px) { html { overscroll-behavior-y: none } }` from `apps/docsite/src/app/globals.css`.

## Add a comment where it will save someone

`fixed` breaks if an ancestor gains `transform` or `filter`. `sticky` breaks **additionally** if any ancestor gains `overflow: hidden` or `overflow: auto` — silently, no error, the hero just stops pinning and nobody notices until they look at the home page.

`.astryx-layout-content` (AppShell) already carries `overflow: clip`. That is safe — `clip` creates no scroll container — but it is one property *value* away from un-pinning the entire landing page. Worth a comment on that rule. (It is also why the `box-shadow` slab workaround in #5392 fails.)

## What is not yet proven

The prototype covered static scroll positions with transitions and animations disabled, in Chromium only. Untested: the theme-swap animation mid-scroll, resize while scrolled, `prefers-reduced-motion`, and **Safari** — which is the browser the rubber-band actually matters in.

Layout equivalence is proven; the *feel* of the tuned effect is not. That is the careful visual pass #3032 asked for, and it is the real cost here.

## Worth sizing honestly

Desktop has no pull-to-refresh. The entire payoff is native macOS trackpad rubber-band — the mobile fix restored a **function**, this restores a **feel**. Windows and Linux have no vertical bounce, so the rule is already a no-op there. It is a refactor of a hand-tuned effect for a polish win on the site's front door: worth doing deliberately, not worth rushing.

Contributor guide

Open the contributing guide

Research direction

Start with `(site)/page.tsx`, `HeroFloatingCards`, `HeroThemeReel`, and `apps/docsite/src/app/globals.css`; trace `heroScope`, `swipeArea`, `showcaseOverlay`, and `heroSpacer` before changing the layer structure. Reproduce the prototype at the listed viewport sizes, then verify sticky layers preserve geometry and CTA hit-testing, the overscroll rule is removed, and the effect remains correct during theme swaps, resizing, reduced motion, and Safari testing.

Written by the indexing model from the issue text.

Assessment

Tech stack
css, typescript
Domain
frontend, web-dev
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.