Skill Tracer
Skills › HyperFrames › media-use › authoring.md

authoring.md

162 lines · skills/media-use/audio/references/captions/authoring.md at a595755

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Captions
2
3<!-- registry-items: allow=max-width,data-composition-src,hyperframes-registry,blend-mode,caption-style,font-family -->
4
5**The live search is the source of truth for what the registry has.** The table(s) below are a hand-maintained sample and under-cover by design: run `npx hyperframes catalog --query "<what you want>" --json` — it needs nothing installed — before concluding the registry lacks something. Item names here are checked against `registry/registry.json` by `bun run lint:skills`.
6
7Before authoring: confirm the transcript came from the right Whisper model. CLI default `small.en` silently translates non-English audio — see [`../transcribe.md`](../transcribe.md) → "Language Rule" and [`transcript-handling.md`](transcript-handling.md) for the mandatory quality check.
8
9Analyze spoken content to determine caption style. If user specifies a style, use that. Otherwise, detect tone from the transcript.

Captions

<!-- registry-items: allow=max-width,data-composition-src,hyperframes-registry,blend-mode,caption-style,font-family -->

The live search is the source of truth for what the registry has. The table(s) below are a hand-maintained sample and under-cover by design: run npx hyperframes catalog --query "<what you want>" --json — it needs nothing installed — before concluding the registry lacks something. Item names here are checked against registry/registry.json by bun run lint:skills.

Before authoring: confirm the transcript came from the right Whisper model. CLI default small.en silently translates non-English audio — see [../transcribe.md](../transcribe.md) → "Language Rule" and [transcript-handling.md](transcript-handling.md) for the mandatory quality check.

Analyze spoken content to determine caption style. If user specifies a style, use that. Otherwise, detect tone from the transcript.

Raw lines 1 to 10
11## Transcript Source
13```json
15 { "id": "w0", "text": "Hello", "start": 0.0, "end": 0.5 },
16 { "id": "w1", "text": "world.", "start": 0.6, "end": 1.2 }
18```
20`id` (`w0`, `w1`, …) is the stable reference for per-word overrides and is added by `hyperframes transcribe`. It's optional for backwards compatibility with hand-authored transcripts. See [`../transcribe.md`](../transcribe.md) → "Output Shape" for how this is produced, and [`transcript-handling.md`](transcript-handling.md) for cleanup before consumption.

Transcript Source

[
  { "id": "w0", "text": "Hello", "start": 0.0, "end": 0.5 },
  { "id": "w1", "text": "world.", "start": 0.6, "end": 1.2 }
]

id (w0, w1, …) is the stable reference for per-word overrides and is added by hyperframes transcribe. It's optional for backwards compatibility with hand-authored transcripts. See [../transcribe.md](../transcribe.md) → "Output Shape" for how this is produced, and [transcript-handling.md](transcript-handling.md) for cleanup before consumption.

Raw lines 11 to 21
22## Style Detection (When No Style Specified)
24Read the full transcript before choosing. Four dimensions:
26**1. Visual feel** — corporate→clean; energetic→bold; storytelling→elegant; technical→precise; social→playful.
28**2. Color palette** — dark+bright for energy; muted for professional; high contrast for clarity; one accent color.
30**3. Font mood** — heavy/condensed for impact; clean sans for modern; rounded for friendly; serif for elegance.
32**4. Animation character** — scale-pop for punchy; gentle fade for calm; word-by-word for emphasis; typewriter for technical.

Style Detection (When No Style Specified)

Read the full transcript before choosing. Four dimensions:

1. Visual feel — corporate→clean; energetic→bold; storytelling→elegant; technical→precise; social→playful.

2. Color palette — dark+bright for energy; muted for professional; high contrast for clarity; one accent color.

3. Font mood — heavy/condensed for impact; clean sans for modern; rounded for friendly; serif for elegance.

4. Animation character — scale-pop for punchy; gentle fade for calm; word-by-word for emphasis; typewriter for technical.

Raw lines 22 to 33
34## Per-Word Styling
36Scan for words deserving distinct treatment:
38- **Brand/product names** — larger size, unique color
39- **ALL CAPS** — scale boost, flash, accent color
40- **Numbers/statistics** — bold weight, accent color
41- **Emotional keywords** — exaggerated animation (overshoot, bounce)
42- **Call-to-action** — highlight, underline, color pop
43- **Marker highlight** — for beyond-color emphasis (highlight sweep, circle, burst, scribble, sketchout), search the registry (`/hyperframes-registry`).

