# Cinematic Web Engine

> A Claude skill from Leo-Atienza/atlas-claude.

- **Type:** Skill
- **Install:** `agentstack add skill-leo-atienza-atlas-claude-cinematic-web-engine`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Leo-Atienza](https://agentstack.voostack.com/s/leo-atienza)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Leo-Atienza](https://github.com/Leo-Atienza)
- **Source:** https://github.com/Leo-Atienza/atlas-claude/tree/main/skills/_archived/cinematic-web-engine

## Install

```sh
agentstack add skill-leo-atienza-atlas-claude-cinematic-web-engine
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## 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

```tsx
// 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.

```typescript
// 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:

```typescript
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:

```typescript
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)

```typescript
if (process.env.NODE_ENV === 'development') {
  gsap.registerPlugin(GSDevTools);
  GSDevTools.create({ animation: masterTimeline });
}
```

### ScrollTrigger Markers

```typescript
ScrollTrigger.defaults({ markers: process.env.NODE_ENV === 'development' });
```

### Lenis Velocity Monitor

```typescript
lenis.on('scroll', ({ velocity }) => {
  if (Math.abs(velocity) > 10) console.warn('High scroll velocity:', velocity);
});
```

### Three.js Stats

```typescript
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)

```typescript
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 `requestAnimationFrame` loops — 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-motion` handling — it's an accessibility requirement
- Use `ease-in-out` on 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](https://github.com/Leo-Atienza)
- **Source:** [Leo-Atienza/atlas-claude](https://github.com/Leo-Atienza/atlas-claude)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-leo-atienza-atlas-claude-cinematic-web-engine
- Seller: https://agentstack.voostack.com/s/leo-atienza
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
