Zero-dependency · Zero JavaScript

This page
scrolls itself.

A progress bar, counters that count up, a pinned reveal sequence, a staggered gallery — every motion below is driven by native CSS scroll-timelines. Our source ships zero JavaScript — the entire page is style rules. (View-source shows one <script>, but it isn't ours — it's Cloudflare's bot-detection beacon, injected at the edge. Our index.html has none.)

The stack: animation-timeline: scroll() / view() + @property for animatable counters + position: sticky for the pin. Chromium-first (Chrome/Edge/Arc); Safari and Firefox get a clean static fallback via @supports — nothing ever breaks.

scroll ↓

Counters that count themselves.

No requestAnimationFrame, no IntersectionObserver — a CSS custom property with an integer syntax, animated by scroll position, rendered through a counter.

of this page's motion is CSS-only
experiments now live in the Labs
lines of JavaScript on this page
Beat one

Keep scrolling.
This stays pinned.

Beat two

Three beats,
one sticky section.

Beat three

Then it
releases naturally.

A staggered gallery,
no library.

Each card's animation-range starts a touch later than the last — the stagger effect everyone reaches for a JS library to get.

> source verification
[GREP] grep -c "<script" index.html → 0 (our authored source)
[CSS] animation-timeline: scroll(root) · view() · @property × 3 · position: sticky
[EDGE] live domain injects 1 script we did not write:
Cloudflare JavaScript Detections, /cdn-cgi/challenge-platform/
zone-wide bot protection, not a dependency of this page
[FALLBACK] @supports not (animation-timeline: view()) → static, fully readable, nothing broken
Drop 011 · complete

The browser
already knows how.

Most scrollytelling ships a library. This page is proof the platform caught up — for the right brief, that's a real performance and reliability win, not just a party trick.

Back to the Labs →