Skill: jacobcwright/open-animate (version 1.0.2)

--- name: open-animate description: Open Animate — the creative suite for AI agents. Create professional motion graphics, generate images, and render MP4 videos. Use when the user wants to make videos, animations, motion graphics, social clips, product launches, explainers, or any visual content. Supports asset generation (images, backgrounds, upscaling) and video composition with animation presets, transitions, and components. license: Apache-2.0 metadata: openclaw: emoji: "\U0001F3AC" homepage: https://open-animate.com requires: bins: - npx install: - kind: node package: oanim bins: - oanim label: "Install oanim CLI (npm)" --- # Open Animate — Creative Suite for Agents Create professional motion graphics and generate visual assets. Describe what you want. Get an MP4. ## Prerequisites This skill builds on **Remotion skills** for core Remotion API patterns: ```bash npx skills add remotion-dev/skills ``` ## Workflow ### 1. Initialize project ```bash npx oanim init my-video cd my-video ``` ### 2. Compose using `@oanim/core` ```tsx import { fadeUp, popIn, Background, SafeArea, palettes } from '@oanim/core'; ``` See `references/animation-cookbook.md` for the full presets reference. ### 3. Preview ```bash npx remotion studio ``` ### 4. Render to MP4 ```bash npx oanim render ``` ### 5. Generate and use media assets (optional) ```bash # Generate image, video, or audio npx oanim assets gen-image --prompt "dark gradient abstract" --out public/bg.png npx oanim assets run --model fal-ai/kling-video/v1/standard/text-to-video \ --input '{"prompt":"cinematic abstract motion","duration":"5"}' --out public/clip.mp4 npx oanim assets run --model fal-ai/stable-audio \ --input '{"prompt":"ambient electronic, no vocals","duration_in_seconds":30}' --out public/music.mp3 ``` Then use in your composition: ```tsx import { Img, OffthreadVideo, Audio, staticFile } from 'remotion'; <Img src={staticFile('bg.png')} style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> <OffthreadVideo src={staticFile('clip.mp4')} style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> <Audio src={staticFile('music.mp3')} volume={0.25} /> ``` ## Capabilities | Capability | Tool | |------------|------| | Project scaffolding | `oanim init` | | Animation presets (fadeUp, popIn, springs) | `@oanim/core` | | Components (Terminal, Card, Badge, GlowOrb) | `@oanim/core` | | Scene transitions (fadeBlur, clipCircle, wipe) | `@oanim/core` | | Typography (AnimatedCharacters, TypewriterText, CountUp) | `@oanim/core` | | Design tokens (5 palettes, fonts, spacing) | `@oanim/core` | | Rendering to video | `oanim render` | | Cloud rendering | `oanim render --cloud` | | AI image generation | `oanim assets gen-image` | | AI video generation | `oanim assets run` (kling, minimax, hunyuan models) | | AI audio generation | `oanim assets run` (stable-audio model) | | Media compositing | `<Img>`, `<OffthreadVideo>`, `<Audio>` via `staticFile()` | | Image editing | `oanim assets edit-image` | | Background removal | `oanim assets remove-bg` | | Image upscaling | `oanim assets upscale` | | Any fal.ai model | `oanim assets run` | ## References - `references/workflow.md` — Step-by-step agent workflow - `references/scene-config.md` — animate.json schema reference - `references/composition-patterns.md` — Multi-scene composition architecture - `references/animation-cookbook.md` — Full `@oanim/core` presets reference - `references/asset-generation.md` — AI asset generation guide - `references/media-guide.md` — Using generated media in compositions (Img, Video, Audio) ## Templates - `templates/launch-video.md` — 4-scene product launch (5s) - `templates/explainer.md` — Step-based explainer video (20s) - `templates/logo-reveal.md` — Logo animation with glow (5s) - `templates/meme-caption.md` — Vertical social clip (6s) - `templates/investor-update.md` — Metrics dashboard (15s)

path: references/animation-cookbook.md
content: # Animation Cookbook

Complete reference for all `@oanim/core` animation presets.

## Element Animations

All element animations take `{ frame, fps, delay?, spring? }` and return `CSSProperties`.

