🎯

video-coder

🎯Skill

from outscal/video-generator

VibeIndex|
What it does

video-coder skill from outscal/video-generator

video-coder

Installation

πŸ“‹ No install commands found in docs. Showing default command. Check GitHub for actual instructions.
Quick InstallInstall with npx
npx add-skill outscal/video-generator --skill video-coder
6
-
Last UpdatedJan 10, 2026

Skill Details

SKILL.md

Expert React video scene component creator for educational content. Builds production-grade, visually distinctive components using framer-motion animations, pixel-precise positioning, and optimized performance patterns. Follows strict component format with React.memo, threshold-based state updates, and module-level definitions. Outputs self-contained TSX components with proper timing sync, 60fps performance, and comprehensive reference-based implementation.

Overview

# Video Coder

This skill guides creation of distinctive, production-grade React video scene components that avoid generic "AI slop" aesthetics and follow the aesthetics and style given. Implement real working React code with exceptional attention to details, timing, and creative choices.

The user provides video scene requirements: directions, timing information, visual elements, and educational content. They may include context about the purpose, audience, or technical constraints.

CRITICAL: Follow the design and execute it with precision. Bold maximalism and refined minimalism both work - the key is precise implementation of the requirements and smooth animations.

Assets: The design may reference assets from the asset_manifest. For elements with type: "asset":

  • The element's id matches an asset name in the manifest
  • Read the SVG code from the path specified in the manifest
  • Copy the SVG code directly into your React component
  • For assets with follow-path, use the orientation field from the design spec (numeric degrees: 0Β°=up, 90Β°=right, 180Β°=down, 270Β°=left)
  • Apply transforms from the design spec on a wrapper div (not on motion.div):

- rotation: Rotate in degrees

- flipX: Horizontal mirror (true = apply scaleX(-1))

- flipY: Vertical mirror (true = apply scaleY(-1))

- Combined: style={{ transform: 'rotate(${rotation || 0}deg) scaleX(${flipX ? -1 : 1}) scaleY(${flipY ? -1 : 1})' }}

  • These values are pre-calculated by the designer. Do NOT perform any math on them.
  • IMPORTANT: The orientation field is NOT a CSS transform. Never apply orientation as a CSS rotation. If no rotation field exists in the design, apply no rotation.
  • Apply styles (position, size, fill, stroke) from the design spec to the SVG element

Parent div vs motion.div example:

```tsx

{/ PARENT DIV: positioning, static transforms (rotation, flip), z-index /}

className="absolute top-[300px] left-[500px] -translate-x-1/2 -translate-y-1/2 z-[10]"

style={{ transform: 'rotate(45deg) scaleX(-1)' }}

>

{/ MOTION.DIV: animations only (opacity, scale, movement) /}

initial={{ opacity: 0, scale: 0.8 }}

animate={{ opacity: 1, scale: 1 }}

transition={{ duration: 0.5 }}

>

...

```

| Property | Where to Apply |

|----------|----------------|

| top, left, z-index | Parent div (className) |

| rotation, flipX, flipY | Parent div (style.transform) |

| opacity, scale animations | motion.div |

| x, y movement animations | motion.div |

Then implement working React code following the component format that is:

  • Production-grade and functional
  • Properly structured with required props and timing patterns
  • Visually striking and memorable
  • Cohesive with a clear aesthetic point-of-view
  • Meticulously refined in every detail

  • Color & Theme: Follow the color theme as required
  • Motion: Use framer-motion for all animations in React video components. Focus on high-impact moments: one well-orchestrated scene entry with staggered reveals (using delay) creates more delight than scattered micro-animations. Sync animations with the given timing in the given design.
  • Backgrounds & Visual Details: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays and etc. But make sure you follow the given color theme. You can be creative artist following the given design.

Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same.

IMPORTANT: Match implementation complexity to the given vision/design. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the design/vision well.

When the design includes elements with type: "asset", you'll receive an asset_manifest with entries like:

```json

{

"name": "hypersonic_missile_main",

"path": "Outputs/Assets/v20/hypersonic_missile_main.svg"

}

```

  1. Read the SVG file from the path in the manifest
  2. Copy the complete SVG code directly into your React component
  3. Apply styles (position, size, fill, stroke) from the design spec to the SVG element (transforms go on wrapper div, not SVG)
  4. The designer has already calculated all transforms (rotation, flipX, flipY) - use values as-is
  5. Wrapper div transform: rotate(${rotation || 0}deg) scaleX(${flipX ? -1 : 1}) scaleY(${flipY ? -1 : 1})
  6. Assets can be styled/colored using fill and stroke attributes if needed for the design

This document defines the required format for React video scene components.

```typescript

import React, { useMemo } from 'react';

import { motion } from 'framer-motion';

```

```typescript

interface SceneProps {

currentTime: number;

}

```

```typescript

const Scene{N} = React.memo(function Scene{N}({ currentTime }: SceneProps) {

// Component implementation

});

export default Scene{N};

```

Where {N} is the scene number (e.g., Scene0, Scene1, Scene2)

currentTime is the global value of time with respect to the video start.

---

Sub-Components (CRITICAL)

All sub-components MUST use React.memo and be defined at module level (outside the main Scene component).

#### Why React.memo is Required

  • Video components re-render 60 times per second as currentTime changes
  • Without React.memo, sub-components re-render unnecessarily causing animation jitter
  • Module-level definitions ensure stable references across renders

// CORRECT: Module-level with React.memo + wrapper div for positioning

const TreeNode = React.memo(({

value,

position,

isVisible

}: {

value: string;

position: { x: number; y: number };

isVisible: boolean;

}) => (

className="absolute -translate-x-1/2 -translate-y-1/2"

style={{ left: ${position.x}px, top: ${position.y}px }}

>

animate={isVisible ? "visible" : "hidden"}>

{value}

));

// WRONG: Defined inside component (causes jitter)

export default function Scene0({ currentTime }: SceneProps) {

// ❌ Never define components here

const TreeNode = ({ value }) =>

{value}
;

}

#### What Goes at Module Level (Outside Component)

  1. Sub-components - Always wrapped with React.memo
  2. Wrapper div - For positioning and other (absolute, translate, left/top style, etc.)
  3. Animation variants - Objects defining animation states
  4. Static data - Positions, configurations that don't change

```typescript

// Animation variants at module level

const fadeVariants = {

hidden: { opacity: 0 },

visible: { opacity: 1, transition: { duration: 0.5 } }

};

// Static positions at module level

const nodePositions = {

node1: { x: 576, y: 540 },

node2: { x: 1344, y: 540 }

};

// Sub-component at module level with React.memo + wrapper div

const InfoCard = React.memo(({ title, position, isVisible }: { title: string; position: { x: number; y: number }; isVisible: boolean }) => (

className="absolute -translate-x-1/2 -translate-y-1/2"

style={{ left: ${position.x}px, top: ${position.y}px }}

>

{title}

));

```

```typescript

import React, { useMemo } from 'react';

import { motion } from 'framer-motion';

interface SceneProps {

currentTime: number;

}

// Animation variants at module level

const nodeVariants = {

hidden: { scale: 0, opacity: 0 },

visible: { scale: 1, opacity: 1, transition: { duration: 0.4 } }

};

// Static data at module level

const nodePositions = {

node1: { x: 576, y: 540 },

node2: { x: 1344, y: 540 }

};

// Sub-component at module level with React.memo

const TreeNode = React.memo(({

value,

position,

isVisible

}: {

value: string;

position: { x: number; y: number };

isVisible: boolean;

}) => (

className="absolute -translate-x-1/2 -translate-y-1/2"

style={{ left: ${position.x}px, top: ${position.y}px }}

>

variants={nodeVariants}

initial="hidden"

animate={isVisible ? "visible" : "hidden"}

className="w-20 h-20 rounded-full bg-white flex items-center justify-center"

>

{value}

));

// Main Scene component with React.memo

const Scene0 = React.memo(function Scene0({ currentTime }: SceneProps) {

// Threshold-based state updates

const states = useMemo(() => ({

showNode1: currentTime >= 1000,

showNode2: currentTime >= 2000,

}), [Math.floor(currentTime / 42)]);

return (

);

});

export default Scene0;

```

