make-tsx — build the visual beats over the master
Step 2 of the pipeline: take an approved master cut and its timing spine and produce the animated beats — UI/browser mockups, full-screen statements, diagrams, image reveals — then composite them over the master into a preview. Work collaboratively and beat-by-beat; the user gives notes per beat.
This is the project orchestration. For the low-level rules of writing a single crash-free Remotion file (frame-based only, monotonic interpolate, Easing.bezier, no useState/useEffect, import gotchas), use the vidtsx-2d-generator skill — shots here follow those rules.
Inputs (read these first, every session)
- The plan —
videos/video-N/work/edit-plan.md(or a change list likev2-update-plan.md). The source of truth for what to build. If there's a change list from a review, that overrides; apply it, don't re-litigate settled beats. videos/video-N/work/timeline.json— maps each shot → its span on the master +type. You edit this. (Schema below.)videos/video-N/work/edited-transcript.json— word-level times in master timeline (ms). Everything syncs to these.brand.md— colors/fonts/motion/delivery tokens. Every shot reads the brand so all videos feel like one channel.remotion/src/lib/kit.tsx(+lib/vscode.tsx,lib/browser.tsx) — the reusable component library;src/brand.ts,src/fonts.ts; existing shots inremotion/src/shots/video-N/.
Workflow
- Read the plan + timeline + transcript + brand + kit. Pull the exact word times for each cue you must hit (grep
edited-transcript.jsonfor the phrase → start/end ms → local frame =(cue_s − shot.master_in_s) × fps). - Ask only what's genuinely open at the start (a small batch), then build. Don't re-ask settled decisions.
- Library-first. Reuse a kit component before writing UI from scratch. If you build something reusable (a window chrome, a browser frame, a card slot), put it in
lib/so later shots and videos get it. Promote good patterns back into the library. - Author / edit the shot as a vidtsx-format
.tsxinremotion/src/shots/video-N/(auto-registered byscripts/gen-registry.mjs). One shot per file. Follow the brand tokens and the principles below. - Sync to the words (see principles). Time each sub-element's entrance to its transcript cue; never show a thing before it's said or after the point has passed.
- Update
timeline.json— add/replace/retime the beat's span andtype. Leave a clear note when a slot is a placeholder (e.g. a pending screen recording). - Render + VERIFY BY SCREENSHOT (never skip):
cd remotion && node scripts/render-all.mjs --scale=1 <ShotIds>(opaque →out/<id>.mp4, transparent overlays →.mov). Validate with a still first (--still); for word-synced reveals/scrolls, render the clip and pull a frame at each cue (ffmpeg -ss <t> -i out/<id>.mp4 -frames:v 1 f.jpg) — one 60% still is not enough. - Bake the preview:
python tools/bake.py(reads timeline.json →videos/video-N/output/video-N-preview.mp4, 1080p30).--end SECONDSto bake a prefix while iterating. - Spot-check composited frames from the baked mp4 at the changed beats (the shot over the real master), not just the isolated shot.
timeline.json (the compositing map)
{
"master": "videos/video-1/reference/master.mp4", "master_fps": 60,
"remotion_out": "remotion/out",
"shots": [
{ "id": "CreditsStat", "type": "cutaway", "master_in_s": 170.0, "master_out_s": 179.0, "cue": "..." }
],
"preview": { "end_s": 329.6, "out": "videos/video-1/output/video-1-preview.mp4", "width": 1920, "height": 1080, "fps": 30 }
}
type: "cutaway"— the shot's.mp4REPLACES the master video for[master_in_s, master_out_s]. Master audio always continues underneath.type: "overlay"— a transparent shot (ProRes 4444.mov,transparent: truein its compositionConfig) is composited OVER the master for that span. For small persistent CTAs/badges in the top/bottom band.bake.pysplits[0, end]at every shot boundary, renders each segment (cutaway from the shot, overlay = master+alpha, else master pass-through), concats, and muxes master audio 0..end. Frame-accurate; a shot's file must exist inremotion/out/first.- Design shots at 1920×1080; render-all
--scale=2→ 4K for final. Preview bakes 1080p30; final delivery is 4K60.
Principles (the house style — apply them)
- Sync to the words (P1). Reveal sub-elements on their narration cue, timed from
edited-transcript.json. "Show everything then hold" is a smell. - Don't pre-empt (P2). Never put on screen a thing mentioned later (don't show "skill" before the skill is introduced; don't show example tiles before "more examples").
- Real assets over generic icons — the DEFAULT (P3, brand.md §6). Any beat naming a real-world
thing (a company, a person, a city/country, a real app/product, a real job posting) shows a REAL
photo or REAL screenshot of it, not a lucide-icon graphic or a hand-drawn clone — icons are the
fallback only for abstract concepts (a stat, a process step) with nothing real to show. Two cases:
- Real website / app / page → capture it LIVE with
tools/capture_web.py(Playwright — see requirements.txt) pointed at the real public URL, saved tomedia/projects/<video>/real/; frame it inWebBrowserFrame(single static page) or replay it with/fake-screencast(a walkthrough with simulated cursor/scroll/click). This is a real state capture, not a redrawn mockup. - Real photo / logo / person / place →
WebSearch/the Browser tool to find the actual asset (the entity's own site, Wikipedia, Wikimedia Commons, a YC/LinkedIn-style profile), thencurl/download it intomedia/projects/<video>/real/. Never redraw a logo or skyline from memory — fetch the real file. Animate it with Ken Burns (slowscale 1.0→1.12+ a small translate drift over the beat's hold) rather than showing it static. - Prefer official/openly-licensed sources over random image-search hits, especially for anything
leaving the machine (a pitch sent to the real company, a public upload). Pair the reveal with the
camera-snapSFX cue (brand.md §10).
- Real website / app / page → capture it LIVE with
- Config/setup → real UI (P4). Keys, dashboards,
.env, skill folders → the actual VS Code / dashboard UI, not an abstract graphic. - Real results in the real editor (P5). A generated result is shown opened inside the real editor (image in a pane next to the chat), not floating on a brand card.
- Full-screen vs overlay. Concept beats = full-screen cutaways (cut away from the talking head). Overlays only for small persistent CTAs/badges in the top/bottom band; never cover the center-framed talking head.
- Real UI realism. UI clones match the real product (Claude Code = dark + coral, real logos), not the indigo brand. Brand indigo is for overlays/full-screen beats.
- Recording vs TSX. TSX when you want controlled highlight/scroll/zoom; a real screen recording only when the point is genuine PROOF (real output, real speed). Leave a clearly-noted placeholder slot for recordings the user will provide.
- On-screen text follows the brand voice — no em-dashes (the site copy avoids them; keep it out of overlays too).
Those are the full principle set. When a video's plan calls for a revision pass, record what changed
and why in videos/<project>/work/v2-update-plan.md so the next pass inherits the reasoning.
Tooling quick reference
- Register shots:
node remotion/scripts/gen-registry.mjs(auto-runs discovery; needed after adding a file). - Render:
node remotion/scripts/render-all.mjs [--still] [--scale=1|2] <ShotIds...>. - Bake:
python tools/bake.py [timeline.json] [--end S] [--keep]. - Scratch renders/frames go in the scratchpad, not the project.
Done = the changed beats render, you have looked at stills/frames at each cue, the preview is re-baked, and composited frames are spot-checked. Update the plan/timeline and any relevant memory when the pass is complete.