Back to skills

Agent Skill

Video Animation

video-animation

Programmatic video animation using Remotion (React-based). Creates kinetic typography, motion graphics, UI mockup animations, and promotional videos rendered to MP4.

O-mega.aiGenerationTypeScriptVideoAnimationRemotionMotion-graphicsMp4React

installs

o-mega.ai/internal

by o-mega.ai

Score

9.0

/ 10

Installs

Repo Stars

Last Updated

0d ago

Fresh

Quality Ratio

95%

Description

Verified

Language

TypeScript

First Published

Feb 2026

Platforms

1

Skill Definition

VIDEO ANIMATION MISSION (MANDATORY READING)

DELIVERABLE TYPE: ANIMATED VIDEO (.mp4 file)

YOU ARE CREATING A PROGRAMMATIC VIDEO ANIMATION using Remotion. This is for kinetic typography, motion graphics, UI mockup animations, and promotional videos.

DO NOT CREATE: index.html, website, landing page, React web app, HTML page MUST CREATE: A rendered .mp4 video file using Remotion

If you create an HTML file instead of a video, YOU HAVE FAILED THE MISSION. The ONLY acceptable output is: /home/user/output/video.mp4


WHAT REMOTION CAN DO

Remotion is React that renders to video. Anything you can build in React, you can animate in Remotion.

YOU CAN BUILD ANY UI FROM SCRATCH

Remotion doesn't just animate images - it can CREATE entire interfaces with code:

  • Chat interfaces with messages appearing
  • Dashboard UIs with charts and metrics
  • Browser windows with content loading
  • Mobile app mockups with interactions
  • Code editors with typing animations
  • Any React component = animatable video content

If the request is for a product demo, chatbot promo, or app showcase, you can BUILD the UI entirely with React components - no screenshots required (though you can use them too).


COLOR & DESIGN SYSTEM

When choosing colors, follow this system (unless the user specifies otherwise):

Color Structure:

  • Primary: The main brand/accent color (choose something modern and vibrant)
  • Secondary: A complementary color that pairs well with primary
  • Neutral: Use primary color at 10-15% opacity for subtle backgrounds, or a desaturated version
  • Background: Dark backgrounds work well for video (Slate-950, Zinc-950, or near-black)
  • Text: High contrast against background (white or very light gray)

If user/company preferences are provided, use those colors. Otherwise, choose a cohesive modern palette.

Palette principles:

  • Avoid clashing colors - secondary should complement, not compete with primary
  • Use neutral tones for supporting elements (borders, subtle backgrounds)
  • Gradients can add depth but use sparingly
  • Ensure sufficient contrast for readability

REMOTION CAPABILITIES

Below are the key features available. These are approaches and examples - combine them creatively.

SPRING PHYSICS - Natural Motion

Springs create organic motion. Different configs for different feels:

// Example: Multiple spring configs for different animation feels
const SPRING_CONFIGS = {
 // Smooth & professional (minimal bounce) - good for UI elements, subtle reveals
 smooth: { damping: 200, stiffness: 100, mass: 1 },

 // Bouncy & playful - good for attention-grabbing elements, logos
 bouncy: { damping: 10, stiffness: 100, mass: 0.5, overshootClamping: false },

 // Snappy & responsive - good for quick transitions, clicks
 snappy: { damping: 20, stiffness: 300, mass: 0.3 },

 // Elastic & wobbly - good for playful text, cartoon effects
 elastic: { damping: 5, stiffness: 80, mass: 0.8, overshootClamping: false },
};

// Usage example
const value = spring({ frame, fps, config: SPRING_CONFIGS.bouncy });

SCENE TRANSITIONS - TransitionSeries

For professional scene-to-scene transitions, use TransitionSeries with effects:

// Example: Scene transitions with fade, wipe, slide effects
import { TransitionSeries, linearTiming, springTiming } from '@remotion/transitions';
import { fade } from '@remotion/transitions/fade';
import { wipe } from '@remotion/transitions/wipe';
import { slide } from '@remotion/transitions/slide';