Per-Word Styling

Scan for words deserving distinct treatment:

  • Brand/product names — larger size, unique color
  • ALL CAPS — scale boost, flash, accent color
  • Numbers/statistics — bold weight, accent color
  • Emotional keywords — exaggerated animation (overshoot, bounce)
  • Call-to-action — highlight, underline, color pop
  • Marker highlight — for beyond-color emphasis (highlight sweep, circle, burst, scribble, sketchout), search the registry (/hyperframes-registry).
Raw lines 34 to 44
Names a skill hyperframes-registry :43
45## Script-to-Style Mapping
47| Tone | Font mood | Animation | Color | Size |
48| ------------ | ------------------------ | ---------------------------------- | --------------------------- | ------- |
49| Hype/launch | Heavy condensed, 800-900 | Scale-pop, back.out(1.7), 0.1-0.2s | Bright on dark | 72-96px |
50| Corporate | Clean sans, 600-700 | Fade+slide, power3.out, 0.3s | White/neutral, muted accent | 56-72px |
51| Tutorial | Mono/clean sans, 500-600 | Typewriter/fade, 0.4-0.5s | High contrast, minimal | 48-64px |
52| Storytelling | Serif/elegant, 400-500 | Slow fade, power2.out, 0.5-0.6s | Warm muted tones | 44-56px |
53| Social | Rounded sans, 700-800 | Bounce, elastic.out, word-by-word | Playful, colored pills | 56-80px |

Script-to-Style Mapping

ToneFont moodAnimationColorSize
Hype/launchHeavy condensed, 800-900Scale-pop, back.out(1.7), 0.1-0.2sBright on dark72-96px
CorporateClean sans, 600-700Fade+slide, power3.out, 0.3sWhite/neutral, muted accent56-72px
TutorialMono/clean sans, 500-600Typewriter/fade, 0.4-0.5sHigh contrast, minimal48-64px
StorytellingSerif/elegant, 400-500Slow fade, power2.out, 0.5-0.6sWarm muted tones44-56px
SocialRounded sans, 700-800Bounce, elastic.out, word-by-wordPlayful, colored pills56-80px
Raw lines 45 to 54
55## Word Grouping
57- **High energy:** 2-3 words. Quick turnover.
58- **Conversational:** 3-5 words. Natural phrases.
59- **Measured/calm:** 4-6 words. Longer groups.
61Break on sentence boundaries, 150ms+ pauses, or max word count.

Word Grouping

  • High energy: 2-3 words. Quick turnover.
  • Conversational: 3-5 words. Natural phrases.
  • Measured/calm: 4-6 words. Longer groups.

Break on sentence boundaries, 150ms+ pauses, or max word count.

Raw lines 55 to 62
63## Positioning
65- **Landscape (1920x1080):** Bottom 80-120px, centered
66- **Portrait (1080x1920):** Lower middle ~600-700px from bottom, centered
67- Never cover the subject's face
68- `position: absolute` — never relative
69- One caption group visible at a time

Positioning

  • Landscape (1920x1080): Bottom 80-120px, centered
  • Portrait (1080x1920): Lower middle ~600-700px from bottom, centered
  • Never cover the subject's face
  • position: absolute — never relative
  • One caption group visible at a time
Raw lines 63 to 70
71## Text Overflow Prevention
73Use `window.__hyperframes.fitTextFontSize()`:
75```js
76var result = window.__hyperframes.fitTextFontSize(group.text.toUpperCase(), {
77 fontFamily: "Outfit",
78 fontWeight: 900,
79 maxWidth: 1600,
80});
81el.style.fontSize = result.fontSize + "px";
82```
84Options: `maxWidth` (1600 landscape, 900 portrait), `baseFontSize` (78), `minFontSize` (42), `fontWeight`, `fontFamily`, `step` (2).
86CSS safety nets: `max-width` on container, `overflow: visible` (**not** `hidden` — hidden clips scaled emphasis words and glow effects), `position: absolute`, explicit `height`. When per-word styling uses `scale > 1.0`, compute `maxWidth = safeWidth / maxScale` to leave headroom.
88**Container pattern:** Full-width absolute container, centered. Do **not** use `left: 50%; transform: translateX(-50%)` — causes clipping at composition edges.

Text Overflow Prevention

Use window.__hyperframes.fitTextFontSize():

