ErtanLabs Motion
Motion work goes generic in a predictable way: every choice a brief leaves open gets filled with whatever is most common, and a video assembled from most-common choices is indistinguishable from thousands of others. This skill makes the open choices explicit, gives each one a traceable source, renders deterministically, and measures the file before anyone sees it.
House rule: every visible choice has an owner. If you cannot point to where a font, colour, curve or sound came from, nobody chose it.
The route
| Phase | Step | Leaves behind |
|---|---|---|
| A · Decide | A1 Probe the machine | --doctor result, chosen backend |
| A2 Take the brief apart | Logline, canvas, inventory, soundtrack, guardrails, tagged assumptions | |
| A3 Find the source world | The artifact whose grammar the piece borrows, story device, hero beat | |
| A4 Write the default, then overturn it | The most-common version and its replacements | |
| A5 Keep the ledger | ledger.md beside the composition | |
| B · Make | B1 Plan in frames | Beat sheet with planned holds |
| B2 Weigh every element | Weight classes, lead and follow | |
| B3 Let the world set type, colour, sound | Ledger rows filled from the source | |
| B4 Proof, then render | Inspected hero frames, then the file | |
| C · Prove | C1 Measure and look | check.py report, contact sheet, tells index |
| C2 Tag claims and hand off | Evidence-grouped handoff |
Working modes
- Sprint (short single-idea pieces, or "just make it"): choose the source world yourself, write a five-line ledger (logline, world, hero beat, three replaced defaults, canvas), build. Proof and measurement still run.
- Studio (brand work, longer pieces, unclear briefs): render the hero frame for two candidate source worlds with
--stillsand show the two images side by side; people judge pictures faster than descriptions. The user picks; with nobody available, keep the one whose hero frame survives the thumbnail squint (C1) and note why in the ledger. - Series (episodes for one channel or campaign): read every earlier
ledger.mdin the project first. A new episode may not repeat both the previous source world and the previous hero-beat structure; brand constants carry afixedtag and are exempt. - Revision (notes on a delivered piece): map each note to beat-sheet rows and write the change as a beat-sheet diff, old row → new row. Example: "too slow at the start" →
grid 6-30 → 4-16,hold 30-60 → 16-40, every later row shifts by −20 frames. Render only the changed range (--range) for review, then the full piece, and append the diff to the ledger. - Narrated explainer (video essays, documentaries, history, money, crime or science stories driven by voice-over, paper-collage looks): the narration is the clock and every sentence becomes a beat. Two rules carry most of the quality: a hold ends with its sentence, and every fact on screen is either spoken or sourced on a slip. Read
references/paper-collage-explainer.mdand start fromassets/collage-template.html.
Production level
Independent of the mode, every piece has a level, written in the ledger:
- Graphic (default): the source world's grammar on a flat canvas. Right for overlays, lower thirds, UI pieces, data hits and anything an editor composites.
- Film: the artifact in its place, seen through a camera, lit by a source the world explains, with materials, life between the beats, three or four scenes and designed sound with music. Right for showreels, landing pages and repository front pages, brand films, launches, map and route showcases, and whenever someone asks for "cinematic", "lively", "premium" or "less flat". Read
references/film-level.mdbefore planning; it adds a scene list above the beat sheet and its own checks.
Phase A · Decide
A1 Probe the machine
node scripts/render.mjs --doctor
The output decides the backend: Node, Playwright or an installed Chrome/Edge, ffmpeg, ffprobe, NVENC. Report it to the user in one line. If the HTML path is unavailable, B4 lists the alternatives; if the user already has a Remotion or HyperFrames project, work inside it.
A2 Take the brief apart
- Logline: the single sentence the video says. Without one, the piece turns into decoration; draft one and confirm it.
- Canvas: ratio, length, fps, destination. Sizes and safe zones:
references/formats-and-safe-zones.md. - Inventory: footage, screen captures, logos, photos, data, AI plates. Existing material shapes the concept rather than being laid on top of it.
- Soundtrack: voice-over, music or none. With VO or music, the audio sets the timing (
references/sound-and-sync.md). Audio the user supplies is final: never add or remove pauses, cut, stretch or re-time it without the user's yes; adjust the picture instead. - Guardrails: brand fonts and colours, required and forbidden elements, deadline.
Unknowns become assumptions in the ledger, tagged assumed. Ask a question only when the answer would change the source world.
A3 Find the source world
Do not pick "a style". Pick a real artifact from the subject's world and borrow its grammar: a tide table, a pharmacy leaflet, a split-flap departure board, a seismograph roll, a redacted dossier, an editor's desk of clippings. One artifact settles several decisions together - the type it is set in, the inks and materials that give the palette, the way things change in it (flip, print, stamp, fill, plot), its surface - so the decisions agree with each other.
- List five artifacts that people who live with the subject actually handle or read.
- Prefer the one with motion built in: something that flips, prints, fills, plots, stamps or scrolls by itself.
- Borrow rules, not pictures. Not a photo of a ticket behind the text, but the ticket's logic: a perforation separates, a serial number counts, a stamp cancels.
references/source-worlds.md describes 31 worlds grouped by the way they move. Treat it as calibration; the best world for a job is often not on the list.
Name two more things:
- Story device: one sentence on how the logline becomes visible ("the tide curve plots and the water in the frame follows the pen").
- Hero beat: the beat that carries the logline. It gets the longest planned hold and the largest change of state or scale in the piece. After rendering, the longest hold
check.pymeasures should be the hero's; if it is not, the emphasis is in the wrong place. Under a voice-over the hold still ends with its sentence and the pause after it. If the hero needs more time, ask for a longer pause while the voice is still being written or recorded; with a finished voice-over, make the hold fit, and never hold the picture over the next line.
A4 Write the default, then overturn it
The first idea is usually the most common one. Make it visible, then replace it:
- Write the default version of this video in one honest line: what an average model would make from this prompt.
- Choose at least three decisions in that line (type, palette, layout, entrance, transition, sound) and write a replacement for each with its source: the source world, the material, the data or the soundtrack.
- A default you cannot replace with a reason may stay; it goes into the ledger marked
owned default.
A5 Keep the ledger
Fill it before building, show it, and save it as ledger.md beside the composition. It is also the memory for the next episode.
LEDGER · <project> · <date>
Logline:
Canvas: 1080×1920 · 30 fps · 8 s · Reels · safe zone: vertical
Source world:
Story device:
Hero beat (frames):
Default version (one line):
| Layer | Decision | Source / reason | Default it replaces |
|-------------|---------------------------|---------------------------------|---------------------|
| Type | | | |
| Palette | hex values + job of each | sampled from ... | |
| Layout | | | |
| Time base | fps, weight classes, holds| | |
| Texture | | | |
| Sound | | | |
| Transitions | | | |
Rights (fonts, music, data, imagery):
Assumptions:
Phase B · Make
B1 Plan in frames
A beat sheet is a compact exposure sheet: motion written as data before code. Ranges are [in, out) in frames; holds get their own rows.
BEAT SHEET · 30 fps · 240 frames (8 s) · 1080×1920
| Beat | Frames | Element | Class | Action | Carries | Sound |
|------|----------|-------------|-----------|---------------------------------|---------|--------------|
| 1 | 6-30 | grid | mark | lines plot, staggered | enter | pen |
| 1 | 30-60 | - | | HOLD | | |
| 2 | 60-168 | tide curve | set piece | plots; water follows the tip | link | low swell |
| 2 | 168-198 | - | | HOLD (hero) | | silence |
| 3 | 198-200 | headline | stamp | lands in the water | land | one knock |
HOLDS: 30-60, 168-198, 200-240 → planned hold ratio 42%
Carries names what the viewer gains: an element enters, two things link, a state changes, or a beat lands on the soundtrack. An empty Carries cell marks decoration; cut it.
Hold ratio is the share of frames in which nothing meaningful moves. Explainers and title work usually sit around 20-35%, music-cut montages lower, contemplative pieces higher. Grain and slow sub-2 px drift are texture, not movement. check.py measures the ratio so plan and result can be compared.
With VO or music, convert word and beat timestamps to frame numbers and write them into the sheet. Under a voice-over each beat also gets a cue, its first stressed word: the beat's main element is in place by the cue, and no hold runs into the next sentence.
B2 Weigh every element
Give every element a weight class; the class chooses its curve and duration from the set in references/motion-and-timing.md, so durations differ because masses differ, not by taste.
| Class | Typical elements | Arrive | Leave | Curve |
|---|---|---|---|---|
| prop | dots, tags, small numbers | 6-12 f | 4-8 f | settle in, launch out |
| panel | titles, cards, chart blocks | 14-24 f | 10-16 f | heavy |
| set piece | camera, water planes, maps, large fields | 30-90 f | 20-45 f | heavy; linear for machinery |
| mark | lines, routes, underlines, handwriting | length ÷ ~30 px per frame | fades | pen |
| stamp | cancels, final values, cuts | 1-2 f | - | step |
- Lead and follow. At any moment one move leads; any other move is caused by it (the pen reaches the extreme, so its label opens).
- Dependency order. An element appears only after what it depends on is on screen: an axis before its data, a route before its marker.
- Poster frame. Frame 0 is often the thumbnail, so compose it as a finished, readable image and let motion grow from it.
- Frame-pure. Every value is computed from the frame number alone: no wall-clock CSS, timers or unseeded randomness (
references/composition-contract.md).
B3 Let the world set type, colour, sound
- Type is inherited from the source world. Emphasis has to survive the thumbnail squint (C1): a word marked only by hue disappears at small size in greyscale, so emphasise with size, weight, placement or timing. Set
<html lang>and use locale-aware casing;render.mjsglyph-tests the text actually on the page. Details:references/typography.md. - Colour: every colour has a named job (ground, type, signal, a data series). A colour without a job is removed. Sample values instead of inventing them:
python scripts/check.py --palette reference.jpg. Details:references/color-and-texture.md. - Sound statements are only ever Measured or Not verified, because audio cannot be inspected by eye. Every entry in the Sound column points at a visible cause or at the bed. Details:
references/sound-and-sync.md.
B4 Proof, then render
| Backend | When |
|---|---|
HTML + render.mjs | Default whenever --doctor passes. Start from assets/composition-template.html (or assets/collage-template.html for narrated explainers); contract in references/composition-contract.md. |
| Remotion / HyperFrames | The user already works in one. Same ledger and beat sheet, different syntax: references/backends.md. |
| Artifact preview | Only if the environment offers a motion or video artifact type, and only as a preview; a deliverable is still a rendered file. |
| Plates | Shots code cannot draw convincingly (people, real places in motion) come from footage or a video model as plates without text; everything typographic stays in the composition. references/backends.md. |
Maps, globes and routes: also read references/maps.md (§7a for showcase routes).
Proof the hero beat before building the rest:
node scripts/render.mjs comp/index.html --stills 0,170,last # poster, hero, end frame
node scripts/render.mjs comp/index.html --range 150-210 --scale 0.5 # hero beat at half size
node scripts/render.mjs comp/index.html -o comp/out.mp4 # full render
node scripts/render.mjs comp/index.html --alpha -o comp/overlay.mov # ProRes 4444 with alpha
Open proof images with your image tool before going on; a file you have not looked at is not Inspected. Overlays for editors ship with an alpha channel (--alpha).
Phase C · Prove
C1 Measure and look
python scripts/check.py comp/out.mp4
The checker reports resolution, fps, frame count, colour tags, faststart, integrated loudness and true peak, silences, holds and hold ratio, a rough flash estimate and suspected corrupted frames, and writes <name>_check/contact.png: six evenly spaced frames with the platform's UI bands shaded. Then:
- Score the contact sheet with
references/default-tells.md(tells index). - Topic swap: mentally replace the subject with an unrelated one. If the frames would still fit, they were not derived from this subject; go back to A3.
- Thumbnail squint: view the contact sheet at about a tenth of its size. The hero beat should be the first thing you find, and emphasis should still read.
- Hold audit: compare measured holds with the planned ones. A missing hold means something is still moving; a longest hold outside the hero beat means misplaced emphasis.
- Cue check (voice-over or music cues):
python scripts/check.py comp/out.mp4 --at <cue seconds>writescues.png, the exact frame at each cue. Every one must show what is being said or hit at that moment.
Accessibility is a gate, not a score: at most three flashes in any one second (WCAG 2.3.1), and text contrast of at least 4.5:1, or 3:1 for large display type (WCAG 1.4.3).
C2 Tag claims and hand off
Every claim in the handoff carries one of four tags:
- Rendered: the file was produced in this session.
- Inspected: these specific frames were opened and looked at.
- Measured: a number from
check.py, ffprobe or ffmpeg. - Not verified: anything else, stated plainly.
A claim that cannot carry one of the first three tags is reported as Not verified, including anything about how audio sounds and anything about a reference you could not open. Charts drawn without a data source say "schematic" on screen.
HANDOFF · <file>
Spec (Measured): codec, size, fps, frames, duration, colour tags, loudness
Rendered: <files>
Inspected: <frames looked at>
Measured: holds, hold ratio, warnings resolved or accepted
Not verified: <one line>
Defaults replaced: <ledger rows that replaced a default, with their reason>
Tells index: <n> · accepted exceptions
Next: <one step, optional>
Toolbox
| File | Reach for it when |
|---|---|
references/source-worlds.md | Choosing a source world: 31 artifacts grouped by how they move, including colour-and-light and navigation worlds |
references/film-level.md | Film-level pieces: space, camera, light, materials, life between beats, scene lists, sound design and music, render budget |
references/paper-collage-explainer.md | Narrated explainers and documentaries: narration-first beats, collage desk grammar, beat recipes, real-people rules, optional plates |
references/motion-and-timing.md | Easing palette, weight classes, seconds to frames, holds, camera, joining beats, short-form rhythm |
references/typography.md | Choosing and testing type, sizes, numerals, locale rules, glyph tests, subtitles, licensing |
references/color-and-texture.md | Colour jobs, sampling, contrast, matching footage, texture recipes and their bitrate cost, sRGB to BT.709 |
references/sound-and-sync.md | Locking to VO and music, the sound map, placing effects, ducking, loudness targets |
references/formats-and-safe-zones.md | Canvas sizes, safe zones, multi-format delivery, export presets, cover frames |
references/maps.md | Map data and licences, projections, routes, zoom, labels, relief without GDAL, globes, lifted flight arcs, computed telemetry |
references/composition-contract.md | The COMPOSITION contract, template helpers, media, libraries, running the renderer, fixing bad frames |
references/backends.md | Remotion, HyperFrames, artifact previews, editor handoff, plates from video models |
references/default-tells.md | Tells grouped by how they are detected, with weights and usual fixes |
assets/composition-template.html | Starting point for a new composition (copy, then edit) |
assets/collage-template.html | Starting point for a narrated paper-collage beat, with the cut-paper kit built in |
scripts/render.mjs | Deterministic parallel render into one continuous stream, stills, alpha, glyph test, doctor |
scripts/check.py | Pre-publish report, contact sheet, cue frames (--at), loudness, holds, flashes, corrupted frames, palette extraction |
scripts/glyph_check.py | Glyph coverage of a font file for given text |
scripts/paper_cutout.py | Turn a supplied photo into a cut-paper cutout with a rim and contact shadow |
scripts/music_bed.py | Generate an instrumental music bed locally with MusicGen on a CUDA GPU (weights CC BY-NC 4.0: non-commercial) |
examples/tide-table/ | A finished job: intake, ledger, beat sheet, code, check results, handoff |