Video product demos are a crutch that locks your landing page into a frozen artifact. That was the realization hit by developer Spanti when exporting a 15-second screen recording for a landing page—3.4 MB on disk, no way to pause at a specific scene, and it would play regardless of whether the user had prefers-reduced-motion enabled. The fix? Delete the MP4 and rebuild the walkthrough as a scripted GSAP animation running entirely in the DOM. The result came in under 40 KB gzipped.
Why Single Timelines Win
The first instinct was multiple timelines—one for the cursor, one for the cards, one for scene transitions. They drifted apart within seconds. Switch tabs and come back, and the cursor ended up clicking empty space where elements had moved. The solution was a single gsap.timeline() using labels as the skeleton, named after what the user does rather than what animates: 'addClick' marks when the cursor clicks Add, 'navClick' marks when it clicks Notes. Everything else positions relative to these with offsets like 'addClick+=0.16'. The critical benefit: inserting a new scene between two labels requires zero downstream recalculation.
Making Cursors Feel Human
Watch the demo closely and you notice the cursor never moves at constant speed—real hands decelerate into targets, so every movement uses power2.out easing. Short hops take 0.4–0.6 seconds; cross-screen travel takes 0.7–1.0 seconds; entering from off-screen gets a full second. The click itself is where craftsmanship matters: the cursor squeezes to 88% scale on press while a ripple bursts outward, then releases with back.out(2.2) overshoot—causing it to slightly exceed full size before settling, like a finger lifting off glass. Swap that for power2.out and the click looks mechanical. That micro-overshoot is the detail your eye expects from a real hand.
State Changes Stay Instant
When the cursor clicks 'Notes' in the demo, three things happen simultaneously: the card grid fades out (0.24s), the list view fades in (0.28s), and sidebar highlighting switches. The first two are animated. The third is instant—handled via classList rather than GSAP. Animating nav highlights makes interfaces feel laggy, not smooth. Spanti also uses autoAlpha instead of opacity everywhere: at zero, GSAP sets visibility:hidden, pulling invisible elements out of tab order and screen readers. Plain opacity:0 leaves ghost elements capturing clicks.
The Loop Reset Problem
The first loop revealed a trap: every card was already visible when the animation restarted, the overlay was still showing, and the cursor sat in the wrong position. The timeline's end state became the second loop's start state. The fix is an explicit reset block at timeline position 0 that restores every animated property—cursor position, card visibility and transforms, overlay opacity, typed text content. Miss one property and it shows immediately on loop two. Spanti admits missing rotation the first time; the cards snapped to tilted positions before animating, a subtle jump that took twenty minutes to track down.
Architecture for Maintainability
The production version at costumary.com spans 1,800 lines across five files: film-script.ts handles data (scenes, cursor paths, timings), film-primitives.tsx contains DOM primitives like the frame and cursor SVG, film-panels.tsx renders each tab's content, film-demo.tsx orchestrates the entire GSAP choreography, and animation-provider.tsx provides React context for play/pause/restart controls. Crucially, GSAP code lives in exactly one file; everything else is inert markup with data-film-* attributes. Designers can rearrange the reference board without touching the timeline.
The Numbers Don't Lie
The comparison table tells the story: GSAP core weighs 28 KB gzipped and shares across all animations on a page. A single demo component adds 5.5 KB gzip. Both demos combined hit 37 KB gzip—everything in Spanti's article. By contrast, a 15-second 1080p H.264 MP4 runs 2–4 MB uncompressed further. WebM (VP9) gets down to 1–2 MB but stays roughly 30x larger than the GSAP approach. The GIF option? Easily 8–15 MB with no pause button, zero accessibility, and looping whether users want it or not. Spanti's full production animation at costumary.com—four scenes with multi-cursor collaboration, sidebar morphing, and typed AI prompts—ships under 40 KB where a screen recording would have hit 3.4 MB.
When This Approach Makes Sense
This is not a universal replacement for video. If your demo involves real user data, logged-in dashboards, or workflows that change weekly, record a video—the maintenance burden of keeping scripted timelines synced with live products will destroy you. Physical products, people talking, anything outside the browser: obvious video territory. But for product walkthroughs of stable UI you want to feel alive? Onboarding sequences where the cursor must hit exact targets? Landing pages where animations are the heaviest asset? The 85x file size reduction plus accessibility support and interactivity make scripted GSAP demos the clear winner—and now there's an agent skill (gsap-choreography) for building these with Claude Code or Cursor.
Key Takeaways
- Single gsap.timeline() prevents drift across scenes; use labels named after user actions, not animations
- Cursor clicks need back.out(2.2) overshoot on release—it mimics real finger lift-off and removes mechanical feel
- State changes via classList stay instant; only visual transitions belong in the timeline
- Loop resets must explicitly restore every animated property or subtle jumps appear on iteration two
- 40 KB gzipped beats 3.4 MB MP4 on size, accessibility, interactivity—use video only when content changes frequently
The Bottom Line
Video demos are lazy defaults that sacrifice performance, accessibility, and interactivity for production convenience. GSAP choreography takes more upfront engineering, but for stable product walkthroughs the payoff in bundle size and user experience is undeniable—and with agent-assisted build tools now available, there's less excuse than ever to ship a frozen 3.4 MB artifact when you could have something alive.