$npx -y skills add gooseworks-ai/goose-skills --skill render-myth-vs-factAssemble a myth-vs-fact kinetic-typography explainer video ad (≈29.5s, 9:16) from N myth/fact pairs + hook / turn / punch copy + palette + a brand end-card PNG + a VO track — a hook, 3 red-strike MYTH cards that flip to teal-check FACT cards (per-line strikethrough that crosses E
| 1 | # render-myth-vs-fact |
| 2 | |
| 3 | The free, deterministic renderer for the **myth-vs-fact** video ad format — the calm, |
| 4 | sound-off-safe kinetic-typography explainer that busts N common myths and hands the viewer |
| 5 | a credible resolution. Red-strike **MYTH** cards flip to teal-check **FACT** cards over a |
| 6 | calm-authority VO, then a "what actually works" **turn** + an optional **proof reveal** + a |
| 7 | **punch** line + a static brand **end card**. |
| 8 | |
| 9 | Every on-screen word is a **deterministic HTML hyperframe** — **no AI image/video gen, no |
| 10 | b-roll, no character**. Visuals cost **$0**. The only metered spend is upstream (VO + |
| 11 | Whisper word-timestamps + an optional music bed), gated to its own capabilities. This |
| 12 | capability OWNS the whole FREE assembly: beat-snap → render → captions → mix → burn → |
| 13 | master. Iterate the cut for free; re-roll only the offending paid audio beat. |
| 14 | |
| 15 | It ports the validated build from the Clinikally "acne myths" run — brand-neutralised, |
| 16 | config-driven, and portable (no `/Users`, no `clients/`; everything via `--config` + |
| 17 | `--work-dir`). |
| 18 | |
| 19 | **SOUND-OFF SAFE is the whole point:** every claim is legible on-screen and the VO only |
| 20 | reinforces it. **VO-FIRST:** render the VO, extract Whisper word onsets on the RENDERED |
| 21 | audio, then snap every beat boundary + strike wipe + reveal to those onsets. |
| 22 | |
| 23 | ## The 8-beat spine (roles) |
| 24 | |
| 25 | `hook` → 3× `myth-fact` (the flip triad — identical grammar so it reads as a pattern) → |
| 26 | `turn` (the "what actually works" pivot) → `proof` (optional actives/proof reveal, omit if |
| 27 | empty) → `punch` (full-frame closer) → `end-card` (the static brand PNG). Each beat carries |
| 28 | its `role`, `duration`, and its copy; ONE role template renders any pair. |
| 29 | |
| 30 | ## Scripts (free — Python + Playwright + ffmpeg, no paid calls) |
| 31 | |
| 32 | - `scripts/beat_snap.py` — VO-first alignment. FAL Whisper word-timestamps on the RENDERED |
| 33 | VO → re-snap every beat boundary to the nearest word onset. Writes |
| 34 | `beat-manifest.json` + `whisper/words-flat.json` into the work dir. `--no-whisper` keeps |
| 35 | the config durations un-snapped for a fully offline run. |
| 36 | - `scripts/render_beats.py` — the deterministic renderer. Per `mg` beat: pick the role |
| 37 | template under `hyperframes/`, inject the beat's copy + palette + fonts as `window.BEAT`, |
| 38 | drive `window.renderAt(t)` frame-by-frame via Playwright, screenshot each frame → ffmpeg |
| 39 | at EXACTLY the configured fps (default 25/1). The `end-card` beat is built from the |
| 40 | pre-supplied brand PNG (scale/crop + a ~0.35s fade-up) — **never generated per run**. |
| 41 | - `scripts/make_captions.py` — karaoke `.ass` from the manifest + Whisper words. ≤3 words |
| 42 | per cue; close on a >0.4s gap / beat-window edge / sentence-ending punctuation. Captions |
| 43 | are burned ONLY in caption-allowed windows; the proof + end-card beats are suppressed. |
| 44 | - `scripts/compose.py` — the assembler: concat the beats → mix VO + optional music (music |
| 45 | −20 dB, `amix normalize=0`, ~0.8s tail fade) → burn the `.ass` LAST → master mp4. |
| 46 | - `scripts/config.example.json` — the shape of the brand `config` the recipe binds (the |
| 47 | brand-neutralised Clinikally values as a worked reference). |
| 48 | - `scripts/hyperframes/` — the bundled hyperframe scaffold: `_shared.css` (palette-tokened |
| 49 | tokens + card/tag/fact/pill/chain type), `_shared.js` (the `initRenderer` / |
| 50 | `springScale` / `buildLineStrikes` + `strikeLines` per-line-strike / `popIn` / |
| 51 | `revealWords` helpers + config injection), and one template per role (`beat-hook.html`, |
| 52 | `beat-myth-fact.html`, `beat-turn.html`, `beat-proof.html`, `beat-punch.html`). |
| 53 | |
| 54 | ## Inputs (all via `--config` + a runtime work dir — NO hardcoded paths) |
| 55 | |
| 56 | `config.json` carries: `fps` (25) / `width` / `height`; `vo` + optional `music` + |
| 57 | `mix{music_db:-20, tail_fade:0.8}`; `palette` (the five CSS-var tokens `bg`, `myth_strike`, |
| 58 | `fact_accent`, `headline_ink`, `accent`); `brand_name`; `display_font`; `end_card_png` + |
| 59 | `end_card_fade`; `caption_style`; `suppress_beats`; and `beats[]` — each `{n, role, |
| 60 | duration, captions, cues |