How General Translation films are made in Prototemplate's motion/ folder with HyperFrames.
node scripts/install-skills.mjs gt-films --project <dir>Run it from a Prototemplate checkout. It links skills/gt-films into the project’s .claude/skills and .agents/skills; --copy vendors the folder instead, and --dry-run prints each step first.
General Translation (GT) makes its brand films, blog trailers and translation-history films as HyperFrames compositions: HTML pages with one paused GSAP timeline that the HyperFrames CLI seeks and renders frame by frame to MP4. They are built in $PROTOTEMPLATE/motion/, one project per film, from one shared kit and one brief, motion/MOTION.md. A new film goes through the same steps: three directors' treatments, a judge who writes the script and the concept, a storyboard on the 0.5 s grid, the build, narration and music, a draft, critic passes and a delivery render. This skill states each step, the rules it answers to, and the checks a film passes before it reaches /motion.
Paths are relative to a Prototemplate checkout ($PROTOTEMPLATE) unless they name another one, and paths that start with films/, kit/ or out/ sit inside motion/. references/ is this skill's folder of detail files. Its helpers need only Node and ffmpeg and run as node <this skill's folder>/scripts/<name>.mjs (skills/gt-films/scripts/ in a Prototemplate checkout). motion/ is untracked and exists only in the checkout of the session that makes the films; the public repository holds the published copies under public/media/, the generated /motion data and this skill. Where motion/ is missing, the kit, the brief and the film sources are missing too: review against this skill, and ask Kevin for the Videos session before building a film.
motion/: Kevin calls it the Videos session. Kevin, 2026-10-03: "the videos one should be making them, and this one is just for prototemplate work". Any other session reads motion/, sends film requests to the Videos session by name, and runs no film lane itself (Kevin, 2026-10-02: "wait another agent is working on the motion and videos").motion/ stays untracked and motion/out/ is in .gitignore. Never run git add -A or git add . in the shared checkout. On 2026-10-01 a broad add in another session pushed 229 files (72.6 MiB) of motion/ to main in b56e64c, and commit 8c8d6fb had to untrack them again. Stage paths by name.motion/out/. The committed outputs are the web copies in public/media/ and the files pnpm build:motion writes, and the Prototemplate session commits them.motion/kit/) is read only for a film lane. A kit change goes through the Videos session, because every film loads it.motion/MOTION.md whole before writing a frame. Its dated sections at the top override the older sections below them: "Dithered artifact pictures (2026-10-05)", "Round 7 direction", "Round 4 direction". A film's own SCRIPT.md, CONCEPT.md and NOTES.md record later rounds (7b, 7d, 8) and override MOTION.md for that film.BRAND.md (the name, the idea, the voice, the mark, color, type, language as material, the avoid list), DESIGN.md (the four colors, the line law, the doubled line, the isometric family, the 1-bit Bayer language, moving type, motion discipline) and the deck (deck/, rendered as deck/preview/sNN-dark.jpg; open slides 1, 3, 6, 9, 14, 17 to 23, 26, 30, 34, 37, 54 and 64 before designing).## The films section of MOTION.md, one entry per film headed ### <slug>: <title> (<length>). scripts/build-motion.mjs parses that heading, so keep its form.BRIEF.md facts and hedges, Kevin's writing rules, public-domain or credited sources, deterministic rendering and the studio standard.| step | output | rule |
|---|---|---|
| Research package (series films) | films/<slug>/BRIEF.md | five numbered sections: an animation script table, the long-form post, a vocabulary table, sources with rights, a fact-check list in three groups |
| Three directors | motion/concepts/<slug>/<lens>/ | each writes a one-sentence story, a script, key frames and a motion test of the signature move |
| Judge | films/<slug>/SCRIPT.md, CONCEPT.md | scores the three on six criteria of 10, picks one, grafts only moves that use the winner's grammar |
| Kevin reads the scripts | a sent page or file | the build waits for his answer |
| Storyboard | films/<slug>/STORYBOARD.md | beats on the 0.5 s grid, words verbatim, picture, motion and easing, transition out |
| Takes and bed | films/<slug>/audio/ (sound/ in journey-to-the-west and modern-hebrew) | one take per line through kit/audio/el.mjs, the bed edited to the film's length |
| Build | index.html, lib/ | key actions timed to the takes' word timings |
| Check | npx -y hyperframes@0.8.106 check . | ends with 0 errors; every kept warning is intentional and named in NOTES.md |
| Draft | motion/out/_draft-<slug>.mp4 | 30 fps, draft quality |
| Critics | CRITIQUE-<n>.md or NOTES.md | a defect table with times and fixes; picture, sound and story, and history and rights for a series film |
| Revise and verify | a new draft | each fix checked on the frames that showed the defect |
| Delivery | motion/out/<slug>.mp4 | 60 fps, delivery quality, the earlier final copied to motion/out/v<N>/ first |
| Finish | poster, contact sheet, credits, NOTES.md | see section 10 |
SCRIPT-v2.md with a list of the visual changes, at 60 to 75 s and about 140 to 190 words.references/pipeline.md has the package format, the director and judge briefs, the storyboard format and the workflow lessons.
cd motion/films
npx -y hyperframes@0.8.106 init <slug> --example blank --non-interactive
cd <slug> && ln -sfn ../../kit kitcheck, snapshot, render, preview). The wiki's hyperframes skill suggests upgrading a pinned project; the films keep 0.8.106 until the Videos session moves every film together.kit/... or the film's own folder. Never load from a CDN: renders must not touch the network.var(--font) from kit/tokens.css. A literal font-family: 'Inter' anywhere in a composition makes the compiler fetch Inter from Google Fonts and override the kit's InterVariable ("[Compiler] Fetched 11 font face(s) for "Inter" from Google Fonts"); the static cut is about 5 percent wider and line masks clip. A film's own faces (JetBrains Mono for a code panel, a CJK serif, a Hebrew face with niqqud) live in its fonts/ folder under private family names. Fallback families for glyph text are set from JavaScript, because the compiler reads family names from <style> text and fetches any it cannot find.window.__timelines['<composition-id>'] (initialise window.__timelines first). The root carries data-composition-id, data-start="0", data-width, data-height and data-duration.Date.now, performance.now, unseeded Math.random (use GTDither.rng(seed)), requestAnimationFrame, timers or repeat: -1. Canvas drawing happens in the timeline's or a tween's onUpdate from tween-driven proxies. A picture a canvas reads is also a hidden <img> in the DOM, so the renderer waits for it.transform with a GSAP tween on the same element; set the start inside fromTo.index.html (composition_file_too_large and timeline_track_too_dense are then kept warnings). A long film splits scenes into sub-compositions (journey-to-the-west builds eight with tools/compose.py).films/_smoke/ (a dither field mixing two pictures, Inter type, the bar monogram), films/_gem/ (gem smoke, a glass shape, a dithered field) and films/_endcard-fire/ and _endcard-blue/.index.html, lib/, audio/ (or sound/), assets/, fonts/, archive/ (earlier compositions and storyboards), SCRIPT.md, CONCEPT.md, STORYBOARD.md, NOTES.md and, for a published film, CREDITS.txt. init also writes hyperframes.json, meta.json, package.json and its own AGENTS.md and CLAUDE.md. Each film builds its sound with its own scripts, and its NOTES.md names them: Fuma Nama's lib/ holds cues.mjs, make-voice.mjs, make-bed.mjs and make-mix.mjs; the docs film keeps its drawing in lib/film.js and its sound in audio/make-bed.py and audio/mix.py; the series films use Python tools under tools/ and sound/.references/traps.md lists the composition and render traps the films met, each with its fix.| path | what it is |
|---|---|
kit/tokens.css | the Inter @font-face on kit/fonts/InterVariable.woff2, --font, the colors and the deck ladder at 1920 (.t-display 106, .t-h1 88, .t-h2 53, .t-lead 31, .t-body 26, .t-cap 18) |
kit/dither.js | window.GTDither: grid(canvas, cell), toneFromImage(img, grid, { fit, focusX, focusY, gamma, invert, blur, lift, region }), draw(grid, field, { ink, paper }), mix(a, b, p) on one smoothstep, fields.ramp, fields.disc, fields.horizon, rng(seed) |
kit/gemsmoke.js | window.GTGem (ES module, fires gtgem-ready): mount(host, { palette, params, image, width, height, pixelRatio, offset, rate }) resolves to { at(t), set(params), dither(grid, { tones, gain, gamma, amount }), canvas, mount } |
kit/endcard/ | the shared series end card, addEndCard(tl, { palette, title: [line1, line2], url, start }) |
kit/sheet.js | window.GTSheet, the deck's series frame (rails 67 px in, registration crosses). The blog films dropped it on 2026-10-04 |
kit/marks/, kit/brand/gt-mark.svg | the speed marks (bar monogram, lockup, dithered, ASCII, plate inverted, double cut, livery stack, two-way, globe G) and the doubled-line GT mark |
kit/logos/, kit/logos/adopters/ | Fumadocs, MDX, Next.js, React, TanStack and Mintlify marks; the thesvg marks and GitHub avatars of Fumadocs' adopters |
kit/gem-shapes/ | processed shape PNGs for gem smoke glass (gt-bar-monogram, gt-mark, fumadocs-moon); add a row to make.mjs and run it for a new one |
kit/sources/, kit/two-tone/, kit/tone/, kit/blog/ | full-resolution picture sources, the deck's dithered mood and opener files, the sign-in plate's tone grids, every image the blog posts ship |
kit/lottie/ | the Ramp demo's translated .lottie files in en, de, es, ja and zh with a manifest |
kit/gsap.min.js and plugins, kit/lottie.min.js, kit/fflate.min.js, kit/paper-shaders/ | GSAP 3.15 with DrawSVG, MorphSVG, SplitText, Flip, MotionPath and CustomEase; lottie-web 5.13; Paper Shaders 0.0.78 (Apache-2.0) |
kit/audio/el.mjs, kit/audio/voice.json | ElevenLabs narration, beds, music and transcription; the narrator's settings |
kit/_retired/ | the dictionary pictures, retired 2026-10-05. Never use them |
gem.at(seconds) inside the timeline's onUpdate. Change uniforms per frame with mount.setUniformValues (cached and synchronous); gem.set() loads its image asynchronously, so never call it per frame. Keep at most two full-frame mounts live at once. Use pixelRatio: 0.5 for a mount that is only read or dithered. An outer glow lays a faint smoke wash over the whole frame (plain above about 0.2, and visible in a dithered field's black cells at any value), so keep it at 0 under a field. Clip glass 1 px outside its limb when hard dither cells sit beside it.gt-dither owns the Blue Marble tone standard for dithered artifact pictures.data-duration to start + 4, end the film's clips at start, add the card after the film's own tweens and register the timeline after it. The card is silent; the bed resolves under it. frame defaults to false. hyperframes check reports two text_occluded infos at start + 0.33, which are expected. The card's last frame is the blog films' poster. kit/endcard/README.md has every measurement.These rules bind the blog and brand films. The series films choose their own look inside section 2's limits.
#2f5ce0, smoke #ffffff and #86a8ff (Designing docs for humans);#000000, smoke #fe5b16, #f7ff61 and #ffffff, with ember #7a2a08 in prints (Fuma Nama);#070707 stays the ground between material scenes.var(--font), sentence case, proper nouns capitalised, no trailing period. Seat each line's first glyph ink on x 160 from the loaded face's side bearing. Fuma Nama's cap tops sit at y 172; the docs film sets its heading box at top 146. A sentence that cannot fit two lines at 100 px is replaced by the post's own shorter wording.CREDITS.txt. The blog films show no photographs or scans, so they print no credit line; their third-party logos follow the Logos rule below.expo.out or power3.out, moves on power2.inOut, processes on none, no overshoot. Arrivals and scene changes land on the 0.5 s grid. A sentence holds at least (words / 3) + 1 s after it has fully arrived. Transitions come from MOTION.md's four: a tone mix between dither fields, a hard cut on a beat, a hairline that draws a seam, or moving type. gt-motion carries the full motion rules.Every film carries a narrator and a music bed, both through kit/audio/el.mjs (references/sound.md has the commands, the mix recipe and the traps).
el.mjs reads (~/.config/elevenlabs-sfx/config.yml). Never print it, copy it, pass it on a command line or put it in a prompt. Which account the key belongs to has changed more than once; test the Music API with a short request before composing a new bed.kit/audio/voice.json names: Clara since 2026-10-02 (eleven_multilingual_v2, stability 0.65, style 0.2, speed 1.0). Kevin chose her after "make the voice more australian and make the voice less shaky", "a much more friendly australian voice", "like 2 but more female" and "lets use clara". Never generate below speed 1.0: the slowed takes measured the most pitch wobble. Calm comes from the gaps between lines.kit/audio/auditions/series/in-context/. A series narrator gets its own voice file or --voice per take, so the blog films keep Clara.--prev and --next, the request text exactly the script's. Respell a name in the request text only (line 7 of Fuma Nama sent "Ver-sell" for Vercel) and keep the script's spelling everywhere else. One take per line unless a take is wrong. Check every take, the bed and the final with el.mjs hear. A non-English take (Mandarin, Hebrew) needs a native listener's sign-off before release.enforceAacTruePeak), and its limiter has no lookahead. Control peaks in the ffmpeg masters before the mix: Fuma Nama limits each narration master to -3.6 dBTP at 192 kHz, and the docs film to a -3.5 dBFS ceiling at 4x oversampling. A film measuring about -1.5 dBTP, or quieter than the sum of its stems, has been corrected.$PROTOTEMPLATE/content/blog/<post>.mdx, or the MDX in gt-cloud apps/landing/content/blog/en-US/) or its BRIEF.md states. Titles, names and dates appear exactly as published.gt-voice: plain declarative English, no marketing adjectives, no exclamation marks, no rhetorical questions, no em dashes, no "X, not Y" pairs, no metaphors, no eyebrows. Product tokens keep their exact form (gt, gt-next, npx gt translate) and never start a sentence.A critic watches the render frame by frame against MOTION.md's "How a film is judged", the BRIEF, SCRIPT.md, CONCEPT.md and the writing rules, and reports every defect with its time and a fix. references/critic.md has the full procedure and the report format.
skills/gt-films/scripts/frames.mjs, which selects by frame index). Read signature moves frame by frame. Read a handful at 1280 x 720 for legibility.skills/gt-films/scripts/scan.mjs --cuts ...).el.mjs hear on the render against SCRIPT.md, skills/gt-films/scripts/measure-render.mjs for loudness, peak and stream lengths, silence at the head and tail.lang and dir, because per-character spans break Arabic joining and Devanagari matras.# | time (s) | defect | fix, then "Left as they are" with a reason for each, then what was fixed and how each fix was checked on a new draft.# from motion/films/<slug>
npx -y hyperframes@0.8.106 check .
npx -y hyperframes@0.8.106 render . -o ../../out/_draft-<slug>.mp4 --quality draft --fps 30 --workers 3 --quiet
npx -y hyperframes@0.8.106 render . -o ../../out/<slug>.mp4 --quality delivery --fps 60 --workers 3
npx -y hyperframes@0.8.106 snapshot . --at <seconds> --no-end --describe falsedelivery is an alias of high (x264, preset slow, CRF 15); the wiki's hyperframes-cli skill writes --quality high for the same preset.--workers 3 on the shared machine, where several films and other lanes render at once. Record the machine's load in NOTES.md when a render or a timing check runs slow.motion/out/v<N>/ folder (today v1 to v5, v7, v7d and v8; there is no v6). Earlier finals are never deleted.--quiet, save its log (| tee), and check that the log never mentions Google Fonts (skills/gt-films/scripts/measure-render.mjs <film.mp4> --log <file>).--crf 0) with different worker counts compared by framemd5 must match on every frame. When large type rasterises differently between render processes on the hardware GPU, render with --no-browser-gpu (modern-hebrew does).out/<slug>.mp4; out/<slug>.png, the poster (the blog films use the settled end card, snapshotted on the hardware GPU path, since --no-browser-gpu moves the smoke by up to 5 levels); out/_sheets/<slug>.png, a contact sheet of one frame per second in six columns of 480 px tiles with the time under each (films/journey-to-the-west/tools/sheet.py); films/<slug>/NOTES.md with the deliverables, measurements, ElevenLabs ledger and traps; and for a published film out/<slug>.credits.txt and films/<slug>/CREDITS.txt.ffmpeg -i <slug>.mp4 -c:v libx264 -preset slow -crf 22 -pix_fmt yuv420p -c:a copy -movflags +faststart <slug>-share.mp4 (jihe-yuanben went from 153 MB to 39 MB)./motion lists every roster film with its length and status, plays the published films, and shows each series film's research package at /motion/<slug>. /brand shows the blog films under "Made with the system".public/media/<name>-film.mp4 with the moov atom at the front (ffmpeg -i <in> -c copy -movflags +faststart <out> when the streams stay unchanged), writes public/media/<name>-poster.jpg at 1920 x 1080, lists both in public/media/README.md, adds the film's film and poster paths to public/motion/published.json (a new film) and pins the cut with node scripts/build-motion.mjs --pin <slug>, then runs pnpm build:motion.public/motion/published.json pins each published cut by the SHA-256 of its video and audio streams (a faststart remux hashes the same as its render), its label and its length. pnpm build:motion copies <slug>.credits.txt, sheets/<slug>.webp (and the .png up to 20 MB) and scripts/<slug>.md to public/motion only from the folder in motion/out whose render has the pinned streams (out/ itself, or an archive folder with the kit's sheets/ or scripts/, such as out/series-100s), and checks the script's label and length against the pin. A newer render in out/ is listed on the site as "in review" by its label and length only. So keep each cut's records beside its render: when a final is replaced, move the old render with its credits, sheet and script into an archive folder with the same layout.pnpm build:motion also reads motion/MOTION.md and each films/<slug>/BRIEF.md and writes src/lib/motion.ts and public/motion/<slug>.md. A film's status comes from the files: rendered when its final or web copy exists, in production when its folder exists, planned otherwise. The script throws before writing anything when a BRIEF's shape or a script's shape changes or a web copy is not its pinned cut, and refuses quoted hex colors in motion.ts and absolute local paths anywhere. Its outputs are committed, so the site builds without motion/.pnpm build:motion. The Videos session regenerates the credits files first when a picture changed.Promos, product demo videos, README and launch GIFs, launch captures and vendor briefs are made outside motion/ and follow references/short-media.md.
/brand or /motion in the same round.var(--font) and the render log has no Google Fonts line; nothing loads from a CDN.el.mjs hear, and non-English takes are flagged for a native listener.measure-render.mjs passes: -17 to -15 LUFS, true peak under -1 dBTP, AAC, audio length equals video length, 1920 x 1080 at 60 fps.scan.mjs shows no unplanned black frame, no single-frame flicker and the largest changes on the planned cuts.hyperframes check ends with 0 errors and NOTES.md names every kept warning.out/v<N>/; poster, contact sheet, NOTES.md and credits are written.motion/ is staged, and the Prototemplate session was told about a re-render of a published film.references/short-media.md).Wiki skills this one depends on: hyperframes (the entry point), hyperframes-core (the composition contract; read references/variables-and-media.md for <audio> clips), hyperframes-animation (motion rules and the Lottie adapter), hyperframes-keyframes (seek-safe moves), hyperframes-cli (check, snapshot, render), hyperframes-audio (the mix, <hf-audio-group> and carve.mjs), elevenlabs-sfx (the config path el.mjs shares). GT skills beside it: gt-motion (the motion rules for pages and films), gt-voice (the writing rules and replies to the vendor), gt-dither (the Bayer material and the artifact picture standard), gt-brand (the marks and the palettes), gt-isometric (the docs film's isometric bridge), gt-diagrams (the doubled-line connector), gt-graphics (still artwork such as the partnership globes), prototemplate (the /motion page).
motion/MOTION.md (the brief, round directions dated 2026-10-01 to 2026-10-05); motion/kit/endcard/README.md; motion/kit/audio/el.mjs; motion/kit/audio/voice.json; motion/kit/tokens.css; motion/kit/dither.js; motion/kit/gemsmoke.js.motion/films/jihe-yuanben/BRIEF.md, SCRIPT.md, CONCEPT.md, NOTES.md, CREDITS.txt; motion/films/blog-fuma-nama/SCRIPT.md, CONCEPT.md, STORYBOARD.md, NOTES.md; motion/films/blog-designing-docs/NOTES.md, STORYBOARD.md; motion/films/journey-to-the-west/CRITIQUE-1.md, NOTES.md; motion/films/modern-hebrew/NOTES.md.delivery maps to high, preset slow and CRF 15) and enforceAacTruePeak, read in the package's dist/ on 2026-10-05.scripts/build-motion.mjs, src/lib/motion.ts, public/media/README.md, .gitignore.skills/engineering/hyperframes/SKILL.md, skills/engineering/hyperframes-core/SKILL.md, skills/engineering/hyperframes-cli/SKILL.md, skills/.runtime/all/hyperframes-audio/SKILL.md, skills/misc/elevenlabs-sfx/SKILL.md.public/media/README.md.The folder skills/gt-films as agents fetch it. Each file opens raw.
SKILL.mdreferences/critic.mdreferences/pipeline.mdreferences/short-media.mdreferences/sound.mdreferences/traps.mdscripts/frames.mjsscripts/measure-render.mjsscripts/scan.mjs