Skip to content

feat(registry): add 15 caption style catalog components#921

Merged
miguel-heygen merged 7 commits into
mainfrom
feat/caption-catalog-components
May 17, 2026
Merged

feat(registry): add 15 caption style catalog components#921
miguel-heygen merged 7 commits into
mainfrom
feat/caption-catalog-components

Conversation

@miguel-heygen
Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen commented May 17, 2026

Summary

Add 15 caption overlay components to the registry catalog, covering karaoke, neon glow, typewriter, glitch, particles, texture masks, kinetic typography, gradient fills, clip-path reveals, and more.

Caption Styles

# Component Technique Preview
1 caption-pill-karaoke Pill container + per-word karaoke color highlight video
2 caption-neon-accent Multi-color neon glow accents with wiggle drift video
3 caption-weight-shift Font-weight transition between caption lines video
4 caption-emoji-pop Emoji integration + stroked text + horizontal squeeze video
5 caption-editorial-emphasis Dual-font (Inter + Playfair Display) emphasis system video
6 caption-parallax-layers 3D text layering with vertical stretch effect video
7 caption-glitch-rgb RGB chromatic aberration + CRT scanline overlay video
8 caption-typewriter Green terminal char-by-char with blinking cursor video
9 caption-matrix-decode Character scramble animation before reveal video
10 caption-particle-burst Keyword words trigger colored particle explosions video
11 caption-texture-lava Flowing lava texture mask over uppercase text video
12 caption-clip-wipe clip-path left-to-right wipe reveal per word video
13 caption-kinetic-slam Full-screen single-word with alternating entrances video
14 caption-gradient-fill Gradient-clipped text with elastic bounce video
15 caption-neon-glow Cyan/magenta neon glow with keyword accents video

Files

  • 63 new files across registry/components/caption-*/ and docs/catalog/components/
  • Each registry directory: <name>.html + demo.html + registry-item.json
  • caption-texture-lava/ includes lava.png texture asset (self-contained, listed in files[])
  • registry/registry.json updated with 15 new component entries
  • docs/public/catalog-index.json regenerated (71 total items)
  • 15 .mdx doc pages added under docs/catalog/components/
  • docs/docs.json updated with "Captions" as first group in Catalog tab
  • Canvas-based fitFontSize added to 14 components to prevent text overflow
  • generate-catalog-pages.ts updated with Captions group mapping

Test plan

  • All 15 compositions rendered to MP4 at high quality and visually verified
  • Videos uploaded to CDN (both registry and docs paths) and URLs verified working
  • bunx oxfmt --check passes on all files
  • Pre-commit hooks pass (lint + format + typecheck + commitlint)
  • Timeline IDs match data-composition-id on all 30 HTML files
  • caption-texture-lava installs self-contained with local lava.png
  • catalog-index.json includes all 15 new entries

miguel-heygen and others added 4 commits May 17, 2026 19:57
Add 15 caption overlay components to the registry, covering a wide
range of animation techniques:

Standalone origins (6):
- caption-pill-karaoke: pill container + karaoke word highlight
- caption-neon-accent: multi-color neon glow with wiggle drift
- caption-weight-shift: font-weight transition between lines
- caption-emoji-pop: emoji + stroked text + horizontal squeeze
- caption-editorial-emphasis: dual-font (Inter + Playfair) emphasis
- caption-parallax-layers: 3D text layering with vertical stretch

Gallery picks (9):
- caption-glitch-rgb: RGB chromatic aberration + CRT scanlines
- caption-typewriter: green terminal char-by-char with cursor
- caption-matrix-decode: scramble-reveal character animation
- caption-particle-burst: keyword particle explosions
- caption-texture-lava: flowing lava texture mask
- caption-clip-wipe: clip-path left-to-right wipe reveal
- caption-kinetic-slam: full-screen single-word slam
- caption-gradient-fill: gradient-clipped text + elastic bounce
- caption-neon-glow: cyan/magenta neon glow accents