### fadeUp
Fades in while moving up 20px. The default go-to for text and UI elements.
```tsx
import { fadeUp } from '@oanim/core';
<div style={fadeUp({ frame, fps, delay: 0.2 })}>Content</div>
```

### fadeDown
Fades in while moving down 20px. Good for dropdown menus, secondary info.
```tsx
import { fadeDown } from '@oanim/core';
<div style={fadeDown({ frame, fps, delay: 0.2 })}>Content</div>
```

### slideInLeft / slideInRight
Slides in from left/right with fade. Good for side-by-side comparisons.
```tsx
import { slideInLeft, slideInRight } from '@oanim/core';
<div style={slideInLeft({ frame, fps, delay: 0.2 })}>Left panel</div>
<div style={slideInRight({ frame, fps, delay: 0.3 })}>Right panel</div>
```

### popIn
Spring-based scale entrance (0.5→1) with bounce. Great for badges, icons, CTAs.
```tsx
import { popIn } from '@oanim/core';
<div style={popIn({ frame, fps, delay: 0.1 })}>🚀</div>
```

### blurIn
Fades in from blurry to sharp. Cinematic feel, good for hero text.
```tsx
import { blurIn } from '@oanim/core';
<div style={blurIn({ frame, fps, delay: 0.2 })}>Title</div>
```

### elasticScale
Wobbly spring scale entrance. Playful, attention-grabbing.
```tsx
import { elasticScale } from '@oanim/core';
<div style={elasticScale({ frame, fps })}>Boing!</div>
```

### perspectiveRotateIn
3D rotation entrance from below. Dramatic, good for cards and panels.
```tsx
import { perspectiveRotateIn } from '@oanim/core';
<div style={perspectiveRotateIn({ frame, fps, delay: 0.3 })}>Card</div>
```

## Spring Presets

Override the default spring on any element animation:
```tsx
fadeUp({ frame, fps, spring: 'bouncy' })
```

| Preset | Feel | Best for |
|--------|------|----------|
| `snappy` | Quick, precise | Default, UI elements |
| `bouncy` | Playful overshoot | Badges, icons, CTAs |
| `gentle` | Slow, smooth | Backgrounds, large elements |
| `stiff` | Very fast, no overshoot | Micro-interactions |
| `wobbly` | Lots of bounce | Playful animations |
| `smooth` | Medium, no overshoot | Text, panels |
| `poppy` | Fast with slight bounce | Buttons, cards |

## Easing Presets

For `interpolate()` with frame-based animations:
```tsx
import { easings } from '@oanim/core';
import { interpolate } from 'remotion';

const value = interpolate(frame, [0, 30], [0, 100], {
  easing: easings.easeOut,
  extrapolateRight: 'clamp',
});
```

| Preset | Curve |
|--------|-------|
| `easeOut` | Fast start, slow end |
| `circOut` | Circular deceleration |
| `easeInOut` | Smooth S-curve |
| `backOut` | Slight overshoot |
| `expoOut` | Exponential deceleration |

## Transition Presets

For use with `<TransitionSeries.Transition presentation={...}>`:

```tsx
import { fadeBlur, scaleFade, clipCircle } from '@oanim/core';
```

| Preset | Effect |
|--------|--------|
| `fadeBlur()` | Crossfade with blur |
| `scaleFade()` | Scale 0.95→1 with fade |
| `clipCircle()` | Circular reveal |
| `clipPolygon()` | Diamond/polygon reveal |
| `wipe()` | Directional wipe (pass `{ direction: 'left'\|'right'\|'up'\|'down' }`) |
| `splitHorizontal()` | Open from horizontal center |
| `splitVertical()` | Open from vertical center |
| `perspectiveFlip()` | 3D flip |
| `morphExpand()` | Scale up from circle to full |
| `zoomThrough()` | Zoom into next scene |
| `pushLeft()` | Push current scene left |
| `pushRight()` | Push current scene right |
| `slideLeft()` | New scene slides in from right |
| `slideRight()` | New scene slides in from left |

## Typography Components

### AnimatedCharacters
Staggered per-character spring entrance:
```tsx
import { AnimatedCharacters } from '@oanim/core';

<AnimatedCharacters
  text="Hello World"
  delay={0.2}       // seconds before first char
  stagger={0.03}    // seconds between chars
  spring="snappy"   // spring preset
/>
```

