@opendirectory.dev/skills 0.1.60 → 0.1.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/registry.json +10 -0
- package/skills/vid-motion-graphics/README.md +87 -0
- package/skills/vid-motion-graphics/SKILL.md +332 -0
- package/skills/vid-motion-graphics/evals/evals.json +27 -0
- package/skills/vid-motion-graphics/references/scene-library.md +504 -0
- package/skills/vid-motion-graphics/references/style-presets.md +202 -0
- package/skills/vid-motion-graphics/scripts/capture-frames.mjs +187 -0
- package/skills/vid-motion-graphics/scripts/export-video.sh +265 -0
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -462,6 +462,16 @@
|
|
|
462
462
|
"version": "0.0.1",
|
|
463
463
|
"path": "skills/vc-finder"
|
|
464
464
|
},
|
|
465
|
+
{
|
|
466
|
+
"name": "vid-motion-graphics",
|
|
467
|
+
"description": "Generates motion graphics videos (MP4) from a content brief.",
|
|
468
|
+
"tags": [
|
|
469
|
+
"Copywriting"
|
|
470
|
+
],
|
|
471
|
+
"author": "OpenDirectory",
|
|
472
|
+
"version": "1.0.0",
|
|
473
|
+
"path": "skills/vid-motion-graphics"
|
|
474
|
+
},
|
|
465
475
|
{
|
|
466
476
|
"name": "where-your-customer-lives",
|
|
467
477
|
"description": "Given a product utility and ICP, researches the internet to find the specific channels.",
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# vid-motion-graphics
|
|
2
|
+
|
|
3
|
+
Generates multi-scene motion graphics as MP4 from a content brief. HTML/CSS animations rendered frame-by-frame in headless Chromium via Playwright, assembled with FFmpeg. No React, no AI APIs, no Python.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx "@opendirectory.dev/skills" install vid-motion-graphics --target claude
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Video Tutorial
|
|
12
|
+
Watch this quick video to see how it's done:
|
|
13
|
+
|
|
14
|
+
https://github.com/user-attachments/assets/ee98a1b5-ebc4-452f-bbfb-c434f2935067
|
|
15
|
+
|
|
16
|
+
### Step 1: Download the skill from GitHub
|
|
17
|
+
1. Click the **Code** button on this repo's GitHub page.
|
|
18
|
+
2. Select **Download ZIP** to download the repository.
|
|
19
|
+
3. Extract the ZIP file on your computer.
|
|
20
|
+
|
|
21
|
+
### Step 2: Install the Skill in Claude
|
|
22
|
+
1. Open your **Claude desktop app**.
|
|
23
|
+
2. Go to the sidebar on the left side and click on the **Customize** section.
|
|
24
|
+
3. Click on the **Skills** tab, then click on the **+** (plus) icon button to create a new skill.
|
|
25
|
+
4. Choose the option to **Upload a skill**, and drag and drop the `.zip` file (or you can extract it and drop the folder, both work).
|
|
26
|
+
|
|
27
|
+
> **Note:** Make sure you are uploading the folder that contains the `SKILL.md` file!
|
|
28
|
+
|
|
29
|
+
## What it does
|
|
30
|
+
|
|
31
|
+
1. Takes a content brief (what each scene should say) and style params
|
|
32
|
+
2. Generates a multi-scene HTML/CSS animation file
|
|
33
|
+
3. Captures frames via Playwright + Web Animations API seeking
|
|
34
|
+
4. Assembles PNG sequence → H.264 MP4 with FFmpeg
|
|
35
|
+
5. Optionally mixes in background audio
|
|
36
|
+
|
|
37
|
+
## Output
|
|
38
|
+
|
|
39
|
+
- Format: H.264 MP4, `-pix_fmt yuv420p` (compatible with QuickTime, iOS, Android, Twitter, LinkedIn, Instagram)
|
|
40
|
+
- Default: 1080×1080px @2× retina (2160×2160 actual)
|
|
41
|
+
- Supports: 1:1, 16:9 (1920×1080), 9:16 (1080×1920)
|
|
42
|
+
- FPS: 24, 30, or 60
|
|
43
|
+
|
|
44
|
+
## Scene types
|
|
45
|
+
|
|
46
|
+
| Type | Best for |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `title-card` | Opening hook, brand intro |
|
|
49
|
+
| `stat-reveal` | Single oversized metric |
|
|
50
|
+
| `bullet-list` | 2–4 supporting points |
|
|
51
|
+
| `split-screen` | Before/after, two values |
|
|
52
|
+
| `quote-card` | Testimonial, pull quote |
|
|
53
|
+
| `cta-card` | Final scene, call to action |
|
|
54
|
+
|
|
55
|
+
## Style presets
|
|
56
|
+
|
|
57
|
+
| Preset | Feel |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `kinetic-dark` | Dark bg, electric yellow, tight grotesque (default) |
|
|
60
|
+
| `editorial-light` | White bg, serif display, refined |
|
|
61
|
+
| `data-pulse` | Deep navy, mono, terminal/dashboard |
|
|
62
|
+
| `bold-type` | White bg, Bebas Neue, red accent, slam-in |
|
|
63
|
+
| `minimal-clean` | Off-white, Cormorant, gentle rise |
|
|
64
|
+
|
|
65
|
+
## Requirements
|
|
66
|
+
|
|
67
|
+
- Node.js (for Playwright frame capture)
|
|
68
|
+
- FFmpeg (for MP4 assembly)
|
|
69
|
+
- Internet access for Google Fonts CDN during capture
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
Create a 9-second motion graphic. Brief: 'Q4 revenue hit $4.2M — 85% growth.
|
|
75
|
+
Three drivers: enterprise deals, churn 1.2%, price increase.
|
|
76
|
+
CTA: acme.com/q4'. Style: data-pulse. Aspect: 1:1.
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Quick export
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
bash scripts/export-video.sh chart/[slug]/video.html --duration 9
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Differentiator vs SkillsMP
|
|
86
|
+
|
|
87
|
+
All top SkillsMP motion-graphic skills require Remotion (React build step) or AI video APIs (Runway, Kling — cost + rate limits). This skill uses the proven HTML/CSS → Playwright → FFmpeg pipeline from the `graphic-gif` family: zero new dependencies, pixel-perfect output, full CSS control.
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vid-motion-graphics
|
|
3
|
+
description: Generates 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 says "create a video", "motion graphic", "animated video", "make a reel", "create an explainer", "animated infographic", or "short video".
|
|
4
|
+
compatibility: [claude-code, gemini-cli, github-copilot]
|
|
5
|
+
author: OpenDirectory
|
|
6
|
+
version: 1.0.0
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# vid-motion-graphics
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
CDN fonts only. No external libraries in HTML.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Critical Rules (read before every generation)
|
|
18
|
+
|
|
19
|
+
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.
|
|
20
|
+
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.
|
|
21
|
+
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.
|
|
22
|
+
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.
|
|
23
|
+
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`.
|
|
24
|
+
6. **Body = exact pixel dimensions.** Width and height are integers (`1080px`, `1920px`). No `%`, `vw/vh`, or responsive units.
|
|
25
|
+
7. **No two scenes visible simultaneously** (except 10% enter overlap). All scenes `opacity: 0` outside their renderFrame window.
|
|
26
|
+
8. **Transitions use `opacity` only.** No `display` toggle, no `visibility` — GPU-composited opacity is frame-perfect.
|
|
27
|
+
9. **Never dump HTML in chat.** Save to file, show summary only.
|
|
28
|
+
10. **Title states the message, not the topic.** "3 Reasons Q4 Crushed Targets" not "Q4 2024 Performance Video".
|
|
29
|
+
11. **Read `references/scene-library.md` before generating ANY HTML.** Use exact HTML structure and CSS class names from that file.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Step 1: Intake
|
|
34
|
+
|
|
35
|
+
**Required:** `content_brief`
|
|
36
|
+
|
|
37
|
+
**Optional parameters and defaults:**
|
|
38
|
+
|
|
39
|
+
| Parameter | Default | Description |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| content_brief | — | Text describing what the video communicates (required) |
|
|
42
|
+
| scenes | auto | Number of scenes (1–6). Auto = derived from brief. |
|
|
43
|
+
| duration_per_scene | 3s | Duration per scene in seconds (1–8s) |
|
|
44
|
+
| style | kinetic-dark | kinetic-dark / editorial-light / data-pulse / bold-type / minimal-clean |
|
|
45
|
+
| aspect_ratio | 1:1 | 1:1 (1080×1080) / 16:9 (1920×1080) / 9:16 (1080×1920) |
|
|
46
|
+
| fps | 30 | Frames per second (24, 30, or 60) |
|
|
47
|
+
| music | none | Path to audio file for background track (mp3/m4a/wav) |
|
|
48
|
+
| source | — | Source attribution shown in final frame footer |
|
|
49
|
+
|
|
50
|
+
**If `content_brief` is missing, ask exactly:**
|
|
51
|
+
|
|
52
|
+
> "To create the video, I need a content brief — what should the video communicate?
|
|
53
|
+
>
|
|
54
|
+
> Example: 'Show 3 reasons why Q4 revenue grew 85%: new enterprise deals, reduced churn, price increase. Use bold numbers. Style: data-pulse.'
|
|
55
|
+
>
|
|
56
|
+
> Optional: style (default: kinetic-dark), aspect ratio (default: 1:1), seconds per scene (default: 3s)"
|
|
57
|
+
|
|
58
|
+
If `content_brief` is present → proceed to Step 2 immediately.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Step 2: Internal Architecture (never shown to user)
|
|
63
|
+
|
|
64
|
+
**1. Parse brief into scenes (max 6):**
|
|
65
|
+
- Scene 1 = hook or title (always)
|
|
66
|
+
- Scenes 2–N-1 = supporting points, metrics, or story beats
|
|
67
|
+
- Scene N = CTA or closing summary (always, if more than 1 scene)
|
|
68
|
+
- One key idea per scene — if brief has 7+ ideas, consolidate the weakest ones
|
|
69
|
+
|
|
70
|
+
**2. Read `references/scene-library.md`** — choose scene type for each scene:
|
|
71
|
+
- Hook/opening → `title-card`
|
|
72
|
+
- Single metric → `stat-reveal`
|
|
73
|
+
- List of 2–4 points → `bullet-list`
|
|
74
|
+
- Before vs after / two values → `split-screen`
|
|
75
|
+
- Testimonial / quote → `quote-card`
|
|
76
|
+
- Final / CTA → `cta-card`
|
|
77
|
+
|
|
78
|
+
**3. Read `references/style-presets.md`** — load CSS tokens + animation personality for chosen style.
|
|
79
|
+
|
|
80
|
+
**4. Calculate timing:**
|
|
81
|
+
```
|
|
82
|
+
totalDuration = sceneCount × duration_per_scene (seconds)
|
|
83
|
+
totalFrames = totalDuration × fps
|
|
84
|
+
```
|
|
85
|
+
Each scene occupies `(100 / sceneCount)%` of the total `@keyframes` range.
|
|
86
|
+
|
|
87
|
+
| Scene | Start % | End % |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| 1 | 0% | (100/N)% |
|
|
90
|
+
| 2 | (100/N)% | (200/N)% |
|
|
91
|
+
| … | … | … |
|
|
92
|
+
| N | ((N-1)×100/N)% | 100% |
|
|
93
|
+
|
|
94
|
+
Within each scene's range:
|
|
95
|
+
- Enter: first 10% of scene range
|
|
96
|
+
- Hold: 10% to 85% of scene range
|
|
97
|
+
- Exit: 85% to 100% of scene range
|
|
98
|
+
|
|
99
|
+
**5. Determine pixel dimensions:**
|
|
100
|
+
- `1:1` → W=1080, H=1080
|
|
101
|
+
- `16:9` → W=1920, H=1080
|
|
102
|
+
- `9:16` → W=1080, H=1920
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Step 3: HTML Generation
|
|
107
|
+
|
|
108
|
+
Read `references/scene-library.md` AND `references/style-presets.md` before writing any code.
|
|
109
|
+
|
|
110
|
+
**Required HTML structure:**
|
|
111
|
+
|
|
112
|
+
```html
|
|
113
|
+
<!DOCTYPE html>
|
|
114
|
+
<html lang="en">
|
|
115
|
+
<head>
|
|
116
|
+
<meta charset="UTF-8">
|
|
117
|
+
[font CDN link from style preset]
|
|
118
|
+
<style>
|
|
119
|
+
:root {
|
|
120
|
+
[all CSS tokens from style preset]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
124
|
+
html, body {
|
|
125
|
+
width: [W]px; height: [H]px;
|
|
126
|
+
overflow: hidden;
|
|
127
|
+
background: var(--bg);
|
|
128
|
+
font-family: var(--font-body);
|
|
129
|
+
position: relative;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.scene {
|
|
133
|
+
position: absolute;
|
|
134
|
+
inset: 0;
|
|
135
|
+
display: flex;
|
|
136
|
+
flex-direction: column;
|
|
137
|
+
align-items: center;
|
|
138
|
+
justify-content: center;
|
|
139
|
+
padding: 80px;
|
|
140
|
+
opacity: 0;
|
|
141
|
+
will-change: opacity, transform;
|
|
142
|
+
}
|
|
143
|
+
.scene-inner {
|
|
144
|
+
width: 100%;
|
|
145
|
+
max-width: 960px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* Scene-type CSS from scene-library.md */
|
|
149
|
+
[paste scene-type CSS here]
|
|
150
|
+
</style>
|
|
151
|
+
</head>
|
|
152
|
+
<body>
|
|
153
|
+
|
|
154
|
+
<div class="scene scene-1">
|
|
155
|
+
<div class="scene-inner">
|
|
156
|
+
[scene 1 HTML from scene-library.md template]
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
[repeat for each scene]
|
|
161
|
+
|
|
162
|
+
<script>
|
|
163
|
+
window.__videoReady = false;
|
|
164
|
+
window.TOTAL_DURATION_MS = [totalDuration * 1000];
|
|
165
|
+
|
|
166
|
+
// ── Animation helpers ─────────────────────────────────────────────────────────
|
|
167
|
+
function lerp(a, b, p) { return a + (b - a) * p; }
|
|
168
|
+
function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }
|
|
169
|
+
function easeOutCubic(t) { return 1 - Math.pow(1 - clamp(t, 0, 1), 3); }
|
|
170
|
+
|
|
171
|
+
function sceneState(t, startMs, endMs) {
|
|
172
|
+
if (t < startMs || t >= endMs) return { opacity: 0, ty: 0 };
|
|
173
|
+
const prog = (t - startMs) / (endMs - startMs);
|
|
174
|
+
if (prog < 0.10) {
|
|
175
|
+
const p = easeOutCubic(prog / 0.10);
|
|
176
|
+
return { opacity: p, ty: lerp(24, 0, p) };
|
|
177
|
+
}
|
|
178
|
+
if (prog < 0.85) return { opacity: 1, ty: 0 };
|
|
179
|
+
const p = (prog - 0.85) / 0.15;
|
|
180
|
+
return { opacity: 1 - p, ty: lerp(0, -12, p) };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function applySceneState(el, state) {
|
|
184
|
+
el.style.opacity = state.opacity;
|
|
185
|
+
el.style.transform = state.ty !== 0 ? `translateY(${state.ty.toFixed(2)}px)` : '';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── Main render function — called by Playwright once per frame ────────────────
|
|
189
|
+
window.renderFrame = function(t) {
|
|
190
|
+
// Scene 1: 0ms – [D]ms
|
|
191
|
+
applySceneState(document.querySelector('.scene-1'), sceneState(t, 0, [D]));
|
|
192
|
+
// Scene 2: [D]ms – [2D]ms
|
|
193
|
+
applySceneState(document.querySelector('.scene-2'), sceneState(t, [D], [2D]));
|
|
194
|
+
// ... repeat per scene ...
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// ── Preview loop — stopped by Playwright before frame capture ─────────────────
|
|
198
|
+
let __previewActive = false;
|
|
199
|
+
let __previewRafId = null;
|
|
200
|
+
window.__stopPreview = function() {
|
|
201
|
+
__previewActive = false;
|
|
202
|
+
if (__previewRafId !== null) { cancelAnimationFrame(__previewRafId); __previewRafId = null; }
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
document.fonts.ready.then(() => {
|
|
206
|
+
window.renderFrame(0);
|
|
207
|
+
window.__videoReady = true;
|
|
208
|
+
__previewActive = true;
|
|
209
|
+
const startTime = performance.now();
|
|
210
|
+
function previewTick() {
|
|
211
|
+
if (!__previewActive) return;
|
|
212
|
+
const elapsed = performance.now() - startTime;
|
|
213
|
+
if (elapsed < window.TOTAL_DURATION_MS) {
|
|
214
|
+
window.renderFrame(elapsed);
|
|
215
|
+
__previewRafId = requestAnimationFrame(previewTick);
|
|
216
|
+
} else {
|
|
217
|
+
window.renderFrame(window.TOTAL_DURATION_MS - 1);
|
|
218
|
+
__previewActive = false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
__previewRafId = requestAnimationFrame(previewTick);
|
|
222
|
+
});
|
|
223
|
+
</script>
|
|
224
|
+
</body>
|
|
225
|
+
</html>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Design quality rules:**
|
|
229
|
+
- Headline font size: never smaller than 60px — text must read at mobile thumbnail size
|
|
230
|
+
- Numbers and stats: always the largest element on screen (120–200px)
|
|
231
|
+
- Tight letter-spacing on display type: `-0.02em` to `-0.04em`
|
|
232
|
+
- One accent color per scene — don't scatter accent across multiple elements
|
|
233
|
+
- Dark presets: dividers `rgba(255,255,255,0.10)`, never solid
|
|
234
|
+
- Light presets: dividers `rgba(0,0,0,0.10)`, never solid
|
|
235
|
+
- `transform-origin: center center` on every element that uses `transform`
|
|
236
|
+
- Padding inside `.scene`: minimum 80px — never let text touch viewport edges
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Step 4: Self-QA (fix every failure before Step 5)
|
|
241
|
+
|
|
242
|
+
**renderFrame correctness:**
|
|
243
|
+
- [ ] `window.renderFrame(t)` defined — pure function, no side effects outside style writes
|
|
244
|
+
- [ ] Scene boundary uses `t < startMs` (not `t <= startMs`) — avoids black frame 0
|
|
245
|
+
- [ ] Zero instances of `animation-delay` or `@keyframes` for scene transitions
|
|
246
|
+
- [ ] No two scenes have overlapping `opacity: 1` windows (except 10% enter overlap)
|
|
247
|
+
- [ ] `window.__stopPreview()` exposed and preview rAF loop checks `__previewActive`
|
|
248
|
+
|
|
249
|
+
**Readiness signal:**
|
|
250
|
+
- [ ] `window.__videoReady = false` declared before `document.fonts.ready`
|
|
251
|
+
- [ ] `window.__videoReady = true` set ONLY inside `document.fonts.ready.then(...)`
|
|
252
|
+
- [ ] `window.renderFrame(0)` called inside `document.fonts.ready.then(...)` before setting `__videoReady = true`
|
|
253
|
+
|
|
254
|
+
**Layout:**
|
|
255
|
+
- [ ] `html, body` use exact pixel dimensions (`[W]px`, `[H]px`)
|
|
256
|
+
- [ ] No `%`, `vw`, `vh`, `rem` units on `body` width/height
|
|
257
|
+
- [ ] `overflow: hidden` on `html, body`
|
|
258
|
+
- [ ] All scenes `position: absolute; inset: 0`
|
|
259
|
+
|
|
260
|
+
**Design:**
|
|
261
|
+
- [ ] All colors from style preset tokens — no free hex values
|
|
262
|
+
- [ ] All fonts from style preset — no free font-family strings
|
|
263
|
+
- [ ] Headline font size ≥ 60px
|
|
264
|
+
- [ ] Source in final scene if `source` param provided
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Step 5: Export
|
|
269
|
+
|
|
270
|
+
Determine slug from brief content (kebab-case, ≤30 chars):
|
|
271
|
+
```bash
|
|
272
|
+
mkdir -p chart/[slug]
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Save HTML: `chart/[slug]/video.html`
|
|
276
|
+
|
|
277
|
+
Browser preview:
|
|
278
|
+
```bash
|
|
279
|
+
open chart/[slug]/video.html
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Run export (replace `[skill-root]` with path to this skill's directory):
|
|
283
|
+
```bash
|
|
284
|
+
bash [skill-root]/scripts/export-video.sh \
|
|
285
|
+
chart/[slug]/video.html \
|
|
286
|
+
chart/[slug]/video.mp4 \
|
|
287
|
+
--duration [totalDuration] \
|
|
288
|
+
--fps [fps] \
|
|
289
|
+
--width [W] \
|
|
290
|
+
--height [H] \
|
|
291
|
+
[--music path/to/audio.mp3]
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The script installs Playwright on first run (~200MB Chromium), captures all frames, then assembles MP4 with FFmpeg.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Step 6: Output Summary
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
## Video: [title from brief]
|
|
302
|
+
Date: [YYYY-MM-DD] | Scenes: [N] | Style: [style] | Aspect: [ratio]
|
|
303
|
+
Duration: [N]s | FPS: [fps] | Frames: [N]
|
|
304
|
+
|
|
305
|
+
Files
|
|
306
|
+
Source: chart/[slug]/video.html
|
|
307
|
+
Output: chart/[slug]/video.mp4
|
|
308
|
+
Size: [X] MB
|
|
309
|
+
|
|
310
|
+
Checklist
|
|
311
|
+
- [ ] All scenes appear in sequence with no blank frames
|
|
312
|
+
- [ ] Text legible at mobile thumbnail size
|
|
313
|
+
- [ ] Scene transitions smooth (no jump cuts)
|
|
314
|
+
- [ ] Final scene includes CTA or closing message
|
|
315
|
+
- [ ] Source attribution present in final frame (if provided)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Prompt Tips (show when user asks for guidance)
|
|
321
|
+
|
|
322
|
+
> "Provide a content brief — bullet points of what each scene should say."
|
|
323
|
+
>
|
|
324
|
+
> "Name the insight directly. '3 reasons Q4 grew 85%' gives the video a spine."
|
|
325
|
+
>
|
|
326
|
+
> "Mention the style if you have a preference: kinetic-dark (default), editorial-light, data-pulse, bold-type, minimal-clean."
|
|
327
|
+
>
|
|
328
|
+
> "Specify aspect ratio for the platform: 1:1 for LinkedIn/Instagram feed, 9:16 for Stories/Reels, 16:9 for YouTube/presentations."
|
|
329
|
+
>
|
|
330
|
+
> ✅ Good: "Create a 9-second video. Q4 revenue hit $4.2M (85% growth). Drivers: enterprise deals, churn 1.2%, price increase. CTA: acme.com/q4. Style: data-pulse. Aspect: 1:1."
|
|
331
|
+
>
|
|
332
|
+
> ❌ Bad: "make a video about our company"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": 1,
|
|
4
|
+
"prompt": "Create a 9-second motion graphic. Brief: 'Q4 revenue hit $4.2M — 85% growth year-over-year. Three drivers: new enterprise deals, churn dropped to 1.2%, successful price increase. CTA: see full report at acme.com/q4'. Style: data-pulse. Aspect: 1:1.",
|
|
5
|
+
"expected": "4 scenes (title-card, stat-reveal for $4.2M, bullet-list for 3 drivers, cta-card), no animation-delay anywhere, all scene @keyframes as % of 9s total, window.__videoReady set inside document.fonts.ready.then(), data-pulse CSS tokens, export produces MP4 via export-video.sh --duration 9"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"id": 2,
|
|
9
|
+
"prompt": "Create a short video. Brief: 'Introducing our new API. 10× faster. 99.99% uptime. Free tier available.' Style: bold-type. Aspect: 9:16. Duration: 2s per scene.",
|
|
10
|
+
"expected": "3 scenes (title-card for intro, stat-reveal for 10×, cta-card for free tier), 9:16 = body width:1080px height:1920px, bold-type CSS tokens (Bebas Neue, --accent: #FF2B00), no animation-delay, 6s total duration, --width 1080 --height 1920 in export command"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": 3,
|
|
14
|
+
"prompt": "Make an animated reel. Brief: 'Sarah Chen, Head of Engineering. 12 years experience. Led 3 platform migrations. Currently hiring for senior roles.' Style: editorial-light.",
|
|
15
|
+
"expected": "3 scenes (title-card for name/title, bullet-list for experience points, cta-card for hiring CTA), editorial-light tokens (Playfair Display, --bg: #FAFAF8), document.fonts.ready pattern, animation-fill-mode: both on all animated elements"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": 4,
|
|
19
|
+
"prompt": "Create a video. Brief: '\"This tool saved us 20 hours a week\" — startup founder. Now used by 500+ teams.' Style: minimal-clean. Aspect: 16:9.",
|
|
20
|
+
"expected": "2 scenes (quote-card for testimonial, stat-reveal or title-card for 500+ teams), 16:9 = body width:1920px height:1080px, minimal-clean tokens (Cormorant Garamond, --bg: #F5F4F0), --width 1920 --height 1080 in export command"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": 5,
|
|
24
|
+
"prompt": "make a video",
|
|
25
|
+
"expected": "content_brief is missing. Ask: 'To create the video, I need a content brief — what should the video communicate? Example: ...' Do NOT generate any HTML. Do NOT proceed to Step 2. Wait for response."
|
|
26
|
+
}
|
|
27
|
+
]
|