
Embedded Captions
- 137k installs
- 39.5k repo stars
- Updated August 5, 2026
- heygen-com/hyperframes
Embedded-captions is an agent skill that generates and embeds captions into existing video content.
About
Adds captions and subtitles to existing talking-head videos without affecting the original footage. Supports multiple embed styles: verbatim rail, embedded climax, or cinematic embed.
- Add captions to existing talking-head videos without re-recording
- Multiple caption styles: verbatim rail, embedded climax, cinematic embed
- Preserves original footage while adding professional captions
Embedded Captions by the numbers
- 137,389 all-time installs (skills.sh)
- +13,670 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #54 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
embedded-captions capabilities & compatibility
- Capabilities
- video generation · caption generation
- Use cases
- video generation
- IDEs
- vscode · cursor ide · jetbrains · zed
- Pricing
- Free
npx skills add https://github.com/heygen-com/hyperframes --skill embedded-captionsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 137k |
|---|---|
| repo stars | ★ 39.5k |
| Security audit | 1 / 3 scanners passed |
| Last updated | August 5, 2026 |
| Repository | heygen-com/hyperframes ↗ |
How do you add styled captions inside talking-head videos?
Adds captions and subtitles to existing talking-head videos without affecting the original footage. Supports multiple embed styles: verbatim rail, embedded climax, or cinematic embed.
Who is it for?
Adding captions to interviews, podcasts, presentations, and talking-head videos
Skip if: Creating videos from scratch, website URL captures, product launch promos, or faceless article explainers without presenter footage.
When should I use this skill?
Adding professional captions or subtitles to an existing video
What you get
Identity-selected caption compositions with in-scene matte blending and a rendered talking-head video with embedded captions.
- Identity-styled caption compositions
- Rendered video with in-scene captions
By the numbers
- Provides a catalog of 32 visual caption identities
- Uses two caption engines: column-flow compositing and themed constitutions
Files
Embedded Captions
One catalog, picked up front (CATALOG.md — 17 identities; the three engines behind it are backend detail). Standard (default) builds a clean verbatim rail (lower-third subtitle carrying most text) + an embed climax composited _into_ the scene behind the subject at the peak. Cinematic is pure embed — no rail, every caption composited behind the subject (hero typography, accumulation, occlusion as the effect). Theme is a complete themed constitution — body paradigm × hero setpiece × front fx × plate reaction, composed from registries (themes/README.md): ordnance terminal neonsign stardust stomp. Most explainer / voiceover is Standard; embed is the scarce, earned peak — embedding every word is the common mistake; Theme is for VFX-grade asks ("炸", "特效", "像 AE 做的").
---
Operational flow (TL;DR)
The craft prose below is long; the pipeline itself is short — and everything deterministic is computed or compiled, never hand-written:
1. Decision gate (refuse bad clips) → pick ONE identity from [CATALOG.md](CATALOG.md) (17 identities; engine/compiler derived by lookup — never surface a mode/category question) 2. hyperframes init (skip it if the project dir already exists with the video inside — matte.cjs/transcribe.cjs adopt any video in the dir as source.mp4) → `bash scripts/prepare.sh <project>` (matte ∥ transcribe ∥ audio-envelope in parallel, then safe-zones v2 with scene palette/optics/lighting — one command, nothing forgotten) 3. author a small JSON of creative choices (read safe-zones.json first): Cinematic → plan.json → fill-timings.cjs → fit-fonts.cjs → make-composition.cjs; Theme → theme.json → make-theme.cjs (rail/panel/poem/takeover paradigms; anchor is the quiet rail default) 4. Visual QA: node scripts/preview-frames.cjs <project> → faithful composite previews in ~2s/frame (no render). Check § Visual QA before paying for a render. 5. render-and-composite.sh → gates (timing / occlusion+hero / overflow / hand-off) → final.mp4
Load-bearing rules people miss:
- rail (default) + embed (promotion).
drop(filler, not shown) /rail(verbatim lower-third subtitle, in front, carries most text) /embed(a peak word composited behind the subject). Standard mode does both, embedding only the peak(s). See § Caption model. - The video is delivered UNTOUCHED (Standard/Cinematic; Theme mode's PLATE budget is the one sanctioned exception — register-gated reaction beats (charge-dim, punch, shake, grain) defined per theme DNA and applied AFTER the matte composite so subject+text+plate move as one frame) — captions are the only thing added; the matte just lets the subject occlude the embed track. Never grade/recolor/scanline the footage.
- Two rulebooks: rail → [references/rail.md](references/rail.md) (thin), embed craft → [references/composition-craft.md](references/composition-craft.md) (rich, embed-only). Skim by need.
---
Caption model — rail + embed
Every spoken phrase is one of three things:
| What | How it's shown | |
|---|---|---|
| drop | filler — um/uh, stutters, self-corrections | not shown |
| rail | the default — ordinary spoken content (verbatim) | clean lower-third subtitle, in front, readable. A punch word can get an inline emphasis highlight (accent colour / active-word pop) — it stays on the rail. |
| embed | a promoted peak — the headline beat | one big word composited behind the subject (matte occlusion), designed entrance + exit |
The rail carries most of the text; embed is the scarce, earned peak. Scarcity is per beat/block, not per clip: ≤1 hero per block (thought), never two co-visible, ≥ a beat of air between hero windows (the compiler warns under 0.6s). A short clip → usually 1–2; a long explainer → ~one per section. Among multiple heroes, the largest authored one is the APEX (it alone gets the full lockup embed + width-fit raise); smaller ones are MINOR peaks that ride their column as oversized emphasis lines (fg, damped motion) — not every beat needs the matte showcase, which is exactly what keeps the apex an event. Embedding every word is still the common mistake.
Rail-surface identities build exactly this (rail = rail.html, embed = the climax in index.html). Column-flow identities drop the rail and make everything embed-style — recommend them only for mood-over-verbatim asks, never for explainer / voiceover where the words must read (CATALOG.md encodes this per identity).
---
Step 0 — pick ONE identity from the CATALOG
One front-end, three engines behind. The user picks an IDENTITY from CATALOG.md (17 entries: 12 classic + 5 themed); the engine, compiler and authoring file are derived by lookup from the catalog row. Never surface "Standard vs Cinematic vs Theme" as a question — those are backend names (a product has one UX even with several engines). The catalog encodes everything routing needs: reading surface, voice, recommend-for, scene needs, adjacency notes for the genuinely-close pairs (loud↔ordnance, neon↔neonsign, cream↔stardust).
Procedure: probe the clip → shortlist 2–3 identities from the catalog → recommend ONE with a one-line why → the user picks → author that identity's file. Identities are engine-locked (no cross combos; opening one is a validation event — see dna/README.md).
Always present your recommendation and let the user pick before you author. Don't silently default.
(The full identity table lives in CATALOG.md — single source of truth for routing. The engine docs below describe each backend's authoring contract.)
Recommendation heuristic: use the "Shortlisting heuristics" in CATALOG.md — they are identity-level (e.g. "炸" shortlists ordnance/stomp/terminal/loud and picks by WHAT should explode), never category-level. Unsure → anchor.
- Cinematic → write
plan.jsonfor a locked template, compiled bymake-composition.cjs. - Theme → read themes/README.md, author
theme.json, runscripts/render-theme.sh(compiles + renders + plate reaction → final_fx.mp4).
---
Decision gate — RUN FIRST
Probe the video and classify the scene before either mode.
ffprobe <video.mp4> # specs
ffmpeg -ss <t> -i <video.mp4> -vframes 1 sample.png # at 20/50/80%Read the samples. Refuse if:
- Multiple speakers / hard cuts (split & render each shot, or refuse)
- No human subject (this skill is for talking-head)
- Under 3 seconds, no speech, or face never clearly visible —
transcribe.cjswarns when audio is near-silent (Whisper hallucinates words like "Thank you." over silence); heed it and refuse rather than caption fabricated words - Source already has burned-in captions / subtitles / heavy text graphics — adding a second caption system conflicts and the footage ships untouched (no covering/inpainting). Burned text often appears only mid-clip: sample a 1fps contact sheet (
ffmpeg -i in.mp4 -vf "fps=1,scale=160:-1,tile=10x5" sheet.png), don't trust 3 spot frames. - Transcript is garbage — non-native/heavy-accent speech can transcribe into confident gibberish. Sanity-read
transcript.jsonbefore authoring; if it doesn't parse as language, tryWHISPER_MODEL=mediumonce, else refuse (a verbatim rail of fabricated words is worse than no captions). - Busy handheld with fast motion (matte flickers)
Pre-flight probes (cost nothing, prevent the worst failures)
1. Shot-cut probe. Sample frames at 20%, 50%, 80%. If a different subject/scene appears, trim the clip before the cut. 2. Letterbox / pillarbox probe. Black bars on the first frame? Compute safe content rect and constrain caption placement inside it. 3. Luminance probe. Sample the caption region's average luminance — under 60 → light text reads as-is, 60-180 → add the glyph scrim, 180+ → opaque text + scrim (never bare light text). Cinematic templates are cream+`screen` and LOCKED — use this probe to _pick a fitting identity_ (bright scenes → ink, or the opaque-rail anchor theme), never to recolour one. 4. Identity recommendation by tone (you recommend; the user picks — see Step 0 + CATALOG.md). explainer / interview / must-read words → rail/panel-surface identities; poetic / social / "cinematic" → column-flow identities by register; "炸 / 特效 / VFX" / named worlds → themed identities. When unsure → anchor (words read, scene safe) — but present a shortlist and let the user choose.
---
Pipeline — 5 steps
1. hyperframes init <project> --non-interactive --video <video.mp4> --skip-skills
2. bash scripts/prepare.sh <project> # matte ∥ transcribe (parallel) → safe-zones. One command.
# → frames_fg/ transcript.json safe-zones.json
3. [AGENT STEP — the only creative step] author a small JSON; see below by mode
Cinematic: author plan.json → node scripts/fill-timings.cjs → fit-fonts.cjs → make-composition.cjs
Theme: author theme.json → bash scripts/render-theme.sh <project> (compiles + renders + plate fx)
4. node scripts/preview-frames.cjs <project> # ~2s/frame composite previews → § Visual QA (BEFORE the render)
5. bash scripts/render-and-composite.sh <project> # gates → final.mp4 + history/ snapshot
(Theme mode: SKIP steps 3b/5 — render-theme.sh already runs compile + render-and-composite
+ _postfx.sh; the deliverable is final_fx.mp4, final.mp4 is pre-plate-reaction)Step 3 differs by mode:
Step 3 — Cinematic mode (pure embed)
1. Read `safe-zones.json` first. Narration planes go in `zones.hugLeft`/`hugRight` — clean strips ABUTTING the silhouette (text far from the body reads as floating, not embedded; far corners are the fallback, not the default). The hero defaults to heroAnchor/heroBands.best (centered ON the subject, ~30–55% occluded). recommendation:"fg" moves NARRATION in front for legibility; the hero stays embedded whenever `heroBands.feasible` — hero-fg is the last resort. 2. The DNA is the identity you picked in Step 0 (CATALOG.md) — do not re-open the choice here. Sanity-check it against the scene (bright hero band luma > 150 wants ink; full pick guidance lives in the catalog, covering all ten incl. neon / glitch / chrome / velocity). State your pick + why; the user decides. The DNA locks type/palette/blend/motion + hero three-act; safe-zones v2 (palette/optics/lighting) parameterizes it to THIS scene automatically. 3. Author `<project>/cinematic.json` — "dna": "<name>" + thought-BLOCKS, not raw groups: each block = lines of words (grouped 2–5 at clause boundaries) + the plane it stacks in + per-line css (size/weight/style only — no positions) + at most ONE line marked "hero": true (the promoted word; "text" for display form). Schema: scripts/make-cinematic.cjs header. 4. Compile: node scripts/make-cinematic.cjs <project> — lowers blocks → plan.json → index.html. Generated for you: transcript-sequenced timings, accumulate-within-block, page-flip-between-blocks, the hero LOCKUP (a hero block's pre-context, HERO and post-context stack as ONE bonded composition centered on the subject — reading order top→bottom = spoken order by construction; context floats in FRONT while the hero embeds BEHIND = the depth sandwich; a mass rule keeps the hero dominating its context), apex/minor hero split, reading order by construction, fg fallback per safe-zones. Then the gates run as usual. _(Hand-authoring plan.json directly remains possible for designs blocks can't express — then run fill-timings.cjs + fit-fonts.cjs + make-composition.cjs yourself.)_
Step 3 — Theme mode (themed constitution)
Read [themes/README.md](themes/README.md) FIRST — paradigm/setpiece registries, linkages, hard rules, and the exact theme.json schema.
1. Pick a theme DNA by content register (each themes/<name>.json has voice + when). State your pick + why; the user decides. 2. Author `<project>/theme.json` — dna, lines (verbatim, transcript order; 1–5 words each — for takeover each line is one CARD), minors (emphasis words), hero:{match} (the climax word/phrase; leave it OUT of lines for embed setpieces, keep it IN for inline setpieces and panel+redact). 3. Render: bash scripts/render-theme.sh <project> — compiles (verbatim-completeness gate at compile time), renders both layers, composites, applies the plate reaction → final_fx.mp4. Use preview-frames.cjs between compile and render for Visual QA.
---
Visual QA — preview BEFORE you render
node scripts/preview-frames.cjs <project> [t…] composites faithful preview frames in ~2s each (caption layers screenshotted at seek-time + real video frame + matte occlusion + rail overlay = what the final composite will look like at that moment). Default samples = each group/climax window. A full render costs minutes — never use it to _discover_ layout problems.
Check the previews (<project>/preview/sheet.png) against this list — these are the failures the geometric gates cannot catch:
1. Washout — light text over a bright region (window/sign/sky): unreadable → move the plane or change DNA/mode (bright scene → ink). 2. Text-on-text — captions over the scene's own text/graphics, or two caption groups colliding. 3. Reading order — on-screen vertical order must match spoken order; the hero must not sit below later words. 4. Hero presence — the climax should be BIG and visibly behind the subject (~30–55% occluded), not a floating label in a margin. 5. Balance — one coherent column/band, not scattered fragments; margins breathing; nothing clipped.
Then the 5 positive checks in references/reference-bar.md (poster test · timid test · one-glance hierarchy · scene handshake · dead-air audit) — the failure list keeps a render from being broken; the positive list is what makes it _designed_. Ship when both pass.
Fresh-eyes review (recommended for anything user-facing): you have confirmation bias about your own layout. If you can spawn a subagent, give it ONLY the preview sheet + this checklist and ask for PASS/FIX verdicts per frame ("review these caption previews against the 5-point checklist; answer PASS or the specific fix per frame"). Apply fixes in plan.json / theme.json, recompile, re-preview — each loop costs seconds. Render once, when the previews pass.
---
The DNA registry — ten visual languages (replaces the template catalog)
Both modes draw from [dna/](dna/README.md) — six art-directed visual languages that parameterize per scene (accent sampled from the footage, contact shadow along the measured light direction, depth-match blur, RMS-coupled hero amplitude):
| DNA | Register | Scene fit | Voice |
|---|---|---|---|
| cream | premium-warm | dark/mid warm scenes | Inter + warm cream + screen; glowing emergence hero (successor of cinematic-cream) |
| ink | premium | bright scenes (luma > 150) | near-black multiply — type printed ON the wall; the bright-scene answer |
| editorial | editorial-luxe | introspective / fashion / poetic | Bodoni Moda, lowercase-italic hero — magazine elegance |
| keynote | tech-premium | product / launch | opaque white Inter 800, dead-center stillness |
| documentary | formal | interview / serious | burn-in reveals, no hero — gravitas IS the style |
| loud | loud | hype / sport / social | Anton + scene-sampled accent, single-unit slam + ripple; body ANNOUNCES in front (bodyLayer: fg) |
| neon | loud-cyber | cyberpunk / nightlife / tech-noir (dark scenes) | electric-cyan signage, ignition flicker, the hero powers ON like a sign |
| glitch | loud-cyber | digital / hacker / AI | RGB-split echoes snap together on landing; machine-percussive timing |
| chrome | loud-luxe | Y2K / fashion-tech / music | liquid-metal gradient hero + one sheen sweep during the hold |
| velocity | loud-sport | sport / auto / fitness | every word arrives along its motion vector (streak+skew), hero passes with speed trails |
Pick by safe-zones.json (heroAnchor.bandLuma, palette.temperature) × content register — dna/README.md has the decision rule. Authoring: cinematic.json takes "dna": "<name>".
The engine generates the hero three-act from the DNA (no authoring needed): co-visible captions dim (setup) → per-letter entrance with amplitude ∝ spoken loudness (impact) → breathe + glow until exit (afterglow).
(Legacy: plan.template:"cinematic-cream" maps to dna:"cream" automatically. The retired 54-template library lives outside the skill at ~/Downloads/embedded-captions-archive/standard-templates-54/; _motion.md remains in-skill as the motion-verb reference catalog.)
---
Aesthetic decision — tone × shot × platform (input to the catalog shortlist, NOT a second router)
Classify the clip on 3 axes and feed the result into CATALOG.md's shortlisting — this section never picks a mode/engine by itself:
Tone (what feel does the content have?)
- documentary | conversational | energetic | poetic | keynote | investigative | music-video
Shot (what's the framing?)
- close-up (head + shoulders) | mid-shot (torso+) | wide (full body+) | cut-montage (mixed shots)
Platform (where will it play?)
- 9:16 portrait (TikTok/IG/Shorts) | 16:9 landscape (YouTube/web) | 1:1 square | broadcast export
Cross-reference in references/direction-catalog.md § Classification matrix for direction language — then return to CATALOG.md to shortlist identities (this matrix informs the shortlist; the catalog is the only routing surface).
Composition craft (embed track) — read before embedding
The full embed-track playbook lives in [references/composition-craft.md](references/composition-craft.md): transcript role-annotation, phrase grouping, planes & clean-zone anchoring, zone coherence, climax pop & readability, edge-breathing, the occlusion 3-step judgement, and accumulation/persistence. It governs how a _promoted_ phrase sits INTO the scene — read it before authoring any embed (Cinematic plan.json or Standard index.html). The default rail track has its own, much simpler spec → [references/rail.md](references/rail.md).
---
Shared knowledge
| Doc | What |
|---|---|
| references/rail.md | The rail track — standard lower-third subtitle spec (the default; carries most text). |
| references/composition-craft.md | The embed-track playbook — grouping, planes, climax pop, occlusion judgement, accumulation/persistence. Read before embedding. |
| dna/README.md | The DNA registry — six scene-parameterized visual languages; how to pick. |
| references/reference-bar.md | The taste bar — per-register world-class references + the 5 positive checks. |
| references/aesthetic-principles.md | The 18 rules. Beat Veed AI on taste. Read first. |
| references/motion-vocabulary.md | 10 named motion primitives + tone→timing lookup |
| references/direction-catalog.md | 10 ship-ready aesthetics + tone×shot×platform matrix |
| references/anti-patterns.md | Bugs already locked out (CoreML, letter-spacing reflow, etc.) |
| references/scene-types.md | When a wall surface is usable (4 conditions) |
| references/layout-heuristics.md | Plane positioning, clean-zone selection, crown 3 conditions, pillarbox math |
| references/typography-presets.md | Font-size × column-width matrix (starting points) |
| references/caption-grouping.md | Word → group rules (pauses, sentence boundaries) |
| references/failure-modes.md | Long tail of dev gotchas |
| references/bespoke-vs-presets.md | Why presets fail sometimes; clone-and-tweak pattern |
Read the aesthetic principles and direction catalog FIRST. Everything else is implementation detail.
---
Non-negotiables
- Face must never be 100%-covered continuously — every 0.3s window, face bbox ≥30% uncovered.
- WCAG contrast — final render lints; fix palette if it fails.
- Deterministic — no
Math.random(), noDate.now(), norepeat:-1. - Never grade/recolor the video. The footage ships untouched — captions are the only addition. No full-frame scanlines / duotone / darken / vignette over the a-roll. Cyberpunk/CRT texture belongs _inside_ a caption element, not over the whole frame.
- Rail-first for talking-head / explainer. Don't embed the whole transcript — most text is the rail; embed only peaks. Embedding everything is the default mistake.
- Embed is scarce + spaced. ≤1 embed per sentence/beat, never two adjacent or co-visible, ≥ a beat apart, at most one
apex. climax = per-beat peak, not "the single payoff of the entire clip." - Matte = the PERSON (hyperframes `remove-background`, u2net_human_seg, Apache-2.0). Human segmentation by intent, but not surgically: thin offset furniture (mic boom arms) is usually excluded — captions render over it, behind the person — while large salient objects NEAR the subject (a telescope, a desk rig) can still leak into the matte and occlude captions. Objects HELD by the subject (products, phones) may drop out intermittently, letting captions pass in front. NEVER assume: sample
frames_fg/at 2-3 timestamps before placing the hero, and prefer hero positions clear of any leaked furniture (heroAnchorcan be skewed by leaks — cross-check against frames_bg). - safe-zones is PROP-BLIND — eyeball every band you use. Zones/heroBands score _subject_ occlusion + luma only: a mic, telescope, or screen sitting inside a "clean" zone is invisible to them (and a prop leaking INTO the matte skews
heroAnchor.centerXPctoff the person). Before authoring, extract ONE frame of each band you intend to use; if a prop lives there, measure its bbox and move/shrink the plane. Two real cases shipped clean only because the agent did exactly this. (Auto prop-saliency is a known gap; zones'peakLumaonly catches _moving_ bright objects.) - Captions stay on-frame. Cinematic mode hard-gates frame-overflow; Standard mode runs
check-overflow.cjsas a WARNING (intentional bleed is the only exception — read the warning). - Each caption ≥ 0.5s on screen — shorter = unreadable.
- Word timings must match transcript.json within 80ms — a caption firing 500ms off-beat destroys the scene illusion. Cinematic runs
check-timing.cjs --strictbefore rendering (via render-and-composite.sh); THEME mode enforces the same timings at compile time instead (make-theme's sequential transcript matcher + verbatim completeness gate — drift is a compile error). Never pack multiple transcript words into one entry (e.g."FUTURE OF"or anIT+ line-break +ALLstack with one start/end) — the second word inherits the first's timestamp and fires early. Split them into separate word entries with their own timings, even if you want them on the same visual line (use CSSwhite-space/ natural wrap instead of<br>). Creative substitutions where caption text ≠ transcript (e.g."15%"replacing"fifteen percent") are supported — register them inCREATIVE_SUBSinsidecheck-timing.cjs. - Group windows must envelop their words —
group.in ≤ min(word.start)andgroup.out ≥ max(word.end)for every group. Ifgroup.inis later than a word's start, the word is silently delayed until the container mounts (we've shipped 800ms lag bugs from this). The validator enforces this. - No two caption groups may overlap in both time AND screen region — overlapping-in-time captions create text-on-text pileups. Options: (a) spatial separation — place each group in a non-overlapping vertical band so they can coexist (memory-wall cascade style); (b) handoff — set the earlier group's
out≤ the next group'sinso only one is on screen; (c) deliberate layered typography — add"allow_overlap": trueon one of the groups to silence the validator. The validator estimates each group's vertical bbox from its CSS and flags collisions. Pick (a) by default — it's what makes cinematic-cream feel like a poem accumulating, not a subtitle track replacing itself. - Screen-blend fails on bright backgrounds (>180 luminance). Cinematic templates are cream +
screenand that DNA is locked (the plan can't recolour them) → on a bright backdrop they wash out, so pickink(letterpress built FOR bright surfaces) or theanchortheme (opaque rail surface) rather than overriding a look. - Don't animate `letter-spacing` or `filter:blur` on word entrance — inline-block reflow causes line-jumps.
- CoreML banned for matting — the onnxruntime CoreML EP's mixed-precision partitioning corrupted face alpha (observed with the previous RVM engine; don't re-try it). Matting is CPU-only (~2 fps @1080p ≈ 2-3 min per 10s clip; budget for it on long clips).
---
Dependencies
- hyperframes, built (
packages/cli/dist/cli.js). Scripts auto-resolve the checkout:HYPERFRAMES_ROOTenv → repo root if this skill ships _inside_ hyperframes →~/Downloads/hyperframes. Build withbun install && bun run build. - Node-first; two Python touchpoints via `uvx` (no manual installs): transcription runs WhisperX through
uvx(word-level timings; falls back per SKILL §transcription), and Theme'sdrawonsetpiece shellspython3 scripts/gen-stroke-path.pyat compile time. Everything else runs on the toolchain hyperframes already ships: matting via the hyperframes CLI's `remove-background` (u2net_human_seg; weights auto-download once, ~168 MB, to~/.cache/hyperframes/), image/alpha math via `sharp`, layout/occlusion/overflow via `puppeteer`, plus `ffmpeg`. The scripts auto-resolve these from the hyperframes checkout — nothing extra to install. - Transcription = WhisperX via `uvx` (word-level timings + alignment; no manual install —
transcribe.cjsdrivesuvx whisperx). Falls back to an existing word-leveltranscript.jsonif present. - Source video —
matte.cjs/transcribe.cjsauto-resolvesource.mp4(or glob the clip / readhyperframes.json), sohyperframes init --video X.mp4needs no manual rename. - fps —
matte.cjsextracts at the source's native rate and recordsmatte.fps;render-and-composite.shuses that so the matte stays frame-aligned. - Matting weights are NOT bundled:
matte.cjsshells the hyperframes CLI'sremove-background, which downloads u2net_human_seg (~168 MB, Apache-2.0) once to~/.cache/hyperframes/background-removal/models/. First prepare on a fresh machine needs network for that one download.
If a hard dependency is missing, STOP and ask the user — don't silently skip steps.
# Matting weights: the PP-MattingV2 ONNX SHIPS with the skill (committed — the
# upstream exports have no stable public download URL). Ignore any other stray
# .onnx (e.g. experimental variants) so they don't get committed by accident.
assets/*.onnx
!assets/ppmattingv2_stdc1_human_544x960.onnx
# Stray render/test artifacts — a project's outputs must never live inside the skill.
*.mp4
*.webm
*.log
_*.png
frames_bg/
frames_fg/
history/
*_shadow/
transcript.json
plan.json
matte.fps
safe-zones.json
.DS_Store
# CDPR fan-kit source SVG (528KB) — only its extracted metrics
# (assets/brand/cyberpunk-widths.json) are consumed at runtime.
assets/brand/Cyberpunk2077.svg
______ __ __ ___ ____ __________
/ ____/_ __/ /_ ___ _________ __ ______ / /__ |__ \ / __ \/__ /__ /
/ / / / / / __ \/ _ \/ ___/ __ \/ / / / __ \/ //_/ __/ // / / / / / / /
/ /___/ /_/ / /_/ / __/ / / /_/ / /_/ / / / / ,< / __// /_/ / / / / /
\____/\__, /_.___/\___/_/ / .___/\__,_/_/ /_/_/|_| /____/\____/ /_/ /_/
/____/ /_/
---Thanks and Rules---
Thanks for downloading this file! I really appreciate the folks checking out the site and I hope you enjoy using the file or if you just wanted to see what was in it, I hope it meets your expectations! A special thanks also to Eli, Madqueen, & Nikolay for helping me on several of the logos found on this page.
////// It's my duty to remind you that these are not for commercial usage. All of the logos here are the intellectual property of CD Projekt Red and are therefore not to be used in commercial applications at all. Don't sell it or use it in something that you are going to sell! If you do, you will be subject to the copyright laws of your country and may face legal action and fees.
Also, companies: Stop using these as a substitute for official files! I'm flattered, but there are differences between my logos and the official ones that can break your contract. In the event that you do, however, remember that you must attribute me, AS PER MY AGREEMENT WITH CD PROJEKT RED. I've got a direct line to legal as they were kind enough to allow me to have this project in the first place. Just use theirs, it's easier and much more official!
CD PROJEKT�, Cyberpunk�, Cyberpunk 2077� are registered trademarks of CD PROJEKT S.A. � 2020 CD PROJEKT S.A. All rights reserved. All other copyrights and trademarks are the property of their respective owners. //////
Use these logos to add a bit of realism to your fan pieces as well as any sort of cosplay you're thinking of doing for your favorite Con or event. There are lots of uses out there for these and I hope they come in handy. I always love seeing where these go, so feel free to send a picture our way:
Valency
Twitter/Instagram: @valencygraphics
Eli
Twitter/Instagram: @hey_elium
The Madqueen
Twitter: @madqueenshow
Youtube: The Madqueen Show
Nikolay Verin
Twitter: @nikolayverin
Best of luck, Choombas!
Valency--- replica font: free for personal use (Lyon ArtZ replica via hyperpix); samurai theme is already fan-kit non-commercial
{
"unitsPerEm": 1000,
"fontBBox": [0.033, -0.3, 1.117, 0.876],
"ascent": 0.876,
"descent": -0.3,
"widths": {
"a": 0.713,
"b": 0.621,
"c": 0.79,
"d": 0.7,
"e": 0.81,
"f": 0.747,
"g": 0.664,
"h": 0.501,
"i": 0.61,
"j": 0.614,
"k": 0.786,
"l": 0.749,
"m": 0.728,
"n": 0.656,
"o": 0.555,
"p": 0.713,
"q": 0.541,
"r": 0.712,
"s": 0.502,
"t": 0.713,
"u": 0.575,
"v": 0.64,
"w": 0.696,
"x": 0.53,
"y": 0.588,
"z": 0.664,
"A": 1.032,
"B": 0.888,
"C": 1.166,
"D": 1.025,
"E": 1.174,
"F": 1.097,
"G": 0.966,
"H": 0.708,
"I": 0.879,
"J": 0.884,
"K": 1.155,
"L": 1.104,
"M": 1.074,
"N": 0.955,
"O": 0.799,
"P": 1.041,
"Q": 0.779,
"R": 1.033,
"S": 0.719,
"T": 1.038,
"U": 0.821,
"V": 0.945,
"W": 1.019,
"X": 0.761,
"Y": 0.867,
"Z": 0.96,
" ": 0.26
},
"bounds": {
"a": [0.058, -0.131, 0.656, 0.285],
"b": [0.058, -0.151, 0.564, 0.504],
"c": [0.058, -0.184, 0.731, 0.139],
"d": [0.058, -0.159, 0.642, 0.448],
"e": [0.058, -0.148, 0.752, 0.292],
"f": [0.058, -0.142, 0.69, 0.401],
"g": [0.057, -0.16, 0.607, 0.316],
"h": [0.057, -0.172, 0.443, 0.441],
"i": [0.057, -0.167, 0.553, 0.29],
"j": [0.058, -0.158, 0.557, 0.266],
"k": [0.057, -0.212, 0.728, 0.354],
"l": [0.059, -0.185, 0.692, 0.276],
"m": [0.057, -0.286, 0.671, 0.308],
"n": [0.058, -0.167, 0.598, 0.252],
"o": [0.057, -0.168, 0.499, 0.226],
"p": [0.058, -0.258, 0.656, 0.239],
"q": [0.056, -0.227, 0.487, 0.245],
"r": [0.058, -0.081, 0.655, 0.32],
"s": [0.058, -0.153, 0.446, 0.245],
"t": [0.057, -0.161, 0.655, 0.276],
"u": [0.058, -0.167, 0.518, 0.312],
"v": [0.051, -0.209, 0.582, 0.458],
"w": [0.057, -0.173, 0.639, 0.391],
"x": [0.058, -0.258, 0.472, 0.208],
"y": [0.057, -0.292, 0.532, 0.22],
"z": [0.058, -0.158, 0.606, 0.295],
"A": [0.058, -0.161, 0.974, 0.484],
"B": [0.058, -0.161, 0.834, 0.846],
"C": [0.058, -0.158, 1.108, 0.342],
"D": [0.057, -0.155, 0.967, 0.79],
"E": [0.058, -0.164, 1.117, 0.508],
"F": [0.054, -0.167, 1.039, 0.67],
"G": [0.057, -0.164, 0.908, 0.573],
"H": [0.057, -0.161, 0.649, 0.794],
"I": [0.057, -0.167, 0.821, 0.529],
"J": [0.057, -0.161, 0.826, 0.5],
"K": [0.057, -0.262, 1.098, 0.621],
"L": [0.056, -0.153, 1.048, 0.567],
"M": [0.057, -0.286, 1.017, 0.645],
"N": [0.057, -0.197, 0.897, 0.445],
"O": [0.057, -0.198, 0.742, 0.408],
"P": [0.058, -0.208, 0.984, 0.567],
"Q": [0.056, -0.173, 0.72, 0.567],
"R": [0.058, -0.166, 0.976, 0.445],
"S": [0.057, -0.129, 0.661, 0.494],
"T": [0.057, -0.151, 0.983, 0.525],
"U": [0.058, -0.159, 0.766, 0.583],
"V": [0.057, -0.16, 0.887, 0.876],
"W": [0.057, -0.228, 0.962, 0.644],
"X": [0.058, -0.233, 0.703, 0.496],
"Y": [0.058, -0.3, 0.809, 0.496],
"Z": [0.056, -0.154, 0.903, 0.545]
}
}
{
"_meta": "Measured per-char advance widths (em @100px) + fontBoundingBox ascent/descent for the bundled families in modes/standard/fonts/fonts.css. Generated once via headless-Chrome canvas measureText over the SAME woff2 faces the renderer embeds (kerning ignored, ~<=3px/word). Used by make-theme.cjs to BAKE pixel-exact word geometry at compile time so font-load timing in the render browser can never skew aim. Regenerate if fonts.css changes.",
"Anton": {
"ascent": 1.18,
"descent": 0.33,
"widths": {
"0": 0.4941,
"1": 0.3306,
"2": 0.4941,
"3": 0.4941,
"4": 0.4941,
"5": 0.4941,
"6": 0.4941,
"7": 0.4941,
"8": 0.4941,
"9": 0.4941,
"A": 0.4854,
"B": 0.4785,
"C": 0.4741,
"D": 0.4932,
"E": 0.4116,
"F": 0.3989,
"G": 0.4849,
"H": 0.499,
"I": 0.2266,
"J": 0.4663,
"K": 0.4722,
"L": 0.3975,
"M": 0.7461,
"N": 0.498,
"O": 0.4863,
"P": 0.4722,
"Q": 0.4937,
"R": 0.4766,
"S": 0.4614,
"T": 0.3955,
"U": 0.4736,
"V": 0.4692,
"W": 0.7119,
"X": 0.4839,
"Y": 0.4463,
"Z": 0.4102,
"a": 0.4834,
"b": 0.5015,
"c": 0.4912,
"d": 0.498,
"e": 0.4883,
"f": 0.2803,
"g": 0.5039,
"h": 0.5054,
"i": 0.2432,
"j": 0.2627,
"k": 0.4907,
"l": 0.248,
"m": 0.7583,
"n": 0.4985,
"o": 0.4966,
"p": 0.5015,
"q": 0.498,
"r": 0.3467,
"s": 0.4746,
"t": 0.3052,
"u": 0.499,
"v": 0.4609,
"w": 0.6963,
"x": 0.459,
"y": 0.4609,
"z": 0.3857,
".": 0.2285,
",": 0.2363,
"!": 0.229,
"?": 0.4922,
"'": 0.2139,
"\u2019": 0.2319,
"\"": 0.4287,
":": 0.2417,
";": 0.2451,
"-": 0.311,
"\u2013": 0.311,
"\u2014": 0.563,
"(": 0.291,
")": 0.291,
"[": 0.3179,
"]": 0.3179,
"/": 0.4053,
"&": 0.52,
"%": 1.0566,
"+": 0.3555,
"#": 0.5464,
"@": 0.8643,
"*": 0.4521,
" ": 0.2344
}
},
"Audiowide": {
"ascent": 0.99,
"descent": 0.29,
"widths": {
"0": 0.9253,
"1": 0.3545,
"2": 0.7524,
"3": 0.7192,
"4": 0.7734,
"5": 0.7505,
"6": 0.7441,
"7": 0.6411,
"8": 0.7461,
"9": 0.7441,
"A": 0.8345,
"B": 0.8091,
"C": 0.7471,
"D": 0.8232,
"E": 0.7554,
"F": 0.7383,
"G": 0.8232,
"H": 0.8491,
"I": 0.2764,
"J": 0.7344,
"K": 0.8135,
"L": 0.7363,
"M": 0.9624,
"N": 0.8472,
"O": 0.8604,
"P": 0.8062,
"Q": 0.8604,
"R": 0.8271,
"S": 0.7783,
"T": 0.7261,
"U": 0.8335,
"V": 0.7725,
"W": 1.0024,
"X": 0.7871,
"Y": 0.7813,
"Z": 0.7134,
"a": 0.6533,
"b": 0.6904,
"c": 0.6162,
"d": 0.7002,
"e": 0.6533,
"f": 0.5503,
"g": 0.6904,
"h": 0.7031,
"i": 0.2505,
"j": 0.2505,
"k": 0.6914,
"l": 0.3135,
"m": 0.8735,
"n": 0.6963,
"o": 0.6821,
"p": 0.6904,
"q": 0.6904,
"r": 0.5454,
"s": 0.5991,
"t": 0.5635,
"u": 0.6963,
"v": 0.6753,
"w": 0.9131,
"x": 0.6953,
"y": 0.7031,
"z": 0.6011,
".": 0.2051,
",": 0.2251,
"!": 0.2603,
"?": 0.6655,
"'": 0.2124,
"\u2019": 0.2305,
"\"": 0.3823,
":": 0.2432,
";": 0.2515,
"-": 0.5713,
"\u2013": 0.5132,
"\u2014": 0.9131,
"(": 0.4272,
")": 0.4272,
"[": 0.3652,
"]": 0.3652,
"/": 0.6592,
"&": 0.6714,
"%": 0.8662,
"+": 0.5391,
"#": 0.7295,
"@": 0.6611,
"*": 0.5425,
" ": 0.3252
}
},
"Baloo 2": {
"ascent": 1.08,
"descent": 0.52,
"widths": {
"0": 0.583,
"1": 0.357,
"2": 0.508,
"3": 0.506,
"4": 0.548,
"5": 0.538,
"6": 0.55,
"7": 0.458,
"8": 0.569,
"9": 0.552,
"A": 0.592,
"B": 0.599,
"C": 0.588,
"D": 0.652,
"E": 0.531,
"F": 0.517,
"G": 0.639,
"H": 0.654,
"I": 0.262,
"J": 0.385,
"K": 0.566,
"L": 0.501,
"M": 0.78,
"N": 0.66,
"O": 0.689,
"P": 0.567,
"Q": 0.69,
"R": 0.589,
"S": 0.523,
"T": 0.523,
"U": 0.649,
"V": 0.583,
"W": 0.803,
"X": 0.591,
"Y": 0.55,
"Z": 0.557,
"a": 0.499,
"b": 0.549,
"c": 0.478,
"d": 0.547,
"e": 0.519,
"f": 0.399,
"g": 0.537,
"h": 0.551,
"i": 0.241,
"j": 0.243,
"k": 0.487,
"l": 0.246,
"m": 0.816,
"n": 0.55,
"o": 0.553,
"p": 0.546,
"q": 0.546,
"r": 0.382,
"s": 0.447,
"t": 0.372,
"u": 0.55,
"v": 0.495,
"w": 0.729,
"x": 0.482,
"y": 0.489,
"z": 0.469,
".": 0.141,
",": 0.145,
"!": 0.25,
"?": 0.459,
"'": 0.139,
"\u2019": 0.127,
"\"": 0.27,
":": 0.189,
";": 0.202,
"-": 0.389,
"\u2013": 0.534,
"\u2014": 0.767,
"(": 0.254,
")": 0.254,
"[": 0.268,
"]": 0.269,
"/": 0.305,
"&": 0.637,
"%": 0.772,
"+": 0.548,
"#": 0.667,
"@": 1,
"*": 0.299,
" ": 0.2
}
},
"Bangers": {
"ascent": 0.88,
"descent": 0.18,
"widths": {
"0": 0.474,
"1": 0.32,
"2": 0.467,
"3": 0.458,
"4": 0.459,
"5": 0.45,
"6": 0.478,
"7": 0.339,
"8": 0.518,
"9": 0.489,
"A": 0.433,
"B": 0.428,
"C": 0.414,
"D": 0.441,
"E": 0.341,
"F": 0.341,
"G": 0.499,
"H": 0.442,
"I": 0.2,
"J": 0.279,
"K": 0.462,
"L": 0.302,
"M": 0.619,
"N": 0.453,
"O": 0.493,
"P": 0.379,
"Q": 0.525,
"R": 0.444,
"S": 0.43,
"T": 0.362,
"U": 0.419,
"V": 0.379,
"W": 0.601,
"X": 0.411,
"Y": 0.384,
"Z": 0.431,
"a": 0.433,
"b": 0.428,
"c": 0.414,
"d": 0.441,
"e": 0.341,
"f": 0.341,
"g": 0.499,
"h": 0.441,
"i": 0.202,
"j": 0.279,
"k": 0.462,
"l": 0.302,
"m": 0.619,
"n": 0.452,
"o": 0.493,
"p": 0.379,
"q": 0.525,
"r": 0.444,
"s": 0.43,
"t": 0.362,
"u": 0.419,
"v": 0.379,
"w": 0.601,
"x": 0.411,
"y": 0.384,
"z": 0.431,
".": 0.209,
",": 0.197,
"!": 0.263,
"?": 0.438,
"'": 0.202,
"\u2019": 0.202,
"\"": 0.421,
":": 0.195,
";": 0.262,
"-": 0.36,
"\u2013": 0.36,
"\u2014": 0.52,
"(": 0.334,
")": 0.331,
"[": 0.319,
"]": 0.313,
"/": 0.551,
"&": 0.535,
"%": 0.799,
"+": 0.33,
"#": 0.626,
"@": 0.987,
"*": 0.423,
" ": 0.195
}
},
"Bodoni Moda": {
"ascent": 1.13,
"descent": 0.4,
"widths": {
"0": 0.6195,
"1": 0.4345,
"2": 0.5495,
"3": 0.5205,
"4": 0.5945,
"5": 0.5,
"6": 0.5595,
"7": 0.5045,
"8": 0.5495,
"9": 0.5595,
"A": 0.772,
"B": 0.656,
"C": 0.7155,
"D": 0.726,
"E": 0.6185,
"F": 0.5985,
"G": 0.739,
"H": 0.762,
"I": 0.387,
"J": 0.45,
"K": 0.705,
"L": 0.6035,
"M": 0.8725,
"N": 0.7735,
"O": 0.75,
"P": 0.6385,
"Q": 0.75,
"R": 0.7215,
"S": 0.576,
"T": 0.6735,
"U": 0.726,
"V": 0.774,
"W": 1.0045,
"X": 0.7665,
"Y": 0.7335,
"Z": 0.577,
"a": 0.523,
"b": 0.6085,
"c": 0.5095,
"d": 0.6085,
"e": 0.5235,
"f": 0.398,
"g": 0.5955,
"h": 0.5955,
"i": 0.303,
"j": 0.305,
"k": 0.604,
"l": 0.333,
"m": 0.8605,
"n": 0.5955,
"o": 0.561,
"p": 0.6085,
"q": 0.6085,
"r": 0.442,
"s": 0.439,
"t": 0.3355,
"u": 0.598,
"v": 0.542,
"w": 0.7615,
"x": 0.577,
"y": 0.5465,
"z": 0.467,
".": 0.1995,
",": 0.2145,
"!": 0.3175,
"?": 0.5495,
"'": 0.1995,
"\u2019": 0.2145,
"\"": 0.3695,
":": 0.1975,
";": 0.2145,
"-": 0.3595,
"\u2013": 0.5405,
"\u2014": 0.7845,
"(": 0.3615,
")": 0.3615,
"[": 0.3245,
"]": 0.3245,
"/": 0.528,
"&": 0.8035,
"%": 1,
"+": 0.5295,
"#": 0.638,
"@": 1.0095,
"*": 0.632,
" ": 0.25
}
},
"Caveat": {
"ascent": 0.96,
"descent": 0.3,
"widths": {
"0": 0.45,
"1": 0.45,
"2": 0.45,
"3": 0.45,
"4": 0.45,
"5": 0.45,
"6": 0.45,
"7": 0.45,
"8": 0.45,
"9": 0.45,
"A": 0.502,
"B": 0.52,
"C": 0.472,
"D": 0.578,
"E": 0.529,
"F": 0.454,
"G": 0.487,
"H": 0.563,
"I": 0.4,
"J": 0.306,
"K": 0.498,
"L": 0.426,
"M": 0.72,
"N": 0.599,
"O": 0.496,
"P": 0.464,
"Q": 0.502,
"R": 0.537,
"S": 0.486,
"T": 0.458,
"U": 0.482,
"V": 0.49,
"W": 0.72,
"X": 0.524,
"Y": 0.504,
"Z": 0.522,
"a": 0.437,
"b": 0.435,
"c": 0.355,
"d": 0.393,
"e": 0.325,
"f": 0.291,
"g": 0.355,
"h": 0.464,
"i": 0.187,
"j": 0.204,
"k": 0.365,
"l": 0.169,
"m": 0.56,
"n": 0.45,
"o": 0.353,
"p": 0.378,
"q": 0.377,
"r": 0.36,
"s": 0.341,
"t": 0.331,
"u": 0.37,
"v": 0.331,
"w": 0.517,
"x": 0.343,
"y": 0.341,
"z": 0.315,
".": 0.198,
",": 0.198,
"!": 0.21,
"?": 0.374,
"'": 0.117,
"\u2019": 0.117,
"\"": 0.237,
":": 0.198,
";": 0.198,
"-": 0.326,
"\u2013": 0.446,
"\u2014": 0.566,
"(": 0.33,
")": 0.33,
"[": 0.33,
"]": 0.33,
"/": 0.33,
"&": 0.564,
"%": 0.6,
"+": 0.446,
"#": 0.559,
"@": 0.643,
"*": 0.363,
" ": 0.242
}
},
"Chakra Petch": {
"ascent": 0.99,
"descent": 0.31,
"widths": {
"0": 0.636,
"1": 0.366,
"2": 0.558,
"3": 0.587,
"4": 0.563,
"5": 0.59,
"6": 0.606,
"7": 0.506,
"8": 0.622,
"9": 0.611,
"A": 0.629,
"B": 0.654,
"C": 0.639,
"D": 0.672,
"E": 0.59,
"F": 0.56,
"G": 0.66,
"H": 0.682,
"I": 0.256,
"J": 0.564,
"K": 0.628,
"L": 0.545,
"M": 0.785,
"N": 0.681,
"O": 0.684,
"P": 0.63,
"Q": 0.684,
"R": 0.659,
"S": 0.621,
"T": 0.568,
"U": 0.684,
"V": 0.634,
"W": 0.868,
"X": 0.62,
"Y": 0.606,
"Z": 0.576,
"a": 0.559,
"b": 0.585,
"c": 0.542,
"d": 0.587,
"e": 0.568,
"f": 0.384,
"g": 0.587,
"h": 0.578,
"i": 0.253,
"j": 0.268,
"k": 0.538,
"l": 0.243,
"m": 0.845,
"n": 0.576,
"o": 0.577,
"p": 0.587,
"q": 0.58,
"r": 0.393,
"s": 0.52,
"t": 0.398,
"u": 0.575,
"v": 0.491,
"w": 0.714,
"x": 0.501,
"y": 0.577,
"z": 0.482,
".": 0.217,
",": 0.217,
"!": 0.254,
"?": 0.542,
"'": 0.216,
"\u2019": 0.215,
"\"": 0.38,
":": 0.215,
";": 0.215,
"-": 0.412,
"\u2013": 0.628,
"\u2014": 0.735,
"(": 0.322,
")": 0.322,
"[": 0.321,
"]": 0.321,
"/": 0.484,
"&": 0.646,
"%": 0.842,
"+": 0.548,
"#": 0.697,
"@": 0.819,
"*": 0.343,
" ": 0.253
}
},
"Cinzel": {
"ascent": 0.98,
"descent": 0.37,
"widths": {
"0": 0.596,
"1": 0.344,
"2": 0.553,
"3": 0.503,
"4": 0.583,
"5": 0.498,
"6": 0.56,
"7": 0.502,
"8": 0.552,
"9": 0.56,
"A": 0.712,
"B": 0.586,
"C": 0.76,
"D": 0.807,
"E": 0.577,
"F": 0.541,
"G": 0.798,
"H": 0.813,
"I": 0.338,
"J": 0.324,
"K": 0.657,
"L": 0.562,
"M": 0.931,
"N": 0.841,
"O": 0.838,
"P": 0.577,
"Q": 0.837,
"R": 0.677,
"S": 0.475,
"T": 0.618,
"U": 0.785,
"V": 0.74,
"W": 0.982,
"X": 0.662,
"Y": 0.661,
"Z": 0.651,
"a": 0.663,
"b": 0.57,
"c": 0.705,
"d": 0.76,
"e": 0.546,
"f": 0.523,
"g": 0.742,
"h": 0.746,
"i": 0.336,
"j": 0.309,
"k": 0.624,
"l": 0.536,
"m": 0.878,
"n": 0.779,
"o": 0.774,
"p": 0.553,
"q": 0.777,
"r": 0.627,
"s": 0.462,
"t": 0.578,
"u": 0.7,
"v": 0.663,
"w": 0.899,
"x": 0.626,
"y": 0.64,
"z": 0.608,
".": 0.179,
",": 0.185,
"!": 0.23,
"?": 0.413,
"'": 0.17,
"\u2019": 0.193,
"\"": 0.315,
":": 0.179,
";": 0.185,
"-": 0.38,
"\u2013": 0.5,
"\u2014": 0.8,
"(": 0.338,
")": 0.338,
"[": 0.332,
"]": 0.332,
"/": 0.406,
"&": 0.646,
"%": 0.684,
"+": 0.481,
"#": 0.536,
"@": 0.977,
"*": 0.386,
" ": 0.25
}
},
"Cormorant Garamond": {
"ascent": 0.92,
"descent": 0.29,
"widths": {
"0": 0.477,
"1": 0.332,
"2": 0.402,
"3": 0.389,
"4": 0.451,
"5": 0.409,
"6": 0.465,
"7": 0.429,
"8": 0.489,
"9": 0.465,
"A": 0.702,
"B": 0.558,
"C": 0.686,
"D": 0.691,
"E": 0.537,
"F": 0.509,
"G": 0.713,
"H": 0.759,
"I": 0.332,
"J": 0.322,
"K": 0.65,
"L": 0.533,
"M": 0.835,
"N": 0.727,
"O": 0.766,
"P": 0.543,
"Q": 0.766,
"R": 0.673,
"S": 0.491,
"T": 0.634,
"U": 0.698,
"V": 0.666,
"W": 0.931,
"X": 0.652,
"Y": 0.609,
"Z": 0.595,
"a": 0.419,
"b": 0.51,
"c": 0.411,
"d": 0.512,
"e": 0.408,
"f": 0.305,
"g": 0.445,
"h": 0.499,
"i": 0.273,
"j": 0.257,
"k": 0.491,
"l": 0.258,
"m": 0.763,
"n": 0.517,
"o": 0.479,
"p": 0.511,
"q": 0.488,
"r": 0.364,
"s": 0.338,
"t": 0.338,
"u": 0.493,
"v": 0.434,
"w": 0.671,
"x": 0.439,
"y": 0.435,
"z": 0.405,
".": 0.197,
",": 0.222,
"!": 0.247,
"?": 0.329,
"'": 0.14,
"\u2019": 0.192,
"\"": 0.283,
":": 0.197,
";": 0.224,
"-": 0.323,
"\u2013": 0.515,
"\u2014": 0.83,
"(": 0.309,
")": 0.309,
"[": 0.274,
"]": 0.274,
"/": 0.341,
"&": 0.697,
"%": 0.574,
"+": 0.398,
"#": 0.526,
"@": 0.719,
"*": 0.448,
" ": 0.234
}
},
"Creepster": {
"ascent": 0.95,
"descent": 0.22,
"widths": {
"0": 0.457,
"1": 0.3105,
"2": 0.4355,
"3": 0.4277,
"4": 0.4219,
"5": 0.4277,
"6": 0.4141,
"7": 0.3975,
"8": 0.4287,
"9": 0.4355,
"A": 0.4453,
"B": 0.4678,
"C": 0.4307,
"D": 0.4541,
"E": 0.4199,
"F": 0.4238,
"G": 0.46,
"H": 0.457,
"I": 0.3076,
"J": 0.374,
"K": 0.4922,
"L": 0.3574,
"M": 0.6563,
"N": 0.5166,
"O": 0.4561,
"P": 0.4434,
"Q": 0.4775,
"R": 0.4629,
"S": 0.4111,
"T": 0.4082,
"U": 0.4854,
"V": 0.4297,
"W": 0.582,
"X": 0.4521,
"Y": 0.4023,
"Z": 0.3818,
"a": 0.4316,
"b": 0.4492,
"c": 0.4336,
"d": 0.4297,
"e": 0.4033,
"f": 0.3975,
"g": 0.4336,
"h": 0.4854,
"i": 0.3281,
"j": 0.3672,
"k": 0.4541,
"l": 0.3174,
"m": 0.6553,
"n": 0.5088,
"o": 0.4697,
"p": 0.4629,
"q": 0.4805,
"r": 0.459,
"s": 0.3984,
"t": 0.4502,
"u": 0.4893,
"v": 0.4531,
"w": 0.6348,
"x": 0.4619,
"y": 0.4316,
"z": 0.3916,
".": 0.165,
",": 0.1484,
"!": 0.2021,
"?": 0.3848,
"'": 0.1416,
"\u2019": 0.1387,
"\"": 0.2764,
":": 0.165,
";": 0.1514,
"-": 0.3896,
"\u2013": 0.3896,
"\u2014": 0.5732,
"(": 0.2246,
")": 0.2266,
"[": 0.2256,
"]": 0.2217,
"/": 0.3369,
"&": 0.4648,
"%": 0.5498,
"+": 0.4014,
"#": 0.5859,
"@": 0.6865,
"*": 0.4121,
" ": 0.2354
}
},
"Fredoka": {
"ascent": 0.97,
"descent": 0.24,
"widths": {
"0": 0.587,
"1": 0.392,
"2": 0.598,
"3": 0.587,
"4": 0.544,
"5": 0.516,
"6": 0.536,
"7": 0.554,
"8": 0.559,
"9": 0.536,
"A": 0.704,
"B": 0.638,
"C": 0.667,
"D": 0.692,
"E": 0.623,
"F": 0.631,
"G": 0.708,
"H": 0.687,
"I": 0.217,
"J": 0.545,
"K": 0.591,
"L": 0.558,
"M": 0.825,
"N": 0.708,
"O": 0.732,
"P": 0.601,
"Q": 0.811,
"R": 0.624,
"S": 0.554,
"T": 0.664,
"U": 0.713,
"V": 0.724,
"W": 0.965,
"X": 0.694,
"Y": 0.638,
"Z": 0.575,
"a": 0.574,
"b": 0.582,
"c": 0.508,
"d": 0.586,
"e": 0.54,
"f": 0.378,
"g": 0.565,
"h": 0.535,
"i": 0.224,
"j": 0.24,
"k": 0.489,
"l": 0.281,
"m": 0.826,
"n": 0.557,
"o": 0.56,
"p": 0.574,
"q": 0.568,
"r": 0.406,
"s": 0.459,
"t": 0.376,
"u": 0.543,
"v": 0.537,
"w": 0.763,
"x": 0.494,
"y": 0.526,
"z": 0.531,
".": 0.211,
",": 0.225,
"!": 0.231,
"?": 0.491,
"'": 0.181,
"\u2019": 0.157,
"\"": 0.335,
":": 0.234,
";": 0.213,
"-": 0.426,
"\u2013": 0.556,
"\u2014": 0.686,
"(": 0.377,
")": 0.377,
"[": 0.342,
"]": 0.342,
"/": 0.517,
"&": 0.713,
"%": 0.823,
"+": 0.537,
"#": 0.796,
"@": 0.829,
"*": 0.494,
" ": 0.254
}
},
"Inter": {
"ascent": 0.97,
"descent": 0.24,
"widths": {
"0": 0.6309,
"1": 0.4067,
"2": 0.6099,
"3": 0.6177,
"4": 0.646,
"5": 0.5933,
"6": 0.6201,
"7": 0.5659,
"8": 0.6187,
"9": 0.6201,
"A": 0.6899,
"B": 0.6543,
"C": 0.7305,
"D": 0.7217,
"E": 0.6011,
"F": 0.5903,
"G": 0.7461,
"H": 0.7432,
"I": 0.2686,
"J": 0.5708,
"K": 0.6719,
"L": 0.5654,
"M": 0.9033,
"N": 0.7534,
"O": 0.7646,
"P": 0.6387,
"Q": 0.7646,
"R": 0.6436,
"S": 0.6416,
"T": 0.6455,
"U": 0.7441,
"V": 0.6899,
"W": 0.9854,
"X": 0.6821,
"Y": 0.6787,
"Z": 0.6289,
"a": 0.5615,
"b": 0.6123,
"c": 0.5713,
"d": 0.6123,
"e": 0.583,
"f": 0.3701,
"g": 0.6133,
"h": 0.5913,
"i": 0.2422,
"j": 0.2422,
"k": 0.5488,
"l": 0.2422,
"m": 0.876,
"n": 0.5908,
"o": 0.5996,
"p": 0.6123,
"q": 0.6123,
"r": 0.3765,
"s": 0.5278,
"t": 0.3271,
"u": 0.5913,
"v": 0.562,
"w": 0.8184,
"x": 0.5459,
"y": 0.562,
"z": 0.5522,
".": 0.2881,
",": 0.2881,
"!": 0.2876,
"?": 0.5112,
"'": 0.2998,
"\u2019": 0.2607,
"\"": 0.4658,
":": 0.2881,
";": 0.3018,
"-": 0.46,
"\u2013": 0.5,
"\u2014": 1,
"(": 0.3647,
")": 0.3647,
"[": 0.3647,
"]": 0.3647,
"/": 0.3604,
"&": 0.644,
"%": 0.9819,
"+": 0.6616,
"#": 0.6333,
"@": 0.9658,
"*": 0.501,
" ": 0.2813
}
},
"Monoton": {
"ascent": 1.16,
"descent": 0.4,
"widths": {
"0": 0.8433,
"1": 0.4263,
"2": 0.583,
"3": 0.6123,
"4": 0.6221,
"5": 0.6919,
"6": 0.7817,
"7": 0.6318,
"8": 0.8086,
"9": 0.7822,
"A": 0.9365,
"B": 0.8623,
"C": 0.8521,
"D": 0.8862,
"E": 0.7065,
"F": 0.6831,
"G": 0.9082,
"H": 0.9014,
"I": 0.3389,
"J": 0.4346,
"K": 0.8159,
"L": 0.605,
"M": 0.9209,
"N": 0.8779,
"O": 0.9336,
"P": 0.8506,
"Q": 0.938,
"R": 0.8438,
"S": 0.6821,
"T": 0.5913,
"U": 0.9053,
"V": 0.9409,
"W": 1.1616,
"X": 0.8765,
"Y": 0.7891,
"Z": 0.854,
"a": 0.832,
"b": 0.7207,
"c": 0.7368,
"d": 0.833,
"e": 0.667,
"f": 0.6255,
"g": 0.8271,
"h": 0.8306,
"i": 0.3379,
"j": 0.3936,
"k": 0.8018,
"l": 0.583,
"m": 1.168,
"n": 0.7871,
"o": 0.8335,
"p": 0.7817,
"q": 0.7881,
"r": 0.8315,
"s": 0.6831,
"t": 0.5884,
"u": 0.7959,
"v": 0.8555,
"w": 1.1299,
"x": 0.79,
"y": 0.7334,
"z": 0.7148,
".": 0.2554,
",": 0.2529,
"!": 0.3364,
"?": 0.6938,
"'": 0.2578,
"\u2019": 0.2515,
"\"": 0.5386,
":": 0.2627,
";": 0.2617,
"-": 0.4736,
"\u2013": 0.4736,
"\u2014": 0.6152,
"(": 0.4106,
")": 0.4087,
"[": 0.3208,
"]": 0.3198,
"/": 0.4478,
"&": 1.0298,
"%": 1.5049,
"+": 0.6011,
"#": 1.0146,
"@": 1.0762,
"*": 0.6646,
" ": 0.1084
}
},
"Orbitron": {
"ascent": 1.01,
"descent": 0.24,
"widths": {
"0": 0.834,
"1": 0.391,
"2": 0.83,
"3": 0.826,
"4": 0.73,
"5": 0.83,
"6": 0.82,
"7": 0.66,
"8": 0.834,
"9": 0.828,
"A": 0.836,
"B": 0.832,
"C": 0.822,
"D": 0.834,
"E": 0.766,
"F": 0.723,
"G": 0.83,
"H": 0.851,
"I": 0.214,
"J": 0.78,
"K": 0.797,
"L": 0.779,
"M": 0.928,
"N": 0.832,
"O": 0.828,
"P": 0.791,
"Q": 0.884,
"R": 0.825,
"S": 0.822,
"T": 0.759,
"U": 0.828,
"V": 1.003,
"W": 1.179,
"X": 0.812,
"Y": 0.806,
"Z": 0.821,
"a": 0.694,
"b": 0.667,
"c": 0.695,
"d": 0.667,
"e": 0.692,
"f": 0.407,
"g": 0.683,
"h": 0.668,
"i": 0.208,
"j": 0.239,
"k": 0.646,
"l": 0.302,
"m": 0.978,
"n": 0.696,
"o": 0.692,
"p": 0.664,
"q": 0.664,
"r": 0.512,
"s": 0.686,
"t": 0.41,
"u": 0.695,
"v": 0.79,
"w": 1.071,
"x": 0.692,
"y": 0.685,
"z": 0.698,
".": 0.214,
",": 0.193,
"!": 0.22,
"?": 0.678,
"'": 0.224,
"\u2019": 0.166,
"\"": 0.372,
":": 0.214,
";": 0.193,
"-": 0.517,
"\u2013": 0.708,
"\u2014": 0.822,
"(": 0.277,
")": 0.278,
"[": 0.275,
"]": 0.276,
"/": 0.521,
"&": 0.938,
"%": 0.966,
"+": 0.433,
"#": 0.797,
"@": 0.832,
"*": 0.491,
" ": 0.272
}
},
"Permanent Marker": {
"ascent": 1.11,
"descent": 0.32,
"widths": {
"0": 0.6865,
"1": 0.3223,
"2": 0.6504,
"3": 0.5615,
"4": 0.582,
"5": 0.5801,
"6": 0.501,
"7": 0.5762,
"8": 0.5371,
"9": 0.5293,
"A": 0.7021,
"B": 0.6865,
"C": 0.6113,
"D": 0.5889,
"E": 0.627,
"F": 0.5752,
"G": 0.7891,
"H": 0.6953,
"I": 0.5156,
"J": 0.6689,
"K": 0.7764,
"L": 0.6328,
"M": 0.9736,
"N": 0.6846,
"O": 0.7441,
"P": 0.6396,
"Q": 0.7441,
"R": 0.6465,
"S": 0.5762,
"T": 0.6074,
"U": 0.6953,
"V": 0.584,
"W": 0.9941,
"X": 0.623,
"Y": 0.4932,
"Z": 0.6641,
"a": 0.5811,
"b": 0.5732,
"c": 0.5137,
"d": 0.4971,
"e": 0.5215,
"f": 0.4551,
"g": 0.6523,
"h": 0.582,
"i": 0.3203,
"j": 0.5342,
"k": 0.6455,
"l": 0.5332,
"m": 0.8145,
"n": 0.5645,
"o": 0.6289,
"p": 0.5244,
"q": 0.625,
"r": 0.541,
"s": 0.4639,
"t": 0.4795,
"u": 0.585,
"v": 0.4854,
"w": 0.8252,
"x": 0.5068,
"y": 0.4004,
"z": 0.5479,
".": 0.2285,
",": 0.2041,
"!": 0.2607,
"?": 0.5566,
"'": 0.2656,
"\u2019": 0.1992,
"\"": 0.4922,
":": 0.2412,
";": 0.2451,
"-": 0.6094,
"\u2013": 0.5889,
"\u2014": 0.8789,
"(": 0.3613,
")": 0.3613,
"[": 0.3877,
"]": 0.4033,
"/": 0.4014,
"&": 0.5996,
"%": 0.6436,
"+": 0.5791,
"#": 0.7031,
"@": 0.7324,
"*": 0.6738,
" ": 0.3701
}
},
"Press Start 2P": {
"ascent": 1,
"descent": 0,
"widths": {
"0": 1,
"1": 1,
"2": 1,
"3": 1,
"4": 1,
"5": 1,
"6": 1,
"7": 1,
"8": 1,
"9": 1,
"A": 1,
"B": 1,
"C": 1,
"D": 1,
"E": 1,
"F": 1,
"G": 1,
"H": 1,
"I": 1,
"J": 1,
"K": 1,
"L": 1,
"M": 1,
"N": 1,
"O": 1,
"P": 1,
"Q": 1,
"R": 1,
"S": 1,
"T": 1,
"U": 1,
"V": 1,
"W": 1,
"X": 1,
"Y": 1,
"Z": 1,
"a": 1,
"b": 1,
"c": 1,
"d": 1,
"e": 1,
"f": 1,
"g": 1,
"h": 1,
"i": 1,
"j": 1,
"k": 1,
"l": 1,
"m": 1,
"n": 1,
"o": 1,
"p": 1,
"q": 1,
"r": 1,
"s": 1,
"t": 1,
"u": 1,
"v": 1,
"w": 1,
"x": 1,
"y": 1,
"z": 1,
".": 1,
",": 1,
"!": 1,
"?": 1,
"'": 1,
"\u2019": 1,
"\"": 1,
":": 1,
";": 1,
"-": 1,
"\u2013": 1,
"\u2014": 1,
"(": 1,
")": 1,
"[": 1,
"]": 1,
"/": 1,
"&": 1,
"%": 1,
"+": 1,
"#": 1,
"@": 1,
"*": 1,
" ": 1
}
},
"Rajdhani": {
"ascent": 0.93,
"descent": 0.35,
"widths": {
"0": 0.52,
"1": 0.322,
"2": 0.482,
"3": 0.5,
"4": 0.526,
"5": 0.489,
"6": 0.505,
"7": 0.395,
"8": 0.526,
"9": 0.505,
"A": 0.54,
"B": 0.549,
"C": 0.541,
"D": 0.556,
"E": 0.48,
"F": 0.457,
"G": 0.541,
"H": 0.572,
"I": 0.241,
"J": 0.505,
"K": 0.538,
"L": 0.451,
"M": 0.705,
"N": 0.581,
"O": 0.553,
"P": 0.526,
"Q": 0.56,
"R": 0.537,
"S": 0.555,
"T": 0.427,
"U": 0.554,
"V": 0.506,
"W": 0.797,
"X": 0.514,
"Y": 0.5,
"Z": 0.493,
"a": 0.507,
"b": 0.51,
"c": 0.399,
"d": 0.51,
"e": 0.492,
"f": 0.318,
"g": 0.51,
"h": 0.512,
"i": 0.221,
"j": 0.221,
"k": 0.443,
"l": 0.221,
"m": 0.777,
"n": 0.512,
"o": 0.5,
"p": 0.51,
"q": 0.507,
"r": 0.323,
"s": 0.433,
"t": 0.349,
"u": 0.512,
"v": 0.431,
"w": 0.686,
"x": 0.427,
"y": 0.463,
"z": 0.418,
".": 0.194,
",": 0.194,
"!": 0.237,
"?": 0.471,
"'": 0.186,
"\u2019": 0.181,
"\"": 0.337,
":": 0.194,
";": 0.194,
"-": 0.312,
"\u2013": 0.405,
"\u2014": 0.565,
"(": 0.29,
")": 0.29,
"[": 0.297,
"]": 0.297,
"/": 0.4,
"&": 0.603,
"%": 0.724,
"+": 0.535,
"#": 0.675,
"@": 0.751,
"*": 0.404,
" ": 0.254
}
},
"Saira Stencil One": {
"ascent": 1.13,
"descent": 0.44,
"widths": {
"0": 0.66,
"1": 0.402,
"2": 0.614,
"3": 0.61,
"4": 0.654,
"5": 0.628,
"6": 0.662,
"7": 0.576,
"8": 0.676,
"9": 0.662,
"A": 0.687,
"B": 0.67,
"C": 0.568,
"D": 0.68,
"E": 0.616,
"F": 0.576,
"G": 0.66,
"H": 0.716,
"I": 0.33,
"J": 0.425,
"K": 0.683,
"L": 0.541,
"M": 0.885,
"N": 0.716,
"O": 0.698,
"P": 0.645,
"Q": 0.698,
"R": 0.674,
"S": 0.612,
"T": 0.581,
"U": 0.69,
"V": 0.663,
"W": 0.996,
"X": 0.678,
"Y": 0.633,
"Z": 0.618,
"a": 0.558,
"b": 0.578,
"c": 0.465,
"d": 0.578,
"e": 0.557,
"f": 0.437,
"g": 0.578,
"h": 0.588,
"i": 0.299,
"j": 0.299,
"k": 0.576,
"l": 0.299,
"m": 0.857,
"n": 0.588,
"o": 0.572,
"p": 0.578,
"q": 0.578,
"r": 0.425,
"s": 0.498,
"t": 0.427,
"u": 0.586,
"v": 0.551,
"w": 0.846,
"x": 0.566,
"y": 0.588,
"z": 0.534,
".": 0.33,
",": 0.33,
"!": 0.363,
"?": 0.521,
"'": 0.3,
"\u2019": 0.32,
"\"": 0.525,
":": 0.33,
";": 0.33,
"-": 0.379,
"\u2013": 0.48,
"\u2014": 0.88,
"(": 0.374,
")": 0.374,
"[": 0.399,
"]": 0.399,
"/": 0.429,
"&": 0.739,
"%": 0.898,
"+": 0.54,
"#": 0.7,
"@": 0.944,
"*": 0.454,
" ": 0.26
}
},
"Shippori Mincho": {
"ascent": 1.16,
"descent": 0.29,
"widths": {
"0": 0.52,
"1": 0.487,
"2": 0.511,
"3": 0.523,
"4": 0.503,
"5": 0.502,
"6": 0.532,
"7": 0.545,
"8": 0.539,
"9": 0.532,
"A": 0.762,
"B": 0.716,
"C": 0.716,
"D": 0.782,
"E": 0.72,
"F": 0.686,
"G": 0.744,
"H": 0.889,
"I": 0.408,
"J": 0.437,
"K": 0.812,
"L": 0.712,
"M": 1.013,
"N": 0.825,
"O": 0.771,
"P": 0.655,
"Q": 0.8,
"R": 0.758,
"S": 0.651,
"T": 0.743,
"U": 0.815,
"V": 0.769,
"W": 1.022,
"X": 0.745,
"Y": 0.754,
"Z": 0.593,
"a": 0.55,
"b": 0.579,
"c": 0.509,
"d": 0.577,
"e": 0.543,
"f": 0.355,
"g": 0.592,
"h": 0.628,
"i": 0.317,
"j": 0.239,
"k": 0.586,
"l": 0.318,
"m": 0.956,
"n": 0.633,
"o": 0.576,
"p": 0.606,
"q": 0.57,
"r": 0.437,
"s": 0.499,
"t": 0.327,
"u": 0.604,
"v": 0.558,
"w": 0.783,
"x": 0.576,
"y": 0.557,
"z": 0.48,
".": 0.249,
",": 0.25,
"!": 0.288,
"?": 0.444,
"'": 0.198,
"\u2019": 0.203,
"\"": 0.389,
":": 0.209,
";": 0.21,
"-": 0.271,
"\u2013": 0.536,
"\u2014": 1,
"(": 0.363,
")": 0.363,
"[": 0.438,
"]": 0.437,
"/": 0.443,
"&": 0.82,
"%": 0.938,
"+": 0.605,
"#": 0.676,
"@": 0.906,
"*": 0.441,
" ": 0.258
}
},
"Sora": {
"ascent": 0.97,
"descent": 0.29,
"widths": {
"0": 0.743,
"1": 0.42,
"2": 0.618,
"3": 0.614,
"4": 0.642,
"5": 0.623,
"6": 0.659,
"7": 0.574,
"8": 0.637,
"9": 0.659,
"A": 0.756,
"B": 0.68,
"C": 0.795,
"D": 0.791,
"E": 0.6,
"F": 0.56,
"G": 0.831,
"H": 0.804,
"I": 0.314,
"J": 0.64,
"K": 0.678,
"L": 0.544,
"M": 0.92,
"N": 0.849,
"O": 0.865,
"P": 0.64,
"Q": 0.865,
"R": 0.7,
"S": 0.689,
"T": 0.594,
"U": 0.789,
"V": 0.708,
"W": 1.043,
"X": 0.696,
"Y": 0.652,
"Z": 0.654,
"a": 0.575,
"b": 0.689,
"c": 0.606,
"d": 0.689,
"e": 0.612,
"f": 0.379,
"g": 0.675,
"h": 0.64,
"i": 0.308,
"j": 0.317,
"k": 0.573,
"l": 0.286,
"m": 0.968,
"n": 0.64,
"o": 0.676,
"p": 0.689,
"q": 0.689,
"r": 0.41,
"s": 0.536,
"t": 0.426,
"u": 0.628,
"v": 0.559,
"w": 0.848,
"x": 0.56,
"y": 0.547,
"z": 0.488,
".": 0.262,
",": 0.262,
"!": 0.296,
"?": 0.552,
"'": 0.262,
"\u2019": 0.244,
"\"": 0.43,
":": 0.262,
";": 0.262,
"-": 0.504,
"\u2013": 0.512,
"\u2014": 0.9,
"(": 0.376,
")": 0.376,
"[": 0.376,
"]": 0.376,
"/": 0.344,
"&": 0.688,
"%": 0.85,
"+": 0.59,
"#": 0.692,
"@": 1.122,
"*": 0.567,
" ": 0.228
}
},
"Space Grotesk": {
"ascent": 0.98,
"descent": 0.29,
"widths": {
"0": 0.641,
"1": 0.418,
"2": 0.603,
"3": 0.604,
"4": 0.625,
"5": 0.599,
"6": 0.612,
"7": 0.564,
"8": 0.623,
"9": 0.612,
"A": 0.63,
"B": 0.661,
"C": 0.642,
"D": 0.663,
"E": 0.563,
"F": 0.54,
"G": 0.66,
"H": 0.656,
"I": 0.244,
"J": 0.594,
"K": 0.61,
"L": 0.546,
"M": 0.865,
"N": 0.664,
"O": 0.669,
"P": 0.601,
"Q": 0.669,
"R": 0.631,
"S": 0.613,
"T": 0.588,
"U": 0.666,
"V": 0.619,
"W": 0.875,
"X": 0.638,
"Y": 0.61,
"Z": 0.577,
"a": 0.575,
"b": 0.644,
"c": 0.598,
"d": 0.644,
"e": 0.592,
"f": 0.442,
"g": 0.642,
"h": 0.613,
"i": 0.245,
"j": 0.247,
"k": 0.538,
"l": 0.245,
"m": 0.86,
"n": 0.613,
"o": 0.618,
"p": 0.644,
"q": 0.644,
"r": 0.377,
"s": 0.527,
"t": 0.46,
"u": 0.612,
"v": 0.539,
"w": 0.801,
"x": 0.593,
"y": 0.612,
"z": 0.521,
".": 0.253,
",": 0.251,
"!": 0.253,
"?": 0.58,
"'": 0.251,
"\u2019": 0.251,
"\"": 0.425,
":": 0.253,
";": 0.253,
"-": 0.455,
"\u2013": 0.607,
"\u2014": 0.911,
"(": 0.379,
")": 0.377,
"[": 0.335,
"]": 0.335,
"/": 0.37,
"&": 0.604,
"%": 0.772,
"+": 0.62,
"#": 0.628,
"@": 1.026,
"*": 0.52,
" ": 0.257
}
},
"Special Elite": {
"ascent": 0.7,
"descent": 0.3,
"widths": {
"0": 0.6108,
"1": 0.5698,
"2": 0.5737,
"3": 0.5571,
"4": 0.6123,
"5": 0.5376,
"6": 0.5884,
"7": 0.5552,
"8": 0.5986,
"9": 0.5845,
"A": 0.5498,
"B": 0.604,
"C": 0.5791,
"D": 0.6226,
"E": 0.6387,
"F": 0.6055,
"G": 0.6152,
"H": 0.6528,
"I": 0.4951,
"J": 0.541,
"K": 0.5825,
"L": 0.6025,
"M": 0.6978,
"N": 0.6279,
"O": 0.6167,
"P": 0.5532,
"Q": 0.6021,
"R": 0.6367,
"S": 0.5889,
"T": 0.5942,
"U": 0.6216,
"V": 0.6113,
"W": 0.6436,
"X": 0.582,
"Y": 0.561,
"Z": 0.5923,
"a": 0.5654,
"b": 0.5654,
"c": 0.5474,
"d": 0.603,
"e": 0.5439,
"f": 0.4731,
"g": 0.583,
"h": 0.6167,
"i": 0.5684,
"j": 0.4155,
"k": 0.6206,
"l": 0.5405,
"m": 0.6636,
"n": 0.6323,
"o": 0.5835,
"p": 0.5874,
"q": 0.5679,
"r": 0.5791,
"s": 0.5195,
"t": 0.5103,
"u": 0.6387,
"v": 0.605,
"w": 0.6787,
"x": 0.6606,
"y": 0.5928,
"z": 0.5288,
".": 0.3496,
",": 0.3364,
"!": 0.2764,
"?": 0.4702,
"'": 0.1963,
"\u2019": 0.2642,
"\"": 0.3521,
":": 0.3433,
";": 0.3286,
"-": 0.6362,
"\u2013": 0.6118,
"\u2014": 1.1138,
"(": 0.2793,
")": 0.2817,
"[": 0.312,
"]": 0.3125,
"/": 0.5571,
"&": 0.6763,
"%": 0.667,
"+": 0.4961,
"#": 0.5547,
"@": 0.665,
"*": 0.5229,
" ": 0.293
}
},
"Teko": {
"ascent": 0.96,
"descent": 0.48,
"widths": {
"0": 0.39,
"1": 0.235,
"2": 0.328,
"3": 0.355,
"4": 0.363,
"5": 0.358,
"6": 0.382,
"7": 0.291,
"8": 0.389,
"9": 0.378,
"A": 0.385,
"B": 0.409,
"C": 0.377,
"D": 0.412,
"E": 0.343,
"F": 0.332,
"G": 0.382,
"H": 0.417,
"I": 0.21,
"J": 0.334,
"K": 0.384,
"L": 0.301,
"M": 0.516,
"N": 0.41,
"O": 0.393,
"P": 0.379,
"Q": 0.393,
"R": 0.396,
"S": 0.362,
"T": 0.31,
"U": 0.397,
"V": 0.364,
"W": 0.609,
"X": 0.373,
"Y": 0.361,
"Z": 0.313,
"a": 0.36,
"b": 0.36,
"c": 0.352,
"d": 0.36,
"e": 0.36,
"f": 0.222,
"g": 0.36,
"h": 0.365,
"i": 0.178,
"j": 0.178,
"k": 0.357,
"l": 0.178,
"m": 0.549,
"n": 0.365,
"o": 0.37,
"p": 0.36,
"q": 0.36,
"r": 0.238,
"s": 0.328,
"t": 0.219,
"u": 0.365,
"v": 0.317,
"w": 0.543,
"x": 0.342,
"y": 0.342,
"z": 0.292,
".": 0.165,
",": 0.165,
"!": 0.209,
"?": 0.342,
"'": 0.161,
"\u2019": 0.166,
"\"": 0.292,
":": 0.165,
";": 0.165,
"-": 0.315,
"\u2013": 0.42,
"\u2014": 0.536,
"(": 0.264,
")": 0.264,
"[": 0.311,
"]": 0.311,
"/": 0.39,
"&": 0.462,
"%": 0.641,
"+": 0.408,
"#": 0.496,
"@": 0.696,
"*": 0.321,
" ": 0.145
}
},
"VT323": {
"ascent": 0.8,
"descent": 0.2,
"widths": {
"0": 0.4,
"1": 0.4,
"2": 0.4,
"3": 0.4,
"4": 0.4,
"5": 0.4,
"6": 0.4,
"7": 0.4,
"8": 0.4,
"9": 0.4,
"A": 0.4,
"B": 0.4,
"C": 0.4,
"D": 0.4,
"E": 0.4,
"F": 0.4,
"G": 0.4,
"H": 0.4,
"I": 0.4,
"J": 0.4,
"K": 0.4,
"L": 0.4,
"M": 0.4,
"N": 0.4,
"O": 0.4,
"P": 0.4,
"Q": 0.4,
"R": 0.4,
"S": 0.4,
"T": 0.4,
"U": 0.4,
"V": 0.4,
"W": 0.4,
"X": 0.4,
"Y": 0.4,
"Z": 0.4,
"a": 0.4,
"b": 0.4,
"c": 0.4,
"d": 0.4,
"e": 0.4,
"f": 0.4,
"g": 0.4,
"h": 0.4,
"i": 0.4,
"j": 0.4,
"k": 0.4,
"l": 0.4,
"m": 0.4,
"n": 0.4,
"o": 0.4,
"p": 0.4,
"q": 0.4,
"r": 0.4,
"s": 0.4,
"t": 0.4,
"u": 0.4,
"v": 0.4,
"w": 0.4,
"x": 0.4,
"y": 0.4,
"z": 0.4,
".": 0.4,
",": 0.4,
"!": 0.4,
"?": 0.4,
"'": 0.4,
"\u2019": 0.4,
"\"": 0.4,
":": 0.4,
";": 0.4,
"-": 0.4,
"\u2013": 0.4,
"\u2014": 0.4,
"(": 0.4,
")": 0.4,
"[": 0.4,
"]": 0.4,
"/": 0.4,
"&": 0.4,
"%": 0.4,
"+": 0.4,
"#": 0.4,
"@": 0.4,
"*": 0.4,
" ": 0.4
}
}
}
{
"name": "chrome",
"title": "Chrome — Y2K liquid metal",
"register": "loud-luxe",
"when": "fashion-tech, music, Y2K-revival, product-flex content. The hero is cast in liquid metal — a gradient sheen sweeps across it once during the hold. Supporting lines stay cool steel.",
"font": {
"family": "Audiowide",
"weights": [400]
},
"palette": {
"scheme": "light-on-dark",
"cap_color": "#e8edf2",
"blend": "normal",
"accent": "#9fb6cc",
"accent_fallback": "#9fb6cc"
},
"optics": {
"depthMatch": true,
"contactShadow": true,
"warmBloom": false
},
"filter": "none",
"motion": {
"soft": {
"y": 6,
"scale": 1.04,
"dur": 0.3,
"ease": "power3.out"
},
"present": {
"y": 6,
"scale": 1.06,
"dur": 0.26,
"ease": "power3.out"
},
"impact": {
"y": 8,
"scale": 1.1,
"dur": 0.22,
"ease": "power4.out"
},
"exit": {
"dur": 0.25,
"ease": "power2.in",
"stagger": 0
}
},
"hero": {
"entrance": "settle",
"perLetter": false,
"letterStagger": 0,
"glow": 0.15,
"dimOthers": 0.4,
"breathe": 0.008,
"sheen": true,
"case": "uppercase",
"tracking": "0.02em",
"sizeRange": [0.18, 0.3],
"css": "",
"wordCss": "background: linear-gradient(105deg,#3a4350 0%,#f6fbff 20%,#8fa0b2 38%,#ffffff 50%,#2e3742 64%,#eef6fd 82%,#6b7888 100%); background-size: 220% 100%; -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; text-shadow: none;"
},
"css": "",
"deliveries": {
"home": "column",
"rail": "unvalidated",
"note": "signature = liquid-metal wordCss + sheen sweep (engine fx) — std ignores wordCss/fx"
}
}
{
"name": "cream",
"title": "Warm cream — the poetic default",
"register": "premium-warm",
"when": "dark / mid-tone warm scenes (band luma < 150): studios, evening interiors, moody interviews. Introspective, narrative, emotional content. The successor of cinematic-cream.",
"font": {
"family": "Inter",
"weights": [500, 600, 700, 900]
},
"palette": {
"scheme": "light-on-dark",
"cap_color": "#fff5df",
"blend": "screen",
"accent": "scene",
"accent_fallback": "#e3c06a"
},
"optics": {
"depthMatch": true,
"contactShadow": true,
"warmBloom": true
},
"filter": "contrast(1.08)",
"motion": {
"soft": {
"y": 8,
"blur": 2.5,
"dur": 0.5,
"ease": "power2.out"
},
"present": {
"y": 6,
"blur": 1.5,
"scale": 1.03,
"dur": 0.3,
"ease": "power3.out"
},
"impact": {
"y": 12,
"blur": 1.5,
"scale": 1.08,
"dur": 0.24,
"ease": "power4.out"
},
"exit": {
"dur": 0.45,
"y": -8,
"ease": "sine.in",
"stagger": 0
}
},
"hero": {
"entrance": "emergence",
"perLetter": true,
"letterStagger": 0.016,
"glow": 0.3,
"dimOthers": 0.45,
"breathe": 0.012,
"case": "uppercase",
"tracking": "-0.03em",
"sizeRange": [0.2, 0.34],
"letterBlur": 2.5
},
"css": "",
"deliveries": {
"home": "column",
"rail": "validated",
"note": "standard-mode lineage IS cinematic-cream; validated std renders (jimeng)"
}
}
{
"name": "documentary",
"title": "Documentary — Errol Morris dignity",
"register": "formal",
"when": "interviews, serious subject matter, journalism, testimonial. Burn-in reveals (no animation), bone on charcoal, a deliberately modest hero. Gravitas IS the style — stillness, no accent color, no glow.",
"font": {
"family": "Inter",
"weights": [500, 700]
},
"palette": {
"scheme": "light-on-dark",
"cap_color": "#F5EFE6",
"blend": "normal",
"accent": "#F5EFE6",
"accent_fallback": "#F5EFE6"
},
"optics": {
"depthMatch": true,
"contactShadow": true,
"warmBloom": false
},
"filter": "none",
"motion": {
"soft": {
"reveal": "burn"
},
"present": {
"y": 0,
"dur": 0.18,
"ease": "power1.out"
},
"impact": {
"y": 6,
"scale": 1.03,
"dur": 0.2,
"ease": "power2.out"
},
"exit": {
"dur": 0.15,
"ease": "power1.in",
"stagger": 0
}
},
"hero": {
"entrance": "rise",
"perLetter": false,
"letterStagger": 0,
"glow": 0,
"dimOthers": 0.3,
"breathe": 0,
"case": "none",
"tracking": "-0.015em",
"sizeRange": [0.09, 0.15],
"css": "font-weight: 700;"
},
"css": "",
"deliveries": {
"home": "column",
"rail": "unvalidated",
"note": "the lower-third IS documentary's home register (Errol Morris); column stays deliberately modest"
}
}
{
"name": "editorial",
"title": "Editorial — serif voice, magazine cadence",
"register": "editorial-luxe",
"when": "introspective monologue, fashion / beauty, poetic or lyrical content, premium brand films on mid/dark scenes. The serif italic carries intimacy that a grotesque can't. The hero stays lowercase italic — elegance over shout.",
"font": {
"family": "Bodoni Moda",
"weights": [400, 700]
},
"palette": {
"scheme": "light-on-dark",
"cap_color": "#f2ead8",
"blend": "screen",
"accent": "scene",
"accent_fallback": "#c9a36a"
},
"optics": {
"depthMatch": true,
"contactShadow": true,
"warmBloom": true
},
"filter": "contrast(1.06)",
"motion": {
"soft": {
"x": -10,
"y": 0,
"dur": 0.55,
"ease": "power2.out"
},
"present": {
"x": -8,
"y": 0,
"scale": 1.02,
"dur": 0.34,
"ease": "power2.out"
},
"impact": {
"x": -10,
"y": 0,
"scale": 1.06,
"dur": 0.28,
"ease": "power3.out"
},
"exit": {
"dur": 0.4,
"x": 8,
"ease": "sine.in",
"stagger": 0.07
}
},
"hero": {
"entrance": "emergence",
"perLetter": true,
"letterStagger": 0.022,
"glow": 0.18,
"dimOthers": 0.5,
"breathe": 0.01,
"case": "none",
"tracking": "-0.01em",
"sizeRange": [0.18, 0.3],
"css": "font-style: italic; font-weight: 700;",
"letterBlur": 1.5
},
"css": "",
"deliveries": {
"home": "column",
"rail": "unvalidated",
"note": "italic serif rail reads fine; the signature x-glide + line cascade-out exits don't exist in the std enum"
}
}
{
"name": "glitch",
"title": "Glitch — signal corruption",
"register": "loud-cyber",
"when": "digital / hacker / AI / dystopia content; anything that wants the captions to feel like a corrupted feed. RGB-split echoes snap together as each beat lands; timing is machine-percussive; nothing eases politely.",
"font": {
"family": "Space Grotesk",
"weights": [400, 700]
},
"palette": {
"scheme": "light-on-dark",
"cap_color": "#eaffff",
"blend": "screen",
"accent": "#ff3df0",
"accent_fallback": "#ff3df0"
},
"optics": {
"depthMatch": false,
"contactShadow": false,
"warmBloom": false
},
"filter": "none",
"motion": {
"soft": {
"x": -6,
"stretchX": 1.12,
"dur": 0.12,
"ease": "power4.out"
},
"present": {
"x": -8,
"stretchX": 1.18,
"dur": 0.11,
"ease": "power4.out"
},
"impact": {
"x": -10,
"stretchX": 1.25,
"dur": 0.1,
"ease": "power4.out"
},
"exit": {
"dur": 0.1,
"scale": 1.02,
"ease": "power4.in",
"stagger": 0
}
},
"hero": {
"entrance": "settle",
"perLetter": false,
"letterStagger": 0,
"glow": 0.18,
"dimOthers": 0.45,
"breathe": 0,
"ripple": 2,
"case": "uppercase",
"tracking": "0.01em",
"sizeRange": [0.2, 0.32],
"css": "font-weight: 700;",
"echoes": [
{
"dx": -5,
"dy": 0,
"color": "#22e6ff",
"opacity": 0.75
},
{
"dx": 5,
"dy": 1,
"color": "#ff3df0",
"opacity": 0.75
}
]
},
"css": "",
"deliveries": {
"home": "column",
"rail": "unvalidated",
"note": "signature = RGB-split echoes converge (engine fx) — rail keeps only a tinted skin"
}
}
{
"name": "ink",
"title": "Ink — printed on the scene",
"register": "premium",
"when": "BRIGHT scenes (band luma > 150): white offices, daylight, pale walls — the scenes where cream/screen washes out. Multiply blend makes the type read as printed ON the wall, picking up its texture. Confident, editorial-tech tone.",
"font": {
"family": "Inter",
"weights": [500, 700, 900]
},
"palette": {
"scheme": "dark-on-light",
"cap_color": "#1b1714",
"blend": "multiply",
"accent": "scene",
"accent_fallback": "#9a3b2e"
},
"optics": {
"depthMatch": true,
"contactShadow": true,
"warmBloom": false
},
"filter": "contrast(1.05)",
"motion": {
"soft": {
"y": 0,
"scale": 1.06,
"dur": 0.22,
"ease": "power3.out"
},
"present": {
"y": 0,
"scale": 1.08,
"dur": 0.2,
"ease": "power3.out"
},
"impact": {
"y": 0,
"scale": 1.12,
"dur": 0.18,
"ease": "power4.out"
},
"exit": {
"dur": 0.22,
"ease": "power2.in",
"stagger": 0
}
},
"hero": {
"entrance": "settle",
"perLetter": false,
"letterStagger": 0.014,
"glow": 0,
"dimOthers": 0.4,
"breathe": 0,
"case": "uppercase",
"tracking": "-0.035em",
"sizeRange": [0.2, 0.34]
},
"css": "",
"deliveries": {
"home": "column",
"rail": "unvalidated",
"note": "rail works (scale-press→settle) but the letterpress identity wants a scene surface to press INTO"
}
}
{
"name": "keynote",
"title": "Keynote — Apple-grade confidence",
"register": "tech-premium",
"when": "product walkthroughs, launches, founder updates, tech explainers. Opaque white type, dead-center planes, few words held long. Confidence comes from mass and stillness, not motion.",
"font": {
"family": "Inter",
"weights": [400, 600, 800]
},
"palette": {
"scheme": "light-on-dark",
"cap_color": "#ffffff",
"blend": "normal",
"accent": "scene",
"accent_fallback": "#8ab4ff"
},
"optics": {
"depthMatch": true,
"contactShadow": true,
"warmBloom": false
},
"filter": "none",
"motion": {
"soft": {
"reveal": "burn"
},
"present": {
"reveal": "burn"
},
"impact": {
"reveal": "burn"
},
"lineReveal": {
"type": "wipe-x",
"dur": 0.4,
"ease": "expo.out"
},
"exit": {
"dur": 0.3,
"ease": "power2.in",
"stagger": 0
}
},
"hero": {
"entrance": "wipe-up",
"perLetter": false,
"letterStagger": 0,
"glow": 0,
"dimOthers": 0.35,
"breathe": 0,
"case": "uppercase",
"tracking": "-0.045em",
"sizeRange": [0.16, 0.3],
"css": "font-weight: 800;"
},
"css": "",
"deliveries": {
"home": "column",
"rail": "validated",
"note": "wipe-up maps to the std entrance enum; keynote_std validated (Shine)"
}
}
{
"name": "loud",
"title": "Loud — the sanctioned blast",
"register": "loud",
"when": "hype, social reels, sport, music, motivational shorts — content that WANTS the type to compete with the face (k-pop-lyric lineage; deliberately breaks 'subject wins' at the hero). Machine-gun letter staggers, slam entrances, scene-sampled saturated accent on the hero. Body lines ANNOUNCE in front of the subject (bodyLayer:fg — k-pop lyric depth); only the hero embeds behind.",
"font": {
"family": "Anton",
"weights": [400]
},
"palette": {
"scheme": "light-on-dark",
"cap_color": "#ffffff",
"blend": "normal",
"accent": "scene",
"accent_fallback": "#ffd600",
"heroColor": "accent",
"accentMode": "counter"
},
"optics": {
"depthMatch": false,
"contactShadow": true,
"warmBloom": false
},
"filter": "none",
"motion": {
"soft": {
"y": 0,
"scale": 1.3,
"rot": 1.2,
"dur": 0.18,
"ease": "back.out(1.7)"
},
"present": {
"y": 0,
"scale": 1.35,
"rot": 1.2,
"dur": 0.18,
"ease": "back.out(1.7)"
},
"impact": {
"y": 0,
"scale": 1.45,
"rot": 1.5,
"dur": 0.16,
"ease": "back.out(1.9)"
},
"exit": {
"dur": 0.14,
"scale": 0.96,
"ease": "power3.in",
"stagger": 0
}
},
"hero": {
"entrance": "slam",
"perLetter": false,
"letterStagger": 0.01,
"glow": 0.25,
"dimOthers": 0.55,
"breathe": 0.016,
"case": "uppercase",
"tracking": "-0.005em",
"sizeRange": [0.26, 0.4],
"css": "-webkit-text-stroke: 2px rgba(0,0,0,0.35); paint-order: stroke fill;",
"ripple": 3,
"loom": 0.03
},
"css": "",
"bodyLayer": "fg",
"deliveries": {
"home": "column",
"rail": "unvalidated",
"note": "slam maps; bodyLayer:fg already a std-shaped hybrid; validated both"
}
}
{
"name": "neon",
"title": "Neon — the sign in the scene",
"register": "loud-cyber",
"when": "cyberpunk / nightlife / tech-noir / music content on dark scenes. The captions ARE neon signage hanging in the room: electric cyan tubes, ignition flicker, a hum of glow. The hero powers ON like a sign being lit.",
"font": {
"family": "Orbitron",
"weights": [400, 700]
},
"palette": {
"scheme": "light-on-dark",
"cap_color": "#d9f7ff",
"blend": "screen",
"accent": "#22e6ff",
"accent_fallback": "#22e6ff",
"heroColor": "accent"
},
"optics": {
"depthMatch": false,
"contactShadow": false,
"warmBloom": false
},
"filter": "none",
"motion": {
"soft": {
"reveal": "flicker"
},
"present": {
"reveal": "flicker"
},
"impact": {
"reveal": "flicker"
},
"exit": {
"dur": 0.18,
"ease": "power2.in",
"stagger": 0
}
},
"hero": {
"entrance": "flicker-on",
"perLetter": false,
"letterStagger": 0,
"glow": 0.5,
"dimOthers": 0.4,
"breathe": 0.01,
"case": "uppercase",
"tracking": "0.04em",
"sizeRange": [0.18, 0.3],
"css": "text-shadow: 0 0 14px rgba(34,230,255,0.65), 0 0 40px rgba(34,230,255,0.35), 0 0 80px rgba(34,230,255,0.18);"
},
"css": ".cap { text-shadow: 0 0 10px rgba(34,230,255,0.4), 0 0 26px rgba(34,230,255,0.2); }",
"deliveries": {
"home": "column",
"rail": "unvalidated",
"note": "signature = flicker-on + glow layer (engine hero fx) — rail keeps only the cyan skin"
}
}
DNA registry — pick a visual language, not a preset
A DNA is a complete, art-directed visual language: typeface, palette logic, motion grammar, and hero orchestration. It parameterizes per scene instead of shipping a fixed look: the accent color is sampled from THIS scene, the contact shadow falls along THIS scene's light, embed text blur matches THIS scene's depth-of-field, and the hero's entrance amplitude follows how hard the word was actually spoken (RMS).
This replaces the template grab-bag. Six deep languages × scene adaptation beats 54 shallow presets — every render is already fitted to its footage.
Category lock (deliveries field, enforced by the compilers)
Every classic DNA's home is Cinematic (column) — that is where all ten were built and validated. (Standard/rail mode was retired 2026-06-12; the verbatim-rail need is served by the anchor theme. The old rail combos live in ~/Downloads/embedded-captions-archive/.)
The ten
| DNA | Register | Scene fit | Voice |
|---|---|---|---|
| cream | premium-warm | dark / mid warm scenes (band luma < 150) | Inter, warm cream, screen blend, glowing emergence hero. The poetic default. |
| ink | premium | bright scenes (band luma > 150) | Inter, near-black, multiply blend — type reads as _printed on_ the wall. Fixes the bright-scene hole. |
| editorial | editorial-luxe | introspective / fashion / poetic, mid-dark | Bodoni Moda, bone, _lowercase italic hero_ — magazine elegance over shout. |
| keynote | tech-premium | product / launch / founder updates | Inter 800, opaque white, line-wipe reveals, hero wipes UP. Stillness = confidence. |
| documentary | formal | interviews, serious subject matter | Inter, bone, burn-in reveals, no hero. Gravitas IS the style. |
| loud | loud | hype / sport / music / social | Anton, scene-sampled accent hero, single-unit slam + caption-layer ripple; body announces in front (bodyLayer: "fg" — k-pop depth, deliberate). |
| neon | loud-cyber | cyberpunk / nightlife / tech-noir (dark scenes) | Orbitron + electric cyan; words flicker-ignite like tubes; the hero powers ON with a strobe and hums (glow 0.5). |
| glitch | loud-cyber | digital / hacker / AI / dystopia | Space Grotesk; RGB-split echo layers converge as each beat lands; 0.10–0.12s machine percussion; landing bumps co-visible captions 2px. |
| chrome | loud-luxe | Y2K / fashion-tech / music | Audiowide cast in a liquid-metal gradient (background-clip:text on the word spans); one sheen sweep crosses the hero during the hold. |
| velocity | loud-sport | sport / automotive / fitness | Teko italic; every word arrives along its motion vector (streak + skew settling upright); the hero passes through with blurred speed-trail echoes and looms while held. |
fx fields (hero block) the engine understands
entrance: emergence | settle | slam | rise | wipe-up | flicker-on | streak · glow (0–0.5) · echoes: [{dx,dy,color,opacity,blur}] (converge-on-land duplicates) · sheen: true (gradient sweep — pair with wordCss background-clip) · wordCss (per-word treatment; required for background-clip:text, which can't clip through composited children) · ripple (px — landing bump, captions only) · loom / breathe (hold-life) · letterBlur · bodyLayer (top-level: default layer for narration lines).
How to pick (agent)
1. Read safe-zones.json → heroAnchor.bandLuma + palette.temperature. 2. Bright band (>150) → ink (never fight a bright scene with cream/screen). 3. Else pick by content register: poetic/warm → cream · introspective/luxe → editorial · product/tech → keynote · serious/interview → documentary · hype/social → loud. 4. State the pick + why in one line; the user decides (SKILL.md Step 0 still applies).
Authoring
- Cinematic mode:
cinematic.json→"dna": "<name>"(drop thetemplatefield). - Locked per DNA: family, palette scheme, blend, motion curves, hero orchestration.
- Open per group (unchanged): size / weight / style / case / spacing + planes.
var(--accent)is available in per-group CSS — it resolves to the scene-sampled accent.
What the engine generates from the DNA (no authoring needed)
- Hero three-act: co-visible captions dim 0.35s before the hero lands (setup) →
entrance with per-letter stagger, amplitude ∝ spoken loudness (impact) → slow breathe
- glow until exit (afterglow).
- Scene optics: depth-match blur on embed captions, light-direction contact shadow.
- All deterministic — measured from files, no randomness.
Climax selection — what earns the promotion
Pick the payoff, not the topic: the words you would quote when paraphrasing the beat. "The stars were only limited by the pixel size" — the payoff is pixel size (the surprising claim), not "stars" (the subject). Test: would the word alone, big on screen, make the listener nod? A topic word makes them wait.
- Phrases are legal (
match: "pixel size"/"skin food"): a peak is a semantic
unit, not necessarily one token. 2–4 words max — beyond that it's a sentence, not a peak.
- Prefer clause-final — nothing left dangling to place after the lift.
- Never strand a determiner: lifting "stars" out of "The stars…" leaves an orphan
"The" that vanishes mid-air. The compiler auto-absorbs a line-leading article into the promoted phrase; mid-line, match the phrase including its article.
- Sizes are TARGETED by the compiler, not just floored — author sizes are hints:
- apex = frame event: under 88% of usable width → raised toward a 93% fill.
Std height cap = sizeRange[1] × 1.25 (≤46cqh; formal register ×1.0) — a std apex bursts a rail, not a body composition. Cinematic cap = sizeRange[1].
- short words fill with TRACKING: when the height cap binds before the width
target (SHINE — 5 glyphs), the compiler letterspaces up to +0.32em until the word owns ~88% of the width (film-title craft: HER / DUNE). Long words fill by scale, short words by air — impact is WIDTH-led either way.
- minor = damped beat in the apex family:
max(3× rail, 0.55× apex-final),
width-capped. A peak that reads as a label is a bug.
- lockup context is RATIO-LOCKED: kicker/tail =
0.26× hero(clamped
0.05–0.085·h), recomputed after any width-fit raise. Don't hand-tune context sizes — size the hero; the context follows like a poster system.
Climax placement — the lockup is the default, NOT the only composition
A hero line takes "placement": "subject" | "column":
- subject (default) — the ORBIT lockup centered ON the subject. NOT a centered
sandwich: context anchors to the hero's EDGES like a poster kicker/tagline — pre-context flush-left off the hero's top-left corner, post-context flush-right off its bottom-right — so the small lines land BESIDE the head/shoulder (on the scene), never on the face, and therefore EMBED (bg) like everything else. The eye reads a diagonal: kicker ↘ HERO ↘ tail. Only the hero crosses the silhouette (the matte-occlusion showcase). The context's job is reading continuity — it gets the margins; the hero's job is the cinematic event — it gets the subject. Right when the peak is THE dramatic moment: cream / loud / glitch / neon / velocity lean here.
- column — the apex stays IN its column's reading flow and BURSTS it (oversized,
full hero motion + dim/glow privileges, but composed within the sentence's home zone; spills toward the subject naturally when oversized). Right when the content is a continuous read and the column is the narrative's home: keynotes, tutorials, explainers, documentary. Registers: keynote / documentary / editorial lean here.
Applying ONE composition to every peak is monoculture — the same failure the motion review caught. Pick per beat: a 15s explainer might run column peaks throughout and save the subject lockup for the single apex. (A third pattern — the editorial split, bg context upper + fg hero lower-third — lives in references/composition-craft.md and is the next placement candidate.)
The motion language (five layers, each DNA answers all five)
A DNA's animation is not a parameter tweak on a shared fade — it's a distinct physics:
| Layer | What it governs | Where it lives |
|---|---|---|
| word entrance | how each word materializes on its beat ({y,x,blur,scale,rot,dur,ease} or burn) | motion.soft/present/impact |
| line exit | direction + speed of leaving, optional top-to-bottom cascade | motion.exit ({dur,y,x,scale,ease,stagger}) |
| hold-life | what text does while on screen — stillness is a deliberate choice | hero.breathe / hero.loom (0 = print-still) |
| hero orchestration | the three-act entrance signature (emergence / settle / slam / rise / wipe-up) + glow / letterBlur / ripple | hero.* |
| timing physics | the duration & easing register everything obeys (percussive 0.15s ↔ atmospheric 0.6s) | implied by the values above — keep one family per DNA |
Shipped signatures: cream = _light condenses_ (blur-settle, drift-up exits) · ink = _letterpress_ (scale-press, no float, dead-still) · editorial = _the pen glides_ (x-glide with the italic, cascade-out page turns) · keynote = _surgical reveal_ (burn words + line wipe + hero wipe-up, exits never move) · documentary = _burn and hold_ (motion's absence IS the language) · loud = _percussion_ (back-eased punches, alternating tilt, hero ripple that bumps every caption — never the footage).
Rules inherited from hyperframes motion-principles: exits faster than entrances; vary ease families BETWEEN DNAs (not within one); .out enters, .in leaves; never two transform tweens on one element in the same window (wipe-up is clip-path so the afterglow scale channel stays free; loom subsumes breathe).
Adding a DNA
Copy an existing dna/<name>.json, change the voice, keep the schema. The engine (modes/cinematic/engine.html) and both compilers consume it as-is. A DNA earns its place by being a _distinct voice with a reason to exist_ — not a recolor.
{
"name": "velocity",
"title": "Velocity — speed lines",
"register": "loud-sport",
"when": "sport, automotive, fitness, anything kinetic. Every word arrives ALONG its own motion vector (streak + skew settles upright); the hero passes through with speed trails behind it. Condensed italic forward lean.",
"font": {
"family": "Teko",
"weights": [400, 700]
},
"palette": {
"scheme": "light-on-dark",
"cap_color": "#ffffff",
"blend": "normal",
"accent": "scene",
"accent_fallback": "#ffd600",
"heroColor": "accent",
"accentMode": "counter"
},
"optics": {
"depthMatch": false,
"contactShadow": true,
"warmBloom": false
},
"filter": "none",
"motion": {
"soft": {
"x": -22,
"skewX": -8,
"stretchX": 1.3,
"dur": 0.22,
"ease": "expo.out"
},
"present": {
"x": -26,
"skewX": -10,
"stretchX": 1.35,
"dur": 0.2,
"ease": "expo.out"
},
"impact": {
"x": -32,
"skewX": -12,
"stretchX": 1.45,
"dur": 0.18,
"ease": "expo.out"
},
"exit": {
"dur": 0.16,
"x": 14,
"ease": "power3.in",
"stagger": 0.04
}
},
"hero": {
"entrance": "streak",
"perLetter": false,
"letterStagger": 0,
"glow": 0,
"dimOthers": 0.5,
"breathe": 0,
"loom": 0.025,
"case": "uppercase",
"tracking": "0.015em",
"sizeRange": [0.24, 0.38],
"css": "font-weight: 700; font-style: italic;",
"echoes": [
{
"dx": -10,
"dy": 0,
"color": "#ffffff",
"opacity": 0.22,
"blur": 1
},
{
"dx": -20,
"dy": 0,
"color": "#ffffff",
"opacity": 0.1,
"blur": 2
}
]
},
"css": "",
"deliveries": {
"home": "column",
"rail": "unvalidated",
"note": "signature = streak + trail echoes along the speed vector (engine fx) — rail loses all of it"
}
}
Template: champion
Look: podcast / interview, upper-side column with intro→phrase→emph beats building toward a center-stage CROWN that crosses the subject's body. "Wimbledon Champion" energy.
Visual identity (LOCKED):
- 5 typography slots: intro / phrase / emph / dream / crown
mix-blend-mode: screenover warm bone color (#fff5df)- Deep text-shadow + brightness boost
- Single-caption-swap layout (caps stack at one point in the column)
- Center-stage crown crosses the subject for cinematic occlusion
When to apply
✅ Good fit:
- Podcast / interview style with a single seated subject
- Cluttered backdrop (bookshelf, props, dark) — screen blend reads through
- Subject roughly centered with clear clean zones on one or both sides
- Speech has a clear payoff line that can become a "crown" (1-3 short words)
- 16:9 landscape, 1920×1080 ideal
❌ Wrong fit:
- Bright/sky/window backdrop (screen blend washes out)
- Subject heavily off-center such that there's no clean column zone
- Speech has no clear climax line
- Portrait 9:16 → use
portrait-headertemplate instead
Layout decisions agent makes
| Field | What | Example (1920×1080) |
|---|---|---|
plane.top/left/width/height | Where the side column sits. Pick the side opposite the subject's gaze (looking room) and hug the cleanest backdrop zone. | {top: 40, left: 40, width: 720, height: 420} |
plane.rotateY | Slight perspective into the wall. Positive for left column (recedes right), negative for right column. Keep small (3-7°). | 4 |
crown_top | Y position of the crown. Should fall on the subject's chest/neck so the body occludes the middle letters. | 420 |
font_scale | Multiplier on locked sizes. | 1.0 for 1920×1080 |
crown_enabled | Whether to render a crown. Skip if the speech has no clean payoff line, or if the subject is too far off-center for a center crown to land. | true |
Crown placement rules
Before enabling the crown, verify all 3 conditions:
1. Subject body center within ±10% of frame horizontal center 2. Clean zones ≥15% of frame width on each side of subject 3. Crown text width (uppercase 140px × ~chars) > subject body width + 400px
If any fails: disable the crown OR use a smaller font + place it in the larger clean zone instead of center. See references/layout-heuristics.md.
Slot assignment
| Slot | Use for |
|---|---|
| intro | Filler / discourse markers ("you know,", "for me,", "so…") |
| phrase | Main statement clauses |
| emph | Key achievement / superlative line |
| dream | Aspirational lines, past-tense reflection |
| crown | The single climax line — center-stage payoff |
Aim for ≤3 slots per video (excluding crown). Don't use all 4 in a 12s clip.
Plan.json shape
{
"mode": "template",
"template": "champion",
"duration": 12.08, "fps": 24, "width": 1920, "height": 1080,
"plane": {
"top": 40, "left": 40, "width": 720, "height": 420,
"rotateY": 4
},
"crown_top": 420,
"font_scale": 1.0,
"groups": [
{ "id": "cg-0", "slot": "intro", "tone": "soft",
"in": 0.10, "out": 1.45, "words": [...] },
{ "id": "cg-1", "slot": "phrase", "tone": "soft",
"in": 1.55, "out": 4.20, "words": [...] },
{ "id": "cg-2", "slot": "emph", "tone": "present",
"in": 6.50, "out": 8.30, "words": [...] }
],
"crown_group": {
"id": "cg-crown", "tone": "present",
"in": 10.85, "out": 12.08,
"words": [
{"text": "Wimbledon", "start": 10.94, "end": 11.28},
{"text": "Champion", "start": 11.38, "end": 11.84}
]
}
}crown_group always uses the crown slot — no need to specify slot. Set crown_group: null to disable.
<!doctype html>
<!--
TEMPLATE: champion
STYLE LOCKED. LAYOUT OPEN.
Look: podcast / interview, upper-left column with intro→phrase→emph
beats, big center-stage CROWN crossing the subject for the payoff.
Locked:
- 5 typography slots: intro / phrase / emph / dream / crown
- mix-blend-mode: screen, color #fff5df, deep text-shadow
- Single-caption-swap layout (caps absolute-positioned in left-plane)
- GSAP soft/present per-word reveal
Open via plan.json:
- left-plane geometry + perspective
- crown_plane top
- --font-scale
- GROUPS array + slot per group
- whether crown is enabled (crown_group)
-->
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width={{WIDTH}}, height={{HEIGHT}}" />
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
:root { --font-scale: {{FONT_SCALE}}; }
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
margin: 0;
width: {{WIDTH}}px; height: {{HEIGHT}}px;
overflow: hidden;
background: #000;
font-family: "Inter", system-ui, sans-serif;
}
#a-roll { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
#stage {
position: absolute;
top: 0; left: 0;
width: {{WIDTH}}px; height: {{HEIGHT}}px;
perspective: 2400px;
perspective-origin: 25% 50%;
pointer-events: none;
z-index: 2;
}
/* LAYOUT */
.left-plane {
position: absolute;
top: {{PLANE_TOP}}px;
left: {{PLANE_LEFT}}px;
width: {{PLANE_WIDTH}}px;
height: {{PLANE_HEIGHT}}px;
transform-style: preserve-3d;
transform: rotateY({{ROTATE_Y}}deg);
transform-origin: left center;
padding: 24px 36px;
}
.left-plane .cap {
position: absolute;
top: 24px;
right: 36px;
max-width: calc({{PLANE_WIDTH}}px - 72px);
}
.crown-plane {
position: absolute;
top: {{CROWN_TOP}}px;
left: {{CROWN_LEFT}}px;
right: {{CROWN_RIGHT}}px;
text-align: {{CROWN_ALIGN}};
pointer-events: none;
}
.crown-plane .cap-crown {
font-size: calc(140px * var(--font-scale) * {{CROWN_SCALE}});
}
/* STYLE (locked) */
.cap {
display: block;
position: relative;
line-height: 1.02;
opacity: 0;
mix-blend-mode: {{BLEND_MODE}};
color: {{CAP_COLOR}};
text-shadow: {{TEXT_SHADOW}};
filter: {{TEXT_FILTER}};
will-change: transform, opacity;
}
.cap .w { display: inline-block; opacity: 0; will-change: opacity, transform; }
.cap-intro { font-size: calc(52px * var(--font-scale));
font-weight: 500; font-style: italic;
letter-spacing: -0.005em; }
.cap-phrase { font-size: calc(60px * var(--font-scale));
font-weight: 600;
letter-spacing: -0.015em; }
.cap-emph { font-size: calc(70px * var(--font-scale));
font-weight: 800;
letter-spacing: -0.025em; }
.cap-dream { font-size: calc(64px * var(--font-scale));
font-weight: 700; font-style: italic;
letter-spacing: -0.015em; }
.cap-crown { font-size: calc(140px * var(--font-scale));
font-weight: 900;
letter-spacing: -0.035em; text-transform: uppercase;
line-height: 1.0; }
</style>
</head>
<body>
<div
id="root"
data-composition-id="main"
data-start="0"
data-duration="{{DURATION}}"
data-width="{{WIDTH}}"
data-height="{{HEIGHT}}"
>
<video
id="a-roll"
src="source.mp4"
muted
playsinline
data-duration="{{DURATION}}"
data-track-index="0"
style="z-index: 1"
></video>
<div id="stage">
<div class="left-plane">{{GROUPS_HTML}}</div>
<div class="crown-plane">{{CROWN_HTML}}</div>
</div>
<audio
id="a-roll-audio"
src="source.mp4"
data-start="0"
data-duration="{{DURATION}}"
data-track-index="3"
data-volume="1"
></audio>
</div>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
const DUR = {{DURATION}};
const GROUPS = {{GROUPS_JSON}};
const CROWN = {{CROWN_JSON}};
function animateGroup(g) {
var sel = "#" + g.id;
var isSoft = g.tone === "soft";
tl.set(sel, { opacity: 1, y: 0 }, Math.max(0, g.in - 0.01));
g.words.forEach(function (w, i) {
var wsel = sel + " .w[data-i='" + i + "']";
if (isSoft) {
tl.fromTo(wsel,
{ opacity: 0, y: 8 },
{ opacity: 1, y: 0, duration: 0.42, ease: "power2.out", overwrite: "auto" },
w.start);
} else {
tl.fromTo(wsel,
{ opacity: 0, y: 6, scale: 1.04 },
{ opacity: 1, y: 0, scale: 1.0, duration: 0.22,
ease: "power3.out", overwrite: "auto", transformOrigin: "50% 50%" },
w.start);
}
});
if (g.out < DUR) {
tl.to(sel, { opacity: 0, duration: 0.45, ease: "power2.in", overwrite: "auto" },
g.out - 0.45);
}
tl.set(sel, { opacity: 0, visibility: "hidden" }, g.out);
}
GROUPS.forEach(animateGroup);
if (CROWN) animateGroup(CROWN);
tl.seek(0);
window.__timelines["main"] = tl;
</script>
</body>
</html>
Template: memory-wall
Look: introspective monologue, italic poem stacking on a back wall, right-aligned cascade for the climax. Sam Harris / Pico Iyer territory.
Visual identity (LOCKED — do not change):
- 4-slot typography arc: italic 600 → italic 500 hanging-indent → upright
700 pivot → uppercase 900 climax
mix-blend-mode: screenover warm-bone color (#fff4dc)- soft text-shadow + brightness boost
- Per-word fade with subtle Y-drift (soft tone) or pop+scale (present tone)
When to apply
✅ Good fit:
- Talking-head with a flat back wall (acoustic foam, plain plaster, soft
bookshelf) on one side
- Single subject, fixed or near-fixed camera
- Voice-over feel: introspective, narrative, "I was thinking…"
- Mid-tone or dark backdrop (luminance < 180)
❌ Wrong fit:
- Bright window/sky backdrop → screen blend blows out
- Cluttered backdrop with text/graphics → conflicts with caption text
- Energetic / vlog / TikTok style → wrong vibe
- Multiple speakers / hard cuts → caption arc breaks
What agent decides per scene
| Field | What | Example |
|---|---|---|
plane.top/right/width/height | Where the wall plane sits in frame. Plane should HUG the side of the wall, not float over the subject. | {top: 90, right: 30, width: 720, height: 520} for 1280×720 |
plane.rotateY | Match the wall's perspective angle. Negative for right-side wall (text recedes left), positive for left-side wall. | -13 for a right-side wall at slight angle |
plane.rotateX | Tiny vertical tilt if wall is angled up/down. Usually 0-2 deg. | 1 |
font_scale | Multiplier on the locked cap-N base sizes. Tune for frame height. | 1.0 for 1280×720, ~1.5 for 1920×1080, ~0.7 for 720p portrait |
groups[].slot | Which of the 4 typography slots this group uses (1/2/3/4). Drives the arc. | See below |
groups[].tone | "soft" or "present" — drives motion curve | "soft" for opening lines, "present" for pivot+climax |
Slot assignment rules
Aim for ONE arc across the video: 1 → 2 → 3 → 4. Don't repeat slots if you can avoid it; don't skip slots without reason.
| Slot | Use for |
|---|---|
| 1 | The opener. The "I was thinking" line. Italic, soft entry. |
| 2 | The dreamy continuation / modifier. Smaller italic, hanging-indent — feels like the second half of a sentence with some staggered drift. |
| 3 | The pivot. Upright (no italic), the syntactic turn. ("but suddenly", "and then…") |
| 4 | The climax. Big uppercase 900. The payoff line. Aim for ≤4 short words so the right-aligned cascade naturally wraps to 2-3 lines. |
If the script is longer than 4 groups, repeat slot 1 or 2 for additional soft lines, but reserve slot 4 for the SINGLE climax.
Plan.json shape
{
"mode": "template",
"template": "memory-wall",
"duration": 8.04,
"fps": 24,
"width": 1280,
"height": 720,
"plane": {
"top": 90,
"right": 30,
"width": 720,
"height": 520,
"rotateY": -13,
"rotateX": 1
},
"font_scale": 1.0,
"groups": [
{
"id": "cg-0",
"slot": 1,
"tone": "soft",
"in": 0.2,
"out": 4.85,
"words": [
{ "text": "Some", "start": 0.24, "end": 0.44 },
{ "text": "memories", "start": 0.48, "end": 0.82 },
{ "text": "feel", "start": 0.92, "end": 1.14 },
{ "text": "soft", "start": 1.2, "end": 1.64 }
]
},
{
"id": "cg-1",
"slot": 2,
"tone": "soft",
"in": 2.55,
"out": 4.85,
"words": [
{ "text": "like", "start": 2.66, "end": 2.78 },
{ "text": "old", "start": 2.86, "end": 3.02 },
{ "text": "film", "start": 3.16, "end": 3.46 }
]
},
{
"id": "cg-2",
"slot": 3,
"tone": "present",
"in": 4.9,
"out": 8.04,
"words": [
{ "text": "but", "start": 5.04, "end": 5.18 },
{ "text": "suddenly", "start": 5.24, "end": 5.76 }
]
},
{
"id": "cg-3",
"slot": 4,
"tone": "present",
"in": 6.7,
"out": 8.04,
"words": [
{ "text": "everything", "start": 6.82, "end": 7.14 },
{ "text": "is", "start": 7.18, "end": 7.28 },
{ "text": "sharp", "start": 7.32, "end": 7.52 },
{ "text": "again", "start": 7.58, "end": 7.92 }
]
}
]
}Don't fight the template
If the user wants different typography, different blend, or different motion: use Standard mode (../../standard/). Don't try to override the locked CSS — the template's visual consistency is the whole point.
<!doctype html>
<!--
TEMPLATE: memory-wall
STYLE LOCKED. LAYOUT OPEN.
Locked (do not edit — overriding breaks visual consistency):
- cap-1/2/3/4 typography (weights, italic, letter-spacing, padding-right)
- mix-blend-mode: screen
- color, text-shadow, filter
- GSAP motion: container fade-in via tl.set, per-word opacity+y reveal,
group fade-out 0.45s before g.out
Open (agent fills via plan.json):
- {{PLANE_TOP}} {{PLANE_RIGHT}} {{PLANE_WIDTH}} {{PLANE_HEIGHT}}
- {{ROTATE_Y}} {{ROTATE_X}} (perspective angle on the wall)
- --font-scale CSS var (multiplies all cap-N sizes — agent tunes for frame)
- GROUPS array (transcript groups, slot assignment 1/2/3/4, in/out/words)
Style typography is keyed to "slots", semantic positions in the arc:
cap-1: opener (italic, soft entry)
cap-2: continuation (smaller italic, hanging indent — ragged poem feel)
cap-3: pivot (upright, no italic, syntactic turn)
cap-4: climax (UPPERCASE 900, the payoff line)
-->
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width={{WIDTH}}, height={{HEIGHT}}" />
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
:root { --font-scale: {{FONT_SCALE}}; }
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
margin: 0;
width: {{WIDTH}}px; height: {{HEIGHT}}px;
overflow: hidden;
background: #000;
font-family: "Inter", system-ui, sans-serif;
}
#a-roll {
position: absolute; inset: 0;
width: 100%; height: 100%;
object-fit: cover;
}
#stage {
position: absolute;
top: 0; left: 0;
width: {{WIDTH}}px; height: {{HEIGHT}}px;
perspective: 1400px;
perspective-origin: 30% 40%;
pointer-events: none;
z-index: 2;
}
/* LAYOUT (agent-tunable via plan.json) */
.wall-plane {
position: absolute;
top: {{PLANE_TOP}}px;
right: {{PLANE_RIGHT}}px;
width: {{PLANE_WIDTH}}px;
height: {{PLANE_HEIGHT}}px;
transform-style: preserve-3d;
transform: rotateY({{ROTATE_Y}}deg) rotateX({{ROTATE_X}}deg);
transform-origin: right center;
padding: 24px 32px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-end;
text-align: right;
gap: 14px;
}
/* STYLE (locked) */
.cap {
display: block;
position: relative;
line-height: 1.05;
opacity: 0;
mix-blend-mode: {{BLEND_MODE}};
color: {{CAP_COLOR}};
text-shadow: {{TEXT_SHADOW}};
filter: {{TEXT_FILTER}};
will-change: transform, opacity;
}
.cap .w { display: inline-block; opacity: 0; will-change: opacity, transform; }
.cap-1 { font-size: calc(78px * var(--font-scale));
font-weight: 600; font-style: italic;
letter-spacing: -0.01em; }
.cap-2 { font-size: calc(66px * var(--font-scale));
font-weight: 500; font-style: italic;
letter-spacing: -0.005em;
padding-right: calc(44px * var(--font-scale)); }
.cap-3 { font-size: calc(72px * var(--font-scale));
font-weight: 700;
letter-spacing: -0.015em; }
.cap-4 { font-size: calc(90px * var(--font-scale));
font-weight: 900;
letter-spacing: -0.03em;
text-transform: uppercase;
line-height: 1.0; }
</style>
</head>
<body>
<div
id="root"
data-composition-id="main"
data-start="0"
data-duration="{{DURATION}}"
data-width="{{WIDTH}}"
data-height="{{HEIGHT}}"
>
<video
id="a-roll"
src="source.mp4"
muted
playsinline
data-duration="{{DURATION}}"
data-track-index="0"
style="z-index: 1"
></video>
<div id="stage">
<div class="wall-plane">{{GROUPS_HTML}}</div>
</div>
<audio
id="a-roll-audio"
src="source.mp4"
data-start="0"
data-duration="{{DURATION}}"
data-track-index="3"
data-volume="1"
></audio>
</div>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
const DUR = {{DURATION}};
const GROUPS = {{GROUPS_JSON}};
GROUPS.forEach(function (g) {
var sel = "#" + g.id;
var isSoft = g.tone === "soft";
tl.set(sel, { opacity: 1, y: 0 }, Math.max(0, g.in - 0.01));
g.words.forEach(function (w, i) {
var wsel = sel + " .w[data-i='" + i + "']";
if (isSoft) {
tl.fromTo(wsel,
{ opacity: 0, y: 8 },
{ opacity: 1, y: 0, duration: 0.45, ease: "power2.out", overwrite: "auto" },
w.start);
} else {
tl.fromTo(wsel,
{ opacity: 0, y: 6, scale: 1.04 },
{ opacity: 1, y: 0, scale: 1.0, duration: 0.22,
ease: "power3.out", overwrite: "auto", transformOrigin: "100% 50%" },
w.start);
}
});
if (g.out < DUR) {
tl.to(sel, { opacity: 0, duration: 0.5, ease: "power2.in", overwrite: "auto" },
g.out - 0.5);
}
tl.set(sel, { opacity: 0, visibility: "hidden" }, g.out);
});
tl.seek(0);
window.__timelines["main"] = tl;
</script>
</body>
</html>
cinematic-cream — spec
The authoritative spec lives in template.html's header comment (DNA-only: what's LOCKED vs OPEN, the LAYOUT/planes contract, READING ORDER + hand-off, hero sizing). Read that header before authoring plan.json. Quick contract:
- LOCKED: Inter, soft/present motion, warm-cream palette, screen blend, shadow logic.
- OPEN per group (css field): size/weight/style/transform/spacing + position WITHIN a plane.
- LAYOUT: run scripts/safe-zones.cjs first. Narration planes HUG the silhouette — use
zones.hugLeft/hugRight (2-6% gap from the subject's edge); a plane parked in the far corner reads as disconnected floating text, not an embed. The ONE hero stays BIG (~0.22-0.34·h) centered ON the subject (heroAnchor / heroBands.best), target ~30-55% occlusion. fg is the LAST resort: only when heroBands.feasible=false (no band ≤62%). hero BIG (~0.22-0.34·h) centered ON the subject (heroAnchor), target ~30-55% occlusion.
- plan.json: groups[] with per-word transcript timings (use scripts/fill-timings.cjs),
fit via scripts/fit-fonts.cjs, compile via scripts/make-composition.cjs.
{
"name": "lastpage",
"voice": "the speaker reads from a manuscript that already exists \u2014 a blurred future-transcript haunts the room all video; one rack-focus reveals every faint line was the apex word. The future was only ever one sentence.",
"when": "visionary thesis, prophecy-toned founder talks, trailers, literary content. Mid/dark scenes (the field needs air).",
"register": "literary-prophetic",
"fonts": {
"body": "Cormorant Garamond"
},
"palette": {
"body": "#F0E9D8"
},
"body": {
"paradigm": "lastpage",
"layer": "bg",
"fontPx": 44,
"bottomPx": 96,
"fieldCount": 9,
"fieldBlur": 9,
"seed": 4242
},
"hero": {
"setpiece": "rackfocus",
"inline": true,
"params": {},
"maxHold": 0
},
"fx": {},
"plate": {
"pushIn": 0.012,
"grain": 6
},
"linkages": ["field-is-the-apex"]
}
Related skills
How it compares
Use embedded-captions only when presenter footage already exists; use faceless-explainer or product-launch-video to generate full videos from text or URLs.
FAQ
How many caption styles does embedded-captions include?
embedded-captions ships a catalog of 32 visual identities, spanning column-flow compositing and themed constitutions like terminal, neonsign, vhs, and nightcity.
How are embedded-captions different from plain subtitles?
embedded-captions composites text into the scene using matte occlusion and mix-blend modes so captions feel part of the footage, not a floating subtitle track.
Is Embedded Captions safe to install?
skills.sh reports 1 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.