Hand-Drawn Animation Short Films
The full pipeline: Each frame is a pure function of t (seconds), drawn with brushes (ink lines, flat fills, watercolor washes, hatching lines). Headless Chrome renders frames in parallel to images, which ffmpeg then assembles into an MP4. The exceptions are fluid ink and sand painting: these have cross-frame simulation states and must be rendered sequentially from start to finish. No image in the frame is AI-generated; everything is drawn with code.
Eight Engines:
| Style | Engine | Starting Point |
|---|---|---|
| Watercolor, Ink (hatching can create local printmaking feel) | p5.js + p5.brush, speed varies significantly with scene complexity | ClaudeAnimationBase (MIT) |
| Crayon, Picture Book, Comic Strip | Pure 2D canvas, 10–50ms per frame | This repo's crayon-kit/, see crayon.md |
| Shadow Puppetry, Silhouette, Stage | Pure 2D canvas, 2–10ms per frame, gongs and drums synthesized with code | This repo's piying-kit/, see piying.md |
| Colored Ink Combat (Fog Mountain Five Elements style) | Pure 2D canvas, 20–100ms per frame | This repo's inkfight-kit/, see wushan.md |
| Science Explainer (diagrams, subtitles, voiceover) | Pure 2D canvas, 60–130ms per frame, sound effects synthesized in code, narration via edge-tts | This repo's explainer-kit/, see explainer.md |
| Printmaking Series (screen printing, copperplate, cyanotype, halftone, etc.) | SVG, ~1 second per frame, multi-path parallel rendering; music with Tone.js + real instrument samples | This repo's prints-kit/, see prints.md, music.md |
| Fluid Ink (note-driven, abstract) | WebGL2 stable fluid, ~65ms per frame, sequential rendering only | This repo's fluid-kit/, see fluid.md |
| Sand Painting (lightbox backlight, one image smeared into next) | WebGL2 height field, ~90ms per frame minimum, sequential rendering only | This repo's sand-kit/, see sand.md |
| Doodle/Notebook Art (ballpoint, colored pencil, photographed paper) | Pure 2D canvas, pre-render full page as texture, 2–10ms per frame | This repo's doodle-kit/, see doodle.md |
| Co-point Montage (style relay) | Node + @napi-rs/canvas directly outputs frames, no browser needed | This repo's montage-kit/, see montage.md |
Reference work: PDoomVideo, a 2.5-minute MV generated by Claude in Claude Code. That repo has no license — learn from it, but do not take code to publish; ClaudeAnimationBase is the MIT base later organized by the same author.
Environment
- This skill comes from the paint-video-skill repo. The
crayon-kit/,piying-kit/,inkfight-kit/,explainer-kit/,prints-kit/,fluid-kit/,sand-kit/,doodle-kit/,montage-kit/,examples/,tools/are all in that repo, not in the skill directory; if not found locally, ask the user where the repo is cloned, or clone a fresh copy. - Node 22.12 or later, Chrome or Chromium (pass
--chrome=<path>torender.mjsif not found, or setCHROME_PATH), ffmpeg in PATH. - Subtitle tools require Python 3 with numpy and Pillow.
npm installwill download and execute third-party code; ask the user before running it. When starting a new project, copy an already-installed template directory (includingnode_modules) instead of reinstalling every time.
Getting Started Steps (Watercolor / Ink)
- Copy the base to a new project directory, excluding
out,docs,.git. On Windows, userobocopy <base> <new_dir> /E /XD out docs .git /XJ; exit code 1 means success. - First, read
ANIMATION_GUIDE.mdin the new directory. That file contains the complete rules and API:paint(),inkLine(), camera, transitions, all parameters forclawd()— everything is there, not repeated here. - First write
STORYBOARD.md, show it to the user, then write code. Format follows the guide: one-sentence synopsis, world & color palette, recurring motifs, emotional arc, shot-by-shot beat sheet. Before writing, read thedirecting.mdin the same directory (director's handbook: shot sizes, axis, cut points, rhythm, composition, acting). Write the shot list in its format, then go through its checklist at the end. - Modify duration and bpm in
src/config.js. Write each scene as a new filesrc/scenes/<name>.js, wrapped in an IIFE, ending withshots([...]). Instudio.html, replace thedemo.jsline with it. - Work shot by shot; after each one, review it using the check cycle below.
- Output:
node render.mjs --clip --out=out/video.mp4. For longer durations, use--frames --workers=4for parallel rendering, then--encodeto assemble.
The examples/brush/ directory in this repo has two complete, runnable scenes: stars.js ("Picking Stars", 14.5s, bpm 90) and ink.js ("Signature", ink style, 16.8s, bpm 80). Place them in src/scenes/, replace demo.js in studio.html, and update duration and bpm in config.js to the values in parentheses. Use them as references for writing style, not story.
Two patches applied to the base (two ffmpeg parameters and one Chrome parameter in render.mjs):
- Change output to standard TV range: replace
'-pix_fmt', 'yuv420p'with'-vf', 'scale=out_range=tv,format=yuv420p', '-color_range', 'tv'. Otherwise, some players will render it completely black. - Add
--force_high_performance_gpufor Windows dual-GPU laptops; otherwise Chrome falls back to the integrated GPU and slows down over time.crayon-kit/render.mjsalready includes both patches.
Most Critical Rules
Applicable to All Styles
The areas where model-driven animation most often goes wrong:
- Frames are pure functions of t: Frames will be rendered out of order and in parallel, so there must be no cross-frame state. Do not use
Math.random(); all random values must be derived from time and element index. - Pure 2D, no 3D projection: Depth is achieved through occlusion, size, and warm/cool color relationships.
- No text: Do not include labels, signs, or text speech bubbles. Express everything visually using emote (
!,?, sweat drops, lightbulbs). The exception is MV lyrics subtitles: render those as a separate layer overlaid later; see the subtitle section ofmv-workflow.md. - Every shot must have one thing happen, showing the audience one event at a time, with cause followed by reaction.
- Timing is the most common failure point: The audience only gets one pass. Allow time for them to find, understand, and pause at each beat. Fast is action, slow is meaning. The first version is almost always too fast.
- Every seam needs a transition: Including the start and end — no hard cuts that begin or end abruptly. Transitions should relate to the story, not repeat the same type. Exception: combat scenes — cut directly between shots mid-action without decorative transitions; see
wushan.md. - Characters must be large: At 1080p, a mid-shot character should be about 1/3 to 1/2 the frame height; a close-up face should be more than half the frame. Anything smaller than 1/10 is for extreme establishing shots only. Different engines have different meanings for size parameters; values cannot be mixed.
p5.brush Specific (Watercolor / Ink)
From the base ANIMATION_GUIDE.md:
- Use
hash(i)for fixed random values,jit()for line jitter. CallboilSeed(key)once at the start of each independent element; otherwise, a moving thing will cause all static things behind it to jitter uncontrollably. - Use brushes for everything: Only use
paint()andinkLine(). Do not use p5's nativerect,ellipse,fill()— that would look like Flash from the 2000s. - Turnaround is done by switching between a few key angle views (via
turn()). clawd()'su: mid-shot 20–28, close-up 40–70, less than 12 only for establishing shots.
The crayon engine has its own API, random system, and character sizing; see crayon.md.
Default Aesthetics
When the user provides no specific style, use the following. It leans toward "paper and ink": low-saturation warm colors, handmade traces, restrained motion. When the user has their own style guide, defer to that.
Color palette: Override the palette at the beginning of each scene file. The base default palette is overly saturated (sky blue, purple, rose):
Object.assign(PAL, {
paper: '#f7f2e6', cream: '#faf7f0', ink: '#2e2a24',
clay: '#bb5f3c', clayDk: '#8f4428', clayLt: '#d98a6c', // clay colors; Clawd's body uses clay
sky: '#a9bfc4', sap: '#8a9a6a', ochre: '#c9a25a', teal: '#6f9590',
rose: '#c98a8a', indigo: '#4a5670', night: '#262a38', violet: '#7d6f8f'
});
Pick 3–5 colors from this set per film; do not use all of them. Colors should follow an arc that matches the story, e.g., starting cold and dark and moving toward warm and bright.
Texture: The base includes a paper grain texture; keep it. Hatching is good for a printmaking feel, but use it only in shadows and local areas, not covering the entire frame.
Rich ≠ Cheap:
- "Rich" is okay: a shot can have many things moving in it; this is a "performance" frame.
- "Cheap" is not okay: do not fill empty space with confetti, stars, or sparkles just to add visual noise. Effects must be story elements, e.g., falling stars, a lamp turning on.
- Self-check: If you remove this element, does the frame lose meaning, or just visual activity? If only the latter, delete it.
- Restrained does not mean empty: White space must have layers. The frame should contain environment, foreground/midground/background, and texture — not just a big paper-colored area with two or three isolated objects. Each shot shows one thing, but that thing should live in a world that feels inhabited.
Brush strokes must look hand-drawn: Characters are fine with flat fills and ink lines per the guide, but don't use the same line width across the entire film: vary the sw in inkLine(pts, sw, colour, brush) by distance and importance, and switch to 'inkfine' or 'dry' brushes for details. Use watercolor fill and wash for backgrounds and environment to create dry/wet layers. If everything uses the same thick black line plus flat fill, it will look like clip art.
One immutable constraint: Before starting each film, define one invariant that runs throughout, e.g., the same composition position, the same shape, the same character pose. Transitions and match cuts revolve around it, and the ending should echo the beginning.
Music and rhythm: Everything in the base is locked to bpm. If there is no soundtrack, use a slower bpm (80–100) for a steadier rhythm.
Lessons from Test Films
"Picking Stars" (examples/brush/stars.js)
Clawd holds an empty jar to catch falling stars; a jarful of starlight gradually warms the night.
- Turn off emotes manually: The base's excited, proud expressions automatically spawn
sparkeffects near the head, which are cheap decorations. Useemotions(t, [[0, 'excited', { emote: null }], ...])to disable. Meaningful reactions like!,?, or hearts can be kept. - "Warming" does not mean the entire sky turns warm: If the night sky base color blends toward brown-yellow, the whole frame becomes muddy earth tones. Keep the sky as night, but weaken the cold colors (blend toward
#4b4356), and place warmth in the horizon glow and luminous objects. - Props must be instantly recognizable: The glass jar on a dark background was initially drawn gray and looked like a trash can. Changing it to a cooler light glass with one highlight made it identifiable.
- Fade-out ending should be in three parts: First close to the subject, pause, then close completely. Closing to 0 in one go looks like a sudden black screen.
- Clawd's arms are very short: It cannot reach things above its head; raising arms looks like balancing an object on its head. For "holding" gestures, you must draw the pose manually.
- Measured performance: 14.5 seconds, 348 frames, 4 process parallel ~6 minutes, ~1 second per frame (RTX 4060 laptop), ffmpeg encoding 10 seconds. Contact sheet each frame 0.1–1.7 seconds, very cheap to do multiple review rounds.
"Borrowing Dew" (Cautionary Tale)
A snail carries dew to save a thirsty seedling. All rules were followed (no text, no sparkles, palette correct, characters on-model), but the film still failed:
- The story's state change must be exaggerated to be obvious: The seedling looked about the same throughout; the "drooping from thirst → standing after water" contrast was not visible, so the entire film lacked causality. Put the before and after keyframes side by side; if a layperson cannot tell the difference immediately, it's not exaggerated enough — push it until it is.
- Plot-propelling props must be at least as big as the character's head: The dew drop was smaller than the snail's eye and low contrast, becoming invisible when it landed on the shell. If a prop is small, push the camera in, or add highlight/outline.
- If the storyboard says movement, the frame must actually move: The storyboard said the snail carried the dew to the root, but actual displacement was minimal, most of the time looking like a static image. Go through each storyboard beat and check "who goes from where to where."
- Frame too empty: The entire film had only a seedling, a snail, and one leaf. See "restrained does not mean empty" above.
- Transitions must have a reason: A green color sweep across the entire screen unrelated to the story is just a fancy hard cut.
Post-writing self-check: Put the first and last frame of each shot side by side. If you can't describe what changed in this shot, it has no drama — go back and enlarge the event.
"Signature," Ink (examples/brush/ink.js)
- Changing style only requires changing the palette and drawing method, not the base: The whole film uses just three gradations of ink plus Clawd's vermillion. "A drop of red among endless ink" is a classic ink-wash technique, and Clawd is exactly that red drop.
- Ink spreads: Each point of a shape radiates outward from the drop point based on distance (
bloom()), which looks more like ink spreading on paper than a simple "fade-in" of the entire shape. - Ink-wash layering: First lay down a light base, then overlay a heavy ink gradient on the mountain top fading downward, press a paper-colored fog at the foot, and have distant mountains move slower with parallax.
- The world grows stroke by stroke with the character: Wherever the character walks, reeds, river, and rocks are drawn; the act of drawing is the event itself. This also ensures clear before/after change within each shot.
- Composition constraint must consider the endpoint: If the character needs to see the path ahead, position them on the trailing side of their movement direction, so they naturally end up in that corner at the end of the move.
Second Style: Crayon Picture Book
When the user wants childlike, picture book, crayon, comic strip, or no longer wants watercolor, read the crayon.md in the same directory. This style does not use p5.brush; it uses a custom 2D canvas crayon engine (crayon-kit/), which is an order of magnitude faster than watercolor. It contains: engine principles and API, character parameters, transformation gags and panel break techniques, crayon subtitles, and probe-based subtitle avoidance.
Third Style: Shadow Puppetry
When the user wants shadow puppetry, silhouette, stage, or wants to make a short film with only gongs and drums (no song), read the piying.md in the same directory. The engine is in piying-kit/: each puppet piece is carved out individually, then multiplied onto a translucent screen; characters are jointed stick puppets; gong and drum hits are synthesized with numpy. It contains: engine principles and API, character parameters, how to sync actions to gong/drum hits, behind-the-scenes camera position mirroring and silhouette rim light, synthesizer.
Fourth Style: Colored Ink Combat (Fog Mountain Five Elements Style)
When the user wants combat, martial arts, action scenes, or specifically mentions Fog Mountain Five Elements / colored ink, read the wushan.md in the same directory. The engine is in inkfight-kit/: characters are drawn with brush ink lines plus flat fills and dark sides; motion follows smooth curves through keyframes; feet move independently on beat points; impact frames use negative colors and vermillion; culminating in fire and ink explosion. It contains: engine and body API, how to choreograph a fight (determine contact points first, then work backward to standing positions, using probe.mjs for numerical verification), shot arrangement, pitfalls encountered.
Fifth: Science Explainer
When the user wants a science explainer video, explaining a discovery or principle clearly, read the explainer.md in the same directory. The base is explainer-kit/ ("Using Light to Open the Brain," 2026 Nobel Prize optogenetics, 3:05). Each segment uses a different style; subtitles, chapters, and diagram annotations are allowed; visuals, sound effects, and narration share a single timeline.js; if narration runs longer than the visuals, stretch the visual to match the sentence timing.
Sixth: Printmaking Series Shorts
When the user wants material texture, non-flat-minimalist series visuals, or specifically mentions printmaking, screen printing, copperplate, cyanotype, halftone, read the prints.md in the same directory. The base is prints-kit/ ("Far Light," 93 seconds, a dandelion seed's six-stage journey). It covers:
- Horizon line and light source remain fixed for six segments, craft changes each segment;
- SVG construction for six different printing techniques;
- Using fixed random seeds to prevent flicker in frame-by-frame redrawing;
- Three things that make the visuals "tell a story": protagonist and companion, time of day, pencil caption;
- Five transition printing methods;
- Multi-path parallel rendering (each path uses ~2–3GB RAM, default 4 paths).
Seventh: Fluid Ink MV (Note-Driven)
When the user wants an MV that is constantly moving, does not tell a specific story, and changes with each beat of the music, read the fluid.md in the same directory. The base is fluid-kit/ ("A Drop of Ink," 91 seconds, classical + rock). WebGL2 stable fluid, pigment layered by absorbance; note table exported from score, each instrument has a fixed visual (violin = lines, kick drum = impact, cymbal = blossom), each section has a different "water character"; climax blossoms have 7 types of explosion cycling. No human figures, no camera cuts, frames must be rendered sequentially.
Eighth: Sand Painting (Lightbox Backlit, One Image Smears into Next)
When the user wants to tell a small story without human characters, and visuals that are constantly changing (not like a dynamic PPT), read the sand.md in the same directory. The base is sand-kit/ ("Hourglass," 75 seconds, a grain of sand's year seen in nine images that loop). WebGL2 height field simulates sand: sprinkling, fingertip dragging, palm pushing, repose angle, sand curtain, smoothing, brightening triangles, opening a moon; backlight transmits through three channels separately, thin areas visible grain by grain. The rule is that light is the protagonist, transitions are made by shapes connecting, white space is used. Visuals are decided first, music is written later, 60 beats per minute, one beat equals one second. Frames must be rendered sequentially.
Ninth: Doodle / Notebook Art (Ballpoint + Colored Pencil + Photographed Paper)
When the user wants the feel of "paper" / "notebook" / "doodling in class," with a living doodle as the protagonist, read the doodle.md in the same directory. The base is doodle-kit/ ("Page Margin," 38 seconds, a little blob from the margin takes a spin while the pen is away). Ballpoint lines skip ink, colored pencil is oblique hatching with paper tooth grain, full page with camera lighting; a physical pen with shadow anchors the photo feel. Living things have trembling lines; when they stop, they revert to flat paper. One continuous shot, music follows the actions. The second half also covers the experience of making a full-song story MV with this style: no human figures, using two pens and a sliding notebook to act out two characters.
Tenth: Co-point Montage (Same Shape Lands on Same Point)
When many unrelated shots cut quickly, ending with a sudden stop, and every cut lands the same shape on the same screen point, read the montage.md in the same directory. The base is montage-kit/ ("One Bite," 25.8 seconds): donuts fill the screen; a cowboy rides through the hole; each beat, the hole changes to a different style, the outer ring switches one beat later, a relay of 24 styles, ending with a bite. Continuous animation uses a universal skeleton system; each style only manages how to draw it. An alternative approach "one cut, one world" is also documented.
Write Your Own Music
When there is no existing song and you need to compose a score for the short film, read the music.md in the same directory. Uses Tone.js with real instrument samples, not synthesized sounds. It covers: music arrangement rules, how to remove machine feel, VSCO2 orchestral samples and the pitfalls of their octave notation, how to align loudness after changing sound sources, local sample caching, headless Chrome offline export with two-pass loudness normalization, and how to align visuals to music using "beats." Classical + rock songs have a separate section: multi-velocity cycling real drums (DRSKit), distorted guitar, multi-track export, and a custom Python mixing console (EQ, compression, saturation, convolution reverb, segment-based loudness alignment, limiting).
Check Cycle (do this for every shot, don't skip)
Reading code will not show animation results; you must render and view:
node render.mjs --sheet=0.1,0.8,1.6,2.4 --cols=4 --w=480 --out=out/check/sheet.jpg # Contact sheet: first, mid, last frames per shot
node render.mjs --strip=2.1:2.6 --cols=6 --w=320 --out=out/check/strip.jpg # Consecutive frames: turn, jump, transition
node render.mjs --sheet=2.3 --crop=760,420,500,400 --w=500 --out=out/check/face.jpg # Local zoom: expression, hand, contact point
If all contact sheet ms/frame values are 0, the page is not drawing anything (likely a syntax error in the scene file); first check the terminal for [page error].
Open with an image viewer and check each item:
- Is the event readable? Is the character large enough, and can it be distinguished from the background?
- Are there teleportation or abrupt frame changes?
- Body and support: for sitting, standing on, or leaning against an object, there must be a transition of lifting leg, bending knee, sitting down — the body cannot just slide onto the object or overlap with it.
- Limb length is constant: reaching for something must not stretch the arm; if it can't reach, have the character walk closer or bend down.
- Swinging or flipping props do not pass through other objects in the scene.
- Does the action have anticipation and follow-through? Are both sides moving symmetrically?
- Is there a transition at every seam?
- Is there any text?
- Are colors muddy? (Yellow over blue turns green; use
glow()for lighting.) - Does the palette stay within the defined color tones?
Performance and Known Pitfalls (p5.brush)
- Re-rendering only checks if frame files exist: If you change the code and run
--framesagain, existing frames will be kept as-is, mixing old and new content into the final video without any error. The baserender.mjsworks this way. After code changes, first moveout/framesaside (don't delete) before re-rendering.crayon-kit'srender.mjsrecords a code signature per frame and stops if the code has changed, asking you to choose--force(old frames moved toout/frames_old_*) or--keep-frames(if you've already manually moved the modified ones). - Page errors:
crayon-kit'srender.mjsfails exit on page errors; the base only prints a line[page error]and continues rendering — you must watch for it. - Contact sheets print frame time per frame; target ≤1.5 seconds/frame. Watercolor
fillis the most expensive: usewashfor large areas, minimizefillusage and keep polygon vertices few. - With a dedicated GPU, expect ~1 second per frame. If it becomes noticeably slower, use
node gpu_probe.mjs <chrome path>to check which GPU Chrome is using. studio.htmlloads Permanent Marker font from Google Fonts; onlyletter()needs it. If the film has no text, delete that<link>line to avoid delays if the network is slow.- When NaN appears in point coordinates, the error message is
Failed to construct 'OffscreenCanvas', and the stack trace points to the scene code, not the NaN itself — trace back to the geometry calculation. - When the camera zooms in beyond 2x, long lines far from the origin can collapse to a single point. For very long edges, draw them in segments with
inkLine. - Occasional
WebGL: INVALID_OPERATIONwarnings (up to 5) are harmless; other page errors are real problems. - Large shapes using only
fillare nearly transparent: Shapes like distant mountains or water bodies, spanning thousands of pixels, become invisible if onlyfillis used. First usewash(washOp: 255, color lightened) as a base, then overlayfillfor wash and edges. inkLinewith only two points draws nothing and gives no error: Even straight lines need 3+ points, preferably with slight undulation to look hand-drawn. Ripples, water marks, and short ground lines are the most common victims.- Do not draw the same shape in segments: If the shadowed area of a mountain is split into several segments, vertical seam lines appear where they overlap. Draw the entire line at once, using
hash(i)for edge variation. - When segmenting
inkLine, check the points in the last segment: The slice may end with only one point, which will error withspline() requires at least 2 points.
Long Films or MVs
First read mv-workflow.md in the same directory: Music analysis, beat-driven shot list, part library, batch processing, segment rendering, local rework, final color grading, lyric subtitles.
Alternative approach: have a main agent write the storyboard and style guide, then split by chapter to multiple sub-agents, each modifying their own chapter file. If the user does not request this, one person doing everything from start to end yields a more cohesive film.
For lyric timing and audio beat analysis, also refer to paint-mv-skills (MIT, Chinese).
Delivery
- Deliverable is
out/video.mp4, plus a contact sheet screenshot. - Report three things: total duration, average time per frame, and what issues you found and fixed during self-check. Mention clearly any parts you did not review.