Remotion Talking-Head Packaging Design System (v1 hub)
Style target: Replicate the talking-head HUD packaging of the benchmark video (reference images in project refs/). This file is the hard-constraint hub: ironclad rules + tokens semantics + component quick-reference + mistake archive; detailed rules are in references/ (the settlement landing point for the 77-gap deep comparison of 2026-07-16). Any visual decision not documented in this system must first be added to the corresponding section file before writing any code.
Section Routing Table (which file to read for what)
| Section | File | Scope |
|---|---|---|
| Spec Sheet (check before writing) | references/specs.md | True source of measured values from benchmark: card width/font size/position/motion frame counts – verify before assembly; if not in table, measure and backfill; no eyeballing |
| Layout & Composition | references/layout.md | Three zones, takeover mechanism (A-level without PiP, darken/slow-motion rejected), one center-of-gravity per screen, grouping templates, bite positioning |
| Typography | references/typography.md | Single largest font, h1 duty, hero sandwich, SideLabel title variant |
| Color & Texture | references/texture.md | Jelly block/glow, SURFACE light/dark variants, stamp specification single line, ≤2 color systems per screen, no naked text on bright surfaces |
| Motion | references/motion.md | useEnter/useExit, beat-by-beat entry, build-up, service interval, Breathe, clean screen |
| Information Design | references/infographic.md | Chart selections, metaphorical graphics, waffle, big number three layers, micrographic vocabulary |
| Narrative/Rhythm/Subtitles/Broll/Audio | remotion-assembly's references/ | narrative / rhythm / subtitle / broll / audio |
| Mistake Index (symptom → repair reference) | independent skill remotion-gotchas-index | Look up the repair location of any "Mistake #NN" mentioned in the full text; check this first for rendering anomalies or visual issues |
Engineering Substrate Checklist (substrate — confirm in place before cold-starting / starting a new episode)
This design system is a shared substrate for the entire repository; it is not rebuilt per episode. The following files are prerequisites for the entire pipeline (assembly "input validation → frame output → three gates" + motion §12 gating); a new episode reuses them by default, only adding its own composition + subtitles + Scene, without rebuilding the substrate. Cold-start readers (forbidden from reading src/) often stumble here — first confirm the files exist using this table; to get specific values, open the corresponding file and read it; do not expect the skill to copy them for you.
| Substrate File | What It Contains | How to Use for Cold Start |
|---|---|---|
src/design/tokens.ts | Single source of truth for values: COLOR (4 semantic + base hex), SURFACE, FONT, GRADIENT, SIZE ladder (kicker…mega), GRID, RADIUS, SAFE, MOTION frame counts | For SIZE.mega, semantic color hex etc., read this file directly — the skill deliberately does not copy them (to prevent snapshot/code drift, see §1); if file missing = substrate missing, build substrate first |
src/design/*.tsx | Component library (§3 full table: SideLabel/HeroText/Chip…) + FontGuard.tsx (font gatekeeper, Mistake #01) + qc.tsx (QC probe QcProbe: per-frame check of [data-qc] boxes for overlap, Mistake #29) | Before writing packaging elements, check §3 component table to locate the file |
src/design/motion.ts | useEnter/useExit/usePop low-level hooks + MOTION constants | The hooks referenced by motion.md rules are all here |
src/design/fonts.ts | @fontsource local font registration (Mistake #01: webfont via network silently falls back to Songti) | package.json sideEffects must include it |
src/koubo/scenes/shared.tsx | Assembly layer gatekeeper quadruple Enter/DimAfter/Exit/ClearOut (motion §12) + Place positioning box + qcAllow QC bite whitelist (assembly §2.5 gate ①) | When writing Scene assembly, directly wrap these wrappers; do not hand-roll opacity |
src/Root.tsx | Composition registration (existing <Composition id="Koubo-0715-01" fps={30} …/>) | Add one <Composition> here for the new episode, id is arbitrary; the segment-render script must target this id (see assembly §6 render-service for changing id) |
src/koubo/ | Per-episode files: subtitles-<episode-name>.ts / SubtitleTrack.tsx / <episode-main-component>.tsx / scenes/SceneNN.tsx | For a new episode, only add files in this layer (structure in assembly §4) |
True from-zero bootstrap (src/ completely empty): Until the substrate is in place, the assembly pipeline cannot start (no tokens available, no first frame, no three gates). Order = build substrate first, then talk about assembly: ① tokens.ts set value source → ② fonts.ts + FontGuard.tsx (Mistake #01) → ③ motion.ts three hooks → ④ §3 component library → ⑤ src/koubo/scenes/shared.tsx gatekeeper quadruple + Place/qcAllow → ⑥ src/design/qc.tsx QC probe → ⑦ src/Root.tsx register composition. Use the src/design in this repo as a reference implementation; after porting, values still read from tokens.ts, do not backfill into the skill.
0. Six Ironclad Rules
- Information area defaults to left, person on right: The information area defaults to the left (author defaults determined by user), HUD anchored to the left side and grows vertically, person area is forbidden; but ask the user about composition for each episode; do not assume. Three-zone coordinates and takeover exceptions are in references/layout.md rule 1.
- All text is two-layered: English UPPERCASE wide-letter-spacing kicker (small) + Chinese bold black (large). A component with only one layer of text is considered incomplete.
- Highlight only one word per sentence: In Chinese titles, emphasize/color-change at most one word; the color must come from the four-color semantic system.
- Real footage is primary, Remotion is a compositor, not a drawing board (2026-07-16 seg01 ruling upgrade): All "product experience" type visuals (typing/generation/interface/demonstration) must use actual footage editing — self-recording screen > official video > web screenshot > UI mockup card; hand-drawn is only for the HUD layer (side labels/subtitles/charts/stamps/metaphorical graphics). Using CSS to hand-craft what professional motion teams do with compositors will always fail; the root cause of "it almost looks right but not quite/far off" is using a drawing board to fight a compositor's battle. Capture and editing pipeline is in assembly references/broll.md.
- No naked text on bright surfaces (2026-07-16 deep comparison ruling, the top cross-section rule spanning 6 sections): On bright backgrounds (whiteboard/white wall/light takeover background), no text without a background — all text must sit on a dark background plate (SURFACE.darkPlate), card body, or light surface variant;
textShadowis not a solution. Detailed rules are in references/texture.md. - One dominant layer per screen: Each information screen has exactly one "largest font/largest element"; outside the main area, at most 2 chip-level small elements. Detailed rules are in references/typography.md and layout.md.
1. Design Tokens
Single source of truth = src/design/tokens.ts; all components are only allowed to import values from there. This section only documents semantics and usage rules, not values — there was a snapshot/code drift incident before (2026-07-16, multiple mismatches found including kicker/h1, etc.), code is the authority.
- Four-color semantics (the only allowed accent colors): blue = definition/method/neutral progression; green = positive/low threshold/activated; yellow = opportunity/warning/controversy/turn; red = negative/trap/crisis. Base colors include white/grey/greyDim/cardBg/cardStroke.
- Fonts (two layers, do not mix): MG packaging layer —
FONT.zh= Noto Sans SC (only Chinese font for packaging layer; titles should always useFONT.zhHeavy);FONT.en= Inter (kicker/body text/numbers);FONT.enTitle= Archivo Black (English titles and large conclusions; narrow super-black, weight always 400 — Inter cannot carry large text, 2026-07 glyph comparison conclusion). Bottom subtitle layer —FONT.subZh= PingFang SC 400 /FONT.subEn= Inter 300 (after 2026-07-26 five-option real-shot comparison, user ruling): subtitles are a reading-following layer, not a packaging layer; only a thin weight has the quality of Apple subtitles, and it separates itself from the MG bold black titles, not competing for attention; the old version using Noto 700 for the entire line looked like stickers. PingFang SC is a macOS system font, not packaged via@fontsource— rendering works fine on this machine; on Windows/Linux environments, it will fall back to the packaged Noto Sans SC 400 thin variant (already imported in fonts.ts, similar but not identical); before cross-machine rendering, render one frame to confirm subtitle appearance. Subtitle English lines must use proportional Western fonts, see Mistake #42. - Large conclusions must always use solid semantic color + textShadow;
backgroundClip: textgradient text is forbidden (Mistake #06);GRADIENTis only for color block/stroke purposes. - Font sizes only use the
SIZEladder (kicker / subSmall / chip / subZh / subEn / card / h2 / h1 / mega); font sizes outside the ladder are forbidden; 1080p baseline, 4K output scales ×2. - Spacing always multiples of
GRID(8px); border radius usesRADIUS; safe zones useSAFE(side label anchor points, stack left edge, subtitle distance from bottom; permanent cards should not exceed x55%, person forbidden zone/right zone coordinates in layout.md; subtitle forbidden zone y>82%, left-zone element bottom edge ≤y840 is a stricter hard line than y82% for the left zone).
2. Section SideLabel (permanent throughout the episode, highest priority component)
- Structure:
│(4px vertical line, semantic color) +ENGLISH KICKER · Chinese phrase(first line, English in kicker specification + semantic color, Chinese white 34px) + second line gray 24px supplementary description. - Position: Fixed
SAFE.sideLabel; when switching, old label fades out over 12 frames, new label slides in from left over 15 frames. - Naming with numbering system:
SCENE 01,PATH 1/2/3, positive-negative contrast (DISSOLVED·01..vsIMMUNE·01..). - Color = emotional semantics of this segment, see tokens comments. Reference:
refs/01-typography-and-side-labels/.
3. Component Specification Quick Reference (detailed specifications will be added as implementation progresses; first look at reference images, then write)
| Component | Key Specifications | Reference |
|---|---|---|
| BilingualSub | 52 white + black shadow / 28 pure white English, bottom-centered, always on topmost layer | any refs |
| HeroText | kicker + Chinese h1/h2, highlight word in semantic color; variants: red strikethrough correction, yellow brush underline, angled stamp | refs/02 |
| Chip | Black card RADIUS.chip + colored icon + 34px text, one color-change word allowed | refs/03 |
| Checklist | Chip vertical stack, pop in one by one; inactive item opacity 0.35 | refs/03 |
| InfoCard | icon + kicker + 44px title + blue highlight word | refs/01 |
| StepList | ①②③ circle numbers + Chinese + small English kicker, step-by-step illumination | refs/04 |
| FlowChain | Black card + → arrow chain, nodes appear sequentially | refs/04 |
| CompareCard | logo + name + red × weak point chip + green ✓ strong point chip, vertical stack | refs/04 |
| Formula | Large A symbol B (>, », ⇄, =), symbol in semantic color | refs/05 |
| BigNumber | Inter large number + small unit text + kicker; counter uses spring scroll | refs/05 |
| BarChart | Data bar 8px border radius; key bar turns yellow + crown chip on top; rest gray | refs/05 |
| CurveOverlay | 2–3px yellow curve overlaid directly on B-roll, right endpoint annotated | refs/05 |
| Timeline | Two-endpoint interval type / event with red/green result chip | refs/05 |
| ScoreBoard | Green:red large number score comparison | refs/05 |
| PhoneMockup | iPhone frame + gradient stroke glow + embedded screenshot long image scrolling | refs/06 |
| WindowCard | Mac traffic lights + app icon + feature chip, dynamic content | refs/04 |
| QuoteDoc | White background screenshot card + yellow fluorescent highlight + black background Chinese translation bar overlay | refs/07 |
| TweetCard | Avatar + identity kicker + red large conclusion + views chip | refs/07 |
| PersonCard | Round avatar + name + small identity text + institution chip | refs/07 |
| Stamp | Tilt -8°~-6° stroke stamp text (red/green), stamp animation = scale 1.3→1 + slight shake | refs/08 |
| MatrixIcon | Block matrix icon: red = will be dissolved camp / green = immune camp | refs/08 |
| CardWall | White small cards 3→20 gradually fill the screen, simulating demand explosion | refs/06 |
| BeforeAfter | Two icon cards + blue arrow + conclusion chip below | refs/04 |
| Breathe | Idle micro-motion wrapper: breathing scale ±0.6% + floating ±3px, phase offset to prevent sync; wraps the main element | src/design/Breathe.tsx |
| BrickWall | Staggered gold brick horizontal wall built brick by brick + center label, metaphor for "building walls/hard barriers" | src/design/BrickWall.tsx |
| UnitMatrix | N sets of block matrices lighting up cell by cell; fillRatio waffle mode (73% data field) | src/design/UnitMatrix.tsx |
| CloneCascade | Source icon card + fakes sequentially fading in + warning chip, metaphor for "being copied/distilled" | src/design/CloneCascade.tsx |
Batch Scene Inline Components (2026-07-17 batch production additions, not yet promoted to shared components)
The following components currently live only in a single Scene, marked as "Scene inline; when reused a second time, promote to src/design shared component and add to specs table" — when encountering a similar requirement in a new episode, check here first, don't rebuild:
| Inline Component | Location | What It Does | Promotion Condition |
|---|---|---|---|
| DissolveGrid | scenes/Scene08.tsx | Dissolve grid: the camp that will be dissolved, blocks dissolve one by one, center ⚡ background fades out leaving white outline floating | When "camp dissolve" metaphor is reused a second time |
| VerdictBox (judgment box) | scenes/Scene17.tsx | Red-green mirror judgment box: ≈400×170 stroke empty box first appears + header inside box + horizontal row of 3 chips entering the box in batches (red fills first, green later, creating a "which side are you on" suspense) | When "VS final judgment" is upgraded a second time |
| NamePlate + CtaBadge (CTA badge row) | scenes/Scene19.tsx | End credits CTA bottom horizontal row: name plate lands first as anchor → four action badges pop in one by one (comment badge widest at the end = emphasis) | When ending CTA wrap-up is used a second time |
Icon Specifications
- Icons must always use lucide-react linear SVG icons, color = semantic color (
COLOR[accent]),strokeWidth 2.5approximately; emoji are forbidden (colored rounded blocks conflict with the benchmark's monochrome linear style, lesson from 2026-07 third batch acceptance). - The
iconprop of Chip / Checklist / SideLabel accepts ReactNode:icon={<Lock size={26} strokeWidth={2.5} />}.
Texture Specifications → references/texture.md
Jelly block gradients, same-color glow, vignette, real logo, SURFACE light/dark variants, stamp specification single line — all in texture.md (as of 2026-07-16, it is the single source of truth for texture; the old text in this section has been migrated).
4. Motion Rules → references/motion.md
useEnter/useExit, beat-by-beat entry, service interval, Breathe, chapter clean screens, forbidden patterns — all in motion.md (as of 2026-07-16, it is the single source of truth for motion; the old text in this section has been migrated; note that the old rule "old info stays with reduced opacity" has been replaced by "service interval + exit discipline").
5. Pre-export Self-Check (run before every render)
- Grep check for any inline color values/font sizes/fonts in components beyond tokens.
- All permanent cards should not cross faceZone / subtitleZone.
- Each title highlight word ≤ 1; English kicker must be uppercase with letter spacing.
- Capture 3–5 keyframes and compare side-by-side with corresponding reference images in refs/; if they don't match, identify which token is off, change the token, not inline component values.
6. Mistake Archive
#01 Chinese silently reverts to Songti (2026-07-15 · Design System Day 1)
- Symptom: Rendered/screenshotted Chinese text becomes Songti, English remains normal; sometimes the same component is normal, sometimes not (depends on luck).
- Cause: Three layers, the last is the real culprit. ①
@remotion/google-fontspulls fonts from gstatic at runtime; domestic network cannot reach, silent fallback; ② Chinese webfont is split by unicode-range into packages; loading has race conditions with screenshot; ③ Tree-shaking:sideEffects: ["*.css"]inpackage.jsoncausesfonts.ts(which is only imported as a side effect) to be completely removed from the production bundle; the font CSS is not in the rendering bundle at all (document.fonts.size === 0) — Studio dev mode does not tree-shake, so "Studio is fine, render is broken". - Fix: ① Fonts use
@fontsourcelocal packages (src/design/fonts.ts); ②sideEffectsinpackage.jsonmust include"src/design/fonts.ts"; ③ Any composition containing text must have<FontGuard/>: load→check loop verification, and must also checkdocument.fonts.size > 0(fonts.check()returns true vacuously when zero fonts are registered); on timeout, callcancelRenderto report error, never silently output a Songti frame. - Lesson: When "Studio normal, export broken", first suspect production build differences (tree-shaking/minification), not mysterious race conditions; font verification must rely on FontGuard as a hard gate, human eyes cannot be trusted with thumbnails.
#02 remotion still output ○ = reusing old image
- Symptom: After code changes, rerender output looks unchanged; command line shows
○instead of+. - Fix: Before verifying render, delete old output (or render to a new filename); if necessary, add
--bundle-cache=false.+= true render,○= suspicious. - Lesson: Verification against output must be based on confirmed fresh render products; otherwise, you will misdiagnose "fix not working" (wasted two rounds because of this).
#03 Thumbnail comparison images can be deceptive
- Symptom: In a side-by-side small image, Chinese text looks like Songti, but the full-resolution original is actually normal.
- Fix: When judging font/stroke details, crop the original resolution and zoom in locally; do not use entire frame thumbnails to draw conclusions.
#04 JSX attribute strings do not parse \uXXXX escapes
- Symptom:
<Chip icon="\u2705" />displays the literal\u2705on screen instead of ✅. - Cause: The string literal of a JSX attribute is HTML-style text, not processed by JS escape parsing; only strings in JS expressions (
icon={'\u2705'}) are parsed for escapes. Most commonly hit when using scripts to batch write TSX. - Fix: Emoji/special characters in JSX attributes should use the real character directly (
icon="✅"); if escapes are truly needed, writeicon={'\u2705'}.
#06 backgroundClip: text gradient text renders dark
- Symptom: Large titles using
backgroundImagegradient +WebkitBackgroundClip:'text'+WebkitTextFillColor:'transparent'look okay in Studio, but rendered output colors come out dark and dull (bright red becomes brick red), rejected by user at first glance. - Fix: Large conclusions always use solid semantic color
COLOR[semantic]+textShadow; for depth, rely on shadows and font weight, not gradient fills.tokens.GRADIENTis only for color block/stroke purposes.
#08 Final MG text edges have halos, smudging ("MG makes video quality worse") (2026-07-16 · 0715-01 v3)
- Symptom: User says rendered video quality is poor; when zoomed in, MG text edges show JPEG mosquito noise/halos, more visible on sharper elements. Original footage at 15 Mbps HEVC is very clean.
- Cause: Remotion's default pipeline has double compression — each frame is saved as JPEG at quality 80, then compressed to H264 at CRF 18. Large MG text/thin lines are high-frequency signals, first to show compression artifacts. Before adding MG, artifacts hide in real footage texture and are not noticeable, so they are misdiagnosed as "MG causing poor quality".
- Fix: In
remotion.config.ts, freezeConfig.setJpegQuality(100)+Config.setCrf(15)(already set in this project, do not revert). Volume increases ~20%, rendering slightly slower, worth it. - Diagnosis Method: For the same frame, capture one from source/old final/new test parameter segment; crop the same MG text area, zoom in, and compare side by side; first render a 5-second test segment to verify parameters, then render the full episode.
#09 Long render disappears midway, mp4 becomes corrupted fragment (2026-07-16 · 0715-01 v3)
- Symptom: After rendering for a long time, the process is still running but output file does not grow; or rendering silently disappears, leaving an unopenable mp4.
- Cause: ① Render is attached to a child process of an AI session; when the session restarts/exits, it kills the render (current case: killed at 90%); ② Checking process with
pgrep -f "remotion render"matches the check script itself (command line contains the same string), creating the illusion that "process is still alive". - Fix: ① Renders longer than a few minutes must always use
nohup npx remotion render … > /tmp/xx.log 2>&1 & disownto detach from the session; ② Completion criterion should look for the+ output filenameline in the log (+= true render success), not process existence; ③ Process checking must exclude self-match (look at CPU usage or use exact PID); ④ Delete corrupted fragment before rerendering. - Lesson: Never deliver a corrupted mp4 fragment (duration/size mismatched); always verify duration and audio track with
ffprobebefore showing to the user.
#42 Subtitle English line must use proportional font, avoiding monospace width breaking character spacing
- Symptom: Subtitle English line looks uneven and has inconsistent character spacing; if a monospace font is used, each character takes the same width, causing spacing imbalances.
- Fix: Ensure subtitle English line uses
FONT.subEn(Inter 300), which is a proportional Western font. If using another font, verify it is proportional. - Lesson: Subtitle English must use a proportional font; otherwise, character spacing, especially in long text, will look odd, breaking the subtitle's visual rhythm.