---
name: code_motion_design
description: Produce motion-design video from code only: turn a creative brief into a beat map (timed scene JSON), review PNG stills, then render frame-by-frame with Playwright and encode to MP4 with ffmpeg. Use when someone asks to "make a video" or "animate" something programmatically, turn a script into motion graphics, render an HTML/JS animation to video, or build a repeatable, version-controlled video pipeline with no editor, no React build, and no wall-clock recording.
---

# Code Motion Design

## Purpose
Ship a finished MP4 from a written brief without a video editor. The pipeline is
deterministic: every frame is a pure function of `t = frame / fps`, so the same
beat map always produces the same video, frame counts are exact, and re-renders
after a copy change are one command. This is the same architecture Remotion and
similar tools use, but framework-free: a single HTML file, Python, Playwright,
and ffmpeg, nothing to install beyond that.

## Workflow

1. **Brief.** Get the creative intent in writing: audience, message, duration,
   aspect ratio, tone. One paragraph is enough for a short; insist on a
   paragraph, not a vibe. No brief, no build.
2. **Beat map.** Write `beatmap.json` following `references/beat-map-schema.md`:
   timed scenes (`title`, `bars`, `lower`, `fade`) with start/end seconds.
   Duration = last scene end. Frames = duration * fps. Budget accordingly.
3. **Stills review.** `python3 bin/render.py stills beatmap.json --times 0.5,2.6,6.0`
   Captures those exact timestamps and prints the PNG paths. Look at them.
   Every one must read correctly before you proceed. This is the quality gate;
   a full render of a broken map is wasted time.
4. **Render.** `python3 bin/render.py render beatmap.json --out out.mp4`
   Captures every frame headless, then encodes (h264, yuv420p, crf 18,
   faststart). Options: `--workdir` to keep frames, `--audio track.wav` to mux
   a soundtrack, `--supersample` for razor text (renders 2x, downscales after),
   `--crf` for quality vs size.
5. **Verify.** Run the ffprobe check from `references/encoding-notes.md`:
   resolution, duration, fps, codec. Extract one frame per scene and confirm the
   subject matches what the beat map promised.
6. **Iterate.** Copy changes mean editing the beat map and re-running step 4.
   The beat map is the source of truth; check it in.

## Output Contract
- Deliverable: an MP4 that plays everywhere (h264, yuv420p, faststart), plus the
  beat map JSON that produced it.
- The video must match the brief: same message order, same durations, text
  readable at the delivered resolution (check the stills at 100%).
- A re-render of the checked-in beat map must reproduce the video; never hand
  over a video whose beat map is lost or differs.

## Operating Rules
- Determinism is load-bearing: the engine never uses `Date.now()`,
  `Math.random()`, or free-running `requestAnimationFrame` in the render path.
  Seeded pseudo-random only. Anything time-varying must be a function of `t`.
- Never record the screen or play-and-capture in real time. Always seek to exact
  timestamps; that is what makes timing frame-accurate and output repeatable.
- Text is the usual failure: fonts fall back to DejaVu Sans in headless Linux,
  so sizes that look right on a designer's machine may not hold. The stills
  review is where you catch this, not after the render.
- Keep scene types in the shipped engine (`assets/engine.html`) to the four it
  supports; new scene types get a painter function in the engine and a test
  still before use.
- Stills before renders, no exceptions. Stills are cheap; renders are not.
- When Remotion or Motion Canvas is the better fit (React team, complex scenes,
  existing Node pipeline), say so and point there instead of forcing this tool.
- Style law: plain direct copy in any on-screen text, no em dashes, no
  marketing filler. The words on screen are part of the craft.

## Quick start
```bash
cd <this skill dir>
cp assets/example-beatmap.json my-video.json   # edit the copy
python3 bin/render.py stills my-video.json --times 1.0,3.5,6.5
python3 bin/render.py render my-video.json --out my-video.mp4
```