### TypewriterText
Character-by-character typing reveal:
```tsx
import { TypewriterText } from '@oanim/core';

<TypewriterText
  text="npm install @oanim/core"
  delay={0.5}
  charsPerSecond={20}
  showCursor={true}
/>
```

### CountUp
Animated number counter:
```tsx
import { CountUp } from '@oanim/core';

<CountUp
  from={0}
  to={10000}
  delay={0.3}
  duration={1}
  prefix="$"
  suffix="+"
  decimals={0}
/>
```

## Helpers

### springValue
Get a 0→1 spring value for custom animations:
```tsx
import { springValue } from '@oanim/core';

const progress = springValue({ frame, fps, delay: 0.2, spring: 'bouncy' });
```

### springInterpolate
Map a spring to a custom range:
```tsx
import { springInterpolate } from '@oanim/core';

const rotation = springInterpolate({ frame, fps }, [0, 360]);
```


path: references/asset-generation.md
content: # Asset Generation

Generate images for your videos using the oanim CLI.

## Setup

Sign in to the oanim platform:
```bash
oanim login
```

## Commands

### Generate an image
```bash
oanim assets gen-image --prompt "dark abstract gradient with purple and blue tones, 16:9" --out public/bg.png
```

### Edit an existing image
```bash
oanim assets edit-image --in public/bg.png --prompt "add subtle grid pattern" --out public/bg-grid.png
```

### Remove background
```bash
oanim assets remove-bg --in public/product.png --out public/product-cutout.png
```

### Upscale 2x
```bash
oanim assets upscale --in public/logo-small.png --out public/logo-2x.png
```

## When to use asset generation

| Scenario | Command |
|----------|---------|
| Background textures/gradients | `gen-image` |
| Product screenshots with edits | `edit-image` |
| Product photos for compositing | `remove-bg` |
| Low-res logos or icons | `upscale` |

## Video generation

```bash
oanim assets run --model fal-ai/kling-video/v1/standard/text-to-video \
  --input '{"prompt":"slow cinematic zoom, abstract flowing shapes, warm tones","duration":"5"}' \
  --out public/clip.mp4
```

Other video models: `fal-ai/minimax-video/video-01-live`, `fal-ai/hunyuan-video`, `fal-ai/kling-video/v1.5/pro/text-to-video`

## Audio generation

```bash
oanim assets run --model fal-ai/stable-audio \
  --input '{"prompt":"minimal ambient electronic, warm pads, no vocals","duration_in_seconds":30}' \
  --out public/bg-music.mp3
```

## Using generated assets in compositions

All generated assets go in `public/` and are referenced via `staticFile()`:

```tsx
import { Img, OffthreadVideo, Audio, staticFile } from 'remotion';

// Image background
<Img
  src={staticFile('bg.png')}
  style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>

// Video background (use OffthreadVideo, not Video — decodes on separate thread)
<OffthreadVideo
  src={staticFile('clip.mp4')}
  style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>

// Global audio track (place outside TransitionSeries for cross-scene audio)
<Audio src={staticFile('bg-music.mp3')} volume={0.25} />
```

Always add a dark overlay between media backgrounds and text:
```tsx
<AbsoluteFill style={{ backgroundColor: 'rgba(0, 0, 0, 0.5)' }} />
```

## Tips

- Generate backgrounds at the video resolution (1920x1080 by default)
- Use `staticFile()` in Remotion to reference files in `public/`
- Remove backgrounds for product shots so they composite cleanly over gradient backgrounds
- For text-heavy videos, generate abstract/blurred backgrounds that don't compete with text
- Use `objectFit: 'cover'` for full-bleed backgrounds
- Keep audio at `volume={0.2}` to `volume={0.3}` for background music


path: references/composition-patterns.md
content: # Composition Patterns

## Multi-scene architecture

### TransitionSeries (recommended)

Use `TransitionSeries` from `@remotion/transitions` for videos with multiple scenes:

