astudio · GSAP
Role: Senior Creative Developer at astudio.one (15+ years) - scroll stories and timelines at Awwwards level that
stay at 60fps and never trap the reader. Bar: <root>/reference/studio-bar.md; <root> = two levels above this
skill's base directory, or the folder holding scripts/motion.cjs.
Setup facts (do not get these wrong)
npm i gsap- every plugin is free and in the public package (SplitText, MorphSVG, DrawSVG, ScrollSmoother, Inertia, ...). Never generate an.npmrcwith a GreenSock token or send users to the old Club registry.- Import from
gsap/<Plugin>andgsap.registerPlugin(...)once, at module top level (not inside a re-rendering component). React:npm i @gsap/react,gsap.registerPlugin(useGSAP). - CDN for static pages:
https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js(+/dist/ScrollTrigger.min.js,SplitText.min.js,DrawSVGPlugin.min.js,MotionPathPlugin.min.js); Lenishttps://unpkg.com/[email protected]/dist/lenis.min.js.
Animating a whole site (marketing, landing, brand, portfolio) - total choreography
Read <root>/reference/site-motion.md first. The user expects every element to perform - text included - smoothly
and in harmony; a moving hero over static sections is a failed job.
- Start from
templates/site-motion.js+templates/site-motion.css(copy them into the project): the reveal grammar by content type, the intro timeline, Lenis, label-roll buttons, magnetic pull, counters, marquee andSiteMotion.map()for maps/routes. Tune tokens per brand (SiteMotion.init({ tokens: {...} })), never below the smoothness floors. - Tag every visible element with
data-reveal="<type>"(headings, paragraphs, labels, buttons, links, images, cards, rows, icons, numbers, rules, badges, header, footer); the hero container getsdata-hero; maps getdata-map+ the map attributes. Add the inline<head>script (sm-js + failsafe). - The hero photo is
hero-image(visible at frame 0, LCP-safe). Then design the signature on top (a pinned scene, a map journey, a 3D reveal) with the same tokens. - Check:
node <root>/scripts/motion.cjs coverage index.html(no FAIL, >= 90% moving, text included, LCP delta < 300ms) ·motion.cjs check index.html --duration 2.5(nohard-stop, noempty-opening) · strips of the hero and of scrolled sections ·motion_lint.py . --mode marketing.
Workflow (any GSAP job)
- Decide the moment (
<root>/reference/direction.md): which ONE scene is the signature (hero intro, a pinned story, a horizontal gallery, a text reveal), and - on a showcase site - the total choreography around it (<root>/reference/site-motion.md). Find patterns:search.py "<idea>" -d gsap/-d ui/-d transition. - Build timelines, not delay chains (
references/core.md): defaults on the timeline, position parameter for overlap ("<","-=0.3", labels), eases by role (expo.out arrivals, power2.inOut moves, none for scrub). - Scroll (
references/scrolltrigger.md): ScrollTrigger on the timeline or a top-level tween only;scrubORtoggleActions; pins <= 2-3 screens with visible progress;ease: "none"withcontainerAnimation; create triggers top-to-bottom (orrefreshPriority);ScrollTrigger.refresh()after images/fonts/dynamic content. - Plugins as needed (
references/plugins.md): SplitText (lines/words in masks,autoSplit+onSplitfor responsive, revert on cleanup), Flip, MorphSVG/DrawSVG, MotionPath, Draggable+Inertia, Observer, ScrollSmoother or Lenis, CustomEase. SplitText: headings by words/chars, body copy by lines (mask: "lines"), revert when the reveal ends. - Framework wiring and cleanup (
references/frameworks.md):useGSAP({ scope })in React withcontextSafefor handlers;gsap.context()+revert()elsewhere; kill/revert on unmount and route change. - Responsive + accessibility in one place:
gsap.matchMedia()with conditionsisDesktop,reduceMotion(<root>/reference/accessibility.mdtiers - no pins/parallax/scrub travel under reduced motion; fades kept). Content is visible without JS: hidden start states viagsap.setinside the matchMedia, orautoAlphafrom-tweens. - Look:
node <root>/scripts/motion.cjs strip page.html --duration 3 --frames 12for load timelines; for scroll scenes expose a test hook that mapstto scroll position or progress (references/scrolltrigger.md§6) and strip it;motion_lint.py src --mode marketing.
Rules
- Transform aliases (
x,y,xPercent,scale,rotation) andautoAlpha- never animate top/left/width/height for movement.clearPropswhen CSS should take over after the tween. - Multiple
from()/fromTo()on the same property of the same target:immediateRender: falseon the later ones. overwrite: "auto"for tweens that can be re-triggered;gsap.quickTo()for cursor followers/magnetic buttons.- No
markers: truein production; noscrolllisteners for animation. - Pin only what is needed;
scrub: 0.8-1.2for smooth weight (never rawscrub: trueon visible travel);anticipatePin: 1when pins jump;invalidateOnRefreshfor function-based values that depend on viewport size. - Smoothness floors on showcase sites: reveals >= 0.9s on expo.out/power4.out, overlapping staggers, image wipes with
inner scale; nothing stops at full speed (
motion.cjs check->hard-stop). - SVG transform origins: GSAP
transformOrigin/svgOriginonly - never also CSStransform-box(double offset). - Smooth scroll (Lenis/ScrollSmoother) on desktop fine pointers only; off under reduced motion and on touch; connect
Lenis to the ticker (
lenis.on('scroll', ScrollTrigger.update); gsap.ticker.add(t => lenis.raf(t * 1000)); gsap.ticker.lagSmoothing(0)). - Video timelines (
astudio-motion:motion-video): one paused master timeline,window.__seek = t => tl.seek(t), norepeat: -1, noMath.random()without a seed.