Li's Code Animation Skill
One-Liner: Turn a "hard to explain" point from a math lesson into a 90-second code-animated video that students can pause and rewatch, with formulas perfectly aligned every time.
The core tech stack is fixed to four steps, with no AI-generated visuals: HTML/CSS/SVG for scenes → GSAP for timelines → Headless Chromium for frame-by-frame capture → FFmpeg for H.264 MP4 encoding. HyperFrames only handles the project contract and scheduling; it does not generate a single frame — it's a printing press; you have to write the manuscript yourself.
Prerequisites (Check Before Starting; Don't Proceed if Missing)
Run npx -y [email protected] doctor in the project root. Proceed only if all four items below are green.
| Software | Version | Purpose | What Happens if Missing | How to Fix (Linux/Cloud PC) |
|---|---|---|---|---|
| Node.js | ≥22 | Run CLI and scripts | The whole pipeline fails | nvm install 22 |
| FFmpeg and ffprobe | 4.x | Encoding, frame extraction, parameter checks | If only ffmpeg is present without ffprobe, both export and checks will fail | apt install ffmpeg (includes ffprobe). Do not use third-party package managers, as they often install only half. |
| Chromium / Chrome (headless) | Recent 2 years | Frame capture | No frames can be generated | apt install chromium. On cloud PCs, it's usually at /usr/local/bin/chromium. |
| GSAP | 3.x Local file | Timelines and tweens | Using CDN will silently produce a black screen with no errors | Copy assets/gsap.min.js from this skill into your project's assets/ folder. |
The self-check also requires three optional tools (self-check is less thorough without them, but video production is unaffected):
| Tool | Purpose | What to Do if Missing |
|---|---|---|
| playwright-core | Automatic screenshot of 6 scenes | npm i playwright-core |
| ffmpeg's scale filter | Screenshot brightness diagnosis | Automatically degrades to DOM diagnosis only |
| zip | Package source code | Use tar.gz instead and mention it in the delivery notes |
Five Pitfalls to Avoid
- Both
ffmpegandffprobemust be present; many systems only install the former. - Do not mix installs from winget or portable packages; PATH versions may conflict.
- GSAP must always be a local file, never CDN; a broken CDN causes a silent black screen for the entire video.
- The browser runs in software rendering mode (seeing
BeginFrame did not runin logs is normal); no need to configure your GPU. - The default rendering bitrate is about 500 kbps; for 1080p, a normal range is 3–8 Mbps. Without explicitly specifying the bitrate, dark fine lines will blur. See Section 6.
Route Selection: Default to A; Use B Only for Narration or Complex Scenes
| A Track · Pure Graphics (Default) | B Track · Narration/Complex Scenes | |
|---|---|---|
| Suitable For | Function graphs, geometry, formula derivations, concept analysis | Needs narration, hands/characters/maps/3D, background music |
| Scenes | This skill's six-scene HTML+SVG, hardcoded coordinates | Calls the code2video skill: film.json script + component library |
| Audio | No narration by default; captions are on-screen text. User adds BGM later. | TTS narration + music + loudness alignment |
| Command | npx [email protected] render | node $C/tools/make.mjs <movie directory> film |
| When to Switch | User explicitly asks for "voiceover/audio/narration" or needs human actions, maps, characters | User only wants visuals and text |
The B track's capabilities are inherited, not duplicated: the code2video engine/kits/presets are not copied into this skill (about 20MB, and would cause version drift after updates). Instead, it calls the already installed copy. Bridging details are in references/two-tracks.md. The storyboard script is shared by both tracks; an A track storyboard can be converted to a B track film.json (via scripts/story-to-filmjson.mjs).
Workflow Upon Receiving a Task
0 · Environment Self-Check
npx -y [email protected] doctor
ls assets/gsap.min.js
If anything is missing, provide the installation command and pause, waiting for the user to install it. Do not pretend things will work.
1 · Clarify and Fill in Details (Users Usually Give Only One Sentence)
Teachers often start with just twenty words: "Use code to make a video about imaginary numbers: does x exist such that x² = -4?" You must fill in the remaining specifications. Ask only four questions, each with suggested options:
| Question | Why It's Essential | Suggested Default |
|---|---|---|
| Where to stop the explanation? | Determines the mathematical conclusion; the only part that can be "wrong." | Stop at the step before a new concept is needed. |
| Duration and segments? | Determines how many scenes to cut. | 90 seconds, six scenes. |
| Who decides the storyboard? | The user might already have a sequence. | You write it; show it to them first. |
| Target audience grade? | Determines depth and vocabulary. | Based on the topic's school level. |
Don't ask about the style: Default is the dark "light trail" theme (user already chose it). Only ask if the user specifies a white background.
When the user says "you decide," summarize the defaults in one sentence ("Making it in dark style, 90 seconds, six scenes, stopping at this step. Tell me if it's wrong"), and wait for their nod.
2 · Create a Six-Scene Storyboard; Wait for User Approval
Following the structure in Section 3, create a storyboard table: scene number, duration, visual elements, caption text, and the question each scene solves. Provide only the storyboard; do not write code. Wait for the user to say "OK" before proceeding to step 3. This step has the lowest rework cost; always wait for confirmation.
3 · Create the Project
node <skill directory>/scripts/init-project.mjs --name <project folder> --title "<video title>" --scenes 6
The script copies the six-scene skeleton and index.html from assets/project-template/, copies GSAP, and generates a skeleton verify-seeks.json. Do not manually run npx hyperframes init; the template is already compliant.
The template contains the complete six-scene example "The Geometric Meaning of k in Inverse Proportion" (dark version). It can be rendered directly to produce the exact same video; to create a new video, keep its coordinate system, timeline, and styles; only change the text and k-value. This is much faster than writing from scratch.
4 · Write Each Scene's Visuals
- Each of the six scenes gets its own file:
compositions/scene-1-hook.html…scene-6-outro.html;index.htmlis only for mounting. Do not put all content into one giant HTML file. - All coordinates are hardcoded, not dependent on flow layout: first define the origin and scale (e.g.,
OX=460, OY=620, S=96, 1 unit = 96 pixels); all points are calculated from these. - Curves are generated by sampling the function to create a path; do not approximate by hand.
- For formulas and colors, refer to references/visuals.md; for mathematical presentation, see references/math-rules.md.
5 · Lint
npx -y [email protected] lint
Must have 0 errors and 0 warnings. Fix any errors and rerun.
6 · Screenshot Each Scene (Self-Checks 1–3)
node <skill directory>/scripts/verify.mjs --browser=/usr/local/bin/chromium
For each scene, seek to the second with the most information, capture a screenshot to verify-shots/, and diagnose whether GSAP loaded, timelines are registered, elements overflow, and if there are any JavaScript errors. Review each screenshot yourself; don't just run the script and move on.
7 · Frame Extraction Verification (Self-Check 4)
Extract 16 frames from the previous video version and create a mosaic to confirm that each key element actually appears at its claimed time. See references/qa.md for the procedure.
8 · Render the Full Video (Must Explicitly Set Bitrate)
npx -y [email protected] render --output <name>.mp4 --quality delivery --video-bitrate 3M
--video-bitrate and --crf are mutually exclusive; use one or the other. A normal 90-second 1080p video is 15–25 MB; anything under 10 MB means it's over-compressed.
9 · Verify Final Video Parameters (Self-Check 6)
node <skill directory>/scripts/inspect-video.mjs <name>.mp4 --expect-duration 90
Checks resolution, frame rate, total frames, duration, bitrate, and file size. If any check fails, return to step 8 and re-render.
10 · Delivery
zip -qr <name>-project.zip <project folder> \
-x "<project folder>/node_modules/*" -x "<project folder>/verify-shots/*" \
-x "<project folder>/.verify-tmp/*" -x "<project folder>/*.mp4"
Deliver three things: the final mp4 file, the pure source code archive (excluding mp4), and three notes (what the video is about, what decisions were made for the user, and which parts the teacher should review). Simply sending the file is not enough.
3. Six-Scene Skeleton (90-Second Baseline)
The duration can be scaled on request, but the structure and order must not change: Hook first, then push forward, then generalize, then derive conclusion, then correct mistakes, finally wrap up.
| # | Scene ID | Start/Duration | Scene's Task | Must Include |
|---|---|---|---|---|
| 1 | s1-hook | 0 / 10 | Present a real problem that makes the viewer want to continue. | Main graphic frozen + three-line question on the right + bottom caption. |
| 2 | s2-q1 | 10 / 20 | Calculate the key quantity in the simplest case. | Coordinate axes, curve, moving point, rectangle/triangle, line-by-line captions. |
| 3 | s3-other | 30 / 18 | Verify the same conclusion in a different quadrant. | Four curves lit sequentially + conclusion card. |
| 4 | s4-triangle | 48 / 15 | Break the conclusion in half, providing the major premise. | Geometric block + right-angle marker + three-line deduction. |
| 5 | s5-table | 63 / 12 | Correct the most common error. | Thin-line table + error card (with strikethrough) + correct card + key phrase. |
| 6 | s6-outro | 75 / 15 | Restate the conclusion on a different coordinate scale, wrap up. | Reduced version of the same diagram + centered three lines + pulsing emphasis. |
index.html mounting points: body background must be hardcoded (dark #050a14), the six scenes' track-index 0–5 are continuous, start/duration align with the table above, and the full video's title is "Topic|Icon Name."
4. Math Presentation Quick Reference (Getting it Wrong is a Teaching Error)
Hard constraints; each one must appear in the visuals. The full list and counterexamples are in references/math-rules.md.
- Use symbols only, no specific numbers. For inverse proportion, go only up to
|k|and½|k|; the number "4" should never appear in the video. Apply the same logic to general conclusions. - Fractions must use a horizontal fraction bar, never a slash or
a/bnotation. - Triangle symbol is a subscript of S, not on the same line.
- Use "common points" instead of "intersection points."
- Fixed wording for negative cases: "When k<0, the same holds for quadrants II and IV."
- Use a single term for the same concept throughout the entire video; don't switch wording. Caption language should match the textbook.
- Before a conclusion appears, the visual evidence must already be drawn; never show the conclusion first and add the evidence later.
5. Visual Quick Reference (Default Dark Light-Trail Theme)
Full color values and implementation are in references/visuals.md. Remember three ironclad rules for dark backgrounds; everything else can be adjusted:
- Reduce area fills to 18% and add an outline — a 40% fill on a dark background becomes a blurry fog.
- Use "amber outline + very pale blue fill" for triangles/geometric blocks — gold solid fill on a dark blue background turns into a dirty olive color.
- Don't use background colors for tables; use thin lines for separation — a white-background table will look like three continuous rows when copied over.
Increase font size by 4 points compared to the white-background version. The top-left corner icon is fixed as Vibe Knowledge Awards | Scene Subtitle, 26px, letter-spacing 5px, left 84 / top 70.
**Font Ironclad Rule: Three font roles — Noto Sans CJK SC for Chinese, Inter for Latin labels and numbers, TeX Gyre Termes serif italic for math symbols (same family as Times, matches textbook letter shapes). @font-face must be written inside the <template>'s <style> (putting it in <head> will cause it to be discarded by the frame capture harness, and the font will silently not load). Details and four common pitfalls are in references/visuals.md, Section 0.
6. Rendering and Bitrate
| Parameter | Value | Description |
|---|---|---|
--quality | delivery | High-quality preset; the default preset will reduce bitrate. |
--video-bitrate | 3M | Must be explicitly set; mutually exclusive with --crf. |
--resolution | Default landscape (1920×1080) | Use 4K only if explicitly requested. |
--resume | Resume after interruption | Already rendered frames are not re-created. |
Criteria: 90 seconds → file size 15–25MB, bitrate 1.5Mbps or higher, 2700 frames, duration 90.000000s. If numbers don't match, re-render; do not deliver.
When Clarity is Insufficient (Tested: --video-bitrate Doesn't Work)
Tested 2026-11: Using --video-bitrate 30M still results in 1.8Mbps; --quality delivery does the same. In other words, the built-in encoder's bitrate parameters are largely ignored. For delivery-grade clarity, you must take over the encoding yourself. If the user says it's "blurry," follow this path instead of repeatedly adjusting --video-bitrate:
# 1) Render frames only, no encoding (2700 frames / ~2.4GB / ~16 minutes)
npx -y [email protected] render --format png-sequence --output pngseq
# 2) Encode yourself. For static content and fine text, use CBR, not CRF (CRF only gives ~0.5Mbps, which will blur after platform re-encoding).
ffmpeg -v error -framerate 30 -i pngseq/frame_%06d.png \
-c:v libx264 -b:v 4M -minrate 4M -maxrate 4M -bufsize 8M \
-preset slow -tune animation \
-x264-params "nal-hrd=cbr:aq-mode=3:chroma-qp-offset=-2" \
-pix_fmt yuv420p -movflags +faststart -y out.mp4
chroma-qp-offset=-2 is for colored fine lines: cyan and amber text can lose color at the edges with 4:2:0 chroma subsampling; this parameter gives chroma a little more bitrate than luma.
Encoding takes about 2 minutes, yielding 90s / 43MB / 4Mbps. The 40MB mark is the sweet spot for "playable in class and still clear after platform re-encoding"; files under 10MB will look soft on mobile devices. For platforms (WeChat Video / Douyin), create a master version with -b:v 8M (about 85MB) so that it retains detail after platform re-encoding.
Don't rely on 4K supersampling: with software rendering, 2700 frames can take over an hour (tested: only 8% done after 20 minutes), which is unsustainable for a single project's timeline.
After encoding, you must zoom in to 1:1 on a single frame (using crop=760:300) to check for blockiness or ringing on text edges; a full-thumbnail view won't show the blur.
Sound Effects (Point Effects, Not BGM)
If the user says "add some audio" or "it's too quiet" but doesn't ask for BGM, only add point sound effects: each action on screen triggers a sound once, then goes silent. Background music in a math explainer video can reduce the sense of reflection and might also cover the teacher's own narration.
# 1) Write a timeline of action points (in absolute seconds). See assets/sfx-events.example.json for a sample.
python3 <skill directory>/scripts/make-sfx.py events.json -o sfx.wav --duration 90
# 2) Copy the video stream; only mix the audio track (don't change a single pixel of the video).
ffmpeg -v error -i in.mp4 -i sfx.wav -c:v copy -c:a aac -b:a 128k -shortest \
-movflags +faststart -y out.mp4
Six types of sound effects, each for one purpose: brush for curve/line growth, slide for a moving point sliding uniformly, tick for a line of text appearing, ding for cards/titles/questions, thump for the most critical quantity, chime for ✓/key phrases/end credits.
Three rules:
- Don't sound for every line of text — only for "visual actions." About 30 points for a 90-second video is enough; more becomes noisy.
- Keep the peak below -16dBFS.
tickat around -29dB,thump/dingat around -24dB. At this level, it's "audible but not distracting." - After mixing, randomly check with
ffmpeg -ss <t> -t 0.6 -af volumedetect: at sound effect points, it should be -24 to -30dB; in silent sections, it should be -91dB (true silence). If any sound effect point is above -18dB, reduce the volume.
7. Six Self-Checks (Must Pass All Before Delivery)
| # | What to Check | Command | Pass Criteria |
|---|---|---|---|
| 1 | Structural compliance | npx -y [email protected] lint | 0/0 |
| 2 | All six scenes have visuals | scripts/verify.mjs | All six scenes produce images; no black screens, no JS errors. |
| 3 | No element overflow | Same as above; review diagnostic report | No overflow entries; captions at least 80px from bottom. |
| 4 | Timing is correct | Frame extraction mosaic (see qa.md) | Each segment's elements appear at their claimed time; no empty segments. |
| 5 | Correct mathematical presentation | Compare captions with math-rules.md scene by scene | All seven rules pass. |
| 6 | Final video parameters | scripts/inspect-video.mjs | All checks (resolution, frames, duration, bitrate, size) pass. |
Checks 1, 2, 3, 4, and 6 are machine-judged and human-reviewed; Check 5 can only be done by a human — it's the only thing that can prevent a teaching error.
8. Rules for Making Changes
User feedback must be received as a "three-part request": which scene / which second / what to change to. For vague requests like "this doesn't seem right," ask for clarification; don't redo the entire section.
After changes, rerun the relevant checks: changes to coordinates or font size → 1+2+3; changes to text → 5; changes to multiple scenes → all six checks.
It's faster to batch several changes together than to make many small, repeated edits, but major cross-scene changes require a new storyboard for confirmation.
See references/revisions.md for real revision logs (ten examples).
9. Prohibited Actions
- Missing
assets/gsap.min.jsor using CDN instead. - Using
scaleX/scaleY+transformOriginfor SVG line growth (only grows from one end). - Using
onUpdateto drive SVG properties (onUpdateis not triggered during seek, leading to wrong screenshots). - Tweening properties that trigger reflow, such as
letterSpacingorwidth. - Calling
getTotalLength()whend=""(must first provide a sentineld). - Rendering without specifying bitrate.
- Putting all content into a single HTML file.
- Using a slash to represent fractions in formulas.
- Delivering only the mp4 without explaining what was changed.
- Including
<sup>inside SVG<text>(HTML tags inside SVG will misalign the entire text; use²for superscripts). - Applying a scale pulse on a full-width (1920) container (a 4% increase will hit the border; Check 3 will flag an overflow. Instead, apply the scale to an inline-block container inside).
- Placing an absolutely positioned strikethrough as a sibling of the target text (it will span across the entire column; place it inside a relative-positioned container within the text itself).
- Using
repeat: -1for a looping pulse (the lint will throw an error if a sub-composition has nodata-duration; instead, use a finite number of repetitions). - Leaving any temporary
.htmlfile in the project root (the lint will report "can only have one index.html"). - When drawing a curve with two paths (a bright line + a glow), only setting
dasharray/dashoffseton the bright line (the glow will appear entirely before it should, looking like "a dark shadow before the line is drawn"; both must be set and tweened at the same time). - Using
--video-bitrateto achieve clarity (tested and ineffective; see Section 6). - Encoding only with CRF without checking the file size (CRF for static content only gives ~0.5Mbps, which will blur after platform re-encoding).
- Using the system default font for Latin letters and numbers (
system-uiin headless Chromium renders as DejaVu Sans, which is dull for letters and numbers; always use"Inter", sans-serif). - Writing Chinese font names in
font-family(e.g.,"Noto Sans SC"; if not installed, the linter will outputfont_family_without_font_face). - Drawing coordinate axes as just two straight lines without adding directional arrows at the endpoints (textbook standard: x to the right, y upward, each with a solid arrow; see the "Coordinate Axes" section in references/visuals.md).
9.5. Adding Fonts to an Old Project
For an old project (where the font is still system-ui), you don't need to rebuild; run one command to upgrade to the three-font system:
node scripts/apply-fonts.mjs --project <project folder>
It does five things: copies fonts to assets/fonts/, merges @font-face into the <template>'s <style> (anything in <head> will be discarded by the frame capture harness), locks .row/.cap/.tag containers to Inter, wraps each bare variable x/y/k/S in the text body with <i> (pure CSS selectors can't target bare text, so this is the most common reason "the font changed but nothing looks different"), and changes SVG's font-family to 'Termes', serif and italicizes x/y/k. Math symbols uniformly get calc(1em + 2px) to compensate for the visual shrinkage of serif vs. sans-serif. It is idempotent; running it multiple times won't duplicate anything.
Proceed as usual: lint → verify → render. The video must be re-rendered; these font changes won't affect the old output file.
10. Reference Files
| File | When to Read |
|---|---|
| references/pipeline.md | First project, or when explaining the four-step pipeline to someone. |
| references/math-rules.md | Before writing any formula or conclusion. |
| references/visuals.md | When defining colors, font sizes, cards, and formula layouts. |
| references/prompts.md | For clarifying questions, writing storyboards, or teaching someone how to start. |
| references/revisions.md | When the user gives feedback, or to explain to a curriculum group "what it's like to revise something ten times." |
| references/qa.md | For every self-check round, and to determine if a video is ready for delivery. |
| references/two-tracks.md | When narration/music/hands/maps/characters are needed; switching to B track. |
assets/project-template/ | Copied by init-project.mjs when creating a project; do not edit manually. |
assets/gsap.min.js | Copied into the project's assets/ folder when creating a project. |