Lazy scene mounting
Demos that wait until you look
The catalog grid used to mount every demo on first paint — 289 live React subtrees on /components, each with its own timers and animations, running in rows nobody had reached yet. Now they mount when a card comes within one screen of the viewport, and the numbers below are the two builds measured side by side.
Measured from the completed build on 2026-09-12 06:33 UTC (build eoCv1yhT4VQe) by npm run build && npm run measure, and committed as docs/build-report.json. The numbers are not re-derived while the page renders, because a build cannot see its own output.
/prompts HTML weight
963.6 → 500.6 KB
48% less markup
Demos deferred in /prompts
116
0 mounted in the built HTML; the rest wait for the viewport
Work avoided on load
289
demo subtrees on /components that no longer mount off-screen
What changed
Catalog cards and prompt posters now hold their demo back until the card is within one screen of the viewport, then mount it for good. The placeholder reserves the exact height, so nothing reflows when a demo appears.
- 1. The page renders the card — title, kind, scores, stack, link — and a placeholder marked data-lazy-state="pending".
- 2. On the client, one IntersectionObserver per demo watches with a 320px root margin; the observer is created inside a frame so nothing is set from an effect body and layout is settled first.
- 3. When the card approaches, the demo mounts and the observer disconnects — it never re-observes, because a mounted demo is not going back.
The trade-off, stated rather than hidden
Mounting needs JavaScript. With JavaScript disabled the placeholders never fill in, so a no-JS visitor sees the catalog without animations. That is a real cost, accepted because the animation is decoration over information that stays in the HTML — and measured on the no-JavaScript page, where all 289 component titles are still present. The alternative, mounting everything eagerly, is what the previous build did, and it is the number on the left.