$npx -y skills add Varnan-Tech/opendirectory --skill vid-motion-graphicsGenerates motion graphics videos (MP4) from a content brief. Multi-scene HTML/CSS animations rendered frame-by-frame in headless Chromium via Playwright, assembled with FFmpeg. 1080×1080 default, 16:9 (1920×1080) and 9:16 (1080×1920) supported. 5 style presets. Trigger when user
| 1 | # vid-motion-graphics |
| 2 | |
| 3 | Generates multi-scene motion graphics as MP4. Renders HTML/CSS animations in headless Chromium via Playwright (Web Animations API frame-seeking), assembles PNG frames with FFmpeg. No React, no AI APIs, no Python — zero new dependencies beyond the graphic-gif family. |
| 4 | |
| 5 | CDN fonts only. No external libraries in HTML. |
| 6 | |
| 7 | --- |
| 8 | |
| 9 | ## Critical Rules (read before every generation) |
| 10 | |
| 11 | 1. **Use `window.renderFrame(t)` — no CSS `@keyframes` for scene transitions.** CSS animation `currentTime` seeking is silently ignored for backward seeks in Chromium. The renderFrame approach: a pure JS function computes `opacity`/`transform` directly from milliseconds. Playwright calls it once per frame. Deterministic, race-free. |
| 12 | 2. **No `animation-delay` on ANY element.** Not needed with renderFrame. If you catch yourself writing `animation-delay`, stop — you're using the wrong architecture. |
| 13 | 3. **`window.__videoReady = true` only inside `document.fonts.ready.then(...)`.** Never set synchronously — fonts must load before Playwright captures frame 1 or text renders with fallback fonts. |
| 14 | 4. **Expose `window.__stopPreview()`.** The browser's rAF preview loop races with Playwright's evaluate/screenshot calls. `capture-frames.mjs` calls `__stopPreview()` before the frame loop. Always include it. |
| 15 | 5. **Use `t < startMs` (not `<=`) in scene boundary checks.** `t <= 0` at frame 0 makes scene 1 black. The correct guard is `if (t < startMs || t >= endMs) return hidden`. |
| 16 | 6. **Body = exact pixel dimensions.** Width and height are integers (`1080px`, `1920px`). No `%`, `vw/vh`, or responsive units. |
| 17 | 7. **No two scenes visible simultaneously** (except 10% enter overlap). All scenes `opacity: 0` outside their renderFrame window. |
| 18 | 8. **Transitions use `opacity` only.** No `display` toggle, no `visibility` — GPU-composited opacity is frame-perfect. |
| 19 | 9. **Never dump HTML in chat.** Save to file, show summary only. |
| 20 | 10. **Title states the message, not the topic.** "3 Reasons Q4 Crushed Targets" not "Q4 2024 Performance Video". |
| 21 | 11. **Read `references/scene-library.md` before generating ANY HTML.** Use exact HTML structure and CSS class names from that file. |
| 22 | |
| 23 | --- |
| 24 | |
| 25 | ## Step 1: Intake |
| 26 | |
| 27 | **Required:** `content_brief` |
| 28 | |
| 29 | **Optional parameters and defaults:** |
| 30 | |
| 31 | | Parameter | Default | Description | |
| 32 | |---|---|---| |
| 33 | | content_brief | — | Text describing what the video communicates (required) | |
| 34 | | scenes | auto | Number of scenes (1–6). Auto = derived from brief. | |
| 35 | | duration_per_scene | 3s | Duration per scene in seconds (1–8s) | |
| 36 | | style | kinetic-dark | kinetic-dark / editorial-light / data-pulse / bold-type / minimal-clean | |
| 37 | | aspect_ratio | 1:1 | 1:1 (1080×1080) / 16:9 (1920×1080) / 9:16 (1080×1920) | |
| 38 | | fps | 30 | Frames per second (24, 30, or 60) | |
| 39 | | music | none | Path to audio file for background track (mp3/m4a/wav) | |
| 40 | | source | — | Source attribution shown in final frame footer | |
| 41 | |
| 42 | **If `content_brief` is missing, ask exactly:** |
| 43 | |
| 44 | > "To create the video, I need a content brief — what should the video communicate? |
| 45 | > |
| 46 | > Example: 'Show 3 reasons why Q4 revenue grew 85%: new enterprise deals, reduced churn, price increase. Use bold numbers. Style: data-pulse.' |
| 47 | > |
| 48 | > Optional: style (default: kinetic-dark), aspect ratio (default: 1:1), seconds per scene (default: 3s)" |
| 49 | |
| 50 | If `content_brief` is present → proceed to Step 2 immediately. |
| 51 | |
| 52 | --- |
| 53 | |
| 54 | ## Step 2: Internal Architecture (never shown to user) |
| 55 | |
| 56 | **1. Parse brief into scenes (max 6):** |
| 57 | - Scene 1 = hook or title (always) |
| 58 | - Scenes 2–N-1 = supporting points, metrics, or story beats |
| 59 | - Scene N = CTA or closing summary (always, if more than 1 scene) |
| 60 | - One key idea per scene — if brief has 7+ ideas, consolidate the weakest ones |
| 61 | |
| 62 | **2. Read `references/scene-library.md`** — choose scene type for each scene: |
| 63 | - Hook/opening → `title-card` |
| 64 | - Single metric → `stat-reveal` |
| 65 | - List of 2–4 points → `bullet-list` |
| 66 | - Before vs after / two values → `split-screen` |
| 67 | - Testimonial / quote → `quote-card` |
| 68 | - Final / CTA → `cta-card` |
| 69 | |
| 70 | **3. Read `references/style-presets.md`** — load CSS tokens + animation personality for chosen style. |
| 71 | |
| 72 | **4. Calculate timing:** |
| 73 | ``` |
| 74 | totalDuration = sceneCount × duration_per_scene (seconds) |
| 75 | totalFrames = totalDuration × fps |
| 76 | ``` |
| 77 | Each scene occupies `(100 / sceneCount)%` of the total `@keyframes` range. |
| 78 | |
| 79 | | Scene | Start % | End % | |
| 80 | |---|---|---| |
| 81 | | 1 | 0% | (100/N)% | |
| 82 | | 2 | (100/N)% | (200/N)% | |
| 83 | | … | … | … | |
| 84 | | N | ((N-1)×100/N)% | 100% | |
| 85 | |
| 86 | Within each scene's range: |