```tsx
import { TransitionSeries, springTiming } from '@remotion/transitions';
import { fadeBlur, scaleFade, clipCircle } from '@oanim/core';

export const MyVideo: React.FC = () => {
  return (
    <AbsoluteFill>
      <TransitionSeries>
        <TransitionSeries.Sequence durationInFrames={120}>
          <Scene1 />
        </TransitionSeries.Sequence>

        <TransitionSeries.Transition
          presentation={fadeBlur()}
          timing={springTiming({ config: { damping: 200 }, durationInFrames: 30 })}
        />

        <TransitionSeries.Sequence durationInFrames={120}>
          <Scene2 />
        </TransitionSeries.Sequence>
      </TransitionSeries>
    </AbsoluteFill>
  );
};
```

### When to use Sequence vs TransitionSeries

| Pattern | Use case |
|---------|----------|
| `TransitionSeries` | Multiple scenes with transitions between them |
| `<Sequence>` | Sequencing elements WITHIN a single scene |
| Delay parameter | Staggering elements that appear together |

### Persistent background layer

For a background that stays consistent across transitions, wrap it outside the TransitionSeries:

```tsx
<AbsoluteFill>
  <Background gradient="linear-gradient(135deg, #0a0a0a, #1a1a2e)" />
  <GlowOrb color="rgba(99, 102, 241, 0.2)" x={30} y={40} />

  <TransitionSeries>
    {/* Scenes transition on top of persistent background */}
  </TransitionSeries>
</AbsoluteFill>
```

## Scene structure pattern

Each scene should follow this layering:

```tsx
<AbsoluteFill>
  {/* 1. Background */}
  <Background gradient="..." />

  {/* 2. Ambient elements */}
  <GlowOrb ... />
  <Grid ... />

  {/* 3. Overlays */}
  <Vignette intensity={0.4} />

  {/* 4. Content */}
  <SafeArea style={{ justifyContent: 'center', alignItems: 'center' }}>
    {/* Animated content here */}
  </SafeArea>
</AbsoluteFill>
```

## Duration guidelines

| Video type | Total duration | Scenes | Per scene |
|------------|---------------|--------|-----------|
| Social clip | 5-10s | 2-3 | 2-4s |
| Product launch | 15-30s | 4-6 | 3-5s |
| Explainer | 30-60s | 6-10 | 4-6s |
| Demo | 30-90s | 5-12 | 5-8s |

## Transition timing

- **Fast transitions** (15-20 frames): energetic, snappy feel
- **Medium transitions** (25-35 frames): professional, smooth
- **Slow transitions** (40-60 frames): cinematic, dramatic

Use `springTiming` with high damping (200) for smooth transitions.
Use `linearTiming` when you want constant-speed transitions.

## Media layers

When using AI-generated media (images, video, audio), layer them into the scene structure:

### Video or image background
```tsx
import { OffthreadVideo, Img, staticFile } from 'remotion';

// Video background
<OffthreadVideo
  src={staticFile('clip.mp4')}
  style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>

// Or image background
<Img
  src={staticFile('bg.png')}
  style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>

// Always add dark overlay for text readability
<AbsoluteFill style={{ backgroundColor: 'rgba(0, 0, 0, 0.5)' }} />
```

### Global audio track
Place `<Audio>` at the composition level (outside TransitionSeries) so it plays across all scenes:
```tsx
import { Audio, staticFile } from 'remotion';

<AbsoluteFill>
  <Audio src={staticFile('bg-music.mp3')} volume={0.25} />
  <TransitionSeries>
    {/* Scenes here */}
  </TransitionSeries>
</AbsoluteFill>
```

### Full layer order (back to front)
1. `<OffthreadVideo>` or `<Img>` — media background
2. Dark overlay — `rgba(0, 0, 0, 0.5)`
3. `<GlowOrb>` — ambient glow
4. `<Vignette>` — edge darkening
5. `<SafeArea>` — content
6. `<Audio>` — non-visual, position doesn't matter


path: references/media-guide.md
content: # Working with Media

## The pattern: Generate → Place → Use

Every media type follows the same workflow:
1. **Generate** with `oanim assets`
2. **Place** in `public/`
3. **Use** via `staticFile()` in Remotion

## Images

```bash
oanim assets gen-image --prompt "abstract warm gradient, dark bg, 16:9" --out public/bg.png
```

```tsx
import { Img, staticFile } from 'remotion';

<Img
  src={staticFile('bg.png')}
  style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
```

## Video

```bash
oanim assets run \
  --model fal-ai/kling-video/v1/standard/text-to-video \
  --input '{"prompt":"cinematic zoom, flowing shapes, warm tones","duration":"5"}' \
  --out public/clip.mp4
```

```tsx
import { OffthreadVideo, staticFile } from 'remotion';

<OffthreadVideo
  src={staticFile('clip.mp4')}
  style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
```

Available video models:
| Model | Notes |
|-------|-------|
| [REDACTED_SECRET] | Good quality, 5s clips |
| `fal-ai/kling-video/v1.5/pro/text-to-video` | Higher quality |
| [REDACTED_SECRET] | Fast generation |
| `fal-ai/hunyuan-video` | High quality |

## Audio

```bash
oanim assets run \
  --model fal-ai/stable-audio \
  --input '{"prompt":"minimal ambient electronic, warm pads, no vocals","duration_in_seconds":30}' \
  --out public/bg-music.mp3
```

```tsx
import { Audio, staticFile } from 'remotion';

// Place at composition level (outside TransitionSeries) for global audio
<Audio src={staticFile('bg-music.mp3')} volume={0.25} />
```

## Layer order (back to front)

1. `<OffthreadVideo>` or `<Img>` — media background
2. `<AbsoluteFill style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}>` — dark overlay
3. `<GlowOrb>` — ambient glow
4. `<Vignette>` — edge darkening
5. `<SafeArea>` — content (text, cards, etc.)
6. `<Audio>` — non-visual, position doesn't matter

## Tips

- Use `objectFit: 'cover'` for full-bleed backgrounds
- Always add a dark overlay between media and text
- Keep audio at `volume={0.2}` to `volume={0.3}` for background music
- Use `<OffthreadVideo>` (not `<Video>`) — decodes on separate thread
- Trim clips with `startFrom={30}` to skip frames
- Generate backgrounds at 1920x1080 to match video resolution


path: references/scene-config.md
content: # animate.json Reference

Every oanim project has an `animate.json` at the root that configures rendering.

## Schema

```json
{
  "name": "My Video",
  "compositionId": "MyComp",
  "render": {
    "fps": 30,
    "width": 1920,
    "height": 1080,
    "codec": "h264",
    "crf": 18
  },
  "props": {}
}
```

## Fields

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `name` | string | required | Human-readable name |
| `compositionId` | string | required | Must match `<Composition id="...">` in Root.tsx |
| `render.fps` | number | 30 | Frames per second |
| `render.width` | number | 1920 | Video width in px |
| `render.height` | number | 1080 | Video height in px |
| `render.codec` | string | "h264" | One of: h264, h265, vp8, vp9 |
| `render.crf` | number | 18 | Constant Rate Factor (lower = higher quality, bigger file) |
| `props` | object | {} | inputProps passed to the composition |

## Common presets

### 1080p (default)
```json
{ "width": 1920, "height": 1080, "fps": 30 }
```

### 4K
```json
{ "width": 3840, "height": 2160, "fps": 30 }
```

### Vertical (Reels/TikTok)
```json
{ "width": 1080, "height": 1920, "fps": 30 }
```

### Square (Instagram)
```json
{ "width": 1080, "height": 1080, "fps": 30 }
```

## CLI overrides

All render settings can be overridden via CLI flags:
```bash
oanim render --fps 60 --res 3840x2160 --codec h265 --out out/4k.mp4
```


path: references/workflow.md
content: # Agent Workflow

## Step-by-step process for creating a video

### 1. Understand the brief
- What is the video for? (product launch, explainer, social clip, etc.)
- Duration target? (default: 15s = 450 frames at 30fps)
- Style? (dark/minimal, colorful, corporate, etc.)
- Key messages or scenes?

### 2. Initialize project
```bash
oanim init <project-name>
cd <project-name>
```

### 3. Plan the composition structure
- Break the video into **scenes** (3-5 scenes for a 15s video)
- Each scene has a clear purpose (hook, demo, features, CTA)
- Plan transitions between scenes

