npx skills add ...
npx skills add heygen-com/hyperframes-community-skills --skill camera-3d-captions
Captions living in 3D space around a talking head: a camera flies between the speaker and the words (whip-in, 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, bold sans) or hand-drawn (p5.brush write-on). Trigger on: "3D text", "3D camera captions", "text around / behind the 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 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.
npx hyperframes@0.8.62 (downloads from
registry.npmjs.org). Use that exact version for every command below.ffmpeg on PATH.fonttools, brotli, numpy, pillow.npx hyperframes@0.8.62 remove-background runs locally. On first run it downloads its person-segmentation
model (~170 MB) to ~/.cache/hyperframes/.cdn.jsdelivr.net at preview/render time.p5-paint-animation skill installed and set up (its setup downloads
pinned 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-backs
never show an edge.person.webm: the alpha matte, same frames.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, a
fly-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 any
width.(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 their
on-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. Put
the 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 hyperframes@0.8.62 check . --no-contrast. The ghost layers trip the WCAG contrast check; the whip and wipe seams
are 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.
npx hyperframes@0.8.62 render . --crf 12, and check full-resolution frames per
beat.snapshot --at shares one page, so grain does not re-roll between captures. Judge grain on a render.