Each component includes the reusable HTML, a demo composition,
and registry-item.json. All use a generic HyperFrames transcript.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add 15 .mdx doc pages under docs/catalog/components/ for all caption styles
- Add "Captions" group as first section in the Catalog tab navigation
- Add canvas-based fitFontSize to 14 caption components to prevent text overflow
- Fix parallax-layers vertical clipping by repositioning the behind safe zone
- Re-render all 15 preview videos at high quality and upload to CDN
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVE — pure additive registry catalog (63 files, +10534/-0), no existing-code changes. Determinism + structural patterns look clean. Two non-blocking flags below.

Audited

  • registry/registry.json — 15 new entries, names match component dirs, all typed hyperframes:component
  • registry/components/caption-{pill-karaoke,particle-burst,matrix-decode,typewriter,glitch-rgb,clip-wipe,texture-lava}/*.html — deterministic patterns (paused timeline, seeded mulberry32 for randomness, __timelines registration), composition IDs match registry names, font-fit canvas measurement is consistent ✓
  • registry-item.json shape for spot-checked entries (caption-pill-karaoke, caption-texture-lava) — schema + files targets look correct for the compositions/components/ install layout ✓
  • docs/catalog/components/caption-pill-karaoke.mdx — MDX renders preview video, install command, file table ✓

Trusting

  • The other 8 caption HTML files — assumed same boilerplate (font-fit + paused GSAP timeline + WORDS+GROUPS dispatch) given the visible pattern across 7 of 15
  • Preview MP4 URLs (15 separate CDN paths) — assumed uploaded and reachable per body claim
  • demo.html content matching the canonical .html per file (identical line counts)
  • lava.png binary content/licensing

Non-blocking flags

1. caption-texture-lava has an undeclared dependency on texture-mask-text.

The HTML references the mask via /assets/texture-mask-text/masks/lava.png — that's the neighboring component's asset path, not the local lava.png in caption-texture-lava/. Looking at registry-item.json:

"files": [
  { "path": "caption-texture-lava.html", "target": "compositions/components/caption-texture-lava.html", ... }
]

The lava.png shipped in this PR's caption-texture-lava/ directory isn't in the files array, so npx hyperframes add caption-texture-lava won't copy it. AND the .html doesn't reference its local sibling — it points at /assets/texture-mask-text/masks/lava.png, which only exists if the user has also installed texture-mask-text.

Result for the documented install flow: user runs npx hyperframes add caption-texture-lava, the mask URL 404s in their composition, and they get solid white text instead of the lava-textured caption.

Fix options (pick one):

  • Add texture-mask-text to registryDependencies (if the schema supports it — same shape blocks use) so add resolves it transitively
  • Self-contain: reference the local ./lava.png, list it in files with target: "compositions/assets/caption-texture-lava/lava.png" (or similar), and update the CSS mask-image URL to match

The included lava.png suggests the second was the intent but the wiring didn't land.

2. Format CI check is failing.

Body says bunx oxfmt --check passes on all files, but the latest CI run shows the Format job concluded failure. Stale claim, or oxfmt found something on a later commit. Run bunx oxfmt --write and re-push — that should clear the blocked mergeable state (CodeQL + player-perf + regression are all green; Format is the only failing required check I see).

Nit (totally optional)

caption-clip-wipe.html declares mulberry32(...) but never calls it — dead boilerplate copied from another caption. Two lines, easy to drop.

— Rames

- Fix caption-texture-lava mask URL to use local lava.png instead of
  /assets/texture-mask-text/masks/ absolute path that 404s on install
- Add lava.png to registry-item.json files array so it ships with
  npx hyperframes add caption-texture-lava
- Remove unused mulberry32 function from caption-clip-wipe
Copy link
Copy Markdown
Collaborator

@vanceingalls vanceingalls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scope: Audited the 15 registry/components/caption-*/registry-item.json, all 15 caption *.html files at the <head>/composition-host/__timelines registration sites, the caption-texture-lava HTML in full, registry/registry.json, docs/docs.json, the 15 new docs/catalog/components/caption-*.mdx, and cross-referenced against registry/components/{shimmer-sweep,texture-mask-text}/ for convention, packages/core/src/lint/rules/core.ts for the timeline-id rule, and scripts/generate-catalog-pages.ts for the catalog-generation contract. Trusted (not read end-to-end): the timeline-body JS inside each component — sampled the registration site only — and the embedded GSAP transcripts.

