Native scroll position drives a GSAP lerp each frame via gsap.ticker. The wrapper is position: fixed and translates opposite to scroll with translateY(−currentY).
current += (target − current) × 0.08 — each frame, current moves 8% of the remaining gap toward target. Fast start, graceful deceleration.
A div.scroll-spacer is sized to the wrapper's scrollHeight so the page has its natural scrollable distance. Without it, the fixed wrapper would make the page unscrollable.
Pure gsap.ticker — no ScrollTrigger needed. The ticker fires every animation frame at a consistent rate, decoupled from scroll events.
0.04 feels dreamlike and slow. 0.15 feels snappy. 0.08 is a comfortable middle ground used here.