@vibeframe/cli 0.52.0 → 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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agent/tools/index.d.ts +1 -0
  3. package/dist/agent/tools/index.d.ts.map +1 -1
  4. package/dist/agent/tools/index.js +3 -0
  5. package/dist/agent/tools/index.js.map +1 -1
  6. package/dist/agent/tools/scene.d.ts +17 -0
  7. package/dist/agent/tools/scene.d.ts.map +1 -0
  8. package/dist/agent/tools/scene.js +263 -0
  9. package/dist/agent/tools/scene.js.map +1 -0
  10. package/dist/agent/tools/scene.test.d.ts +2 -0
  11. package/dist/agent/tools/scene.test.d.ts.map +1 -0
  12. package/dist/agent/tools/scene.test.js +139 -0
  13. package/dist/agent/tools/scene.test.js.map +1 -0
  14. package/dist/commands/_shared/scene-html-emit.d.ts +85 -0
  15. package/dist/commands/_shared/scene-html-emit.d.ts.map +1 -0
  16. package/dist/commands/_shared/scene-html-emit.js +353 -0
  17. package/dist/commands/_shared/scene-html-emit.js.map +1 -0
  18. package/dist/commands/_shared/scene-html-emit.test.d.ts +2 -0
  19. package/dist/commands/_shared/scene-html-emit.test.d.ts.map +1 -0
  20. package/dist/commands/_shared/scene-html-emit.test.js +203 -0
  21. package/dist/commands/_shared/scene-html-emit.test.js.map +1 -0
  22. package/dist/commands/_shared/scene-lint.d.ts +110 -0
  23. package/dist/commands/_shared/scene-lint.d.ts.map +1 -0
  24. package/dist/commands/_shared/scene-lint.js +207 -0
  25. package/dist/commands/_shared/scene-lint.js.map +1 -0
  26. package/dist/commands/_shared/scene-lint.test.d.ts +2 -0
  27. package/dist/commands/_shared/scene-lint.test.d.ts.map +1 -0
  28. package/dist/commands/_shared/scene-lint.test.js +208 -0
  29. package/dist/commands/_shared/scene-lint.test.js.map +1 -0
  30. package/dist/commands/_shared/scene-project.d.ts +101 -0
  31. package/dist/commands/_shared/scene-project.d.ts.map +1 -0
  32. package/dist/commands/_shared/scene-project.js +281 -0
  33. package/dist/commands/_shared/scene-project.js.map +1 -0
  34. package/dist/commands/_shared/scene-project.test.d.ts +2 -0
  35. package/dist/commands/_shared/scene-project.test.d.ts.map +1 -0
  36. package/dist/commands/_shared/scene-project.test.js +203 -0
  37. package/dist/commands/_shared/scene-project.test.js.map +1 -0
  38. package/dist/commands/_shared/scene-render.d.ts +79 -0
  39. package/dist/commands/_shared/scene-render.d.ts.map +1 -0
  40. package/dist/commands/_shared/scene-render.js +138 -0
  41. package/dist/commands/_shared/scene-render.js.map +1 -0
  42. package/dist/commands/_shared/scene-render.test.d.ts +2 -0
  43. package/dist/commands/_shared/scene-render.test.d.ts.map +1 -0
  44. package/dist/commands/_shared/scene-render.test.js +145 -0
  45. package/dist/commands/_shared/scene-render.test.js.map +1 -0
  46. package/dist/commands/_shared/segments-to-scenes.d.ts +85 -0
  47. package/dist/commands/_shared/segments-to-scenes.d.ts.map +1 -0
  48. package/dist/commands/_shared/segments-to-scenes.js +202 -0
  49. package/dist/commands/_shared/segments-to-scenes.js.map +1 -0
  50. package/dist/commands/_shared/segments-to-scenes.test.d.ts +2 -0
  51. package/dist/commands/_shared/segments-to-scenes.test.d.ts.map +1 -0
  52. package/dist/commands/_shared/segments-to-scenes.test.js +219 -0
  53. package/dist/commands/_shared/segments-to-scenes.test.js.map +1 -0
  54. package/dist/commands/ai-script-pipeline-cli.d.ts.map +1 -1
  55. package/dist/commands/ai-script-pipeline-cli.js +49 -0
  56. package/dist/commands/ai-script-pipeline-cli.js.map +1 -1
  57. package/dist/commands/ai-script-pipeline.d.ts +16 -0
  58. package/dist/commands/ai-script-pipeline.d.ts.map +1 -1
  59. package/dist/commands/ai-script-pipeline.js +28 -0
  60. package/dist/commands/ai-script-pipeline.js.map +1 -1
  61. package/dist/commands/ai.test.js +4 -1
  62. package/dist/commands/ai.test.js.map +1 -1
  63. package/dist/commands/generate.d.ts.map +1 -1
  64. package/dist/commands/generate.js +8 -2
  65. package/dist/commands/generate.js.map +1 -1
  66. package/dist/commands/scene.d.ts +60 -0
  67. package/dist/commands/scene.d.ts.map +1 -0
  68. package/dist/commands/scene.js +621 -0
  69. package/dist/commands/scene.js.map +1 -0
  70. package/dist/commands/scene.test.d.ts +2 -0
  71. package/dist/commands/scene.test.d.ts.map +1 -0
  72. package/dist/commands/scene.test.js +133 -0
  73. package/dist/commands/scene.test.js.map +1 -0
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +2 -0
  76. package/dist/index.js.map +1 -1
  77. package/dist/pipeline/renderers/chrome.d.ts +26 -0
  78. package/dist/pipeline/renderers/chrome.d.ts.map +1 -0
  79. package/dist/pipeline/renderers/chrome.js +48 -0
  80. package/dist/pipeline/renderers/chrome.js.map +1 -0
  81. package/dist/pipeline/renderers/hyperframes.d.ts.map +1 -1
  82. package/dist/pipeline/renderers/hyperframes.js +2 -32
  83. package/dist/pipeline/renderers/hyperframes.js.map +1 -1
  84. 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, "&lt;")
14
+ .replace(/>/g, "&gt;")
15
+ .replace(/"/g, "&quot;")
16
+ .replace(/'/g, "&#39;");
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scene-html-emit.test.d.ts.map
@@ -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("&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;");
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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"}