Strengths — calibrated:

  • Registry metadata is uniformly shaped across all 15 entries: every registry-item.json carries $schema, name, type, title, description, tags, files[], and a CDN preview URL with a ?v= cachebuster (caption-clip-wipe/registry-item.json:2-15 and 14 siblings). Easy mental diff, no schema drift.
  • registry/registry.json:67-127 adds the 15 entries in a single contiguous block with the right hyperframes:component type — no collisions against the existing names I audited (grain-overlay, shimmer-sweep, grid-pixelate-wipe, texture-mask-text).
  • Preview asset hygiene is clean: only approved hosts in the diff (static.heygen.ai, fonts.googleapis.com, fonts.gstatic.com, cdn.jsdelivr.net, hyperframes.heygen.com). No localhost / raw GitHub / dev S3 leaks.

blocker — timeline_id_mismatch on all 15 components (both *.html and demo.html — 30 files)

Every component pairs data-composition-id="caption-<slug>" with window.__timelines["<slug>"] — the caption- prefix is stripped on the timeline key. Examples:

  • caption-clip-wipe/caption-clip-wipe.html:783 has data-composition-id="caption-clip-wipe", line 944 has window.__timelines["clip-wipe"] = tl.
  • caption-typewriter/caption-typewriter.htmldata-composition-id="caption-typewriter" paired with window.__timelines["typewriter"].
  • Same shape across all 15 entries (×2 files each).

This trips the timeline_id_mismatch lint rule (packages/core/src/lint/rules/core.ts:115-140, severity error):

Timeline registered as "<key>" but no element has data-composition-id="<key>". The runtime cannot auto-nest this timeline.

Compare existing convention — registry/components/shimmer-sweep/demo.html:26 and :157: data-composition-id="shimmer-sweep-demo" and window.__timelines["shimmer-sweep-demo"] match exactly. Player runtime impact: packages/player/src/hyperframes-player.ts:1207 emits "Composition timeline not found after 8s" when the lookup fails.

Net effect: when a user runs npx hyperframes add caption-clip-wipe, then bun run lint, they get a hard error on the installed file; at render time the player may not bind the timeline. Fix: either rename __timelines["clip-wipe"]__timelines["caption-clip-wipe"] (and 14 siblings), or rename data-composition-id to match the key — but the slug-prefixed form is what convention demands.

blocker — caption-texture-lava has a broken installable contract

Three intersecting issues in this one component:

  1. registry/components/caption-texture-lava/lava.png is committed (~74 KB) but not listed in registry-item.json:files[] (caption-texture-lava/registry-item.json:11-15 — only the .html is declared). npx hyperframes add caption-texture-lava will not copy the PNG.
  2. caption-texture-lava/caption-texture-lava.html:9217-9218 (and demo.html:9445-9446) point at the absolute path /assets/texture-mask-text/masks/lava.png — i.e. the other component's installed asset. Comment at the top of the style block explicitly says /* Inlined texture-mask-text component styles */. So caption-texture-lava silently requires texture-mask-text to also be installed.
  3. The registry-item.json has no registryDependencies field declaring this. A user who runs npx hyperframes add caption-texture-lava without already having texture-mask-text installed will get a broken mask.

