Install
$ agentstack add skill-leo-atienza-atlas-claude-cinematic-web-engine ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Cinematic Web Engine
When to Use This Skill
Load when building a premium web experience that uses MORE THAN ONE animation library simultaneously. If you are only using GSAP, only using Motion, or only using Three.js, load the individual skill instead. This skill teaches how to orchestrate multiple animation tools without conflicts, clock drift, or performance regressions.
This skill extends Vanguard (SK-083). Load Vanguard first for Render Tiers and CSS-First principles, then load this skill for the animation orchestration layer.
Individual skill references:
- GSAP Core (SK-042) + Advanced (SK-044) — timelines, ScrollTrigger, plugins
- Lenis (SK-048) — smooth momentum scroll
- Motion (SK-047) — React component animation
- Anime.js (SK-093) — lightweight batch animation
- Barba.js (SK-094) — MPA page transitions
- Spline (SK-095) — design-driven 3D
- Three.js/R3F (SK-007) — code-driven 3D
The Three Laws of Cinematic Web
Law 1: One Clock (SALA)
All animation systems read from GSAP's ticker. No independent requestAnimationFrame loops. One clock = zero drift between scroll position and animation progress.
Law 2: One Owner
Each DOM element is animated by exactly one tool. GSAP and Motion both write to element.style — if two tools animate the same property on the same element, they fight. One tool per element, no exceptions.
Law 3: One Budget
Total animation JavaScript void; }
export function initSALA(): SALAInstance { // 1. Lenis reads from GSAP's ticker (NOT its own RAF) const lenis = new Lenis({ autoRaf: false }); lenis.on('scroll', ScrollTrigger.update);
const lenisRaf = (time: number) => lenis.raf(time * 1000); gsap.ticker.add(lenisRaf); gsap.ticker.lagSmoothing(0);
// 2. Three.js/R3F: set frameloop="never" on Canvas, // then advance from GSAP ticker (see R3F SALA Sync section)
// 3. Anime.js: use engine.tick() in GSAP ticker if needed
return { lenis, destroy() { lenis.destroy(); gsap.ticker.remove(lenisRaf); }, }; }
### React / Next.js SALA Provider
```tsx
// providers/SALAProvider.tsx
'use client';
import { useEffect, useRef, createContext, useContext } from 'react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { ReactLenis } from 'lenis/react';
import type { LenisRef } from 'lenis/react';
gsap.registerPlugin(ScrollTrigger);
const SALAContext = createContext } | null>(null);
export function SALAProvider({ children }: { children: React.ReactNode }) {
const lenisRef = useRef(null);
useEffect(() => {
function update(time: number) {
lenisRef.current?.lenis?.raf(time * 1000);
}
gsap.ticker.add(update);
gsap.ticker.lagSmoothing(0);
return () => gsap.ticker.remove(update);
}, []);
return (
{children}
);
}
export const useSALA = () => useContext(SALAContext);
R3F SALA Sync Component
// components/SALASync.tsx
'use client';
import { useEffect } from 'react';
import { useThree } from '@react-three/fiber';
import gsap from 'gsap';
export function SALASync() {
const advance = useThree((state) => state.advance);
useEffect(() => {
const tick = () => advance(performance.now() / 1000);
gsap.ticker.add(tick);
return () => gsap.ticker.remove(tick);
}, [advance]);
return null;
}
// Usage:
//
//
// {/* Scene content */}
//
Layer Ownership Model
Extended from Vanguard's Animation Layer Cake. Each layer has a single owner — the tool responsible for animating elements at that level.
Layer 5 — 3D Immersion
Code-driven: Three.js / R3F + Drei (SK-007)
Design-driven: Spline (SK-095)
Sync: frameloop="never", advanced from GSAP ticker via SALA
Layer 4 — Scroll Choreography
Owner: GSAP ScrollTrigger + Lenis (SK-042/044 + SK-048)
Use for: Pinned sections, scrubbed timelines, parallax, SplitText reveals
Sync: Lenis → GSAP ticker via SALA
Layer 3 — Batch & Lightweight
Owner: Anime.js (SK-093)
Use for: >20 element stagger reveals, text effects, simple micro-sequences
Fallback: ScrollTrigger.batch() if Anime.js not in project
Layer 2 — React Component
Owner: Motion / Framer Motion (SK-047)
Use for: Modals, drawers, tabs, cards, list reorders, hover states, exit animations
Note: Motion owns React component lifecycle animation
Layer 1 — CSS-Native (ZERO JavaScript)
Owner: Pure CSS
Use for: Transitions on transform+opacity, scroll-driven animations (animation-timeline: view())
Cost: 0 bytes JS, GPU-composited
Layer 0 — Page Transitions
MPA: Barba.js (SK-094) — GSAP timeline hooks, prefetch
SPA/Next.js: ViewTransition API + Motion AnimatePresence
Sync: Barba kills ScrollTriggers + Lenis on leave, recreates on enter
Ownership Conflict Resolution
When two tools could handle the same element:
| Conflict | Resolution | Reason | |----------|-----------|--------| | GSAP vs Motion on same element | GSAP for scroll-driven, Motion for interaction-driven | GSAP owns scroll axis, Motion owns user input | | Anime.js vs GSAP for stagger | Anime.js if >20 elements + simple; GSAP if timeline control needed | Anime.js is lighter for batch operations | | CSS vs any JS for hover | CSS always wins for hover states | Zero JS, zero overhead | | CSS animation-timeline vs ScrollTrigger | CSS for simple reveals, ScrollTrigger for orchestrated sequences | CSS is T0 (free), ScrollTrigger is T3 | | Spline vs R3F | Spline for designer-owned visuals, R3F for developer-owned features | Ownership follows who iterates on the asset | | Motion vs GSAP for modal | Motion for standard modals, GSAP if complex choreography | Motion's AnimatePresence handles exit natively |
Cardinal rule: If you catch yourself adding a second animation tool to an element, stop and choose one.
Motion Token System
Unified easing/timing tokens that produce visually identical motion across all libraries. Define once, use everywhere — ensures consistent motion language across the entire site.
// lib/motion-tokens.ts
export const cinematicTokens = {
snappy: {
gsap: { duration: 0.4, ease: 'back.out(1.4)' },
motion: { type: 'spring' as const, stiffness: 400, damping: 30 },
anime: { duration: 400, easing: 'easeOutBack' },
css: '0.4s cubic-bezier(0.34, 1.56, 0.64, 1)',
},
standard: {
gsap: { duration: 0.6, ease: 'power3.out' },
motion: { type: 'spring' as const, stiffness: 300, damping: 25 },
anime: { duration: 600, easing: 'easeOutCubic' },
css: '0.6s cubic-bezier(0.33, 1, 0.68, 1)',
},
gentle: {
gsap: { duration: 0.8, ease: 'power2.out' },
motion: { type: 'spring' as const, stiffness: 200, damping: 20 },
anime: { duration: 800, easing: 'easeOutQuad' },
css: '0.8s cubic-bezier(0.25, 1, 0.5, 1)',
},
cinematic: {
gsap: { duration: 1.2, ease: 'expo.out' },
motion: { type: 'spring' as const, stiffness: 100, damping: 20 },
anime: { duration: 1200, easing: 'easeOutExpo' },
css: '1.2s cubic-bezier(0.16, 1, 0.3, 1)',
},
} as const;
// Usage examples — consistent motion regardless of which library owns the element:
// GSAP (Layer 4 — scroll choreography)
gsap.to('.hero-text', { ...cinematicTokens.cinematic.gsap, y: 0, autoAlpha: 1 });
// Motion (Layer 2 — React component)
//
// Anime.js (Layer 3 — batch stagger)
animate('.card', { translateY: [30, 0], opacity: [0, 1], ...cinematicTokens.standard.anime });
// CSS (Layer 1 — zero JS)
// .element { transition: transform ${cinematicTokens.standard.css}; }
Rule: cinematicTokens.snappy for ALL interactive elements (buttons, cards, toggles). Never ease-in-out on anything a user touches — always ease-out for responsiveness.
Performance Budget
Total Animation Budget: 120KB gzipped
├── GSAP Core + ScrollTrigger: ~52KB (mandatory — the animation backbone)
├── Lenis: ~3KB (mandatory — smooth scroll)
├── Motion: ~18KB (if React project)
├── Anime.js: ~17KB (optional — Layer 3 batch animations)
├── Remaining for 3D: ~30KB (budget for 3D runtime)
│ ├── Three.js/R3F: lazy-loaded, code-split (not in initial bundle)
│ └── Spline: lazy-loaded, IntersectionObserver (not in initial bundle)
└── Barba.js: ~7KB (MPA only, not needed in SPA)
Frame Budget
16ms per frame (60fps). SALA guarantees a single RAF, so all animation work must complete within one frame:
GSAP ticker fires →
1. Lenis.raf() updates scroll position (~0.1ms)
2. ScrollTrigger.update() reads scroll, fires triggers (~0.5ms)
3. GSAP tweens execute (~1-3ms)
4. Anime.js animations execute (~0.5ms)
5. R3F advance() renders 3D scene (~2-8ms)
─────────────────────────────────
Total: must stay under 16ms
Core Web Vitals Targets
- **LCP 50ms
- **CLS {
const fps = gsap.ticker.fps; if (fps (null!);
useGSAP(() => { gsap.to(meshRef.current.rotation, { y: Math.PI * 2, scrollTrigger: { trigger: '.scene-section', start: 'top top', end: 'bottom bottom', scrub: 1, }, }); gsap.to(meshRef.current.position, { z: -5, scrollTrigger: { trigger: '.scene-section', start: 'top top', end: 'bottom bottom', scrub: true, }, }); });
return (
); }
### Recipe 4: Motion + GSAP on the Same Page
Ownership boundaries — Motion handles React components, GSAP handles scroll:
```tsx
// Modal (Motion owns this — AnimatePresence for exit)
{isOpen && (
{/* Modal content */}
)}
// Scroll section (GSAP owns this — ScrollTrigger for scrub)
// These are DIFFERENT elements — no ownership conflict
useGSAP(() => {
gsap.to('.parallax-bg', {
y: -200,
scrollTrigger: { trigger: '.section', scrub: true },
});
});
Recipe 5: Anime.js Batch Reveals + GSAP Scroll Detection
Use GSAP ScrollTrigger for scroll detection, Anime.js for the element animation:
import { animate } from 'animejs';
import { stagger } from 'animejs/stagger';
ScrollTrigger.batch('.reveal-card', {
onEnter: (elements) => {
animate(elements, {
opacity: [0, 1],
translateY: [40, 0],
delay: stagger(60),
...cinematicTokens.standard.anime,
});
},
start: 'top 85%',
once: true,
});
prefers-reduced-motion Strategy
Unified approach across all 6 libraries. One gsap.matchMedia() context handles everything:
const mm = gsap.matchMedia();
mm.add('(prefers-reduced-motion: reduce)', () => {
// 1. Kill Lenis smooth scroll — use native scroll
lenis?.destroy();
// 2. Set all GSAP animations to instant
gsap.globalTimeline.timeScale(100);
// 3. Disable Anime.js animations
// (don't initialize Anime.js animations in this context)
// 4. Spline/Three.js — render static frame only
// (set frameloop="demand" and don't call advance)
// 5. Motion — set instant transitions
// Pass { duration: 0 } to all motion transitions
// 6. CSS — browser handles prefers-reduced-motion natively
// (no action needed if using @media queries)
return () => {
// Cleanup when user toggles preference back
};
});
mm.add('(prefers-reduced-motion: no-preference)', () => {
// Full cinematic experience
initSALA();
initAnimations();
});
Debugging & Diagnostics
GSAP DevTools (Dev Only)
if (process.env.NODE_ENV === 'development') {
gsap.registerPlugin(GSDevTools);
GSDevTools.create({ animation: masterTimeline });
}
ScrollTrigger Markers
ScrollTrigger.defaults({ markers: process.env.NODE_ENV === 'development' });
Lenis Velocity Monitor
lenis.on('scroll', ({ velocity }) => {
if (Math.abs(velocity) > 10) console.warn('High scroll velocity:', velocity);
});
Three.js Stats
import Stats from 'three/addons/libs/stats.module.js';
const stats = new Stats();
document.body.appendChild(stats.dom);
gsap.ticker.add(() => stats.update());
Frame Budget Overlay (Dev Only)
if (process.env.NODE_ENV === 'development') {
const overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed;top:0;right:0;background:black;color:lime;padding:4px 8px;font:12px monospace;z-index:9999';
document.body.appendChild(overlay);
gsap.ticker.add(() => {
overlay.textContent = `${gsap.ticker.fps.toFixed(0)}fps`;
overlay.style.color = gsap.ticker.fps < 55 ? 'red' : 'lime';
});
}
Best Practices
- Initialize SALA before any animations — it must be the first thing that runs
- Never let two animation tools write to the same element's properties
- Use Motion Tokens for consistent motion language across all libraries
- Lazy-load 3D (Three.js/R3F and Spline) — never in the initial bundle
- Kill all ScrollTriggers and Lenis before page transitions
- Use
gsap.matchMedia()as the single source of truth for responsive + reduced-motion - Monitor frame budget in development — fix drops before they ship
- Cap DPR at 1.5 for 3D scenes — users can't perceive the difference above this
Do Not
- Run multiple
requestAnimationFrameloops — use SALA - Animate the same CSS property from two different tools on the same element
- Ship GSDevTools or ScrollTrigger markers to production
- Use Spline for more than one scene per page
- Use Barba.js in a React/Next.js SPA — use ViewTransition API
- Exceed 120KB gzipped for total animation JavaScript
- Skip
prefers-reduced-motionhandling — it's an accessibility requirement - Use
ease-in-outon interactive elements — always use ease-out for responsiveness
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Leo-Atienza
- Source: Leo-Atienza/atlas-claude
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.