<TransitionSeries>
 <TransitionSeries.Sequence durationInFrames={90}>
 <Scene1 />
 </TransitionSeries.Sequence>

 <TransitionSeries.Transition
 timing={springTiming({ config: { damping: 200 } })}
 presentation={fade()}
 />

 <TransitionSeries.Sequence durationInFrames={90}>
 <Scene2 />
 </TransitionSeries.Sequence>

 <TransitionSeries.Transition
 timing={linearTiming({ durationInFrames: 20 })}
 presentation={wipe({ direction: 'from-left' })}
 />

 <TransitionSeries.Sequence durationInFrames={90}>
 <Scene3 />
 </TransitionSeries.Sequence>
</TransitionSeries>

Available transition effects: fade, wipe, slide, flip, clockWipe

SEQUENCING & TIMING

Control when elements appear and for how long:

// Example: Staggered element reveals
<AbsoluteFill>
 <Sequence from={0} durationInFrames={120}>
 <Title />
 </Sequence>
 <Sequence from={30} durationInFrames={90}>
 <Subtitle />
 </Sequence>
 <Sequence from={60} durationInFrames={60}>
 <CallToAction />
 </Sequence>
</AbsoluteFill>

// Example: Auto-sequential with Series
import { Series } from 'remotion';
<Series>
 <Series.Sequence durationInFrames={60}><Intro /></Series.Sequence>
 <Series.Sequence durationInFrames={90}><MainContent /></Series.Sequence>
 <Series.Sequence durationInFrames={60}><Outro /></Series.Sequence>
</Series>

TEXT ANIMATION APPROACHES

Many ways to animate text - pick what fits the mood:

// APPROACH 1: Whole word/line fade + scale (clean, professional)
const WordReveal = ({ children, delay = 0 }) => {
 const frame = useCurrentFrame();
 const progress = spring({ frame: frame - delay, fps: 60, config: { damping: 20, stiffness: 100 } });
 return (
 <div style={{
 opacity: progress,
 transform: `translateY(${(1 - progress) * 30}px)`,
 }}>
 {children}
 </div>
 );
};

// APPROACH 2: Character stagger (energetic, playful)
const CharacterStagger = ({ text, delay = 0 }) => (
 <div style={{ display: 'flex' }}>
 {text.split('').map((char, i) => {
 const charProgress = spring({ frame: frame - delay - i * 2, fps: 60, config: { damping: 15 } });
 return <span key={i} style={{ opacity: charProgress }}>{char === ' ' ? ' ' : char}</span>;
 })}
 </div>
);

// APPROACH 3: Typewriter effect (technical, code-like)
const Typewriter = ({ text, delay = 0, charsPerFrame = 0.5 }) => {
 const frame = useCurrentFrame();
 const visibleChars = Math.floor((frame - delay) * charsPerFrame);
 return <span>{text.slice(0, Math.max(0, visibleChars))}</span>;
};

// APPROACH 4: Scale pop (impactful, attention-grabbing)
const ScalePop = ({ children, delay = 0 }) => {
 const progress = spring({ frame: frame - delay, fps: 60, config: { damping: 8, overshootClamping: false } });
 return (
 <div style={{ transform: `scale(${progress})`, opacity: Math.min(1, progress * 2) }}>
 {children}
 </div>
 );
};

// APPROACH 5: Slide from direction (directional, cinematic)
const SlideIn = ({ children, delay = 0, from = 'bottom' }) => {
 const progress = spring({ frame: frame - delay, fps: 60, config: { damping: 20 } });
 const offsets = { bottom: [0, 60], top: [0, -60], left: [-100, 0], right: [100, 0] };
 const [x, y] = offsets [from] || offsets.bottom;
 return (
 <div style={{
 opacity: progress,
 transform: `translate(${x * (1-progress)}px, ${y * (1-progress)}px)`,
 }}>
 {children}
 </div>
 );
};

Choose the approach that matches the video's tone. Mix approaches for variety within a single video.

BUILDING UIs FROM SCRATCH

You can create entire interfaces with React - no images needed:

// Example: Chat interface built from scratch
const ChatMessage = ({ text, isUser, delay }) => {
 const progress = spring({ frame: frame - delay, fps: 60, config: { damping: 15 } });
 return (
 <div style={{
 alignSelf: isUser ? 'flex-end' : 'flex-start',
 backgroundColor: isUser ? PRIMARY_COLOR : NEUTRAL_COLOR,
 borderRadius: 16,
 padding: '12px 18px',
 maxWidth: '70%',
 opacity: progress,
 transform: `translateY(${(1-progress) * 20}px)`,
 }}>
 {text}
 </div>
 );
};

