Animations & GSAP
How scroll effects, hero animations, and ScrollTrigger initialization work on the marketing site.
The home page uses GSAP for hero entrance animations, scroll-triggered reveals, and the pinned section stack. Effects must measure layout after fonts and images settle — especially on the first prerendered load.
Initialization
Plugin registration happens once via app/lib/gsap.ts (imported from app/root.tsx):
ScrollTriggerand@gsap/reactuseGSAPare registered before any component mounts.waitForLayout()inapp/lib/wait-for-layout.tswaits fordocument.fonts.ready, decodes eager or already-loaded images in scope (skipsloading="lazy"images that have not started fetching — they would deadlockimg.decode()and block pin init), then double-requestAnimationFramebefore measuring. Late lazy loads are handled by imageloadlisteners inPinnedSectionStack.scheduleScrollTriggerRefresh()runsScrollTrigger.refresh()after layout settle,window.load, and fonts — used bySmoothScroll.notifyGsapLayoutReady()dispatchestarg:gsap-layout-readyafter the pinned stack finishes pin setup so scroll reveals can refresh.
Scroll reveals
useScrollReveal (app/hooks/useScrollReveal.ts) scopes data-reveal-group / data-reveal queries to the hook’s ref (not the whole document), waits for layout, creates ScrollTriggers, then refreshes and plays any triggers already in view.
Listen for targ:gsap-layout-ready or call refreshScrollTrigger() + playActiveScrollTriggers() after layout-changing work (e.g. pin stack init).
Pinned section stack
PinnedSectionStack defers pin timeline creation until waitForLayout on the stack root, then calls notifyGsapLayoutReady() when pin metrics are applied. Below lg (1024px) it skips pin/fake-scroll and still notifies layout ready.
Manual verification
- Hard refresh (empty cache) on
/— hero wordmark sweep and tagline/marquee should animate without a second load. - Scroll through pinned sections — reveals should fire on first visit; pin scrub should stay aligned with scroll.
- Resize across the
lgbreakpoint — pin should enable/disable without stale triggers. - Enable
prefers-reduced-motion— content should appear without motion, no invisible stuck elements.