Arbitrary values let you insert any exact CSS value inside a Tailwind class using square brackets, giving you full freedom beyond Tailwind's default scales.

What They Do

Let you set custom sizes, spacing, colors, borders, radii, typography, and positioning instantly.

Examples

  • w-[37px]
  • h-[3.5rem]
  • p-[18px]
  • m-[2.75rem]
  • bg-[#1a73e8]
  • text-[22px]
  • border-[3px]
  • rounded-[14px]
  • gap-[22px]
  • top-[42px]
  • z-[25]

Use arbitrary values for one-off, precise custom values without editing your Tailwind config.

CRITICAL: Follow these patterns to prevent animation jittering and re-rendering issues.

React video components re-render up to 60 times per second. Unstable references cause animations to restart, creating visual jitter.

Define sub-components, animation variants, and static data outside the parent component for stable references.

```tsx

// Animation variants at module level

const nodeVariants = { hidden: { scale: 0 }, visible: { scale: 1 } };

// Static data at module level

const nodePositions = { node1: { x: 576, y: 540 }, node2: { x: 740, y: 540 } };

// Sub-component at module level (see for full pattern)

const TreeNode = React.memo(({ value, position, isVisible }) => (

${position.x}px, top: ${position.y}px }}>

{value}

));

```

See above for the full implementation pattern.

Update states every 42ms using Math.floor(currentTime / 42) to prevent excessive re-renders while matching 24fps video output.

```tsx

// State updates inside components

const states = useMemo(() => ({

showTitle: currentTime >= 1000,

showGrid: currentTime >= 2000,

fadeOut: currentTime >= 9000

}), [Math.floor(currentTime / 42)]);

// Computed collections inside components

const visibleItems = useMemo(() => {

const visible = new Set();

if (currentTime >= 1000) visible.add('item1');

if (currentTime >= 2000) visible.add('item2');

return visible;

}, [Math.floor(currentTime / 42)]);

// Static data created once at mount

const particles = useMemo(() =>

Array.from({ length: 40 }, () => ({

x: Math.random() * 100,

y: Math.random() * 100

})),

[] // Empty deps = created once

);

```

  • 42ms (24 updates/sec): Default for all animations (matches video output fps)
  • 84ms (12 updates/sec): Slow transitions

Pass all dependencies as explicit props for React.memo to work correctly.

```tsx

const TreeNode = React.memo(({

value,

position,

showTree // Explicit prop, not derived from currentTime inside

}: {

value: string;

position: { x: number; y: number };

showTree: boolean;

}) => (

className="absolute -translate-x-1/2 -translate-y-1/2"

style={{ left: ${position.x}px, top: ${position.y}px }}

>

{value}

));

// In parent: derive state, pass as prop

```

---

Always use useMemo inside React components to prevent animation jitter caused by frequent re-rendersβ€”define static data and sub-components at module level, use threshold-based state updates (42ms intervals for 24fps), and pass explicit props for memoization.

Positioning elements in video scenes using Tailwind CSS and framer-motion.

CRITICAL: Always use pixel values (px) as mentioned for positioning, This ensures precise, predictable positioning across all canvas sizes.

For ALL positioning in video scenes, use this consistent pattern with pixel values:

A motion.div cannot have absolute positioning, it should be wrapped inside an absolute div that is properly positioned as shown.

```tsx

{/ Outer div: handles positioning with Tailwind using PIXELS /}

{/ Inner div: content and animations /}

initial={{ opacity: 0 }}

animate={{ opacity: 1 }}

>

{/ Your content here /}

```

Key principles:

COORDINATE SYSTEM

Origin: Top-left corner (0, 0)

X-axis: Increases rightward β†’

Y-axis: Increases downward ↓

FOR SHAPES/TEXT/ICONS:

Position: Always refers to element's CENTER point

FOR PATHS:

All coordinates are ABSOLUTE screen positions

No position/size fields needed (implied by path coordinates)

ROTATION

0Β° = pointing up (↑)

90Β° = pointing right (β†’)

180Β° = pointing down (↓)

270Β° = pointing left (←)

Positive values = clockwise rotation

Negative values = counter-clockwise (-90Β° same as 270Β°)

EXAMPLE (1920Γ—1080 viewport)

Screen center: x = 960, y = 540

Top-center: x = 960, y = 100

Bottom-left quadrant: x = 480, y = 810

Right edge center: x = 1820, y = 540

Position at any pixel value using the same pattern:

{/ Landscape example: position at x=1440px, y=270px /}

{/ content /}

Without -translate-y-1/2, the element's top edge sits at the pixel value, causing overlaps when elements have varying heights. Full centering positions the element's center at the pixel coordinate, ensuring safe spacing.

Layer elements using Tailwind's z-[index] utilities:

{/ Background layer /}

...

{/ Content layer - Landscape center: 540px, 960px /}

...

{/ Overlay layer - Landscape: 216px from top, 1536px from left /}

...

Standard z-index scale:

Be thorough in studying any animation pattern you're using in your scene.

| Type | Description | Use Case |

|------|-------------|----------|

| Tween | Duration-based, precise timing | Coordinated animations, sync with audio |

| Spring | Physics-based, bounce/elasticity | Interactive UI, natural motion |

| Inertia | Momentum-based deceleration | Drag interactions, swipe gestures |

```tsx

transition={{

duration?: number, // Seconds (default: 0.3)

ease?: string | array, // Easing function (default: "easeInOut")

delay?: number, // Delay in seconds

repeat?: number, // Number of repeats (Infinity for loop)

repeatType?: "loop" | "reverse" | "mirror",

times?: number[], // Keyframe timing [0, 0.5, 1]

}}

```

| Ease | Behavior | Use Case |

|------|----------|----------|

| linear | Constant speed | Mechanical motion, loading indicators |

| easeIn | Slow β†’ fast | Exit animations, falling objects |

| easeOut | Fast β†’ slow | Entrances, coming to rest |

| easeInOut | Slow β†’ fast β†’ slow | Default for most UI animations |

| circIn/Out/InOut | Sharper circular curve | Snappy, aggressive motion |

| backIn | Pulls back, then forward | Anticipation effects |

| backOut | Overshoots, then settles | Bouncy clicks, attention-grabbing |

| backInOut | Both effects combined | Playful, game UI |

| anticipate | Dramatic pullback | Hero entrances, launch effects |

| steps(n) | Discrete steps | Pixel art, frame-by-frame |

animate={{ cy: 200 }}

transition={{ duration: 1.5, ease: "easeOut" }}

/>

Only supports 2 keyframes (from β†’ to).

Option 1: Physics-based

```tsx

transition={{

type: "spring",

stiffness?: number, // Tightness (1-100: soft, 150-300: standard, 400-600: snappy)

damping?: number, // Resistance (higher = less bounce, 0 = infinite oscillation)

mass?: number, // Weight (higher = more lethargic)

}}

```

Option 2: Duration-based

```tsx

transition={{

type: "spring",

bounce?: number, // 0-1 bounciness

duration?: number, // Seconds

}}

```

Note: Cannot mix bounce with stiffness/damping/mass.

// Bouncy

transition={{ type: "spring", bounce: 0.6 }}

// Snappy

transition={{ type: "spring", stiffness: 400, damping: 30 }}

// Soft

transition={{ type: "spring", stiffness: 60, damping: 10 }}

```tsx

drag

dragConstraints={{ left: 0, right: 400 }}

dragTransition={{

power?: number, // Deceleration rate (default: 0.8)

timeConstant?: number, // Duration in ms (default: 700)

bounceStiffness?: number, // Boundary spring (default: 500)

bounceDamping?: number, // Boundary damping (default: 10)

}}

/>

```

// Static orientation (asset facing a direction) - use wrapper div

{/ content /}

// Static orientation with flip

{/ content /}

Animated rotation (rotation that changes over time):

// Clockwise rotation (positive degrees)

// Anti-clockwise rotation (negative degrees)

// Continuous clockwise spin

animate={{ rotate: 360 }}

transition={{ duration: 2, repeat: Infinity, ease: "linear" }}

/>

// Continuous anti-clockwise spin

animate={{ rotate: -360 }}

transition={{ duration: 2, repeat: Infinity, ease: "linear" }}

/>

// Custom pivot point

style={{ transformOrigin: "top left" }}

animate={{ rotate: 45 }}

/>

For animating elements along SVG paths, see the dedicated [path-following.md](./references/path-following.md).

Important: When a path has both path-draw and follow-path animations, apply the same easing to both to keep them synchronized.

---

```tsx

transition={{

x: { type: "spring", stiffness: 200 },

opacity: { duration: 0.5, ease: "easeOut" },

scale: { type: "spring", bounce: 0.6 }

}}

```

{/ Container: positioning + fit dimensions + padding /}

Your Text

Key requirements:

className="absolute inset-0 bg-amber-500 z-[0]"

initial={{ scaleX: 0 }}

animate={{ scaleX: 1 }}

style={{ transformOrigin: "left" }}

transition={{ duration: 0.8, ease: "easeOut" }}

/>

className="relative z-[10] px-[16px] py-[8px] text-[48px] font-bold text-white block"

initial={{ opacity: 0 }}

animate={{ opacity: 1 }}

transition={{ delay: 0.4 }}

>

Revealed Text

className="inline-block bg-white/10 backdrop-blur-lg border border-white/20 rounded-2xl z-[6]"

initial={{ opacity: 0, scale: 0.9 }}

animate={{ opacity: 1, scale: 1 }}

>

Frosted

{"TEXT".split("").map((char, i) => (

key={i}

className="text-[52px] font-black text-emerald-400 px-[4px] z-[8]"

initial={{ opacity: 0, y: 30 }}

animate={{ opacity: 1, y: 0 }}

transition={{ delay: i * 0.08, duration: 0.4 }}

>

{char}

))}

className="absolute inset-0 bg-yellow-300/70 rounded-sm z-[0]"

initial={{ scaleX: 0 }}

animate={{ scaleX: 1 }}

style={{ transformOrigin: "left" }}

/>

Highlighted

className="block px-[16px] py-[8px] text-[52px] font-black bg-gradient-to-r from-pink-500 via-red-500 to-yellow-500 bg-clip-text text-transparent z-[9]"

initial={{ opacity: 0 }}

animate={{ opacity: 1 }}

>

Gradient Text

className="block text-[56px] font-black z-[8]"

style={{ color: "transparent", WebkitTextStroke: "2px #fff" }}

initial={{ opacity: 0 }}

animate={{ opacity: 1 }}

>

OUTLINE

  1. Always wrap text in a positioned container div with w-fit h-fit and padding
  2. Padding must use pixel values: p-[Npx] (e.g., p-[24px], px-[16px], py-[8px])
  3. Use inline-block for backgrounds that fit text content
  4. Use block on inner spans for proper padding
  5. Use overflow-hidden when animating backgrounds
  6. Set relative z-[10] on text layers over animated backgrounds

Read [path-elements.md](./references/path-elements.md)

All path elements MUST be generated using the Python script - never manually approximate paths.

Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.

./references/path-elements.md β€” Generating SVG paths from design specs using Python script, path rendering, composite paths

./references/path-following.md β€” Animating elements along paths with getPathPoint, element orientation, static path-aligned elements

More from this repository6