Per reference_hyperframes_asset_hosting.md (the convention I've seen the team follow on texture-mask-text and #650), installable component assets must (a) live under registry/components/<slug>/, (b) be listed in files[] with type: "hyperframes:asset" + an explicit target:, (c) be referenced from CSS by relative path (e.g. url("masks/lava.png")), to keep render deterministic and offline-safe.

Fix options: either (A) add lava.png to files[] with target: "assets/caption-texture-lava/lava.png" and rewrite the two url() references to that path; or (B) drop the committed lava.png (it's dead bytes today) and declare "registryDependencies": ["texture-mask-text"]. (A) is the lower-coupling option. Whatever the choice, the current state — bytes committed but not wired in, plus an undeclared cross-component dep — is the worst of both.

important — docs/public/catalog-index.json not regenerated

scripts/generate-catalog-pages.ts:501-506 writes three outputs: the per-component MDX pages, docs/docs.json navigation, and docs/public/catalog-index.json (the flat grid manifest). The PR ships the first two for all 15 components but docs/public/catalog-index.json is untouched — grep '"caption-' docs/public/catalog-index.json returns 0 matches on main and remains unchanged in the diff. Existing components like shimmer-sweep / grain-overlay are in there; these 15 won't be discoverable on the catalog grid page until the index is regenerated. This is the same "missing catalog index" footgun the texture-mask-text PR hit on round 1.

Fix: run bunx tsx scripts/generate-catalog-pages.ts and commit the updated docs/public/catalog-index.json alongside the existing MDX changes.

important — PR body has two verifiable claims that don't hold

  • "47 new files" — actual file count is 63 (47 registry + 15 MDX + docs/docs.json + docs/registry.json). Underclaims the doc-surface impact and would have made the missing catalog-index.json regen step easier to spot at PR-prep time.
  • "bunx oxfmt --check passes" / "Pre-commit hooks pass" — both Preflight (lint + format) and Format failed on the first push (26 files un-formatted, all 13 caption components' HTML+demo.html except pill-karaoke and texture-lava). Subsequently fixed by commit ed5747a ("style: format caption component HTML files") — which is the right resolution, but the test-plan box was checked before that fix. Audit-table drift is itself a finding even when subsequently true.

nit

  • All 15 registry-item.json files use one preview URL with ?v=1779051416 except caption-parallax-layers/registry-item.json:14 which is ?v=1779051692. Probably a re-upload — fine functionally, just flagging the inconsistency in case a single cachebuster was intended.

Verdict: REQUEST CHANGES
Reasoning: Two correctness blockers: every one of the 15 components ships with a timeline_id_mismatch against the team's lint rule (will fire on user's installed code and risks player runtime failure), and caption-texture-lava is structurally broken in install (PNG committed but not in files[], HTML points at another component's asset path, no registryDependencies declared). The catalog-index.json regen miss is the third must-fix for these components to actually appear on the catalog grid.

Review by Vai

Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retracting my APPROVE — aligning with @vai's REQUEST_CHANGES.

Vai caught two real blockers I missed:

  1. Timeline ID mismatch across all 15 components. Every entry pairs data-composition-id="caption-<slug>" with window.__timelines["<slug>"] — the caption- prefix is stripped on the timeline key. I literally read those exact lines on 7 of 15 caption HTMLs and noted "composition IDs match registry names" — but I only checked the composition ID against the registry entry name, not against the timeline key within the same file. Trips packages/core/src/lint/rules/core.ts:115-140 (severity error) and will hit Composition timeline not found after 8s at runtime in the player. Existing registry components (shimmer-sweep and others) have matching keys.

  2. caption-texture-lava should be a blocker, not a non-blocking flag. I called it out but called it non-blocking. Vai's right — when the documented install path (npx hyperframes add caption-texture-lava) ships a snippet that's broken three different ways (orphan lava.png not in files[], HTML pointing at another component's installed surface, no registryDependencies), that's a blocker on the catalog's user-facing contract. Calibration miss on my end.

Also missing from my review:

  • docs/public/catalog-index.json not regenerated — same footgun as hf#650 (the file is auto-generated by scripts/generate-catalog-pages.ts and must be committed). I should have grep'd for it given that prior incident is in my reviewer notes.
  • PR-body drift on file count — body says "47 new files" but actual is 63.

@miguel-heygen — please address Vai's REQUEST_CHANGES before re-review. I'll defer to Vai as primary reviewer on the re-review since they caught the structural pattern I missed.

— Rames

- Fix timeline_id_mismatch on all 15 caption components: __timelines key
  now matches data-composition-id (e.g. "caption-clip-wipe" not "clip-wipe")
- Regenerate docs/public/catalog-index.json with 15 new caption entries
- Add "Captions" group mapping to generate-catalog-pages.ts (priority 0)
- Regenerate docs.json nav and mdx pages via the catalog script
- Upload docs preview videos to docs/images CDN path
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVE on re-review. All three blockers from the prior round are fixed; CI is green on what was previously failing.

Verified

Blocker 1 — timeline ID mismatch fix (uniform across all 15): spot-checked 7 of 15 caption components — data-composition-id now matches window.__timelines[key] in all of them:

  • caption-pill-karaokecaption-pill-karaoke
  • caption-clip-wipecaption-clip-wipe
  • caption-texture-lavacaption-texture-lava
  • caption-particle-burstcaption-particle-burst
  • caption-matrix-decodecaption-matrix-decode
  • caption-typewritercaption-typewriter
  • caption-glitch-rgbcaption-glitch-rgb (verified in earlier round, still consistent) ✓

Trusting the remaining 8 follow the same shape — fix in commit ae193d9 is a uniform sed-style update.

Blocker 2 — caption-texture-lava self-contained:

  • registry-item.json files[] now includes lava.png with target: "compositions/components/lava.png"
  • HTML's mask URL is now url("lava.png") (local sibling), no longer pointing at the neighboring texture-mask-text component's asset surface ✓
  • npx hyperframes add caption-texture-lava will now copy both files into a self-contained install location ✓

Important — docs/public/catalog-index.json regenerated:

  • All 15 caption entries present in the index ✓
  • Each has the right name / type: "component" / title / description / tags / href / preview shape ✓

Important — PR body file count fixed: body now says "63 new files" matching the actual file count ✓

Nit — mulberry32 dead code removed from caption-clip-wipe: verified absent ✓

CI status

The previously-failing Format check is now green. All other required checks have passed:

  • Format ✓ / Lint ✓ / Build ✓ / Test ✓ / Typecheck ✓
  • CodeQL ✓ / player-perf ✓ / regression ✓ / preview-regression ✓
  • Preflight (lint + format) ✓

(Render catalog previews + Windows tests + CLI smoke are still in_progress on the latest commit — not regressions; they need time to complete.)

@vanceingalls — Vai's REQUEST_CHANGES from the prior round is still on file. Once they review the fixes and dismiss/approve, the blocked state should clear.

— Rames

Copy link
Copy Markdown
Collaborator

@vanceingalls vanceingalls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review at ae193d99 (one fixup commit). All four prior findings re-verified against the working tree.

Posting as COMMENT because --approve was gated by the local stamp-harness — verdict is effectively APPROVE; main Vai session has the context to clear the gate if a formal approval is desired.

blocker #1 (timeline_id_mismatch) — ADDRESSED

Grepped all 30 HTML files (15 components × <slug>.html + demo.html); every file now pairs data-composition-id="caption-<slug>" with window.__timelines["caption-<slug>"] exactly. Sample: caption-clip-wipe/caption-clip-wipe.html and demo.html both dcid=caption-clip-wipe / tl=caption-clip-wipe. packages/core/src/lint/rules/core.ts:115-140 is unchanged, so the rule still gates this — and the rule is now satisfied uniformly. No partial fix.

blocker #2 (caption-texture-lava broken install) — ADDRESSED

registry/components/caption-texture-lava/registry-item.json:14-18 now lists lava.png with "type": "hyperframes:asset" and target: "compositions/components/lava.png". Both caption-texture-lava.html:24-25 and demo.html:24-25 reference url("lava.png") (relative path, own component) — the /assets/texture-mask-text/masks/lava.png cross-component pointer is gone. HTML shrunk from 9217 → 7219 bytes (the previously-inlined texture-mask-text stylesheet was dropped in favor of a minimal local .hf-texture-text / .hf-texture-lava ruleset). No registryDependencies needed because the component is genuinely self-contained now. Nit: the /* Inlined texture-mask-text component styles */ comment at line 12 is now mildly misleading (the styles are local, not inlined from elsewhere) — happy follow-up.

important #1 (catalog-index.json) — ADDRESSED

docs/public/catalog-index.json regenerated with all 15 caption entries (grep -c '"name": "caption-' → 15; total 71 items, matching the PR body). scripts/generate-catalog-pages.ts:+3/-2 now has the Captions group mapping (priority 0). The 15 *.mdx pages were re-stamped (each +1/-1) by the regen run, confirming the script was actually executed end-to-end.

important #2 (PR body audit-table drift) — MOSTLY ADDRESSED

Body now says "63 new files"; API compare reports 61 added + 4 modified = 65 touched (registry/registry.json, docs/docs.json, docs/public/catalog-index.json, scripts/generate-catalog-pages.ts are the modified four). Close enough; the materially load-bearing claims (bunx oxfmt --check passes, Pre-commit hooks pass, timeline IDs match, catalog-index regenerated, self-contained caption-texture-lava) all now match the diff. CI is green across all required checks (Preflight, Format, Lint, Test, Typecheck, CLI smoke, runtime contract).


Verdict: APPROVE (posted as COMMENT due to stamp-harness gating)
Reasoning: Both blockers addressed across the full surface (30/30 timeline-id files OK; caption-texture-lava install contract is clean), catalog index regenerated, all required CI green. The remaining quibbles (file-count off by 2, stale comment in lava HTML) are nits, not merge blockers. Prior REQUEST_CHANGES from vanceingalls (review 4306309716) is now stale; once dismissed the BLOCKED mergeable state should clear.

Review by Vai (re-review)

Copy link
Copy Markdown
Collaborator

@vanceingalls vanceingalls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review at HEAD ae193d99. Flipping CHANGES_REQUESTED → APPROVE — both blockers + both importants addressed.

Status of prior findings

  • Blocker #1 (timeline_id_mismatch across all 15) — ADDRESSED. Grepped all 30 HTML files (15 components × .html + demo.html); every file now has data-composition-id="caption-<slug>" matching window.__timelines["caption-<slug>"]. Uniform fix, no partials. Lint rule at packages/core/src/lint/rules/core.ts:115-140 is satisfied everywhere.
  • Blocker #2 (caption-texture-lava broken) — ADDRESSED. registry-item.json:14-18 lists lava.png as hyperframes:asset; HTML + demo at :24-25 use url("lava.png") (relative, self-contained). Cross-component /assets/texture-mask-text/masks/lava.png pointer is gone. HTML shrunk 9217 → 7219 bytes — substantive cleanup, not a minimal patch.
  • Important #1 (catalog-index.json not regenerated) — ADDRESSED. grep -c '"name": "caption-' docs/public/catalog-index.json → 15; total items 71 matches the body.
  • Important #2 (PR body audit-table drift) — MOSTLY ADDRESSED. Body now claims 63 files; API compare shows 61 added + 4 modified = 65 touched. Minor drift, not material.

CI required checks all green.

Verdict: APPROVE.

Review by Vai (re-review)

@miguel-heygen miguel-heygen merged commit f44d53e into main May 17, 2026
49 of 51 checks passed
@miguel-heygen miguel-heygen deleted the feat/caption-catalog-components branch May 17, 2026 21:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants