ether-scroll
Skill0 starsChanged 28 days agoMITView on GitHub
Lenis + GSAP ScrollTrigger bridge for Astro + three.js sites built on the ether engine (ether/scroll exports ScrollBridge + createScrollProgress). Documents the rAF-shared loop, conditional smooth-scroll by quality tier, the canonical trigger patterns (scroll-progress via the engine factory; class-toggle and body-flag inline), and the scroll-progress-to-3D handoff. Triggers on Lenis, ScrollTrigger, smooth scroll, scroll-driven animation, scroll progress, scroll-bound camera, scroll-bound shader uniform, parallax, scroll pinning, scrollytelling, scroll restoration, scroll cue, scroll cascade, persistent canvas + scroll, scroll-driven shader uniform. Do NOT use for non-scroll GSAP timelines, CSS scroll-snap, basic anchor-link smooth scrolling, or multi-beat camera moves across a scroll story (ether-threejs owns camera choreography).
In this repository3 other files it gives its agents.
- ether-shadersSkill
- ether-threejsSkill
- studio-onboardSkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 20
Do NOT invoke for:
- Very specific: line 64
After implementing, run premium-review.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
ether-scroll
Technique reference for the Lenis + GSAP ScrollTrigger stack in the ether engine. Invoke before writing scroll-driven motion on a site built on it.
Symbol names are the durable references. Paths cited here are ether repo paths (src/...); your site's consumer scene is referred to by role.
When to use
Any of:
- Adding a new section that binds visual state to scroll position.
- Modifying an existing ScrollTrigger in a scene's
setupScrollTrigger(). - Wiring a shader uniform, camera transform, or DOM class to scroll progress.
- Diagnosing scroll feel — jank, mis-firing triggers, ghost updates after view transitions.
Do NOT invoke for:
- Pure CSS scroll-snap or
scroll-timeline()work (those don't need ScrollTrigger). - Sites that don't use
ether/scroll(this skill encodes its bridge architecture).
Read first
recipes.mdin this directory — ten numbered recipes.- The engine bridge at
src/scroll/—ScrollBridge(Lenis lifecycle, idempotent plugin registration, seconds→ms raf) andcreateScrollProgress(page progress 0..1 → callback, raw and unsmoothed). - Your site's scene
setupScrollTrigger()— the consumer.
Hard rules
- The bridge is ticked from the engine's render loop, not its own. Your scene's
tick(time)callsthis.scroll?.raf(time)—ScrollBridgeconverts seconds→ms internally; never multiply at the call site. Never calllenis.start(). Two rAF loops produce one-frame lag between scroll and 3D. - The
ScrollBridgeis conditional onquality.enableSmoothScroll. Null on LOW tier and under prefers-reduced-motion — touch gets the bridge (LenissyncTouchsmooths on top of native iOS momentum, the fix for choppy mobile scroll-to-3D; it does NOT hijack scroll). When null, ScrollTrigger falls back to native scroll automatically — don't paper over the null with a fake bridge. Progress triggers still work without it:createScrollProgressregisters the plugin itself. - Construct the bridge at
enterTransitionSTART, never the scene constructor. Lenis intercepts wheel from the moment it exists but only moves the page when its raf is pumped — andtick()only runs once the scene is the manager's activeScene, after preload. A constructor-built bridge eats every wheel event for the whole preload window, then lurches when ticking starts. Native scroll covers input until enter. - Triggers born mid-range deliver RAW progress at creation. If the user scrolled during the intro, every trigger writes its full progress into the scene's
*Targetfields the instantsetupScrollTrigger()runs — while the live values still sit at their construction-time rest states.tick()'s smoothing then eases the world from rest to the live scroll state through the exact same path as normal scrolling. No separate catch-up tween is needed: the target/live split is the catch-up. - Progress triggers come from
createScrollProgress(ether/scroll), not hand-rolledScrollTrigger.create. Call it with noscrub. Event-style triggers (class/attr toggles) stay inline — site-specific DOM hooks. - ScrollTriggers go through
setupScrollTrigger(), called from introonComplete, not from the constructor and not frompreload(). Triggers measure layout at construction time; create them after the intro has finished modifying layout. history.scrollRestoration = 'manual'lives inline in your layout's<head>. Do not move it to a module script. Do not remove it. Without it, mid-scroll reloads causeonUpdate(>0)to fire on first render and snap state mid-animation.- Every trigger you create has a paired
.kill()in the scene's teardown helper (and the bridge a.destroy()). Same commit. Centralize the kills in one privatekillTriggers()and call it from every exit path —exitTransition()above all, which runs synchronously insideastro:before-swap, ahead of Astro's DOM swap — plusdispose()as the backstop. View transitions (transition:persist) ghost old triggers if the exit path skips this. - Pull thresholds and smoothing settle times from your constants module when reusable. Inline only with a one-line comment explaining the value (e.g. a
top 72%reveal threshold tuned to a section's margin).
Slop indicators (do not ship)
- A second
requestAnimationFrameloop driving scroll updates. lenis.start()called anywhere.ScrollTrigger.updatebound directly towindow.scrollevents.new Lenis(...)or a bare page-progressScrollTrigger.createin site code —ether/scrollowns those primitives.raf(time * 1000)at a call site — the bridge converts; double-scaling teleports the scroll.- ScrollTriggers created in module scope or in the scene constructor.
- Mutating three.js objects directly from
onUpdate(bypassestick()'s deltaTime-aware path). - Passing
scrubtocreateScrollProgressand expecting eased motion — it is inert on an onUpdate-only trigger (GSAP builds a scrub tween only for an attachedanimation; the callback always receives raw progress). Smooth intick()instead. onTogglecollapsing enter + exit into one callback when the intent is asymmetric.- A ScrollTrigger created without a matching
.kill()in the scene's teardown helper. history.scrollRestorationset in a module script instead of inline-head.
Procedure for a new scroll-driven section
- Pick the pattern. Scroll-progress via
createScrollProgress(§4 in recipes), class-toggle on threshold (§5), or body data-flag (§6). Often you need two — a progress trigger for 3D, class-toggle for CSS reveals. - Create the trigger inside
setupScrollTrigger()— not in the constructor. Store the instance on the scene (this.fooTrigger). - Write the RAW value into a
*Targetfield inonUpdate, and ease the live value toward it intick(). Never mutate three.js objects fromonUpdate, and never reach forscrubto do the easing. - Add the
.kill()tokillTriggers()in the same commit. Always paired. - Test the LOW-tier fallback — Lenis null, native scroll. Scrub feel changes but values must still drive correctly.
After implementing, run premium-review.
After the skill
- Point Claude at
ether-threejsif the scroll-bound value is a shader uniform. - Point at
ether-shadersif the value drives material parameters. - Recipes in
recipes.mdshow the copy-paste skeletons.
Files
SKILL.md— this file (the script).recipes.md— ten numbered technique recipes.evals/triggers.json— should/shouldn't-trigger regression set for the description.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

