video-talkcraft — Talking-Head Video Skill
Combines three core sources: Pipeline (voiceover → word timestamps → Remotion) + Vocabulary (108 motion recipe cards, all with playable demos + self-contained tsx) + Lens System (7-layer anti-PPT model) + Visual Language (Apple paradigm default).
Core paradigm: narration drives visuals, each sentence must have a living visual response (ultra-slow camera dolly / changes to existing elements), but new elements only enter at semantic paragraph boundaries, forbidding the mechanical "one element per sentence" (one element per sentence is the root cause of cluttered disorganization; storyboarding cuts by semantic paragraphs, layout budget see cinematography.md §4.5); one beat has only one hero, yield after speaking; subtitle sentence boundaries = full film time anchors.
Workflow
① Script → ② Voiceover input + timestamps (local CPU) → ③ Assets → ④ SHOTBOOK layer matrix → ⑤ Implementation (global system → first shot produced first and confirmed → remaining shots)
→ ⑥ Render → ⑦ Triple acceptance (all machine gates passed + 1 review round fixing P0/P1) → ⑧ Delivery (optional additional review ≤3 rounds)
⓪ Pre-flight Health Check: Unified Dependencies + Frame & Visual Language
Only one dependency: All talking-head projects' remotion/node_modules and workbench/node_modules are symlinks pointing to <skill root>/runtime/node_modules, with versions pinned only in runtime/package.json (@remotion/* all flavors same version). First thing when starting a new video (user finalized 2026-09-15: check for updates on each new video):
bash <skill root>/runtime/check-runtime.sh --upgrade # Install shared deps + shared headless browser + symlink workbench; if Remotion has a new version, upgrade the entire family and smoke-test render 1 frame; auto-rollback on failure
Only proceed after the last line says [runtime] OK; if upgraded, commit the changes to runtime/package.json / package-lock.json. Do not upgrade mid-production (segment caches don't recognize dependency versions; mixed versions cause inconsistent segments). Never run npm install in a project directory (it would write into the shared directory); to add a package: add to runtime/package.json, npm ci, then commit.
Frame & Visual Language (decide at start, referenced throughout)
- Default frame: horizontal 1920×1080 (user preference); use 1080×1920 only for clear Douyin/vertical channel needs
- Visual Language: if user explicitly specifies a style, follow it (full token replacement); if not specified, first determine the domain from the script, then derive the film's style profile
(
references/design-language.md §0: read the full script, answer "what is it about / who is the audience / what tone" → domain → style profile table giving base color strategy / accent / font personality / texture & card shape / chart language / asset personality / energy level, write into SHOTBOOK §0G0 Style Profile), the skeleton remains the Apple paradigm (one accent color / one drop shadow / base color alternates scenes / two font weights / default backdrop: lightpastel-mesh-flow/ darkmesh-flow-dark—12 backdrops in §1.1, codetemplate/motion-systems/backdrop.tsx), derive the film's tokens intotheme.ts. The only universal rule for any style: never pick colors scene-by-scene on the fly. Cards are neutral UI; they must be skinned when entering a film: motion card demos / tsx are style-neutral presentations; after copying into the project, skin them according to the style profile (colors / fonts / border radius / textures / chart axes and markers / placeholder graphics), but do not change motion fundamentals (timing / easing / geometric proportion / hierarchy), contract and counterexamples in design-language §0.4; each shot in SHOTBOOK writes a skinning line
① Script
- One information point per sentence, hook in the first sentence (number/conflict); ~13 sentences ≈ 95s
- All numbers in Chinese characters (timestamps anchor character-by-character to text;
197747cannot align with the pronunciation of "nineteen seventy-seven thousand"); English brand words written directly (Chinese-English mixed alignment verified) - First research and verify facts, create a "fact red line list" (numbers that cannot be wrong / unverified data not cited)
② Voiceover Input → Pre-cut → Character-Level Timestamps (default local CPU; optional Fish Audio)
Default: use finished voiceover: real human recording or any TTS; input = one complete audio track (wav/mp3) + a script that matches it character-for-character. If no recording exists and the user chooses synthesis, use plan B below; it will not replace existing voiceover or the default local CPU process.
②-0 Voiceover Pre-cut (required for real recordings; TTS only compresses breath pauses, can skip) — Remove filler words (um / uh / you know…), stutters, repeated phrases, excessively long pauses, run before generating timestamps (if you move the audio after timestamps, timing / beats will be completely misaligned; pre-cut → timestamps → everything else, order is fixed):
python3 scripts/voice_trim.py audio/raw.wav script.json --dry-run --words-out audio/asr_words.json # First only output a report: list [filler / repeat / pause] start/end + total cut time; ASR result saved to disk
python3 scripts/voice_trim.py audio/raw.wav script.json --words audio/asr_words.json --out audio/full.wav \
[--video dh/host_raw.mp4 --video-out dh/host.mp4] # After user approves, save to disk (reuses the word list from previous step, no re-ASR); same recording's host video uses same EDL (audio and video frame-perfect same length)
- Script is ground truth: rather miss than mis-cut: only cut segments from ASR that do not exist in the script — entirely composed of filler words (filler), or stutters/repeats equal to adjacent script text (repeat);
never cut characters that ASR misrecognized (cutting them removes the real pronunciation); for rephrased repeats, only report,
--cut-unmatchedto actually cut. Default backend for word list is FireRed (same as timestamps below), also accepts character-level SRT from CapCut etc. (--srt) or word-level JSON (--words); sentence-level SRT cannot locate filler words, script will warn. With no script, only cut filler words whose entire token text exactly matches a conservative word list (嗯 / 呃 / 额 / um…) — precise character match, no homophone recognition (五≠唔, 饿≠呃); for consecutive short repeated words with no script, only report, do not cut (cannot distinguish reduplication from stutter at character token level). When cutting a concurrently recorded transparent webm, decode one frame's alpha; FAIL if frame count matches but alpha is lost. - Pauses: compress silence ≥0.7s to 0.35s (keep the quietest real room noise window within the segment, do not insert digital zeros), head/tail padding 0.25/0.5s;
cut points first find energy valleys, then snap to 30fps frame grid;
cuts.jsonis an EDL (source → new timeline mapping, used by host video cutting and any subsequent reconciliation). - dry-run report must be reviewed by user before saving: items marked "cut point not in silence, please listen" let the user hear that spot; script only cuts and does not synthesize, never changes speed or pitch.
After cutting the host video, run
preflight.py --media-onlyfrom ③ to verify duration — same EDL only valid for the same recording.
Character-level timestamps (for post-pre-cut audio/full.wav):
# Plan A (existing recording, local CPU alignment):
pip install zhconv pypinyin sherpa-onnx soundfile numpy # All deps for default backend FireRedASR2-CTC int8
# First time: download model 767MB (model.int8.onnx + tokens.txt) to ~/.cache/koubo/<model name>/, URL in script header comment
python3 scripts/timestamps_cpu.py audio/full.wav script.json audio/timestamps.json
# Alternative (no manual model download): pip install faster-whisper then add --backend whisper (auto-downloads 460MB on first run)
python3 scripts/make_timing.py audio/timestamps.json remotion/src/timing.json
# Plan B (optional generate voiceover + timestamps, default requests s2.1-pro-free; availability depends on Fish Audio):
pip install requests python-dotenv # Also requires ffmpeg, checked before generation
# Configure in .env: FISH_AUDIO_API_KEY=xxx and optional FISH_AUDIO_REFERENCE_ID=xxx (see .env.example)
python3 scripts/tts_fishaudio.py script.json audio/full.wav audio/timestamps.json --timing-out remotion/src/timing.json
- tts_fishaudio.py: default
--mode sentencemakes one request per sentence, inserts--pause-secreal silence (default 0.25s);--mode streammakes one request for the whole script, preserving natural pauses. Both receive UTF-8 SSE, write files only after generation completes, no streaming playback. Decode PCM first then concatenate, offsets calculated by sample count. Errors on empty audio, missing/invalid alignment, or text mismatch; only ignores punctuation, case, and character width, does not auto-convert number pronunciations. Chinese uses API character-level anchors, English interpolates within word tokens;match/okonly confirms text mapping, still requires audition. If re-pre-cut after synthesis, must re-run CPU alignment. - timestamps_cpu.py: ASR word timestamps → character-level alignment with script (CJK is reliable anchor,
match key = simplified/traditional normalized + toneless pinyin, homophones not an error; Latin words are often misspelled by various ASR, interpolate between anchors) →
per-sentence match quality check, <0.90 mark for manual verification. Default backend FireRedASR2-CTC (most stable tail, zero false positives; whole segment feeding ~200s crashes due to quadratic memory, script defaults to splitting by silence into ≤75s segments then re-adding offsets,
--chunk-secadjustable), alternative faster-whisper; cross-backend benchmarks and model download URLs in script header comments. - timestamps.json schema:
{sr, total, sentences:[{i,text,start,end,match,ok,words:[{text,start,end}]}]}— words are CJK character-by-character + Latin entire token (punctuation skipped); any alignment tool satisfying this schema can be substituted. - make_timing.py: converts to timing.json (chars 1:1 with text characters, punctuation zero duration), used for
tSay/msSayanchor queries. - Self-check voiceover (listen): no popping/truncation/mispronunciation; leave ~0.3s breath pause between sentences for more stable timing anchors.
②-1 Semantic Annotation (mandatory from 2026-09-22, ground truth references/semantic-annotation.md)
Annotate each script sentence with "what this sentence does", save to project root semantics.json — this is the demand-side input for card selection; without it, card selection only has input type as one filter, remaining dozens of cards chosen by memory (for the 2026-09-21 film "I am Wanli" this resulted in picking the lowest-energy text card from the entire library, and the P0 name tag from the library wasn't found).
python3 <skill root>/scripts/semantic_annotate.py --init # Generate skeleton from timestamps.json (with lexical hints)
# Fill in per-sentence: sem (26-word closed vocabulary, see taxonomy.md "Semantic Index") / weight (main = allows new elements, sub = supporting sentence) / entities / need
python3 <skill root>/scripts/semantic_annotate.py --stats # Validate: character-level consistency with timestamps · vocabulary closed · lexical hard rules (I am X → self-intro, like/subscribe → call-to-action, quantity word → data) · at least one main per shot
Annotation is derivative: pre-cut → timestamps → annotation → everything after; if script is changed or re-cut, text mismatch makes annotation invalid, re-run --init.
②-1 precedes storyboarding, so the shot field is empty at this point (SHOTBOOK not yet written): after ④ write, must backfill once, otherwise downstream shot-level coverage cannot check anything —
python3 <skill root>/scripts/semantic_annotate.py --sync-shots # After ④: backfill shot based on SHOTBOOK heading start/end seconds (or shots.json), only changes shot, annotation preserved
③ Assets
- Asset list is generated from ②-1 semantic annotations, not by guessing: sentences with
needevidencego to capture real pages / find sources,identityrequires avatar and account info,quantifyneeds charts or data sources,entitiesperson names / brands / locations / URLs are search terms. - First assign an asset mode to each shot (multi-select, combinable):
B-roll(recorded video) /Image(photos / posters / illustrations) /Screenshot(webpage / interface evidence) /Pure motion— e.g., "B-roll base + screenshot evidence card". For news/info topics, evidence takes priority: Playwright real-time screenshots carry more information than generic B-roll. Four levels correspond to taxonomy.md input type codes V / Image / Screenshot / Text, SHOTBOOK each shot writes one lineAssets: V (path) · Image (path) · Text(format see ④) - Real footage is required, not optional (hard rule 2026-09-06): zero B-roll / images in the whole film = only motion + talking head = audience sees "a PowerPoint presentation".
preflight.pyreconciles SHOTBOOK: shots with zero V / Image FAIL directly, shots with V / Image ratio < 1/3 WARN (must provide justification in SHOTBOOK, e.g., evidence-style topics primarily use screenshots). "No B-roll for this film" is not allowed as a design decision; if no video source can be found, downgrade to images (Pexels photos original → Pixabay), if no images either, then enter "Incomplete / Uncollected List". Images and videos share the same key; acquisition specs (resolution minimum, save directory, registration) seereferences/broll-sources.md"Image Acquisition" - Real image hard rule: when a real capturable page exists for the topic (product website/GitHub/documentation/gallery),
browser/page shots in the final film must not use code mock to fake screenshots — gray bar fake UI in card demos are placeholders;
the final film must replace the entire block with
<img>real images per card "reuse guide"; mock is only allowed for illustrative UI without real-world counterparts, and SHOTBOOK must annotate per shot "why no real image". Extension: when the talking head says "such a final film/effect", the example must be a real snippet (embedded<OffthreadVideo>of existing film/real device screen recording, muted); for "long page/dashboard/parameter page", use Playwright full-page long screenshots (magnifier/tour motions directly use real image coordinates). Annotation coordinates on real images must be machine-measured, never by eye: page elements use DOMgetBoundingClientRect, final images use pixel-level measurement (eye estimate ±30px will put the ring on the wrong element); for scrolling/moving real images, annotations (ring/frame/pill) must be pinned to the content coordinate system and move with it, pinning to fixed screen position is the source of misalignment. - Single video shots must have a thematic frame (user finalized 2026-09-07): when the only subject of a shot is a video (screen recording / single B-roll / referenced film or interview),
no bare full-width display, no bare white card, no fake player (no progress bar / play button / timecode — not a real player, it's deceptive), the video area must be wrapped with
template/components/theme-frame.tsx<ThemeFrame kind>: eight styles (retro browser window / magazine photo frame / 35mm film / Polaroid / engineering drawing / notebook / postage stamp edge / double hairline), choose one style based on film tone, one style per film only, write into SHOTBOOK skinning line; multi-video cards (bed-echo-blur foreground / split-60-40-story left cell / gallery-wall-dolly) also wrap video areas with the same style. The frame only handles shape and its own decorative transition; overall entry / exit / ultra-slow dolly belongs to the lens layer; the frame's video is untouched (no filters / no scaling / no fade). Only two exceptions: video used as base layer not subject (bed-echo-blur / §1.2 real base), and product interface cards (chat-gpt / claude-code type, skin is content). Static images don't go in frames (frame says "this is a recording", but no motion is immediately fake; use media-pop-in / slow-push-in for images). Rules and eight-style table: design-language §1.3 - Webpage shooting, no screenshots: when finding a useful webpage during research, no static screenshot directly on screen in the final film,
must "shoot" it like a handheld camera — scroll (
evidence-scroll-tour◆: scroll up at ≈10% page height/s, slow down and stop for 1~2s at key lines) / tour (stage-keyframe-tour◇: long page stationary on stage, camera stops at each point of interest) / magnify (magnifier-detailcircular magnifier glance and dismiss,pip-zoom-box◈lift out and keep visible) / highlight (highlighter-sweepsweep whole sentence,ink-underline◇underline one word,scribble-annotationcircle/arrow annotation,corner-bracket-frame◈frame an area); for pages that fit one screen, at least useslow-push-inas base noise. One primary mode per shot: during scroll/tour segment, no popping up magnifier, no live marking; order is "scroll to → stop → mark/magnify → scroll again"; mode selection table see shot-design.md §2④ "Webpage Shooting". Assets saved per broll-sources.md "Webpage Shooting Asset Acquisition" specs: Playwright full page 2× long screenshot + in-session DOM-measured target coordinate JSON (magnifier/marking/stop points all use this coordinate set, linking to the previous "no eye measurement" rule). Shooting is always done by Remotion on the long screenshot (seek-safe, word-anchorable), no browser screen recording (unstable frame rate, lazy loading and sticky headers break the illusion, cannot align with character timestamps). Only allowed exception: a small screenshot that fits one screen and is only a supporting role (one of many in a media-pop-in stack) can enter a card statically, but still with Ken Burns effect, must not be the main subject of that shooting. - For shots marked B-roll, list 2–3 English visual concept words, run Pexels + Pixabay API dual-source parallel, candidates saved to
assets/broll/; source tiers and licensing boundaries (only use no-attribution sources) seereferences/broll-sources.md - Research bookkeeping: archive each source page carrying key facts with a screenshot,
sources.mdhas links and local screenshots 1:1 (no links without evidence); prefer archived screenshots as visual evidence + micro-level source line in the final film. - When B-roll/screenshots exist + corresponding host footage (recording/digital human finished), host is always downgraded to a permanent corner bug —
circular avatar chip (
host-shrink-to-chip◆) or keyed host pasted to corner, placed bottom-left / bottom-right; never cut away from host, host must not fill frame. Both options and all hard constraints according toreferences/host-footage.md §5, shot presets see shot-design.md §2⑦. - Manim charts: after
--transparent --format=mov, must convert to VP9 webm (-c:v libvpx-vp9 -pix_fmt yuva420p) - All assets saved to public/, no remote fetching during rendering.
- ③ Must end with asset check (turn hard rules from host-footage / broll-sources into assertions; the answers were always in the references, but no one checked them at the right time, which was the common failure mode for the two biggest issues in the 2026-09-06 retrospective — so no more documentation additions, instead run this check before starting):
python3 scripts/preflight.py --media-only --host remotion/public/dh/host.webm --fps 30 --voice audio/full.wav
# Check: host material r/avg frame rate consistent (VFR) · source has no duplicate frame signatures · material fps = film fps (if not, change film fps to material fps, never use -r to convert) ·
# duration frame-perfect aligned with voiceover · real aspect ratio · asset inventory (number of real footage/images/webpage long screenshots) · sources.md registered. Any FAIL prevents entry to ④
④ SHOTBOOK (mandatory output, review before implementation)
First use references/shot-design.md to fill in the three-layer work sheet for each shot (background layer / subject layer / text layer + each layer's motion
- 7 lens type presets), then expand into a layer matrix per
references/cinematography.md §4, example:references/shotbook-example.md. Each scene: one line intent + subject relay line + layer matrix per beat (beats anchored to character timestamps; each action must answer "accompanying who"). Each shot must write anAssets:line (machine-readable, preflight stats files one by one):- Assets: V (public/broll/gpu.mp4) · Image (public/stills/a.jpg, public/stills/b.jpg) · Screenshot (public/pages/gh/page.png) · Text— V / Image / Screenshot must have paths in parentheses, writing "to be collected" = FAIL; pure motion shots still writeAssets: Text. Must include an## Incomplete / Uncollected Listsection: for any "X not done for this film" — either put in §0 design decision (+ rationale), or in this section (+ blocking reason + if fallback source tried); "None" is allowed, but missing section is not. This section specifically prevents "unfinished work being packaged as design principles, then becoming unquestioned assumptions" (the real failure mode from the 2026-09-06 retrospective). G0 writes one lineTemplate Shot: sNN(for ⑤-1 first shot produced first and confirmed): default s01; if s01 is a chapter card / empty shot without subject motion, choose the first shot with a card, subtitle, host or assets. Beats must be machine-verifiable: visual accents saved asremotion/beats.json({t, anchor, sentence, what}, t always looked up by timing.json/atChar(), no handwritten approximate seconds — static frame QA cannot catch the error from handwritten timings), SHOTBOOK beat table must match beats.json, machine gate usesscripts/beat_lint.pyagainst timestamps.json validating |Δ|≤0.1s. Not visible before the beat: numbers/graphics not yet anchored by word must be completely invisible (opacity 0), no dimmed/grayed "preview"; inline numbers must gate together with their following characters (an empty hole like "just __ categories" is equally a defect). No empty opening: if a shot's opening to the first motion anchor is >1.5s, there must be a carrying visual (real b-roll / continuation of previous shot's elements / real asset wall), no empty canvas waiting for word anchor. Shot tail safety zone: motion anchor endpoint <0.7s from shot out-point must either be moved earlier or into the next shot — the endpoint will be swallowed by the transition;beat_lint.py --shots shots.jsonmachine-checks ≥0.5s hard floor. Scene-level transitions also enter beats.json: the peak occlusion moment of shape wipe/scene change is also generated by word anchor and entered into the table — transition event tables with handwritten absolute seconds float outside the machine-verifiable system, invisible to static frame QA and beat_lint. Layout Budget (full table cinematography.md §4.5): storyboard cuts by semantic paragraphs, each shot one primary visual job; Pure text shots must have supporting graphics (2026-09-07 user feedback "only text motion stacking is too monotonous"): for shots whose asset line only hasText(except chapter cards), the layer matrix must have an extra rowG5 Schematic diagram ← because talking about X, drawing Y(references/schematic.md, codetemplate/motion-systems/schematic.tsx), preflight WARNs for pure text shots missing this row; Pivot sentences ("but this time it's not X" type twists/rhetorical questions) assign their motion to the next shot they initiate; at any moment, on-screen subject group ≤3 (including weight-downgraded lingering count them), each shot leaves at least one empty quadrant; one hero shape per screen. Layout Rules (full tablereferences/layout.md): budget governs "how much", rules govern "where, how big, how aligned" — SHOTBOOK each shot writes a layout line (column span + group bounding box + alignment baseline + type scale), verification frame opensdebugOverlayto check nine items, any failure = P1; Single-sentence hero centered but must not cover faces (including faces in B-roll, vertically switch to a rule-of-thirds line outside the face). Layout Rotation (2026-09-21 user: films "host bottom-left, assets middle box, text on right, too fixed"; full table cinematography.md §4.5 item 9): before writing the per-shot matrix, first write in G0 a Layout Rhythm Table (| Shot | Host Form · Position | Asset Container | Main Card |, format cinematography.md §4) — same presentation card (asset presentation / data / camera motion type) not used for 3 consecutive shots, ≤1/3 of the whole film (for films ≥6 shots); host form (waist-up / corner bug bottom-left / corner bug bottom-right / split cell / keyed corner / brief absence) and asset container (full-bleed full-screen / framed / split cell / base layer / multi-image layout / 3D wall / long page) at least one changes every 3 consecutive shots; same B-roll not in adjacent shots. Single B-roll selects from shot-design.md §2⑦ seven styles, adjacent shots different style, multi-asset follows §2④′ relation table; preflight checks rhythm table + skinning line (consecutive use / ratio FAIL). Rotation changes composition, not adding motion — inside each style, only ultra-slow camera dolly. Each shot must write aCard Selection Line(②-1 semantics → card; candidates provided by machine):
python3 <skill root>/scripts/card_match.py --out qa/card-candidates.md # Semantics × asset line × card index → per shot, per main sentence semantic, viable candidates + ranking reason + rejection reason
Run semantic_annotate.py --sync-shots first to backfill shot (②-1 doesn't have storyboard yet). Candidate table also lists for shots declaring assets a separate line
Asset-carrying candidate — if a shot declares V / Image / Screenshot but no presentation / camera motion card carries it, it's bare assets, preflight FAILs.
SHOTBOOK each shot writes - Card Selection Line: <semantic> → <card>, <semantic> → <card> (cards in this line must actually appear in the skinning line, preflight reconciles);
choosing a card outside candidates requires writing one line in that shot - Semantic deviation: self-intro ← opening already stated identity, s11 no repeat (reason ≥4 characters, preflight recognizes this line to pass).
Add a semantic column to the layer matrix beat rows (value from semantics.json, no new words), so "what this beat does" is machine-readable within the storyboard.
Card selection must read card experience: for each selected card, copy references/cards/<slug>.md's "Known Pitfalls" and "Self-Check for Placement" line by line into the shot's layer matrix self-check column, then verify point by point after implementation
(e.g., framing / circle-annotation / underline cards must check annotations actually cover the target; gooey-morph only for images not text and no host present then centered; chapter-title-card each chapter a unique theme color + a line art motif related to that chapter's content, SHOTBOOK writes chapter theme line — four chapter cards with same color and pattern is "here we go again" not "turn the page") —
if card experience is not in SHOTBOOK, it's as if unread.
Motion vocabulary is selected from 108 recipe cards, both filters checked against the machine-generated index in references/taxonomy.md (source: each card frontmatter's five fields: input / semantic / asset form / position / props, generated by scripts/cards_index.py --write, validated by --check, manual index modification invalid): First filter by this shot's input (person / V / Image / Screenshot / Text / Interface / Scene — "Input Type Index"), then filter by this sentence's semantic (self-intro / data / comparison / enumeration / citation / call-to-action… 26-word closed vocabulary — "Semantic Index", a card is listed only under the semantics it is specifically designed for); cards with ◦ in the index have no content-replaceable props (only expose resource/skin type, or nothing), changing copy/data requires modifying tsx (each card's reuse guide's props line tells what to change). New batches of cards ◉◎ (29 cards) have an additional "Input Type" table at the md start + four "Common Scenarios" lines. Then references/taxonomy.md classification index → references/cards/<slug>.md parameters and pitfalls → template/cards/<slug>.tsx self-contained Remotion source code (implementation takes precedence; copy into project and modify CONFIG for use); demos/<slug>/index.html is same-scene HTML preview (open gallery/index.html browse one screen, demos autoplay on scroll-in; production-proven cards with ★ have separate master in template/motion-systems|components).
Fidelity invariant: each used card must physically exist in the project at src/cards/<slug>.tsx
(copied from template and CONFIG modified) — reading only md and hand-writing a "similar" simplified version is the biggest source of accidents
(lost bounce/snap/density, bracket annotation drawn backward, name card turned into color block), machine gate uses scripts/card_lint.py
per slug to validate existence and similarity (≥0.55, CONFIG/copy modifications within tolerance).
Skinning is not rewriting: copied cards are neutral UI, must be skinned per SHOTBOOK §0 style profile — change all colors to theme tokens, font stack and weight, border radius / stroke / shadow / texture,
chart axes / grid / markers / numerical font, card placeholder graphics to real assets or stylized graphics — only change the skin, not timing / easing / geometric proportions / hierarchy;
write a skinning line next to each shot's layer matrix (card name → what skin was changed), same card type within a film shares one skin set; contract, exceptions (product interface cards not skinned, semantic colors not hue-shifted) and counterexamples in design-language §0.4.
The card_lint 0.55 threshold accounts for skinning: skin changes pass, motion rewrite causes failure.
Three-stage invariant: entry 0.20.8s → hold (static is fine — visual life comes from the scene camera's ultra-slow dolly) → exit 0.150.5s; entry always more forceful than exit; only one on-screen emphasis at a time.
If you choose a motion, include its sound effect: each card in demos/_lib/sfx-map.js has a cue table ({t, name, vol, rate?, clip?}, t is relative seconds within the card) —
SHOTBOOK during card selection copies the cues into the shot's layer matrix (convert to absolute seconds; vol re-leveled to ≤0.35 for final film caliber,
demo library's 0.65 ceiling is audition caliber, not final). Implement per ⑤'s sfx steps.
Coverage caliber: main motion entry fully covered, matching demo library density
(26 hits per card ≈ 0.4 hits/s, a 100s film ≈ 4050 hits) — "few but precise" governs: no double hits at the same point,
volume restrained (≤0.35), continuous reveal types (slow pull/focus/character-by-character rise) and pure text cards for impactful lines, logo closing silence,
transitions only have build-up, not landing points, no ending jingles or heavy thuds; it does not reduce coverage area. Real samples (pk-...
[truncated for translation]