@vibeframe/cli 0.52.1 → 0.53.0
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/LICENSE +21 -0
- package/dist/agent/tools/index.d.ts +1 -0
- package/dist/agent/tools/index.d.ts.map +1 -1
- package/dist/agent/tools/index.js +3 -0
- package/dist/agent/tools/index.js.map +1 -1
- package/dist/agent/tools/scene.d.ts +17 -0
- package/dist/agent/tools/scene.d.ts.map +1 -0
- package/dist/agent/tools/scene.js +263 -0
- package/dist/agent/tools/scene.js.map +1 -0
- package/dist/agent/tools/scene.test.d.ts +2 -0
- package/dist/agent/tools/scene.test.d.ts.map +1 -0
- package/dist/agent/tools/scene.test.js +139 -0
- package/dist/agent/tools/scene.test.js.map +1 -0
- package/dist/commands/_shared/scene-html-emit.d.ts +85 -0
- package/dist/commands/_shared/scene-html-emit.d.ts.map +1 -0
- package/dist/commands/_shared/scene-html-emit.js +353 -0
- package/dist/commands/_shared/scene-html-emit.js.map +1 -0
- package/dist/commands/_shared/scene-html-emit.test.d.ts +2 -0
- package/dist/commands/_shared/scene-html-emit.test.d.ts.map +1 -0
- package/dist/commands/_shared/scene-html-emit.test.js +203 -0
- package/dist/commands/_shared/scene-html-emit.test.js.map +1 -0
- package/dist/commands/_shared/scene-lint.d.ts +110 -0
- package/dist/commands/_shared/scene-lint.d.ts.map +1 -0
- package/dist/commands/_shared/scene-lint.js +207 -0
- package/dist/commands/_shared/scene-lint.js.map +1 -0
- package/dist/commands/_shared/scene-lint.test.d.ts +2 -0
- package/dist/commands/_shared/scene-lint.test.d.ts.map +1 -0
- package/dist/commands/_shared/scene-lint.test.js +208 -0
- package/dist/commands/_shared/scene-lint.test.js.map +1 -0
- package/dist/commands/_shared/scene-project.d.ts +101 -0
- package/dist/commands/_shared/scene-project.d.ts.map +1 -0
- package/dist/commands/_shared/scene-project.js +281 -0
- package/dist/commands/_shared/scene-project.js.map +1 -0
- package/dist/commands/_shared/scene-project.test.d.ts +2 -0
- package/dist/commands/_shared/scene-project.test.d.ts.map +1 -0
- package/dist/commands/_shared/scene-project.test.js +203 -0
- package/dist/commands/_shared/scene-project.test.js.map +1 -0
- package/dist/commands/_shared/scene-render.d.ts +79 -0
- package/dist/commands/_shared/scene-render.d.ts.map +1 -0
- package/dist/commands/_shared/scene-render.js +138 -0
- package/dist/commands/_shared/scene-render.js.map +1 -0
- package/dist/commands/_shared/scene-render.test.d.ts +2 -0
- package/dist/commands/_shared/scene-render.test.d.ts.map +1 -0
- package/dist/commands/_shared/scene-render.test.js +145 -0
- package/dist/commands/_shared/scene-render.test.js.map +1 -0
- package/dist/commands/_shared/segments-to-scenes.d.ts +85 -0
- package/dist/commands/_shared/segments-to-scenes.d.ts.map +1 -0
- package/dist/commands/_shared/segments-to-scenes.js +202 -0
- package/dist/commands/_shared/segments-to-scenes.js.map +1 -0
- package/dist/commands/_shared/segments-to-scenes.test.d.ts +2 -0
- package/dist/commands/_shared/segments-to-scenes.test.d.ts.map +1 -0
- package/dist/commands/_shared/segments-to-scenes.test.js +219 -0
- package/dist/commands/_shared/segments-to-scenes.test.js.map +1 -0
- package/dist/commands/ai-script-pipeline-cli.d.ts.map +1 -1
- package/dist/commands/ai-script-pipeline-cli.js +49 -0
- package/dist/commands/ai-script-pipeline-cli.js.map +1 -1
- package/dist/commands/ai-script-pipeline.d.ts +16 -0
- package/dist/commands/ai-script-pipeline.d.ts.map +1 -1
- package/dist/commands/ai-script-pipeline.js +28 -0
- package/dist/commands/ai-script-pipeline.js.map +1 -1
- package/dist/commands/ai.test.js +4 -1
- package/dist/commands/ai.test.js.map +1 -1
- package/dist/commands/scene.d.ts +60 -0
- package/dist/commands/scene.d.ts.map +1 -0
- package/dist/commands/scene.js +621 -0
- package/dist/commands/scene.js.map +1 -0
- package/dist/commands/scene.test.d.ts +2 -0
- package/dist/commands/scene.test.d.ts.map +1 -0
- package/dist/commands/scene.test.js +133 -0
- package/dist/commands/scene.test.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/renderers/chrome.d.ts +26 -0
- package/dist/pipeline/renderers/chrome.d.ts.map +1 -0
- package/dist/pipeline/renderers/chrome.js +48 -0
- package/dist/pipeline/renderers/chrome.js.map +1 -0
- package/dist/pipeline/renderers/hyperframes.d.ts.map +1 -1
- package/dist/pipeline/renderers/hyperframes.js +2 -32
- package/dist/pipeline/renderers/hyperframes.js.map +1 -1
- package/package.json +32 -13
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
export const SCENE_PRESETS = [
|
|
2
|
+
"simple",
|
|
3
|
+
"announcement",
|
|
4
|
+
"explainer",
|
|
5
|
+
"kinetic-type",
|
|
6
|
+
"product-shot",
|
|
7
|
+
];
|
|
8
|
+
const GSAP_CDN = "https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js";
|
|
9
|
+
/** Minimal HTML escaper for text we inline into elements/attributes. */
|
|
10
|
+
function esc(text) {
|
|
11
|
+
return text
|
|
12
|
+
.replace(/&/g, "&")
|
|
13
|
+
.replace(/</g, "<")
|
|
14
|
+
.replace(/>/g, ">")
|
|
15
|
+
.replace(/"/g, """)
|
|
16
|
+
.replace(/'/g, "'");
|
|
17
|
+
}
|
|
18
|
+
/** Normalise whitespace and trim — used on user-supplied headlines/subheads. */
|
|
19
|
+
function clean(text) {
|
|
20
|
+
return (text ?? "").replace(/\s+/g, " ").trim();
|
|
21
|
+
}
|
|
22
|
+
/** Humanise a scene id — "product-shot" → "Product Shot". */
|
|
23
|
+
function humanise(id) {
|
|
24
|
+
return id
|
|
25
|
+
.split(/[-_]+/)
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
28
|
+
.join(" ");
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Slugify a free-form scene name to a kebab-case id usable in HTML attributes
|
|
32
|
+
* and filenames. Strips diacritics, collapses whitespace, lowercases.
|
|
33
|
+
*/
|
|
34
|
+
export function slugifySceneName(name) {
|
|
35
|
+
const normalised = name
|
|
36
|
+
.normalize("NFKD")
|
|
37
|
+
.replace(/[̀-ͯ]/g, "");
|
|
38
|
+
const slug = normalised
|
|
39
|
+
.toLowerCase()
|
|
40
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
41
|
+
.replace(/^-+|-+$/g, "");
|
|
42
|
+
return slug || "scene";
|
|
43
|
+
}
|
|
44
|
+
function buildPreset(input) {
|
|
45
|
+
const id = input.id;
|
|
46
|
+
const scope = `[data-composition-id="${id}"]`;
|
|
47
|
+
const headline = clean(input.headline) || humanise(id);
|
|
48
|
+
const subhead = clean(input.subhead);
|
|
49
|
+
const kicker = clean(input.kicker);
|
|
50
|
+
const dur = input.duration;
|
|
51
|
+
const hasImage = !!input.imagePath;
|
|
52
|
+
const backdrop = hasImage
|
|
53
|
+
? `${scope} .backdrop {
|
|
54
|
+
position: absolute;
|
|
55
|
+
inset: 0;
|
|
56
|
+
width: 100%;
|
|
57
|
+
height: 100%;
|
|
58
|
+
background-image: url("${esc(input.imagePath)}");
|
|
59
|
+
background-size: cover;
|
|
60
|
+
background-position: center;
|
|
61
|
+
}
|
|
62
|
+
${scope} .backdrop::after {
|
|
63
|
+
content: "";
|
|
64
|
+
position: absolute;
|
|
65
|
+
inset: 0;
|
|
66
|
+
background: linear-gradient(180deg, rgba(0,0,0,0.0) 30%, rgba(0,0,0,0.55) 100%);
|
|
67
|
+
}`
|
|
68
|
+
: `${scope} .backdrop {
|
|
69
|
+
position: absolute;
|
|
70
|
+
inset: 0;
|
|
71
|
+
background: radial-gradient(ellipse at center, #1a1a2e 0%, #000 70%);
|
|
72
|
+
}`;
|
|
73
|
+
const backdropMarkup = `<div class="backdrop"></div>`;
|
|
74
|
+
switch (input.preset) {
|
|
75
|
+
case "simple": {
|
|
76
|
+
const caption = subhead || headline;
|
|
77
|
+
return {
|
|
78
|
+
css: `${scope} {
|
|
79
|
+
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
80
|
+
font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
|
|
81
|
+
color: #fff; overflow: hidden; background: #000;
|
|
82
|
+
}
|
|
83
|
+
${backdrop}
|
|
84
|
+
${scope} .caption {
|
|
85
|
+
position: absolute;
|
|
86
|
+
left: 8%; right: 8%; bottom: 12%;
|
|
87
|
+
text-align: center;
|
|
88
|
+
font-size: 56px;
|
|
89
|
+
font-weight: 700;
|
|
90
|
+
line-height: 1.2;
|
|
91
|
+
text-shadow: 0 4px 20px rgba(0,0,0,0.65);
|
|
92
|
+
}`,
|
|
93
|
+
body: `${backdropMarkup}
|
|
94
|
+
<div class="caption" id="caption">${esc(caption)}</div>`,
|
|
95
|
+
timeline: `tl.from('${scope} .caption', { opacity: 0, y: 28, duration: 0.6, ease: 'power2.out' }, 0.1);
|
|
96
|
+
tl.to('${scope} .caption', { opacity: 0, duration: 0.4, ease: 'power2.in' }, ${(dur - 0.4).toFixed(2)});`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
case "announcement": {
|
|
100
|
+
return {
|
|
101
|
+
css: `${scope} {
|
|
102
|
+
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
103
|
+
font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
|
|
104
|
+
color: #fff; overflow: hidden; background: #000;
|
|
105
|
+
}
|
|
106
|
+
${backdrop}
|
|
107
|
+
${scope} .announce {
|
|
108
|
+
position: absolute; inset: 0;
|
|
109
|
+
display: flex; align-items: center; justify-content: center;
|
|
110
|
+
text-align: center;
|
|
111
|
+
padding: 0 8%;
|
|
112
|
+
}
|
|
113
|
+
${scope} .announce h1 {
|
|
114
|
+
margin: 0;
|
|
115
|
+
font-size: 160px;
|
|
116
|
+
font-weight: 900;
|
|
117
|
+
letter-spacing: -4px;
|
|
118
|
+
line-height: 1;
|
|
119
|
+
background: linear-gradient(90deg, #8e2de2, #00c9ff);
|
|
120
|
+
-webkit-background-clip: text; background-clip: text; color: transparent;
|
|
121
|
+
text-shadow: 0 8px 40px rgba(142,45,226,0.35);
|
|
122
|
+
}`,
|
|
123
|
+
body: `${backdropMarkup}
|
|
124
|
+
<div class="announce"><h1 id="headline">${esc(headline)}</h1></div>`,
|
|
125
|
+
timeline: `tl.from('${scope} #headline', { opacity: 0, scale: 0.8, duration: 0.9, ease: 'back.out(1.6)' }, 0.15);
|
|
126
|
+
tl.to('${scope} #headline', { opacity: 0, duration: 0.4, ease: 'power2.in' }, ${(dur - 0.4).toFixed(2)});`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
case "explainer": {
|
|
130
|
+
const k = kicker || humanise(id).toUpperCase();
|
|
131
|
+
const sub = subhead || "";
|
|
132
|
+
return {
|
|
133
|
+
css: `${scope} {
|
|
134
|
+
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
135
|
+
font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
|
|
136
|
+
color: #fff; overflow: hidden; background: #000;
|
|
137
|
+
}
|
|
138
|
+
${backdrop}
|
|
139
|
+
${scope} .stage {
|
|
140
|
+
position: absolute; inset: 0;
|
|
141
|
+
display: flex; flex-direction: column; justify-content: center;
|
|
142
|
+
gap: 24px; padding: 0 10%;
|
|
143
|
+
}
|
|
144
|
+
${scope} .kicker {
|
|
145
|
+
font-size: 28px; letter-spacing: 6px; text-transform: uppercase;
|
|
146
|
+
color: #00c9ff; font-weight: 600;
|
|
147
|
+
}
|
|
148
|
+
${scope} .title {
|
|
149
|
+
font-size: 110px; font-weight: 800; letter-spacing: -2px;
|
|
150
|
+
line-height: 1.05; margin: 0;
|
|
151
|
+
}
|
|
152
|
+
${scope} .subtitle {
|
|
153
|
+
font-size: 38px; font-weight: 300; color: #c0c0d0; max-width: 80%;
|
|
154
|
+
}`,
|
|
155
|
+
body: `${backdropMarkup}
|
|
156
|
+
<div class="stage">
|
|
157
|
+
<div class="kicker" id="kicker">${esc(k)}</div>
|
|
158
|
+
<h1 class="title" id="title">${esc(headline)}</h1>${sub ? `
|
|
159
|
+
<div class="subtitle" id="subtitle">${esc(sub)}</div>` : ""}
|
|
160
|
+
</div>`,
|
|
161
|
+
timeline: `tl.from('${scope} #kicker', { opacity: 0, y: 16, duration: 0.4, ease: 'power2.out' }, 0.1);
|
|
162
|
+
tl.from('${scope} #title', { opacity: 0, y: 60, duration: 0.7, ease: 'power3.out' }, 0.25);
|
|
163
|
+
${sub ? `tl.from('${scope} #subtitle', { opacity: 0, y: 30, duration: 0.55, ease: 'power3.out' }, 0.55);` : ""}`,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
case "kinetic-type": {
|
|
167
|
+
const words = headline.split(/\s+/).filter(Boolean);
|
|
168
|
+
const wordSpans = words
|
|
169
|
+
.map((w, i) => `<span class="word" id="w-${i}">${esc(w)}</span>`)
|
|
170
|
+
.join(" ");
|
|
171
|
+
const stagger = Math.max(0.08, Math.min(0.3, (dur - 0.6) / Math.max(words.length, 1)));
|
|
172
|
+
const tweens = words
|
|
173
|
+
.map((_, i) => {
|
|
174
|
+
const start = (0.05 + i * stagger).toFixed(2);
|
|
175
|
+
return `tl.from('${scope} #w-${i}', { opacity: 0, y: 80, scale: 0.8, duration: 0.45, ease: 'back.out(1.8)' }, ${start});`;
|
|
176
|
+
})
|
|
177
|
+
.join("\n ");
|
|
178
|
+
return {
|
|
179
|
+
css: `${scope} {
|
|
180
|
+
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
181
|
+
font-family: "Helvetica Neue", Arial, sans-serif;
|
|
182
|
+
color: #fff; overflow: hidden; background: #000;
|
|
183
|
+
}
|
|
184
|
+
${backdrop}
|
|
185
|
+
${scope} .kinetic {
|
|
186
|
+
position: absolute; inset: 0;
|
|
187
|
+
display: flex; align-items: center; justify-content: center;
|
|
188
|
+
text-align: center; padding: 0 6%;
|
|
189
|
+
font-size: 180px; font-weight: 900; letter-spacing: -6px;
|
|
190
|
+
line-height: 1; text-shadow: 0 6px 30px rgba(0,0,0,0.6);
|
|
191
|
+
}
|
|
192
|
+
${scope} .kinetic .word { display: inline-block; margin: 0 12px; }`,
|
|
193
|
+
body: `${backdropMarkup}
|
|
194
|
+
<div class="kinetic">${wordSpans}</div>`,
|
|
195
|
+
timeline: tweens,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
case "product-shot": {
|
|
199
|
+
const label = kicker || humanise(id);
|
|
200
|
+
return {
|
|
201
|
+
css: `${scope} {
|
|
202
|
+
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
203
|
+
font-family: -apple-system, "Helvetica Neue", Arial, sans-serif;
|
|
204
|
+
color: #fff; overflow: hidden; background: #000;
|
|
205
|
+
}
|
|
206
|
+
${backdrop}
|
|
207
|
+
${scope} .backdrop { transform-origin: center; }
|
|
208
|
+
${scope} .label {
|
|
209
|
+
position: absolute; top: 8%; left: 8%;
|
|
210
|
+
padding: 12px 24px; border-radius: 999px;
|
|
211
|
+
background: rgba(0,0,0,0.55); backdrop-filter: blur(6px);
|
|
212
|
+
font-size: 24px; font-weight: 600; letter-spacing: 2px;
|
|
213
|
+
text-transform: uppercase;
|
|
214
|
+
}
|
|
215
|
+
${scope} .product-headline {
|
|
216
|
+
position: absolute; left: 8%; right: 8%; bottom: 14%;
|
|
217
|
+
font-size: 72px; font-weight: 800; letter-spacing: -1px;
|
|
218
|
+
line-height: 1.1; text-shadow: 0 4px 20px rgba(0,0,0,0.7);
|
|
219
|
+
}${subhead ? `
|
|
220
|
+
${scope} .product-sub {
|
|
221
|
+
position: absolute; left: 8%; right: 8%; bottom: 8%;
|
|
222
|
+
font-size: 28px; font-weight: 400; color: #d0d0e0;
|
|
223
|
+
text-shadow: 0 2px 10px rgba(0,0,0,0.7);
|
|
224
|
+
}` : ""}`,
|
|
225
|
+
body: `${backdropMarkup}
|
|
226
|
+
<div class="label" id="label">${esc(label)}</div>
|
|
227
|
+
<div class="product-headline" id="headline">${esc(headline)}</div>${subhead ? `
|
|
228
|
+
<div class="product-sub" id="subhead">${esc(subhead)}</div>` : ""}`,
|
|
229
|
+
timeline: `tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: 1.08, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);
|
|
230
|
+
tl.from('${scope} #label', { opacity: 0, x: -30, duration: 0.5, ease: 'power3.out' }, 0.2);
|
|
231
|
+
tl.from('${scope} #headline', { opacity: 0, y: 40, duration: 0.6, ease: 'power3.out' }, 0.4);${subhead ? `
|
|
232
|
+
tl.from('${scope} #subhead', { opacity: 0, y: 20, duration: 0.5, ease: 'power3.out' }, 0.65);` : ""}`,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Emit the full per-scene HTML. Returns a complete `<template>`-wrapped
|
|
239
|
+
* composition ready to write to `compositions/scene-<id>.html`.
|
|
240
|
+
*/
|
|
241
|
+
export function emitSceneHtml(input) {
|
|
242
|
+
if (input.duration <= 0) {
|
|
243
|
+
throw new Error(`Scene duration must be > 0, got ${input.duration}`);
|
|
244
|
+
}
|
|
245
|
+
if (input.width <= 0 || input.height <= 0) {
|
|
246
|
+
throw new Error(`Invalid canvas dims: ${input.width}x${input.height}`);
|
|
247
|
+
}
|
|
248
|
+
const id = input.id;
|
|
249
|
+
const dur = Number(input.duration.toFixed(3));
|
|
250
|
+
const parts = buildPreset({ ...input, id, duration: dur });
|
|
251
|
+
const audioBlock = input.audioPath
|
|
252
|
+
? `\n <audio
|
|
253
|
+
id="narration"
|
|
254
|
+
data-start="0"
|
|
255
|
+
data-duration="auto"
|
|
256
|
+
data-track-index="2"
|
|
257
|
+
src="${esc(input.audioPath)}"
|
|
258
|
+
data-volume="1"
|
|
259
|
+
></audio>\n`
|
|
260
|
+
: "";
|
|
261
|
+
return `<template id="scene-${id}-template">
|
|
262
|
+
<div data-composition-id="${id}" data-start="0" data-duration="${dur}" data-width="${input.width}" data-height="${input.height}">
|
|
263
|
+
<style>
|
|
264
|
+
${parts.css}
|
|
265
|
+
</style>
|
|
266
|
+
|
|
267
|
+
${parts.body}
|
|
268
|
+
${audioBlock}
|
|
269
|
+
<script src="${GSAP_CDN}"></script>
|
|
270
|
+
<script>
|
|
271
|
+
window.__timelines = window.__timelines || {};
|
|
272
|
+
const tl = gsap.timeline({ paused: true });
|
|
273
|
+
|
|
274
|
+
${parts.timeline}
|
|
275
|
+
|
|
276
|
+
window.__timelines["${id}"] = tl;
|
|
277
|
+
</script>
|
|
278
|
+
</div>
|
|
279
|
+
</template>
|
|
280
|
+
`;
|
|
281
|
+
}
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// Root index.html mutation
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
/**
|
|
286
|
+
* Sum existing `<div class="clip">` end-times to find where the next scene
|
|
287
|
+
* should start. Returns 0 for an empty root.
|
|
288
|
+
*/
|
|
289
|
+
export function nextSceneStart(rootHtml) {
|
|
290
|
+
const clipRegex = /<div\s+class="clip"[^>]*?\sdata-start="([\d.]+)"[^>]*?\sdata-duration="([\d.]+)"/gi;
|
|
291
|
+
let maxEnd = 0;
|
|
292
|
+
let match;
|
|
293
|
+
while ((match = clipRegex.exec(rootHtml)) !== null) {
|
|
294
|
+
const end = parseFloat(match[1]) + parseFloat(match[2]);
|
|
295
|
+
if (Number.isFinite(end) && end > maxEnd)
|
|
296
|
+
maxEnd = end;
|
|
297
|
+
}
|
|
298
|
+
return maxEnd;
|
|
299
|
+
}
|
|
300
|
+
/** Build the `<div class="clip" data-composition-id=... data-composition-src=...>` reference string. */
|
|
301
|
+
export function buildClipReference(opts) {
|
|
302
|
+
const start = Number(opts.start.toFixed(3));
|
|
303
|
+
const duration = Number(opts.duration.toFixed(3));
|
|
304
|
+
const track = opts.trackIndex ?? 1;
|
|
305
|
+
const src = opts.src ?? `compositions/scene-${opts.id}.html`;
|
|
306
|
+
return `<div class="clip" data-composition-id="${esc(opts.id)}" data-composition-src="${esc(src)}" data-start="${start}" data-duration="${duration}" data-track-index="${track}"></div>`;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Insert a clip reference inside the root composition's `#root` div, just
|
|
310
|
+
* before its closing `</div>`. Throws if the expected closing tag isn't
|
|
311
|
+
* found — callers should surface this as a structured error so users know
|
|
312
|
+
* to inspect a hand-edited root.
|
|
313
|
+
*
|
|
314
|
+
* Also grows the root's `data-duration` to fit the new clip's end-time.
|
|
315
|
+
*/
|
|
316
|
+
export function insertClipIntoRoot(rootHtml, clip) {
|
|
317
|
+
const clipDiv = buildClipReference(clip);
|
|
318
|
+
// Find the root closing tag. Match `</div>` followed by blank line + `<script>`,
|
|
319
|
+
// which matches `buildEmptyRootHtml` and any user-edited root that keeps the
|
|
320
|
+
// standard structure.
|
|
321
|
+
const closeMatch = rootHtml.match(/\n(\s*)<\/div>(\s*\n\s*<script>)/);
|
|
322
|
+
if (!closeMatch) {
|
|
323
|
+
throw new Error("Could not find root composition closing </div>. Ensure index.html follows the layout from `vibe scene init`.");
|
|
324
|
+
}
|
|
325
|
+
const closeIdx = closeMatch.index;
|
|
326
|
+
const closeIndent = closeMatch[1];
|
|
327
|
+
const childIndent = closeIndent + " ";
|
|
328
|
+
// Splice the clip just above the closing div, indented one level deeper.
|
|
329
|
+
const insertion = `\n${childIndent}${clipDiv}`;
|
|
330
|
+
let updated = rootHtml.slice(0, closeIdx) + insertion + rootHtml.slice(closeIdx);
|
|
331
|
+
// Grow root data-duration to fit the new clip end (never shrink — preserves
|
|
332
|
+
// user-set padding).
|
|
333
|
+
const newEnd = clip.start + clip.duration;
|
|
334
|
+
updated = updated.replace(/(<div\b[^>]*\bid="root"[^>]*\bdata-duration=")([\d.]+)(")/, (_full, prefix, value, suffix) => {
|
|
335
|
+
const current = parseFloat(value);
|
|
336
|
+
const next = Math.max(current, newEnd);
|
|
337
|
+
return `${prefix}${Number(next.toFixed(3))}${suffix}`;
|
|
338
|
+
});
|
|
339
|
+
return updated;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Read the root composition's canvas dims by parsing `data-width`/`data-height`
|
|
343
|
+
* on the `#root` div. Returns null if either is missing — caller should fall
|
|
344
|
+
* back to the project's aspect config.
|
|
345
|
+
*/
|
|
346
|
+
export function readRootDims(rootHtml) {
|
|
347
|
+
const widthMatch = rootHtml.match(/<div\b[^>]*\bid="root"[^>]*\bdata-width="(\d+)"/);
|
|
348
|
+
const heightMatch = rootHtml.match(/<div\b[^>]*\bid="root"[^>]*\bdata-height="(\d+)"/);
|
|
349
|
+
if (!widthMatch || !heightMatch)
|
|
350
|
+
return null;
|
|
351
|
+
return { width: parseInt(widthMatch[1], 10), height: parseInt(heightMatch[1], 10) };
|
|
352
|
+
}
|
|
353
|
+
//# sourceMappingURL=scene-html-emit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene-html-emit.js","sourceRoot":"","sources":["../../../src/commands/_shared/scene-html-emit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene-html-emit.test.d.ts","sourceRoot":"","sources":["../../../src/commands/_shared/scene-html-emit.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildClipReference, emitSceneHtml, insertClipIntoRoot, nextSceneStart, readRootDims, slugifySceneName, SCENE_PRESETS, } from "./scene-html-emit.js";
|
|
3
|
+
import { buildEmptyRootHtml } from "./scene-project.js";
|
|
4
|
+
// ── slugifySceneName ────────────────────────────────────────────────────────
|
|
5
|
+
describe("slugifySceneName", () => {
|
|
6
|
+
it.each([
|
|
7
|
+
["intro", "intro"],
|
|
8
|
+
["My Intro", "my-intro"],
|
|
9
|
+
["Product Shot!", "product-shot"],
|
|
10
|
+
["scene 3 hello", "scene-3-hello"],
|
|
11
|
+
["__weird__name", "weird-name"],
|
|
12
|
+
["", "scene"],
|
|
13
|
+
["!!!", "scene"],
|
|
14
|
+
])("%j → %j", (input, expected) => {
|
|
15
|
+
expect(slugifySceneName(input)).toBe(expected);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
// ── nextSceneStart ──────────────────────────────────────────────────────────
|
|
19
|
+
describe("nextSceneStart", () => {
|
|
20
|
+
it("returns 0 for an empty root", () => {
|
|
21
|
+
const root = buildEmptyRootHtml({ aspect: "16:9", duration: 10 });
|
|
22
|
+
expect(nextSceneStart(root)).toBe(0);
|
|
23
|
+
});
|
|
24
|
+
it("returns the max end-time of existing clips", () => {
|
|
25
|
+
const root = `
|
|
26
|
+
<div class="clip" data-composition-src="a.html" data-start="0" data-duration="3" data-track-index="1"></div>
|
|
27
|
+
<div class="clip" data-composition-src="b.html" data-start="3" data-duration="2.5" data-track-index="1"></div>
|
|
28
|
+
`;
|
|
29
|
+
expect(nextSceneStart(root)).toBeCloseTo(5.5, 5);
|
|
30
|
+
});
|
|
31
|
+
it("ignores clips with malformed data-start", () => {
|
|
32
|
+
const root = `<div class="clip" data-composition-src="x.html" data-start="oops" data-duration="2" data-track-index="1"></div>`;
|
|
33
|
+
expect(nextSceneStart(root)).toBe(0);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
// ── buildClipReference ──────────────────────────────────────────────────────
|
|
37
|
+
describe("buildClipReference", () => {
|
|
38
|
+
it("builds a Hyperframes-compatible clip div with data-composition-id", () => {
|
|
39
|
+
const out = buildClipReference({ id: "intro", start: 0, duration: 4 });
|
|
40
|
+
expect(out).toContain('class="clip"');
|
|
41
|
+
expect(out).toContain('data-composition-id="intro"');
|
|
42
|
+
expect(out).toContain('data-composition-src="compositions/scene-intro.html"');
|
|
43
|
+
expect(out).toContain('data-start="0"');
|
|
44
|
+
expect(out).toContain('data-duration="4"');
|
|
45
|
+
expect(out).toContain('data-track-index="1"');
|
|
46
|
+
});
|
|
47
|
+
it("respects an explicit track and src override and still emits data-composition-id", () => {
|
|
48
|
+
const out = buildClipReference({ id: "x", start: 1.234, duration: 2.345, trackIndex: 3, src: "custom/path.html" });
|
|
49
|
+
expect(out).toContain('data-composition-id="x"');
|
|
50
|
+
expect(out).toContain('data-composition-src="custom/path.html"');
|
|
51
|
+
expect(out).toContain('data-track-index="3"');
|
|
52
|
+
expect(out).toContain('data-start="1.234"');
|
|
53
|
+
expect(out).toContain('data-duration="2.345"');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
// ── insertClipIntoRoot ──────────────────────────────────────────────────────
|
|
57
|
+
describe("insertClipIntoRoot", () => {
|
|
58
|
+
it("inserts a clip before the root closing div", () => {
|
|
59
|
+
const root = buildEmptyRootHtml({ aspect: "16:9", duration: 10 });
|
|
60
|
+
const updated = insertClipIntoRoot(root, { id: "intro", start: 0, duration: 4 });
|
|
61
|
+
expect(updated).toContain('<div class="clip" data-composition-id="intro" data-composition-src="compositions/scene-intro.html"');
|
|
62
|
+
// The new clip must appear *before* the root's closing </div>
|
|
63
|
+
const clipIdx = updated.indexOf("compositions/scene-intro.html");
|
|
64
|
+
const rootCloseIdx = updated.lastIndexOf("</div>\n\n <script>");
|
|
65
|
+
expect(clipIdx).toBeGreaterThan(0);
|
|
66
|
+
expect(rootCloseIdx).toBeGreaterThan(clipIdx);
|
|
67
|
+
});
|
|
68
|
+
it("grows root data-duration when clip end exceeds it but never shrinks", () => {
|
|
69
|
+
const root = buildEmptyRootHtml({ aspect: "16:9", duration: 10 });
|
|
70
|
+
const grown = insertClipIntoRoot(root, { id: "long", start: 0, duration: 14 });
|
|
71
|
+
expect(grown).toContain('data-duration="14"');
|
|
72
|
+
const stillShort = insertClipIntoRoot(root, { id: "short", start: 0, duration: 4 });
|
|
73
|
+
expect(stillShort).toContain('data-duration="10"');
|
|
74
|
+
});
|
|
75
|
+
it("appends sequentially and tracks the new start via nextSceneStart", () => {
|
|
76
|
+
let root = buildEmptyRootHtml({ aspect: "16:9", duration: 0 });
|
|
77
|
+
root = insertClipIntoRoot(root, { id: "a", start: 0, duration: 3 });
|
|
78
|
+
expect(nextSceneStart(root)).toBe(3);
|
|
79
|
+
root = insertClipIntoRoot(root, { id: "b", start: 3, duration: 2 });
|
|
80
|
+
expect(nextSceneStart(root)).toBe(5);
|
|
81
|
+
expect(root).toContain('data-composition-src="compositions/scene-a.html"');
|
|
82
|
+
expect(root).toContain('data-composition-src="compositions/scene-b.html"');
|
|
83
|
+
});
|
|
84
|
+
it("throws on a root that lacks the expected closing tag", () => {
|
|
85
|
+
const broken = "<html><body>no root</body></html>";
|
|
86
|
+
expect(() => insertClipIntoRoot(broken, { id: "x", start: 0, duration: 1 })).toThrow(/closing/);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
// ── readRootDims ────────────────────────────────────────────────────────────
|
|
90
|
+
describe("readRootDims", () => {
|
|
91
|
+
it("reads dims from the standard scaffold", () => {
|
|
92
|
+
const root = buildEmptyRootHtml({ aspect: "9:16", duration: 8 });
|
|
93
|
+
expect(readRootDims(root)).toEqual({ width: 1080, height: 1920 });
|
|
94
|
+
});
|
|
95
|
+
it("returns null when dims are missing", () => {
|
|
96
|
+
expect(readRootDims('<div id="root"></div>')).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
// ── emitSceneHtml — shared invariants ───────────────────────────────────────
|
|
100
|
+
const baseInput = {
|
|
101
|
+
id: "intro",
|
|
102
|
+
width: 1920,
|
|
103
|
+
height: 1080,
|
|
104
|
+
duration: 4,
|
|
105
|
+
headline: "Welcome to VibeFrame",
|
|
106
|
+
};
|
|
107
|
+
describe.each(SCENE_PRESETS)("emitSceneHtml(%s)", (preset) => {
|
|
108
|
+
it("emits a Hyperframes-compatible composition", () => {
|
|
109
|
+
const html = emitSceneHtml({ ...baseInput, preset });
|
|
110
|
+
expect(html).toContain('<template id="scene-intro-template">');
|
|
111
|
+
expect(html).toContain('data-composition-id="intro"');
|
|
112
|
+
expect(html).toContain('data-start="0"');
|
|
113
|
+
expect(html).toContain('data-duration="4"');
|
|
114
|
+
expect(html).toContain('data-width="1920"');
|
|
115
|
+
expect(html).toContain('data-height="1080"');
|
|
116
|
+
});
|
|
117
|
+
it("registers a paused timeline keyed by the scene id", () => {
|
|
118
|
+
const html = emitSceneHtml({ ...baseInput, preset });
|
|
119
|
+
expect(html).toContain("gsap.timeline({ paused: true })");
|
|
120
|
+
expect(html).toContain('window.__timelines["intro"] = tl');
|
|
121
|
+
});
|
|
122
|
+
it("includes the audio element only when audioPath is given", () => {
|
|
123
|
+
const without = emitSceneHtml({ ...baseInput, preset });
|
|
124
|
+
expect(without).not.toContain("<audio");
|
|
125
|
+
const withAudio = emitSceneHtml({ ...baseInput, preset, audioPath: "assets/narration-intro.mp3" });
|
|
126
|
+
expect(withAudio).toContain('<audio');
|
|
127
|
+
expect(withAudio).toContain('src="assets/narration-intro.mp3"');
|
|
128
|
+
expect(withAudio).toContain('data-track-index="2"');
|
|
129
|
+
});
|
|
130
|
+
it("includes the backdrop image only when imagePath is given", () => {
|
|
131
|
+
const without = emitSceneHtml({ ...baseInput, preset });
|
|
132
|
+
expect(without).not.toContain("background-image: url(");
|
|
133
|
+
const withImage = emitSceneHtml({ ...baseInput, preset, imagePath: "assets/scene-intro.png" });
|
|
134
|
+
expect(withImage).toContain('background-image: url("assets/scene-intro.png")');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
// ── emitSceneHtml — preset specifics ────────────────────────────────────────
|
|
138
|
+
describe("emitSceneHtml — preset specifics", () => {
|
|
139
|
+
it("simple: shows narration as the caption", () => {
|
|
140
|
+
const html = emitSceneHtml({ ...baseInput, preset: "simple", subhead: "Hello world" });
|
|
141
|
+
expect(html).toContain("Hello world");
|
|
142
|
+
expect(html).toContain('id="caption"');
|
|
143
|
+
});
|
|
144
|
+
it("announcement: renders headline as the centered h1", () => {
|
|
145
|
+
const html = emitSceneHtml({ ...baseInput, preset: "announcement" });
|
|
146
|
+
expect(html).toMatch(/<h1[^>]*id="headline">\s*Welcome to VibeFrame\s*<\/h1>/);
|
|
147
|
+
});
|
|
148
|
+
it("explainer: renders kicker + title and includes subtitle when subhead is set", () => {
|
|
149
|
+
const html = emitSceneHtml({
|
|
150
|
+
...baseInput,
|
|
151
|
+
preset: "explainer",
|
|
152
|
+
kicker: "Episode 1",
|
|
153
|
+
subhead: "Authoring videos with code",
|
|
154
|
+
});
|
|
155
|
+
expect(html).toContain('id="kicker"');
|
|
156
|
+
expect(html).toContain("Episode 1");
|
|
157
|
+
expect(html).toContain('id="subtitle"');
|
|
158
|
+
expect(html).toContain("Authoring videos with code");
|
|
159
|
+
});
|
|
160
|
+
it("explainer: omits the subtitle div when no subhead is provided", () => {
|
|
161
|
+
const html = emitSceneHtml({ ...baseInput, preset: "explainer" });
|
|
162
|
+
expect(html).not.toContain('id="subtitle"');
|
|
163
|
+
});
|
|
164
|
+
it("kinetic-type: emits one span per word", () => {
|
|
165
|
+
const html = emitSceneHtml({ ...baseInput, preset: "kinetic-type" });
|
|
166
|
+
// baseInput.headline = "Welcome to VibeFrame" → 3 words
|
|
167
|
+
expect(html).toContain('id="w-0"');
|
|
168
|
+
expect(html).toContain('id="w-1"');
|
|
169
|
+
expect(html).toContain('id="w-2"');
|
|
170
|
+
expect(html).not.toContain('id="w-3"');
|
|
171
|
+
});
|
|
172
|
+
it("product-shot: applies a Ken-Burns scale tween over the full duration", () => {
|
|
173
|
+
const html = emitSceneHtml({ ...baseInput, preset: "product-shot", duration: 6 });
|
|
174
|
+
expect(html).toContain("scale: 1.08, duration: 6.00");
|
|
175
|
+
});
|
|
176
|
+
it("escapes HTML metacharacters in headline + subhead", () => {
|
|
177
|
+
const html = emitSceneHtml({
|
|
178
|
+
id: "x",
|
|
179
|
+
preset: "simple",
|
|
180
|
+
width: 1920,
|
|
181
|
+
height: 1080,
|
|
182
|
+
duration: 3,
|
|
183
|
+
subhead: '<script>alert("xss")</script>',
|
|
184
|
+
});
|
|
185
|
+
expect(html).not.toContain("<script>alert");
|
|
186
|
+
expect(html).toContain("<script>alert("xss")</script>");
|
|
187
|
+
});
|
|
188
|
+
it("rejects non-positive durations", () => {
|
|
189
|
+
expect(() => emitSceneHtml({ ...baseInput, preset: "simple", duration: 0 })).toThrow(/duration/);
|
|
190
|
+
expect(() => emitSceneHtml({ ...baseInput, preset: "simple", duration: -1 })).toThrow(/duration/);
|
|
191
|
+
});
|
|
192
|
+
it("falls back to humanised id when no headline is provided", () => {
|
|
193
|
+
const html = emitSceneHtml({
|
|
194
|
+
id: "product-shot",
|
|
195
|
+
preset: "announcement",
|
|
196
|
+
width: 1920,
|
|
197
|
+
height: 1080,
|
|
198
|
+
duration: 4,
|
|
199
|
+
});
|
|
200
|
+
expect(html).toContain("Product Shot");
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
//# sourceMappingURL=scene-html-emit.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene-html-emit.test.js","sourceRoot":"","sources":["../../../src/commands/_shared/scene-html-emit.test.ts"],"names":[],"mappings":"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"}
|