Animation video
You are making a short cartoon film from a story. The user gives you a story, a logline, a scene list or a script. You deliver a finished mp4 and an Instagram caption. You do every step yourself and you do not stop to ask for approval along the way.
The films this skill makes look hand-animated: characters that squash, stretch, blink and react, a camera that is never still, a paper-and-ink texture, and picture that hits exactly on the voice. They must NOT look like a slideshow of static pictures with text. Everything in this skill exists to get that result reliably, so follow the steps in order and use the kit instead of inventing your own system.
SK below means this skill's folder: ~/.claude/skills/animation-video.
How it works (read this once)
- The whole film is one JavaScript file,
film.js, that you write. It describes what is on screen at any timet. A script renders it frame by frame in headless Chrome and ffmpeg makes the mp4. There is no video editor and no image generation. - The voiceover is generated first, line by line. Its real timings are written to
timeline.js. Every moment in the film is then defined relative to the voice (for example "0.2 s before line 4 starts"), so picture and voice cannot drift apart. - A kit gives you the hard parts ready-made: an engine (easing, camera, scenes, captions), two complete art styles with characters, poses and props, and 25 tested animation recipes.
- You check your work by rendering still frames and looking at them. This is not optional. You cannot judge a film you have not looked at.
The steps
Create the task list from these steps and work through them in order.
| # | Step | Result |
|---|---|---|
| 0 | Preflight | all tools present, VoiceStudio running |
| 1 | Read the story, pick the style | decisions made, project folder created |
| 2 | Write the script | vo/script.json, at most 130 spoken words |
| 3 | Generate the voice | one checked wav per line |
| 4 | Build the timeline | timeline.js, film length 35-45 s |
| 5 | Plan the shots | a shot plan comment at the top of film.js |
| 6 | Write the film | film.js, scene by scene, checking stills as you go |
| 7 | Review the whole film | contact sheets viewed, defects fixed |
| 8 | Export | the mp4 |
| 9 | Deliver | mp4 + script as voiced + Instagram caption + honest notes |
Step 0 - Preflight
bash ~/.claude/skills/animation-video/scripts/preflight.sh
Every line must say OK. If VoiceStudio is MISSING, ask the user to open the VoiceStudio app and wait; never substitute a different voice or skip the voiceover. Fix anything else with the command the script prints.
Step 1 - Read the story and pick the style
Do not ask the user questions unless there is no story at all. Decide these yourself:
- Style. There are two. Look at
SK/references/characters-ink.jpgandcharacters-paper.jpg(use the Read tool on the images) to see them.ink: thick wobbly ink outlines, chunky serif signs, warm retro cartoon. Best for stories with a villain, slapstick, shops, streets, physical gags.paper: cut-paper collage, no outlines, soft shadows, elegant serif. Best for stories about phones, apps, links, nature, softer or more modern moods.- If the user asked for a style, use it. Otherwise, if the working folder already has an
earlier episode, use the OTHER style (run
head -3 */style.js 2>/dev/nullto see which was used). Otherwise pick by the story.
- Cast. Who is the hero (the small business owner or main character)? Who or what is the obstacle (a villain, a confused customer, a maze)? Two main characters is plenty.
- Project folder. A short name from the story, in the user's working folder.
bash ~/.claude/skills/animation-video/scripts/new_project.sh "./film-<short-name>" ink # or paper
This creates the project with a working starter film.js. Read that starter file now: it is
short and shows the exact structure you will keep.
Step 2 - Write the script
Read SK/references/story.md first. It has the structure, the word budget and worked examples.
The rules that matter most:
- At most 130 spoken words in total. The voice speaks about 3.2 words per second, so 130 words is about 43 s. If the user's script is longer, cut it down yourself, keeping their best lines and the order of the story. Do not ask; tell them at the end what you cut. A short film that moves is always better than a long one that drags.
- One line per beat, 5-16 words each, ids
l01,l02, ... Each line becomes one audio clip and usually one visual idea. - Write for the ear: numbers as words ("forty", not "40"), no emoji, no symbols, brand names spelled the way they sound ("Site Keep" for SiteKeep).
- The last line is a hook that asks viewers to comment (see Brand defaults below).
Write it to <project>/vo/script.json:
{"l01": "Meet Maya. She makes custom treats from scratch.",
"l02": "So she set up shop inside a giant online marketplace."}
Step 3 - Generate the voice
python3 ~/.claude/skills/animation-video/scripts/voice.py "./film-<short-name>"
This takes about 20 s per line (4-5 minutes for a full script): run it with a 10 minute
timeout. If it stops part-way, run the same command again; lines already voiced are kept. It generates each line with the
user's voice (profile 3a02464e, "ep1 D sample-locked", English, steps 16, speed 1.0, no
instruct text; these are the defaults, do not change them unless the user says so), listens to
it again, and prints for every line OK or DIFF with what it heard.
Read every DIFF line and decide:
- Fine: the same words written differently ("SiteKeep" / "Site Keep", "40" / "forty", "two" / "to", punctuation).
- Not fine: a word is missing, added, or is a different word ("her menu" heard as "our
menu"). Regenerate just those lines:
voice.py <project> --only l05 l11. If a line still fails after two more tries, reword that line slightly inscript.jsonand regenerate it.
Never ship a line with a dropped, added or wrong word.
Step 4 - Build the timeline
python3 ~/.claude/skills/animation-video/scripts/audio.py timeline "./film-<short-name>"
It prints the total length and each line's start time and duration. Then tune the pauses in
<project>/film.json and run it again:
{"lead": 0.55, "gap": 0.4, "tail": 1.6, "gaps": {"l04": 0.7, "l09": 0.9}}
gapis the default pause after a line. Give a longer gap (0.6-0.9) after a line whose visual needs room to play out: a gag, a fall, a big reveal, the turn of the story.- Use 0.25-0.35 between lines that are one thought.
tailis how long the end card holds after the last word (1.4-1.8).- Target total: 35-45 s. Over 46 s: cut a line from the script and redo steps 3-4 for it.
Whenever you re-voice a line later, run audio.py timeline again. Because every cue is
relative to the voice, the film re-syncs by itself.
Step 5 - Plan the shots
Read SK/references/story.md section "From script to shots". Then write the plan as a comment
block at the top of film.js (not a separate document). One row per scene:
/* SHOT PLAN
A l01-l02 street, day Maya pops up behind her stall, waves. Camera: slow push-in.
B l03-l04 same street Landlord's shadow falls over her, sign SLAMS down (shake, dust). She jumps.
C l05 basement Trapdoor opens, she hangs in the air, drops. Camera follows down.
...
Z l12 end card Circle wipe. Question + "drop one in the comments". Hero peeks in.
*/
A good plan has: 5-8 scenes; a different picture at least every 6 s; one clear visual gag or surprise in every scene; every abstract idea turned into something physical (a fee becomes a toll gate, an algorithm becomes a trapdoor, a confusing link becomes a jungle); the mood turning from trouble to relief at the solution; and an end card.
Step 6 - Write the film
Before writing, read these (they are short, and they are the difference between a good film and a broken one):
SK/references/engine.md- every function you can call.SK/references/kit-ink.mdorkit-paper.md- your style's characters, poses, props.SK/references/recipes.js- 25 tested moves to copy. Look atrecipes-<style>.jpgtoo.SK/references/craft.md- the timing numbers and rules that make it feel hand-animated.
Then write film.js in the starter's four sections: settings, cue times, sound cues, scenes.
Work one scene at a time. Write scene A, render two or three stills from it, look at them, fix, then go on to scene B:
node ~/.claude/skills/animation-video/scripts/render.mjs "./film-<short-name>" stills 1.2 3.5 6.0
Then open <project>/check/sheet-1.jpg with the Read tool. If the command prints
PAGE ERROR, the film has a JavaScript error: fix the first one printed and run it again.
The ten rules of film.js (each one prevents a failure that has actually happened):
- Only edit
film.js(and colours instyle.jsif needed). Never editengine.jsorindex.html. - Never type a raw time. Every cue is built from the voice:
L('l04')(line start),E('l04')(line end),wt('l04', 'three rival')(when a phrase is spoken), plus or minus a small offset. Give each a name:const tSlam = L('l04') + 0.25; - Start an action 0.1-0.3 s BEFORE its word so that it lands ON the word.
- Poses and objects use
q(time stepped 12 times a second: the hand-animated feel). Camera, shake and wipes uset(smooth). - Nothing may be visible before its cue. Use
pop(...)orif (q < tCue) ...so things appear when the voice mentions them, not before. - No
Math.random(), noDate, no CSS animation, nosetTimeout. Useh(n)for randomness. The film must give the same picture every time for the samet. - Draw backgrounds much bigger than the screen (for example x from -1000 to 2080) so camera moves and shake never show an empty edge.
- Draw order is depth: things drawn later are in front. Background first, then far objects, characters, near objects, then effects.
- Keep important things inside x 70-1010 and y 180-1400. The captions sit at y 1420-1560 and Instagram's own buttons cover everything below y 1600 and the right edge.
- Every sound belongs to a visible event and is declared with
sfx(time, name)at the same cue time as the event. Onemusic(...)bed for the trouble part, another for the relief.
The quality bar. Before you call a scene done, it must have all of these:
- Life in every frame. The hero blinks and breathes (
POSE.idle(q)), something in the background moves (cloud, sign sway, floating shapes), and the camera has at least a slow push-in. A frame where nothing moves for more than a second is a defect. - Entrances and exits are animated, never a plain cut-in: pop with overshoot, drop with a bounce, hop in, zip off with anticipation (recipes 1-6).
- Impacts hit on one frame, together: squash + screen shake + dust or burst + a sound (recipe 3). This single thing does more for "feel" than anything else.
- Characters act. They look at what is happening (
look), react a beat AFTER it happens (recipe 9), and change pose and face when the story's mood changes. Never leave a character in one pose for a whole scene. - One gag per scene that makes the idea physical and a little funny.
- Readable text. Signs and labels are at least 40 px tall, 1-4 words, high contrast, on screen for at least 1 s, never covered by a character or the captions.
- A real transition between scenes (recipes 19-20, or a fast camera move within one world), not just one picture replacing another.
Step 7 - Review the whole film
node ~/.claude/skills/animation-video/scripts/render.mjs "./film-<short-name>" check 36
Open both contact sheets (check/sheet-1.jpg, sheet-2.jpg) with the Read tool. Each shows 18
frames in time order, left to right, top to bottom; the times are printed by the command. Go
through this list on every frame, and write down what you find before fixing anything:
- Text cut off, overlapping other text, or covered by a character or prop
- Captions covering something important, or a brand name split or misspelled
(set
CAPTION.replace, or move the action up) - A big empty area (more than a third of the frame with nothing in it): zoom the camera in, or move things, or add background detail
- Something visible before the voice has mentioned it, or still there after it should be gone
- A character or key prop partly off-screen, tiny (under 250 px tall), or hidden
- A hand, arm or object passing through something it should be in front of or behind
- Two frames in a row (2+ seconds apart) that look identical: add motion or a camera move
- The wrong emotion on a face for that moment in the story
- The end card: question readable, comment prompt visible, nothing overlapping
Fix what you found, then render stills at just those times to confirm
(render.mjs <project> stills 4.2 11.8). Look at single frames full size
(check/f003.jpg) when a sheet is too small to judge. Do at most two full review rounds;
do not polish forever, and do not skip the review either.
Step 8 - Export
bash ~/.claude/skills/animation-video/scripts/build.sh "./film-<short-name>" "./<short-name>.mp4"
This takes 2-10 minutes (paper style is slower): run it in the background or with a 10 minute
timeout. It renders every frame, mixes voice + sound effects + music, and prints a check.
Confirm: h264 1080x1920 at 30 fps with aac audio, duration equal to the timeline total, mean
volume between -16 and -19 dB. If the render prints PAGE ERROR, fix it and run again.
Step 9 - Deliver
Give the user, in this order and briefly:
- The mp4 as a link, with its length.
- The script exactly as voiced (and, if you shortened theirs, one line saying what you cut and why).
- The Instagram caption (see
story.md, "The caption"). - Honest notes: anything you could not verify (you have looked at stills, not watched the video play with sound), and any line the voice check flagged.
Brand defaults
The user makes these for SiteKeep (sitekeep.studio, websites for small businesses). Unless the story is clearly for something else:
- Spoken as "Site Keep" in the script; shown as "SiteKeep" everywhere on screen. The starter
already sets
CAPTION.replace = { 'Site Keep': 'SiteKeep' }. - The story turns when the hero switches to SiteKeep; show the brand name on screen then.
- The last spoken line and the end card ask viewers to drop a small business in the comments for the next episode.
- The Instagram caption ends with a call to action to DM the word "KEEP".
For any other brand or a story with no brand, keep the same shape (hook, problem, turn, payoff, comment prompt) and leave SiteKeep out.
When something goes wrong
| What you see | What to do |
|---|---|
PAGE ERROR ... is not defined | A typo or a function from the other style. Check the name in engine.md / your kit file. |
PAGE ERROR ... Cannot read properties of undefined (reading 'start') | A line id that is not in the script, e.g. L('l13') when the script ends at l12. |
PAGE ERROR ... attribute ... Expected number, "NaN" | A calculation produced NaN, usually a misspelled cue name or dividing by zero. |
| Stills are blank or one flat colour | Your scenes do not cover that time. Check each scene(from, to, ...) range; together they must cover 0 to END. |
| Something appears in the wrong place | You mixed local and absolute coordinates. Inside G(x, y, inner) the inner drawing is relative to (x, y). Inside pop(x, y, u, inner) it is NOT. |
| Things drift or jump between renders | You used Math.random() or state kept between frames. Use h(n) and pure functions of q. |
Cannot reach VoiceStudio | Ask the user to open the VoiceStudio app, then rerun. |
VoiceStudio stopped responding | The app quit or restarted mid-run. Ask the user to reopen it, wait 20 s, run the same voice.py command again (finished lines are kept). |
timeline.js is missing / No voice file for line | Steps were skipped: run voice.py, then audio.py timeline, before rendering. |
unknown sfx name | Valid names: pop ding tick coin thud slam whoosh fall snip magic. |
| Render is very slow | Too many P(...) shadows or filters. Wrap whole objects in one P, not each small shape. |
| Voice and picture out of sync | You typed a raw time somewhere, or forgot to rerun audio.py timeline after re-voicing. |
Reference files
Read the ones marked * before writing the film. Open the images with the Read tool.
| File | What it is |
|---|---|
references/story.md * | Script structure, word budget, turning lines into shots, the caption |
references/engine.md * | Every engine function with examples |
references/kit-ink.md / kit-paper.md * | Your style: palette, text, characters, poses, props, how to draw new things |
references/recipes.js * | 25 tested animation moves, copy and adapt |
references/craft.md * | Timing numbers and the principles behind the quality bar |
references/recipes-ink.jpg, recipes-paper.jpg | What each recipe looks like |
references/poses-ink.jpg, poses-paper.jpg | Every ready-made pose |
references/characters-ink.jpg, characters-paper.jpg | Character options and props |
assets/examples/ | Two complete finished episodes to study (see assets/examples/README.md) |
assets/kit/ | The files copied into each project. Do not edit these originals. |