@opendirectory.dev/skills 0.1.59 → 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 +18 -0
- package/skills/graphic-chart/README.md +104 -0
- package/skills/graphic-chart/SKILL.md +318 -0
- package/skills/graphic-chart/evals/evals.json +30 -0
- package/skills/graphic-chart/references/chart-library.md +487 -0
- package/skills/graphic-chart/references/style-presets.md +219 -0
- package/skills/graphic-chart/scripts/export-chart.sh +182 -0
- package/skills/graphic-chart/scripts/screenshot-chart.mjs +143 -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
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
# Scene Library — vid-motion-graphics
|
|
2
|
+
|
|
3
|
+
6 scene types with complete HTML/CSS templates. Read this before generating any HTML in Step 3.
|
|
4
|
+
|
|
5
|
+
Each template shows the HTML structure and required CSS classes. Apply colors and fonts from the chosen style preset (`references/style-presets.md`).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Timing Architecture (read this first)
|
|
10
|
+
|
|
11
|
+
All scene timing is driven by `window.renderFrame(t)` — a pure JS function called by Playwright once per frame. It computes `opacity`/`transform` directly from the millisecond timestamp. No CSS `@keyframes`, no `animation-delay`, no Web Animations API seeking.
|
|
12
|
+
|
|
13
|
+
**Why renderFrame instead of CSS animations:**
|
|
14
|
+
CSS `@keyframes` `currentTime` seeking is silently ignored for backward seeks in Chromium. A 12s animation finishes during `waitUntil: 'networkidle'` (Google Fonts CDN), so all frames capture the final state. The renderFrame approach is deterministic: same `t` → same output, always.
|
|
15
|
+
|
|
16
|
+
**Scene CSS base (apply to ALL scenes):**
|
|
17
|
+
```css
|
|
18
|
+
.scene {
|
|
19
|
+
position: absolute;
|
|
20
|
+
inset: 0;
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: center;
|
|
25
|
+
padding: 80px;
|
|
26
|
+
opacity: 0; /* start hidden — renderFrame sets opacity per frame */
|
|
27
|
+
will-change: opacity, transform;
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**renderFrame helpers (include verbatim in every HTML):**
|
|
32
|
+
```javascript
|
|
33
|
+
window.__videoReady = false;
|
|
34
|
+
window.TOTAL_DURATION_MS = N * 1000; // total duration in ms
|
|
35
|
+
|
|
36
|
+
function lerp(a, b, p) { return a + (b - a) * p; }
|
|
37
|
+
function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }
|
|
38
|
+
function easeOutCubic(t) { return 1 - Math.pow(1 - clamp(t, 0, 1), 3); }
|
|
39
|
+
|
|
40
|
+
// Returns {opacity, ty} for scene window [startMs, endMs)
|
|
41
|
+
function sceneState(t, startMs, endMs) {
|
|
42
|
+
if (t < startMs || t >= endMs) return { opacity: 0, ty: 0 };
|
|
43
|
+
const prog = (t - startMs) / (endMs - startMs);
|
|
44
|
+
if (prog < 0.10) {
|
|
45
|
+
const p = easeOutCubic(prog / 0.10);
|
|
46
|
+
return { opacity: p, ty: lerp(24, 0, p) };
|
|
47
|
+
}
|
|
48
|
+
if (prog < 0.85) return { opacity: 1, ty: 0 };
|
|
49
|
+
const p = (prog - 0.85) / 0.15;
|
|
50
|
+
return { opacity: 1 - p, ty: lerp(0, -12, p) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function applySceneState(el, state) {
|
|
54
|
+
el.style.opacity = state.opacity;
|
|
55
|
+
el.style.transform = state.ty !== 0 ? `translateY(${state.ty.toFixed(2)}px)` : '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
window.renderFrame = function(t) {
|
|
59
|
+
// Scene 1: 0ms → scene1DurMs
|
|
60
|
+
applySceneState(document.querySelector('.scene-1'), sceneState(t, 0, scene1EndMs));
|
|
61
|
+
// Scene 2: scene1EndMs → scene2EndMs
|
|
62
|
+
applySceneState(document.querySelector('.scene-2'), sceneState(t, scene1EndMs, scene2EndMs));
|
|
63
|
+
// ... repeat per scene
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Preview rAF loop — stopped by Playwright before frame capture
|
|
67
|
+
let __previewActive = false;
|
|
68
|
+
let __previewRafId = null;
|
|
69
|
+
window.__stopPreview = function() {
|
|
70
|
+
__previewActive = false;
|
|
71
|
+
if (__previewRafId !== null) { cancelAnimationFrame(__previewRafId); __previewRafId = null; }
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
document.fonts.ready.then(() => {
|
|
75
|
+
window.renderFrame(0);
|
|
76
|
+
window.__videoReady = true;
|
|
77
|
+
__previewActive = true;
|
|
78
|
+
const startTime = performance.now();
|
|
79
|
+
function previewTick() {
|
|
80
|
+
if (!__previewActive) return;
|
|
81
|
+
const elapsed = performance.now() - startTime;
|
|
82
|
+
if (elapsed < window.TOTAL_DURATION_MS) {
|
|
83
|
+
window.renderFrame(elapsed);
|
|
84
|
+
__previewRafId = requestAnimationFrame(previewTick);
|
|
85
|
+
} else {
|
|
86
|
+
window.renderFrame(window.TOTAL_DURATION_MS - 1);
|
|
87
|
+
__previewActive = false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
__previewRafId = requestAnimationFrame(previewTick);
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Timing math for N scenes of D seconds each:**
|
|
95
|
+
```
|
|
96
|
+
scene 1: startMs = 0, endMs = D*1000
|
|
97
|
+
scene 2: startMs = D*1000, endMs = D*2000
|
|
98
|
+
scene N: startMs = (N-1)*D*1000, endMs = N*D*1000
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Within each scene's `[startMs, endMs)` window:
|
|
102
|
+
- Enter: first 10% → opacity 0→1, translateY 24px→0 (easeOutCubic)
|
|
103
|
+
- Hold: 10%–85% → opacity 1, no transform
|
|
104
|
+
- Exit: 85%–100% → opacity 1→0, translateY 0→-12px
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 1. title-card
|
|
109
|
+
|
|
110
|
+
Large headline with optional eyebrow label and subtext. Best for scene 1 (hook/intro) or final scene.
|
|
111
|
+
|
|
112
|
+
**HTML:**
|
|
113
|
+
```html
|
|
114
|
+
<div class="scene scene-N">
|
|
115
|
+
<div class="scene-inner title-card">
|
|
116
|
+
<div class="eyebrow">Q4 2024</div>
|
|
117
|
+
<h1 class="headline">Revenue Grew 85%</h1>
|
|
118
|
+
<p class="subtext">Fastest quarter in company history</p>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**CSS:**
|
|
124
|
+
```css
|
|
125
|
+
.title-card {
|
|
126
|
+
text-align: center;
|
|
127
|
+
max-width: 900px;
|
|
128
|
+
}
|
|
129
|
+
.title-card .eyebrow {
|
|
130
|
+
font-family: var(--font-body);
|
|
131
|
+
font-size: 14px;
|
|
132
|
+
font-weight: 500;
|
|
133
|
+
letter-spacing: 0.12em;
|
|
134
|
+
text-transform: uppercase;
|
|
135
|
+
color: var(--accent);
|
|
136
|
+
margin-bottom: 20px;
|
|
137
|
+
}
|
|
138
|
+
.title-card .headline {
|
|
139
|
+
font-family: var(--font-display);
|
|
140
|
+
font-size: clamp(72px, 10vw, 120px);
|
|
141
|
+
font-weight: 700;
|
|
142
|
+
color: var(--text);
|
|
143
|
+
line-height: 1.0;
|
|
144
|
+
letter-spacing: -0.03em;
|
|
145
|
+
}
|
|
146
|
+
.title-card .subtext {
|
|
147
|
+
font-family: var(--font-body);
|
|
148
|
+
font-size: 22px;
|
|
149
|
+
color: var(--text-muted);
|
|
150
|
+
margin-top: 24px;
|
|
151
|
+
line-height: 1.5;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Omit eyebrow or subtext if not needed** — the headline stands alone fine.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 2. stat-reveal
|
|
160
|
+
|
|
161
|
+
Oversized number + label. Best for a single metric that needs maximum impact.
|
|
162
|
+
|
|
163
|
+
**HTML:**
|
|
164
|
+
```html
|
|
165
|
+
<div class="scene scene-N">
|
|
166
|
+
<div class="scene-inner stat-reveal">
|
|
167
|
+
<div class="stat-number">$4.2M</div>
|
|
168
|
+
<div class="stat-label">New ARR this quarter</div>
|
|
169
|
+
<div class="stat-context">vs $2.3M same period last year</div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**CSS:**
|
|
175
|
+
```css
|
|
176
|
+
.stat-reveal {
|
|
177
|
+
text-align: center;
|
|
178
|
+
}
|
|
179
|
+
.stat-reveal .stat-number {
|
|
180
|
+
font-family: var(--font-display);
|
|
181
|
+
font-size: clamp(120px, 18vw, 200px);
|
|
182
|
+
font-weight: 800;
|
|
183
|
+
color: var(--accent);
|
|
184
|
+
line-height: 0.9;
|
|
185
|
+
letter-spacing: -0.04em;
|
|
186
|
+
}
|
|
187
|
+
.stat-reveal .stat-label {
|
|
188
|
+
font-family: var(--font-body);
|
|
189
|
+
font-size: 28px;
|
|
190
|
+
font-weight: 500;
|
|
191
|
+
color: var(--text);
|
|
192
|
+
margin-top: 20px;
|
|
193
|
+
letter-spacing: -0.01em;
|
|
194
|
+
}
|
|
195
|
+
.stat-reveal .stat-context {
|
|
196
|
+
font-family: var(--font-body);
|
|
197
|
+
font-size: 18px;
|
|
198
|
+
color: var(--text-muted);
|
|
199
|
+
margin-top: 12px;
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Omit `.stat-context` if not needed.**
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 3. bullet-list
|
|
208
|
+
|
|
209
|
+
2–4 short bullet points. Best for listing factors, drivers, or steps. Bullets animate in staggered within scene's keyframe window.
|
|
210
|
+
|
|
211
|
+
**HTML:**
|
|
212
|
+
```html
|
|
213
|
+
<div class="scene scene-N">
|
|
214
|
+
<div class="scene-inner bullet-list">
|
|
215
|
+
<h2 class="list-title">3 Drivers of Growth</h2>
|
|
216
|
+
<ul class="bullets">
|
|
217
|
+
<li class="bullet bullet-1">
|
|
218
|
+
<span class="bullet-marker">01</span>
|
|
219
|
+
<span class="bullet-text">Enterprise deals +120%</span>
|
|
220
|
+
</li>
|
|
221
|
+
<li class="bullet bullet-2">
|
|
222
|
+
<span class="bullet-marker">02</span>
|
|
223
|
+
<span class="bullet-text">Churn dropped to 1.2%</span>
|
|
224
|
+
</li>
|
|
225
|
+
<li class="bullet bullet-3">
|
|
226
|
+
<span class="bullet-marker">03</span>
|
|
227
|
+
<span class="bullet-text">Price increase fully absorbed</span>
|
|
228
|
+
</li>
|
|
229
|
+
</ul>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**CSS:**
|
|
235
|
+
```css
|
|
236
|
+
.bullet-list {
|
|
237
|
+
max-width: 800px;
|
|
238
|
+
width: 100%;
|
|
239
|
+
}
|
|
240
|
+
.bullet-list .list-title {
|
|
241
|
+
font-family: var(--font-display);
|
|
242
|
+
font-size: 52px;
|
|
243
|
+
font-weight: 700;
|
|
244
|
+
color: var(--text);
|
|
245
|
+
margin-bottom: 40px;
|
|
246
|
+
letter-spacing: -0.02em;
|
|
247
|
+
}
|
|
248
|
+
.bullet-list .bullets {
|
|
249
|
+
list-style: none;
|
|
250
|
+
display: flex;
|
|
251
|
+
flex-direction: column;
|
|
252
|
+
gap: 20px;
|
|
253
|
+
}
|
|
254
|
+
.bullet-list .bullet {
|
|
255
|
+
display: flex;
|
|
256
|
+
align-items: baseline;
|
|
257
|
+
gap: 20px;
|
|
258
|
+
opacity: 0; /* renderFrame sets per-bullet opacity */
|
|
259
|
+
will-change: opacity;
|
|
260
|
+
}
|
|
261
|
+
.bullet-list .bullet-marker {
|
|
262
|
+
font-family: var(--font-mono);
|
|
263
|
+
font-size: 14px;
|
|
264
|
+
color: var(--accent);
|
|
265
|
+
letter-spacing: 0.05em;
|
|
266
|
+
min-width: 28px;
|
|
267
|
+
}
|
|
268
|
+
.bullet-list .bullet-text {
|
|
269
|
+
font-family: var(--font-body);
|
|
270
|
+
font-size: 32px;
|
|
271
|
+
font-weight: 500;
|
|
272
|
+
color: var(--text);
|
|
273
|
+
line-height: 1.2;
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Stagger bullets in renderFrame:**
|
|
278
|
+
Add a `bulletOpacity()` helper and drive bullets from within `window.renderFrame`:
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
function bulletOpacity(sceneProgress, showAtProg) {
|
|
282
|
+
if (sceneProgress < showAtProg) return 0;
|
|
283
|
+
if (sceneProgress < showAtProg + 0.06) return (sceneProgress - showAtProg) / 0.06;
|
|
284
|
+
if (sceneProgress < 0.85) return 1;
|
|
285
|
+
return clamp(1 - (sceneProgress - 0.85) / 0.15, 0, 1);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Inside window.renderFrame(t), for a bullet-list scene (e.g. 6000ms–9000ms):
|
|
289
|
+
const sProgress = clamp((t - startMs) / (endMs - startMs), 0, 1);
|
|
290
|
+
const staggerOffsets = [0.10, 0.28, 0.46]; // scene-progress when each bullet appears
|
|
291
|
+
['.bullet-1', '.bullet-2', '.bullet-3'].forEach((cls, i) => {
|
|
292
|
+
const el = document.querySelector(cls);
|
|
293
|
+
if (el) el.style.opacity = bulletOpacity(sProgress, staggerOffsets[i]);
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Bullet 1 appears at 10% into scene, bullet 2 at 28%, bullet 3 at 46%. All fade out with the scene exit at 85%.
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## 4. split-screen
|
|
302
|
+
|
|
303
|
+
Left column: text/context. Right column: large number or metric. Side-by-side contrast.
|
|
304
|
+
|
|
305
|
+
**HTML:**
|
|
306
|
+
```html
|
|
307
|
+
<div class="scene scene-N">
|
|
308
|
+
<div class="scene-inner split-screen">
|
|
309
|
+
<div class="split-left">
|
|
310
|
+
<div class="split-label">Before</div>
|
|
311
|
+
<div class="split-value dim">200 req/s</div>
|
|
312
|
+
<div class="split-desc">Legacy system capacity</div>
|
|
313
|
+
</div>
|
|
314
|
+
<div class="split-divider"></div>
|
|
315
|
+
<div class="split-right">
|
|
316
|
+
<div class="split-label accent">After</div>
|
|
317
|
+
<div class="split-value">2,000 req/s</div>
|
|
318
|
+
<div class="split-desc">Post-migration capacity</div>
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**CSS:**
|
|
325
|
+
```css
|
|
326
|
+
.split-screen {
|
|
327
|
+
display: flex;
|
|
328
|
+
align-items: center;
|
|
329
|
+
gap: 0;
|
|
330
|
+
width: 100%;
|
|
331
|
+
padding: 80px;
|
|
332
|
+
}
|
|
333
|
+
.split-left,
|
|
334
|
+
.split-right {
|
|
335
|
+
flex: 1;
|
|
336
|
+
display: flex;
|
|
337
|
+
flex-direction: column;
|
|
338
|
+
align-items: center;
|
|
339
|
+
text-align: center;
|
|
340
|
+
gap: 12px;
|
|
341
|
+
}
|
|
342
|
+
.split-divider {
|
|
343
|
+
width: 1px;
|
|
344
|
+
height: 200px;
|
|
345
|
+
background: var(--divider);
|
|
346
|
+
margin: 0 40px;
|
|
347
|
+
flex-shrink: 0;
|
|
348
|
+
}
|
|
349
|
+
.split-label {
|
|
350
|
+
font-family: var(--font-body);
|
|
351
|
+
font-size: 13px;
|
|
352
|
+
letter-spacing: 0.10em;
|
|
353
|
+
text-transform: uppercase;
|
|
354
|
+
color: var(--text-muted);
|
|
355
|
+
}
|
|
356
|
+
.split-label.accent { color: var(--accent); }
|
|
357
|
+
.split-value {
|
|
358
|
+
font-family: var(--font-display);
|
|
359
|
+
font-size: clamp(60px, 9vw, 96px);
|
|
360
|
+
font-weight: 700;
|
|
361
|
+
color: var(--text);
|
|
362
|
+
letter-spacing: -0.03em;
|
|
363
|
+
line-height: 1.0;
|
|
364
|
+
}
|
|
365
|
+
.split-value.dim { opacity: 0.35; }
|
|
366
|
+
.split-desc {
|
|
367
|
+
font-family: var(--font-body);
|
|
368
|
+
font-size: 18px;
|
|
369
|
+
color: var(--text-muted);
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## 5. quote-card
|
|
376
|
+
|
|
377
|
+
Large pull quote with attribution. Best for testimonials, founder quotes, or key insights.
|
|
378
|
+
|
|
379
|
+
**HTML:**
|
|
380
|
+
```html
|
|
381
|
+
<div class="scene scene-N">
|
|
382
|
+
<div class="scene-inner quote-card">
|
|
383
|
+
<div class="quote-mark">"</div>
|
|
384
|
+
<blockquote class="quote-text">
|
|
385
|
+
This tool saved us 20 hours a week and paid for itself on day one.
|
|
386
|
+
</blockquote>
|
|
387
|
+
<div class="quote-attribution">
|
|
388
|
+
<span class="quote-name">Sarah Chen</span>
|
|
389
|
+
<span class="quote-role">Head of Engineering, Acme Corp</span>
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**CSS:**
|
|
396
|
+
```css
|
|
397
|
+
.quote-card {
|
|
398
|
+
max-width: 860px;
|
|
399
|
+
text-align: center;
|
|
400
|
+
position: relative;
|
|
401
|
+
}
|
|
402
|
+
.quote-card .quote-mark {
|
|
403
|
+
font-family: var(--font-display);
|
|
404
|
+
font-size: 120px;
|
|
405
|
+
line-height: 0.6;
|
|
406
|
+
color: var(--accent);
|
|
407
|
+
margin-bottom: 24px;
|
|
408
|
+
display: block;
|
|
409
|
+
}
|
|
410
|
+
.quote-card .quote-text {
|
|
411
|
+
font-family: var(--font-display);
|
|
412
|
+
font-size: clamp(32px, 5vw, 52px);
|
|
413
|
+
font-weight: 600;
|
|
414
|
+
color: var(--text);
|
|
415
|
+
line-height: 1.25;
|
|
416
|
+
letter-spacing: -0.02em;
|
|
417
|
+
font-style: normal;
|
|
418
|
+
}
|
|
419
|
+
.quote-card .quote-attribution {
|
|
420
|
+
margin-top: 36px;
|
|
421
|
+
display: flex;
|
|
422
|
+
flex-direction: column;
|
|
423
|
+
gap: 4px;
|
|
424
|
+
}
|
|
425
|
+
.quote-card .quote-name {
|
|
426
|
+
font-family: var(--font-body);
|
|
427
|
+
font-size: 18px;
|
|
428
|
+
font-weight: 600;
|
|
429
|
+
color: var(--text);
|
|
430
|
+
}
|
|
431
|
+
.quote-card .quote-role {
|
|
432
|
+
font-family: var(--font-body);
|
|
433
|
+
font-size: 15px;
|
|
434
|
+
color: var(--text-muted);
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## 6. cta-card
|
|
441
|
+
|
|
442
|
+
Final scene: brand name + call to action + URL or handle. Always the last scene.
|
|
443
|
+
|
|
444
|
+
**HTML:**
|
|
445
|
+
```html
|
|
446
|
+
<div class="scene scene-N">
|
|
447
|
+
<div class="scene-inner cta-card">
|
|
448
|
+
<div class="cta-brand">Acme</div>
|
|
449
|
+
<div class="cta-divider"></div>
|
|
450
|
+
<p class="cta-message">See the full Q4 report</p>
|
|
451
|
+
<div class="cta-url">acme.com/q4-2024</div>
|
|
452
|
+
</div>
|
|
453
|
+
</div>
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
**CSS:**
|
|
457
|
+
```css
|
|
458
|
+
.cta-card {
|
|
459
|
+
text-align: center;
|
|
460
|
+
align-items: center;
|
|
461
|
+
gap: 0;
|
|
462
|
+
}
|
|
463
|
+
.cta-card .cta-brand {
|
|
464
|
+
font-family: var(--font-display);
|
|
465
|
+
font-size: 72px;
|
|
466
|
+
font-weight: 700;
|
|
467
|
+
color: var(--text);
|
|
468
|
+
letter-spacing: -0.04em;
|
|
469
|
+
}
|
|
470
|
+
.cta-card .cta-divider {
|
|
471
|
+
width: 48px;
|
|
472
|
+
height: 3px;
|
|
473
|
+
background: var(--accent);
|
|
474
|
+
margin: 28px auto;
|
|
475
|
+
}
|
|
476
|
+
.cta-card .cta-message {
|
|
477
|
+
font-family: var(--font-body);
|
|
478
|
+
font-size: 26px;
|
|
479
|
+
color: var(--text-muted);
|
|
480
|
+
margin-bottom: 16px;
|
|
481
|
+
}
|
|
482
|
+
.cta-card .cta-url {
|
|
483
|
+
font-family: var(--font-mono);
|
|
484
|
+
font-size: 28px;
|
|
485
|
+
font-weight: 600;
|
|
486
|
+
color: var(--accent);
|
|
487
|
+
letter-spacing: -0.01em;
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Scene Selection Guide
|
|
494
|
+
|
|
495
|
+
| Content type | Recommended scene type |
|
|
496
|
+
|---|---|
|
|
497
|
+
| Hook / opening | title-card |
|
|
498
|
+
| Single metric / number | stat-reveal |
|
|
499
|
+
| Multiple points / list | bullet-list |
|
|
500
|
+
| Before vs after / two values | split-screen |
|
|
501
|
+
| Testimonial / quote | quote-card |
|
|
502
|
+
| Final / CTA / contact | cta-card |
|
|
503
|
+
|
|
504
|
+
**Max 6 scenes per video.** Beyond 6, the message fragments. If the brief has more than 6 ideas, consolidate similar points or promote the top 5 with a summary CTA.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Style Presets — vid-motion-graphics
|
|
2
|
+
|
|
3
|
+
5 motion-optimized style presets. Each includes CSS tokens, font stack, Google Fonts CDN link, and animation personality.
|
|
4
|
+
|
|
5
|
+
Read this file before generating any HTML in Step 3.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. kinetic-dark (default)
|
|
10
|
+
|
|
11
|
+
**Feel:** High-energy. Electric yellow on near-black. Tight grotesque. Fast-in, slow-hold.
|
|
12
|
+
**Best for:** Product launches, growth metrics, startup content.
|
|
13
|
+
|
|
14
|
+
```css
|
|
15
|
+
/* Font CDN */
|
|
16
|
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700;800&family=DM+Sans:wght@400;500&display=swap" rel="stylesheet">
|
|
17
|
+
|
|
18
|
+
:root {
|
|
19
|
+
--bg: #0A0A0A;
|
|
20
|
+
--text: #FAFAFA;
|
|
21
|
+
--text-muted: #888888;
|
|
22
|
+
--accent: #F5FF00;
|
|
23
|
+
--accent-dim: rgba(245,255,0,0.15);
|
|
24
|
+
--divider: rgba(255,255,255,0.10);
|
|
25
|
+
--font-display: 'Space Grotesk', sans-serif;
|
|
26
|
+
--font-body: 'DM Sans', sans-serif;
|
|
27
|
+
--font-mono: 'Space Grotesk', monospace;
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Data palette:** `['#F5FF00', '#60A5FA', '#4ADE80', '#F87171', '#A78BFA']`
|
|
32
|
+
|
|
33
|
+
**Animation feel:**
|
|
34
|
+
- In: `translateY(24px) → 0, opacity 0 → 1` over first 8% of scene
|
|
35
|
+
- Hold: static for 80% of scene
|
|
36
|
+
- Out: `translateY(-12px), opacity → 0` over last 12% of scene
|
|
37
|
+
- Easing: `cubic-bezier(0.22, 1, 0.36, 1)` for in, `linear` for out
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 2. editorial-light
|
|
42
|
+
|
|
43
|
+
**Feel:** Refined. Ink on paper. Serif display. Long hold, gentle crossfade.
|
|
44
|
+
**Best for:** Thought leadership, executive communications, annual reports.
|
|
45
|
+
|
|
46
|
+
```css
|
|
47
|
+
/* Font CDN */
|
|
48
|
+
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;800&family=Source+Serif+4:wght@400;600&display=swap" rel="stylesheet">
|
|
49
|
+
|
|
50
|
+
:root {
|
|
51
|
+
--bg: #FAFAF8;
|
|
52
|
+
--text: #111111;
|
|
53
|
+
--text-muted: #777777;
|
|
54
|
+
--accent: #111111;
|
|
55
|
+
--accent-dim: rgba(17,17,17,0.08);
|
|
56
|
+
--divider: rgba(0,0,0,0.10);
|
|
57
|
+
--font-display: 'Playfair Display', serif;
|
|
58
|
+
--font-body: 'Source Serif 4', serif;
|
|
59
|
+
--font-mono: 'Source Serif 4', monospace;
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Data palette:** `['#111111', '#3B82F6', '#16A34A', '#DC2626', '#7C3AED']`
|
|
64
|
+
|
|
65
|
+
**Animation feel:**
|
|
66
|
+
- In: `opacity 0 → 1` only, over first 10% of scene (no transform)
|
|
67
|
+
- Hold: static for 85% of scene
|
|
68
|
+
- Out: `opacity → 0` over last 5% of scene
|
|
69
|
+
- Easing: `ease-in-out` throughout
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 3. data-pulse
|
|
74
|
+
|
|
75
|
+
**Feel:** Terminal/dashboard. Deep navy, electric blue, monospaced. Scan-line entrance.
|
|
76
|
+
**Best for:** Technical metrics, engineering announcements, SaaS dashboards.
|
|
77
|
+
|
|
78
|
+
```css
|
|
79
|
+
/* Font CDN */
|
|
80
|
+
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600;700&family=IBM+Plex+Sans:wght@400;500&display=swap" rel="stylesheet">
|
|
81
|
+
|
|
82
|
+
:root {
|
|
83
|
+
--bg: #050B1A;
|
|
84
|
+
--text: #E8F4FF;
|
|
85
|
+
--text-muted: #4A7FA5;
|
|
86
|
+
--accent: #00C2FF;
|
|
87
|
+
--accent-dim: rgba(0,194,255,0.12);
|
|
88
|
+
--divider: rgba(0,194,255,0.15);
|
|
89
|
+
--font-display: 'IBM Plex Mono', monospace;
|
|
90
|
+
--font-body: 'IBM Plex Sans', sans-serif;
|
|
91
|
+
--font-mono: 'IBM Plex Mono', monospace;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Data palette:** `['#00C2FF', '#4ADE80', '#F5FF00', '#F87171', '#A78BFA']`
|
|
96
|
+
|
|
97
|
+
**Animation feel:**
|
|
98
|
+
- In: `translateX(-8px) → 0, opacity 0 → 1` with slight stagger on text lines
|
|
99
|
+
- Hold: static with subtle `box-shadow` pulse on accent elements
|
|
100
|
+
- Out: `opacity → 0, translateX(8px)` — slide out opposite direction
|
|
101
|
+
- Easing: `steps(4)` for entrance (scan-line effect), `linear` for exit
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 4. bold-type
|
|
106
|
+
|
|
107
|
+
**Feel:** Brutalist typographic. White bg, black/red, maximum weight. Slam in.
|
|
108
|
+
**Best for:** Announcements, milestones, provocative statements.
|
|
109
|
+
|
|
110
|
+
```css
|
|
111
|
+
/* Font CDN */
|
|
112
|
+
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
|
|
113
|
+
|
|
114
|
+
:root {
|
|
115
|
+
--bg: #FFFFFF;
|
|
116
|
+
--text: #000000;
|
|
117
|
+
--text-muted: #555555;
|
|
118
|
+
--accent: #FF2B00;
|
|
119
|
+
--accent-dim: rgba(255,43,0,0.10);
|
|
120
|
+
--divider: #000000;
|
|
121
|
+
--font-display: 'Bebas Neue', sans-serif;
|
|
122
|
+
--font-body: 'Inter', sans-serif;
|
|
123
|
+
--font-mono: 'Inter', monospace;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Data palette:** `['#FF2B00', '#000000', '#1D4ED8', '#16A34A', '#9333EA']`
|
|
128
|
+
|
|
129
|
+
**Animation feel:**
|
|
130
|
+
- In: `scale(1.08) → 1, opacity 0 → 1` over first 5% of scene (slam)
|
|
131
|
+
- Hold: static for 90% of scene
|
|
132
|
+
- Out: `scale(0.96), opacity → 0` over last 5% of scene (snap)
|
|
133
|
+
- Easing: `cubic-bezier(0.34, 1.56, 0.64, 1)` for in (overshoot), `linear` for out
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 5. minimal-clean
|
|
138
|
+
|
|
139
|
+
**Feel:** Warm, refined. Off-white, ink-warm neutrals, thin serif display. Gentle rise.
|
|
140
|
+
**Best for:** People-focused content, hiring, testimonials, brand storytelling.
|
|
141
|
+
|
|
142
|
+
```css
|
|
143
|
+
/* Font CDN */
|
|
144
|
+
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@600;700&family=Jost:wght@300;400;500&display=swap" rel="stylesheet">
|
|
145
|
+
|
|
146
|
+
:root {
|
|
147
|
+
--bg: #F5F4F0;
|
|
148
|
+
--text: #1A1A18;
|
|
149
|
+
--text-muted: #8A8A82;
|
|
150
|
+
--accent: #1A1A18;
|
|
151
|
+
--accent-dim: rgba(26,26,24,0.06);
|
|
152
|
+
--divider: rgba(26,26,24,0.12);
|
|
153
|
+
--font-display: 'Cormorant Garamond', serif;
|
|
154
|
+
--font-body: 'Jost', sans-serif;
|
|
155
|
+
--font-mono: 'Jost', monospace;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Data palette:** `['#1A1A18', '#6366F1', '#16A34A', '#CA8A04', '#DC2626']`
|
|
160
|
+
|
|
161
|
+
**Animation feel:**
|
|
162
|
+
- In: `translateY(32px) → 0, opacity 0 → 1` over first 12% of scene
|
|
163
|
+
- Hold: static for 80% of scene
|
|
164
|
+
- Out: `opacity → 0` only (no transform) over last 8%
|
|
165
|
+
- Easing: `cubic-bezier(0.25, 0.46, 0.45, 0.94)` throughout
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## renderFrame Timing Quick Reference
|
|
170
|
+
|
|
171
|
+
All scene timing is computed in `window.renderFrame(t)` (milliseconds). No CSS `@keyframes`.
|
|
172
|
+
|
|
173
|
+
For a scene at `[startMs, endMs)`:
|
|
174
|
+
```javascript
|
|
175
|
+
const prog = (t - startMs) / (endMs - startMs); // 0.0 → 1.0
|
|
176
|
+
// Enter: prog 0.00–0.10 → opacity 0→1, translateY 24px→0 (easeOutCubic)
|
|
177
|
+
// Hold: prog 0.10–0.85 → opacity 1, no transform
|
|
178
|
+
// Exit: prog 0.85–1.00 → opacity 1→0, translateY 0→-12px
|
|
179
|
+
// Hidden: t < startMs or t >= endMs → opacity 0
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Example — 3 scenes, 3s each, 9s total:**
|
|
183
|
+
```javascript
|
|
184
|
+
// scene 1: 0ms–3000ms | scene 2: 3000ms–6000ms | scene 3: 6000ms–9000ms
|
|
185
|
+
window.renderFrame = function(t) {
|
|
186
|
+
applySceneState(document.querySelector('.scene-1'), sceneState(t, 0, 3000));
|
|
187
|
+
applySceneState(document.querySelector('.scene-2'), sceneState(t, 3000, 6000));
|
|
188
|
+
applySceneState(document.querySelector('.scene-3'), sceneState(t, 6000, 9000));
|
|
189
|
+
};
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Animation feel by preset (all implemented in sceneState/easing):**
|
|
193
|
+
|
|
194
|
+
| Preset | Enter transform | Enter easing | Exit transform |
|
|
195
|
+
|---|---|---|---|
|
|
196
|
+
| kinetic-dark | translateY(24px)→0 | easeOutCubic | translateY(0→-12px) |
|
|
197
|
+
| editorial-light | opacity only | ease-in-out | opacity only |
|
|
198
|
+
| data-pulse | translateX(-8px)→0 | easeOutCubic | translateX(0→8px) |
|
|
199
|
+
| bold-type | scale(1.08)→1 | easeOutCubic (overshoot) | scale(1→0.96) |
|
|
200
|
+
| minimal-clean | translateY(32px)→0 | easeOutCubic | opacity only |
|
|
201
|
+
|
|
202
|
+
Adapt `sceneState()` or add a style-specific `enterTransform`/`exitTransform` param to match the preset's feel.
|