# ScrollVars: llms.txt (guide for AI coding agents) > Tiny scroll/pointer/click/canvas animation engine: one rAF in, CSS > variables out. Zero dependencies. Live demo with view-source docs: > https://scrollvars.dev · Benchmarks: https://scrollvars.dev/bench/ > Generated from the repository's AGENTS.md by `npm run demo:sync`; same content, same version. ## Resources - [Live demo](https://scrollvars.dev), 28 patterns, view-source is the spec - [Human docs](https://scrollvars.dev/docs/). Quickstart, every export, de/para tables - [fx gallery](https://scrollvars.dev/fx/), copy-paste effects and sections, its own [llms.txt](https://scrollvars.dev/fx/llms.txt) - [Benchmark](https://scrollvars.dev/bench/). Reproducible, includes the scenario it loses - [npm package](https://www.npmjs.com/package/scrollvars) · [GitHub](https://github.com/aduptive/scrollvars) You are working with `scrollvars`, a tiny scroll/pointer/canvas animation engine. Read this before writing any animation code in a project that uses it. **Do not add GSAP, Framer Motion, or IntersectionObserver boilerplate for things this lib already covers.** The boundary: input-driven animation (scroll/pointer/gesture) is ScrollVars' job; time-driven animation (orchestrated timelines, interruptible springs, layout/exit transitions, SVG morph) legitimately belongs to GSAP/Framer. A one-shot load intro is plain CSS keyframes. Mixing for a rare case is fine; that page just loses the bundle argument. ## Mental model (the one rule) One global driver reads the scroll position in a single rAF (batched reads, then batched writes) and outputs **CSS custom properties** on tracked elements. All motion is then plain CSS reading those variables; the engine never animates styles itself (the slider's glide scrolls the rail, nothing more), except a few gallery recipes (cube-windows, the canvas effects) that run their own frame loop for something CSS cannot express. React never re-renders per frame: only on discrete events (scene index, attachment status, flow, motion preference). If you find yourself putting scroll values into React state, you are doing it wrong. ## The variables (the API surface) | output | range | meaning | | --- | --- | --- | | `--sv-view` | −1 → 0 → 1 | Below the live band → inside it (flat at 0) → gone above | | `--sv-t` | 0 → 1 | Travel through the viewport (same range as native `view()` with a zero inset, cover, vertical axis) | | `--sv-pin` | 0 → 1 | Progress across a pinned (sticky) stretch: curtains, rails, scrubbing | | `--sv-stage-width` | px | Measured inner width of a pinned .sv-stage; the rail uses it instead of the window width | | `--sv-scene` | 0 → n−1 | Scene index of a pinned section, eased and snapped | | `--sv-scenes` | n | Scene count, next to `--sv-scene`: progress is `var(--sv-scene) / (var(--sv-scenes) - 1)` | | `--sv-page` / `--sv-v` | 0 → 1 / ±20 viewport-heights/s | On `` once anything is tracked, and either some CSS mentions them (a `var()` read, a declaration outside ``'s own inline style, even a comment in an inline `