const ChatDemo = () => (
 <div style={{ display: 'flex', flexDirection: 'column', gap: 12, padding: 40 }}>
 <ChatMessage text="How can I help you today?" isUser={false} delay={0} />
 <ChatMessage text="I need help with my project" isUser={true} delay={30} />
 <ChatMessage text="Of course! What are you working on?" isUser={false} delay={60} />
 </div>
);

// Example: Dashboard card built from scratch
const DashboardCard = ({ title, value, delay }) => {
 const progress = spring({ frame: frame - delay, fps: 60, config: { damping: 12 } });
 return (
 <div style={{
 background: NEUTRAL_COLOR,
 borderRadius: 16,
 padding: 24,
 opacity: progress,
 transform: `scale(${0.9 + progress * 0.1})`,
 }}>
 <div style={{ color: '#888', fontSize: 14 }}>{title}</div>
 <div style={{ color: '#fff', fontSize: 36, fontWeight: 700 }}>{value}</div>
 </div>
 );
};

// Example: Browser window frame
const BrowserWindow = ({ children, url = 'example.com' }) => (
 <div style={{ borderRadius: 12, overflow: 'hidden', boxShadow: '0 25px 50px rgba(0,0,0,0.4)' }}>
 <div style={{ background: '#333', padding: '8px 16px', display: 'flex', gap: 8 }}>
 <div style={{ width: 12, height: 12, borderRadius: '50%', background: '#ff5f57' }} />
 <div style={{ width: 12, height: 12, borderRadius: '50%', background: '#ffbd2e' }} />
 <div style={{ width: 12, height: 12, borderRadius: '50%', background: '#28c840' }} />
 <div style={{ flex: 1, background: '#222', borderRadius: 4, padding: '2px 12px', color: '#888', fontSize: 12 }}>{url}</div>
 </div>
 <div style={{ background: '#1a1a1a' }}>{children}</div>
 </div>
);

ANIMATING UI MOCKUPS (with images)

If using screenshots, animate them with perspective and effects:

// Fly-in with perspective
const FlyIn = ({ children, delay = 0 }) => {
 const progress = spring({ frame: frame - delay, fps: 60, config: { damping: 15, stiffness: 80 } });
 return (
 <div style={{
 transform: `perspective(1000px) rotateX(${(1-progress) * 30}deg) translateY(${(1-progress) * 200}px) scale(${0.5 + progress * 0.5})`,
 opacity: progress,
 boxShadow: `0 ${progress * 30}px ${progress * 60}px rgba(0,0,0,0.4)`,
 }}>
 {children}
 </div>
 );
};

// Cursor with click effect
const Cursor = ({ x, y, clicking = false }) => (
 <>
 <div style={{ position: 'absolute', left: x, top: y, width: 20, height: 20, borderRadius: '50%', background: '#fff', boxShadow: '0 2px 8px rgba(0,0,0,0.3)' }} />
 {clicking && <div style={{ position: 'absolute', left: x - 15, top: y - 15, width: 50, height: 50, borderRadius: '50%', border: '2px solid rgba(255,255,255,0.4)' }} />}
 </>
);

AUDIO INTEGRATION

Add sound effects for impacts, whooshes, music:

// Example: Adding audio
import { Audio, Sequence } from 'remotion';

<Sequence from={30}>
 <Audio src={staticFile('whoosh.mp3')} volume={0.5} />
</Sequence>
<Sequence from={60}>
 <Audio src={staticFile('impact.mp3')} volume={0.8} />
</Sequence>

// Background music with fade
<Audio
 src={staticFile('music.mp3')}
 volume={(f) => interpolate(f, [0, 30, 270, 300], [0, 0.3, 0.3, 0], { extrapolateRight: 'clamp' })}
/>

IMAGES & MEDIA

Use Remotion's media components for proper asset handling:

import { Img, Video, Gif, staticFile } from 'remotion';

// Images from public folder
<Img src={staticFile('logo.png')} style={{ width: 200 }} />

// Remote images
<Img src="https://example.com/image.png" />

// Animated GIFs
import { Gif } from '@remotion/gif';
<Gif src={staticFile('animation.gif')} />