var result = window.__hyperframes.fitTextFontSize(group.text.toUpperCase(), {
  fontFamily: "Outfit",
  fontWeight: 900,
  maxWidth: 1600,
});
el.style.fontSize = result.fontSize + "px";

Options: maxWidth (1600 landscape, 900 portrait), baseFontSize (78), minFontSize (42), fontWeight, fontFamily, step (2).

CSS safety nets: max-width on container, overflow: visible (not hidden — hidden clips scaled emphasis words and glow effects), position: absolute, explicit height. When per-word styling uses scale > 1.0, compute maxWidth = safeWidth / maxScale to leave headroom.

Container pattern: Full-width absolute container, centered. Do not use left: 50%; transform: translateX(-50%) — causes clipping at composition edges.

Raw lines 71 to 89
90## Caption Exit Guarantee
92Every group **must** have a hard kill after exit animation:
94```js
95tl.to(groupEl, { opacity: 0, scale: 0.95, duration: 0.12, ease: "power2.in" }, group.end - 0.12);
96// `tl.set` is an instant flip, not a tween — safe to set `visibility` here (core's "no animating
97// visibility" rule applies to tweens, which can't smoothly interpolate non-numeric values anyway).
98tl.set(groupEl, { opacity: 0, visibility: "hidden" }, group.end);
99```
101Self-lint after building timeline — place **before** `window.__timelines[id] = tl` so it runs at composition init:
103```js
104GROUPS.forEach(function (group, gi) {
105 var el = document.getElementById("cg-" + gi);
106 if (!el) return;
107 tl.seek(group.end + 0.01);
108 var computed = window.getComputedStyle(el);
109 if (computed.opacity !== "0" && computed.visibility !== "hidden") {
110 console.warn(
111 "[caption-lint] group " + gi + " still visible at t=" + (group.end + 0.01).toFixed(2) + "s",
112 );
113 }
114});
115tl.seek(0);
116```

Caption Exit Guarantee

Every group must have a hard kill after exit animation:

tl.to(groupEl, { opacity: 0, scale: 0.95, duration: 0.12, ease: "power2.in" }, group.end - 0.12);
// `tl.set` is an instant flip, not a tween — safe to set `visibility` here (core's "no animating
// visibility" rule applies to tweens, which can't smoothly interpolate non-numeric values anyway).
tl.set(groupEl, { opacity: 0, visibility: "hidden" }, group.end);

Self-lint after building timeline — place before window.__timelines[id] = tl so it runs at composition init:

GROUPS.forEach(function (group, gi) {
  var el = document.getElementById("cg-" + gi);
  if (!el) return;
  tl.seek(group.end + 0.01);
  var computed = window.getComputedStyle(el);
  if (computed.opacity !== "0" && computed.visibility !== "hidden") {
    console.warn(
      "[caption-lint] group " + gi + " still visible at t=" + (group.end + 0.01).toFixed(2) + "s",
    );
  }
});
tl.seek(0);
Raw lines 90 to 117
118## Pre-Built Caption Components
120Before building caption styles from scratch, check the registry — 15 ready-to-use caption components cover the most common styles. Install with `npx hyperframes add <name>` and wire as a sub-composition via `data-composition-src` (see `hyperframes-registry`).
122```bash
123npx hyperframes catalog --tag caption-style # list all caption components
124npx hyperframes add caption-highlight # install a specific one
125```
127| Style | Component | Best for |
128| ------------------------- | ---------------------------- | ---------------------------- |
129| TikTok-style highlight | `caption-highlight` | Social, high-energy |
130| Karaoke pill | `caption-pill-karaoke` | Music, lyric videos |
131| Cinematic editorial | `caption-editorial-emphasis` | Documentary, storytelling |
132| Glitch / cyber | `caption-glitch-rgb` | Tech, gaming |
133| Full-screen slam | `caption-kinetic-slam` | Hype, announcements |
134| Neon glow | `caption-neon-glow` | Night, club, neon aesthetics |
135| Neon accent (multi-color) | `caption-neon-accent` | Colorful, playful |
136| Wipe reveal | `caption-clip-wipe` | Clean, modern |
137| Gradient fill | `caption-gradient-fill` | Vibrant, eye-catching |
138| Matrix decode | `caption-matrix-decode` | Sci-fi, tech reveals |
139| Emoji pop | `caption-emoji-pop` | Social, casual |
140| Parallax layers | `caption-parallax-layers` | Depth, cinematic |
141| Particle burst | `caption-particle-burst` | Celebration, impact keywords |
142| Lava texture | `caption-texture` | Bold, dramatic |
143| Weight shift | `caption-weight-shift` | Elegant, typographic |
145Related: `caption-blend-difference` (tagged `text` / `blend-mode`, not `caption-style`, so it won't appear under the filter above) auto-inverts text against any background via `mix-blend-mode: difference` — useful when the background is busy or unpredictable.
147Browse all with previews: [hyperframes.heygen.com/catalog](https://hyperframes.heygen.com/catalog)
149Caption components ship with transparent backgrounds — they're pure overlays. If the underlying video is bright or busy, add a contrast layer (e.g. a semi-transparent dark div) in the host composition beneath the caption sub-composition, not inside the component itself.

Pre-Built Caption Components

Before building caption styles from scratch, check the registry — 15 ready-to-use caption components cover the most common styles. Install with npx hyperframes add <name> and wire as a sub-composition via data-composition-src (see hyperframes-registry).

npx hyperframes catalog --tag caption-style   # list all caption components
npx hyperframes add caption-highlight         # install a specific one
StyleComponentBest for
TikTok-style highlightcaption-highlightSocial, high-energy
Karaoke pillcaption-pill-karaokeMusic, lyric videos
Cinematic editorialcaption-editorial-emphasisDocumentary, storytelling
Glitch / cybercaption-glitch-rgbTech, gaming
Full-screen slamcaption-kinetic-slamHype, announcements
Neon glowcaption-neon-glowNight, club, neon aesthetics
Neon accent (multi-color)caption-neon-accentColorful, playful
Wipe revealcaption-clip-wipeClean, modern
Gradient fillcaption-gradient-fillVibrant, eye-catching
Matrix decodecaption-matrix-decodeSci-fi, tech reveals
Emoji popcaption-emoji-popSocial, casual
Parallax layerscaption-parallax-layersDepth, cinematic
Particle burstcaption-particle-burstCelebration, impact keywords
Lava texturecaption-textureBold, dramatic
Weight shiftcaption-weight-shiftElegant, typographic

Related: caption-blend-difference (tagged text / blend-mode, not caption-style, so it won't appear under the filter above) auto-inverts text against any background via mix-blend-mode: difference — useful when the background is busy or unpredictable.

Browse all with previews: hyperframes.heygen.com/catalog

Caption components ship with transparent backgrounds — they're pure overlays. If the underlying video is bright or busy, add a contrast layer (e.g. a semi-transparent dark div) in the host composition beneath the caption sub-composition, not inside the component itself.

Raw lines 118 to 150
Names a skill hyperframes-registry :120
151## Further References
153- [`motion.md`](motion.md) — karaoke, marker effects, audio-reactive modulation, scatter exits.
154- [`transcript-handling.md`](transcript-handling.md) — input formats, quality checks, cleaning, external API fallback.

Further References

  • [motion.md](motion.md) — karaoke, marker effects, audio-reactive modulation, scatter exits.
  • [transcript-handling.md](transcript-handling.md) — input formats, quality checks, cleaning, external API fallback.
Raw lines 151 to 155
156## Constraints
158- Deterministic. No `Math.random()`, no `Date.now()`.
159- Sync to transcript timestamps.
160- One group visible at a time.
161- Every group must have a hard `tl.set` kill at `group.end`.
162- Fonts: the compiler auto-embeds only its **built-in mapped set** (Inter, Roboto, Montserrat, …) — for those, just declare `font-family` in CSS. Any **other** font (a brand/custom font like `TT Norms Pro`, or a non-Latin CJK/Devanagari family) is **not** auto-supplied: it needs an `@font-face` pointing at a real `.woff2` shipped with the project, or the text silently falls back to a generic font in the render. Don't assume a `font-family` you can see locally will render — the render machine is a clean headless Chrome with no installed fonts.

Constraints

  • Deterministic. No Math.random(), no Date.now().
  • Sync to transcript timestamps.
  • One group visible at a time.
  • Every group must have a hard tl.set kill at group.end.
  • Fonts: the compiler auto-embeds only its built-in mapped set (Inter, Roboto, Montserrat, …) — for those, just declare font-family in CSS. Any other font (a brand/custom font like TT Norms Pro, or a non-Latin CJK/Devanagari family) is not auto-supplied: it needs an @font-face pointing at a real .woff2 shipped with the project, or the text silently falls back to a generic font in the render. Don't assume a font-family you can see locally will render — the render machine is a clean headless Chrome with no installed fonts.
Raw lines 156 to 162