> parallax truck, push), caption groups sit at different depths so camera moves pull them apart, hero words hide BEHIND the speaker through an alpha matte, a ring of words wraps round the speaker and turns in front of them, focus racks between depths, and text steps at 15 fps with ghost motion blur. Works in any type style (editorial serif, speaker", "camera moves through the text", "depth captions on my avatar video". Covers prep (portion, padded plate, matte, word clock, font metrics), the shot grammar, depth rules, QC, and limits.
npx skills add https://github.com/heygen-com/hyperframes-community-skills --skill camera-3d-captions
The After Effects move (3D camera, depth of field, text on a path, Posterize Time 15, motion blur) rebuilt as a
deterministic per-frame painter for HyperFrames. One clock paints plate, matte and every glyph. Nothing is a CSS
animation.
The words must perform in depth around the speaker.
shot), extreme close-ups (no room behind or around the head), or a subject a person-matting model can't separate.
npx [email protected] (downloads fromregistry.npmjs.org). Use that exact version for every command below.
ffmpeg on PATH.fonttools, brotli, numpy, pillow.npx [email protected] remove-background runs locally. On first run it downloads its person-segmentationmodel (~170 MB) to ~/.cache/hyperframes/.
cdn.jsdelivr.net at preview/render time.starts run late.
p5-paint-animation skill installed and set up (its setup downloadspinned puppeteer, Chrome for Testing, p5 and p5.brush). build-sprites.py runs it headlessly; frames are written
to that skill's out/ and removed afterwards.
(assets/, renders/).
bash scripts/prep-take.sh <take.mp4> <start_s> <dur_s> <project>/assets produces:portion.mp4: the audio source.plate-tall.mp4: reflect-padded 240 px each side, 480 px top, 240 px bottom, so whips, trucks and pull-backsnever show an edge.
person.webm: the alpha matte, same frames.any mismatch.
python3 scripts/font-metrics.py assets/metrics.js key=font.woff2[@wght=…,opsz=…] ….
font-variation-settings,font-optical-sizing: none). Otherwise the browser's opsz follows the size and words overlap.
person.webm alpha before placing anything.assets/kit/{cam3d.js,tables.js,finish.js} and metrics.js into the project's assets/.references/worked-film.html is the complete reference film. It has two shots, a body wipe, both ring types, afly-through, a finale and the grade; its media is not included.
references/recipes.md holds the depth-first groups, the wrapped ring, the staircase and hand-drawn words.C3.init(metrics, {W, H}):tables.js applies unscaled at anywidth.
(W − 1440) / 2 to wipe.x.Pick 4–6 beats. Split each spoken clause into its own group.
| Beat | Build | Rule |
|---|---|---|
| Whip-in punch-in | camA table (19 f from 502 px low) then a linear creep | plate S0 1.1–1.3; the frame never lands |
| Depth groups | 2–3 caption groups per clause at D 1250 / 1500 / 2200 | near = bigger with a bigger shadow; the creep and truck reveal the parallax |
| Hero behind | hero word in the behind layer at the plate depth, masthead-style across the head | the head hides the middle letters; ≥ 60 % stays legible; the ascender is inside the frame |
| Parallax truck | swing segment, tx 420 | the old layout fades over 3 steps; the next layout arrives by parallax |
| Body wipe | the speaker's own matte, brightness(0) blur(), leading edge from wipe.x | shot A clips left of the band, shot B right; pull-out on both sides |
| Wrapped ring | horizontal circle IN FRONT of the speaker (centre Z 2000), 11–16° from above, turning | words enter small at the side as spoken, swing round the front, settle; lower arc flips to read upright |
| Fly-through | phrases at D 1500 / 2000 / 2500, camera swings between them | focus follows the phrase |
| Staircase finale | push segment; caption words each deeper and smaller down a diagonal | the push drives the near words apart |
shutter: 0.5 on turning rings.fr(t) = 2·round((t − 0.2)·15).ENTRY: captions 30 px, heroes 110–260 px. Ring words ease their last 30–44 px along the path.Use RING_ENTRY's overshoot only when no neighbour is seated yet.
[6, 16, 0.5, 1500] scales as 1500/(D − cz).size / rise are on-screen px at the landing camera (tland). Sprites are rendered at theiron-screen size.
ringLine (on-screen advance; type: track 1.10, gap 26–30; hand-drawn: track 1.0,gap 28).
side, zSplit) only when it is MEANT to pass behind the head.finish.js supplies curves, saturation 1.12 for skin, and grain re-rolled per frame. Putthe opaque scene (plate + behind text + matte) in a filtered wrapper. Give front text the same grade with grain
composited atop, so a word split behind/front stays one colour (see references/worked-film.html).
scripts/hand/build-sprites.py:references/recipes.md.npx [email protected] check . --no-contrast. The ghost layers trip the WCAG contrast check; the whip and wipe seamsare overlapping by design.
scripts/frame-bounds.js and run __frameBounds(0, dur).[]: no group outside the frame for 5+ steps while visible.scripts/glyph-gaps.js and run __glyphGaps(t) at rest.opposite sides of the wipe band both show.
npx [email protected] render . --crf 12, and check full-resolution frames perbeat.
snapshot --at shares one page, so grain does not re-roll between captures. Judge grain on a render.Take heygen-com/camera-3d-captions from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npx.
Without those the skill loads but fails at the first command.