TECHNICAL REQUIREMENTS

  1. NO PYTHON f-strings: Use triple-quoted strings and .replace() instead.
  2. TARGET FILE: /home/user/project/src/index.tsx
  3. ASSET BRIDGE: Copy images from /home/user/output/ to /home/user/project/public/, use staticFile("filename.png").
  4. ENVIRONMENT:
  5. COMPOSITION ID: Must be "OmegaPromo".
  6. RENDER COMMAND:
npx remotion render src/index.tsx OmegaPromo /home/user/output/video.mp4 --concurrency=4 --gl=angle-egl --overwrite

QUALITY OPTIONS (optional flags):

  • --crf=18 for higher quality (lower = better, default 18 for H.264)
  • --codec=h264 (default) or --codec=prores for professional quality

BUILDER SCRIPT PATTERN

import os, json, subprocess, shutil
PROJECT_DIR = "/home/user/project"
PUBLIC_DIR = os.path.join(PROJECT_DIR, "public")
INDEX_FILE = os.path.join(PROJECT_DIR, "src/index.tsx")
OUTPUT_VIDEO = "/home/user/output/video.mp4"

# 1. Prepare Asset Bridge
os.makedirs(PUBLIC_DIR, exist_ok=True)
if os.path.exists("/home/user/output"):
 input_assets = [f for f in os.listdir("/home/user/output") if f.endswith(('.png', '.jpg', '.jpeg', '.svg', '.mp3', '.wav'))]
 for asset in input_assets:
 shutil.copy(os.path.join("/home/user/output", asset), os.path.join(PUBLIC_DIR, asset))

# 2. Define React/Remotion code
CODE = """
import React from 'react';
import {
 registerRoot,
 Composition,
 AbsoluteFill,
 useCurrentFrame,
 useVideoConfig,
 interpolate,
 spring,
 Sequence,
 Img,
 staticFile,
} from 'remotion';
import { TransitionSeries, linearTiming, springTiming } from '@remotion/transitions';
import { fade } from '@remotion/transitions/fade';

// Your creative implementation here...
// Use the Remotion capabilities above as needed for the request.

const MainVideo = () => {
 const frame = useCurrentFrame();
 const { fps } = useVideoConfig();

 return (
 <TransitionSeries>
 <TransitionSeries.Sequence durationInFrames={120}>
 {/* Scene 1 */}
 </TransitionSeries.Sequence>
 <TransitionSeries.Transition
 timing={springTiming({ config: { damping: 200 } })}
 presentation={fade()}
 />
 <TransitionSeries.Sequence durationInFrames={120}>
 {/* Scene 2 */}
 </TransitionSeries.Sequence>
 </TransitionSeries>
 );
};

const RemotionRoot = () => (
 <Composition
 id="OmegaPromo"
 component={MainVideo}
 durationInFrames={600}
 fps={60}
 width={1920}
 height={1080}
 />
);

registerRoot(RemotionRoot);
"""

# 3. Write and Render
with open(INDEX_FILE, "w") as f:
 f.write(CODE)

render_cmd = [
 "npx", "remotion", "render",
 "src/index.tsx", "OmegaPromo", OUTPUT_VIDEO,
 "--concurrency=4", # Match E2B sandbox cpu_count=4 for parallel rendering
 "--gl=angle-egl",
 "--overwrite"
]
# Use a generous timeout for the render process
RENDER_TIMEOUT = 900
try:
 res = subprocess.run(render_cmd, cwd=PROJECT_DIR, capture_output=True, text=True, timeout=RENDER_TIMEOUT)

 # 4. Validate output
 if res.returncode == 0 and os.path.exists(OUTPUT_VIDEO):
 file_size = os.path.getsize(OUTPUT_VIDEO)
 if file_size > 10000:
 print(json.dumps({"success": True, "summary": "Video animation rendered successfully.", "output_files": ["video.mp4"]}))
 else:
 print(json.dumps({"success": False, "summary": f"Render produced empty file ({file_size} bytes)."}))
 else:
 print(json.dumps({"success": False, "summary": f"Render failed: {res.stderr}"}))
except subprocess.TimeoutExpired:
 print(json.dumps({"success": False, "summary": f"Render timed out after {RENDER_TIMEOUT} seconds."}))

How to Use