How it works
The whole system is one formula: progress p equals scrollTop divided by the scrollable height (scrollHeight minus clientHeight). A passive scroll listener reads the position, normalizes it, and writes outputs derived from p: the progress bar's height via scaleY, the dot's position via translateX along a rail, and the accent color via an hsl hue that rotates with p. Because every frame recomputes from the current scroll position rather than accumulating state, reversibility and pausing are automatic - there is no play-head to sync, only a function.
Modern browsers offer an even cleaner path: CSS scroll-driven animations, where animation-timeline: scroll() binds a keyframes animation to the scroller in pure CSS with zero JavaScript. The JS approach in this demo is the compatibility baseline that behaves identically everywhere; mention both to your AI and let it feature-detect.
How to use this prompt
Ideal for long-page storytelling: chapter progression on marketing sites, features lighting up section by section, reading progress indicators. Send the prompt listing which visuals should bind to progress - bars, parallax images, counters - and the AI generates the mapping. To target modern browsers first, add: prefer animation-timeline with a JS fallback.
Tips for better results
Do not map p linearly to visuals; wrap it in an easeOut so constant-speed scrolling produces perceived acceleration - instant premium feel. iOS rubber-band scrolling produces negative scrollTop values, so clamp p to the 0-1 range or the dot escapes the rail. Recompute scrollable height on resize, ideally through a ResizeObserver. And when dozens of elements follow the same progress, write all their values inside a single requestAnimationFrame loop instead of binding separate scroll listeners - callback count drops by an order of magnitude.