cinematic-renderer2d 0.1.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.
@@ -0,0 +1,1243 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync } from 'fs';
3
+ import { resolve } from 'path';
4
+ import { z } from 'zod';
5
+
6
+ // src/cli/index.ts
7
+
8
+ // src/animation/AnimationCompiler.ts
9
+ var AnimationCompiler = class {
10
+ /**
11
+ * Compiles an animation track specification into an optimized runtime track
12
+ *
13
+ * @param track - Animation track specification to compile
14
+ * @returns Compiled animation track with precompiled interpolation function
15
+ */
16
+ static compileTrack(track) {
17
+ const easingFunction = this.compileEasing(track.easing || "ease");
18
+ const interpolationFunction = this.compileInterpolation(track.from, track.to, easingFunction);
19
+ return {
20
+ property: track.property,
21
+ startMs: track.startMs,
22
+ endMs: track.endMs,
23
+ interpolate: interpolationFunction,
24
+ loop: track.loop || false,
25
+ yoyo: track.yoyo || false,
26
+ easingType: track.easing || "ease",
27
+ currentLoop: 0,
28
+ isReverse: false
29
+ };
30
+ }
31
+ /**
32
+ * Compiles an easing function from string specification to optimized function
33
+ *
34
+ * @param easing - Easing type or cubic-bezier specification
35
+ * @returns Optimized easing function that maps [0,1] to [0,1]
36
+ */
37
+ static compileEasing(easing) {
38
+ if (typeof easing === "string" && easing.startsWith("cubic-bezier(")) {
39
+ return this.compileCubicBezier(easing);
40
+ }
41
+ switch (easing) {
42
+ case "linear":
43
+ return (t) => t;
44
+ case "ease":
45
+ return this.cubicBezier(0.25, 0.1, 0.25, 1);
46
+ case "ease-in":
47
+ return this.cubicBezier(0.42, 0, 1, 1);
48
+ case "ease-out":
49
+ return this.cubicBezier(0, 0, 0.58, 1);
50
+ case "ease-in-out":
51
+ return this.cubicBezier(0.42, 0, 0.58, 1);
52
+ // Sine easing functions
53
+ case "ease-in-sine":
54
+ return (t) => 1 - Math.cos(t * Math.PI / 2);
55
+ case "ease-out-sine":
56
+ return (t) => Math.sin(t * Math.PI / 2);
57
+ case "ease-in-out-sine":
58
+ return (t) => -(Math.cos(Math.PI * t) - 1) / 2;
59
+ // Quadratic easing functions
60
+ case "ease-in-quad":
61
+ return (t) => t * t;
62
+ case "ease-out-quad":
63
+ return (t) => 1 - (1 - t) * (1 - t);
64
+ case "ease-in-out-quad":
65
+ return (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
66
+ // Cubic easing functions
67
+ case "ease-in-cubic":
68
+ return (t) => t * t * t;
69
+ case "ease-out-cubic":
70
+ return (t) => 1 - Math.pow(1 - t, 3);
71
+ case "ease-in-out-cubic":
72
+ return (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
73
+ // Quartic easing functions
74
+ case "ease-in-quart":
75
+ return (t) => t * t * t * t;
76
+ case "ease-out-quart":
77
+ return (t) => 1 - Math.pow(1 - t, 4);
78
+ case "ease-in-out-quart":
79
+ return (t) => t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
80
+ // Quintic easing functions
81
+ case "ease-in-quint":
82
+ return (t) => t * t * t * t * t;
83
+ case "ease-out-quint":
84
+ return (t) => 1 - Math.pow(1 - t, 5);
85
+ case "ease-in-out-quint":
86
+ return (t) => t < 0.5 ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2;
87
+ // Exponential easing functions
88
+ case "ease-in-expo":
89
+ return (t) => t === 0 ? 0 : Math.pow(2, 10 * (t - 1));
90
+ case "ease-out-expo":
91
+ return (t) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
92
+ case "ease-in-out-expo":
93
+ return (t) => {
94
+ if (t === 0) return 0;
95
+ if (t === 1) return 1;
96
+ return t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2;
97
+ };
98
+ // Circular easing functions
99
+ case "ease-in-circ":
100
+ return (t) => 1 - Math.sqrt(1 - Math.pow(t, 2));
101
+ case "ease-out-circ":
102
+ return (t) => Math.sqrt(1 - Math.pow(t - 1, 2));
103
+ case "ease-in-out-circ":
104
+ return (t) => t < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2 : (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2;
105
+ // Back easing functions
106
+ case "ease-in-back":
107
+ return this.backEasing(1.70158, "in");
108
+ case "ease-out-back":
109
+ return this.backEasing(1.70158, "out");
110
+ case "ease-in-out-back":
111
+ return this.backEasing(1.70158, "in-out");
112
+ // Elastic easing functions
113
+ case "ease-in-elastic":
114
+ return this.elasticEasing(2 * Math.PI / 3, "in");
115
+ case "ease-out-elastic":
116
+ return this.elasticEasing(2 * Math.PI / 3, "out");
117
+ case "ease-in-out-elastic":
118
+ return this.elasticEasing(2 * Math.PI / 4.5, "in-out");
119
+ // Bounce easing functions
120
+ case "ease-in-bounce":
121
+ return (t) => 1 - this.bounceOut(1 - t);
122
+ case "ease-out-bounce":
123
+ return this.bounceOut;
124
+ case "ease-in-out-bounce":
125
+ return (t) => t < 0.5 ? (1 - this.bounceOut(1 - 2 * t)) / 2 : (1 + this.bounceOut(2 * t - 1)) / 2;
126
+ default:
127
+ return this.cubicBezier(0.25, 0.1, 0.25, 1);
128
+ }
129
+ }
130
+ /**
131
+ * Compiles an interpolation function for animating between two values
132
+ *
133
+ * @param from - Starting value
134
+ * @param to - Ending value
135
+ * @param easingFunction - Easing function to apply
136
+ * @returns Optimized interpolation function
137
+ */
138
+ static compileInterpolation(from, to, easingFunction) {
139
+ const fromType = this.getValueType(from);
140
+ const toType = this.getValueType(to);
141
+ if (fromType !== toType) {
142
+ return (progress) => {
143
+ const easedProgress = easingFunction(Math.max(0, Math.min(1, progress)));
144
+ return easedProgress < 0.5 ? from : to;
145
+ };
146
+ }
147
+ switch (fromType) {
148
+ case "number":
149
+ return this.compileNumberInterpolation(from, to, easingFunction);
150
+ case "string":
151
+ return this.compileStringInterpolation(from, to, easingFunction);
152
+ case "boolean":
153
+ return this.compileBooleanInterpolation(from, to, easingFunction);
154
+ case "object":
155
+ return this.compileObjectInterpolation(
156
+ from,
157
+ to,
158
+ easingFunction
159
+ );
160
+ default:
161
+ return (progress) => {
162
+ const easedProgress = easingFunction(Math.max(0, Math.min(1, progress)));
163
+ return easedProgress < 0.5 ? from : to;
164
+ };
165
+ }
166
+ }
167
+ /**
168
+ * Creates a cubic-bezier easing function with the given control points
169
+ */
170
+ static cubicBezier(x1, y1, x2, y2) {
171
+ return (t) => {
172
+ const cx = 3 * x1;
173
+ const bx = 3 * (x2 - x1) - cx;
174
+ const ax = 1 - cx - bx;
175
+ const cy = 3 * y1;
176
+ const by = 3 * (y2 - y1) - cy;
177
+ const ay = 1 - cy - by;
178
+ let x = t;
179
+ for (let i = 0; i < 8; i++) {
180
+ const currentX = ((ax * x + bx) * x + cx) * x;
181
+ const currentSlope = (3 * ax * x + 2 * bx) * x + cx;
182
+ if (Math.abs(currentSlope) < 1e-6) break;
183
+ x = x - (currentX - t) / currentSlope;
184
+ }
185
+ return ((ay * x + by) * x + cy) * x;
186
+ };
187
+ }
188
+ /**
189
+ * Parses and compiles a cubic-bezier string specification
190
+ */
191
+ static compileCubicBezier(bezierString) {
192
+ const match = bezierString.match(/cubic-bezier\(([^)]+)\)/);
193
+ if (!match || !match[1]) {
194
+ return this.cubicBezier(0.25, 0.1, 0.25, 1);
195
+ }
196
+ const values = match[1].split(",").map((v) => parseFloat(v.trim()));
197
+ if (values.length !== 4 || values.some((v) => isNaN(v))) {
198
+ return this.cubicBezier(0.25, 0.1, 0.25, 1);
199
+ }
200
+ return this.cubicBezier(values[0], values[1], values[2], values[3]);
201
+ }
202
+ /**
203
+ * Creates a back easing function with overshoot
204
+ */
205
+ static backEasing(c, type) {
206
+ const c1 = c;
207
+ const c2 = c1 * 1.525;
208
+ const c3 = c1 + 1;
209
+ switch (type) {
210
+ case "in":
211
+ return (t) => c3 * t * t * t - c1 * t * t;
212
+ case "out":
213
+ return (t) => 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
214
+ case "in-out":
215
+ return (t) => t < 0.5 ? Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2) / 2 : (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
216
+ default:
217
+ return (t) => t;
218
+ }
219
+ }
220
+ /**
221
+ * Creates an elastic easing function with oscillation
222
+ */
223
+ static elasticEasing(c, type) {
224
+ switch (type) {
225
+ case "in":
226
+ return (t) => {
227
+ if (t === 0) return 0;
228
+ if (t === 1) return 1;
229
+ return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c);
230
+ };
231
+ case "out":
232
+ return (t) => {
233
+ if (t === 0) return 0;
234
+ if (t === 1) return 1;
235
+ return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c) + 1;
236
+ };
237
+ case "in-out":
238
+ return (t) => {
239
+ if (t === 0) return 0;
240
+ if (t === 1) return 1;
241
+ return t < 0.5 ? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c)) / 2 : Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c) / 2 + 1;
242
+ };
243
+ default:
244
+ return (t) => t;
245
+ }
246
+ }
247
+ /**
248
+ * Bounce easing out function
249
+ */
250
+ static bounceOut(t) {
251
+ const n1 = 7.5625;
252
+ const d1 = 2.75;
253
+ if (t < 1 / d1) {
254
+ return n1 * t * t;
255
+ } else if (t < 2 / d1) {
256
+ return n1 * (t -= 1.5 / d1) * t + 0.75;
257
+ } else if (t < 2.5 / d1) {
258
+ return n1 * (t -= 2.25 / d1) * t + 0.9375;
259
+ } else {
260
+ return n1 * (t -= 2.625 / d1) * t + 0.984375;
261
+ }
262
+ }
263
+ /**
264
+ * Determines the type of an animation value
265
+ */
266
+ static getValueType(value) {
267
+ if (typeof value === "number") return "number";
268
+ if (typeof value === "string") return "string";
269
+ if (typeof value === "boolean") return "boolean";
270
+ if (typeof value === "object" && value !== null) return "object";
271
+ return "object";
272
+ }
273
+ /**
274
+ * Compiles number interpolation with optimized performance
275
+ */
276
+ static compileNumberInterpolation(from, to, easingFunction) {
277
+ const delta = to - from;
278
+ return (progress) => {
279
+ const easedProgress = easingFunction(Math.max(0, Math.min(1, progress)));
280
+ return from + delta * easedProgress;
281
+ };
282
+ }
283
+ /**
284
+ * Compiles string interpolation with CSS value support
285
+ */
286
+ static compileStringInterpolation(from, to, easingFunction) {
287
+ const fromMatch = from.match(/^([+-]?\d*\.?\d+)(.*)$/);
288
+ const toMatch = to.match(/^([+-]?\d*\.?\d+)(.*)$/);
289
+ if (fromMatch && toMatch && fromMatch[2] === toMatch[2]) {
290
+ const fromNum = parseFloat(fromMatch[1]);
291
+ const toNum = parseFloat(toMatch[1]);
292
+ const unit = fromMatch[2];
293
+ const delta = toNum - fromNum;
294
+ return (progress) => {
295
+ const easedProgress = easingFunction(Math.max(0, Math.min(1, progress)));
296
+ const interpolatedValue = fromNum + delta * easedProgress;
297
+ const roundedValue = Math.round(interpolatedValue * 1e6) / 1e6;
298
+ return `${roundedValue}${unit}`;
299
+ };
300
+ }
301
+ return (progress) => {
302
+ const easedProgress = easingFunction(Math.max(0, Math.min(1, progress)));
303
+ return easedProgress < 0.5 ? from : to;
304
+ };
305
+ }
306
+ /**
307
+ * Compiles boolean interpolation (discrete transition)
308
+ */
309
+ static compileBooleanInterpolation(from, to, easingFunction) {
310
+ return (progress) => {
311
+ const easedProgress = easingFunction(Math.max(0, Math.min(1, progress)));
312
+ return easedProgress < 0.5 ? from : to;
313
+ };
314
+ }
315
+ /**
316
+ * Compiles object interpolation for complex values
317
+ */
318
+ static compileObjectInterpolation(from, to, easingFunction) {
319
+ const propertyInterpolators = /* @__PURE__ */ new Map();
320
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(from), ...Object.keys(to)]);
321
+ for (const key of allKeys) {
322
+ const fromValue = from[key];
323
+ const toValue = to[key];
324
+ if (fromValue !== void 0 && toValue !== void 0) {
325
+ propertyInterpolators.set(key, this.compileInterpolation(fromValue, toValue, easingFunction));
326
+ } else if (fromValue !== void 0) {
327
+ propertyInterpolators.set(key, () => fromValue);
328
+ } else {
329
+ propertyInterpolators.set(key, (progress) => {
330
+ const easedProgress = easingFunction(Math.max(0, Math.min(1, progress)));
331
+ return easedProgress < 0.5 ? void 0 : toValue;
332
+ });
333
+ }
334
+ }
335
+ return (progress) => {
336
+ const result = {};
337
+ for (const [key, interpolator] of propertyInterpolators) {
338
+ const value = interpolator(progress);
339
+ if (value !== void 0) {
340
+ result[key] = value;
341
+ }
342
+ }
343
+ return result;
344
+ };
345
+ }
346
+ };
347
+
348
+ // src/parsing/SpecParser.ts
349
+ var SUPPORTED_SCHEMA_VERSIONS = ["1.0.0", "1.1.0"];
350
+ var QualityLevelSchema = z.enum(["low", "medium", "high", "ultra", "auto"]);
351
+ var LayerTypeSchema = z.enum([
352
+ "gradient",
353
+ "image",
354
+ "textBlock",
355
+ "vignette",
356
+ "glowOrb",
357
+ "noiseOverlay",
358
+ "particles",
359
+ "starfield",
360
+ "dust",
361
+ "nebulaNoise",
362
+ "webgl-custom"
363
+ ]);
364
+ var EasingTypeSchema = z.enum([
365
+ "linear",
366
+ "ease",
367
+ "ease-in",
368
+ "ease-out",
369
+ "ease-in-out",
370
+ "ease-in-sine",
371
+ "ease-out-sine",
372
+ "ease-in-out-sine",
373
+ "ease-in-quad",
374
+ "ease-out-quad",
375
+ "ease-in-out-quad",
376
+ "ease-in-cubic",
377
+ "ease-out-cubic",
378
+ "ease-in-out-cubic",
379
+ "ease-in-quart",
380
+ "ease-out-quart",
381
+ "ease-in-out-quart",
382
+ "ease-in-quint",
383
+ "ease-out-quint",
384
+ "ease-in-out-quint",
385
+ "ease-in-expo",
386
+ "ease-out-expo",
387
+ "ease-in-out-expo",
388
+ "ease-in-circ",
389
+ "ease-out-circ",
390
+ "ease-in-out-circ",
391
+ "ease-in-back",
392
+ "ease-out-back",
393
+ "ease-in-out-back",
394
+ "ease-in-elastic",
395
+ "ease-out-elastic",
396
+ "ease-in-out-elastic",
397
+ "ease-in-bounce",
398
+ "ease-out-bounce",
399
+ "ease-in-out-bounce"
400
+ ]).or(z.string().regex(/^cubic-bezier\(\d*\.?\d+,\d*\.?\d+,\d*\.?\d+,\d*\.?\d+\)$/));
401
+ var AudioTrackTypeSchema = z.enum(["voiceover", "ambience", "transition", "music", "sfx"]);
402
+ var TransitionTypeSchema = z.enum(["fade", "slide", "zoom", "wipe", "dissolve", "blur"]);
403
+ var AssetTypeSchema = z.enum(["image", "video", "audio", "font", "json", "binary"]);
404
+ var TransformConfigSchema = z.object({
405
+ x: z.union([z.number(), z.string()]).optional(),
406
+ y: z.union([z.number(), z.string()]).optional(),
407
+ scale: z.number().optional(),
408
+ rotation: z.number().optional(),
409
+ origin: z.string().optional()
410
+ }).optional();
411
+ var LayerConfigSchema = z.object({
412
+ opacity: z.number().min(0).max(1).optional(),
413
+ visible: z.boolean().optional(),
414
+ transform: TransformConfigSchema
415
+ }).catchall(z.any());
416
+ var AnimationValueSchema = z.union([
417
+ z.number(),
418
+ z.string(),
419
+ z.boolean(),
420
+ z.record(z.union([z.number(), z.string(), z.boolean()]))
421
+ ]);
422
+ var AnimationTrackSpecSchema = z.object({
423
+ property: z.string().min(1),
424
+ from: AnimationValueSchema,
425
+ to: AnimationValueSchema,
426
+ startMs: z.number().min(0),
427
+ endMs: z.number().min(0),
428
+ easing: EasingTypeSchema.optional().default("ease"),
429
+ loop: z.boolean().optional().default(false),
430
+ yoyo: z.boolean().optional().default(false)
431
+ }).refine((data) => data.endMs > data.startMs, {
432
+ message: "endMs must be greater than startMs",
433
+ path: ["endMs"]
434
+ });
435
+ var AudioTrackSpecSchema = z.object({
436
+ id: z.string().min(1),
437
+ type: AudioTrackTypeSchema,
438
+ src: z.string().min(1),
439
+ startMs: z.number().min(0),
440
+ endMs: z.number().min(0).optional(),
441
+ volume: z.number().min(0).max(1).optional().default(1),
442
+ fadeIn: z.number().min(0).optional().default(0),
443
+ fadeOut: z.number().min(0).optional().default(0),
444
+ loop: z.boolean().optional().default(false)
445
+ });
446
+ var TransitionConfigSchema = z.object({
447
+ direction: z.enum(["up", "down", "left", "right", "in", "out"]).optional(),
448
+ blurAmount: z.number().min(0).optional()
449
+ }).catchall(z.any()).optional();
450
+ var TransitionSpecSchema = z.object({
451
+ type: TransitionTypeSchema,
452
+ duration: z.number().min(0),
453
+ easing: EasingTypeSchema.optional().default("ease"),
454
+ config: TransitionConfigSchema
455
+ });
456
+ var AssetMetadataSchema = z.object({
457
+ size: z.number().min(0).optional(),
458
+ mimeType: z.string().optional(),
459
+ cacheDuration: z.number().min(0).optional(),
460
+ dimensions: z.object({
461
+ width: z.number().min(1),
462
+ height: z.number().min(1)
463
+ }).optional(),
464
+ duration: z.number().min(0).optional()
465
+ }).optional();
466
+ var AssetDefinitionSchema = z.object({
467
+ id: z.string().min(1),
468
+ type: AssetTypeSchema,
469
+ src: z.string().min(1),
470
+ preload: z.boolean().optional().default(true),
471
+ fallback: z.string().optional(),
472
+ metadata: AssetMetadataSchema
473
+ });
474
+ var LayerSpecSchema = z.object({
475
+ id: z.string().min(1),
476
+ type: LayerTypeSchema,
477
+ zIndex: z.number(),
478
+ config: LayerConfigSchema,
479
+ animations: z.array(AnimationTrackSpecSchema).optional().default([])
480
+ });
481
+ var CinematicSceneSchema = z.object({
482
+ id: z.string().min(1),
483
+ name: z.string().min(1),
484
+ duration: z.number().min(0),
485
+ layers: z.array(LayerSpecSchema).min(1),
486
+ audio: z.array(AudioTrackSpecSchema).optional().default([])
487
+ });
488
+ var CinematicEventSchema = z.object({
489
+ id: z.string().min(1),
490
+ name: z.string().min(1),
491
+ scenes: z.array(z.string().min(1)).min(1),
492
+ transitions: z.array(TransitionSpecSchema).optional().default([])
493
+ });
494
+ var EngineConfigSchema = z.object({
495
+ targetFps: z.number().min(1).max(240).optional().default(60),
496
+ quality: QualityLevelSchema.optional().default("auto"),
497
+ debug: z.boolean().optional().default(false),
498
+ autoplay: z.boolean().optional().default(false)
499
+ });
500
+ var CinematicSpecSchema = z.object({
501
+ schemaVersion: z.string().min(1),
502
+ engine: EngineConfigSchema,
503
+ events: z.array(CinematicEventSchema).min(1),
504
+ scenes: z.array(CinematicSceneSchema).min(1),
505
+ assets: z.array(AssetDefinitionSchema).optional().default([])
506
+ }).refine((data) => {
507
+ const sceneIds = new Set(data.scenes.map((scene) => scene.id));
508
+ const referencedSceneIds = new Set(data.events.flatMap((event) => event.scenes));
509
+ for (const sceneId of referencedSceneIds) {
510
+ if (!sceneIds.has(sceneId)) {
511
+ return false;
512
+ }
513
+ }
514
+ return true;
515
+ }, {
516
+ message: "All scene IDs referenced in events must exist in the scenes array",
517
+ path: ["events"]
518
+ }).refine((data) => {
519
+ var _a;
520
+ const eventIds = data.events.map((e) => e.id);
521
+ const sceneIds = data.scenes.map((s) => s.id);
522
+ const assetIds = ((_a = data.assets) == null ? void 0 : _a.map((a) => a.id)) || [];
523
+ return new Set(eventIds).size === eventIds.length && new Set(sceneIds).size === sceneIds.length && new Set(assetIds).size === assetIds.length;
524
+ }, {
525
+ message: "All IDs must be unique within their respective collections",
526
+ path: ["events", "scenes", "assets"]
527
+ });
528
+ var SpecParser = class {
529
+ /**
530
+ * Validates and parses a raw JSON specification into a validated CinematicSpec
531
+ *
532
+ * @param spec - Raw JSON specification to validate
533
+ * @returns Validated CinematicSpec with applied defaults
534
+ * @throws ZodError with descriptive validation messages
535
+ */
536
+ static validate(spec) {
537
+ try {
538
+ if (typeof spec !== "object" || spec === null) {
539
+ throw new Error("Specification must be a valid object");
540
+ }
541
+ const rawSpec = spec;
542
+ const schemaVersion = rawSpec["schemaVersion"];
543
+ if (!schemaVersion || typeof schemaVersion !== "string") {
544
+ throw new Error("Missing or invalid schemaVersion. Current supported versions: " + SUPPORTED_SCHEMA_VERSIONS.join(", "));
545
+ }
546
+ if (!SUPPORTED_SCHEMA_VERSIONS.includes(schemaVersion)) {
547
+ throw new Error(`Unsupported schema version: ${schemaVersion}. Supported versions: ${SUPPORTED_SCHEMA_VERSIONS.join(", ")}`);
548
+ }
549
+ const validatedSpec = this.validateByVersion(spec, schemaVersion);
550
+ return validatedSpec;
551
+ } catch (error) {
552
+ if (error instanceof z.ZodError) {
553
+ throw new Error(this.formatZodError(error));
554
+ }
555
+ throw error;
556
+ }
557
+ }
558
+ /**
559
+ * Compiles a validated CinematicSpec into an optimized CompiledSpec
560
+ *
561
+ * @param spec - Validated CinematicSpec to compile
562
+ * @returns CompiledSpec with precompiled animations and optimized runtime structures
563
+ */
564
+ static parse(spec) {
565
+ const context = {
566
+ schemaVersion: spec.schemaVersion,
567
+ assetPromises: /* @__PURE__ */ new Map(),
568
+ layerTypes: new Set(spec.scenes.flatMap((s) => s.layers.map((l) => l.type))),
569
+ diagnostics: [],
570
+ optimizations: {
571
+ precompileAnimations: true,
572
+ optimizeAssetLoading: true,
573
+ enableObjectPooling: true,
574
+ minimizeDOMUpdates: true,
575
+ useWebWorkers: false
576
+ // Disabled for now
577
+ }
578
+ };
579
+ try {
580
+ const compiledAssets = this.compileAssets(spec.assets || [], context);
581
+ const compiledScenes = this.compileScenes(spec.scenes, context);
582
+ const compiledEvents = this.compileEvents(spec.events, compiledScenes, context);
583
+ const totalDuration = Math.max(...Array.from(compiledEvents.values()).map((event) => event.startTime + event.duration));
584
+ const compiledSpec = {
585
+ events: compiledEvents,
586
+ scenes: compiledScenes,
587
+ assets: compiledAssets,
588
+ globalConfig: spec.engine,
589
+ schemaVersion: spec.schemaVersion,
590
+ totalDuration,
591
+ compiledAt: Date.now()
592
+ };
593
+ if (context.diagnostics.length > 0) {
594
+ console.warn("Compilation completed with warnings:", context.diagnostics);
595
+ }
596
+ return compiledSpec;
597
+ } catch (error) {
598
+ context.diagnostics.push({
599
+ level: "error",
600
+ message: error instanceof Error ? error.message : "Unknown compilation error",
601
+ code: "COMPILATION_ERROR"
602
+ });
603
+ throw new Error(`Compilation failed: ${error instanceof Error ? error.message : "Unknown error"}`);
604
+ }
605
+ }
606
+ /**
607
+ * Validates specification based on schema version
608
+ */
609
+ static validateByVersion(spec, version) {
610
+ switch (version) {
611
+ case "1.0.0":
612
+ case "1.1.0":
613
+ return CinematicSpecSchema.parse(spec);
614
+ default:
615
+ throw new Error(`Unsupported schema version: ${version}`);
616
+ }
617
+ }
618
+ /**
619
+ * Compiles asset definitions into runtime assets
620
+ */
621
+ static compileAssets(assets, _context) {
622
+ var _a, _b, _c, _d, _e;
623
+ const compiledAssets = /* @__PURE__ */ new Map();
624
+ for (const assetDef of assets) {
625
+ const asset = {
626
+ id: assetDef.id,
627
+ type: assetDef.type,
628
+ src: assetDef.src,
629
+ data: null,
630
+ loaded: false,
631
+ error: null,
632
+ fallback: assetDef.fallback,
633
+ metadata: {
634
+ size: ((_a = assetDef.metadata) == null ? void 0 : _a.size) || 0,
635
+ mimeType: ((_b = assetDef.metadata) == null ? void 0 : _b.mimeType) || "",
636
+ cacheDuration: ((_c = assetDef.metadata) == null ? void 0 : _c.cacheDuration) || 36e5,
637
+ // 1 hour default
638
+ dimensions: (_d = assetDef.metadata) == null ? void 0 : _d.dimensions,
639
+ duration: (_e = assetDef.metadata) == null ? void 0 : _e.duration,
640
+ quality: 1,
641
+ streamable: false,
642
+ priority: "normal"
643
+ },
644
+ progress: 0
645
+ };
646
+ compiledAssets.set(assetDef.id, asset);
647
+ }
648
+ return compiledAssets;
649
+ }
650
+ /**
651
+ * Compiles scene definitions into runtime scenes
652
+ */
653
+ static compileScenes(scenes, context) {
654
+ const compiledScenes = /* @__PURE__ */ new Map();
655
+ let currentStartTime = 0;
656
+ for (const scene of scenes) {
657
+ const compiledLayers = this.compileLayers(scene.layers, context);
658
+ const compiledAudioTracks = this.compileAudioTracks(scene.audio || [], context);
659
+ const compiledScene = {
660
+ id: scene.id,
661
+ name: scene.name,
662
+ duration: scene.duration,
663
+ layers: compiledLayers,
664
+ audioTracks: compiledAudioTracks,
665
+ startTime: currentStartTime,
666
+ endTime: currentStartTime + scene.duration
667
+ };
668
+ compiledScenes.set(scene.id, compiledScene);
669
+ currentStartTime += scene.duration;
670
+ }
671
+ return compiledScenes;
672
+ }
673
+ /**
674
+ * Compiles layer specifications into runtime layers
675
+ */
676
+ static compileLayers(layers, context) {
677
+ return layers.sort((a, b) => a.zIndex - b.zIndex).map((layer) => {
678
+ const compiledAnimations = this.compileAnimationTracks(layer.animations || [], context);
679
+ const layerInstance = {
680
+ id: layer.id,
681
+ type: layer.type,
682
+ zIndex: layer.zIndex,
683
+ mount: () => {
684
+ },
685
+ update: () => {
686
+ },
687
+ destroy: () => {
688
+ }
689
+ };
690
+ const compiledLayer = {
691
+ id: layer.id,
692
+ type: layer.type,
693
+ zIndex: layer.zIndex,
694
+ instance: layerInstance,
695
+ animations: compiledAnimations,
696
+ initialConfig: { ...layer.config },
697
+ active: true
698
+ };
699
+ return compiledLayer;
700
+ });
701
+ }
702
+ /**
703
+ * Compiles animation tracks into optimized runtime animations
704
+ */
705
+ static compileAnimationTracks(tracks, _context) {
706
+ return tracks.map((track) => AnimationCompiler.compileTrack(track));
707
+ }
708
+ /**
709
+ * Compiles audio tracks into runtime audio objects
710
+ */
711
+ static compileAudioTracks(tracks, _context) {
712
+ return tracks.map((track) => {
713
+ const asset = {
714
+ id: track.id,
715
+ type: "audio",
716
+ src: track.src,
717
+ data: null,
718
+ loaded: false,
719
+ error: null,
720
+ metadata: {
721
+ size: 0,
722
+ mimeType: "audio/mpeg",
723
+ cacheDuration: 36e5,
724
+ priority: "normal"
725
+ },
726
+ progress: 0
727
+ };
728
+ const compiledTrack = {
729
+ id: track.id,
730
+ type: track.type,
731
+ asset,
732
+ startMs: track.startMs,
733
+ endMs: track.endMs || 0,
734
+ // Will be set when audio loads
735
+ volume: track.volume || 1,
736
+ fadeIn: track.fadeIn || 0,
737
+ fadeOut: track.fadeOut || 0,
738
+ loop: track.loop || false
739
+ };
740
+ return compiledTrack;
741
+ });
742
+ }
743
+ /**
744
+ * Compiles event definitions into runtime events
745
+ */
746
+ static compileEvents(events, scenes, context) {
747
+ const compiledEvents = /* @__PURE__ */ new Map();
748
+ let currentStartTime = 0;
749
+ for (const event of events) {
750
+ const eventScenes = event.scenes.map((sceneId) => {
751
+ const scene = scenes.get(sceneId);
752
+ if (!scene) {
753
+ throw new Error(`Scene with ID "${sceneId}" not found for event "${event.id}"`);
754
+ }
755
+ return scene;
756
+ });
757
+ const compiledTransitions = this.compileTransitions(event.transitions || [], context);
758
+ const eventDuration = eventScenes.reduce((total, scene) => total + scene.duration, 0);
759
+ const compiledEvent = {
760
+ id: event.id,
761
+ name: event.name,
762
+ scenes: eventScenes,
763
+ transitions: compiledTransitions,
764
+ duration: eventDuration,
765
+ startTime: currentStartTime
766
+ };
767
+ compiledEvents.set(event.id, compiledEvent);
768
+ currentStartTime += eventDuration;
769
+ }
770
+ return compiledEvents;
771
+ }
772
+ /**
773
+ * Compiles transition specifications into runtime transitions
774
+ */
775
+ static compileTransitions(transitions, _context) {
776
+ return transitions.map((transition) => {
777
+ const easingFunction = AnimationCompiler.compileEasing(transition.easing || "ease");
778
+ const executeFunction = (progress, _fromElement, _toElement) => {
779
+ };
780
+ const compiledTransition = {
781
+ type: transition.type,
782
+ duration: transition.duration,
783
+ easingFunction,
784
+ config: transition.config || {},
785
+ execute: executeFunction
786
+ };
787
+ return compiledTransition;
788
+ });
789
+ }
790
+ /**
791
+ * Formats Zod validation errors into human-readable messages
792
+ */
793
+ static formatZodError(error) {
794
+ const messages = error.errors.map((err) => {
795
+ const path = err.path.length > 0 ? `at ${err.path.join(".")}` : "at root";
796
+ return `${path}: ${err.message}`;
797
+ });
798
+ return `Specification validation failed:
799
+ ${messages.join("\n")}`;
800
+ }
801
+ };
802
+
803
+ // src/cli/index.ts
804
+ async function main() {
805
+ const args = process.argv.slice(2);
806
+ if (args.length === 0) {
807
+ showHelp();
808
+ process.exit(0);
809
+ }
810
+ try {
811
+ const options = parseArgs(args);
812
+ await executeCommand(options);
813
+ } catch (error) {
814
+ console.error("Error:", error instanceof Error ? error.message : "Unknown error");
815
+ process.exit(1);
816
+ }
817
+ }
818
+ function parseArgs(args) {
819
+ const options = {};
820
+ for (let i = 0; i < args.length; i++) {
821
+ const arg = args[i];
822
+ switch (arg) {
823
+ case "validate":
824
+ case "preview":
825
+ if (options.command) {
826
+ throw new Error("Multiple commands specified. Use only one command.");
827
+ }
828
+ options.command = arg;
829
+ break;
830
+ case "-f":
831
+ case "--file":
832
+ if (i + 1 >= args.length) {
833
+ throw new Error("--file requires a file path");
834
+ }
835
+ const fileArg = args[i + 1];
836
+ if (!fileArg) {
837
+ throw new Error("--file requires a file path");
838
+ }
839
+ options.file = fileArg;
840
+ i++;
841
+ break;
842
+ case "-v":
843
+ case "--verbose":
844
+ options.verbose = true;
845
+ break;
846
+ case "-o":
847
+ case "--output":
848
+ if (i + 1 >= args.length) {
849
+ throw new Error("--output requires a file path");
850
+ }
851
+ const outputArg = args[i + 1];
852
+ if (!outputArg) {
853
+ throw new Error("--output requires a file path");
854
+ }
855
+ options.output = outputArg;
856
+ i++;
857
+ break;
858
+ case "-h":
859
+ case "--help":
860
+ showHelp();
861
+ process.exit(0);
862
+ break;
863
+ default:
864
+ if (arg && !options.command && !arg.startsWith("-")) {
865
+ throw new Error(`Unknown command: ${arg}. Use 'validate' or 'preview'.`);
866
+ } else if (arg && !options.file && !arg.startsWith("-")) {
867
+ options.file = arg;
868
+ } else if (arg) {
869
+ throw new Error(`Unknown option: ${arg}`);
870
+ }
871
+ }
872
+ }
873
+ if (!options.command) {
874
+ throw new Error('No command specified. Use "validate" or "preview".');
875
+ }
876
+ if (!options.file) {
877
+ throw new Error("No file specified. Use --file or provide file path.");
878
+ }
879
+ return options;
880
+ }
881
+ async function executeCommand(options) {
882
+ switch (options.command) {
883
+ case "validate":
884
+ await validateCommand(options);
885
+ break;
886
+ case "preview":
887
+ await previewCommand(options);
888
+ break;
889
+ default:
890
+ throw new Error(`Unknown command: ${options.command}`);
891
+ }
892
+ }
893
+ async function validateCommand(options) {
894
+ var _a, _b;
895
+ const filePath = resolve(options.file);
896
+ if (!existsSync(filePath)) {
897
+ throw new Error(`File not found: ${filePath}`);
898
+ }
899
+ if (options.verbose) {
900
+ console.log(`Validating specification: ${filePath}`);
901
+ }
902
+ try {
903
+ const fileContent = readFileSync(filePath, "utf-8");
904
+ let jsonSpec;
905
+ try {
906
+ jsonSpec = JSON.parse(fileContent);
907
+ } catch (parseError) {
908
+ throw new Error(`Invalid JSON format: ${parseError instanceof Error ? parseError.message : "Unknown JSON error"}`);
909
+ }
910
+ const validatedSpec = SpecParser.validate(jsonSpec);
911
+ const compiledSpec = SpecParser.parse(validatedSpec);
912
+ console.log("\u2705 Specification is valid!");
913
+ if (options.verbose) {
914
+ console.log("\nSpecification Details:");
915
+ console.log(` Schema Version: ${validatedSpec.schemaVersion}`);
916
+ console.log(` Events: ${validatedSpec.events.length}`);
917
+ console.log(` Scenes: ${validatedSpec.scenes.length}`);
918
+ console.log(` Assets: ${((_a = validatedSpec.assets) == null ? void 0 : _a.length) || 0}`);
919
+ console.log(` Total Duration: ${compiledSpec.totalDuration}ms`);
920
+ console.log(` Target FPS: ${validatedSpec.engine.targetFps}`);
921
+ console.log(` Quality: ${validatedSpec.engine.quality}`);
922
+ const layerTypes = new Set(validatedSpec.scenes.flatMap((s) => s.layers.map((l) => l.type)));
923
+ console.log(` Layer Types: ${Array.from(layerTypes).join(", ")}`);
924
+ const audioTracks = validatedSpec.scenes.flatMap((s) => s.audio || []);
925
+ if (audioTracks.length > 0) {
926
+ console.log(` Audio Tracks: ${audioTracks.length}`);
927
+ const audioTypes = new Set(audioTracks.map((a) => a.type));
928
+ console.log(` Audio Types: ${Array.from(audioTypes).join(", ")}`);
929
+ }
930
+ }
931
+ if (options.output) {
932
+ const report = {
933
+ valid: true,
934
+ file: filePath,
935
+ validatedAt: (/* @__PURE__ */ new Date()).toISOString(),
936
+ spec: {
937
+ schemaVersion: validatedSpec.schemaVersion,
938
+ events: validatedSpec.events.length,
939
+ scenes: validatedSpec.scenes.length,
940
+ assets: ((_b = validatedSpec.assets) == null ? void 0 : _b.length) || 0,
941
+ totalDuration: compiledSpec.totalDuration,
942
+ targetFps: validatedSpec.engine.targetFps,
943
+ quality: validatedSpec.engine.quality
944
+ }
945
+ };
946
+ const { writeFileSync } = await import('fs');
947
+ writeFileSync(options.output, JSON.stringify(report, null, 2));
948
+ console.log(`\u{1F4C4} Validation report saved to: ${options.output}`);
949
+ }
950
+ } catch (error) {
951
+ console.error("\u274C Specification validation failed:");
952
+ console.error(error instanceof Error ? error.message : "Unknown validation error");
953
+ if (options.output) {
954
+ const errorReport = {
955
+ valid: false,
956
+ file: filePath,
957
+ validatedAt: (/* @__PURE__ */ new Date()).toISOString(),
958
+ error: error instanceof Error ? error.message : "Unknown validation error"
959
+ };
960
+ const { writeFileSync } = await import('fs');
961
+ writeFileSync(options.output, JSON.stringify(errorReport, null, 2));
962
+ console.log(`\u{1F4C4} Error report saved to: ${options.output}`);
963
+ }
964
+ process.exit(1);
965
+ }
966
+ }
967
+ async function previewCommand(options) {
968
+ const filePath = resolve(options.file);
969
+ if (!existsSync(filePath)) {
970
+ throw new Error(`File not found: ${filePath}`);
971
+ }
972
+ if (options.verbose) {
973
+ console.log(`Generating preview for: ${filePath}`);
974
+ }
975
+ try {
976
+ const fileContent = readFileSync(filePath, "utf-8");
977
+ let jsonSpec;
978
+ try {
979
+ jsonSpec = JSON.parse(fileContent);
980
+ } catch (parseError) {
981
+ throw new Error(`Invalid JSON format: ${parseError instanceof Error ? parseError.message : "Unknown JSON error"}`);
982
+ }
983
+ const validatedSpec = SpecParser.validate(jsonSpec);
984
+ const compiledSpec = SpecParser.parse(validatedSpec);
985
+ const previewHtml = generatePreviewHtml(validatedSpec, compiledSpec, filePath);
986
+ if (options.output) {
987
+ const { writeFileSync } = await import('fs');
988
+ writeFileSync(options.output, previewHtml);
989
+ console.log(`\u{1F3AC} Preview saved to: ${options.output}`);
990
+ console.log(` Open in browser to view the cinematic experience.`);
991
+ } else {
992
+ const { tmpdir } = await import('os');
993
+ const { join } = await import('path');
994
+ const tempFile = join(tmpdir(), `cinematic-preview-${Date.now()}.html`);
995
+ const { writeFileSync } = await import('fs');
996
+ writeFileSync(tempFile, previewHtml);
997
+ console.log(`\u{1F3AC} Preview generated: ${tempFile}`);
998
+ console.log(` Open this file in your browser to view the cinematic experience.`);
999
+ try {
1000
+ const { exec } = await import('child_process');
1001
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1002
+ exec(`${command} "${tempFile}"`);
1003
+ console.log(` Opening in default browser...`);
1004
+ } catch {
1005
+ }
1006
+ }
1007
+ } catch (error) {
1008
+ console.error("\u274C Preview generation failed:");
1009
+ console.error(error instanceof Error ? error.message : "Unknown preview error");
1010
+ process.exit(1);
1011
+ }
1012
+ }
1013
+ function generatePreviewHtml(spec, compiledSpec, filePath) {
1014
+ var _a, _b;
1015
+ const specJson = JSON.stringify(spec, null, 2);
1016
+ return `<!DOCTYPE html>
1017
+ <html lang="en">
1018
+ <head>
1019
+ <meta charset="UTF-8">
1020
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1021
+ <title>Cinematic Preview - ${((_a = spec.events[0]) == null ? void 0 : _a.name) || "Untitled"}</title>
1022
+ <style>
1023
+ body {
1024
+ margin: 0;
1025
+ padding: 20px;
1026
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1027
+ background: #1a1a1a;
1028
+ color: #ffffff;
1029
+ }
1030
+ .container {
1031
+ max-width: 1200px;
1032
+ margin: 0 auto;
1033
+ }
1034
+ .header {
1035
+ text-align: center;
1036
+ margin-bottom: 30px;
1037
+ }
1038
+ .preview-area {
1039
+ background: #000;
1040
+ border-radius: 8px;
1041
+ margin-bottom: 30px;
1042
+ position: relative;
1043
+ aspect-ratio: 16/9;
1044
+ display: flex;
1045
+ align-items: center;
1046
+ justify-content: center;
1047
+ border: 2px solid #333;
1048
+ }
1049
+ .placeholder {
1050
+ text-align: center;
1051
+ color: #666;
1052
+ }
1053
+ .controls {
1054
+ display: flex;
1055
+ justify-content: center;
1056
+ gap: 10px;
1057
+ margin-bottom: 30px;
1058
+ }
1059
+ .btn {
1060
+ padding: 10px 20px;
1061
+ background: #007acc;
1062
+ color: white;
1063
+ border: none;
1064
+ border-radius: 4px;
1065
+ cursor: pointer;
1066
+ font-size: 14px;
1067
+ }
1068
+ .btn:hover {
1069
+ background: #005a9e;
1070
+ }
1071
+ .btn:disabled {
1072
+ background: #333;
1073
+ cursor: not-allowed;
1074
+ }
1075
+ .info-panel {
1076
+ background: #2a2a2a;
1077
+ border-radius: 8px;
1078
+ padding: 20px;
1079
+ margin-bottom: 20px;
1080
+ }
1081
+ .info-grid {
1082
+ display: grid;
1083
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1084
+ gap: 15px;
1085
+ }
1086
+ .info-item {
1087
+ background: #333;
1088
+ padding: 15px;
1089
+ border-radius: 4px;
1090
+ }
1091
+ .info-label {
1092
+ font-size: 12px;
1093
+ color: #999;
1094
+ text-transform: uppercase;
1095
+ margin-bottom: 5px;
1096
+ }
1097
+ .info-value {
1098
+ font-size: 16px;
1099
+ font-weight: 600;
1100
+ }
1101
+ .spec-details {
1102
+ background: #2a2a2a;
1103
+ border-radius: 8px;
1104
+ padding: 20px;
1105
+ }
1106
+ .spec-json {
1107
+ background: #1a1a1a;
1108
+ border-radius: 4px;
1109
+ padding: 15px;
1110
+ overflow-x: auto;
1111
+ font-family: 'Monaco', 'Menlo', monospace;
1112
+ font-size: 12px;
1113
+ line-height: 1.4;
1114
+ max-height: 400px;
1115
+ overflow-y: auto;
1116
+ }
1117
+ .warning {
1118
+ background: #ff6b35;
1119
+ color: white;
1120
+ padding: 15px;
1121
+ border-radius: 4px;
1122
+ margin-bottom: 20px;
1123
+ }
1124
+ </style>
1125
+ </head>
1126
+ <body>
1127
+ <div class="container">
1128
+ <div class="header">
1129
+ <h1>\u{1F3AC} Cinematic Preview</h1>
1130
+ <p>Specification: <code>${filePath}</code></p>
1131
+ </div>
1132
+
1133
+ <div class="warning">
1134
+ <strong>\u26A0\uFE0F Preview Mode:</strong> This is a static preview of your specification.
1135
+ For full interactive playback, integrate the cinematic-renderer2d library into your application.
1136
+ </div>
1137
+
1138
+ <div class="preview-area">
1139
+ <div class="placeholder">
1140
+ <h2>\u{1F3AD} Cinematic Renderer Preview</h2>
1141
+ <p>Interactive preview would appear here</p>
1142
+ <p>Total Duration: ${compiledSpec.totalDuration}ms</p>
1143
+ </div>
1144
+ </div>
1145
+
1146
+ <div class="controls">
1147
+ <button class="btn" disabled>\u25B6\uFE0F Play</button>
1148
+ <button class="btn" disabled>\u23F8\uFE0F Pause</button>
1149
+ <button class="btn" disabled>\u23F9\uFE0F Stop</button>
1150
+ <button class="btn" disabled>\u23EE\uFE0F Previous</button>
1151
+ <button class="btn" disabled>\u23ED\uFE0F Next</button>
1152
+ </div>
1153
+
1154
+ <div class="info-panel">
1155
+ <h3>Specification Overview</h3>
1156
+ <div class="info-grid">
1157
+ <div class="info-item">
1158
+ <div class="info-label">Schema Version</div>
1159
+ <div class="info-value">${spec.schemaVersion}</div>
1160
+ </div>
1161
+ <div class="info-item">
1162
+ <div class="info-label">Events</div>
1163
+ <div class="info-value">${spec.events.length}</div>
1164
+ </div>
1165
+ <div class="info-item">
1166
+ <div class="info-label">Scenes</div>
1167
+ <div class="info-value">${spec.scenes.length}</div>
1168
+ </div>
1169
+ <div class="info-item">
1170
+ <div class="info-label">Assets</div>
1171
+ <div class="info-value">${((_b = spec.assets) == null ? void 0 : _b.length) || 0}</div>
1172
+ </div>
1173
+ <div class="info-item">
1174
+ <div class="info-label">Target FPS</div>
1175
+ <div class="info-value">${spec.engine.targetFps}</div>
1176
+ </div>
1177
+ <div class="info-item">
1178
+ <div class="info-label">Quality</div>
1179
+ <div class="info-value">${spec.engine.quality}</div>
1180
+ </div>
1181
+ <div class="info-item">
1182
+ <div class="info-label">Total Duration</div>
1183
+ <div class="info-value">${Math.round(compiledSpec.totalDuration / 1e3)}s</div>
1184
+ </div>
1185
+ <div class="info-item">
1186
+ <div class="info-label">Layer Types</div>
1187
+ <div class="info-value">${Array.from(new Set(spec.scenes.flatMap((s) => s.layers.map((l) => l.type)))).join(", ")}</div>
1188
+ </div>
1189
+ </div>
1190
+ </div>
1191
+
1192
+ <div class="spec-details">
1193
+ <h3>Specification Details</h3>
1194
+ <pre class="spec-json">${specJson}</pre>
1195
+ </div>
1196
+ </div>
1197
+ </body>
1198
+ </html>`;
1199
+ }
1200
+ function showHelp() {
1201
+ console.log(`
1202
+ \u{1F3AC} Cinematic Renderer 2D CLI
1203
+
1204
+ USAGE:
1205
+ cinematic-cli <command> [options]
1206
+
1207
+ COMMANDS:
1208
+ validate Validate a JSON specification file
1209
+ preview Generate an HTML preview of the specification
1210
+
1211
+ OPTIONS:
1212
+ -f, --file <path> Specification file to process (required)
1213
+ -v, --verbose Show detailed output
1214
+ -o, --output <path> Output file path (optional)
1215
+ -h, --help Show this help message
1216
+
1217
+ EXAMPLES:
1218
+ # Validate a specification
1219
+ cinematic-cli validate --file my-spec.json
1220
+
1221
+ # Validate with verbose output
1222
+ cinematic-cli validate --file my-spec.json --verbose
1223
+
1224
+ # Generate preview
1225
+ cinematic-cli preview --file my-spec.json
1226
+
1227
+ # Save preview to specific file
1228
+ cinematic-cli preview --file my-spec.json --output preview.html
1229
+
1230
+ # Save validation report
1231
+ cinematic-cli validate --file my-spec.json --output report.json
1232
+ `);
1233
+ }
1234
+ if (import.meta.url === `file://${process.argv[1]}`) {
1235
+ main().catch((error) => {
1236
+ console.error("Fatal error:", error);
1237
+ process.exit(1);
1238
+ });
1239
+ }
1240
+
1241
+ export { main as runCLI };
1242
+ //# sourceMappingURL=index.js.map
1243
+ //# sourceMappingURL=index.js.map