### 4. Choose a color palette
```tsx
import { palettes } from '@oanim/core';
// Available: dark, light, midnight, sunset, ocean
const colors = palettes.midnight;
```

### 5. Build scenes
Create each scene as its own component in `src/scenes/`:
```tsx
// src/scenes/Hook.tsx
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from 'remotion';
import { fadeUp, Background, SafeArea, palettes } from '@oanim/core';

export const Hook: React.FC = () => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();
  // ...
};
```

### 6. Add generated media assets (optional)
Generate images, video, or audio, then use them in scenes:
```bash
oanim assets gen-image --prompt "dark abstract gradient" --out public/bg.png
oanim assets run --model fal-ai/kling-video/v1/standard/text-to-video \
  --input '{"prompt":"cinematic abstract motion","duration":"5"}' --out public/clip.mp4
oanim assets run --model fal-ai/stable-audio \
  --input '{"prompt":"ambient electronic, no vocals","duration_in_seconds":30}' --out public/music.mp3
```

Use in Remotion:
```tsx
import { Img, OffthreadVideo, Audio, staticFile } from 'remotion';

// Background image
<Img src={staticFile('bg.png')} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />

// Background video
<OffthreadVideo src={staticFile('clip.mp4')} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />

// Global audio (place outside TransitionSeries)
<Audio src={staticFile('music.mp3')} volume={0.25} />
```

### 7. Compose with TransitionSeries
```tsx
import { TransitionSeries, springTiming } from '@remotion/transitions';
import { fadeBlur, scaleFade } from '@oanim/core';
```

