Agent skill

video-coder

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.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/video-coder-outscal-video-generator

SKILL.md

Video Coder

Parent div vs motion.div example:

tsx
{/* PARENT DIV: positioning, static transforms (rotation, flip), z-index */}
<div
  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) */}
  <motion.div
    initial={{ opacity: 0, scale: 0.8 }}
    animate={{ opacity: 1, scale: 1 }}
    transition={{ duration: 0.5 }}
  >
    <svg>...</svg>
  </motion.div>
</div>
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"
}

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.
</export-pattern>

</required-structure>

---

<sub-components>

### Sub-Components (CRITICAL)

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

<react-memo>
#### 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
</react-memo>

<sub-component-pattern>
// 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;
}) => (
  <div
    className="absolute -translate-x-1/2 -translate-y-1/2"
    style={{ left: `${position.x}px`, top: `${position.y}px` }}
  >
    <motion.div
      animate={isVisible ? "visible" : "hidden"}>
      {value}
    </motion.div>
  </div>
));
// WRONG: Defined inside component (causes jitter)
export default function Scene0({ currentTime }: SceneProps) {
  // ❌ Never define components here
  const TreeNode = ({ value }) => <div>{value}</div>;
}
</sub-component-pattern>

<module-level-definitions>
#### 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 }) => (
  <div
    className="absolute -translate-x-1/2 -translate-y-1/2"
    style={{ left: `${position.x}px`, top: `${position.y}px` }}
  >
    <motion.div variants={fadeVariants} animate={isVisible ? "visible" : "hidden"}>
      {title}
    </motion.div>
  </div>
));
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;
}) => (
  <div
    className="absolute -translate-x-1/2 -translate-y-1/2"
    style={{ left: `${position.x}px`, top: `${position.y}px` }}
  >
    <motion.div
      variants={nodeVariants}
      initial="hidden"
      animate={isVisible ? "visible" : "hidden"}
      className="w-20 h-20 rounded-full bg-white flex items-center justify-center"
    >
      {value}
    </motion.div>
  </div>
));

// 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 (
    <div className="relative w-full h-full bg-gray-900">
      <TreeNode value="A" position={nodePositions.node1} isVisible={states.showNode1} />
      <TreeNode value="B" position={nodePositions.node2} isVisible={states.showNode2} />
    </div>
  );
});

export default Scene0;

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 <complete-example> for full pattern)
const TreeNode = React.memo(({ value, position, isVisible }) => (
  <div style={{ left: `${position.x}px`, top: `${position.y}px` }}>
    <motion.div animate={isVisible ? "visible" : "hidden"}>{value}</motion.div>
  </div>
));

See <complete-example> 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<string>();
  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
);

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;
}) => (
  <div
    className="absolute -translate-x-1/2 -translate-y-1/2"
    style={{ left: `${position.x}px`, top: `${position.y}px` }}
  >
    <motion.div animate={showTree ? "visible" : "hidden"}>
      {value}
    </motion.div>
  </div>
));

// In parent: derive state, pass as prop
<TreeNode value="50" position={{ x: 960, y: 540 }} showTree={states.showTree} />

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:

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

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
<motion.circle
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)
}}

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

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

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

tsx
<motion.div
  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

// Static orientation with flip

Animated rotation (rotation that changes over time):

// Clockwise rotation (positive degrees) <motion.div animate={{ rotate: 90 }} transition={{ duration: 1 }} />

// Anti-clockwise rotation (negative degrees) <motion.div animate={{ rotate: -90 }} transition={{ duration: 1 }} />

// Continuous clockwise spin <motion.div animate={{ rotate: 360 }} transition={{ duration: 2, repeat: Infinity, ease: "linear" }} />

// Continuous anti-clockwise spin <motion.div animate={{ rotate: -360 }} transition={{ duration: 2, repeat: Infinity, ease: "linear" }} />

// Custom pivot point <motion.div style={{ transformOrigin: "top left" }} animate={{ rotate: 45 }} />

  • rotate - 2D rotation in degrees (positive = clockwise, negative = anti-clockwise)
  • rotateX, rotateY - 2D axis rotation
  • transformOrigin - pivot point (default: "center")

For animating elements along SVG paths, see the dedicated 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 }
}}

Didn't find tool you were looking for?

Be as detailed as possible for better results