Slide & fade
Translate + opacity keyed to entry. It rises into place as it enters.
// live demo
Parallax, reveals, and scrub progress with zero JavaScript driving the animation. The browser is the engine:animation-timeline: scroll() ties the progress bar and parallax layers to the page, animation-timeline: view()with animation-range reveals each panel as it enters, and@property interpolates the custom props that morph the pinned scene. Scroll is the only input.
Heads up: this browser doesn’t support CSS Scroll-Driven Animations yet. Everything below is laid out and fully readable — it just won’t move. Try a recent Chrome, Edge, or Firefox to see scroll drive the whole page.
// view() timeline
No IntersectionObserver. No scroll listener. Every card below carries its own view() timeline and ananimation-range like entry 0% cover 40% — the platform reveals it as it crosses the fold and freezes it there.
Translate + opacity keyed to entry. It rises into place as it enters.
A @property-registered scale interpolates from 0.82 to 1 across the entry range.
Rotate + skew unwinding to flat. Pure declarative keyframes, no easing library.
Same effect, later in the grid — so it triggers later, naturally staggered by layout.
Reveals over cover instead of entry — tied to time on screen.
This one also dims toward exit 100% — scroll past and it settles back.
// scroll() scrub
This panel sticks while you scroll a tall track. A singlescroll(root block) timeline drives a registered--p custom property from 0 to 1, and that one value morphs the shape’s rotation, scale, border-radius, and hue. Rewind by scrolling up — it’s the timeline, not a play-once.
// why it matters
Scroll-driven animations run in the browser’s compositor, not in a requestAnimationFrame loop you have to budget. That means no scroll-listener layout thrash, no GSAP, no ScrollTrigger — and it degrades to a clean static layout where the API isn’t present.