### 8. Layer the scene (back to front)
1. `<OffthreadVideo>` or `<Img>` — media background (optional, from generated assets)
2. Dark overlay — `<AbsoluteFill style={{ backgroundColor: 'rgba(0,0,0,0.5)' }} />`
3. `<Background>` — gradient base (or use as fallback when no media)
4. `<GlowOrb>` — ambient glow elements
5. `<Grid>` — subtle pattern (optional)
6. `<Vignette>` — edge darkening (optional)
7. `<SafeArea>` — content wrapper with safe margins
8. Content — text, cards, terminals, etc.
9. `<Audio>` — background music (non-visual, position doesn't matter)

### 9. Animate content with delays
Stagger elements by increasing the `delay` parameter:
```tsx
<div style={fadeUp({ frame, fps, delay: 0.1 })}>First</div>
<div style={fadeUp({ frame, fps, delay: 0.3 })}>Second</div>
<div style={fadeUp({ frame, fps, delay: 0.5 })}>Third</div>
```

### 10. Preview and iterate
```bash
npx remotion studio
```

### 11. Render
```bash
oanim render --out out/video.mp4
```

## Tips
- Keep scenes focused — one idea per scene
- Use consistent spring presets within a video
- 0.1-0.2s delay between staggered elements feels natural
- Background elements (GlowOrb, Grid) add depth without effort
- Always wrap content in `<SafeArea>` for broadcast-safe margins


path: templates/explainer.md
content: # Example: Step-Based Explainer

A 30-second explainer video walking through a process in numbered steps.

## Structure

| Scene | Duration | Content |
|-------|----------|---------|
| Intro | 4s | Problem statement |
| Step 1 | 5s | First step with visual |
| Step 2 | 5s | Second step with visual |
| Step 3 | 5s | Third step with visual |
| Result | 5s | Outcome / benefit |
| CTA | 3s | Next steps |

## Pattern: Numbered steps

```tsx
const StepScene: React.FC<{ number: number; title: string; description: string }> = ({
  number, title, description
}) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  return (
    <AbsoluteFill>
      <Background gradient="linear-gradient(135deg, #0a0a0a, #1a1a2e)" />
      <SafeArea style={{ justifyContent: 'center', gap: 32 }}>
        <div style={{
          ...popIn({ frame, fps, delay: 0.1 }),
          fontSize: 120,
          fontWeight: 800,
          color: 'rgba(99, 102, 241, 0.15)',
        }}>
          {number}
        </div>
        <div style={{
          ...fadeUp({ frame, fps, delay: 0.3 }),
          fontSize: 48,
          fontWeight: 700,
          color: '#f1f5f9',
        }}>
          {title}
        </div>
        <div style={{
          ...fadeUp({ frame, fps, delay: 0.5 }),
          fontSize: 24,
          color: '#94a3b8',
          maxWidth: 600,
        }}>
          {description}
        </div>
      </SafeArea>
    </AbsoluteFill>
  );
};
```

## Transition choice

Use `wipe()` or `pushLeft()` for step-based content — the directional motion implies forward progression.


path: templates/investor-update.md
content: # Example: Investor Update / Metrics Video

A 20-second video showcasing key metrics with animated counters.

## Structure

| Scene | Duration | Content |
|-------|----------|---------|
| Title | 3s | Company name + period |
| Metrics | 6s | 3 animated KPI cards |
| Growth | 5s | Key achievement highlight |
| Closing | 4s | Thank you + next steps |

## Pattern: Metrics scene

```tsx
const metrics = [
  { label: 'Revenue', value: 125000, prefix: '$', suffix: '' },
  { label: 'Users', value: 5200, prefix: '', suffix: '+' },
  { label: 'Growth', value: 340, prefix: '', suffix: '%' },
];

const MetricsScene: React.FC = () => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  return (
    <AbsoluteFill>
      <Background gradient="linear-gradient(135deg, #0c1222, #162032)" />
      <SafeArea style={{ justifyContent: 'center', alignItems: 'center', gap: 48 }}>
        <div style={{
          ...fadeUp({ frame, fps, delay: 0.1 }),
          fontSize: 36,
          color: '#94a3b8',
          textTransform: 'uppercase',
          letterSpacing: '0.15em',
        }}>
          Q4 2025 Highlights
        </div>

        <div style={{ display: 'flex', gap: 32 }}>
          {metrics.map((m, i) => (
            <Card key={i} delay={0.3 + i * 0.2} style={{ width: 300, textAlign: 'center' }}>
              <div style={{ fontSize: 18, color: '#64748b', marginBottom: 12 }}>
                {m.label}
              </div>
              <CountUp
                to={m.value}
                delay={0.5 + i * 0.2}
                duration={1.5}
                prefix={m.prefix}
                suffix={m.suffix}
                style={{ fontSize: 56, fontWeight: 700, color: '#e0f2fe' }}
              />
            </Card>
          ))}
        </div>
      </SafeArea>
    </AbsoluteFill>
  );
};
```

## Color palette

Use `palettes.ocean` for a professional, corporate feel with blue tones.

## Tips

- `CountUp` component makes numbers feel dynamic
- Stagger cards with increasing delays for a cascade effect
- Keep the design clean — data should be the hero
- Use `palettes.ocean` or `palettes.midnight` for professional tone


path: templates/launch-video.md
content: # Example: Product Launch Video

A 15-second, 4-scene product launch video.

## Structure

| Scene | Duration | Purpose | Transition |
|-------|----------|---------|------------|
| Hook | 4s (120f) | Grab attention with bold text | → fadeBlur |
| Product Hero | 4.5s (135f) | Show the product in action | → scaleFade |
| Features | 3.5s (105f) | Highlight 3 key features | → clipCircle |
| CTA | 4s (120f) | Call to action | — |

## Composition

```tsx
<TransitionSeries>
  <TransitionSeries.Sequence durationInFrames={120}>
    <Hook />
  </TransitionSeries.Sequence>

  <TransitionSeries.Transition
    presentation={fadeBlur()}
    timing={springTiming({ config: { damping: 200 }, durationInFrames: 30 })}
  />

  <TransitionSeries.Sequence durationInFrames={135}>
    <ProductHero />
  </TransitionSeries.Sequence>

  <TransitionSeries.Transition
    presentation={scaleFade()}
    timing={springTiming({ config: { damping: 200 }, durationInFrames: 25 })}
  />

  <TransitionSeries.Sequence durationInFrames={105}>
    <Features />
  </TransitionSeries.Sequence>

  <TransitionSeries.Transition
    presentation={clipCircle()}
    timing={springTiming({ config: { damping: 200 }, durationInFrames: 30 })}
  />

  <TransitionSeries.Sequence durationInFrames={120}>
    <CTA />
  </TransitionSeries.Sequence>
</TransitionSeries>
```

## Key patterns used

- **Hook scene**: `AnimatedCharacters` for staggered title reveal, `Badge` for label, `blurIn` for cinematic feel
- **Product Hero**: `Terminal` component with animated typing, `Grid` background, `popIn` entrance
- **Features**: Staggered `Card` components with increasing delays (0.3, 0.45, 0.6)
- **CTA**: `AnimatedCharacters` + `TypewriterText` for the install command

## Color palette

Uses `palettes.midnight` — deep blue/indigo tones with high contrast.


path: templates/logo-reveal.md
content: # Example: Logo Reveal

A 5-second logo animation — good for intros/outros.

## Structure

| Phase | Frames | Animation |
|-------|--------|-----------|
| Build-up | 0-60 | Glow orbs fade in, grid appears |
| Reveal | 60-90 | Logo scales in with `popIn` + `spring: 'bouncy'` |
| Settle | 90-120 | Tagline fades up below logo |
| Hold | 120-150 | Static hold |

## Pattern

```tsx
export const LogoReveal: React.FC = () => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  return (
    <AbsoluteFill>
      <Background gradient="radial-gradient(circle, #141414, #0a0a0a)" />
      <GlowOrb color="rgba(99, 102, 241, 0.3)" x={50} y={50} size={600} />

      <SafeArea style={{ justifyContent: 'center', alignItems: 'center', gap: 24 }}>
        {/* Logo */}
        <div style={{
          ...popIn({ frame, fps, delay: 0.5, spring: 'bouncy' }),
          fontSize: 96,
          fontWeight: 800,
          color: '#fff',
        }}>
          YourLogo
        </div>

        {/* Tagline */}
        <div style={{
          ...fadeUp({ frame, fps, delay: 1.2 }),
          fontSize: 24,
          color: '#94a3b8',
          letterSpacing: '0.15em',
          textTransform: 'uppercase',
        }}>
          Your tagline here
        </div>
      </SafeArea>
    </AbsoluteFill>
  );
};
```

## Tips

- Use `Img` from `remotion` for actual logo files: `<Img src={staticFile('logo.png')} />`
- Remove background from logo first: `oanim assets remove-bg --in logo.png --out logo-clean.png`
- The `bouncy` spring gives logos a satisfying pop


path: templates/meme-caption.md
content: # Example: Meme / Social Clip

A 5-8 second captioned clip for social media (vertical 1080x1920).

## animate.json

```json
{
  "name": "Meme Clip",
  "compositionId": "MemeClip",
  "render": {
    "fps": 30,
    "width": 1080,
    "height": 1920,
    "codec": "h264",
    "crf": 18
  }
}
```

## Pattern

```tsx
export const MemeClip: React.FC = () => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  return (
    <AbsoluteFill style={{ backgroundColor: '#000' }}>
      {/* Top caption */}
      <div style={{
        position: 'absolute',
        top: '8%',
        width: '100%',
        textAlign: 'center',
        padding: '0 10%',
      }}>
        <div style={{
          ...fadeDown({ frame, fps, delay: 0.1 }),
          fontSize: 48,
          fontWeight: 800,
          color: '#fff',
          textShadow: '0 2px 20px rgba(0,0,0,0.8)',
        }}>
          When the deploy works first try
        </div>
      </div>

      {/* Center content area */}
      <AbsoluteFill style={{ justifyContent: 'center', alignItems: 'center' }}>
        <div style={popIn({ frame, fps, delay: 0.4 })}>
          {/* Image or emoji */}
          <span style={{ fontSize: 200 }}>🚀</span>
        </div>
      </AbsoluteFill>

      {/* Bottom caption */}
      <div style={{
        position: 'absolute',
        bottom: '12%',
        width: '100%',
        textAlign: 'center',
      }}>
        <div style={{
          ...fadeUp({ frame, fps, delay: 0.8 }),
          fontSize: 32,
          color: '#94a3b8',
        }}>
          @yourhandle
        </div>
      </div>
    </AbsoluteFill>
  );
};
```

## Tips

- Vertical format (1080x1920) for Reels/TikTok/Shorts
- Keep text large — minimum 48px for readability on mobile
- Short duration (5-8s) for maximum engagement
- Use `textShadow` for text over images


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)