{"slug":"render-myth-vs-fact","title":"render-myth-vs-fact","summary":"Assemble 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","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-11T17:23:10.142682Z","repo":{"url":"https://github.com/gooseworks-ai/goose-skills","stars":1222,"forks":208,"license":"MIT","updatedAt":"2026-09-22T18:53:29Z"},"bodyHtml":"<hr>\n<h2>name: render-myth-vs-fact\ndescription: Assemble 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 EVERY wrapped line), a \"what actually works\" turn, an optional proof reveal, a punch line, and a static end card. DETERMINISTIC assembly with ZERO AI-gen visuals — HTML hyperframes rendered frame-exact via Playwright (<code>window.renderAt(t)</code>, animation a pure function of beat-local time), Whisper beat-snap to VO word onsets, concat at a uniform fps, karaoke <code>.ass</code> captions burned last (suppressed on the proof + end-card beats), and a VO + optional music mix (music −20 dB, <code>amix normalize=0</code>, tail fade). FREE (Python + Playwright + ffmpeg); the recipe supplies the copy / palette / end-card / VO and gates the paid VO / music / Whisper calls to their own capabilities. Use for the myth-vs-fact format.\nstatus: active</h2>\n<h1>render-myth-vs-fact</h1>\n<p>The free, deterministic renderer for the <strong>myth-vs-fact</strong> video ad format — the calm,\nsound-off-safe kinetic-typography explainer that busts N common myths and hands the viewer\na credible resolution. Red-strike <strong>MYTH</strong> cards flip to teal-check <strong>FACT</strong> cards over a\ncalm-authority VO, then a \"what actually works\" <strong>turn</strong> + an optional <strong>proof reveal</strong> + a\n<strong>punch</strong> line + a static brand <strong>end card</strong>.</p>\n<p>Every on-screen word is a <strong>deterministic HTML hyperframe</strong> — <strong>no AI image/video gen, no\nb-roll, no character</strong>. Visuals cost <strong>$0</strong>. The only metered spend is upstream (VO +\nWhisper word-timestamps + an optional music bed), gated to its own capabilities. This\ncapability OWNS the whole FREE assembly: beat-snap → render → captions → mix → burn →\nmaster. Iterate the cut for free; re-roll only the offending paid audio beat.</p>\n<p>It ports the validated build from the Clinikally \"acne myths\" run — brand-neutralised,\nconfig-driven, and portable (no <code>/Users</code>, no <code>clients/</code>; everything via <code>--config</code> +\n<code>--work-dir</code>).</p>\n<p><strong>SOUND-OFF SAFE is the whole point:</strong> every claim is legible on-screen and the VO only\nreinforces it. <strong>VO-FIRST:</strong> render the VO, extract Whisper word onsets on the RENDERED\naudio, then snap every beat boundary + strike wipe + reveal to those onsets.</p>\n<h2>The 8-beat spine (roles)</h2>\n<p><code>hook</code> → 3× <code>myth-fact</code> (the flip triad — identical grammar so it reads as a pattern) →\n<code>turn</code> (the \"what actually works\" pivot) → <code>proof</code> (optional actives/proof reveal, omit if\nempty) → <code>punch</code> (full-frame closer) → <code>end-card</code> (the static brand PNG). Each beat carries\nits <code>role</code>, <code>duration</code>, and its copy; ONE role template renders any pair.</p>\n<h2>Scripts (free — Python + Playwright + ffmpeg, no paid calls)</h2>\n<ul>\n<li><code>scripts/beat_snap.py</code> — VO-first alignment. FAL Whisper word-timestamps on the RENDERED\nVO → re-snap every beat boundary to the nearest word onset. Writes\n<code>beat-manifest.json</code> + <code>whisper/words-flat.json</code> into the work dir. <code>--no-whisper</code> keeps\nthe config durations un-snapped for a fully offline run.</li>\n<li><code>scripts/render_beats.py</code> — the deterministic renderer. Per <code>mg</code> beat: pick the role\ntemplate under <code>hyperframes/</code>, inject the beat's copy + palette + fonts as <code>window.BEAT</code>,\ndrive <code>window.renderAt(t)</code> frame-by-frame via Playwright, screenshot each frame → ffmpeg\nat EXACTLY the configured fps (default 25/1). The <code>end-card</code> beat is built from the\npre-supplied brand PNG (scale/crop + a ~0.35s fade-up) — <strong>never generated per run</strong>.</li>\n<li><code>scripts/make_captions.py</code> — karaoke <code>.ass</code> from the manifest + Whisper words. ≤3 words\nper cue; close on a &gt;0.4s gap / beat-window edge / sentence-ending punctuation. Captions\nare burned ONLY in caption-allowed windows; the proof + end-card beats are suppressed.</li>\n<li><code>scripts/compose.py</code> — the assembler: concat the beats → mix VO + optional music (music\n−20 dB, <code>amix normalize=0</code>, ~0.8s tail fade) → burn the <code>.ass</code> LAST → master mp4.</li>\n<li><code>scripts/config.example.json</code> — the shape of the brand <code>config</code> the recipe binds (the\nbrand-neutralised Clinikally values as a worked reference).</li>\n<li><code>scripts/hyperframes/</code> — the bundled hyperframe scaffold: <code>_shared.css</code> (palette-tokened\ntokens + card/tag/fact/pill/chain type), <code>_shared.js</code> (the <code>initRenderer</code> /\n<code>springScale</code> / <code>buildLineStrikes</code> + <code>strikeLines</code> per-line-strike / <code>popIn</code> /\n<code>revealWords</code> helpers + config injection), and one template per role (<code>beat-hook.html</code>,\n<code>beat-myth-fact.html</code>, <code>beat-turn.html</code>, <code>beat-proof.html</code>, <code>beat-punch.html</code>).</li>\n</ul>\n<h2>Inputs (all via <code>--config</code> + a runtime work dir — NO hardcoded paths)</h2>\n<p><code>config.json</code> carries: <code>fps</code> (25) / <code>width</code> / <code>height</code>; <code>vo</code> + optional <code>music</code> +\n<code>mix{music_db:-20, tail_fade:0.8}</code>; <code>palette</code> (the five CSS-var tokens <code>bg</code>, <code>myth_strike</code>,\n<code>fact_accent</code>, <code>headline_ink</code>, <code>accent</code>); <code>brand_name</code>; <code>display_font</code>; <code>end_card_png</code> +\n<code>end_card_fade</code>; <code>caption_style</code>; <code>suppress_beats</code>; and <code>beats[]</code> — each <code>{n, role, duration, captions, cues{...}}</code> plus the role's copy:</p>\n<ul>\n<li><strong>hook:</strong> <code>eyebrow</code>, <code>hook_line</code>, <code>emphasis</code>, <code>strike_word</code></li>\n<li><strong>myth-fact:</strong> <code>myth_index</code>, <code>myth_line</code>, and either <code>fact_line</code> (a <code>[bracketed]</code> phrase\nbecomes the accented payload) OR <code>fact_clauses[]</code> (a staggered clause chain)</li>\n<li><strong>turn:</strong> <code>turn_slate</code>, <code>turn_sub</code> (<code>[brackets]</code> → emphasis)</li>\n<li><strong>proof:</strong> <code>proof_eyebrow</code>, <code>proof_items[]</code> (<code>{name, badge}</code>), <code>proof_footnote</code></li>\n<li><strong>punch:</strong> <code>punch_line</code></li>\n<li><strong>end-card:</strong> none (built from <code>end_card_png</code>)</li>\n</ul>\n<p>The recipe's <code>myth_fact_pairs</code>, <code>hook_line</code>, <code>turn_slate</code>, <code>punch_line</code>, <code>palette</code>,\n<code>end_card_png</code>, and optional <code>actives_or_proof</code> map onto these beats 1:1. See\n<code>config.example.json</code>.</p>\n<h2>Craft rules (load-bearing — faithful to the source molecule)</h2>\n<ul>\n<li><strong>MYTH strikethrough is PER-LINE.</strong> Measure each wrapped line box (<code>Range.getClientRects</code>,\ndeduped to one rect per visual line) and lay one red bar at each line's vertical MIDDLE,\ndriven as ONE continuous L→R sweep. A single fixed-Y rule reads as an underline the moment\nthe headline wraps.</li>\n<li><strong>The end card is the pre-built brand PNG — NEVER generated per run.</strong> Scale/crop to the\ncanvas with a short fade-up; it carries its own baked logo + claim + CTA.</li>\n<li><strong>Animation is a pure function of beat-local time</strong> — no <code>setTimeout</code>, no CSS keyframes —\nso Playwright seeks frame-exact and the render is fully reproducible.</li>\n<li><strong>Every beat mp4 is exactly the configured fps (25/1).</strong> <code>render_beats.py</code> enforces +\nwarns; a mismatch makes the concat demuxer silently drop frames.</li>\n<li><strong>All text fits the 88% safe area at the ~15% spring-overshoot PEAK</strong>, not at rest.</li>\n<li><strong>Captions burned LAST</strong>, ≤3 words/cue, closing on &gt;0.4s gap / window edge / sentence end.\nThe ASS <code>Events Format:</code> line MUST carry the <code>Name</code> field or every cue gets a\nleading-comma artifact. Suppress the proof/footnote + end-card beats (two text layers at\none spot both go unreadable).</li>\n<li><strong>Mix constants are validated</strong> — music <code>−20 dB</code> under the VO, <code>amix normalize=0</code> (with\nnormalize on the bed pumps), ~0.8s tail fade. Sound-off must still work without the bed.</li>\n<li><strong>Keep the MYTH triad's flip grammar + internal timing identical</strong> so it reads as a\npattern (anaphora).</li>\n</ul>\n<h2>Requires</h2>\n<ul>\n<li><strong>Python 3</strong> + <strong>Playwright chromium</strong> (<code>pip install playwright &amp;&amp; playwright install chromium</code>) for the frame-exact hyperframe render, and <strong>ffmpeg/ffprobe</strong> on PATH. If\nPlaywright is unavailable, <code>compose.py</code> + <code>make_captions.py</code> (the concat / mix / caption\npath) still run; only <code>render_beats.py</code> needs the browser.</li>\n<li><code>watch</code> (QC the final master — the red strike crosses the vertical MIDDLE of EVERY wrapped\nmyth line, the VO is intelligible, captions are legible with no card collision,\nsuppression is correct on the proof + end-card beats, framerate is uniform 25/1, no\nclipping, every claim is legible sound-off). The recipe gates the paid <code>create-vo-eleven</code>\n(VO), <code>create-music-elevenlabs</code> (bed), and FAL Whisper calls — this capability itself\nmakes NO paid calls.</li>\n</ul>\n","files":[{"path":"scripts/beat_snap.py","sizeBytes":6251,"isText":true},{"path":"scripts/compose.py","sizeBytes":6951,"isText":true},{"path":"scripts/config.example.json","sizeBytes":4063,"isText":true},{"path":"scripts/hyperframes/beat-hook.html","sizeBytes":3305,"isText":false},{"path":"scripts/hyperframes/beat-myth-fact.html","sizeBytes":4169,"isText":false},{"path":"scripts/hyperframes/beat-proof.html","sizeBytes":2950,"isText":false},{"path":"scripts/hyperframes/beat-punch.html","sizeBytes":1242,"isText":false},{"path":"scripts/hyperframes/beat-turn.html","sizeBytes":1966,"isText":false},{"path":"scripts/hyperframes/_shared.css","sizeBytes":6906,"isText":false},{"path":"scripts/hyperframes/_shared.js","sizeBytes":7948,"isText":true},{"path":"scripts/make_captions.py","sizeBytes":4741,"isText":true},{"path":"scripts/render_beats.py","sizeBytes":7469,"isText":true},{"path":"SKILL.md","sizeBytes":8088,"isText":true},{"path":"skill.meta.json","sizeBytes":315,"isText":true},{"path":"tests/smoke-test.md","sizeBytes":5751,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-11T17:23:58.904881Z","sha256":"530C0172CAC476A0FF07D16201C3C2FCD25B0B341B044E479F5A82AA184E14A2","sizeBytes":32364},"review":null,"source":{"repositoryUrl":"https://github.com/gooseworks-ai/goose-skills","path":"skills/ads/capabilities/render-myth-vs-fact","license":"MIT","commit":"a9f4676e266f9f04786e3f4145baf931f61fc309","subtreeSha":"D116A5FED010F58F9827063E4C73FDCCDBEEB3ED25A6550FB168B6C4A80D537F","lastSyncedAt":"2026-09-24T06:48:52.524221Z"},"reviewedAt":"2026-09-11T17:25:32.602817Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/gooseworks-ai/goose-skills/tree/main/skills/ads/capabilities/render-myth-vs-fact"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install gooseworks-ai-goose-skills@llmmart"},{"target":"git","command":"git clone https://github.com/gooseworks-ai/goose-skills.git"}]}