{"id":"300716a7-f7d1-408b-9177-b6a21ed39a96","shortId":"QZjZLk","kind":"skill","title":"vid-product-launch","tagline":"Generates a cinematic product launch video (MP4) from a product description and launch context. 5-section narrative arc — Tease, Build, Reveal, Proof, CTA — rendered as HTML/CSS in headless Chromium via Playwright, assembled with FFmpeg. 4 tone presets. 30/60/90 second durations.","description":"# vid-product-launch\n\nGenerates a narrative MP4 product launch video from a product description and launch context.\nPipeline: HTML/CSS animations → headless Chromium (Playwright, frame-by-frame) → FFmpeg (H.264 MP4).\nNo React. No AI video APIs. No Python. Zero runtime cost beyond Playwright + FFmpeg.\n\n---\n\n## Critical Rules (read before every generation)\n\n1. **The tagline is not optional. Do not skip it.** It is the product's entire promise in 4–6 words. If the user did not provide it, derive one from the description — a sharp, active-voice distillation. Never write \"[tagline here]\" or leave it blank.\n\n2. **The reveal moment must feel earned.** The tease and build sections exist to create tension. If the product name appears in the first 20% of the video, the narrative collapses. Never reveal the product name before the Reveal section.\n\n3. **product_name font size: minimum 120px for 16:9, minimum 80px for 9:16.** The name must be the largest element at the reveal moment. If it isn't dominant, the reveal fails.\n\n4. **One proof stat. Not a list.** If the user provides multiple stats, pick the strongest one. A list of 5 numbers destroys the punch. One oversized number creates it.\n\n5. **Use `window.renderFrame(t)` — no CSS `@keyframes` for scene transitions.** CSS `currentTime` seeking is silently ignored for backward seeks in Chromium. The renderFrame function computes element styles directly from milliseconds. Playwright calls it once per frame.\n\n6. **No `animation-delay` on ANY element.** Not needed with renderFrame. If you write `animation-delay`, stop — you are using the wrong architecture.\n\n7. **`window.__videoReady = true` only inside `document.fonts.ready.then(...)`.** Never set synchronously.\n\n8. **Expose `window.__stopPreview()`.** The rAF preview loop races with Playwright's evaluate/screenshot calls. The capture script calls `__stopPreview()` before the frame loop.\n\n9. **Use `t < startMs` (not `t <= startMs`) in scene boundary checks.** `t <= 0` at frame 0 makes scene 1 black.\n\n10. **Body = exact pixel dimensions.** Width and height are integers. No `%`, `vw/vh`, `rem`.\n\n11. **Read `references/scene-library.md` AND `references/style-presets.md` before generating ANY HTML.**\n\n12. **Never dump HTML in chat.** Save to file. Show summary only.\n\n13. **Film grain canvas MUST be 240×135, not W×H.** Set `width=\"240\" height=\"135\"` on the canvas element, then stretch with `style=\"width:[W]px;height:[H]px\"`. Full-resolution grain at 1920×1080 is 64× slower — 8MB of ImageData per frame — and will make 1800-frame exports take hours.\n\n14. **The tease section MUST be dark (#000 or near-black), regardless of tone.** White backgrounds in the tease section read as demo slides, not product launch videos. The dark-to-light narrative arc (dark tease → dark build → product reveal) is how launch videos create drama. Even the `minimal` and `energetic` presets should use `background: #000` for tease-problem and tension-build scenes.\n\n15. **Each tease word must be its own beat at 200px+ font size.** Word-by-word on a single line is not punchy enough. Each problem word (e.g. \"Research.\" / \"Write.\" / \"Outreach.\" / \"Repeat.\") gets 1500–1800ms of screen time at `font-size: 200px; font-weight: 900` centered, one at a time. See scene-library.md `tease-words` pattern.\n\n16. **Build section must show content that narrates the problem — not just particles.** Use the `terminal-card` pattern: 3 cards appear sequentially with a typewriter animation showing the manual work being done. Cards have `border: 1px solid rgba(255,255,255,0.07)`, blue dot accent, monospace text. Particles alone for 20 seconds is empty screen time.\n\n17. **Dot-grid CSS background on dark scenes.** Add `background-image: radial-gradient(circle, rgba(255,255,255,0.035) 1px, transparent 1px); background-size: 60px 60px;` to build, reveal, proof, and CTA scenes. It costs zero compute and adds depth.\n\n---\n\n## Step 1: Intake\n\n**Required:**\n- `product_name` — the name of the product or feature being launched\n- `product_description` — 2–3 sentences: what it does, who it's for, key benefit\n\n**Optional parameters and defaults:**\n\n| Parameter | Default | Description |\n|---|---|---|\n| tagline | auto | 4–6 words — the product's core promise |\n| problem_statement | auto-inferred | 1 sentence for tease section (the pain the product solves) |\n| proof_stat | auto-inferred | Single metric (e.g. \"500+ teams\", \"10× faster\", \"$2M saved\") |\n| cta | auto | URL or action phrase (e.g. \"opendirectory.dev\", \"Join the waitlist\") |\n| launch_date | — | ISO date string (enables countdown-card scene) |\n| tone | cinematic | cinematic / energetic / minimal / emotional |\n| duration | 60 | 30 / 60 / 90 (seconds) |\n| aspect_ratio | 16:9 | 16:9 (1920×1080) / 9:16 (1080×1920) |\n| letterbox | false | Cinematic 2.35:1 black bars — 16:9 only |\n| music | — | Path to audio file (mp3/m4a/wav) |\n| fps | 30 | Frames per second (24, 30, or 60) |\n\n**If `product_name` or `product_description` is missing, ask exactly:**\n\n> \"To create the launch video, I need two things:\n>\n> 1. **Product name** — what is the product called?\n> 2. **Product description** — 2–3 sentences: what does it do, who is it for, what is the key benefit?\n>\n> Optional: tagline (4–6 words), proof stat (one number), CTA (URL or action phrase), tone (cinematic / energetic / minimal / emotional), duration (30 / 60 / 90s).\"\n\nIf both are present → proceed to Step 2 immediately.\n\n---\n\n## Step 2: Internal Architecture (never shown to user)\n\n**1. Derive missing params:**\n- **tagline** (if not provided): Write one from product_description. 4–6 words. Active voice. No filler (\"The future of…\", \"Introducing…\"). Derive the sharpest possible promise.\n- **problem_statement** (if not provided): Infer from product_description. 1 sentence stating the pain. Never mentions the product name. Written to make the audience nod in recognition.\n- **proof_stat** (if not provided): Infer from product_description. If no stat is available, use a credible scale indicator (\"Trusted by 500+ teams\", \"From days to minutes\").\n- **cta** (if not provided): Use product domain if inferable from product_name, or \"Learn more\" as action phrase.\n\n**2. Calculate section timing from `duration`:**\n\n| Section | 30s | 60s | 90s | Start formula |\n|---|---|---|---|---|\n| Tease | 0–5s | 0–10s | 0–15s | 0ms |\n| Build | 5–12s | 10–30s | 15–40s | Tease end |\n| Reveal | 12–20s | 30–45s | 40–60s | Build end |\n| Proof | 20–25s | 45–55s | 60–75s | Reveal end |\n| CTA | 25–30s | 55–60s | 75–90s | Proof end |\n\nConvert every boundary to milliseconds. Assign to constants:\n```\nTEASE_START_MS, TEASE_END_MS\nBUILD_START_MS, BUILD_END_MS\nREVEAL_START_MS, REVEAL_END_MS\nPROOF_START_MS, PROOF_END_MS\nCTA_START_MS, CTA_END_MS\n```\n\n**3. Select scenes per section:**\n\n- **Tease:** Always starts with `blackout-opener` (1500ms). Then `tease-words` (each problem keyword as its own 1600–1800ms beat at 200px+). If `launch_date` provided, optionally add `countdown-card` at end of tease.\n- **Build:** `terminal-card` sequence (3 cards showing manual tasks being typed in) + optional closing accent line. Duration ≥ 30s gets all 3 cards. Duration = 30s uses 2 cards. Never use bare particles-only for more than 5s — the screen must show content that narrates the problem.\n- **Reveal:** `reveal-hero` — always first in this section. Optionally followed by `tagline-card` if duration ≥ 60s.\n- **Proof:** `proof-stat` if a numeric stat is available. Otherwise `feature-bullet`.\n- **CTA:** `cta-card` — always, always last.\n\nTypical scene counts: 30s = 4 scenes, 60s = 6–7 scenes, 90s = 7–8 scenes.\n\n**4. Calculate per-scene timing:**\nWithin each section, divide time equally across scenes in that section.\nException: `blackout-opener` always gets exactly the first 1500ms of Tease.\n\n**5. Determine pixel dimensions:**\n- `16:9` → W=1920, H=1080\n- `9:16` → W=1080, H=1920\n\n**6. Letterbox calculation (if enabled, 16:9 only):**\n- 2.35:1 content height = W / 2.35 = 1920 / 2.35 ≈ 817px\n- Bar height = (H - 817) / 2 = (1080 - 817) / 2 ≈ 132px\n- Top bar: `top: 0; height: 132px`\n- Bottom bar: `bottom: 0; height: 132px`\n\n**7. Cinematic effects flags (by preset):**\n- `cinematic`: film-grain=ON (canvas 240×135, opacity 0.025), vignette=ON, light-leak=ON (warm gold), dot-grid=ON on dark scenes\n- `energetic`: film-grain=OFF, vignette=OFF, dot-grid=ON, white flash at reveal\n- `minimal`: film-grain=ON (canvas 240×135, opacity 0.022), vignette=ON (subtle, 0.5 radial), dot-grid=ON, no light-leak — accent color = #4B9FFF (electric blue)\n- `emotional`: film-grain=ON (canvas 240×135, opacity 0.018, warm tint blend), vignette=ON (soft), light-leak=ON (copper warm), dot-grid=OFF\n\n**All presets: tease and build scenes ALWAYS use dark background (#000 or near-black), regardless of preset.**\n\n**8. Embed `window.__sfxTimeline` in the HTML (always — even when no music is provided):**\n\nThe export script reads this array, synthesizes each SFX type natively with FFmpeg `aevalsrc`/`anoisesrc`, and places events at exact millisecond offsets using `adelay`. No external audio files required.\n\nSFX type reference:\n\n| Type | Sound | Duration | Notes |\n|---|---|---|---|\n| `word-hit` | Sub punch (50Hz) + transient click (2.2kHz) + noise burst | 180ms | One per tease word — 3 layers via amix |\n| `type-sequence` | Mechanical keyboard clicks at 15Hz (sin^30 pulse envelope) | 1.9s | One per card — runs for full TYPE_DUR |\n| `whoosh` | Two-band noise sweep (1.1kHz body + 4–8kHz air) | 700ms | At `BUILD_CLOSE` transition |\n| `tension-riser` | Low rumble (250Hz) + sub tone growing with t/2.8 ramp | 2.9s | Start at `BUILD_CLOSE + 700ms` — peaks at `REVEAL_START` |\n| `reveal-boom` | Sub (45Hz) + body (90Hz) + shimmer (4.5–11kHz) + 85ms echo | 900ms | Exactly at `REVEAL_START` — THE hit |\n| `counter-tick` | Harmonic click (880Hz + 440Hz + 1760Hz + 2640Hz) | 80ms | 3 beats — decrescendo 0.32 → 0.26 → 0.20 |\n| `cta-chime` | A major chord (440 + 554 + 659 + 880Hz) + aecho bell shimmer | 1.2s | Exactly at `CTA_START` |\n\nSFX timing map — compute `ms` values from section constants, never hardcode:\n\n```js\nwindow.__sfxTimeline = [\n  // ── TEASE: one word-hit per word beat, last louder ───────────────────────\n  { ms: WORD_BEATS[0].start,   sfx: 'word-hit',      vol: 0.55 },\n  { ms: WORD_BEATS[1].start,   sfx: 'word-hit',      vol: 0.55 },\n  { ms: WORD_BEATS[2].start,   sfx: 'word-hit',      vol: 0.55 },\n  { ms: WORD_BEATS[N].start,   sfx: 'word-hit',      vol: 0.65 },  // last word louder\n\n  // ── BUILD: type-sequence per card (1.9s, matches TYPE_DUR 1800ms) ────────\n  { ms: CARDS[0].start + 180,  sfx: 'type-sequence', vol: 0.28 },\n  { ms: CARDS[1].start + 180,  sfx: 'type-sequence', vol: 0.28 },\n  { ms: CARDS[2].start + 180,  sfx: 'type-sequence', vol: 0.28 },\n\n  // ── TRANSITION: whoosh then 2.9s riser peaking at REVEAL_START ───────────\n  { ms: BUILD_CLOSE,           sfx: 'whoosh',        vol: 0.50 },\n  { ms: BUILD_CLOSE + 700,     sfx: 'tension-riser', vol: 0.35 },\n\n  // ── REVEAL: the cinematic hit ─────────────────────────────────────────────\n  { ms: REVEAL_START,          sfx: 'reveal-boom',   vol: 0.88 },\n\n  // ── PROOF: harmonic ticks, decrescendo ───────────────────────────────────\n  { ms: PROOF_START,           sfx: 'counter-tick',  vol: 0.32 },\n  { ms: PROOF_START + 2200,    sfx: 'counter-tick',  vol: 0.26 },\n  { ms: PROOF_START + 4400,    sfx: 'counter-tick',  vol: 0.20 },\n\n  // ── CTA: A major chord landing ────────────────────────────────────────────\n  { ms: CTA_START,             sfx: 'cta-chime',     vol: 0.62 },\n];\n```\n\nPlace this block immediately before the preview loop, after `window.renderFrame`.\n\n---\n\n## Step 3: HTML Generation\n\nRead `references/scene-library.md` AND `references/style-presets.md` before writing any code.\nUse the exact CSS class names, HTML structure, and renderFrame patterns from those files.\n\n**Required HTML skeleton:**\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n[font CDN link from style preset]\n<style>\n:root {\n  [all CSS tokens from style preset]\n}\n\n*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }\nhtml, body {\n  width: [W]px; height: [H]px;\n  overflow: hidden;\n  background: var(--bg);\n  font-family: var(--font-body);\n  position: relative;\n}\n\n.scene {\n  position: absolute;\n  inset: 0;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  padding: 80px;\n  opacity: 0;\n  will-change: opacity, transform;\n}\n.scene-inner {\n  width: 100%;\n  max-width: 1200px;\n}\n\n[paste CSS from each scene type in scene-library.md]\n</style>\n</head>\n<body>\n\n<!-- Cinematic overlay elements — rendered on top of all scenes -->\n[if film-grain: ON]\n<canvas id=\"grain-overlay\"\n  width=\"[W]\" height=\"[H]\"\n  style=\"position:fixed;inset:0;pointer-events:none;opacity:0.025;mix-blend-mode:overlay;z-index:50\"></canvas>\n[end if]\n\n[if vignette: ON]\n<div id=\"vignette-overlay\"\n  style=\"position:fixed;inset:0;background:radial-gradient(ellipse at center,transparent 35%,rgba(0,0,0,0.65) 100%);pointer-events:none;z-index:51\"></div>\n[end if]\n\n[if light-leak: ON]\n<div id=\"light-leak\"\n  style=\"position:fixed;inset:0;background:linear-gradient(135deg,rgba(255,220,140,0.5) 0%,rgba(255,255,255,0.3) 45%,transparent 70%);opacity:0;pointer-events:none;z-index:52\"></div>\n[end if]\n\n[if letterbox: ON]\n<div id=\"lbox-top\" style=\"position:fixed;top:0;left:0;width:[W]px;height:132px;background:#000;z-index:100\"></div>\n<div id=\"lbox-bot\" style=\"position:fixed;bottom:0;left:0;width:[W]px;height:132px;background:#000;z-index:100\"></div>\n[end if]\n\n<!-- Scenes -->\n[scene HTML from scene-library.md templates, one per selected scene]\n\n<script>\nwindow.__videoReady = false;\nwindow.TOTAL_DURATION_MS = [duration * 1000];\n\n// ── Section timing constants ──────────────────────────────────────────────────\nconst TEASE_START_MS  = [N];\nconst TEASE_END_MS    = [N];\nconst BUILD_START_MS  = [N];\nconst BUILD_END_MS    = [N];\nconst REVEAL_START_MS = [N];\nconst REVEAL_END_MS   = [N];\nconst PROOF_START_MS  = [N];\nconst PROOF_END_MS    = [N];\nconst CTA_START_MS    = [N];\nconst CTA_END_MS      = [N];\n\n// ── Animation helpers ─────────────────────────────────────────────────────────\nfunction lerp(a, b, p) { return a + (b - a) * p; }\nfunction clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }\nfunction easeOutCubic(t) { return 1 - Math.pow(1 - clamp(t, 0, 1), 3); }\nfunction easeInOutCubic(t) { return t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t+2,3)/2; }\n\nfunction sceneState(t, startMs, endMs) {\n  // Standard scene envelope: fade-in, hold, fade-out\n  if (t < startMs || t >= endMs) return { opacity: 0, ty: 0 };\n  const prog = (t - startMs) / (endMs - startMs);\n  if (prog < 0.10) {\n    const p = easeOutCubic(prog / 0.10);\n    return { opacity: p, ty: lerp(20, 0, p) };\n  }\n  if (prog < 0.88) return { opacity: 1, ty: 0 };\n  // CTA scene: no exit fade (holds until end)\n  if (startMs === CTA_START_MS) return { opacity: 1, ty: 0 };\n  const p = (prog - 0.88) / 0.12;\n  return { opacity: 1 - easeOutCubic(p), ty: lerp(0, -10, p) };\n}\n\nfunction applySceneState(el, state) {\n  el.style.opacity = state.opacity.toFixed(3);\n  el.style.transform = state.ty !== 0 ? `translateY(${state.ty.toFixed(2)}px)` : '';\n}\n\n// ── Film grain (if enabled) ───────────────────────────────────────────────────\n[if film-grain: ON]\nconst grainCanvas = document.getElementById('grain-overlay');\nconst grainCtx = grainCanvas ? grainCanvas.getContext('2d') : null;\nconst W_GRAIN = [W], H_GRAIN = [H];\nfunction renderGrain(t) {\n  if (!grainCtx) return;\n  const d = grainCtx.createImageData(W_GRAIN, H_GRAIN);\n  const buf = d.data;\n  let seed = (Math.floor(t * 37) ^ 0x5E3779B9) >>> 0;\n  for (let i = 0; i < buf.length; i += 4) {\n    seed = (seed ^ (seed >>> 13)) >>> 0;\n    seed = (seed * 1664525 + 1013904223) >>> 0;\n    const n = seed >>> 24;\n    buf[i] = buf[i+1] = buf[i+2] = n;\n    buf[i+3] = 255;\n  }\n  grainCtx.putImageData(d, 0, 0);\n}\n[end if]\n\n// ── Light leak (if enabled) ───────────────────────────────────────────────────\n[if light-leak: ON]\nconst lightLeakEl = document.getElementById('light-leak');\nfunction renderLightLeak(t) {\n  if (!lightLeakEl) return;\n  const leakStart = REVEAL_START_MS;\n  const leakDur = 500; // ms\n  const lt = t - leakStart;\n  if (lt < 0 || lt > leakDur) { lightLeakEl.style.opacity = 0; return; }\n  const p = lt / leakDur;\n  const v = p < 0.35 ? p / 0.35 : 1 - (p - 0.35) / 0.65;\n  lightLeakEl.style.opacity = (v * 0.55).toFixed(3);\n}\n[end if]\n\n// ── Main render function ──────────────────────────────────────────────────────\nwindow.renderFrame = function(t) {\n\n  // Cinematic overlays\n  [if film-grain: ON] renderGrain(t); [end if]\n  [if light-leak: ON] renderLightLeak(t); [end if]\n\n  // ── TEASE SECTION ──\n  // blackout-opener\n  applySceneState(document.querySelector('.blackout-opener'), sceneState(t, TEASE_START_MS, TEASE_START_MS + 1500));\n  // Opener line appears at 40% of its scene\n  const openerLine = document.querySelector('.opener-line');\n  if (openerLine) {\n    const opP = clamp((t - (TEASE_START_MS + 600)) / 500, 0, 1);\n    openerLine.style.opacity = easeOutCubic(opP).toFixed(3);\n    openerLine.style.transform = `translateY(${lerp(8, 0, easeOutCubic(opP)).toFixed(2)}px)`;\n  }\n\n  // tease-problem\n  const tpScene = document.querySelector('.tease-problem');\n  const tpStart = TEASE_START_MS + 1500;\n  const tpEnd = TEASE_END_MS;\n  applySceneState(tpScene, sceneState(t, tpStart, tpEnd));\n  const words = tpScene ? tpScene.querySelectorAll('.word') : [];\n  if (words.length) {\n    const stagger = 180, revealStart = tpStart + (tpEnd - tpStart) * 0.15;\n    words.forEach((w, i) => {\n      const ws = revealStart + i * stagger;\n      const wp = clamp((t - ws) / 350, 0, 1);\n      w.style.opacity = easeOutCubic(wp).toFixed(3);\n      w.style.transform = `translateY(${lerp(20, 0, easeOutCubic(wp)).toFixed(2)}px)`;\n    });\n    const subLine = tpScene.querySelector('.sub-line');\n    if (subLine) {\n      const slStart = revealStart + words.length * stagger + 200;\n      const slP = clamp((t - slStart) / 400, 0, 1);\n      subLine.style.opacity = easeOutCubic(slP).toFixed(3);\n    }\n  }\n\n  [if countdown-card exists in this video]\n  // countdown-card (in tease)\n  const cdScene = document.querySelector('.countdown-card');\n  if (cdScene) {\n    applySceneState(cdScene, sceneState(t, [CD_START_MS], [CD_END_MS]));\n    const cdLabel = document.getElementById('cd-label');\n    const cdGrid  = document.getElementById('cd-grid');\n    const cdDate  = document.getElementById('cd-date');\n    if (cdLabel) cdLabel.style.opacity = easeOutCubic(clamp((t - [CD_START_MS]) / 300, 0, 1)).toFixed(3);\n    if (cdGrid)  cdGrid.style.opacity  = easeOutCubic(clamp((t - ([CD_START_MS] + 200)) / 400, 0, 1)).toFixed(3);\n    if (cdDate)  cdDate.style.opacity  = easeOutCubic(clamp((t - ([CD_START_MS] + 600)) / 400, 0, 1)).toFixed(3);\n  }\n  [end if]\n\n  // ── BUILD SECTION ──\n  // tension-build\n  const tbScene = document.querySelector('.tension-build');\n  applySceneState(tbScene, sceneState(t, BUILD_START_MS, BUILD_END_MS));\n  if (tbScene) {\n    // Particle canvas — agent replaces accent hex with literal preset color\n    const tbCanvas = document.getElementById('tension-canvas');\n    if (tbCanvas && t >= BUILD_START_MS && t < BUILD_END_MS) {\n      const tbCtx = tbCanvas.getContext('2d');\n      if (!window.__tensionParticles) {\n        window.__tensionParticles = Array.from({length: 60}, () => ({\n          x: Math.random() * [W], y: Math.random() * [H],\n          tx: [W]/2 + (Math.random()-0.5)*60, ty: [H]/2 + (Math.random()-0.5)*60,\n          r: Math.random() * 2.5 + 1,\n        }));\n      }\n      const prog = clamp((t - BUILD_START_MS) / (BUILD_END_MS - BUILD_START_MS), 0, 1);\n      tbCtx.clearRect(0, 0, [W], [H]);\n      window.__tensionParticles.forEach(p => {\n        const px = lerp(p.x, p.tx, easeInOutCubic(prog));\n        const py = lerp(p.y, p.ty, easeInOutCubic(prog));\n        tbCtx.beginPath();\n        tbCtx.arc(px, py, p.r, 0, Math.PI * 2);\n        tbCtx.fillStyle = '[ACCENT_HEX_FROM_PRESET]'; // replace with literal hex\n        tbCtx.globalAlpha = easeOutCubic(prog) * 0.55;\n        tbCtx.fill();\n        tbCtx.globalAlpha = 1;\n      });\n    }\n    // Build counter (optional label)\n    const buildCounter = document.getElementById('build-counter');\n    const buildLabel = tbScene.querySelector('.build-label');\n    if (buildCounter) {\n      const cp = clamp((t - BUILD_START_MS) / ((BUILD_END_MS - BUILD_START_MS) * 0.65), 0, 1);\n      buildCounter.style.opacity = easeOutCubic(clamp((t - BUILD_START_MS) / 300, 0, 1)).toFixed(3);\n    }\n    if (buildLabel) {\n      buildLabel.style.opacity = easeOutCubic(clamp((t - (BUILD_START_MS + 600)) / 400, 0, 1)).toFixed(3);\n    }\n  }\n\n  // ── REVEAL SECTION ──\n  // reveal-hero\n  const rhScene = document.querySelector('.reveal-hero');\n  const rhStart = REVEAL_START_MS;\n  const rhEnd   = [REVEAL_END_MS — full section if no tagline-card, else midpoint];\n  applySceneState(rhScene, sceneState(t, rhStart, rhEnd));\n  if (rhScene) {\n    const flash   = document.getElementById('reveal-flash');\n    const nameEl  = document.getElementById('product-name-el');\n    const tagEl   = document.getElementById('tagline-el');\n\n    // Flash: triangle wave over 350ms at reveal start\n    if (flash) {\n      const fp = clamp((t - rhStart) / 350, 0, 1);\n      const fv = fp < 0.4 ? fp / 0.4 : 1 - (fp - 0.4) / 0.6;\n      flash.style.opacity = (fv * 0.65).toFixed(3);\n    }\n    // Product name: materialise (cinematic/emotional) or slam (energetic) or fade (minimal)\n    if (nameEl) {\n      const nameStart = rhStart + 220;\n      const nameDur = [700 for cinematic/emotional | 120 for energetic | 450 for minimal];\n      const np = clamp((t - nameStart) / nameDur, 0, 1);\n      nameEl.style.opacity = easeOutCubic(np).toFixed(3);\n      // cinematic/emotional: blur\n      // [if cinematic or emotional]\n      nameEl.style.filter = `blur(${lerp(10, 0, easeOutCubic(np)).toFixed(2)}px)`;\n      // [if energetic]\n      // nameEl.style.transform = `scale(${lerp(1.12, 1, easeOutCubic(np)).toFixed(4)})`;\n    }\n    // Tagline: after name\n    if (tagEl) {\n      const tagStart = rhStart + 220 + [nameDur] + 150;\n      const tp = clamp((t - tagStart) / 500, 0, 1);\n      tagEl.style.opacity = easeOutCubic(tp).toFixed(3);\n    }\n  }\n\n  [if tagline-card exists]\n  // tagline-card (second beat of Reveal)\n  const tcScene = document.querySelector('.tagline-card');\n  applySceneState(tcScene, sceneState(t, [TC_START_MS], REVEAL_END_MS));\n  if (tcScene) {\n    const tcMain = document.getElementById('tagline-main');\n    const tcBar  = document.getElementById('tagline-bar');\n    if (tcMain) tcMain.style.opacity = easeOutCubic(clamp((t - [TC_START_MS]) / 500, 0, 1)).toFixed(3);\n    if (tcBar)  tcBar.style.transform = `scaleX(${easeOutCubic(clamp((t - ([TC_START_MS]+600)) / 200, 0, 1)).toFixed(3)})`;\n  }\n  [end if]\n\n  // ── PROOF SECTION ──\n  [if proof-stat]\n  const psScene = document.querySelector('.proof-stat');\n  applySceneState(psScene, sceneState(t, PROOF_START_MS, PROOF_END_MS));\n  if (psScene) {\n    const statEl    = psScene.querySelector('.stat-value');\n    const counterEl = document.getElementById('stat-counter');\n    const labelEl   = document.getElementById('stat-label');\n    const TARGET_NUM = [numeric value — agent fills this in];\n\n    const ap = clamp((t - PROOF_START_MS) / 200, 0, 1);\n    if (statEl) statEl.style.opacity = easeOutCubic(ap).toFixed(3);\n\n    const countDur = (PROOF_END_MS - PROOF_START_MS) * 0.60;\n    const cp = clamp((t - PROOF_START_MS) / countDur, 0, 1);\n    if (counterEl) counterEl.textContent = Math.round(easeOutCubic(cp) * TARGET_NUM).toLocaleString();\n\n    const labelStart = PROOF_START_MS + countDur + 80;\n    if (labelEl) labelEl.style.opacity = easeOutCubic(clamp((t - labelStart) / 400, 0, 1)).toFixed(3);\n  }\n  [end if]\n\n  [if feature-bullet]\n  const fbScene = document.querySelector('.feature-bullet');\n  applySceneState(fbScene, sceneState(t, PROOF_START_MS, PROOF_END_MS));\n  if (fbScene) {\n    const iconEl = document.getElementById('bullet-icon');\n    const mainEl = document.getElementById('bullet-main');\n    const subEl  = document.getElementById('bullet-sub');\n    if (iconEl) iconEl.style.transform  = `scaleX(${easeOutCubic(clamp((t - PROOF_START_MS) / 200, 0, 1)).toFixed(3)})`;\n    if (mainEl) mainEl.style.opacity    = easeOutCubic(clamp((t - (PROOF_START_MS + 200)) / 500, 0, 1)).toFixed(3);\n    if (subEl)  subEl.style.opacity     = easeOutCubic(clamp((t - (PROOF_START_MS + 700)) / 400, 0, 1)).toFixed(3);\n  }\n  [end if]\n\n  // ── CTA SECTION ──\n  const ctaScene = document.querySelector('.cta-card');\n  applySceneState(ctaScene, sceneState(t, CTA_START_MS, CTA_END_MS));\n  if (ctaScene) {\n    const actionEl = document.getElementById('cta-action');\n    const urlEl    = document.getElementById('cta-url');\n    const accentEl = document.getElementById('cta-accent');\n    const subEl    = document.getElementById('cta-sub');\n    if (actionEl) actionEl.style.opacity = easeOutCubic(clamp((t - CTA_START_MS) / 350, 0, 1)).toFixed(3);\n    if (urlEl) {\n      const up = clamp((t - (CTA_START_MS + 250)) / 500, 0, 1);\n      urlEl.style.opacity = easeOutCubic(up).toFixed(3);\n      // cinematic/emotional: blur reveal\n      urlEl.style.filter = `blur(${lerp(6, 0, easeOutCubic(up)).toFixed(2)}px)`;\n    }\n    if (accentEl) accentEl.style.transform = `scaleX(${easeOutCubic(clamp((t-(CTA_START_MS+800))/200,0,1)).toFixed(3)})`;\n    if (subEl) subEl.style.opacity = easeOutCubic(clamp((t-(CTA_START_MS+1050))/400,0,1)).toFixed(3);\n  }\n};\n\n// ── Preview loop (stopped by Playwright before frame capture) ─────────────────\nlet __previewActive = false;\nlet __previewRafId  = null;\nwindow.__stopPreview = function() {\n  __previewActive = false;\n  if (__previewRafId !== null) { cancelAnimationFrame(__previewRafId); __previewRafId = null; }\n};\n\ndocument.fonts.ready.then(() => {\n  window.renderFrame(0);\n  window.__videoReady = true;\n  __previewActive = true;\n  const startTime = performance.now();\n  function tick() {\n    if (!__previewActive) return;\n    const elapsed = performance.now() - startTime;\n    if (elapsed < window.TOTAL_DURATION_MS) {\n      window.renderFrame(elapsed);\n      __previewRafId = requestAnimationFrame(tick);\n    } else {\n      window.renderFrame(window.TOTAL_DURATION_MS - 1);\n      __previewActive = false;\n    }\n  }\n  __previewRafId = requestAnimationFrame(tick);\n});\n</script>\n</body>\n</html>\n```\n\n**Design quality rules:**\n- `product_name` font size: ≥120px for 16:9, ≥80px for 9:16. Non-negotiable.\n- Headline letter-spacing: `var(--tracking-tight)` — always.\n- One accent color highlight per scene.\n- `transform-origin: center center` on every element that uses `transform`.\n- Padding inside `.scene`: minimum 80px — text must never touch viewport edges.\n- Cinematic preset: all colors from `--` token variables. No free hex except on canvas fillStyle (replace `[ACCENT_HEX_FROM_PRESET]` with literal hex from preset).\n- Never write placeholder text (\"Your tagline here\", \"TBD\", \"[INSERT STAT]\").\n\n---\n\n## Step 4: Self-QA (fix every failure before Step 5)\n\n**Narrative structure:**\n- [ ] product_name does NOT appear before `REVEAL_START_MS`\n- [ ] Tagline is a real 4–6 word phrase — not a placeholder or generic line\n- [ ] `reveal-hero` scene falls within `REVEAL_START_MS` to `REVEAL_END_MS`\n- [ ] `cta-card` is the final scene, covering `CTA_START_MS` to `CTA_END_MS`\n- [ ] CTA contains actual URL or action phrase — not \"[your URL here]\"\n\n**renderFrame correctness:**\n- [ ] `window.renderFrame(t)` is a pure function — no side effects outside style writes\n- [ ] Scene boundary uses `t < startMs` (not `t <= startMs`)\n- [ ] Zero `animation-delay` or `@keyframes` for scene transitions\n- [ ] `window.__stopPreview()` exposed and checks `__previewActive` flag\n- [ ] Film grain canvas re-seeds from `t` each frame (different grain per frame)\n\n**Readiness signal:**\n- [ ] `window.__videoReady = false` declared before fonts.ready\n- [ ] `window.__videoReady = true` set ONLY inside `document.fonts.ready.then(...)`\n- [ ] `window.renderFrame(0)` called inside fonts.ready BEFORE setting `__videoReady = true`\n\n**Layout:**\n- [ ] `html, body` use exact pixel dimensions (`[W]px`, `[H]px`)\n- [ ] No `%`, `vw`, `vh`, `rem` on body width/height\n- [ ] `overflow: hidden` on `html, body`\n- [ ] All scenes `position: absolute; inset: 0`\n\n**Cinematic effects:**\n- [ ] Film grain canvas: `id=\"grain-overlay\"`, `position: fixed`, above all scenes\n- [ ] Vignette div: `id=\"vignette-overlay\"`, `position: fixed`, above all scenes\n- [ ] Light leak div: `id=\"light-leak\"`, `opacity: 0` initially, fires at `REVEAL_START_MS`\n- [ ] Letterbox (if enabled): exact bar heights for 2.35:1 ratio, `z-index: 100`\n\n**Design:**\n- [ ] `product_name` font size ≥ 120px (16:9) or ≥ 80px (9:16)\n- [ ] All colors from preset token variables\n- [ ] All fonts from preset CDN link\n- [ ] No placeholder text anywhere\n\n**Sound effects:**\n- [ ] `window.__sfxTimeline` present before the preview loop\n- [ ] `ms` values derived from actual timing constants — never hardcoded guesses\n- [ ] `reveal-boom` fires at exactly `REVEAL_START`\n- [ ] `cta-chime` fires at exactly `CTA_START`\n- [ ] No SFX event fires before 1000ms (blackout silence must be true silence)\n- [ ] Last tease word has `vol: 0.65` (louder than others at `0.55`)\n\n---\n\n## Step 5: Export\n\nDetermine slug from `product_name` (kebab-case, ≤30 chars):\n\n```bash\nmkdir -p launch/[slug]\n```\n\nSave HTML: `launch/[slug]/product-launch.html`\n\nOpen for browser preview:\n```bash\nopen launch/[slug]/product-launch.html\n```\n\nRun export (replace `[skill-root]` with path to this skill's directory):\n```bash\nbash [skill-root]/scripts/export-video.sh \\\n  launch/[slug]/product-launch.html \\\n  launch/[slug]/product-launch.mp4 \\\n  --duration [total_duration] \\\n  --fps [fps] \\\n  --width [W] \\\n  --height [H] \\\n  [--music path/to/audio.mp3]\n```\n\nFirst run installs Playwright (~200MB Chromium, cached after first use) and verifies FFmpeg.\n\n---\n\n## Step 6: Output Summary\n\n```\n## Launch Video: [product_name]\nDate: [YYYY-MM-DD] | Tone: [tone] | Duration: [N]s | Aspect: [ratio]\nSections: Tease [0–Ns] → Build [N–Ns] → Reveal [N–Ns] → Proof [N–Ns] → CTA [N–Ns]\n\nNarrative\n  Tease:  [problem_statement — 1 sentence]\n  Reveal: [product_name] — \"[tagline]\"\n  Proof:  [proof_stat]\n  CTA:    [cta]\n\nFiles\n  Source: launch/[slug]/product-launch.html\n  Output: launch/[slug]/product-launch.mp4\n\nChecklist\n- [ ] Product name does not appear before reveal section\n- [ ] Reveal moment feels distinct — flash/materialise visible\n- [ ] Tagline is present and legible\n- [ ] Proof stat counter animates\n- [ ] CTA URL readable at final frame\n- [ ] No blank frames\n```\n\n---\n\n## Prompt Tips (show when user asks for guidance)\n\n> \"Write the tagline yourself. It is the most important 4–6 words in the video — don't AI-generate it lazily.\"\n>\n> \"The reveal moment is everything. Everything before it builds tension; the reveal must feel earned.\"\n>\n> \"One benefit in the proof section. Trying to show 5 features kills launch video pacing.\"\n>\n> \"Match tone to your market: cinematic for B2C premium / Series A+, energetic for dev tools and SaaS, minimal for design-forward products, emotional for consumer / mission-driven.\"\n>\n> Good: \"Product launch video, 60 seconds. Product: OpenDirectory. Description: A library of pre-built AI agent skills for Claude, Codex, and Gemini. Covers GTM, content, research, and developer tools. Tagline: 'AI skills, ready to install.' Tone: minimal. Proof: '52+ skills, zero setup.' CTA: 'Browse skills at opendirectory.dev.' Aspect: 16:9.\"\n>\n> ❌ Bad: \"launch video for our new product\"","tags":["vid","product","launch","opendirectory","varnan-tech","agent-skills","gtm","hermes-agent","marketing-skills","openclaw-skills","skill-pack","skills"],"capabilities":["skill","source-varnan-tech","skill-vid-product-launch","topic-agent-skills","topic-gtm","topic-hermes-agent","topic-marketing-skills","topic-openclaw-skills","topic-skill-pack","topic-skills","topic-technical-seo"],"categories":["opendirectory"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/Varnan-Tech/opendirectory/vid-product-launch","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add Varnan-Tech/opendirectory","source_repo":"https://github.com/Varnan-Tech/opendirectory","install_from":"skills.sh"}},"qualityScore":"0.593","qualityRationale":"deterministic score 0.59 from registry signals: · indexed on github topic:agent-skills · 286 github stars · SKILL.md body (31,682 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T18:54:44.383Z","embedding":null,"createdAt":"2026-05-06T06:55:03.924Z","updatedAt":"2026-05-18T18:54:44.383Z","lastSeenAt":"2026-05-18T18:54:44.383Z","tsv":"'/product-launch.html':2335,2344,2366,2449 '/product-launch.mp4':2369,2453 '/scripts/export-video.sh':2363 '0':351,354,1018,1020,1022,1314,1320,1653,1711,2137,2173,2207,2416 '0.018':1406 '0.022':1378 '0.025':1338 '0.035':650 '0.07':614 '0.20':1607,1814 '0.26':1606,1804 '0.28':1719,1730,1741 '0.32':1605,1794 '0.35':1768 '0.5':1382 '0.50':1758 '0.55':1660,1671,1682,2312 '0.62':1828 '0.65':1693,2307 '0.88':1781 '000':453,503,1433 '0ms':1024 '1':96,357,674,724,797,837,904,942,1294,1664,1722,2222,2434 '1.1':1539 '1.2':1621 '1.9':1523,1703 '10':359,744,1028 '100':2227 '1000ms':2295 '1080':429,788,791,1278,1282,1307 '10s':1021 '11':372 '11khz':1582 '12':381,1035 '120px':189,1915,2233 '12s':1027 '13':393 '132px':1310,1316,1322 '135':400,408,1336,1376,1404 '14':446 '15':513,1030 '1500':547 '1500ms':1111,1266 '15hz':1518 '15s':1023 '16':191,197,572,783,785,790,800,1273,1280,1290,1917,1922,2234,2239,2625 '1600':1122 '17':629 '1760hz':1599 '180':1713,1724,1735 '1800':441 '1800ms':548,1123,1708 '180ms':1502 '1920':428,787,792,1276,1284,1299 '1px':608,651,653 '2':143,690,845,848,894,897,1005,1166,1306,1309,1675,1733 '2.2':1498 '2.35':796,1293,1298,1300,2221 '2.9':1562,1745 '20':167,623,1044 '200mb':2385 '200px':523,556,1126 '20s':1036 '2200':1798 '24':814 '240':399,406,1335,1375,1403 '25':1053 '250hz':1555 '255':611,612,613,647,648,649 '25s':1045 '2640hz':1600 '2m':746 '3':183,591,691,849,1099,1145,1161,1507,1602,1840 '30':777,810,815,884,1037,1520,2324 '30/60/90':42 '30s':1012,1029,1054,1158,1164,1229 '4':39,114,217,711,866,917,1230,1240,1542,1998,2023,2504 '4.5':1581 '40':1039 '40s':1031 '440':1614 '4400':1808 '440hz':1598 '45':1046 '45hz':1577 '45s':1038 '4b9fff':1394 '5':19,237,247,1026,1269,2007,2314,2541 '500':742,981 '50hz':1495 '52':2615 '55':1055 '554':1615 '55s':1047 '5s':1019,1177 '6':115,283,712,867,918,1233,1285,2024,2395,2505 '60':776,778,817,885,1048,2580 '60px':657,658 '60s':1013,1040,1056,1204,1232 '64':431 '659':1616 '7':308,1234,1237,1323 '700':1762 '700ms':1545,1568 '75':1057 '75s':1049 '8':317,1238,1441 '80ms':1601 '80px':194,1919,1956,2237 '817':1305,1308 '817px':1301 '85ms':1583 '880hz':1597,1617 '8khz':1543 '8mb':433 '9':192,196,339,784,786,789,801,1274,1279,1291,1918,1921,2235,2238,2626 '90':779 '900':560 '900ms':1585 '90hz':1579 '90s':886,1014,1058,1236 'absolut':2171 'accent':617,1155,1392,1936,1978 'across':1252 'action':752,876,1003,2066 'activ':132,920 'active-voic':131 'actual':2063,2268 'add':638,671,1132 'adelay':1477 'aecho':1618 'aevalsrc':1467 'agent':2592 'ai':79,2513,2591,2607 'ai-gener':2512 'air':1544 'alon':621 'alway':1105,1191,1223,1224,1261,1429,1447,1934 'amix':1510 'anim':65,286,299,598,2096,2477 'animation-delay':285,298,2095 'anoisesrc':1468 'anywher':2255 'api':81 'appear':163,593,2014,2459 'arc':22,481 'architectur':307,899 'array':1459 'ask':826,2492 'aspect':781,2412,2624 'assembl':36 'assign':1066 'audienc':956 'audio':806,1480 'auto':710,722,737,749 'auto-inf':721,736 'avail':973,1214 'b2c':2554 'background':462,502,634,640,655,1432 'background-imag':639 'background-s':654 'backward':264 'bad':2627 'band':1536 'bar':799,1302,1312,1318,2218 'bare':1170 'bash':2326,2340,2358,2359 'beat':521,1124,1603,1647,1652,1663,1674,1685 'bell':1619 'benefit':701,863,2533 'beyond':87 'black':358,457,798,1437 'blackout':1109,1259,2296 'blackout-open':1108,1258 'blank':142,2485 'blend':1409 'block':1831 'blue':615,1396 'bodi':360,1541,1578,2147,2161,2167 'boom':1575,1779,2276 'border':607 'bottom':1317,1319 'boundari':348,1063,2087 'brows':2620 'browser':2338 'build':24,153,485,511,573,660,1025,1041,1075,1078,1140,1427,1547,1566,1697,1753,1760,2418,2525 'built':2590 'bullet':1218 'burst':1501 'cach':2387 'calcul':1006,1241,1287 'call':278,329,333,844,2138 'canva':396,411,1334,1374,1402,1975,2111,2178 'captur':331 'card':589,592,605,767,1135,1143,1146,1162,1167,1201,1222,1527,1702,1710,1721,1732,2048 'case':2323 'cdn':1870,2250 'center':561,1944,1945 'char':2325 'chat':386 'check':349,2106 'checklist':2454 'chime':1610,1826,2284 'chord':1613,1818 'chromium':33,67,267,2386 'cinemat':7,770,771,795,879,1324,1329,1771,1963,2174,2552 'circl':645 'class':1855 'claud':2595 'click':1497,1516,1596 'close':1154,1548,1567,1754,1761 'code':1850 'codex':2596 'collaps':173 'color':1393,1937,1966,2241 'comput':271,669,1630 'constant':1068,1635,2270 'consum':2572 'contain':2062 'content':577,1182,1295,2601 'context':18,62 'convert':1061 'copper':1417 'core':717 'correct':2073 'cost':86,667 'count':1228 'countdown':766,1134 'countdown-card':765,1133 'counter':1593,1791,1801,1811,2476 'counter-tick':1592,1790,1800,1810 'cover':2053,2599 'creat':157,245,492,829 'credibl':976 'critic':90 'css':252,257,633,1854 'cta':27,664,748,873,987,1052,1093,1096,1219,1221,1609,1625,1815,1821,1825,2047,2054,2058,2061,2283,2288,2427,2443,2444,2478,2619 'cta-card':1220,2046 'cta-chim':1608,1824,2282 'currenttim':258 'dark':452,477,482,484,636,1352,1431 'dark-to-light':476 'date':760,762,1129,2402 'day':984 'dd':2406 'declar':2127 'decrescendo':1604,1785 'default':705,707 'delay':287,300,2097 'demo':469 'depth':672 'deriv':124,905,928,2266 'descript':15,59,128,689,708,823,847,916,941,968,2584 'design':1908,2228,2567 'design-forward':2566 'destroy':239 'determin':1270,2316 'dev':2560 'develop':2604 'differ':2119 'dimens':363,1272,2151 'direct':274 'directori':2357 'distil':134 'distinct':2466 'div':2189,2201 'divid':1249 'document.fonts.ready.then':313,2135 'domain':993 'domin':213 'done':604 'dot':616,631,1348,1362,1385,1420 'dot-grid':630,1347,1361,1384,1419 'drama':493 'driven':2575 'dump':383 'dur':1532,1707 'durat':44,775,883,1010,1157,1163,1203,1488,2370,2372,2409 'e.g':541,741,754 'earn':149,2531 'echo':1584 'edg':1962 'effect':1325,2082,2175,2257 'electr':1395 'element':204,272,290,412,1948 'emb':1442 'emot':774,882,1397,2570 'empti':626 'enabl':764,1289,2216 'end':1033,1042,1051,1060,1073,1079,1085,1091,1097,1137,1880,1885,1892,1897,2044,2059 'energet':498,772,880,1354,2558 'enough':537 'entir':111 'envelop':1522 'equal':1251 'evaluate/screenshot':328 'even':494,1448 'event':1471,2292 'everi':94,1062,1947,2003 'everyth':2521,2522 'exact':361,827,1263,1473,1586,1623,1853,2149,2217,2279,2287 'except':1257,1973 'exist':155 'export':443,1455,2315,2346 'expos':318,2104 'extern':1479 'fail':216 'failur':2004 'fall':2037 'fals':794,2126 'faster':745 'featur':685,1217,2542 'feature-bullet':1216 'feel':148,2465,2530 'ffmpeg':38,73,89,1466,2393 'file':389,807,1481,1864,2445 'filler':923 'fillstyl':1976 'film':394,1331,1356,1371,1399,1877,2109,2176 'film-grain':1330,1355,1370,1398,1876 'final':2051,2482 'fire':2209,2277,2285,2293 'first':166,1192,1265,2381,2389 'fix':2002,2184,2195 'flag':1326,2108 'flash':1366 'flash/materialise':2467 'follow':1197 'font':186,524,554,558,1869,1913,2231,2247 'font-siz':553 'font-weight':557 'fonts.ready':2129,2140 'formula':1016 'forward':2568 'fps':809,2373,2374 'frame':70,72,282,337,353,437,442,811,2118,2122,2483,2486 'frame-by-fram':69 'free':1971 'full':424,1530 'full-resolut':423 'function':270,2079 'futur':925 'gemini':2598 'generat':5,49,95,378,1842,2514 'generic':2031 'get':546,1159,1262 'gold':1346 'good':2576 'gradient':644 'grain':395,426,1332,1357,1372,1400,1878,2110,2120,2177,2181 'grain-overlay':2180 'grid':632,1349,1363,1386,1421 'grow':1558 'gtm':2600 'guess':2273 'guidanc':2494 'h':403,421,1277,1283,1304,2154,2378 'h.264':74 'hardcod':1637,2272 'harmon':1595,1783 'headless':32,66 'headlin':1926 'height':366,407,420,1296,1303,1315,1321,2219,2377 'hero':1190,2035 'hex':1972,1979,1984 'hidden':2164 'highlight':1938 'hit':1492,1591,1644,1658,1669,1680,1691,1772 'hour':445 'html':380,384,1446,1841,1857,1866,1868,1900,2146,2166,2332 'html/css':30,64 'id':2179,2190,2202 'ignor':262 'imag':641 'imagedata':435 'immedi':895,1832 'import':2503 'index':2226 'indic':978 'infer':723,738,938,965,995 'initi':2208 'insert':1995 'inset':2172 'insid':312,1953,2134,2139 'instal':2383,2611 'intak':675 'integ':368 'intern':898 'introduc':927 'isn':211 'iso':761 'join':756 'js':1638 'kebab':2322 'kebab-cas':2321 'key':700,862 'keyboard':1515 'keyfram':253,2099 'keyword':1118 'khz':1499,1540 'kill':2543 'land':1819 'largest':203 'last':1225,1648,1694,2302 'launch':4,9,17,48,54,61,473,490,687,759,831,1128,2329,2333,2342,2364,2367,2398,2447,2451,2544,2578,2628 'layer':1508 'layout':2145 'lazili':2516 'leak':1343,1391,1415,1890,2200,2205 'learn':1000 'leav':140 'legibl':2473 'letter':1928 'letter-spac':1927 'letterbox':793,1286,1895,2214 'librari':2586 'light':479,1342,1390,1414,1889,2199,2204 'light-leak':1341,1389,1413,1888,2203 'line':533,1156,2032 'link':1871,2251 'list':223,235 'liter':1983 'loop':323,338,1836,2263 'louder':1649,1696,2308 'low':1553 'major':1612,1817 'make':355,440,954 'manual':601,1148 'map':1629 'market':2551 'match':1705,2547 'mechan':1514 'mention':948 'metric':740 'millisecond':276,1065,1474 'minim':496,773,881,1369,2564,2613 'minimum':188,193,1955 'minut':986 'miss':825,906 'mission':2574 'mission-driven':2573 'mkdir':2327 'mm':2405 'moment':146,208,2464,2519 'monospac':618 'mp3/m4a/wav':808 'mp4':11,52,75 'ms':1071,1074,1077,1080,1083,1086,1089,1092,1095,1098,1631,1650,1661,1672,1683,1709,1720,1731,1752,1759,1773,1786,1795,1805,1820,2018,2041,2045,2056,2060,2213,2264 'multipl':228 'music':803,1451,2379 'must':147,200,397,450,517,575,1180,1958,2298,2529 'n':1686,2410,2419,2422,2425,2428 'name':162,178,185,199,678,680,820,839,951,998,1856,1912,2011,2230,2320,2401,2438,2456 'narrat':21,51,172,480,579,1184,2008,2430 'nativ':1464 'near':456,1436 'near-black':455,1435 'need':292,834 'negoti':1925 'never':135,174,314,382,900,947,1168,1636,1959,1987,2271 'new':2632 'nod':957 'nois':1500,1537 'non':1924 'non-negoti':1923 'note':1489 'ns':2417,2420,2423,2426,2429 'number':238,244,872 'numer':1211 'offset':1475 'one':125,218,233,242,562,871,913,1503,1525,1641,1904,1935,2532 'opac':1337,1377,1405,2206 'open':1110,1260,2336,2341 'opendirectori':2583 'opendirectory.dev':755,2623 'option':101,702,864,1131,1153,1196 'origin':1943 'other':2310 'otherwis':1215 'output':2396,2450 'outreach':544 'outsid':2083 'overflow':2163 'overlay':2182,2193 'overs':243 'p':2328 'pace':2546 'pad':1952 'pain':730,946 'param':907 'paramet':703,706 'particl':584,620,1172 'particles-on':1171 'path':804,2352 'path/to/audio.mp3':2380 'pattern':571,590,1861 'peak':1569,1748 'per':281,436,812,1102,1243,1504,1526,1645,1701,1905,1939,2121 'per-scen':1242 'phrase':753,877,1004,2026,2067 'pick':230 'pipelin':63 'pixel':362,1271,2150 'place':1470,1829 'placehold':1989,2029,2253 'playwright':35,68,88,277,326,2384 'posit':2170,2183,2194 'possibl':931 'pre':2589 'pre-built':2588 'premium':2555 'present':890,2259,2471 'preset':41,499,1328,1424,1440,1874,1964,1981,1986,2243,2249 'preview':322,1835,2262,2339 'previewact':2107 'problem':507,539,581,719,933,1117,1186,2432 'proceed':891 'product':3,8,14,47,53,58,109,161,177,184,472,486,677,683,688,715,732,819,822,838,843,846,915,940,950,967,992,997,1911,2010,2229,2319,2400,2437,2455,2569,2577,2582,2633 'promis':112,718,932 'prompt':2487 'proof':26,219,662,734,869,960,1043,1059,1087,1090,1205,1207,1782,1787,1796,1806,2424,2440,2441,2474,2536,2614 'proof-stat':1206 'provid':122,227,911,937,964,990,1130,1453 'puls':1521 'punch':241,1494 'punchi':536 'pure':2078 'px':419,422,2153,2155 'python':83 'qa':2001 'qualiti':1909 'race':324 'radial':643,1383 'radial-gradi':642 'raf':321 'ramp':1561 'ratio':782,2223,2413 're':2113 're-se':2112 'react':77 'read':92,373,467,1457,1843 'readabl':2480 'readi':2123,2609 'real':2022 'recognit':959 'refer':1485 'references/scene-library.md':374,1844 'references/style-presets.md':376,1846 'regardless':458,1438 'rem':371,2159 'render':28 'renderfram':269,294,1860,2072 'repeat':545 'replac':1977,2347 'requir':676,1482,1865 'research':542,2602 'resolut':425 'reveal':25,145,175,181,207,215,487,661,1034,1050,1081,1084,1187,1189,1368,1571,1574,1588,1750,1769,1774,1778,2016,2034,2039,2043,2211,2275,2280,2421,2436,2461,2463,2518,2528 'reveal-boom':1573,1777,2274 'reveal-hero':1188,2033 'rgba':610,646 'riser':1552,1747,1766 'root':2350,2362 'rule':91,1910 'rumbl':1554 'run':1528,2345,2382 'runtim':85 'saa':2563 'save':387,747,2331 'scale':977 'scene':255,347,356,512,637,665,768,1101,1227,1231,1235,1239,1244,1253,1353,1428,1899,1907,1940,1954,2036,2052,2086,2101,2169,2187,2198 'scene-library.md':567,1902 'screen':550,627,1179 'script':332,1456 'second':43,624,780,813,2581 'section':20,154,182,449,466,574,728,1007,1011,1103,1195,1248,1256,1634,2414,2462,2537 'see':566 'seed':2114 'seek':259,265 'select':1100,1906 'self':2000 'self-qa':1999 'sentenc':692,725,850,943,2435 'sequenc':1144,1513,1700,1717,1728,1739 'sequenti':594 'seri':2556 'set':315,404,2132,2142 'setup':2618 'sfx':1462,1483,1627,1655,1666,1677,1688,1714,1725,1736,1755,1763,1776,1789,1799,1809,1823,2291 'sharp':130 'sharpest':930 'shimmer':1580,1620 'show':390,576,599,1147,1181,2489,2540 'shown':901 'side':2081 'signal':2124 'silenc':2297,2301 'silent':261 'sin':1519 'singl':532,739 'size':187,525,555,656,1914,2232 'skeleton':1867 'skill':2349,2355,2361,2593,2608,2616,2621 'skill-root':2348,2360 'skill-vid-product-launch' 'skip':104 'slide':470 'slower':432 'slug':2317,2330,2334,2343,2365,2368,2448,2452 'soft':1412 'solid':609 'solv':733 'sound':1487,2256 'sourc':2446 'source-varnan-tech' 'space':1929 'start':1015,1070,1076,1082,1088,1094,1106,1564,1572,1589,1626,1654,1665,1676,1687,1712,1723,1734,1751,1775,1788,1797,1807,1822,2017,2040,2055,2212,2281,2289 'startm':342,345,2090,2093 'stat':220,229,735,870,961,971,1208,1212,1996,2442,2475 'state':944 'statement':720,934,2433 'step':673,893,896,1839,1997,2006,2313,2394 'stop':301 'stoppreview':334 'stretch':414 'string':763 'strongest':232 'structur':1858,2009 'style':273,416,1873,2084 'sub':1493,1556,1576 'subtl':1381 'summari':391,2397 'sweep':1538 'synchron':316 'synthes':1460 't/2.8':1560 'taglin':98,137,709,865,908,1200,1992,2019,2439,2469,2497,2606 'tagline-card':1199 'take':444 'task':1149 'tbd':1994 'team':743,982 'teas':23,151,448,465,483,506,515,569,727,1017,1032,1069,1072,1104,1114,1139,1268,1425,1505,1640,2303,2415,2431 'tease-problem':505 'tease-word':568,1113 'templat':1903 'tension':158,510,1551,1765,2526 'tension-build':509 'tension-ris':1550,1764 'termin':588,1142 'terminal-card':587,1141 'text':619,1957,1990,2254 'thing':836 'tick':1594,1784,1792,1802,1812 'tight':1933 'time':551,565,628,1008,1245,1250,1628,2269 'tint':1408 'tip':2488 'token':1968,2244 'tone':40,460,769,878,1557,2407,2408,2548,2612 'tool':2561,2605 'top':1311,1313 'topic-agent-skills' 'topic-gtm' 'topic-hermes-agent' 'topic-marketing-skills' 'topic-openclaw-skills' 'topic-skill-pack' 'topic-skills' 'topic-technical-seo' 'total':2371 'touch':1960 'track':1932 'tracking-tight':1931 'transform':1942,1951 'transform-origin':1941 'transient':1496 'transit':256,1549,1742,2102 'transpar':652 'tri':2538 'true':310,2131,2144,2300 'trust':979 'two':835,1535 'two-band':1534 'type':1151,1463,1484,1486,1512,1531,1699,1706,1716,1727,1738 'type-sequ':1511,1698,1715,1726,1737 'typewrit':597 'typic':1226 'url':750,874,2064,2070,2479 'use':248,304,340,501,585,974,991,1165,1169,1430,1476,1851,1950,2088,2148,2390 'user':119,226,903,2491 'valu':1632,2265 'var':1930 'variabl':1969,2245 'verifi':2392 'vh':2158 'via':34,1509 'vid':2,46 'vid-product-launch':1,45 'video':10,55,80,170,474,491,832,2399,2509,2545,2579,2629 'videoreadi':2143 'viewport':1961 'vignett':1339,1359,1379,1410,1883,2188,2192 'vignette-overlay':2191 'visibl':2468 'voic':133,921 'vol':1659,1670,1681,1692,1718,1729,1740,1757,1767,1780,1793,1803,1813,1827,2306 'vw':2157 'vw/vh':370 'w':402,418,1275,1281,1297,2152,2376 'waitlist':758 'warm':1345,1407,1418 'weight':559 'white':461,1365 'whoosh':1533,1743,1756 'width':364,405,417,2375 'width/height':2162 'window.__sfxtimeline':1443,1639,2258 'window.__stoppreview':319,2103 'window.__videoready':309,2125,2130 'window.renderframe':249,1838,2074,2136 'within':1246,2038 'word':116,516,527,529,540,570,713,868,919,1115,1491,1506,1643,1646,1651,1657,1662,1668,1673,1679,1684,1690,1695,2025,2304,2506 'word-by-word':526 'word-hit':1490,1642,1656,1667,1678,1689 'work':602 'write':136,297,543,912,1848,1988,2085,2495 'written':952 'wrong':306 'yyyi':2404 'yyyy-mm-dd':2403 'z':2225 'z-index':2224 'zero':84,668,2094,2617","prices":[{"id":"6d4d9213-5689-48ef-a9e9-f45c80d3470c","listingId":"300716a7-f7d1-408b-9177-b6a21ed39a96","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"Varnan-Tech","category":"opendirectory","install_from":"skills.sh"},"createdAt":"2026-05-06T06:55:03.924Z"}],"sources":[{"listingId":"300716a7-f7d1-408b-9177-b6a21ed39a96","source":"github","sourceId":"Varnan-Tech/opendirectory/vid-product-launch","sourceUrl":"https://github.com/Varnan-Tech/opendirectory/tree/main/skills/vid-product-launch","isPrimary":false,"firstSeenAt":"2026-05-06T06:55:03.924Z","lastSeenAt":"2026-05-18T18:54:44.383Z"}],"details":{"listingId":"300716a7-f7d1-408b-9177-b6a21ed39a96","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"Varnan-Tech","slug":"vid-product-launch","github":{"repo":"Varnan-Tech/opendirectory","stars":286,"topics":["agent-skills","gtm","hermes-agent","marketing-skills","openclaw-skills","skill-pack","skills","technical-seo"],"license":"mit","html_url":"https://github.com/Varnan-Tech/opendirectory","pushed_at":"2026-05-18T18:27:10Z","description":" AI Agent Skills built for Founders who hate Marketing","skill_md_sha":"b143c6e012896338b881673da045b7bf667b8793","skill_md_path":"skills/vid-product-launch/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/Varnan-Tech/opendirectory/tree/main/skills/vid-product-launch"},"layout":"multi","source":"github","category":"opendirectory","frontmatter":{"name":"vid-product-launch","description":"Generates a cinematic product launch video (MP4) from a product description and launch context. 5-section narrative arc — Tease, Build, Reveal, Proof, CTA — rendered as HTML/CSS in headless Chromium via Playwright, assembled with FFmpeg. 4 tone presets. 30/60/90 second durations. Trigger when user says \"product launch video\", \"launch video\", \"product reveal video\", \"cinematic product video\", \"product announcement video\", or \"launch day video\".","compatibility":"[claude-code, gemini-cli, github-copilot]"},"skills_sh_url":"https://skills.sh/Varnan-Tech/opendirectory/vid-product-launch"},"updatedAt":"2026-05-18T18:54:44.383Z"}}