reframe-video 0.6.34 → 0.6.39

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/dist/lint.js ADDED
@@ -0,0 +1,1060 @@
1
+ #!/usr/bin/env tsx
2
+
3
+ // ../core/src/ir.ts
4
+ var SFX_NAMES = [
5
+ "whoosh",
6
+ "swish",
7
+ "swoosh",
8
+ "rise",
9
+ "riser",
10
+ "warp",
11
+ "tick",
12
+ "click",
13
+ "blip",
14
+ "pop",
15
+ "select",
16
+ "thud",
17
+ "boom",
18
+ "knock",
19
+ "sub",
20
+ "chime",
21
+ "ding",
22
+ "coin",
23
+ "sparkle",
24
+ "shimmer",
25
+ "success",
26
+ "zap",
27
+ "error",
28
+ "glitch",
29
+ "static",
30
+ "scan",
31
+ "powerup",
32
+ "powerdown",
33
+ "snare",
34
+ "hat",
35
+ "bubble",
36
+ "notify",
37
+ "camera"
38
+ ];
39
+ var BGM_SYNTHS = ["ambient-pad", "lofi", "pulse", "tension", "uplift"];
40
+ var DEFAULT_TO_DURATION = 0.5;
41
+ var DEFAULT_TWEEN_DURATION = 0.5;
42
+ var DEFAULT_MOTIONPATH_DURATION = 1;
43
+ var DEFAULT_STILL_DURATION = 1;
44
+
45
+ // ../core/src/path.ts
46
+ function locate(segCount, u) {
47
+ if (segCount <= 0) return { i: 0, t: 0 };
48
+ const clamped = Math.max(0, Math.min(1, u));
49
+ const scaled = clamped * segCount;
50
+ let i = Math.floor(scaled);
51
+ if (i >= segCount) i = segCount - 1;
52
+ return { i, t: scaled - i };
53
+ }
54
+ function controls(points, closed, i) {
55
+ const n = points.length;
56
+ const at = (k) => {
57
+ if (closed) return points[(k % n + n) % n];
58
+ return points[Math.max(0, Math.min(n - 1, k))];
59
+ };
60
+ return [at(i - 1), at(i), at(i + 1), at(i + 2)];
61
+ }
62
+ function segCountOf(points, closed) {
63
+ const n = points.length;
64
+ if (n < 2) return 0;
65
+ return closed ? n : n - 1;
66
+ }
67
+ function pathPoint(points, closed, u, curviness = 1) {
68
+ const n = points.length;
69
+ if (n === 0) return [0, 0];
70
+ if (n === 1) return [points[0][0], points[0][1]];
71
+ const segs = segCountOf(points, closed);
72
+ const { i, t } = locate(segs, u);
73
+ const [p0, p1, p2, p3] = controls(points, closed, i);
74
+ const t2 = t * t;
75
+ const t3 = t2 * t;
76
+ if (curviness === 1) {
77
+ const f = (a, b, c, d) => 0.5 * (2 * b + (-a + c) * t + (2 * a - 5 * b + 4 * c - d) * t2 + (-a + 3 * b - 3 * c + d) * t3);
78
+ return [f(p0[0], p1[0], p2[0], p3[0]), f(p0[1], p1[1], p2[1], p3[1])];
79
+ }
80
+ const h00 = 2 * t3 - 3 * t2 + 1;
81
+ const h10 = t3 - 2 * t2 + t;
82
+ const h01 = -2 * t3 + 3 * t2;
83
+ const h11 = t3 - t2;
84
+ const k = curviness * 0.5;
85
+ const H = (a, b, c, d) => h00 * b + h10 * k * (c - a) + h01 * c + h11 * k * (d - b);
86
+ return [H(p0[0], p1[0], p2[0], p3[0]), H(p0[1], p1[1], p2[1], p3[1])];
87
+ }
88
+ function pathTangentAngle(points, closed, u, curviness = 1) {
89
+ const n = points.length;
90
+ if (n < 2) return 0;
91
+ const segs = segCountOf(points, closed);
92
+ const { i, t } = locate(segs, u);
93
+ const [p0, p1, p2, p3] = controls(points, closed, i);
94
+ const t2 = t * t;
95
+ let dx;
96
+ let dy;
97
+ if (curviness === 1) {
98
+ const d = (a, b, c, e) => 0.5 * (-a + c + 2 * (2 * a - 5 * b + 4 * c - e) * t + 3 * (-a + 3 * b - 3 * c + e) * t2);
99
+ dx = d(p0[0], p1[0], p2[0], p3[0]);
100
+ dy = d(p0[1], p1[1], p2[1], p3[1]);
101
+ } else {
102
+ const g00 = 6 * t2 - 6 * t;
103
+ const g10 = 3 * t2 - 4 * t + 1;
104
+ const g01 = -6 * t2 + 6 * t;
105
+ const g11 = 3 * t2 - 2 * t;
106
+ const k = curviness * 0.5;
107
+ const D = (a, b, c, e) => g00 * b + g10 * k * (c - a) + g01 * c + g11 * k * (e - b);
108
+ dx = D(p0[0], p1[0], p2[0], p3[0]);
109
+ dy = D(p0[1], p1[1], p2[1], p3[1]);
110
+ }
111
+ if (dx === 0 && dy === 0) return 0;
112
+ return Math.atan2(dy, dx) * 180 / Math.PI;
113
+ }
114
+
115
+ // ../core/src/compile.ts
116
+ var key = (target, prop) => `${target}.${prop}`;
117
+ function scaleTimeline(tl, k) {
118
+ switch (tl.kind) {
119
+ case "seq":
120
+ case "par":
121
+ return { ...tl, children: tl.children.map((c) => scaleTimeline(c, k)) };
122
+ case "stagger":
123
+ return { ...tl, interval: tl.interval * k, children: tl.children.map((c) => scaleTimeline(c, k)) };
124
+ case "wait":
125
+ return { ...tl, duration: tl.duration * k };
126
+ case "tween":
127
+ return { ...tl, duration: (tl.duration ?? DEFAULT_TWEEN_DURATION) * k };
128
+ case "motionPath":
129
+ return { ...tl, duration: (tl.duration ?? DEFAULT_MOTIONPATH_DURATION) * k };
130
+ case "to":
131
+ return {
132
+ ...tl,
133
+ duration: (tl.duration ?? DEFAULT_TO_DURATION) * k,
134
+ ...tl.stagger !== void 0 && { stagger: tl.stagger * k }
135
+ };
136
+ case "beat":
137
+ return {
138
+ ...tl,
139
+ children: tl.children.map((c) => scaleTimeline(c, k)),
140
+ ...tl.gap !== void 0 && { gap: tl.gap * k }
141
+ };
142
+ }
143
+ }
144
+ function orderBeats(children) {
145
+ return children.map((c, i) => ({ c, i, key: c.kind === "beat" && c.order !== void 0 ? c.order : i })).sort((a, b) => a.key - b.key || a.i - b.i).map((x) => x.c);
146
+ }
147
+ function compileScene(ir) {
148
+ const nodeById = /* @__PURE__ */ new Map();
149
+ const nodeOrder = [];
150
+ const collect = (nodes) => {
151
+ for (const node of nodes) {
152
+ nodeById.set(node.id, node);
153
+ nodeOrder.push(node.id);
154
+ if (node.type === "group") collect(node.children);
155
+ }
156
+ };
157
+ collect(ir.nodes);
158
+ const initialValues = /* @__PURE__ */ new Map();
159
+ for (const [id, node] of nodeById) {
160
+ for (const [prop, value] of Object.entries(node.props)) {
161
+ if (typeof value === "number" || typeof value === "string") {
162
+ initialValues.set(key(id, prop), value);
163
+ }
164
+ }
165
+ }
166
+ if (ir.initial !== void 0) {
167
+ const override = ir.states?.[ir.initial] ?? {};
168
+ for (const [id, props] of Object.entries(override)) {
169
+ for (const [prop, value] of Object.entries(props)) {
170
+ initialValues.set(key(id, prop), value);
171
+ }
172
+ }
173
+ }
174
+ const cameraIsNode = nodeById.has("camera");
175
+ if (!cameraIsNode) {
176
+ const cam = ir.camera ?? {};
177
+ initialValues.set(key("camera", "x"), cam.x ?? ir.size.width / 2);
178
+ initialValues.set(key("camera", "y"), cam.y ?? ir.size.height / 2);
179
+ initialValues.set(key("camera", "zoom"), cam.zoom ?? 1);
180
+ initialValues.set(key("camera", "rotation"), cam.rotation ?? 0);
181
+ if (cam.perspective !== void 0) initialValues.set(key("camera", "perspective"), cam.perspective);
182
+ if (cam.focus !== void 0) initialValues.set(key("camera", "focus"), cam.focus);
183
+ if (cam.aperture !== void 0) initialValues.set(key("camera", "aperture"), cam.aperture);
184
+ }
185
+ const segments = /* @__PURE__ */ new Map();
186
+ const motionPaths = /* @__PURE__ */ new Map();
187
+ const current = new Map(initialValues);
188
+ const pushSegment = (seg) => {
189
+ const k = key(seg.target, seg.prop);
190
+ let list = segments.get(k);
191
+ if (!list) segments.set(k, list = []);
192
+ list.push(seg);
193
+ current.set(k, seg.to);
194
+ };
195
+ const currentValue = (target, prop) => {
196
+ const v = current.get(key(target, prop));
197
+ if (v !== void 0) return v;
198
+ if (prop === "opacity" || prop === "scale" || prop === "progress" || prop === "scaleX" || prop === "scaleY") return 1;
199
+ if (prop === "rotation" || prop === "skewX" || prop === "skewY") return 0;
200
+ throw new Error(`cannot animate "${prop}" of "${target}": no base value to start from`);
201
+ };
202
+ const labelTimes = /* @__PURE__ */ new Map();
203
+ const beatTimes = /* @__PURE__ */ new Map();
204
+ const durationOf = (tl, start) => {
205
+ switch (tl.kind) {
206
+ case "seq": {
207
+ let t = start;
208
+ for (const child of orderBeats(tl.children)) t = durationOf(child, t);
209
+ return t;
210
+ }
211
+ case "par": {
212
+ let end = start;
213
+ for (const child of tl.children) end = Math.max(end, durationOf(child, start));
214
+ return end;
215
+ }
216
+ case "stagger": {
217
+ let end = start;
218
+ tl.children.forEach((child, i) => {
219
+ end = Math.max(end, durationOf(child, start + i * tl.interval));
220
+ });
221
+ return end;
222
+ }
223
+ case "wait":
224
+ return start + tl.duration;
225
+ case "tween":
226
+ return start + (tl.duration ?? DEFAULT_TWEEN_DURATION);
227
+ case "motionPath":
228
+ return start + (tl.duration ?? DEFAULT_MOTIONPATH_DURATION);
229
+ case "to": {
230
+ const override = ir.states?.[tl.state] ?? {};
231
+ const duration = tl.duration ?? DEFAULT_TO_DURATION;
232
+ const si = tl.stagger ?? 0;
233
+ const targets = nodeOrder.filter(
234
+ (id) => id in override && (tl.filter === void 0 || tl.filter.includes(id))
235
+ );
236
+ return start + duration + Math.max(0, targets.length - 1) * si;
237
+ }
238
+ case "beat": {
239
+ const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
240
+ const natural = durationOf(grouping, 0);
241
+ const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, natural) : 1);
242
+ const at = typeof tl.at === "number" ? tl.at : void 0;
243
+ const beatStart = at ?? start + (tl.gap ?? 0);
244
+ return beatStart + k * natural;
245
+ }
246
+ }
247
+ };
248
+ let labelClock;
249
+ const anyAnchor = (tl) => tl.kind === "beat" && typeof tl.at === "string" || "children" in tl && tl.children.some(anyAnchor);
250
+ if (ir.timeline && anyAnchor(ir.timeline)) {
251
+ const clock = /* @__PURE__ */ new Map();
252
+ const clockWalk = (tl, start) => {
253
+ let end = start;
254
+ switch (tl.kind) {
255
+ case "seq": {
256
+ let t = start;
257
+ for (const c of orderBeats(tl.children)) t = clockWalk(c, t);
258
+ end = t;
259
+ break;
260
+ }
261
+ case "par": {
262
+ for (const c of tl.children) end = Math.max(end, clockWalk(c, start));
263
+ break;
264
+ }
265
+ case "stagger": {
266
+ tl.children.forEach((c, i) => {
267
+ end = Math.max(end, clockWalk(c, start + i * tl.interval));
268
+ });
269
+ break;
270
+ }
271
+ case "wait":
272
+ end = start + tl.duration;
273
+ break;
274
+ case "tween":
275
+ end = start + (tl.duration ?? DEFAULT_TWEEN_DURATION);
276
+ break;
277
+ case "motionPath":
278
+ end = start + (tl.duration ?? DEFAULT_MOTIONPATH_DURATION);
279
+ break;
280
+ case "to": {
281
+ const override = ir.states?.[tl.state] ?? {};
282
+ const si = tl.stagger ?? 0;
283
+ const targets = nodeOrder.filter((id) => id in override && (tl.filter === void 0 || tl.filter.includes(id)));
284
+ end = start + (tl.duration ?? DEFAULT_TO_DURATION) + Math.max(0, targets.length - 1) * si;
285
+ break;
286
+ }
287
+ case "beat": {
288
+ const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
289
+ const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
290
+ const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
291
+ const at = typeof tl.at === "number" ? tl.at : void 0;
292
+ const beatStart = at ?? start + (tl.gap ?? 0);
293
+ end = clockWalk(inner, beatStart);
294
+ clock.set(tl.name, { t0: beatStart, t1: end });
295
+ break;
296
+ }
297
+ }
298
+ if ("label" in tl && tl.label !== void 0) clock.set(tl.label, { t0: start, t1: end });
299
+ return end;
300
+ };
301
+ clockWalk(ir.timeline, 0);
302
+ labelClock = clock;
303
+ }
304
+ const walk = (tl, start) => {
305
+ const end = walkInner(tl, start);
306
+ if ("label" in tl && tl.label !== void 0) labelTimes.set(tl.label, { t0: start, t1: end });
307
+ return end;
308
+ };
309
+ const walkInner = (tl, start) => {
310
+ switch (tl.kind) {
311
+ case "seq": {
312
+ let t = start;
313
+ for (const child of orderBeats(tl.children)) t = walk(child, t);
314
+ return t;
315
+ }
316
+ case "beat": {
317
+ const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
318
+ const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
319
+ const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
320
+ const anchored = typeof tl.at === "string" ? labelClock?.get(tl.at)?.t0 : tl.at;
321
+ const beatStart = anchored !== void 0 ? anchored + (typeof tl.at === "string" ? tl.gap ?? 0 : 0) : start + (tl.gap ?? 0);
322
+ const end = walk(inner, beatStart);
323
+ beatTimes.set(tl.name, { t0: beatStart, t1: end });
324
+ labelTimes.set(tl.name, { t0: beatStart, t1: end });
325
+ return end;
326
+ }
327
+ case "par": {
328
+ let end = start;
329
+ for (const child of tl.children) end = Math.max(end, walk(child, start));
330
+ return end;
331
+ }
332
+ case "stagger": {
333
+ let end = start;
334
+ tl.children.forEach((child, i) => {
335
+ end = Math.max(end, walk(child, start + i * tl.interval));
336
+ });
337
+ return end;
338
+ }
339
+ case "wait":
340
+ return start + tl.duration;
341
+ case "tween": {
342
+ const duration = tl.duration ?? DEFAULT_TWEEN_DURATION;
343
+ for (const [prop, toValue] of Object.entries(tl.props)) {
344
+ pushSegment({
345
+ target: tl.target,
346
+ prop,
347
+ t0: start,
348
+ t1: start + duration,
349
+ from: currentValue(tl.target, prop),
350
+ to: toValue,
351
+ ...tl.ease !== void 0 && { ease: tl.ease }
352
+ });
353
+ }
354
+ return start + duration;
355
+ }
356
+ case "motionPath": {
357
+ const duration = tl.duration ?? DEFAULT_MOTIONPATH_DURATION;
358
+ const points = tl.points;
359
+ const closed = tl.closed ?? false;
360
+ const curviness = tl.curviness ?? 1;
361
+ const autoRotate = tl.autoRotate ?? false;
362
+ const rotateOffset = tl.rotateOffset ?? 0;
363
+ let list = motionPaths.get(tl.target);
364
+ if (!list) motionPaths.set(tl.target, list = []);
365
+ list.push({ t0: start, t1: start + duration, points, closed, curviness, autoRotate, rotateOffset, ...tl.ease !== void 0 && { ease: tl.ease } });
366
+ if (points.length > 0) {
367
+ const [ex, ey] = pathPoint(points, closed, 1, curviness);
368
+ current.set(key(tl.target, "x"), ex);
369
+ current.set(key(tl.target, "y"), ey);
370
+ if (autoRotate) current.set(key(tl.target, "rotation"), pathTangentAngle(points, closed, 1, curviness) + rotateOffset);
371
+ }
372
+ return start + duration;
373
+ }
374
+ case "to": {
375
+ const override = ir.states?.[tl.state] ?? {};
376
+ const duration = tl.duration ?? DEFAULT_TO_DURATION;
377
+ const staggerInterval = tl.stagger ?? 0;
378
+ const targets = nodeOrder.filter(
379
+ (id) => id in override && (tl.filter === void 0 || tl.filter.includes(id))
380
+ );
381
+ targets.forEach((id, i) => {
382
+ const t0 = start + i * staggerInterval;
383
+ for (const [prop, toValue] of Object.entries(override[id])) {
384
+ pushSegment({
385
+ target: id,
386
+ prop,
387
+ t0,
388
+ t1: t0 + duration,
389
+ from: currentValue(id, prop),
390
+ to: toValue,
391
+ ...tl.ease !== void 0 && { ease: tl.ease }
392
+ });
393
+ }
394
+ });
395
+ const last = Math.max(0, targets.length - 1);
396
+ return start + duration + last * staggerInterval;
397
+ }
398
+ }
399
+ };
400
+ const inferredEnd = (ir.timeline ? walk(ir.timeline, 0) : 0) || 0;
401
+ for (const list of segments.values()) list.sort((a, b) => a.t0 - b.t0);
402
+ for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
403
+ const hasCamera = !cameraIsNode && (ir.camera !== void 0 || motionPaths.has("camera") || [...segments.keys()].some((k) => k.startsWith("camera.")));
404
+ const hasPerspective = !cameraIsNode && (ir.camera?.perspective !== void 0 || segments.has("camera.perspective"));
405
+ const zSort = !cameraIsNode && ir.camera?.zSort === true && hasPerspective;
406
+ return {
407
+ ir,
408
+ duration: ir.duration ?? (inferredEnd > 0 ? inferredEnd : DEFAULT_STILL_DURATION),
409
+ segments,
410
+ motionPaths,
411
+ initialValues,
412
+ nodeById,
413
+ nodeOrder,
414
+ labelTimes,
415
+ beatTimes,
416
+ hasCamera,
417
+ hasPerspective,
418
+ zSort
419
+ };
420
+ }
421
+
422
+ // ../core/src/interpolate.ts
423
+ var BACK_C1 = 1.70158;
424
+ var BACK_C2 = BACK_C1 * 1.525;
425
+ var BACK_C3 = BACK_C1 + 1;
426
+ var ELASTIC_C4 = 2 * Math.PI / 3;
427
+ var ELASTIC_C5 = 2 * Math.PI / 4.5;
428
+ function springEase(stiffness, damping, velocity) {
429
+ const K = 5;
430
+ const zeta = Math.min(0.999, Math.max(0.05, damping / (2 * Math.sqrt(Math.max(1e-6, stiffness)))));
431
+ const wd = K / zeta * Math.sqrt(1 - zeta * zeta);
432
+ const coef = (K - velocity) / wd;
433
+ return (u) => {
434
+ if (u <= 0) return 0;
435
+ if (u >= 1) return 1;
436
+ return 1 - Math.exp(-K * u) * (Math.cos(wd * u) + coef * Math.sin(wd * u));
437
+ };
438
+ }
439
+ function easeOutBounce(u) {
440
+ const n1 = 7.5625;
441
+ const d1 = 2.75;
442
+ if (u < 1 / d1) return n1 * u * u;
443
+ if (u < 2 / d1) return n1 * (u -= 1.5 / d1) * u + 0.75;
444
+ if (u < 2.5 / d1) return n1 * (u -= 2.25 / d1) * u + 0.9375;
445
+ return n1 * (u -= 2.625 / d1) * u + 0.984375;
446
+ }
447
+ var EASE_TABLE = {
448
+ linear: (u) => u,
449
+ easeInQuad: (u) => u * u,
450
+ easeOutQuad: (u) => 1 - (1 - u) * (1 - u),
451
+ easeInOutQuad: (u) => u < 0.5 ? 2 * u * u : 1 - (-2 * u + 2) ** 2 / 2,
452
+ easeInCubic: (u) => u ** 3,
453
+ easeOutCubic: (u) => 1 - (1 - u) ** 3,
454
+ easeInOutCubic: (u) => u < 0.5 ? 4 * u ** 3 : 1 - (-2 * u + 2) ** 3 / 2,
455
+ easeInQuart: (u) => u ** 4,
456
+ easeOutQuart: (u) => 1 - (1 - u) ** 4,
457
+ easeInOutQuart: (u) => u < 0.5 ? 8 * u ** 4 : 1 - (-2 * u + 2) ** 4 / 2,
458
+ easeInExpo: (u) => u === 0 ? 0 : 2 ** (10 * u - 10),
459
+ easeOutExpo: (u) => u === 1 ? 1 : 1 - 2 ** (-10 * u),
460
+ easeInOutExpo: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? 2 ** (20 * u - 10) / 2 : (2 - 2 ** (-20 * u + 10)) / 2,
461
+ // --- expressive eases (GSAP's signature feel) — standard Penner equations ---
462
+ // back: overshoots past the target then settles (pop / snap)
463
+ easeInBack: (u) => BACK_C3 * u ** 3 - BACK_C1 * u * u,
464
+ easeOutBack: (u) => 1 + BACK_C3 * (u - 1) ** 3 + BACK_C1 * (u - 1) ** 2,
465
+ easeInOutBack: (u) => u < 0.5 ? (2 * u) ** 2 * ((BACK_C2 + 1) * 2 * u - BACK_C2) / 2 : ((2 * u - 2) ** 2 * ((BACK_C2 + 1) * (2 * u - 2) + BACK_C2) + 2) / 2,
466
+ // elastic: rings around the target before settling (playful spring)
467
+ easeInElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : -(2 ** (10 * u - 10)) * Math.sin((u * 10 - 10.75) * ELASTIC_C4),
468
+ easeOutElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : 2 ** (-10 * u) * Math.sin((u * 10 - 0.75) * ELASTIC_C4) + 1,
469
+ easeInOutElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? -(2 ** (20 * u - 10) * Math.sin((20 * u - 11.125) * ELASTIC_C5)) / 2 : 2 ** (-20 * u + 10) * Math.sin((20 * u - 11.125) * ELASTIC_C5) / 2 + 1,
470
+ // bounce: drops and bounces to rest (lands without overshoot)
471
+ easeInBounce: (u) => 1 - easeOutBounce(1 - u),
472
+ easeOutBounce,
473
+ easeInOutBounce: (u) => u < 0.5 ? (1 - easeOutBounce(1 - 2 * u)) / 2 : (1 + easeOutBounce(2 * u - 1)) / 2,
474
+ // damped-spring presets (ζ from damping/(2√stiffness)): 0.5 / 0.30 / 0.90
475
+ spring: springEase(100, 10, 0),
476
+ springBouncy: springEase(180, 8, 0),
477
+ springStiff: springEase(210, 26, 0)
478
+ };
479
+ var EASE_NAMES = Object.keys(EASE_TABLE);
480
+
481
+ // ../core/src/validate.ts
482
+ var EASE_SET = new Set(EASE_NAMES);
483
+ var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY", "blend"];
484
+ var BLEND_MODES = /* @__PURE__ */ new Set([
485
+ "normal",
486
+ "multiply",
487
+ "screen",
488
+ "overlay",
489
+ "lighten",
490
+ "darken",
491
+ "add",
492
+ "color-dodge",
493
+ "soft-light",
494
+ "hard-light",
495
+ "difference"
496
+ ]);
497
+ var IMAGE_FITS = /* @__PURE__ */ new Set(["fill", "cover"]);
498
+ var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "z", "rotateX", "rotateY", "anchor", "fixed", ...FX_PROPS];
499
+ var CAMERA_PROPS = ["x", "y", "zoom", "rotation", "perspective", "focus", "aperture"];
500
+ var PROPS_BY_TYPE = {
501
+ rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
502
+ ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
503
+ line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
504
+ text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "prefix", "suffix", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
505
+ image: [...COMMON_PROPS, "src", "width", "height", "fit"],
506
+ video: [...COMMON_PROPS, "src", "width", "height", "fit", "start", "rate", "clipStart", "volume", "fadeIn", "pan"],
507
+ path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
508
+ group: COMMON_PROPS
509
+ };
510
+ var SceneValidationError = class extends Error {
511
+ constructor(problems) {
512
+ super(`Scene validation failed:
513
+ ${problems.map((p) => ` - ${p}`).join("\n")}`);
514
+ this.problems = problems;
515
+ this.name = "SceneValidationError";
516
+ }
517
+ problems;
518
+ };
519
+ function validateScene(ir) {
520
+ const problems = [];
521
+ const nodeById = /* @__PURE__ */ new Map();
522
+ const startAnchors = [];
523
+ const checkPaint = (where, value) => {
524
+ if (typeof value !== "object" || value === null) return;
525
+ const g = value;
526
+ if (g.kind !== "linear" && g.kind !== "radial" && g.kind !== "conic") {
527
+ problems.push(`${where}: a paint object must be a gradient with kind "linear" / "radial" / "conic"`);
528
+ return;
529
+ }
530
+ if (!Array.isArray(g.stops) || g.stops.length === 0) {
531
+ problems.push(`${where}: gradient "${g.kind}" needs at least one color stop`);
532
+ return;
533
+ }
534
+ g.stops.forEach((s, i) => {
535
+ const st = s;
536
+ if (typeof st?.color !== "string") problems.push(`${where}: gradient stop ${i} needs a color string`);
537
+ if (typeof st?.offset !== "number" || st.offset < 0 || st.offset > 1) {
538
+ problems.push(`${where}: gradient stop ${i} "offset" must be a number in 0..1`);
539
+ }
540
+ });
541
+ };
542
+ const collect = (nodes) => {
543
+ for (const node of nodes) {
544
+ if (nodeById.has(node.id)) {
545
+ problems.push(`duplicate node id "${node.id}" \u2014 every node id must be unique`);
546
+ }
547
+ nodeById.set(node.id, node);
548
+ const props = node.props;
549
+ checkPaint(`node "${node.id}" fill`, props.fill);
550
+ checkPaint(`node "${node.id}" stroke`, props.stroke);
551
+ if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
552
+ if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
553
+ if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
554
+ if (typeof props.fit === "string" && !IMAGE_FITS.has(props.fit)) problems.push(`node "${node.id}": unknown fit "${props.fit}" \u2014 use ${[...IMAGE_FITS].join(", ")}`);
555
+ if (node.type === "video" && typeof node.props.start === "string") startAnchors.push({ id: node.id, at: node.props.start });
556
+ if (node.type === "group") {
557
+ const clip = node.props.clip;
558
+ if (clip) {
559
+ if (clip.kind !== "rect" && clip.kind !== "ellipse") {
560
+ problems.push(`group "${node.id}" clip: unknown kind "${clip.kind}" \u2014 use "rect" or "ellipse"`);
561
+ }
562
+ if (!(clip.width > 0) || !(clip.height > 0)) {
563
+ problems.push(`group "${node.id}" clip: width and height must be > 0`);
564
+ }
565
+ }
566
+ const matte = node.props.matte;
567
+ if (matte !== void 0) {
568
+ if (matte !== "alpha" && matte !== "luma") {
569
+ problems.push(`group "${node.id}" matte: unknown mode "${String(matte)}" \u2014 use "alpha" or "luma"`);
570
+ } else if (node.children.length < 2) {
571
+ problems.push(`group "${node.id}" matte: needs \u22652 children (first masks the rest)`);
572
+ }
573
+ }
574
+ collect(node.children);
575
+ }
576
+ }
577
+ };
578
+ collect(ir.nodes);
579
+ const checkProps = (where, nodeId, props) => {
580
+ if (nodeId === "camera" && !nodeById.has("camera")) {
581
+ for (const key2 of Object.keys(props)) {
582
+ if (!CAMERA_PROPS.includes(key2)) {
583
+ problems.push(`${where}: "${key2}" is not a camera prop \u2014 valid props: ${CAMERA_PROPS.join(", ")}`);
584
+ }
585
+ }
586
+ return;
587
+ }
588
+ const node = nodeById.get(nodeId);
589
+ if (!node) {
590
+ problems.push(
591
+ `${where} targets unknown node "${nodeId}" \u2014 known ids: ${[...nodeById.keys()].join(", ")}`
592
+ );
593
+ return;
594
+ }
595
+ const allowed = PROPS_BY_TYPE[node.type];
596
+ for (const key2 of Object.keys(props)) {
597
+ if (!allowed.includes(key2)) {
598
+ problems.push(
599
+ `${where}: "${key2}" is not a prop of ${node.type} "${nodeId}" \u2014 valid props: ${allowed.join(", ")}`
600
+ );
601
+ }
602
+ }
603
+ };
604
+ const states = ir.states ?? {};
605
+ for (const [stateName, overrides] of Object.entries(states)) {
606
+ for (const [nodeId, props] of Object.entries(overrides)) {
607
+ checkProps(`state "${stateName}"`, nodeId, props);
608
+ }
609
+ }
610
+ if (ir.initial !== void 0 && !(ir.initial in states)) {
611
+ problems.push(
612
+ `initial state "${ir.initial}" is not defined \u2014 defined states: ${Object.keys(states).join(", ") || "(none)"}`
613
+ );
614
+ }
615
+ const labels = /* @__PURE__ */ new Set();
616
+ const beatAnchors = [];
617
+ const checkEase = (path3, ease) => {
618
+ if (ease === void 0) return;
619
+ if (typeof ease === "string") {
620
+ if (!EASE_SET.has(ease)) {
621
+ problems.push(`${path3}: unknown ease "${ease}" \u2014 valid: ${EASE_NAMES.join(", ")} (note: there are no *Sine eases)`);
622
+ }
623
+ return;
624
+ }
625
+ if (typeof ease === "object" && ease !== null) {
626
+ const o = ease;
627
+ if ("spring" in o) return;
628
+ if ("cubicBezier" in o) {
629
+ if (!Array.isArray(o.cubicBezier) || o.cubicBezier.length !== 4) {
630
+ problems.push(`${path3}: ease cubicBezier must be [x1, y1, x2, y2]`);
631
+ }
632
+ return;
633
+ }
634
+ }
635
+ problems.push(`${path3}: invalid ease \u2014 use a name, { spring }, or { cubicBezier: [x1,y1,x2,y2] }`);
636
+ };
637
+ const checkTimeline = (tl, path3) => {
638
+ if ("label" in tl && tl.label !== void 0) {
639
+ if (labels.has(tl.label)) {
640
+ problems.push(
641
+ `${path3}: duplicate timeline label "${tl.label}" \u2014 labels are overlay addresses and must be unique`
642
+ );
643
+ }
644
+ labels.add(tl.label);
645
+ }
646
+ switch (tl.kind) {
647
+ case "seq":
648
+ case "par":
649
+ tl.children.forEach((c, i) => checkTimeline(c, `${path3}.${tl.kind}[${i}]`));
650
+ break;
651
+ case "stagger":
652
+ if (tl.interval < 0) problems.push(`${path3}: stagger interval must be >= 0`);
653
+ tl.children.forEach((c, i) => checkTimeline(c, `${path3}.stagger[${i}]`));
654
+ break;
655
+ case "to":
656
+ if (!(tl.state in states)) {
657
+ problems.push(
658
+ `${path3}: to("${tl.state}") references an undefined state \u2014 defined states: ${Object.keys(states).join(", ") || "(none)"}`
659
+ );
660
+ }
661
+ if (tl.duration !== void 0 && tl.duration <= 0) {
662
+ problems.push(`${path3}: to("${tl.state}") duration must be > 0`);
663
+ }
664
+ checkEase(path3, tl.ease);
665
+ for (const id of tl.filter ?? []) {
666
+ if (!nodeById.has(id)) problems.push(`${path3}: filter contains unknown node "${id}"`);
667
+ }
668
+ break;
669
+ case "tween":
670
+ checkProps(path3, tl.target, tl.props);
671
+ if (tl.duration !== void 0 && tl.duration <= 0) {
672
+ problems.push(`${path3}: tween duration must be > 0`);
673
+ }
674
+ checkEase(path3, tl.ease);
675
+ break;
676
+ case "motionPath": {
677
+ const node = nodeById.get(tl.target);
678
+ const isSceneCamera = tl.target === "camera" && !node;
679
+ if (!isSceneCamera) {
680
+ if (!node) {
681
+ problems.push(
682
+ `${path3}: motionPath targets unknown node "${tl.target}" \u2014 known ids: ${[...nodeById.keys()].join(", ")}`
683
+ );
684
+ } else if (node.type === "line") {
685
+ problems.push(`${path3}: motionPath cannot target a line (no x/y) \u2014 "${tl.target}"`);
686
+ }
687
+ }
688
+ if (tl.points.length < 1) problems.push(`${path3}: motionPath "${tl.target}" needs at least 1 point`);
689
+ if (tl.duration !== void 0 && tl.duration <= 0) {
690
+ problems.push(`${path3}: motionPath "${tl.target}" duration must be > 0`);
691
+ }
692
+ if (tl.curviness !== void 0 && tl.curviness < 0) {
693
+ problems.push(`${path3}: motionPath "${tl.target}" curviness must be >= 0`);
694
+ }
695
+ checkEase(path3, tl.ease);
696
+ break;
697
+ }
698
+ case "wait":
699
+ if (tl.duration < 0) problems.push(`${path3}: wait duration must be >= 0`);
700
+ break;
701
+ case "beat":
702
+ if (labels.has(tl.name)) {
703
+ problems.push(
704
+ `${path3}: duplicate timeline label "${tl.name}" (beat name) \u2014 labels are overlay addresses and must be unique`
705
+ );
706
+ }
707
+ labels.add(tl.name);
708
+ if (typeof tl.at === "string") beatAnchors.push({ name: tl.name, at: tl.at, path: path3 });
709
+ if (tl.duration !== void 0 && tl.duration <= 0) {
710
+ problems.push(`${path3}: beat "${tl.name}" duration must be > 0`);
711
+ }
712
+ if (tl.scale !== void 0 && tl.scale <= 0) {
713
+ problems.push(`${path3}: beat "${tl.name}" scale must be > 0`);
714
+ }
715
+ for (const id of tl.nodes ?? []) {
716
+ if (!nodeById.has(id)) {
717
+ problems.push(
718
+ `${path3}: beat "${tl.name}" owns unknown node "${id}" \u2014 known ids: ${[...nodeById.keys()].join(", ")}`
719
+ );
720
+ }
721
+ }
722
+ tl.children.forEach((c, i) => checkTimeline(c, `${path3}.beat(${tl.name})[${i}]`));
723
+ break;
724
+ }
725
+ };
726
+ if (ir.timeline) checkTimeline(ir.timeline, "timeline");
727
+ for (const a of beatAnchors) {
728
+ if (a.at === a.name) {
729
+ problems.push(`${a.path}: beat "${a.name}" at: "${a.at}" cannot anchor to itself`);
730
+ } else if (!labels.has(a.at)) {
731
+ problems.push(
732
+ `${a.path}: beat "${a.name}" at: "${a.at}" \u2014 unknown timeline label \u2014 known labels: ${[...labels].join(", ") || "(none)"}`
733
+ );
734
+ }
735
+ }
736
+ for (const a of startAnchors) {
737
+ if (!labels.has(a.at)) {
738
+ problems.push(
739
+ `video "${a.id}" start: "${a.at}" \u2014 unknown timeline label \u2014 known labels: ${[...labels].join(", ") || "(none)"}`
740
+ );
741
+ }
742
+ }
743
+ for (const [i, b] of (ir.behaviors ?? []).entries()) {
744
+ checkProps(`behaviors[${i}]`, b.target, { [b.prop]: 0 });
745
+ }
746
+ if (ir.duration !== void 0 && ir.duration <= 0) {
747
+ problems.push("scene duration must be > 0");
748
+ }
749
+ if (ir.camera) {
750
+ if (nodeById.has("camera")) {
751
+ problems.push(`camera: a node is already named "camera" \u2014 rename that node or drop the scene camera (the id "camera" can't be both)`);
752
+ }
753
+ for (const [key2, value] of Object.entries(ir.camera)) {
754
+ if (key2 === "zSort") {
755
+ if (typeof value !== "boolean") problems.push(`camera.zSort must be a boolean`);
756
+ } else if (!CAMERA_PROPS.includes(key2)) {
757
+ problems.push(`camera: "${key2}" is not a camera prop \u2014 valid props: ${CAMERA_PROPS.join(", ")}, zSort`);
758
+ } else if (typeof value !== "number") {
759
+ problems.push(`camera.${key2} must be a number`);
760
+ } else if (key2 === "perspective" && value <= 0) {
761
+ problems.push(`camera.perspective must be > 0 (focal distance in px) \u2014 drop it to disable perspective`);
762
+ } else if (key2 === "aperture" && value < 0) {
763
+ problems.push(`camera.aperture must be >= 0 (blur px per unit depth) \u2014 0 disables depth of field`);
764
+ }
765
+ }
766
+ }
767
+ for (const [i, cue] of (ir.audio?.cues ?? []).entries()) {
768
+ if (typeof cue.at === "string" && !labels.has(cue.at)) {
769
+ problems.push(
770
+ `audio.cues[${i}]: unknown timeline label "${cue.at}" \u2014 known labels: ${[...labels].join(", ") || "(none)"}`
771
+ );
772
+ }
773
+ if (typeof cue.at === "number" && cue.at < 0) {
774
+ problems.push(`audio.cues[${i}]: "at" must be >= 0`);
775
+ }
776
+ if (cue.sfx === void 0 === (cue.file === void 0)) {
777
+ problems.push(`audio.cues[${i}]: exactly one of "sfx" or "file" is required`);
778
+ }
779
+ if (cue.sfx !== void 0 && !SFX_NAMES.includes(cue.sfx)) {
780
+ problems.push(`audio.cues[${i}]: unknown sfx "${cue.sfx}" \u2014 valid: ${SFX_NAMES.join(", ")}`);
781
+ }
782
+ if (cue.gain !== void 0 && cue.gain < 0) {
783
+ problems.push(`audio.cues[${i}]: gain must be >= 0`);
784
+ }
785
+ if (cue.fadeIn !== void 0 && cue.fadeIn < 0) {
786
+ problems.push(`audio.cues[${i}]: fadeIn must be >= 0`);
787
+ }
788
+ if (cue.fadeOut !== void 0 && cue.fadeOut < 0) {
789
+ problems.push(`audio.cues[${i}]: fadeOut must be >= 0`);
790
+ }
791
+ if (cue.pan !== void 0 && (cue.pan < -1 || cue.pan > 1)) {
792
+ problems.push(`audio.cues[${i}]: pan must be in [-1, 1] (-1 left \u2026 +1 right)`);
793
+ }
794
+ }
795
+ const duck = ir.audio?.bgm?.duck;
796
+ if (typeof duck === "object" && duck !== null && duck.depth !== void 0 && (duck.depth < 0 || duck.depth > 1)) {
797
+ problems.push("audio.bgm.duck.depth must be in [0, 1]");
798
+ }
799
+ if (ir.audio?.bgm?.file !== void 0 && ir.audio.bgm.synth !== void 0) {
800
+ problems.push('audio.bgm: use either "file" or "synth", not both');
801
+ }
802
+ const bgmSynth = ir.audio?.bgm?.synth;
803
+ if (bgmSynth !== void 0 && !BGM_SYNTHS.includes(bgmSynth)) {
804
+ problems.push(`audio.bgm.synth: unknown synth "${bgmSynth}" \u2014 valid: ${BGM_SYNTHS.join(", ")}`);
805
+ }
806
+ if (problems.length > 0) throw new SceneValidationError(problems);
807
+ }
808
+
809
+ // ../core/src/compose.ts
810
+ var TIMELINE_PATCHABLE = {
811
+ to: ["duration", "ease", "stagger"],
812
+ tween: ["duration", "ease"],
813
+ wait: ["duration"],
814
+ motionPath: ["points", "duration", "ease", "curviness", "autoRotate"],
815
+ beat: ["at", "gap", "scale", "duration", "order"]
816
+ };
817
+
818
+ // ../core/src/manifest.ts
819
+ function animatedPropsOf(compiled, id) {
820
+ const props = /* @__PURE__ */ new Set();
821
+ const prefix = `${id}.`;
822
+ for (const key2 of compiled.segments.keys()) {
823
+ if (key2.startsWith(prefix)) props.add(key2.slice(prefix.length));
824
+ }
825
+ const drivers = compiled.motionPaths.get(id);
826
+ if (drivers && drivers.length > 0) {
827
+ props.add("x");
828
+ props.add("y");
829
+ if (drivers.some((d) => d.autoRotate)) props.add("rotation");
830
+ }
831
+ return [...props].sort();
832
+ }
833
+ function indexTimeline(tl) {
834
+ const byLabel = /* @__PURE__ */ new Map();
835
+ const walk = (t) => {
836
+ if ("label" in t && t.label !== void 0) byLabel.set(t.label, t);
837
+ if (t.kind === "beat") byLabel.set(t.name, t);
838
+ if ("children" in t) t.children.forEach(walk);
839
+ };
840
+ if (tl) walk(tl);
841
+ return byLabel;
842
+ }
843
+ function walkMotion(tl, visit) {
844
+ const walk = (t) => {
845
+ if (t.kind === "tween" || t.kind === "motionPath") {
846
+ visit(t.kind, "label" in t && t.label !== void 0, t.target);
847
+ } else if (t.kind === "to") {
848
+ visit("to", t.label !== void 0, t.state);
849
+ }
850
+ if ("children" in t) t.children.forEach(walk);
851
+ };
852
+ if (tl) walk(tl);
853
+ }
854
+ function sceneManifest(compiled) {
855
+ const ir = compiled.ir;
856
+ const statesByNode = /* @__PURE__ */ new Map();
857
+ for (const [name, override] of Object.entries(ir.states ?? {})) {
858
+ for (const id of Object.keys(override)) {
859
+ const list = statesByNode.get(id) ?? [];
860
+ list.push(name);
861
+ statesByNode.set(id, list);
862
+ }
863
+ }
864
+ const nodes = [];
865
+ const walkNodes = (list, parent) => {
866
+ for (const node of list) {
867
+ nodes.push({
868
+ id: node.id,
869
+ type: node.type,
870
+ ...parent !== void 0 ? { parent } : {},
871
+ address: `nodes.${node.id}`,
872
+ editableProps: [...PROPS_BY_TYPE[node.type]],
873
+ animatedProps: animatedPropsOf(compiled, node.id),
874
+ inStates: statesByNode.get(node.id) ?? []
875
+ });
876
+ if (node.type === "group") walkNodes(node.children, node.id);
877
+ }
878
+ };
879
+ walkNodes(ir.nodes, void 0);
880
+ const states = Object.entries(ir.states ?? {}).map(([name, override]) => ({
881
+ name,
882
+ address: `states.${name}`,
883
+ touches: Object.entries(override).map(([id, props]) => ({ id, props: Object.keys(props) }))
884
+ }));
885
+ const byLabel = indexTimeline(ir.timeline);
886
+ const timeline = [];
887
+ const beats = [];
888
+ for (const [label, span] of compiled.labelTimes) {
889
+ const step = byLabel.get(label);
890
+ const kind = step?.kind ?? "seq";
891
+ if (compiled.beatTimes.has(label) && step?.kind === "beat") {
892
+ beats.push({ name: label, t0: span.t0, t1: span.t1, ownsNodes: step.nodes ?? [], address: `timeline.${label}` });
893
+ } else {
894
+ timeline.push({ label, kind, t0: span.t0, t1: span.t1, patchable: TIMELINE_PATCHABLE[kind] ?? [], address: `timeline.${label}` });
895
+ }
896
+ }
897
+ timeline.sort((a, b) => a.t0 - b.t0 || a.label.localeCompare(b.label));
898
+ beats.sort((a, b) => a.t0 - b.t0 || a.name.localeCompare(b.name));
899
+ const behaviors = (ir.behaviors ?? []).map((b) => ({
900
+ target: b.target,
901
+ prop: b.prop,
902
+ kind: b.behavior.name,
903
+ address: `behaviors.${b.target}.${b.prop}`
904
+ }));
905
+ let motionTotal = 0;
906
+ let motionLabeled = 0;
907
+ walkMotion(ir.timeline, (_kind, labeled) => {
908
+ motionTotal++;
909
+ if (labeled) motionLabeled++;
910
+ });
911
+ return {
912
+ scene: {
913
+ id: ir.id,
914
+ duration: compiled.duration,
915
+ fps: ir.fps ?? 30,
916
+ size: ir.size,
917
+ ...ir.background !== void 0 ? { background: ir.background } : {}
918
+ },
919
+ nodes,
920
+ states,
921
+ timeline,
922
+ beats,
923
+ behaviors,
924
+ summary: {
925
+ nodeCount: nodes.length,
926
+ labeledSteps: timeline.length + beats.length,
927
+ unlabeledMotionSteps: motionTotal - motionLabeled,
928
+ motionAddressableRatio: motionTotal === 0 ? 1 : motionLabeled / motionTotal
929
+ }
930
+ };
931
+ }
932
+ function lintScene(compiled) {
933
+ const findings = [];
934
+ walkMotion(compiled.ir.timeline, (kind, labeled, target) => {
935
+ if (labeled) return;
936
+ const what = kind === "to" ? `to("${target}")` : `${kind} on "${target}"`;
937
+ findings.push({
938
+ rule: "unlabeled-motion",
939
+ severity: "warn",
940
+ message: `${what} has no label \u2014 its timing can't be retimed or redirected by an overlay, and a base regeneration can silently drop it. Add a stable label.`
941
+ });
942
+ });
943
+ return findings;
944
+ }
945
+
946
+ // ../core/src/evaluate.ts
947
+ var DEG = Math.PI / 180;
948
+
949
+ // ../core/src/presets.ts
950
+ var SET = 1 / 120;
951
+
952
+ // ../render-cli/src/loadScene.ts
953
+ import { build } from "esbuild";
954
+ import { readFile } from "node:fs/promises";
955
+ import { dirname, resolve } from "node:path";
956
+ import { fileURLToPath } from "node:url";
957
+ var HERE = dirname(fileURLToPath(import.meta.url));
958
+ var CORE_ENTRY = true ? resolve(HERE, "index.js") : resolve(HERE, "..", "..", "core", "src", "index.ts");
959
+ var SceneLoadError = class extends Error {
960
+ kind;
961
+ constructor(kind, message, options) {
962
+ super(message, options);
963
+ this.name = "SceneLoadError";
964
+ this.kind = kind;
965
+ }
966
+ };
967
+ var clean = (err) => (err instanceof Error ? err.message : String(err)).replace(
968
+ /data:text\/javascript;base64,[A-Za-z0-9+/=]+/g,
969
+ "<scene bundle>"
970
+ );
971
+ var ALIAS = { "@reframe/core": CORE_ENTRY, "reframe-video": CORE_ENTRY };
972
+ async function bundle(input) {
973
+ const common = {
974
+ bundle: true,
975
+ format: "esm",
976
+ platform: "neutral",
977
+ write: false,
978
+ logLevel: "silent",
979
+ sourcemap: "inline",
980
+ alias: ALIAS
981
+ };
982
+ try {
983
+ const out = await build(
984
+ "path" in input ? { ...common, entryPoints: [input.path] } : { ...common, stdin: { contents: input.code, resolveDir: input.resolveDir, loader: "ts", sourcefile: "scene.ts" } }
985
+ );
986
+ return out.outputFiles[0].text;
987
+ } catch (err) {
988
+ throw new SceneLoadError("bundle", clean(err), { cause: err });
989
+ }
990
+ }
991
+ async function importDefault(code, label) {
992
+ let mod;
993
+ try {
994
+ mod = await import(`data:text/javascript;base64,${Buffer.from(code).toString("base64")}`);
995
+ } catch (err) {
996
+ const kind = err instanceof Error && err.name === "SceneValidationError" ? "validation" : "eval";
997
+ throw new SceneLoadError(kind, clean(err), { cause: err });
998
+ }
999
+ if (mod.default === void 0) throw new SceneLoadError("eval", `${label} must default-export a scene or composition`);
1000
+ return mod.default;
1001
+ }
1002
+ async function loadDefault(path3) {
1003
+ if (path3.endsWith(".json")) {
1004
+ try {
1005
+ return JSON.parse(await readFile(path3, "utf8"));
1006
+ } catch (err) {
1007
+ throw new SceneLoadError("eval", `failed to read ${path3}: ${clean(err)}`, { cause: err });
1008
+ }
1009
+ }
1010
+ return importDefault(await bundle({ path: path3 }), path3);
1011
+ }
1012
+ function isComposition(def) {
1013
+ return typeof def === "object" && def !== null && Array.isArray(def.scenes);
1014
+ }
1015
+ function asScene(def, label) {
1016
+ if (isComposition(def)) {
1017
+ throw new SceneLoadError("validation", `${label} is a composition \u2014 render it directly, not as a single scene`);
1018
+ }
1019
+ try {
1020
+ validateScene(def);
1021
+ } catch (err) {
1022
+ throw new SceneLoadError("validation", clean(err), { cause: err });
1023
+ }
1024
+ return def;
1025
+ }
1026
+ async function loadScene(path3) {
1027
+ return asScene(await loadDefault(path3), path3);
1028
+ }
1029
+
1030
+ // ../render-cli/src/lint.ts
1031
+ var args = process.argv.slice(2);
1032
+ var json = args.includes("--json");
1033
+ var strict = args.includes("--strict");
1034
+ var path2 = args.find((a) => !a.startsWith("-"));
1035
+ if (!path2) {
1036
+ console.error("usage: reframe lint <scene.ts|.json> [--json] [--strict]");
1037
+ process.exit(1);
1038
+ }
1039
+ async function main() {
1040
+ const compiled = compileScene(await loadScene(path2));
1041
+ const findings = lintScene(compiled);
1042
+ const s = sceneManifest(compiled).summary;
1043
+ if (json) {
1044
+ console.log(JSON.stringify({ findings, summary: s }, null, 2));
1045
+ } else {
1046
+ console.log(
1047
+ `# ${s.nodeCount} nodes \xB7 ${s.labeledSteps} labeled steps \xB7 motion addressable ${(s.motionAddressableRatio * 100).toFixed(0)}% (${s.unlabeledMotionSteps} unlabeled)`
1048
+ );
1049
+ if (findings.length === 0) {
1050
+ console.log("\u2713 no addressability findings");
1051
+ } else {
1052
+ for (const f of findings) console.log(` ${f.severity === "error" ? "\u2717" : "!"} [${f.rule}] ${f.message}`);
1053
+ }
1054
+ }
1055
+ if (strict && findings.length > 0) process.exit(1);
1056
+ }
1057
+ main().catch((err) => {
1058
+ console.error(`error: ${err instanceof Error ? err.message : String(err)}`);
1059
+ process.exit(1);
1060
+ });