@depths/waves 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -38,7 +38,7 @@ var import_promises2 = __toESM(require("fs/promises"));
38
38
  var import_node_path2 = __toESM(require("path"));
39
39
 
40
40
  // src/version.ts
41
- var __wavesVersion = "0.1.0";
41
+ var __wavesVersion = "0.2.0";
42
42
 
43
43
  // src/utils/json-schema.ts
44
44
  var import_zod = require("zod");
@@ -70,62 +70,63 @@ var BackgroundSpecSchema = import_zod2.z.discriminatedUnion("type", [
70
70
  value: AssetPathSchema
71
71
  })
72
72
  ]);
73
- var BaseComponentIRSchema = import_zod2.z.object({
74
- id: import_zod2.z.string().min(1).max(100).describe("Unique component instance ID"),
75
- type: import_zod2.z.string().min(1).describe("Component type identifier"),
76
- timing: TimingSpecSchema,
77
- metadata: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional().describe("Optional metadata")
73
+ var VideoConfigSchema = import_zod2.z.object({
74
+ id: import_zod2.z.string().default("main"),
75
+ width: import_zod2.z.number().int().min(360).max(7680),
76
+ height: import_zod2.z.number().int().min(360).max(4320),
77
+ fps: import_zod2.z.number().int().min(1).max(120).default(30),
78
+ durationInFrames: import_zod2.z.number().int().positive()
78
79
  });
79
- var TextComponentIRSchema = BaseComponentIRSchema.extend({
80
- type: import_zod2.z.literal("Text"),
81
- props: import_zod2.z.object({
82
- content: import_zod2.z.string().min(1).max(1e3),
83
- fontSize: import_zod2.z.number().int().min(12).max(200).default(48),
84
- color: import_zod2.z.string().regex(/^#[0-9A-Fa-f]{6}$/).default("#FFFFFF"),
85
- position: import_zod2.z.enum(["top", "center", "bottom", "left", "right"]).default("center"),
86
- animation: import_zod2.z.enum(["none", "fade", "slide", "zoom"]).default("fade")
87
- })
80
+ var AudioSpecSchema = import_zod2.z.object({
81
+ background: AssetPathSchema.optional(),
82
+ volume: import_zod2.z.number().min(0).max(1).default(0.5)
83
+ }).optional();
84
+ var TransitionSpecSchema = import_zod2.z.object({
85
+ type: import_zod2.z.string().min(1),
86
+ durationInFrames: import_zod2.z.number().int().positive(),
87
+ props: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional()
88
88
  });
89
- var AudioComponentIRSchema = BaseComponentIRSchema.extend({
90
- type: import_zod2.z.literal("Audio"),
91
- props: import_zod2.z.object({
92
- src: AssetPathSchema,
93
- volume: import_zod2.z.number().min(0).max(1).default(1),
94
- startFrom: import_zod2.z.number().int().min(0).default(0).describe("Start playback from frame N"),
95
- fadeIn: import_zod2.z.number().int().min(0).default(0).describe("Fade in duration in frames"),
96
- fadeOut: import_zod2.z.number().int().min(0).default(0).describe("Fade out duration in frames")
89
+ var ComponentNodeSchema = import_zod2.z.lazy(
90
+ () => import_zod2.z.object({
91
+ id: import_zod2.z.string().min(1).max(100).describe("Unique component instance ID"),
92
+ type: import_zod2.z.string().min(1).describe("Registered component type identifier"),
93
+ timing: TimingSpecSchema.optional().describe("Optional timing (frames)"),
94
+ props: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional().describe("Component props object"),
95
+ metadata: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional().describe("Optional metadata"),
96
+ children: import_zod2.z.array(ComponentNodeSchema).optional().describe("Nested children nodes")
97
97
  })
98
+ );
99
+ var SegmentSchema = import_zod2.z.object({
100
+ id: import_zod2.z.string().min(1).max(100),
101
+ durationInFrames: import_zod2.z.number().int().positive(),
102
+ root: ComponentNodeSchema,
103
+ transitionToNext: TransitionSpecSchema.optional()
98
104
  });
99
- function getComponentIRSchema() {
100
- return ComponentIRSchema;
101
- }
102
- var SceneComponentIRSchema = BaseComponentIRSchema.extend({
103
- type: import_zod2.z.literal("Scene"),
104
- props: import_zod2.z.object({
105
- background: BackgroundSpecSchema
106
- }),
107
- children: import_zod2.z.lazy(() => import_zod2.z.array(getComponentIRSchema())).optional()
108
- });
109
- var ComponentIRSchema = import_zod2.z.discriminatedUnion("type", [
110
- SceneComponentIRSchema,
111
- TextComponentIRSchema,
112
- AudioComponentIRSchema
113
- ]);
114
- var VideoIRSchema = import_zod2.z.object({
115
- version: import_zod2.z.literal("1.0").describe("IR schema version"),
116
- video: import_zod2.z.object({
117
- id: import_zod2.z.string().default("main"),
118
- width: import_zod2.z.number().int().min(360).max(7680),
119
- height: import_zod2.z.number().int().min(360).max(4320),
120
- fps: import_zod2.z.number().int().min(1).max(120).default(30),
121
- durationInFrames: import_zod2.z.number().int().positive()
122
- }),
123
- audio: import_zod2.z.object({
124
- background: AssetPathSchema.optional(),
125
- volume: import_zod2.z.number().min(0).max(1).default(0.5)
126
- }).optional(),
127
- scenes: import_zod2.z.array(SceneComponentIRSchema).min(1)
105
+ var VideoIRv2Schema = import_zod2.z.object({
106
+ version: import_zod2.z.literal("2.0").describe("IR schema version"),
107
+ video: VideoConfigSchema,
108
+ audio: AudioSpecSchema,
109
+ segments: import_zod2.z.array(SegmentSchema).min(1).optional(),
110
+ timeline: import_zod2.z.array(ComponentNodeSchema).min(1).optional()
111
+ }).superRefine((v, ctx) => {
112
+ const hasSegments = Array.isArray(v.segments);
113
+ const hasTimeline = Array.isArray(v.timeline);
114
+ if (hasSegments === hasTimeline) {
115
+ ctx.addIssue({
116
+ code: import_zod2.z.ZodIssueCode.custom,
117
+ message: 'Exactly one of "segments" or "timeline" must be provided',
118
+ path: []
119
+ });
120
+ }
128
121
  });
122
+ var VideoIRv2AuthoringSchema = import_zod2.z.object({
123
+ version: import_zod2.z.literal("2.0").describe("IR schema version"),
124
+ video: VideoConfigSchema,
125
+ audio: AudioSpecSchema,
126
+ segments: import_zod2.z.array(SegmentSchema).min(1),
127
+ timeline: import_zod2.z.never().optional()
128
+ }).describe("Preferred authoring format: sequential segments with optional transitions");
129
+ var VideoIRSchema = VideoIRv2Schema;
129
130
 
130
131
  // src/core/registry.ts
131
132
  var ComponentRegistry = class {
@@ -142,12 +143,23 @@ var ComponentRegistry = class {
142
143
  getTypes() {
143
144
  return Array.from(this.components.keys());
144
145
  }
146
+ getTypesForLLM(options) {
147
+ const includeInternal = options?.includeInternal ?? false;
148
+ const out = [];
149
+ for (const [type, reg] of this.components) {
150
+ if (!includeInternal && reg.metadata.internal) continue;
151
+ out.push(type);
152
+ }
153
+ return out;
154
+ }
145
155
  has(type) {
146
156
  return this.components.has(type);
147
157
  }
148
- getJSONSchemaForLLM() {
158
+ getJSONSchemaForLLM(options) {
159
+ const includeInternal = options?.includeInternal ?? false;
149
160
  const schemas = {};
150
161
  for (const [type, registration] of this.components) {
162
+ if (!includeInternal && registration.metadata.internal) continue;
151
163
  schemas[type] = {
152
164
  schema: zodSchemaToJsonSchema(registration.propsSchema),
153
165
  metadata: registration.metadata
@@ -223,57 +235,555 @@ var Audio = ({
223
235
  );
224
236
  };
225
237
  var AudioComponentMetadata = {
226
- category: "primitive",
238
+ kind: "primitive",
239
+ category: "media",
227
240
  description: "Plays an audio file with optional trimming and fade in/out",
228
241
  llmGuidance: "Use for background music or sound effects. Prefer short clips for SFX. Use fadeIn/fadeOut (in frames) for smoother audio starts/ends."
229
242
  };
230
243
 
231
- // src/components/primitives/Scene.tsx
232
- var import_remotion2 = require("remotion");
244
+ // src/components/primitives/Box.tsx
245
+ var import_react = __toESM(require("react"));
233
246
  var import_zod4 = require("zod");
247
+
248
+ // src/remotion/Fill.tsx
234
249
  var import_jsx_runtime2 = require("react/jsx-runtime");
235
- var ScenePropsSchema = import_zod4.z.object({
250
+ var Fill = ({ style, children }) => {
251
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
252
+ "div",
253
+ {
254
+ style: {
255
+ width: "100%",
256
+ height: "100%",
257
+ position: "relative",
258
+ boxSizing: "border-box",
259
+ ...style ?? {}
260
+ },
261
+ children
262
+ }
263
+ );
264
+ };
265
+
266
+ // src/components/primitives/Box.tsx
267
+ var import_jsx_runtime3 = require("react/jsx-runtime");
268
+ var BoxPropsSchema = import_zod4.z.object({
269
+ width: import_zod4.z.number().positive().optional().describe("Width in pixels (optional)"),
270
+ height: import_zod4.z.number().positive().optional().describe("Height in pixels (optional)"),
271
+ padding: import_zod4.z.number().min(0).default(0).describe("Padding in pixels"),
272
+ backgroundColor: import_zod4.z.string().optional().describe("CSS color (e.g. #RRGGBB, rgba())"),
273
+ borderRadius: import_zod4.z.number().min(0).default(0).describe("Border radius in pixels"),
274
+ opacity: import_zod4.z.number().min(0).max(1).default(1).describe("Opacity 0..1")
275
+ });
276
+ var Box = ({ width, height, padding, backgroundColor, borderRadius, opacity, children }) => {
277
+ const layers = import_react.default.Children.toArray(children);
278
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
279
+ Fill,
280
+ {
281
+ style: {
282
+ width,
283
+ height,
284
+ padding,
285
+ backgroundColor,
286
+ borderRadius,
287
+ opacity
288
+ },
289
+ children: layers.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { position: "absolute", inset: 0 }, children: child }, i))
290
+ }
291
+ );
292
+ };
293
+ var BoxComponentMetadata = {
294
+ kind: "primitive",
295
+ category: "layout",
296
+ acceptsChildren: true,
297
+ description: "Flow container for layout and backgrounds (layout-safe)",
298
+ llmGuidance: "Use Box as a container inside Grid/Stack. Box participates in layout flow. For x/y positioning, use Frame."
299
+ };
300
+
301
+ // src/components/primitives/Frame.tsx
302
+ var import_react2 = __toESM(require("react"));
303
+ var import_zod5 = require("zod");
304
+ var import_jsx_runtime4 = require("react/jsx-runtime");
305
+ var FramePropsSchema = import_zod5.z.object({
306
+ x: import_zod5.z.number().default(0).describe("Left offset in pixels"),
307
+ y: import_zod5.z.number().default(0).describe("Top offset in pixels"),
308
+ width: import_zod5.z.number().positive().optional().describe("Width in pixels (optional)"),
309
+ height: import_zod5.z.number().positive().optional().describe("Height in pixels (optional)"),
310
+ padding: import_zod5.z.number().min(0).default(0).describe("Padding in pixels"),
311
+ backgroundColor: import_zod5.z.string().optional().describe("CSS color (e.g. #RRGGBB, rgba())"),
312
+ borderRadius: import_zod5.z.number().min(0).default(0).describe("Border radius in pixels"),
313
+ opacity: import_zod5.z.number().min(0).max(1).default(1).describe("Opacity 0..1")
314
+ });
315
+ var Frame = ({
316
+ x,
317
+ y,
318
+ width,
319
+ height,
320
+ padding,
321
+ backgroundColor,
322
+ borderRadius,
323
+ opacity,
324
+ children
325
+ }) => {
326
+ const layers = import_react2.default.Children.toArray(children);
327
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
328
+ "div",
329
+ {
330
+ style: {
331
+ position: "absolute",
332
+ left: x,
333
+ top: y,
334
+ width,
335
+ height,
336
+ padding,
337
+ backgroundColor,
338
+ borderRadius,
339
+ opacity,
340
+ boxSizing: "border-box"
341
+ },
342
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Fill, { children: layers.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { position: "absolute", inset: 0 }, children: child }, i)) })
343
+ }
344
+ );
345
+ };
346
+ var FrameComponentMetadata = {
347
+ kind: "primitive",
348
+ category: "layout",
349
+ acceptsChildren: true,
350
+ description: "Absolute-positioned container (x/y placement)",
351
+ llmGuidance: "Use Frame for precise pixel placement (x/y). Use Box for normal layout flow inside Grid/Stack."
352
+ };
353
+
354
+ // src/components/primitives/Grid.tsx
355
+ var import_zod6 = require("zod");
356
+ var import_jsx_runtime5 = require("react/jsx-runtime");
357
+ var GridPropsSchema = import_zod6.z.object({
358
+ columns: import_zod6.z.number().int().min(1).max(12).default(2),
359
+ rows: import_zod6.z.number().int().min(1).max(12).default(1),
360
+ gap: import_zod6.z.number().min(0).default(24),
361
+ padding: import_zod6.z.number().min(0).default(0),
362
+ align: import_zod6.z.enum(["start", "center", "end", "stretch"]).default("stretch"),
363
+ justify: import_zod6.z.enum(["start", "center", "end", "stretch"]).default("stretch")
364
+ });
365
+ var mapAlign = (a) => {
366
+ if (a === "start") return "start";
367
+ if (a === "end") return "end";
368
+ if (a === "center") return "center";
369
+ return "stretch";
370
+ };
371
+ var mapJustify = (j) => {
372
+ if (j === "start") return "start";
373
+ if (j === "end") return "end";
374
+ if (j === "center") return "center";
375
+ return "stretch";
376
+ };
377
+ var Grid = ({ columns, rows, gap, padding, align, justify, children }) => {
378
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
379
+ Fill,
380
+ {
381
+ style: {
382
+ display: "grid",
383
+ gridTemplateColumns: `repeat(${columns}, 1fr)`,
384
+ gridTemplateRows: `repeat(${rows}, 1fr)`,
385
+ gap,
386
+ padding,
387
+ alignItems: mapAlign(align),
388
+ justifyItems: mapJustify(justify),
389
+ boxSizing: "border-box"
390
+ },
391
+ children
392
+ }
393
+ );
394
+ };
395
+ var GridComponentMetadata = {
396
+ kind: "primitive",
397
+ category: "layout",
398
+ acceptsChildren: true,
399
+ description: "Grid layout container with configurable rows/columns",
400
+ llmGuidance: "Use Grid for photo collages and dashboards. Provide exactly rows*columns children when possible."
401
+ };
402
+
403
+ // src/components/primitives/Image.tsx
404
+ var import_remotion2 = require("remotion");
405
+ var import_zod7 = require("zod");
406
+ var import_jsx_runtime6 = require("react/jsx-runtime");
407
+ var ImagePropsSchema = import_zod7.z.object({
408
+ src: import_zod7.z.string().min(1),
409
+ fit: import_zod7.z.enum(["cover", "contain"]).default("cover"),
410
+ borderRadius: import_zod7.z.number().min(0).default(0),
411
+ opacity: import_zod7.z.number().min(0).max(1).default(1)
412
+ });
413
+ var resolveAsset = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion2.staticFile)(staticFileInputFromAssetPath(value));
414
+ var Image = ({ src, fit, borderRadius, opacity }) => {
415
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Fill, { style: { opacity }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
416
+ import_remotion2.Img,
417
+ {
418
+ src: resolveAsset(src),
419
+ style: { width: "100%", height: "100%", objectFit: fit, borderRadius }
420
+ }
421
+ ) });
422
+ };
423
+ var ImageComponentMetadata = {
424
+ kind: "primitive",
425
+ category: "image",
426
+ description: "Full-frame image with object-fit options",
427
+ llmGuidance: 'Use Image for pictures and backgrounds. Use fit="cover" for full-bleed, fit="contain" to avoid cropping.'
428
+ };
429
+
430
+ // src/components/primitives/Layer.tsx
431
+ var import_react3 = __toESM(require("react"));
432
+ var import_zod8 = require("zod");
433
+ var import_jsx_runtime7 = require("react/jsx-runtime");
434
+ var LayerPropsSchema = import_zod8.z.object({
435
+ zIndex: import_zod8.z.number().int().default(0).describe("Higher zIndex renders on top"),
436
+ inset: import_zod8.z.number().min(0).default(0).describe("Inset from all sides in pixels"),
437
+ opacity: import_zod8.z.number().min(0).max(1).default(1),
438
+ pointerEvents: import_zod8.z.enum(["none", "auto"]).default("none")
439
+ });
440
+ var Layer = ({ zIndex, inset, opacity, pointerEvents, children }) => {
441
+ const layers = import_react3.default.Children.toArray(children);
442
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
443
+ "div",
444
+ {
445
+ style: {
446
+ position: "absolute",
447
+ left: inset,
448
+ right: inset,
449
+ top: inset,
450
+ bottom: inset,
451
+ zIndex,
452
+ opacity,
453
+ pointerEvents,
454
+ boxSizing: "border-box"
455
+ },
456
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Fill, { children: layers.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { position: "absolute", inset: 0 }, children: child }, i)) })
457
+ }
458
+ );
459
+ };
460
+ var LayerComponentMetadata = {
461
+ kind: "primitive",
462
+ category: "layout",
463
+ acceptsChildren: true,
464
+ minChildren: 1,
465
+ description: "One overlay layer with explicit zIndex inside Layers",
466
+ llmGuidance: "Use Layer inside Layers to control stacking. Put exactly one child in a Layer (recommended)."
467
+ };
468
+
469
+ // src/components/primitives/Layers.tsx
470
+ var import_zod9 = require("zod");
471
+ var import_jsx_runtime8 = require("react/jsx-runtime");
472
+ var LayersPropsSchema = import_zod9.z.object({
473
+ overflow: import_zod9.z.enum(["visible", "hidden"]).default("visible").describe("Clip layers to bounds")
474
+ });
475
+ var Layers = ({ overflow, children }) => {
476
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Fill, { style: { overflow }, children });
477
+ };
478
+ var LayersComponentMetadata = {
479
+ kind: "primitive",
480
+ category: "layout",
481
+ acceptsChildren: true,
482
+ minChildren: 1,
483
+ description: "Overlay container for stacking children (use Layer for zIndex)",
484
+ llmGuidance: "Use Layers to stack background/content/overlays. Prefer Layer children with explicit zIndex."
485
+ };
486
+
487
+ // src/components/primitives/Scene.tsx
488
+ var import_react4 = __toESM(require("react"));
489
+ var import_remotion3 = require("remotion");
490
+ var import_zod10 = require("zod");
491
+ var import_jsx_runtime9 = require("react/jsx-runtime");
492
+ var ScenePropsSchema = import_zod10.z.object({
236
493
  background: BackgroundSpecSchema
237
494
  });
238
- var resolveAsset = (value) => {
239
- return isRemoteAssetPath(value) ? value : (0, import_remotion2.staticFile)(staticFileInputFromAssetPath(value));
495
+ var resolveAsset2 = (value) => {
496
+ return isRemoteAssetPath(value) ? value : (0, import_remotion3.staticFile)(staticFileInputFromAssetPath(value));
240
497
  };
241
498
  var Scene = ({ background, children }) => {
242
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_remotion2.AbsoluteFill, { children: [
243
- background.type === "color" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_remotion2.AbsoluteFill, { style: { backgroundColor: background.value } }) : background.type === "image" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
244
- import_remotion2.Img,
499
+ const layers = import_react4.default.Children.toArray(children);
500
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_remotion3.AbsoluteFill, { children: [
501
+ background.type === "color" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_remotion3.AbsoluteFill, { style: { backgroundColor: background.value } }) : background.type === "image" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
502
+ import_remotion3.Img,
245
503
  {
246
- src: resolveAsset(background.value),
504
+ src: resolveAsset2(background.value),
247
505
  style: { width: "100%", height: "100%", objectFit: "cover" }
248
506
  }
249
- ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
250
- import_remotion2.Video,
507
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
508
+ import_remotion3.Video,
251
509
  {
252
- src: resolveAsset(background.value),
510
+ src: resolveAsset2(background.value),
253
511
  style: { width: "100%", height: "100%", objectFit: "cover" }
254
512
  }
255
513
  ),
256
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_remotion2.AbsoluteFill, { children })
514
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_remotion3.AbsoluteFill, { children: layers.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_remotion3.AbsoluteFill, { children: child }, i)) })
257
515
  ] });
258
516
  };
259
517
  var SceneComponentMetadata = {
260
- category: "primitive",
518
+ kind: "primitive",
519
+ category: "layout",
520
+ acceptsChildren: true,
261
521
  description: "Scene container with a background and nested children",
262
522
  llmGuidance: "Use Scene to define a segment of the video. Scene timings must be sequential with no gaps. Put Text and Audio as children."
263
523
  };
264
524
 
525
+ // src/components/primitives/Segment.tsx
526
+ var import_remotion4 = require("remotion");
527
+ var import_zod11 = require("zod");
528
+ var import_jsx_runtime10 = require("react/jsx-runtime");
529
+ var TransitionPropsSchema = import_zod11.z.object({
530
+ type: import_zod11.z.string().min(1),
531
+ durationInFrames: import_zod11.z.number().int().positive(),
532
+ props: import_zod11.z.record(import_zod11.z.string(), import_zod11.z.unknown()).optional()
533
+ });
534
+ var SegmentPropsSchema = import_zod11.z.object({
535
+ enterTransition: TransitionPropsSchema.optional(),
536
+ exitTransition: TransitionPropsSchema.optional()
537
+ });
538
+ function getSlideOptions(raw) {
539
+ const parsed = import_zod11.z.object({
540
+ direction: import_zod11.z.enum(["left", "right", "up", "down"]).default("left"),
541
+ distance: import_zod11.z.number().int().min(1).max(2e3).default(80)
542
+ }).safeParse(raw ?? {});
543
+ if (parsed.success) return parsed.data;
544
+ return { direction: "left", distance: 80 };
545
+ }
546
+ function getZoomOptions(raw) {
547
+ const parsed = import_zod11.z.object({
548
+ type: import_zod11.z.enum(["zoomIn", "zoomOut"]).default("zoomIn")
549
+ }).safeParse(raw ?? {});
550
+ if (parsed.success) return parsed.data;
551
+ return { type: "zoomIn" };
552
+ }
553
+ function getWipeOptions(raw) {
554
+ const parsed = import_zod11.z.object({
555
+ direction: import_zod11.z.enum(["left", "right", "up", "down", "diagonal"]).default("right"),
556
+ softEdge: import_zod11.z.boolean().default(false)
557
+ }).safeParse(raw ?? {});
558
+ if (parsed.success) return parsed.data;
559
+ return { direction: "right", softEdge: false };
560
+ }
561
+ function clipFor(direction, p) {
562
+ if (direction === "left") return `inset(0 ${100 * (1 - p)}% 0 0)`;
563
+ if (direction === "right") return `inset(0 0 0 ${100 * (1 - p)}%)`;
564
+ if (direction === "up") return `inset(0 0 ${100 * (1 - p)}% 0)`;
565
+ if (direction === "down") return `inset(${100 * (1 - p)}% 0 0 0)`;
566
+ return `polygon(0 0, ${100 * p}% 0, 0 ${100 * p}%)`;
567
+ }
568
+ function getCircularOptions(raw) {
569
+ const parsed = import_zod11.z.object({
570
+ direction: import_zod11.z.enum(["open", "close"]).default("open"),
571
+ center: import_zod11.z.object({
572
+ x: import_zod11.z.number().min(0).max(1).default(0.5),
573
+ y: import_zod11.z.number().min(0).max(1).default(0.5)
574
+ }).default({ x: 0.5, y: 0.5 })
575
+ }).safeParse(raw ?? {});
576
+ if (parsed.success) return parsed.data;
577
+ return { direction: "open", center: { x: 0.5, y: 0.5 } };
578
+ }
579
+ var Segment = ({ enterTransition, exitTransition, children, __wavesDurationInFrames }) => {
580
+ const frame = (0, import_remotion4.useCurrentFrame)();
581
+ const durationInFrames = __wavesDurationInFrames ?? 0;
582
+ let opacity = 1;
583
+ let translateX = 0;
584
+ let translateY = 0;
585
+ let scale = 1;
586
+ let clipPath;
587
+ let filter;
588
+ if (enterTransition) {
589
+ const d = enterTransition.durationInFrames;
590
+ if (enterTransition.type === "FadeTransition") {
591
+ opacity *= (0, import_remotion4.interpolate)(frame, [0, d], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
592
+ }
593
+ if (enterTransition.type === "SlideTransition") {
594
+ const opts = getSlideOptions(enterTransition.props);
595
+ const t = (0, import_remotion4.interpolate)(frame, [0, d], [1, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
596
+ const delta = opts.distance * t;
597
+ if (opts.direction === "left") translateX += -delta;
598
+ if (opts.direction === "right") translateX += delta;
599
+ if (opts.direction === "up") translateY += -delta;
600
+ if (opts.direction === "down") translateY += delta;
601
+ opacity *= (0, import_remotion4.interpolate)(frame, [0, d], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
602
+ }
603
+ if (enterTransition.type === "ZoomTransition") {
604
+ const opts = getZoomOptions(enterTransition.props);
605
+ const fromScale = opts.type === "zoomIn" ? 1.2 : 0.85;
606
+ const t = (0, import_remotion4.interpolate)(frame, [0, d], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
607
+ scale *= fromScale + (1 - fromScale) * t;
608
+ opacity *= t;
609
+ }
610
+ if (enterTransition.type === "WipeTransition") {
611
+ const opts = getWipeOptions(enterTransition.props);
612
+ const t = (0, import_remotion4.interpolate)(frame, [0, d], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
613
+ clipPath = clipFor(opts.direction, t);
614
+ filter = opts.softEdge ? "blur(0.4px)" : void 0;
615
+ }
616
+ if (enterTransition.type === "CircularReveal") {
617
+ const opts = getCircularOptions(enterTransition.props);
618
+ const open = opts.direction === "open";
619
+ const t = (0, import_remotion4.interpolate)(frame, [0, d], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
620
+ const radiusPct = open ? 150 * t : 150 * (1 - t);
621
+ clipPath = `circle(${radiusPct}% at ${Math.round(opts.center.x * 100)}% ${Math.round(opts.center.y * 100)}%)`;
622
+ }
623
+ }
624
+ if (exitTransition && durationInFrames > 0) {
625
+ const d = exitTransition.durationInFrames;
626
+ const start = Math.max(0, durationInFrames - d);
627
+ if (exitTransition.type === "FadeTransition") {
628
+ opacity *= (0, import_remotion4.interpolate)(frame, [start, durationInFrames], [1, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
629
+ }
630
+ if (exitTransition.type === "SlideTransition") {
631
+ const opts = getSlideOptions(exitTransition.props);
632
+ const t = (0, import_remotion4.interpolate)(frame, [start, durationInFrames], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
633
+ const delta = opts.distance * t;
634
+ if (opts.direction === "left") translateX += delta;
635
+ if (opts.direction === "right") translateX += -delta;
636
+ if (opts.direction === "up") translateY += delta;
637
+ if (opts.direction === "down") translateY += -delta;
638
+ opacity *= (0, import_remotion4.interpolate)(frame, [start, durationInFrames], [1, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
639
+ }
640
+ if (exitTransition.type === "ZoomTransition") {
641
+ const opts = getZoomOptions(exitTransition.props);
642
+ const toScale = opts.type === "zoomIn" ? 1.25 : 0.75;
643
+ const t = (0, import_remotion4.interpolate)(frame, [start, durationInFrames], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
644
+ scale *= 1 + (toScale - 1) * t;
645
+ opacity *= 1 - t;
646
+ }
647
+ if (exitTransition.type === "WipeTransition") {
648
+ const opts = getWipeOptions(exitTransition.props);
649
+ const t = (0, import_remotion4.interpolate)(frame, [start, durationInFrames], [1, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
650
+ clipPath = clipFor(opts.direction, t);
651
+ filter = opts.softEdge ? "blur(0.4px)" : void 0;
652
+ }
653
+ if (exitTransition.type === "CircularReveal") {
654
+ const opts = getCircularOptions(exitTransition.props);
655
+ const open = opts.direction === "open";
656
+ const t = (0, import_remotion4.interpolate)(frame, [start, durationInFrames], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
657
+ const radiusPct = open ? 150 * (1 - t) : 150 * t;
658
+ clipPath = `circle(${radiusPct}% at ${Math.round(opts.center.x * 100)}% ${Math.round(opts.center.y * 100)}%)`;
659
+ }
660
+ }
661
+ const transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
662
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_remotion4.AbsoluteFill, { style: { opacity, transform, clipPath, filter }, children });
663
+ };
664
+ var SegmentComponentMetadata = {
665
+ kind: "primitive",
666
+ category: "layout",
667
+ internal: true,
668
+ acceptsChildren: true,
669
+ minChildren: 1,
670
+ maxChildren: 1,
671
+ description: "Internal segment wrapper (used by v2 segments compiler)"
672
+ };
673
+
674
+ // src/components/primitives/Shape.tsx
675
+ var import_zod12 = require("zod");
676
+ var import_jsx_runtime11 = require("react/jsx-runtime");
677
+ var ShapePropsSchema = import_zod12.z.object({
678
+ shape: import_zod12.z.enum(["rect", "circle"]).default("rect"),
679
+ x: import_zod12.z.number().default(0),
680
+ y: import_zod12.z.number().default(0),
681
+ width: import_zod12.z.number().positive().default(100),
682
+ height: import_zod12.z.number().positive().default(100),
683
+ fill: import_zod12.z.string().default("#FFFFFF"),
684
+ strokeColor: import_zod12.z.string().optional(),
685
+ strokeWidth: import_zod12.z.number().min(0).default(0),
686
+ opacity: import_zod12.z.number().min(0).max(1).default(1)
687
+ });
688
+ var Shape = ({
689
+ shape,
690
+ x,
691
+ y,
692
+ width,
693
+ height,
694
+ fill,
695
+ strokeColor,
696
+ strokeWidth,
697
+ opacity
698
+ }) => {
699
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
700
+ "div",
701
+ {
702
+ style: {
703
+ position: "absolute",
704
+ left: x,
705
+ top: y,
706
+ width,
707
+ height,
708
+ backgroundColor: fill,
709
+ borderRadius: shape === "circle" ? 9999 : 0,
710
+ border: strokeWidth > 0 ? `${strokeWidth}px solid ${strokeColor ?? fill}` : void 0,
711
+ opacity
712
+ }
713
+ }
714
+ );
715
+ };
716
+ var ShapeComponentMetadata = {
717
+ kind: "primitive",
718
+ category: "layout",
719
+ description: "Simple rect/circle shape for UI accents",
720
+ llmGuidance: "Use Shape for lines, badges, and simple UI blocks. Use circle for dots and rings."
721
+ };
722
+
723
+ // src/components/primitives/Stack.tsx
724
+ var import_zod13 = require("zod");
725
+ var import_jsx_runtime12 = require("react/jsx-runtime");
726
+ var StackPropsSchema = import_zod13.z.object({
727
+ direction: import_zod13.z.enum(["row", "column"]).default("column"),
728
+ gap: import_zod13.z.number().min(0).default(24),
729
+ padding: import_zod13.z.number().min(0).default(0),
730
+ align: import_zod13.z.enum(["start", "center", "end", "stretch"]).default("center"),
731
+ justify: import_zod13.z.enum(["start", "center", "end", "between"]).default("center")
732
+ });
733
+ var mapAlign2 = (a) => {
734
+ if (a === "start") return "flex-start";
735
+ if (a === "end") return "flex-end";
736
+ if (a === "stretch") return "stretch";
737
+ return "center";
738
+ };
739
+ var mapJustify2 = (j) => {
740
+ if (j === "start") return "flex-start";
741
+ if (j === "end") return "flex-end";
742
+ if (j === "between") return "space-between";
743
+ return "center";
744
+ };
745
+ var Stack = ({ direction, gap, padding, align, justify, children }) => {
746
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
747
+ Fill,
748
+ {
749
+ style: {
750
+ display: "flex",
751
+ flexDirection: direction,
752
+ gap,
753
+ padding,
754
+ alignItems: mapAlign2(align),
755
+ justifyContent: mapJustify2(justify),
756
+ boxSizing: "border-box"
757
+ },
758
+ children
759
+ }
760
+ );
761
+ };
762
+ var StackComponentMetadata = {
763
+ kind: "primitive",
764
+ category: "layout",
765
+ acceptsChildren: true,
766
+ description: "Flexbox stack layout (row/column) with gap and alignment",
767
+ llmGuidance: "Use Stack to arrange child components in a row or column without manual positioning."
768
+ };
769
+
265
770
  // src/components/primitives/Text.tsx
266
- var import_remotion3 = require("remotion");
267
- var import_zod5 = require("zod");
268
- var import_jsx_runtime3 = require("react/jsx-runtime");
269
- var TextPropsSchema = import_zod5.z.object({
270
- content: import_zod5.z.string(),
271
- fontSize: import_zod5.z.number().default(48),
272
- color: import_zod5.z.string().default("#FFFFFF"),
273
- position: import_zod5.z.enum(["top", "center", "bottom", "left", "right"]).default("center"),
274
- animation: import_zod5.z.enum(["none", "fade", "slide", "zoom"]).default("fade")
771
+ var import_remotion5 = require("remotion");
772
+ var import_zod14 = require("zod");
773
+ var import_jsx_runtime13 = require("react/jsx-runtime");
774
+ var TextPropsSchema = import_zod14.z.object({
775
+ content: import_zod14.z.string(),
776
+ fontSize: import_zod14.z.number().default(48),
777
+ color: import_zod14.z.string().default("#FFFFFF"),
778
+ fontFamily: import_zod14.z.string().default("Inter"),
779
+ position: import_zod14.z.enum(["top", "center", "bottom", "left", "right"]).default("center"),
780
+ animation: import_zod14.z.enum(["none", "fade", "slide", "zoom"]).default("fade"),
781
+ maxWidthPct: import_zod14.z.number().min(0.1).max(1).default(0.9),
782
+ safeInsetPct: import_zod14.z.number().min(0).max(0.25).default(0.06),
783
+ textAlign: import_zod14.z.enum(["left", "center", "right"]).default("center")
275
784
  });
276
- var getPositionStyles = (position) => {
785
+ var getPositionStyles = (position, safeInsetPct) => {
786
+ const inset = `${(safeInsetPct * 100).toFixed(2)}%`;
277
787
  const base = {
278
788
  display: "flex",
279
789
  justifyContent: "center",
@@ -281,95 +791,2530 @@ var getPositionStyles = (position) => {
281
791
  };
282
792
  switch (position) {
283
793
  case "top":
284
- return { ...base, alignItems: "flex-start", paddingTop: 60 };
794
+ return { ...base, alignItems: "flex-start", paddingTop: inset };
285
795
  case "bottom":
286
- return { ...base, alignItems: "flex-end", paddingBottom: 60 };
796
+ return { ...base, alignItems: "flex-end", paddingBottom: inset };
287
797
  case "left":
288
- return { ...base, justifyContent: "flex-start", paddingLeft: 60 };
798
+ return { ...base, justifyContent: "flex-start", paddingLeft: inset };
289
799
  case "right":
290
- return { ...base, justifyContent: "flex-end", paddingRight: 60 };
800
+ return { ...base, justifyContent: "flex-end", paddingRight: inset };
291
801
  default:
292
802
  return base;
293
803
  }
294
- };
295
- var getAnimationStyle = (frame, animation) => {
296
- const animDuration = 30;
297
- switch (animation) {
298
- case "fade": {
299
- const opacity = (0, import_remotion3.interpolate)(frame, [0, animDuration], [0, 1], {
300
- extrapolateLeft: "clamp",
301
- extrapolateRight: "clamp"
302
- });
303
- return { opacity };
304
- }
305
- case "slide": {
306
- const translateY = (0, import_remotion3.interpolate)(frame, [0, animDuration], [50, 0], {
307
- extrapolateLeft: "clamp",
308
- extrapolateRight: "clamp"
309
- });
310
- const opacity = (0, import_remotion3.interpolate)(frame, [0, animDuration], [0, 1], {
311
- extrapolateLeft: "clamp",
312
- extrapolateRight: "clamp"
313
- });
314
- return { transform: `translateY(${translateY}px)`, opacity };
315
- }
316
- case "zoom": {
317
- const scale = (0, import_remotion3.interpolate)(frame, [0, animDuration], [0.8, 1], {
318
- extrapolateLeft: "clamp",
319
- extrapolateRight: "clamp"
320
- });
321
- const opacity = (0, import_remotion3.interpolate)(frame, [0, animDuration], [0, 1], {
322
- extrapolateLeft: "clamp",
323
- extrapolateRight: "clamp"
324
- });
325
- return { transform: `scale(${scale})`, opacity };
326
- }
327
- default:
328
- return {};
804
+ };
805
+ var getAnimationStyle = (frame, animation) => {
806
+ const animDuration = 30;
807
+ switch (animation) {
808
+ case "fade": {
809
+ const opacity = (0, import_remotion5.interpolate)(frame, [0, animDuration], [0, 1], {
810
+ extrapolateLeft: "clamp",
811
+ extrapolateRight: "clamp"
812
+ });
813
+ return { opacity };
814
+ }
815
+ case "slide": {
816
+ const translateY = (0, import_remotion5.interpolate)(frame, [0, animDuration], [50, 0], {
817
+ extrapolateLeft: "clamp",
818
+ extrapolateRight: "clamp"
819
+ });
820
+ const opacity = (0, import_remotion5.interpolate)(frame, [0, animDuration], [0, 1], {
821
+ extrapolateLeft: "clamp",
822
+ extrapolateRight: "clamp"
823
+ });
824
+ return { transform: `translateY(${translateY}px)`, opacity };
825
+ }
826
+ case "zoom": {
827
+ const scale = (0, import_remotion5.interpolate)(frame, [0, animDuration], [0.8, 1], {
828
+ extrapolateLeft: "clamp",
829
+ extrapolateRight: "clamp"
830
+ });
831
+ const opacity = (0, import_remotion5.interpolate)(frame, [0, animDuration], [0, 1], {
832
+ extrapolateLeft: "clamp",
833
+ extrapolateRight: "clamp"
834
+ });
835
+ return { transform: `scale(${scale})`, opacity };
836
+ }
837
+ default:
838
+ return {};
839
+ }
840
+ };
841
+ var Text = ({
842
+ content,
843
+ fontSize,
844
+ color,
845
+ fontFamily,
846
+ position,
847
+ animation,
848
+ maxWidthPct,
849
+ safeInsetPct,
850
+ textAlign
851
+ }) => {
852
+ const frame = (0, import_remotion5.useCurrentFrame)();
853
+ const positionStyles6 = getPositionStyles(position, safeInsetPct);
854
+ const animationStyles = getAnimationStyle(frame, animation);
855
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Fill, { style: positionStyles6, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
856
+ "div",
857
+ {
858
+ style: {
859
+ fontSize,
860
+ color,
861
+ fontFamily,
862
+ fontWeight: 600,
863
+ textAlign,
864
+ maxWidth: `${Math.round(maxWidthPct * 100)}%`,
865
+ overflowWrap: "anywhere",
866
+ wordBreak: "break-word",
867
+ ...animationStyles
868
+ },
869
+ children: content
870
+ }
871
+ ) });
872
+ };
873
+ var TextComponentMetadata = {
874
+ kind: "primitive",
875
+ category: "text",
876
+ description: "Displays animated text with positioning and animation options",
877
+ llmGuidance: 'Use for titles, subtitles, captions. Keep content under 100 characters for readability. Position "center" works best for titles.',
878
+ examples: [
879
+ {
880
+ content: "Welcome to Waves",
881
+ fontSize: 72,
882
+ color: "#FFFFFF",
883
+ position: "center",
884
+ animation: "fade"
885
+ },
886
+ {
887
+ content: "Building the future of video",
888
+ fontSize: 36,
889
+ color: "#CCCCCC",
890
+ position: "bottom",
891
+ animation: "slide"
892
+ }
893
+ ]
894
+ };
895
+
896
+ // src/components/primitives/Video.tsx
897
+ var import_remotion6 = require("remotion");
898
+ var import_zod15 = require("zod");
899
+ var import_jsx_runtime14 = require("react/jsx-runtime");
900
+ var VideoPropsSchema = import_zod15.z.object({
901
+ src: import_zod15.z.string().min(1),
902
+ fit: import_zod15.z.enum(["cover", "contain"]).default("cover"),
903
+ borderRadius: import_zod15.z.number().min(0).default(0),
904
+ opacity: import_zod15.z.number().min(0).max(1).default(1),
905
+ muted: import_zod15.z.boolean().default(true)
906
+ });
907
+ var resolveAsset3 = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion6.staticFile)(staticFileInputFromAssetPath(value));
908
+ var Video2 = ({ src, fit, borderRadius, opacity, muted }) => {
909
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Fill, { style: { opacity }, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
910
+ import_remotion6.Video,
911
+ {
912
+ src: resolveAsset3(src),
913
+ muted,
914
+ style: { width: "100%", height: "100%", objectFit: fit, borderRadius }
915
+ }
916
+ ) });
917
+ };
918
+ var VideoComponentMetadata = {
919
+ kind: "primitive",
920
+ category: "media",
921
+ description: "Full-frame video with object-fit options",
922
+ llmGuidance: "Use Video for B-roll. Keep videos short and muted unless you intentionally want audio."
923
+ };
924
+
925
+ // src/components/composites/AnimatedCounter.tsx
926
+ var import_remotion7 = require("remotion");
927
+ var import_zod16 = require("zod");
928
+ var import_jsx_runtime15 = require("react/jsx-runtime");
929
+ var AnimatedCounterPropsSchema = import_zod16.z.object({
930
+ from: import_zod16.z.number().default(0),
931
+ to: import_zod16.z.number().default(100),
932
+ fontSize: import_zod16.z.number().min(8).max(300).default(96),
933
+ color: import_zod16.z.string().default("#FFFFFF"),
934
+ fontFamily: import_zod16.z.string().default("Inter"),
935
+ fontWeight: import_zod16.z.number().int().min(100).max(900).default(700),
936
+ icon: import_zod16.z.string().optional(),
937
+ suffix: import_zod16.z.string().optional(),
938
+ animationType: import_zod16.z.enum(["spring", "linear"]).default("spring")
939
+ });
940
+ var AnimatedCounter = ({
941
+ from,
942
+ to,
943
+ fontSize,
944
+ color,
945
+ fontFamily,
946
+ fontWeight,
947
+ icon,
948
+ suffix,
949
+ animationType,
950
+ __wavesDurationInFrames
951
+ }) => {
952
+ const frame = (0, import_remotion7.useCurrentFrame)();
953
+ const { fps } = (0, import_remotion7.useVideoConfig)();
954
+ const total = Math.max(1, __wavesDurationInFrames ?? 1);
955
+ const progress = animationType === "spring" ? (0, import_remotion7.spring)({ frame, fps, config: { damping: 14, stiffness: 110, mass: 0.9 } }) : (0, import_remotion7.interpolate)(frame, [0, total], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
956
+ const value = from + (to - from) * Math.max(0, Math.min(1, progress));
957
+ const rounded = Math.round(value);
958
+ const label = `${rounded.toLocaleString()}${suffix ?? ""}`;
959
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Fill, { style: { display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { textAlign: "center" }, children: [
960
+ icon ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { fontSize: Math.round(fontSize * 0.5), marginBottom: 18 }, children: icon }) : null,
961
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { fontSize, color, fontFamily, fontWeight, lineHeight: 1 }, children: label })
962
+ ] }) });
963
+ };
964
+ var AnimatedCounterComponentMetadata = {
965
+ kind: "composite",
966
+ category: "data",
967
+ description: "Animated numeric counter (spring or linear), optionally with an icon and suffix",
968
+ llmGuidance: 'Use for big stats. animationType="spring" feels natural. suffix for units (%, K, M).',
969
+ examples: [
970
+ { from: 0, to: 100, suffix: "%", icon: "\u{1F4C8}" },
971
+ { from: 0, to: 25e3, suffix: "+", animationType: "linear" }
972
+ ]
973
+ };
974
+
975
+ // src/components/composites/BarChart.tsx
976
+ var import_remotion8 = require("remotion");
977
+ var import_zod17 = require("zod");
978
+ var import_jsx_runtime16 = require("react/jsx-runtime");
979
+ var BarChartPropsSchema = import_zod17.z.object({
980
+ data: import_zod17.z.array(import_zod17.z.object({
981
+ label: import_zod17.z.string().min(1),
982
+ value: import_zod17.z.number(),
983
+ color: import_zod17.z.string().optional()
984
+ })).min(2).max(8),
985
+ orientation: import_zod17.z.enum(["horizontal", "vertical"]).default("vertical"),
986
+ showValues: import_zod17.z.boolean().default(true),
987
+ showGrid: import_zod17.z.boolean().default(false),
988
+ maxValue: import_zod17.z.number().optional()
989
+ });
990
+ var BarChart = ({ data, orientation, showValues, showGrid, maxValue }) => {
991
+ const frame = (0, import_remotion8.useCurrentFrame)();
992
+ const { fps } = (0, import_remotion8.useVideoConfig)();
993
+ const max = maxValue ?? Math.max(...data.map((d) => d.value));
994
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(Fill, { style: { padding: 90, boxSizing: "border-box" }, children: [
995
+ showGrid ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { position: "absolute", left: 90, right: 90, top: 90, bottom: 90, opacity: 0.15, backgroundImage: "linear-gradient(#fff 1px, transparent 1px)", backgroundSize: "100% 60px" } }) : null,
996
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
997
+ "div",
998
+ {
999
+ style: {
1000
+ display: "flex",
1001
+ flexDirection: orientation === "vertical" ? "row" : "column",
1002
+ gap: 24,
1003
+ width: "100%",
1004
+ height: "100%",
1005
+ alignItems: orientation === "vertical" ? "flex-end" : "stretch",
1006
+ justifyContent: "space-between"
1007
+ },
1008
+ children: data.map((d, i) => {
1009
+ const p = (0, import_remotion8.spring)({ frame: frame - i * 4, fps, config: { damping: 14, stiffness: 120, mass: 0.9 } });
1010
+ const ratio = max === 0 ? 0 : d.value / max * p;
1011
+ const color = d.color ?? "#0A84FF";
1012
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 10 }, children: [
1013
+ orientation === "vertical" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { width: "100%", flex: 1, display: "flex", alignItems: "flex-end" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { width: "100%", height: `${Math.round(ratio * 100)}%`, backgroundColor: color, borderRadius: 12 } }) }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { width: "100%", display: "flex", alignItems: "center", gap: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { flex: 1, height: 18, backgroundColor: "rgba(255,255,255,0.15)", borderRadius: 9999, overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { width: `${Math.round(ratio * 100)}%`, height: "100%", backgroundColor: color } }) }) }),
1014
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { color: "#FFFFFF", fontWeight: 700, fontSize: 22, opacity: 0.9 }, children: d.label }),
1015
+ showValues ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { color: "#FFFFFF", fontWeight: 800, fontSize: 26 }, children: Math.round(d.value).toLocaleString() }) : null
1016
+ ] }, d.label);
1017
+ })
1018
+ }
1019
+ )
1020
+ ] });
1021
+ };
1022
+ var BarChartComponentMetadata = {
1023
+ kind: "composite",
1024
+ category: "data",
1025
+ description: "Animated bar chart (vertical or horizontal)",
1026
+ llmGuidance: "Use 2-6 bars. Provide maxValue to lock scale across multiple charts."
1027
+ };
1028
+
1029
+ // src/components/composites/CardStack.tsx
1030
+ var import_remotion9 = require("remotion");
1031
+ var import_zod18 = require("zod");
1032
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1033
+ var CardSchema = import_zod18.z.object({
1034
+ title: import_zod18.z.string().min(1),
1035
+ content: import_zod18.z.string().min(1),
1036
+ backgroundColor: import_zod18.z.string().optional()
1037
+ });
1038
+ var CardStackPropsSchema = import_zod18.z.object({
1039
+ cards: import_zod18.z.array(CardSchema).min(2).max(5),
1040
+ transition: import_zod18.z.enum(["flip", "slide", "fade"]).default("flip"),
1041
+ displayDuration: import_zod18.z.number().int().min(30).max(150).default(90)
1042
+ });
1043
+ var CardStack = ({ cards, transition, displayDuration }) => {
1044
+ const frame = (0, import_remotion9.useCurrentFrame)();
1045
+ const { fps } = (0, import_remotion9.useVideoConfig)();
1046
+ const index = Math.min(cards.length - 1, Math.floor(frame / displayDuration));
1047
+ const local = frame - index * displayDuration;
1048
+ const p = (0, import_remotion9.spring)({ frame: local, fps, config: { damping: 14, stiffness: 120, mass: 0.9 } });
1049
+ const card = cards[index];
1050
+ const bg = card.backgroundColor ?? "rgba(255,255,255,0.1)";
1051
+ const opacity = transition === "fade" ? p : 1;
1052
+ const slideX = transition === "slide" ? (0, import_remotion9.interpolate)(p, [0, 1], [80, 0]) : 0;
1053
+ const rotateY = transition === "flip" ? (0, import_remotion9.interpolate)(p, [0, 1], [70, 0]) : 0;
1054
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Fill, { style: { display: "flex", justifyContent: "center", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1055
+ "div",
1056
+ {
1057
+ style: {
1058
+ width: 980,
1059
+ height: 520,
1060
+ borderRadius: 28,
1061
+ padding: 60,
1062
+ boxSizing: "border-box",
1063
+ backgroundColor: bg,
1064
+ boxShadow: "0 30px 90px rgba(0,0,0,0.35)",
1065
+ color: "#FFFFFF",
1066
+ transform: `translateX(${slideX}px) rotateY(${rotateY}deg)`,
1067
+ transformStyle: "preserve-3d",
1068
+ opacity
1069
+ },
1070
+ children: [
1071
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: { fontSize: 56, fontWeight: 900, marginBottom: 22 }, children: card.title }),
1072
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: { fontSize: 30, fontWeight: 700, opacity: 0.9, lineHeight: 1.3 }, children: card.content })
1073
+ ]
1074
+ }
1075
+ ) });
1076
+ };
1077
+ var CardStackComponentMetadata = {
1078
+ kind: "composite",
1079
+ category: "layout",
1080
+ description: "Sequential stacked cards (2-5) with flip/slide/fade transitions",
1081
+ llmGuidance: "Use for steps/features. displayDuration is frames per card."
1082
+ };
1083
+
1084
+ // src/components/composites/CircularReveal.tsx
1085
+ var import_react5 = __toESM(require("react"));
1086
+ var import_remotion10 = require("remotion");
1087
+ var import_zod19 = require("zod");
1088
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1089
+ var CenterSchema = import_zod19.z.object({
1090
+ x: import_zod19.z.number().min(0).max(1).default(0.5),
1091
+ y: import_zod19.z.number().min(0).max(1).default(0.5)
1092
+ });
1093
+ var CircularRevealPropsSchema = import_zod19.z.object({
1094
+ durationInFrames: import_zod19.z.number().int().min(10).max(60).default(30),
1095
+ direction: import_zod19.z.enum(["open", "close"]).default("open"),
1096
+ center: CenterSchema.optional(),
1097
+ phase: import_zod19.z.enum(["in", "out", "inOut"]).default("inOut")
1098
+ });
1099
+ var CircularReveal = ({
1100
+ durationInFrames,
1101
+ direction,
1102
+ center,
1103
+ phase,
1104
+ children,
1105
+ __wavesDurationInFrames
1106
+ }) => {
1107
+ const layers = import_react5.default.Children.toArray(children);
1108
+ const frame = (0, import_remotion10.useCurrentFrame)();
1109
+ const total = Math.max(1, __wavesDurationInFrames ?? 1);
1110
+ const d = Math.min(durationInFrames, total);
1111
+ const c = center ?? { x: 0.5, y: 0.5 };
1112
+ const open = direction === "open";
1113
+ let radiusPct = open ? 0 : 150;
1114
+ if (phase === "in" || phase === "inOut") {
1115
+ const t = (0, import_remotion10.interpolate)(frame, [0, d], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
1116
+ radiusPct = open ? 150 * t : 150 * (1 - t);
1117
+ }
1118
+ if (phase === "out" || phase === "inOut") {
1119
+ const start = Math.max(0, total - d);
1120
+ const t = (0, import_remotion10.interpolate)(frame, [start, total], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
1121
+ radiusPct = open ? 150 * (1 - t) : 150 * t;
1122
+ }
1123
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Fill, { style: { clipPath: `circle(${radiusPct}% at ${Math.round(c.x * 100)}% ${Math.round(c.y * 100)}%)` }, children: layers.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { position: "absolute", inset: 0 }, children: child }, i)) });
1124
+ };
1125
+ var CircularRevealComponentMetadata = {
1126
+ kind: "composite",
1127
+ category: "transition",
1128
+ acceptsChildren: true,
1129
+ minChildren: 1,
1130
+ description: "Circular iris reveal/hide transition wrapper",
1131
+ llmGuidance: 'direction="open" reveals from center, direction="close" hides to a point. center controls origin.'
1132
+ };
1133
+
1134
+ // src/components/composites/CountUpText.tsx
1135
+ var import_remotion11 = require("remotion");
1136
+ var import_zod20 = require("zod");
1137
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1138
+ var CountUpTextPropsSchema = import_zod20.z.object({
1139
+ from: import_zod20.z.number().default(0),
1140
+ to: import_zod20.z.number().default(100),
1141
+ fontSize: import_zod20.z.number().min(8).max(240).default(72),
1142
+ color: import_zod20.z.string().default("#FFFFFF"),
1143
+ fontFamily: import_zod20.z.string().default("Inter"),
1144
+ fontWeight: import_zod20.z.number().int().min(100).max(900).default(700),
1145
+ position: import_zod20.z.enum(["top", "center", "bottom"]).default("center"),
1146
+ format: import_zod20.z.enum(["integer", "decimal", "currency", "percentage"]).default("integer"),
1147
+ decimals: import_zod20.z.number().int().min(0).max(4).default(0),
1148
+ prefix: import_zod20.z.string().optional(),
1149
+ suffix: import_zod20.z.string().optional()
1150
+ });
1151
+ var positionStyles = (position) => {
1152
+ if (position === "top") return { justifyContent: "center", alignItems: "flex-start", paddingTop: 90 };
1153
+ if (position === "bottom") return { justifyContent: "center", alignItems: "flex-end", paddingBottom: 90 };
1154
+ return { justifyContent: "center", alignItems: "center" };
1155
+ };
1156
+ function formatValue(v, format, decimals) {
1157
+ if (format === "integer") return Math.round(v).toLocaleString();
1158
+ if (format === "decimal") return v.toFixed(decimals);
1159
+ if (format === "currency") return `$${v.toFixed(decimals).toLocaleString()}`;
1160
+ return `${(v * 100).toFixed(decimals)}%`;
1161
+ }
1162
+ var CountUpText = ({
1163
+ from,
1164
+ to,
1165
+ fontSize,
1166
+ color,
1167
+ fontFamily,
1168
+ fontWeight,
1169
+ position,
1170
+ format,
1171
+ decimals,
1172
+ prefix,
1173
+ suffix,
1174
+ __wavesDurationInFrames
1175
+ }) => {
1176
+ const frame = (0, import_remotion11.useCurrentFrame)();
1177
+ const { fps } = (0, import_remotion11.useVideoConfig)();
1178
+ const total = Math.max(1, __wavesDurationInFrames ?? 1);
1179
+ const p = (0, import_remotion11.spring)({ frame, fps, config: { damping: 14, stiffness: 110, mass: 0.9 } });
1180
+ const progress = Math.max(0, Math.min(1, p));
1181
+ const value = from + (to - from) * progress;
1182
+ const fade = (0, import_remotion11.interpolate)(frame, [0, Math.min(12, total)], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
1183
+ const label = `${prefix ?? ""}${formatValue(value, format, decimals)}${suffix ?? ""}`;
1184
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(Fill, { style: { display: "flex", ...positionStyles(position) }, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { style: { fontSize, color, fontFamily, fontWeight, opacity: fade, lineHeight: 1 }, children: label }) });
1185
+ };
1186
+ var CountUpTextComponentMetadata = {
1187
+ kind: "composite",
1188
+ category: "text",
1189
+ description: "Counts from a start value to an end value with formatting options",
1190
+ llmGuidance: 'Use for metrics. format="currency" adds $, format="percentage" multiplies by 100 and adds %.'
1191
+ };
1192
+
1193
+ // src/components/composites/FadeTransition.tsx
1194
+ var import_react6 = __toESM(require("react"));
1195
+ var import_remotion12 = require("remotion");
1196
+ var import_zod21 = require("zod");
1197
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1198
+ var EasingSchema = import_zod21.z.enum(["linear", "easeIn", "easeOut", "easeInOut"]);
1199
+ function ease(t, easing) {
1200
+ const x = Math.max(0, Math.min(1, t));
1201
+ if (easing === "linear") return x;
1202
+ if (easing === "easeIn") return x * x;
1203
+ if (easing === "easeOut") return 1 - (1 - x) * (1 - x);
1204
+ return x < 0.5 ? 2 * x * x : 1 - 2 * (1 - x) * (1 - x);
1205
+ }
1206
+ var FadeTransitionPropsSchema = import_zod21.z.object({
1207
+ durationInFrames: import_zod21.z.number().int().min(10).max(60).default(30),
1208
+ easing: EasingSchema.default("easeInOut"),
1209
+ phase: import_zod21.z.enum(["in", "out", "inOut"]).default("inOut")
1210
+ });
1211
+ var FadeTransition = ({
1212
+ durationInFrames,
1213
+ easing,
1214
+ phase,
1215
+ children,
1216
+ __wavesDurationInFrames
1217
+ }) => {
1218
+ const layers = import_react6.default.Children.toArray(children);
1219
+ const frame = (0, import_remotion12.useCurrentFrame)();
1220
+ const total = Math.max(1, __wavesDurationInFrames ?? 1);
1221
+ const d = Math.min(durationInFrames, total);
1222
+ let opacity = 1;
1223
+ if (phase === "in" || phase === "inOut") {
1224
+ const t = (0, import_remotion12.interpolate)(frame, [0, d], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
1225
+ opacity *= ease(t, easing);
1226
+ }
1227
+ if (phase === "out" || phase === "inOut") {
1228
+ const start = Math.max(0, total - d);
1229
+ const t = (0, import_remotion12.interpolate)(frame, [start, total], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
1230
+ opacity *= 1 - ease(t, easing);
1231
+ }
1232
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(Fill, { style: { opacity }, children: layers.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { position: "absolute", inset: 0 }, children: child }, i)) });
1233
+ };
1234
+ var FadeTransitionComponentMetadata = {
1235
+ kind: "composite",
1236
+ category: "transition",
1237
+ acceptsChildren: true,
1238
+ minChildren: 1,
1239
+ description: "Fade in/out wrapper (used for segment transitions and overlays)",
1240
+ llmGuidance: "Use for gentle transitions. durationInFrames ~30 is standard."
1241
+ };
1242
+
1243
+ // src/components/composites/GlitchText.tsx
1244
+ var import_remotion13 = require("remotion");
1245
+ var import_zod22 = require("zod");
1246
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1247
+ var GlitchTextPropsSchema = import_zod22.z.object({
1248
+ content: import_zod22.z.string().max(100),
1249
+ fontSize: import_zod22.z.number().min(8).max(240).default(72),
1250
+ color: import_zod22.z.string().default("#FFFFFF"),
1251
+ fontFamily: import_zod22.z.string().default("monospace"),
1252
+ position: import_zod22.z.enum(["top", "center", "bottom"]).default("center"),
1253
+ intensity: import_zod22.z.number().int().min(1).max(10).default(5),
1254
+ glitchDuration: import_zod22.z.number().int().min(5).max(30).default(10)
1255
+ });
1256
+ var positionStyles2 = (position) => {
1257
+ if (position === "top") return { justifyContent: "center", alignItems: "flex-start", paddingTop: 90 };
1258
+ if (position === "bottom") return { justifyContent: "center", alignItems: "flex-end", paddingBottom: 90 };
1259
+ return { justifyContent: "center", alignItems: "center" };
1260
+ };
1261
+ function pseudoRandom(n) {
1262
+ const x = Math.sin(n * 12.9898) * 43758.5453;
1263
+ return x - Math.floor(x);
1264
+ }
1265
+ var GlitchText = ({
1266
+ content,
1267
+ fontSize,
1268
+ color,
1269
+ fontFamily,
1270
+ position,
1271
+ intensity,
1272
+ glitchDuration
1273
+ }) => {
1274
+ const frame = (0, import_remotion13.useCurrentFrame)();
1275
+ const active = frame < glitchDuration;
1276
+ const seed = frame + 1;
1277
+ const jitter = active ? (pseudoRandom(seed) - 0.5) * intensity * 6 : 0;
1278
+ const jitterY = active ? (pseudoRandom(seed + 99) - 0.5) * intensity * 3 : 0;
1279
+ const baseStyle = {
1280
+ position: "absolute",
1281
+ fontSize,
1282
+ fontFamily,
1283
+ fontWeight: 800,
1284
+ letterSpacing: 1,
1285
+ textTransform: "uppercase",
1286
+ textAlign: "center"
1287
+ };
1288
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Fill, { style: { display: "flex", ...positionStyles2(position) }, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { position: "relative" }, children: [
1289
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { ...baseStyle, color, transform: `translate(${jitter}px, ${jitterY}px)` }, children: content }),
1290
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { ...baseStyle, color: "#FF3B30", transform: `translate(${jitter + 4}px, ${jitterY}px)`, mixBlendMode: "screen", opacity: active ? 0.9 : 0 }, children: content }),
1291
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { ...baseStyle, color: "#0A84FF", transform: `translate(${jitter - 4}px, ${jitterY}px)`, mixBlendMode: "screen", opacity: active ? 0.9 : 0 }, children: content })
1292
+ ] }) });
1293
+ };
1294
+ var GlitchTextComponentMetadata = {
1295
+ kind: "composite",
1296
+ category: "text",
1297
+ description: "Cyberpunk-style glitch text with RGB split jitter",
1298
+ llmGuidance: "Use for tech/error moments. intensity 1-3 subtle, 7-10 extreme. glitchDuration is frames at start."
1299
+ };
1300
+
1301
+ // src/components/composites/GridLayout.tsx
1302
+ var import_zod23 = require("zod");
1303
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1304
+ var GridLayoutPropsSchema = import_zod23.z.object({
1305
+ columns: import_zod23.z.number().int().min(1).max(4).default(2),
1306
+ rows: import_zod23.z.number().int().min(1).max(4).default(2),
1307
+ gap: import_zod23.z.number().min(0).max(50).default(20),
1308
+ padding: import_zod23.z.number().min(0).max(100).default(40)
1309
+ });
1310
+ var GridLayout = ({ columns, rows, gap, padding, children }) => {
1311
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Fill, { style: { padding, boxSizing: "border-box" }, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1312
+ "div",
1313
+ {
1314
+ style: {
1315
+ display: "grid",
1316
+ gridTemplateColumns: `repeat(${columns}, 1fr)`,
1317
+ gridTemplateRows: `repeat(${rows}, 1fr)`,
1318
+ gap,
1319
+ width: "100%",
1320
+ height: "100%"
1321
+ },
1322
+ children
1323
+ }
1324
+ ) });
1325
+ };
1326
+ var GridLayoutComponentMetadata = {
1327
+ kind: "composite",
1328
+ category: "layout",
1329
+ acceptsChildren: true,
1330
+ minChildren: 1,
1331
+ description: "Simple responsive grid layout for child components",
1332
+ llmGuidance: "Use for dashboards and collages. 2x2 is a good default for 4 items."
1333
+ };
1334
+
1335
+ // src/components/composites/ImageCollage.tsx
1336
+ var import_remotion14 = require("remotion");
1337
+ var import_zod24 = require("zod");
1338
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1339
+ var ImageCollagePropsSchema = import_zod24.z.object({
1340
+ images: import_zod24.z.array(import_zod24.z.object({ src: import_zod24.z.string().min(1), caption: import_zod24.z.string().optional() })).min(2).max(9),
1341
+ layout: import_zod24.z.enum(["grid", "stack", "scatter"]).default("grid"),
1342
+ stagger: import_zod24.z.number().int().min(2).max(10).default(5)
1343
+ });
1344
+ var resolveAsset4 = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion14.staticFile)(staticFileInputFromAssetPath(value));
1345
+ var ImageCollage = ({ images, layout, stagger }) => {
1346
+ const frame = (0, import_remotion14.useCurrentFrame)();
1347
+ const { fps } = (0, import_remotion14.useVideoConfig)();
1348
+ const n = images.length;
1349
+ const cols = Math.ceil(Math.sqrt(n));
1350
+ const rows = Math.ceil(n / cols);
1351
+ if (layout === "grid") {
1352
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Fill, { style: { padding: 80, boxSizing: "border-box" }, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1353
+ "div",
1354
+ {
1355
+ style: {
1356
+ display: "grid",
1357
+ gridTemplateColumns: `repeat(${cols}, 1fr)`,
1358
+ gridTemplateRows: `repeat(${rows}, 1fr)`,
1359
+ gap: 24,
1360
+ width: "100%",
1361
+ height: "100%"
1362
+ },
1363
+ children: images.map((img, i) => {
1364
+ const p = (0, import_remotion14.spring)({ frame: frame - i * stagger, fps, config: { damping: 14, stiffness: 120, mass: 0.9 } });
1365
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { position: "relative", overflow: "hidden", borderRadius: 18, opacity: p, transform: `scale(${0.92 + 0.08 * p})` }, children: [
1366
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_remotion14.Img, { src: resolveAsset4(img.src), style: { width: "100%", height: "100%", objectFit: "cover" } }),
1367
+ img.caption ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { position: "absolute", left: 0, right: 0, bottom: 0, padding: 12, background: "linear-gradient(transparent, rgba(0,0,0,0.7))", color: "#fff", fontWeight: 700 }, children: img.caption }) : null
1368
+ ] }, i);
1369
+ })
1370
+ }
1371
+ ) });
1372
+ }
1373
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Fill, { children: images.map((img, i) => {
1374
+ const p = (0, import_remotion14.spring)({ frame: frame - i * stagger, fps, config: { damping: 14, stiffness: 120, mass: 0.9 } });
1375
+ const baseRotate = layout === "stack" ? (i - (n - 1) / 2) * 4 : i * 17 % 20 - 10;
1376
+ const baseX = layout === "scatter" ? i * 137 % 300 - 150 : 0;
1377
+ const baseY = layout === "scatter" ? (i + 3) * 89 % 200 - 100 : 0;
1378
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1379
+ "div",
1380
+ {
1381
+ style: {
1382
+ position: "absolute",
1383
+ left: "50%",
1384
+ top: "50%",
1385
+ width: 520,
1386
+ height: 360,
1387
+ transform: `translate(-50%, -50%) translate(${baseX}px, ${baseY}px) rotate(${baseRotate}deg) scale(${0.85 + 0.15 * p})`,
1388
+ opacity: p,
1389
+ borderRadius: 18,
1390
+ overflow: "hidden",
1391
+ boxShadow: "0 20px 60px rgba(0,0,0,0.35)"
1392
+ },
1393
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_remotion14.Img, { src: resolveAsset4(img.src), style: { width: "100%", height: "100%", objectFit: "cover" } })
1394
+ },
1395
+ i
1396
+ );
1397
+ }) });
1398
+ };
1399
+ var ImageCollageComponentMetadata = {
1400
+ kind: "composite",
1401
+ category: "image",
1402
+ description: "Collage of multiple images in a grid/stack/scatter layout with staggered entrances",
1403
+ llmGuidance: 'Use 2-6 images for best results. layout="grid" is clean; "scatter" is energetic.'
1404
+ };
1405
+
1406
+ // src/components/composites/ImageReveal.tsx
1407
+ var import_remotion15 = require("remotion");
1408
+ var import_zod25 = require("zod");
1409
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1410
+ var ImageRevealPropsSchema = import_zod25.z.object({
1411
+ src: import_zod25.z.string().min(1),
1412
+ direction: import_zod25.z.enum(["left", "right", "top", "bottom", "center"]).default("left"),
1413
+ revealType: import_zod25.z.enum(["wipe", "expand", "iris"]).default("wipe")
1414
+ });
1415
+ var resolveAsset5 = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion15.staticFile)(staticFileInputFromAssetPath(value));
1416
+ var ImageReveal = ({ src, direction, revealType, __wavesDurationInFrames }) => {
1417
+ const frame = (0, import_remotion15.useCurrentFrame)();
1418
+ const total = Math.max(1, __wavesDurationInFrames ?? 1);
1419
+ const d = Math.min(30, total);
1420
+ const p = (0, import_remotion15.interpolate)(frame, [0, d], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
1421
+ let clipPath;
1422
+ let transform = "none";
1423
+ let transformOrigin = "center center";
1424
+ if (revealType === "wipe") {
1425
+ if (direction === "left") clipPath = `inset(0 ${100 * (1 - p)}% 0 0)`;
1426
+ if (direction === "right") clipPath = `inset(0 0 0 ${100 * (1 - p)}%)`;
1427
+ if (direction === "top") clipPath = `inset(0 0 ${100 * (1 - p)}% 0)`;
1428
+ if (direction === "bottom") clipPath = `inset(${100 * (1 - p)}% 0 0 0)`;
1429
+ if (direction === "center") clipPath = `inset(${50 * (1 - p)}% ${50 * (1 - p)}% ${50 * (1 - p)}% ${50 * (1 - p)}%)`;
1430
+ }
1431
+ if (revealType === "expand") {
1432
+ const s = 0.85 + 0.15 * p;
1433
+ transform = `scale(${s})`;
1434
+ if (direction === "left") transformOrigin = "left center";
1435
+ if (direction === "right") transformOrigin = "right center";
1436
+ if (direction === "top") transformOrigin = "center top";
1437
+ if (direction === "bottom") transformOrigin = "center bottom";
1438
+ }
1439
+ if (revealType === "iris") {
1440
+ clipPath = `circle(${Math.round(150 * p)}% at 50% 50%)`;
1441
+ }
1442
+ const opacity = revealType === "expand" ? p : 1;
1443
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Fill, { children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1444
+ import_remotion15.Img,
1445
+ {
1446
+ src: resolveAsset5(src),
1447
+ style: {
1448
+ width: "100%",
1449
+ height: "100%",
1450
+ objectFit: "cover",
1451
+ clipPath,
1452
+ transform,
1453
+ transformOrigin,
1454
+ opacity
1455
+ }
1456
+ }
1457
+ ) });
1458
+ };
1459
+ var ImageRevealComponentMetadata = {
1460
+ kind: "composite",
1461
+ category: "image",
1462
+ description: "Reveals an image with wipe/expand/iris entrance effects",
1463
+ llmGuidance: "Use wipe for directional reveals, expand for subtle pop-in, iris for circular mask openings."
1464
+ };
1465
+
1466
+ // src/components/composites/ImageSequence.tsx
1467
+ var import_remotion16 = require("remotion");
1468
+ var import_zod26 = require("zod");
1469
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1470
+ var ImageSequencePropsSchema = import_zod26.z.object({
1471
+ basePath: import_zod26.z.string().min(1),
1472
+ frameCount: import_zod26.z.number().int().positive(),
1473
+ filePattern: import_zod26.z.string().default("frame_{frame}.png"),
1474
+ fps: import_zod26.z.number().int().min(1).max(120).default(30)
1475
+ });
1476
+ function joinPath(base, file) {
1477
+ if (base.endsWith("/")) return `${base}${file}`;
1478
+ return `${base}/${file}`;
1479
+ }
1480
+ var resolveAsset6 = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion16.staticFile)(staticFileInputFromAssetPath(value));
1481
+ var ImageSequence = ({ basePath, frameCount, filePattern, fps }) => {
1482
+ const frame = (0, import_remotion16.useCurrentFrame)();
1483
+ const { fps: compFps } = (0, import_remotion16.useVideoConfig)();
1484
+ const index = Math.min(frameCount - 1, Math.max(0, Math.floor(frame * fps / compFps)));
1485
+ const file = filePattern.replace("{frame}", String(index));
1486
+ const src = joinPath(basePath, file);
1487
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(Fill, { children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_remotion16.Img, { src: resolveAsset6(src), style: { width: "100%", height: "100%", objectFit: "cover" } }) });
1488
+ };
1489
+ var ImageSequenceComponentMetadata = {
1490
+ kind: "composite",
1491
+ category: "image",
1492
+ description: "Plays a numbered image sequence (frame-by-frame)",
1493
+ llmGuidance: "Use for exported sprite sequences. basePath can be /assets/seq and filePattern like img_{frame}.png."
1494
+ };
1495
+
1496
+ // src/components/composites/ImageWithCaption.tsx
1497
+ var import_remotion17 = require("remotion");
1498
+ var import_zod27 = require("zod");
1499
+ var import_jsx_runtime26 = require("react/jsx-runtime");
1500
+ var CaptionStyleSchema = import_zod27.z.object({
1501
+ fontSize: import_zod27.z.number().min(12).max(80).default(32),
1502
+ color: import_zod27.z.string().default("#FFFFFF"),
1503
+ backgroundColor: import_zod27.z.string().default("rgba(0,0,0,0.7)")
1504
+ });
1505
+ var ImageWithCaptionPropsSchema = import_zod27.z.object({
1506
+ src: import_zod27.z.string().min(1),
1507
+ caption: import_zod27.z.string().max(200),
1508
+ captionPosition: import_zod27.z.enum(["top", "bottom", "overlay"]).default("bottom"),
1509
+ captionStyle: CaptionStyleSchema.optional()
1510
+ });
1511
+ var resolveAsset7 = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion17.staticFile)(staticFileInputFromAssetPath(value));
1512
+ var ImageWithCaption = ({ src, caption, captionPosition, captionStyle }) => {
1513
+ const frame = (0, import_remotion17.useCurrentFrame)();
1514
+ const p = (0, import_remotion17.interpolate)(frame, [8, 24], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
1515
+ const style = captionStyle ?? { fontSize: 32, color: "#FFFFFF", backgroundColor: "rgba(0,0,0,0.7)" };
1516
+ const captionBox = /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
1517
+ "div",
1518
+ {
1519
+ style: {
1520
+ width: "100%",
1521
+ padding: 22,
1522
+ boxSizing: "border-box",
1523
+ backgroundColor: style.backgroundColor,
1524
+ color: style.color,
1525
+ fontWeight: 800,
1526
+ fontSize: style.fontSize,
1527
+ opacity: p
1528
+ },
1529
+ children: caption
1530
+ }
1531
+ );
1532
+ if (captionPosition === "top" || captionPosition === "bottom") {
1533
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(Fill, { style: { display: "flex", flexDirection: "column" }, children: [
1534
+ captionPosition === "top" ? captionBox : null,
1535
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { style: { flex: 1, position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_remotion17.Img, { src: resolveAsset7(src), style: { width: "100%", height: "100%", objectFit: "cover" } }) }),
1536
+ captionPosition === "bottom" ? captionBox : null
1537
+ ] });
1538
+ }
1539
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(Fill, { children: [
1540
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_remotion17.Img, { src: resolveAsset7(src), style: { width: "100%", height: "100%", objectFit: "cover" } }),
1541
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { style: { position: "absolute", left: 0, right: 0, bottom: 0 }, children: captionBox })
1542
+ ] });
1543
+ };
1544
+ var ImageWithCaptionComponentMetadata = {
1545
+ kind: "composite",
1546
+ category: "image",
1547
+ description: "Image with a caption strip (top/bottom) or overlay caption",
1548
+ llmGuidance: "Use overlay for quotes/testimonials over photos. Use bottom for standard captions."
1549
+ };
1550
+
1551
+ // src/components/composites/InstagramStory.tsx
1552
+ var import_remotion18 = require("remotion");
1553
+ var import_zod28 = require("zod");
1554
+ var import_jsx_runtime27 = require("react/jsx-runtime");
1555
+ var InstagramStoryPropsSchema = import_zod28.z.object({
1556
+ backgroundImage: import_zod28.z.string().optional(),
1557
+ backgroundColor: import_zod28.z.string().default("#000000"),
1558
+ profilePic: import_zod28.z.string().optional(),
1559
+ username: import_zod28.z.string().optional(),
1560
+ text: import_zod28.z.string().max(100).optional(),
1561
+ sticker: import_zod28.z.enum(["none", "poll", "question", "countdown"]).default("none"),
1562
+ musicTrack: import_zod28.z.string().optional()
1563
+ });
1564
+ var resolveAsset8 = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion18.staticFile)(staticFileInputFromAssetPath(value));
1565
+ var InstagramStory = ({
1566
+ backgroundImage,
1567
+ backgroundColor,
1568
+ profilePic,
1569
+ username,
1570
+ text,
1571
+ sticker,
1572
+ musicTrack
1573
+ }) => {
1574
+ const frame = (0, import_remotion18.useCurrentFrame)();
1575
+ const fade = (0, import_remotion18.interpolate)(frame, [0, 20], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
1576
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Fill, { style: { backgroundColor }, children: [
1577
+ musicTrack ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_remotion18.Audio, { src: resolveAsset8(musicTrack), volume: 0.6 }) : null,
1578
+ backgroundImage ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_remotion18.Img, { src: resolveAsset8(backgroundImage), style: { width: "100%", height: "100%", objectFit: "cover", opacity: fade } }) : null,
1579
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(Fill, { style: { padding: 60, boxSizing: "border-box" }, children: [
1580
+ profilePic || username ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 18 }, children: [
1581
+ profilePic ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_remotion18.Img, { src: resolveAsset8(profilePic), style: { width: 78, height: 78, borderRadius: 9999, objectFit: "cover" } }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { style: { width: 78, height: 78, borderRadius: 9999, backgroundColor: "rgba(255,255,255,0.2)" } }),
1582
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { style: { color: "#FFFFFF", fontWeight: 800, fontSize: 34 }, children: username ?? "username" })
1583
+ ] }) : null,
1584
+ text ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1585
+ "div",
1586
+ {
1587
+ style: {
1588
+ marginTop: 120,
1589
+ color: "#FFFFFF",
1590
+ fontSize: 54,
1591
+ fontWeight: 900,
1592
+ textShadow: "0 8px 30px rgba(0,0,0,0.6)",
1593
+ maxWidth: 900
1594
+ },
1595
+ children: text
1596
+ }
1597
+ ) : null,
1598
+ sticker !== "none" ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
1599
+ "div",
1600
+ {
1601
+ style: {
1602
+ position: "absolute",
1603
+ left: 60,
1604
+ bottom: 180,
1605
+ width: 520,
1606
+ padding: 28,
1607
+ borderRadius: 22,
1608
+ backgroundColor: "rgba(255,255,255,0.9)",
1609
+ color: "#111",
1610
+ fontWeight: 900,
1611
+ fontSize: 30
1612
+ },
1613
+ children: sticker === "poll" ? "POLL" : sticker === "question" ? "QUESTION" : "COUNTDOWN"
1614
+ }
1615
+ ) : null
1616
+ ] })
1617
+ ] });
1618
+ };
1619
+ var InstagramStoryComponentMetadata = {
1620
+ kind: "composite",
1621
+ category: "social",
1622
+ description: "Instagram story-style layout with profile header, text overlay, and optional sticker",
1623
+ llmGuidance: "Best with 1080x1920 (9:16). Use backgroundImage + short text + optional sticker for mobile-style content."
1624
+ };
1625
+
1626
+ // src/components/composites/IntroScene.tsx
1627
+ var import_remotion19 = require("remotion");
1628
+ var import_zod29 = require("zod");
1629
+ var import_jsx_runtime28 = require("react/jsx-runtime");
1630
+ var IntroScenePropsSchema = import_zod29.z.object({
1631
+ logoSrc: import_zod29.z.string().min(1),
1632
+ companyName: import_zod29.z.string().min(1),
1633
+ tagline: import_zod29.z.string().optional(),
1634
+ backgroundColor: import_zod29.z.string().default("#000000"),
1635
+ primaryColor: import_zod29.z.string().default("#FFFFFF"),
1636
+ musicTrack: import_zod29.z.string().optional()
1637
+ });
1638
+ var resolveAsset9 = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion19.staticFile)(staticFileInputFromAssetPath(value));
1639
+ var IntroScene = ({
1640
+ logoSrc,
1641
+ companyName,
1642
+ tagline,
1643
+ backgroundColor,
1644
+ primaryColor,
1645
+ musicTrack,
1646
+ __wavesDurationInFrames
1647
+ }) => {
1648
+ const frame = (0, import_remotion19.useCurrentFrame)();
1649
+ const { fps } = (0, import_remotion19.useVideoConfig)();
1650
+ const total = Math.max(1, __wavesDurationInFrames ?? 1);
1651
+ const logoP = (0, import_remotion19.spring)({ frame, fps, config: { damping: 14, stiffness: 120, mass: 0.9 } });
1652
+ const nameOpacity = (0, import_remotion19.interpolate)(frame, [20, 60], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
1653
+ const taglineY = (0, import_remotion19.interpolate)(frame, [50, 80], [20, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
1654
+ const outroFade = (0, import_remotion19.interpolate)(frame, [Math.max(0, total - 20), total], [1, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
1655
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(Fill, { style: { backgroundColor, display: "flex", justifyContent: "center", alignItems: "center" }, children: [
1656
+ musicTrack ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_remotion19.Audio, { src: resolveAsset9(musicTrack), volume: 0.7 }) : null,
1657
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { style: { textAlign: "center", opacity: outroFade }, children: [
1658
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1659
+ import_remotion19.Img,
1660
+ {
1661
+ src: resolveAsset9(logoSrc),
1662
+ style: { width: 280, height: 280, objectFit: "contain", transform: `scale(${logoP})` }
1663
+ }
1664
+ ),
1665
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { style: { marginTop: 24, color: primaryColor, fontSize: 64, fontWeight: 900, opacity: nameOpacity }, children: companyName }),
1666
+ tagline ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
1667
+ "div",
1668
+ {
1669
+ style: {
1670
+ marginTop: 12,
1671
+ color: primaryColor,
1672
+ fontSize: 32,
1673
+ fontWeight: 700,
1674
+ opacity: nameOpacity,
1675
+ transform: `translateY(${taglineY}px)`
1676
+ },
1677
+ children: tagline
1678
+ }
1679
+ ) : null
1680
+ ] })
1681
+ ] });
1682
+ };
1683
+ var IntroSceneComponentMetadata = {
1684
+ kind: "composite",
1685
+ category: "branding",
1686
+ description: "Branded intro scene (logo + company name + optional tagline)",
1687
+ llmGuidance: "Use as the first segment. Works best at 3-5 seconds. musicTrack can add ambience."
1688
+ };
1689
+
1690
+ // src/components/composites/KenBurnsImage.tsx
1691
+ var import_remotion20 = require("remotion");
1692
+ var import_zod30 = require("zod");
1693
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1694
+ var KenBurnsImagePropsSchema = import_zod30.z.object({
1695
+ src: import_zod30.z.string().min(1),
1696
+ startScale: import_zod30.z.number().min(1).max(2).default(1),
1697
+ endScale: import_zod30.z.number().min(1).max(2).default(1.2),
1698
+ panDirection: import_zod30.z.enum(["none", "left", "right", "up", "down"]).default("none"),
1699
+ panAmount: import_zod30.z.number().min(0).max(100).default(50)
1700
+ });
1701
+ var resolveAsset10 = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion20.staticFile)(staticFileInputFromAssetPath(value));
1702
+ var KenBurnsImage = ({
1703
+ src,
1704
+ startScale,
1705
+ endScale,
1706
+ panDirection,
1707
+ panAmount,
1708
+ __wavesDurationInFrames
1709
+ }) => {
1710
+ const frame = (0, import_remotion20.useCurrentFrame)();
1711
+ const duration = Math.max(1, __wavesDurationInFrames ?? 1);
1712
+ const t = (0, import_remotion20.interpolate)(frame, [0, duration], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
1713
+ const scale = startScale + (endScale - startScale) * t;
1714
+ const dx = panDirection === "left" ? -panAmount * t : panDirection === "right" ? panAmount * t : 0;
1715
+ const dy = panDirection === "up" ? -panAmount * t : panDirection === "down" ? panAmount * t : 0;
1716
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(Fill, { children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1717
+ import_remotion20.Img,
1718
+ {
1719
+ src: resolveAsset10(src),
1720
+ style: {
1721
+ width: "110%",
1722
+ height: "110%",
1723
+ objectFit: "cover",
1724
+ transform: `translate(${dx}px, ${dy}px) scale(${scale})`,
1725
+ transformOrigin: "center center"
1726
+ }
1727
+ }
1728
+ ) });
1729
+ };
1730
+ var KenBurnsImageComponentMetadata = {
1731
+ kind: "composite",
1732
+ category: "image",
1733
+ description: "Slow zoom and pan (Ken Burns effect) for a still image",
1734
+ llmGuidance: "Classic documentary-style motion. startScale 1 -> endScale 1.2 is subtle; add panDirection for extra movement."
1735
+ };
1736
+
1737
+ // src/components/composites/KineticTypography.tsx
1738
+ var import_remotion21 = require("remotion");
1739
+ var import_zod31 = require("zod");
1740
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1741
+ var WordSchema = import_zod31.z.object({
1742
+ text: import_zod31.z.string().min(1),
1743
+ emphasis: import_zod31.z.enum(["normal", "bold", "giant"]).default("normal")
1744
+ });
1745
+ var KineticTypographyPropsSchema = import_zod31.z.object({
1746
+ words: import_zod31.z.array(WordSchema).min(1).max(50),
1747
+ fontSize: import_zod31.z.number().min(12).max(140).default(48),
1748
+ color: import_zod31.z.string().default("#FFFFFF"),
1749
+ fontFamily: import_zod31.z.string().default("Inter"),
1750
+ timing: import_zod31.z.array(import_zod31.z.number().int().min(0)).min(1).describe("Frame timing for each word"),
1751
+ transition: import_zod31.z.enum(["fade", "scale", "slideLeft", "slideRight"]).default("scale")
1752
+ });
1753
+ var KineticTypography = ({
1754
+ words,
1755
+ fontSize,
1756
+ color,
1757
+ fontFamily,
1758
+ timing,
1759
+ transition
1760
+ }) => {
1761
+ const frame = (0, import_remotion21.useCurrentFrame)();
1762
+ const { fps } = (0, import_remotion21.useVideoConfig)();
1763
+ const starts = (() => {
1764
+ if (timing.length >= words.length) return timing.slice(0, words.length);
1765
+ const last = timing.length ? timing[timing.length - 1] : 0;
1766
+ const extra = Array.from({ length: words.length - timing.length }, () => last + 15);
1767
+ return [...timing, ...extra];
1768
+ })();
1769
+ let activeIndex = 0;
1770
+ for (let i = 0; i < words.length; i++) {
1771
+ if (frame >= (starts[i] ?? 0)) activeIndex = i;
1772
+ }
1773
+ const word = words[activeIndex];
1774
+ const start = starts[activeIndex] ?? 0;
1775
+ const p = (0, import_remotion21.spring)({ frame: frame - start, fps, config: { damping: 14, stiffness: 120, mass: 0.9 } });
1776
+ const progress = Math.max(0, Math.min(1, p));
1777
+ const scaleBase = word.emphasis === "giant" ? 2 : word.emphasis === "bold" ? 1.3 : 1;
1778
+ const opacity = transition === "fade" ? progress : 1;
1779
+ const scale = transition === "scale" ? (0.85 + 0.15 * progress) * scaleBase : 1 * scaleBase;
1780
+ const tx = transition === "slideLeft" ? 40 * (1 - progress) : transition === "slideRight" ? -40 * (1 - progress) : 0;
1781
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Fill, { style: { display: "flex", justifyContent: "center", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1782
+ "div",
1783
+ {
1784
+ style: {
1785
+ fontSize,
1786
+ color,
1787
+ fontFamily,
1788
+ fontWeight: word.emphasis === "normal" ? 800 : 900,
1789
+ textTransform: "uppercase",
1790
+ letterSpacing: 1,
1791
+ opacity,
1792
+ transform: `translateX(${tx}px) scale(${scale})`
1793
+ },
1794
+ children: word.text
1795
+ }
1796
+ ) });
1797
+ };
1798
+ var KineticTypographyComponentMetadata = {
1799
+ kind: "composite",
1800
+ category: "text",
1801
+ description: "Rhythmic single-word kinetic typography driven by a timing array",
1802
+ llmGuidance: 'Provide timing frames for when each word appears. Use emphasis="giant" sparingly for impact.'
1803
+ };
1804
+
1805
+ // src/components/composites/LineGraph.tsx
1806
+ var import_remotion22 = require("remotion");
1807
+ var import_zod32 = require("zod");
1808
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1809
+ var PointSchema = import_zod32.z.object({ x: import_zod32.z.number(), y: import_zod32.z.number() });
1810
+ var LineGraphPropsSchema = import_zod32.z.object({
1811
+ data: import_zod32.z.array(PointSchema).min(2).max(50),
1812
+ color: import_zod32.z.string().default("#00FF00"),
1813
+ strokeWidth: import_zod32.z.number().min(1).max(10).default(3),
1814
+ showDots: import_zod32.z.boolean().default(true),
1815
+ fillArea: import_zod32.z.boolean().default(false),
1816
+ animate: import_zod32.z.enum(["draw", "reveal"]).default("draw")
1817
+ });
1818
+ function normalize(data, w, h, pad) {
1819
+ const xs = data.map((d) => d.x);
1820
+ const ys = data.map((d) => d.y);
1821
+ const minX = Math.min(...xs);
1822
+ const maxX = Math.max(...xs);
1823
+ const minY = Math.min(...ys);
1824
+ const maxY = Math.max(...ys);
1825
+ return data.map((d) => ({
1826
+ x: pad + (maxX === minX ? (w - 2 * pad) / 2 : (d.x - minX) / (maxX - minX) * (w - 2 * pad)),
1827
+ y: pad + (maxY === minY ? (h - 2 * pad) / 2 : (1 - (d.y - minY) / (maxY - minY)) * (h - 2 * pad))
1828
+ }));
1829
+ }
1830
+ function toPath(points) {
1831
+ return points.reduce((acc, p, i) => i === 0 ? `M ${p.x} ${p.y}` : `${acc} L ${p.x} ${p.y}`, "");
1832
+ }
1833
+ var LineGraph = ({
1834
+ data,
1835
+ color,
1836
+ strokeWidth,
1837
+ showDots,
1838
+ fillArea,
1839
+ animate,
1840
+ __wavesDurationInFrames
1841
+ }) => {
1842
+ const frame = (0, import_remotion22.useCurrentFrame)();
1843
+ const total = Math.max(1, __wavesDurationInFrames ?? 1);
1844
+ const progress = (0, import_remotion22.interpolate)(frame, [0, total], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
1845
+ const w = 1e3;
1846
+ const h = 520;
1847
+ const pad = 30;
1848
+ const pts = normalize(data, w, h, pad);
1849
+ const d = toPath(pts);
1850
+ const dash = 3e3;
1851
+ const dashOffset = dash * (1 - progress);
1852
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(Fill, { style: { display: "flex", justifyContent: "center", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("svg", { viewBox: `0 0 ${w} ${h}`, style: { width: "100%", height: "100%" }, children: [
1853
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("clipPath", { id: "waves-line-reveal", children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("rect", { x: "0", y: "0", width: w * progress, height: h }) }) }),
1854
+ fillArea ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1855
+ "path",
1856
+ {
1857
+ d: `${d} L ${pts[pts.length - 1].x} ${h - pad} L ${pts[0].x} ${h - pad} Z`,
1858
+ fill: color,
1859
+ opacity: 0.12,
1860
+ clipPath: animate === "reveal" ? "url(#waves-line-reveal)" : void 0
1861
+ }
1862
+ ) : null,
1863
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
1864
+ "path",
1865
+ {
1866
+ d,
1867
+ fill: "none",
1868
+ stroke: color,
1869
+ strokeWidth,
1870
+ strokeLinecap: "round",
1871
+ strokeLinejoin: "round",
1872
+ strokeDasharray: animate === "draw" ? dash : void 0,
1873
+ strokeDashoffset: animate === "draw" ? dashOffset : void 0,
1874
+ clipPath: animate === "reveal" ? "url(#waves-line-reveal)" : void 0
1875
+ }
1876
+ ),
1877
+ showDots ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("g", { clipPath: animate === "reveal" ? "url(#waves-line-reveal)" : void 0, children: pts.map((p, i) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("circle", { cx: p.x, cy: p.y, r: 6, fill: color }, i)) }) : null
1878
+ ] }) });
1879
+ };
1880
+ var LineGraphComponentMetadata = {
1881
+ kind: "composite",
1882
+ category: "data",
1883
+ description: "Animated line graph (SVG) with draw/reveal modes",
1884
+ llmGuidance: 'Use 5-20 points. animate="draw" traces the line; animate="reveal" wipes it left-to-right.'
1885
+ };
1886
+
1887
+ // src/components/composites/LogoReveal.tsx
1888
+ var import_remotion23 = require("remotion");
1889
+ var import_zod33 = require("zod");
1890
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1891
+ var LogoRevealPropsSchema = import_zod33.z.object({
1892
+ logoSrc: import_zod33.z.string().min(1),
1893
+ effect: import_zod33.z.enum(["fade", "scale", "rotate", "slide"]).default("scale"),
1894
+ backgroundColor: import_zod33.z.string().default("#000000"),
1895
+ soundEffect: import_zod33.z.string().optional()
1896
+ });
1897
+ var resolveAsset11 = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion23.staticFile)(staticFileInputFromAssetPath(value));
1898
+ var LogoReveal = ({ logoSrc, effect, backgroundColor, soundEffect }) => {
1899
+ const frame = (0, import_remotion23.useCurrentFrame)();
1900
+ const { fps } = (0, import_remotion23.useVideoConfig)();
1901
+ const p = (0, import_remotion23.spring)({ frame, fps, config: { damping: 14, stiffness: 110, mass: 0.9 } });
1902
+ const opacity = effect === "fade" ? p : Math.min(1, Math.max(0, p));
1903
+ const scale = effect === "scale" ? p : 1;
1904
+ const rotate = effect === "rotate" ? (1 - p) * 360 : 0;
1905
+ const translateY = effect === "slide" ? -200 * (1 - p) : 0;
1906
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(Fill, { style: { backgroundColor, display: "flex", justifyContent: "center", alignItems: "center" }, children: [
1907
+ soundEffect ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_remotion23.Audio, { src: resolveAsset11(soundEffect), volume: 1 }) : null,
1908
+ /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
1909
+ import_remotion23.Img,
1910
+ {
1911
+ src: resolveAsset11(logoSrc),
1912
+ style: {
1913
+ width: 320,
1914
+ height: 320,
1915
+ objectFit: "contain",
1916
+ opacity,
1917
+ transform: `translateY(${translateY}px) scale(${scale}) rotate(${rotate}deg)`
1918
+ }
1919
+ }
1920
+ )
1921
+ ] });
1922
+ };
1923
+ var LogoRevealComponentMetadata = {
1924
+ kind: "composite",
1925
+ category: "branding",
1926
+ description: "Logo intro animation (fade/scale/rotate/slide), optionally with a sound effect",
1927
+ llmGuidance: "Use for intros/outros. Keep the logo high-contrast and centered. soundEffect can be a short sting."
1928
+ };
1929
+
1930
+ // src/components/composites/OutroScene.tsx
1931
+ var import_remotion24 = require("remotion");
1932
+ var import_zod34 = require("zod");
1933
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1934
+ var CtaSchema = import_zod34.z.object({ text: import_zod34.z.string().min(1), icon: import_zod34.z.string().optional() });
1935
+ var HandleSchema = import_zod34.z.object({
1936
+ platform: import_zod34.z.enum(["twitter", "instagram", "youtube", "linkedin"]),
1937
+ handle: import_zod34.z.string().min(1)
1938
+ });
1939
+ var OutroScenePropsSchema = import_zod34.z.object({
1940
+ logoSrc: import_zod34.z.string().min(1),
1941
+ message: import_zod34.z.string().default("Thank You"),
1942
+ ctaButtons: import_zod34.z.array(CtaSchema).max(3).optional(),
1943
+ socialHandles: import_zod34.z.array(HandleSchema).max(4).optional(),
1944
+ backgroundColor: import_zod34.z.string().default("#000000")
1945
+ });
1946
+ var resolveAsset12 = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion24.staticFile)(staticFileInputFromAssetPath(value));
1947
+ var OutroScene = ({ logoSrc, message, ctaButtons, socialHandles, backgroundColor }) => {
1948
+ const frame = (0, import_remotion24.useCurrentFrame)();
1949
+ const { fps } = (0, import_remotion24.useVideoConfig)();
1950
+ const logoP = (0, import_remotion24.spring)({ frame, fps, config: { damping: 14, stiffness: 120, mass: 0.9 } });
1951
+ const msgP = (0, import_remotion24.spring)({ frame: frame - 18, fps, config: { damping: 14, stiffness: 120, mass: 0.9 } });
1952
+ const ctaP = (0, import_remotion24.spring)({ frame: frame - 40, fps, config: { damping: 14, stiffness: 120, mass: 0.9 } });
1953
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Fill, { style: { backgroundColor, display: "flex", justifyContent: "center", alignItems: "center", padding: 80, boxSizing: "border-box" }, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { style: { textAlign: "center" }, children: [
1954
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(import_remotion24.Img, { src: resolveAsset12(logoSrc), style: { width: 220, height: 220, objectFit: "contain", transform: `scale(${logoP})` } }),
1955
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { style: { marginTop: 24, color: "#FFFFFF", fontSize: 72, fontWeight: 1e3, opacity: msgP }, children: message }),
1956
+ ctaButtons?.length ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { style: { marginTop: 34, display: "flex", gap: 18, justifyContent: "center", opacity: ctaP }, children: ctaButtons.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
1957
+ "div",
1958
+ {
1959
+ style: {
1960
+ padding: "18px 28px",
1961
+ borderRadius: 18,
1962
+ backgroundColor: "rgba(255,255,255,0.12)",
1963
+ color: "#FFFFFF",
1964
+ fontSize: 28,
1965
+ fontWeight: 900
1966
+ },
1967
+ children: [
1968
+ b.icon ? `${b.icon} ` : "",
1969
+ b.text
1970
+ ]
1971
+ },
1972
+ i
1973
+ )) }) : null,
1974
+ socialHandles?.length ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { style: { marginTop: 50, display: "flex", flexDirection: "column", gap: 10, opacity: ctaP }, children: socialHandles.map((h, i) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { style: { color: "rgba(255,255,255,0.85)", fontSize: 26, fontWeight: 800 }, children: [
1975
+ h.platform,
1976
+ ": ",
1977
+ h.handle
1978
+ ] }, i)) }) : null
1979
+ ] }) });
1980
+ };
1981
+ var OutroSceneComponentMetadata = {
1982
+ kind: "composite",
1983
+ category: "branding",
1984
+ description: "End screen with logo, message, optional CTA buttons and social handles",
1985
+ llmGuidance: "Use as the last segment. Keep CTAs <=3 for clarity."
1986
+ };
1987
+
1988
+ // src/components/composites/OutlineText.tsx
1989
+ var import_remotion25 = require("remotion");
1990
+ var import_zod35 = require("zod");
1991
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1992
+ var OutlineTextPropsSchema = import_zod35.z.object({
1993
+ content: import_zod35.z.string().max(50),
1994
+ fontSize: import_zod35.z.number().min(8).max(240).default(96),
1995
+ outlineColor: import_zod35.z.string().default("#FFFFFF"),
1996
+ fillColor: import_zod35.z.string().default("#000000"),
1997
+ fontFamily: import_zod35.z.string().default("Inter"),
1998
+ fontWeight: import_zod35.z.number().int().min(100).max(900).default(800),
1999
+ position: import_zod35.z.enum(["top", "center", "bottom"]).default("center"),
2000
+ animation: import_zod35.z.enum(["draw", "fill"]).default("draw"),
2001
+ strokeWidth: import_zod35.z.number().min(1).max(10).default(3)
2002
+ });
2003
+ var positionStyles3 = (position) => {
2004
+ if (position === "top") return { justifyContent: "center", alignItems: "flex-start", paddingTop: 90 };
2005
+ if (position === "bottom") return { justifyContent: "center", alignItems: "flex-end", paddingBottom: 90 };
2006
+ return { justifyContent: "center", alignItems: "center" };
2007
+ };
2008
+ var OutlineText = ({
2009
+ content,
2010
+ fontSize,
2011
+ outlineColor,
2012
+ fillColor,
2013
+ fontFamily,
2014
+ fontWeight,
2015
+ position,
2016
+ animation,
2017
+ strokeWidth
2018
+ }) => {
2019
+ const frame = (0, import_remotion25.useCurrentFrame)();
2020
+ const t = (0, import_remotion25.interpolate)(frame, [0, 24], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2021
+ const strokeOpacity = animation === "draw" ? t : 1;
2022
+ const fillOpacity = animation === "fill" ? t : 0;
2023
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(Fill, { style: { display: "flex", ...positionStyles3(position) }, children: [
2024
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2025
+ "div",
2026
+ {
2027
+ style: {
2028
+ fontSize,
2029
+ fontFamily,
2030
+ fontWeight,
2031
+ color: fillColor,
2032
+ opacity: fillOpacity,
2033
+ WebkitTextStroke: `${strokeWidth}px ${outlineColor}`,
2034
+ textShadow: `0 0 1px ${outlineColor}`,
2035
+ textAlign: "center",
2036
+ lineHeight: 1
2037
+ },
2038
+ children: content
2039
+ }
2040
+ ),
2041
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2042
+ "div",
2043
+ {
2044
+ style: {
2045
+ position: "absolute",
2046
+ fontSize,
2047
+ fontFamily,
2048
+ fontWeight,
2049
+ color: "transparent",
2050
+ opacity: strokeOpacity,
2051
+ WebkitTextStroke: `${strokeWidth}px ${outlineColor}`,
2052
+ textAlign: "center",
2053
+ lineHeight: 1
2054
+ },
2055
+ children: content
2056
+ }
2057
+ )
2058
+ ] });
2059
+ };
2060
+ var OutlineTextComponentMetadata = {
2061
+ kind: "composite",
2062
+ category: "text",
2063
+ description: "Outlined title text with simple draw/fill animation",
2064
+ llmGuidance: 'Use for bold titles. animation="draw" emphasizes the outline; animation="fill" reveals the fill color.'
2065
+ };
2066
+
2067
+ // src/components/composites/ProgressBar.tsx
2068
+ var import_remotion26 = require("remotion");
2069
+ var import_zod36 = require("zod");
2070
+ var import_jsx_runtime35 = require("react/jsx-runtime");
2071
+ var ProgressBarPropsSchema = import_zod36.z.object({
2072
+ label: import_zod36.z.string().optional(),
2073
+ color: import_zod36.z.string().default("#00FF00"),
2074
+ backgroundColor: import_zod36.z.string().default("rgba(255,255,255,0.2)"),
2075
+ height: import_zod36.z.number().min(5).max(50).default(10),
2076
+ position: import_zod36.z.enum(["top", "bottom"]).default("bottom"),
2077
+ showPercentage: import_zod36.z.boolean().default(true)
2078
+ });
2079
+ var ProgressBar = ({
2080
+ label,
2081
+ color,
2082
+ backgroundColor,
2083
+ height,
2084
+ position,
2085
+ showPercentage,
2086
+ __wavesDurationInFrames
2087
+ }) => {
2088
+ const frame = (0, import_remotion26.useCurrentFrame)();
2089
+ const total = Math.max(1, __wavesDurationInFrames ?? 1);
2090
+ const p = (0, import_remotion26.interpolate)(frame, [0, total], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2091
+ const pct = Math.round(p * 100);
2092
+ const yStyle = position === "top" ? { top: 50 } : { bottom: 50 };
2093
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(Fill, { children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { style: { position: "absolute", left: 80, right: 80, ...yStyle }, children: [
2094
+ label || showPercentage ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: 10, color: "#FFFFFF", fontWeight: 700 }, children: [
2095
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { children: label ?? "" }),
2096
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { children: showPercentage ? `${pct}%` : "" })
2097
+ ] }) : null,
2098
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { style: { width: "100%", height, backgroundColor, borderRadius: 9999, overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { style: { width: `${pct}%`, height: "100%", backgroundColor: color } }) })
2099
+ ] }) });
2100
+ };
2101
+ var ProgressBarComponentMetadata = {
2102
+ kind: "composite",
2103
+ category: "data",
2104
+ description: "Animated progress bar that fills over the component duration",
2105
+ llmGuidance: "Use for loading/countdowns. showPercentage=true is helpful for clarity."
2106
+ };
2107
+
2108
+ // src/components/composites/ProgressRing.tsx
2109
+ var import_remotion27 = require("remotion");
2110
+ var import_zod37 = require("zod");
2111
+ var import_jsx_runtime36 = require("react/jsx-runtime");
2112
+ var ProgressRingPropsSchema = import_zod37.z.object({
2113
+ percentage: import_zod37.z.number().min(0).max(100),
2114
+ size: import_zod37.z.number().min(100).max(500).default(200),
2115
+ strokeWidth: import_zod37.z.number().min(5).max(50).default(20),
2116
+ color: import_zod37.z.string().default("#00FF00"),
2117
+ backgroundColor: import_zod37.z.string().default("rgba(255,255,255,0.2)"),
2118
+ showLabel: import_zod37.z.boolean().default(true)
2119
+ });
2120
+ var ProgressRing = ({
2121
+ percentage,
2122
+ size,
2123
+ strokeWidth,
2124
+ color,
2125
+ backgroundColor,
2126
+ showLabel,
2127
+ __wavesDurationInFrames
2128
+ }) => {
2129
+ const frame = (0, import_remotion27.useCurrentFrame)();
2130
+ const total = Math.max(1, __wavesDurationInFrames ?? 1);
2131
+ const p = (0, import_remotion27.interpolate)(frame, [0, total], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2132
+ const current = percentage * p;
2133
+ const r = (size - strokeWidth) / 2;
2134
+ const c = 2 * Math.PI * r;
2135
+ const dash = c;
2136
+ const offset = c - current / 100 * c;
2137
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(Fill, { style: { display: "flex", justifyContent: "center", alignItems: "center" }, children: [
2138
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, children: [
2139
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2140
+ "circle",
2141
+ {
2142
+ cx: size / 2,
2143
+ cy: size / 2,
2144
+ r,
2145
+ stroke: backgroundColor,
2146
+ strokeWidth,
2147
+ fill: "transparent"
2148
+ }
2149
+ ),
2150
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
2151
+ "circle",
2152
+ {
2153
+ cx: size / 2,
2154
+ cy: size / 2,
2155
+ r,
2156
+ stroke: color,
2157
+ strokeWidth,
2158
+ fill: "transparent",
2159
+ strokeDasharray: dash,
2160
+ strokeDashoffset: offset,
2161
+ strokeLinecap: "round",
2162
+ transform: `rotate(-90 ${size / 2} ${size / 2})`
2163
+ }
2164
+ )
2165
+ ] }),
2166
+ showLabel ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { style: { position: "absolute", color: "#FFFFFF", fontWeight: 800, fontSize: Math.round(size * 0.22) }, children: [
2167
+ Math.round(current),
2168
+ "%"
2169
+ ] }) : null
2170
+ ] });
2171
+ };
2172
+ var ProgressRingComponentMetadata = {
2173
+ kind: "composite",
2174
+ category: "data",
2175
+ description: "Circular progress indicator (SVG) that animates from 0 to percentage over duration",
2176
+ llmGuidance: "Use for completion and goals. size 160-260 is typical. showLabel displays the percentage."
2177
+ };
2178
+
2179
+ // src/components/composites/SlideTransition.tsx
2180
+ var import_react7 = __toESM(require("react"));
2181
+ var import_remotion28 = require("remotion");
2182
+ var import_zod38 = require("zod");
2183
+ var import_jsx_runtime37 = require("react/jsx-runtime");
2184
+ var SlideTransitionPropsSchema = import_zod38.z.object({
2185
+ durationInFrames: import_zod38.z.number().int().min(10).max(60).default(30),
2186
+ direction: import_zod38.z.enum(["left", "right", "up", "down"]).default("left"),
2187
+ distance: import_zod38.z.number().int().min(1).max(2e3).default(160),
2188
+ phase: import_zod38.z.enum(["in", "out", "inOut"]).default("inOut")
2189
+ });
2190
+ function translateFor(direction, delta) {
2191
+ if (direction === "left") return { x: -delta, y: 0 };
2192
+ if (direction === "right") return { x: delta, y: 0 };
2193
+ if (direction === "up") return { x: 0, y: -delta };
2194
+ return { x: 0, y: delta };
2195
+ }
2196
+ var SlideTransition = ({
2197
+ durationInFrames,
2198
+ direction,
2199
+ distance,
2200
+ phase,
2201
+ children,
2202
+ __wavesDurationInFrames
2203
+ }) => {
2204
+ const layers = import_react7.default.Children.toArray(children);
2205
+ const frame = (0, import_remotion28.useCurrentFrame)();
2206
+ const total = Math.max(1, __wavesDurationInFrames ?? 1);
2207
+ const d = Math.min(durationInFrames, total);
2208
+ let opacity = 1;
2209
+ let tx = 0;
2210
+ let ty = 0;
2211
+ if (phase === "in" || phase === "inOut") {
2212
+ const t = (0, import_remotion28.interpolate)(frame, [0, d], [1, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2213
+ const { x, y } = translateFor(direction, distance * t);
2214
+ tx += x;
2215
+ ty += y;
2216
+ opacity *= (0, import_remotion28.interpolate)(frame, [0, d], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2217
+ }
2218
+ if (phase === "out" || phase === "inOut") {
2219
+ const start = Math.max(0, total - d);
2220
+ const t = (0, import_remotion28.interpolate)(frame, [start, total], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2221
+ const opposite = direction === "left" ? "right" : direction === "right" ? "left" : direction === "up" ? "down" : "up";
2222
+ const { x, y } = translateFor(opposite, distance * t);
2223
+ tx += x;
2224
+ ty += y;
2225
+ opacity *= (0, import_remotion28.interpolate)(frame, [start, total], [1, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2226
+ }
2227
+ return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Fill, { style: { opacity, transform: `translate(${tx}px, ${ty}px)` }, children: layers.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { style: { position: "absolute", inset: 0 }, children: child }, i)) });
2228
+ };
2229
+ var SlideTransitionComponentMetadata = {
2230
+ kind: "composite",
2231
+ category: "transition",
2232
+ acceptsChildren: true,
2233
+ minChildren: 1,
2234
+ description: "Slide in/out wrapper (used for segment transitions and overlays)",
2235
+ llmGuidance: "Use for more dynamic transitions. direction controls where content enters from."
2236
+ };
2237
+
2238
+ // src/components/composites/SplitScreen.tsx
2239
+ var import_react8 = __toESM(require("react"));
2240
+ var import_zod39 = require("zod");
2241
+ var import_jsx_runtime38 = require("react/jsx-runtime");
2242
+ var SplitScreenPropsSchema = import_zod39.z.object({
2243
+ orientation: import_zod39.z.enum(["vertical", "horizontal"]).default("vertical"),
2244
+ split: import_zod39.z.number().min(0.1).max(0.9).default(0.5),
2245
+ gap: import_zod39.z.number().min(0).default(48),
2246
+ padding: import_zod39.z.number().min(0).default(80),
2247
+ dividerColor: import_zod39.z.string().optional()
2248
+ });
2249
+ var SplitScreen = ({
2250
+ orientation,
2251
+ split,
2252
+ gap,
2253
+ padding,
2254
+ dividerColor,
2255
+ children
2256
+ }) => {
2257
+ const items = import_react8.default.Children.toArray(children);
2258
+ const first = items[0] ?? null;
2259
+ const second = items[1] ?? null;
2260
+ const isVertical = orientation === "vertical";
2261
+ const flexDirection = isVertical ? "row" : "column";
2262
+ return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(Fill, { style: { padding, boxSizing: "border-box" }, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { style: { display: "flex", flexDirection, gap, width: "100%", height: "100%" }, children: [
2263
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { style: { flex: split, position: "relative" }, children: first }),
2264
+ dividerColor ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
2265
+ "div",
2266
+ {
2267
+ style: {
2268
+ backgroundColor: dividerColor,
2269
+ width: isVertical ? 2 : "100%",
2270
+ height: isVertical ? "100%" : 2,
2271
+ opacity: 0.7
2272
+ }
2273
+ }
2274
+ ) : null,
2275
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { style: { flex: 1 - split, position: "relative" }, children: second })
2276
+ ] }) });
2277
+ };
2278
+ var SplitScreenComponentMetadata = {
2279
+ kind: "composite",
2280
+ category: "layout",
2281
+ acceptsChildren: true,
2282
+ minChildren: 2,
2283
+ maxChildren: 2,
2284
+ description: "Two-panel split screen layout",
2285
+ llmGuidance: 'Provide exactly 2 children. Use orientation="vertical" for left/right and "horizontal" for top/bottom.'
2286
+ };
2287
+
2288
+ // src/components/composites/SplitText.tsx
2289
+ var import_remotion29 = require("remotion");
2290
+ var import_zod40 = require("zod");
2291
+ var import_jsx_runtime39 = require("react/jsx-runtime");
2292
+ var SplitTextPropsSchema = import_zod40.z.object({
2293
+ content: import_zod40.z.string().max(200),
2294
+ fontSize: import_zod40.z.number().min(8).max(200).default(48),
2295
+ color: import_zod40.z.string().default("#FFFFFF"),
2296
+ fontFamily: import_zod40.z.string().default("Inter"),
2297
+ position: import_zod40.z.enum(["top", "center", "bottom"]).default("center"),
2298
+ splitBy: import_zod40.z.enum(["word", "letter"]).default("word"),
2299
+ stagger: import_zod40.z.number().int().min(1).max(10).default(3),
2300
+ animation: import_zod40.z.enum(["fade", "slideUp", "slideDown", "scale", "rotate"]).default("slideUp"),
2301
+ maxWidthPct: import_zod40.z.number().min(0.1).max(1).default(0.9),
2302
+ safeInsetPct: import_zod40.z.number().min(0).max(0.25).default(0.06)
2303
+ });
2304
+ var positionStyles4 = (position, safeInsetPct) => {
2305
+ const inset = `${(safeInsetPct * 100).toFixed(2)}%`;
2306
+ if (position === "top") return { justifyContent: "center", alignItems: "flex-start", paddingTop: inset };
2307
+ if (position === "bottom") return { justifyContent: "center", alignItems: "flex-end", paddingBottom: inset };
2308
+ return { justifyContent: "center", alignItems: "center" };
2309
+ };
2310
+ function getItemStyle(progress, animation) {
2311
+ const clamped = Math.max(0, Math.min(1, progress));
2312
+ switch (animation) {
2313
+ case "fade":
2314
+ return { opacity: clamped };
2315
+ case "slideDown":
2316
+ return { opacity: clamped, transform: `translateY(${-20 * (1 - clamped)}px)` };
2317
+ case "scale":
2318
+ return { opacity: clamped, transform: `scale(${0.9 + 0.1 * clamped})` };
2319
+ case "rotate":
2320
+ return { opacity: clamped, transform: `rotate(${(-10 * (1 - clamped)).toFixed(2)}deg)` };
2321
+ case "slideUp":
2322
+ default:
2323
+ return { opacity: clamped, transform: `translateY(${20 * (1 - clamped)}px)` };
2324
+ }
2325
+ }
2326
+ var SplitText = ({
2327
+ content,
2328
+ fontSize,
2329
+ color,
2330
+ fontFamily,
2331
+ position,
2332
+ splitBy,
2333
+ stagger,
2334
+ animation,
2335
+ maxWidthPct,
2336
+ safeInsetPct
2337
+ }) => {
2338
+ const frame = (0, import_remotion29.useCurrentFrame)();
2339
+ const { fps } = (0, import_remotion29.useVideoConfig)();
2340
+ const items = splitBy === "letter" ? content.split("") : content.trim().split(/\s+/);
2341
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Fill, { style: { display: "flex", ...positionStyles4(position, safeInsetPct) }, children: /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2342
+ "div",
2343
+ {
2344
+ style: {
2345
+ display: "flex",
2346
+ flexWrap: "wrap",
2347
+ justifyContent: "center",
2348
+ gap: splitBy === "letter" ? 0 : 12,
2349
+ maxWidth: `${Math.round(maxWidthPct * 100)}%`,
2350
+ fontSize,
2351
+ color,
2352
+ fontFamily,
2353
+ fontWeight: 700,
2354
+ textAlign: "center",
2355
+ overflowWrap: "anywhere",
2356
+ wordBreak: "break-word"
2357
+ },
2358
+ children: items.map((t, i) => {
2359
+ const progress = (0, import_remotion29.spring)({
2360
+ frame: frame - i * stagger,
2361
+ fps,
2362
+ config: { damping: 14, stiffness: 120, mass: 0.9 }
2363
+ });
2364
+ return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
2365
+ "span",
2366
+ {
2367
+ style: {
2368
+ display: "inline-block",
2369
+ whiteSpace: t === " " ? "pre" : "pre-wrap",
2370
+ ...getItemStyle(progress, animation)
2371
+ },
2372
+ children: splitBy === "word" ? `${t} ` : t
2373
+ },
2374
+ `${i}-${t}`
2375
+ );
2376
+ })
2377
+ }
2378
+ ) });
2379
+ };
2380
+ var SplitTextComponentMetadata = {
2381
+ kind: "composite",
2382
+ category: "text",
2383
+ description: "Animated text where each word or letter enters with a staggered effect",
2384
+ llmGuidance: 'Use for titles. splitBy="word" is best for phrases; splitBy="letter" is dramatic for short words.',
2385
+ examples: [
2386
+ { content: "Welcome to Waves", splitBy: "word", stagger: 3, animation: "slideUp" },
2387
+ { content: "HELLO", splitBy: "letter", stagger: 2, animation: "scale" }
2388
+ ]
2389
+ };
2390
+
2391
+ // src/components/composites/SubtitleText.tsx
2392
+ var import_remotion30 = require("remotion");
2393
+ var import_zod41 = require("zod");
2394
+ var import_jsx_runtime40 = require("react/jsx-runtime");
2395
+ var SubtitleTextPropsSchema = import_zod41.z.object({
2396
+ text: import_zod41.z.string().max(200),
2397
+ fontSize: import_zod41.z.number().min(12).max(80).default(36),
2398
+ color: import_zod41.z.string().default("#FFFFFF"),
2399
+ backgroundColor: import_zod41.z.string().default("rgba(0,0,0,0.7)"),
2400
+ fontFamily: import_zod41.z.string().default("Inter"),
2401
+ position: import_zod41.z.enum(["top", "bottom"]).default("bottom"),
2402
+ maxWidth: import_zod41.z.number().min(200).max(1200).default(800),
2403
+ padding: import_zod41.z.number().min(0).max(80).default(20),
2404
+ highlightWords: import_zod41.z.array(import_zod41.z.string()).optional()
2405
+ });
2406
+ function normalizeWord(w) {
2407
+ return w.toLowerCase().replace(/[^a-z0-9]/g, "");
2408
+ }
2409
+ var SubtitleText = ({
2410
+ text,
2411
+ fontSize,
2412
+ color,
2413
+ backgroundColor,
2414
+ fontFamily,
2415
+ position,
2416
+ maxWidth,
2417
+ padding,
2418
+ highlightWords,
2419
+ __wavesDurationInFrames
2420
+ }) => {
2421
+ const frame = (0, import_remotion30.useCurrentFrame)();
2422
+ const total = Math.max(1, __wavesDurationInFrames ?? 1);
2423
+ const inFade = (0, import_remotion30.interpolate)(frame, [0, 10], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2424
+ const outFade = (0, import_remotion30.interpolate)(frame, [Math.max(0, total - 10), total], [1, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2425
+ const opacity = inFade * outFade;
2426
+ const highlights = new Set((highlightWords ?? []).map(normalizeWord));
2427
+ const tokens = text.split(/\s+/);
2428
+ const yStyle = position === "top" ? { top: 70 } : { bottom: 90 };
2429
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Fill, { children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { style: { position: "absolute", left: "50%", transform: "translateX(-50%)", maxWidth, width: "100%", ...yStyle }, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2430
+ "div",
2431
+ {
2432
+ style: {
2433
+ display: "inline-block",
2434
+ backgroundColor,
2435
+ padding,
2436
+ borderRadius: 18,
2437
+ opacity,
2438
+ color,
2439
+ fontSize,
2440
+ fontFamily,
2441
+ fontWeight: 800,
2442
+ lineHeight: 1.2,
2443
+ textAlign: "center"
2444
+ },
2445
+ children: tokens.map((t, i) => {
2446
+ const n = normalizeWord(t);
2447
+ const isHighlighted = highlights.has(n);
2448
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { style: { color: isHighlighted ? "#FFD60A" : color, marginRight: 8 }, children: t }, i);
2449
+ })
2450
+ }
2451
+ ) }) });
2452
+ };
2453
+ var SubtitleTextComponentMetadata = {
2454
+ kind: "composite",
2455
+ category: "text",
2456
+ description: "Caption/subtitle box with fade in/out and optional highlighted words",
2457
+ llmGuidance: "Use for narration/captions. highlightWords helps emphasize key terms."
2458
+ };
2459
+
2460
+ // src/components/composites/TikTokCaption.tsx
2461
+ var import_remotion31 = require("remotion");
2462
+ var import_zod42 = require("zod");
2463
+ var import_jsx_runtime41 = require("react/jsx-runtime");
2464
+ var TikTokCaptionPropsSchema = import_zod42.z.object({
2465
+ text: import_zod42.z.string().max(150),
2466
+ fontSize: import_zod42.z.number().min(12).max(120).default(48),
2467
+ color: import_zod42.z.string().default("#FFFFFF"),
2468
+ strokeColor: import_zod42.z.string().default("#000000"),
2469
+ strokeWidth: import_zod42.z.number().min(0).max(10).default(3),
2470
+ position: import_zod42.z.enum(["center", "bottom"]).default("center"),
2471
+ highlightStyle: import_zod42.z.enum(["word", "bounce", "none"]).default("word"),
2472
+ maxWidthPct: import_zod42.z.number().min(0.1).max(1).default(0.92),
2473
+ safeInsetPct: import_zod42.z.number().min(0).max(0.25).default(0.06)
2474
+ });
2475
+ var TikTokCaption = ({
2476
+ text,
2477
+ fontSize,
2478
+ color,
2479
+ strokeColor,
2480
+ strokeWidth,
2481
+ position,
2482
+ highlightStyle,
2483
+ maxWidthPct,
2484
+ safeInsetPct,
2485
+ __wavesDurationInFrames
2486
+ }) => {
2487
+ const frame = (0, import_remotion31.useCurrentFrame)();
2488
+ const total = Math.max(1, __wavesDurationInFrames ?? 1);
2489
+ const p = (0, import_remotion31.interpolate)(frame, [0, total], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2490
+ const words = text.trim().split(/\s+/);
2491
+ const idx = highlightStyle === "none" ? -1 : Math.min(words.length - 1, Math.floor(p * words.length));
2492
+ const inset = `${(safeInsetPct * 100).toFixed(2)}%`;
2493
+ const yStyle = position === "bottom" ? { alignItems: "flex-end", paddingBottom: inset } : { alignItems: "center" };
2494
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(Fill, { style: { display: "flex", justifyContent: "center", ...yStyle }, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { style: { textAlign: "center", paddingLeft: inset, paddingRight: inset, maxWidth: `${Math.round(maxWidthPct * 100)}%`, fontWeight: 900, lineHeight: 1.1, overflowWrap: "anywhere", wordBreak: "break-word" }, children: words.map((w, i) => {
2495
+ const isActive = i === idx && highlightStyle !== "none";
2496
+ const bounce = isActive && highlightStyle === "bounce" ? (0, import_remotion31.interpolate)(frame % 18, [0, 9, 18], [1, 1.08, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" }) : 1;
2497
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
2498
+ "span",
2499
+ {
2500
+ style: {
2501
+ display: "inline-block",
2502
+ marginRight: 12,
2503
+ fontSize,
2504
+ color: isActive ? "#FFD60A" : color,
2505
+ WebkitTextStroke: `${strokeWidth}px ${strokeColor}`,
2506
+ transform: `scale(${bounce})`
2507
+ },
2508
+ children: w
2509
+ },
2510
+ `${i}-${w}`
2511
+ );
2512
+ }) }) });
2513
+ };
2514
+ var TikTokCaptionComponentMetadata = {
2515
+ kind: "composite",
2516
+ category: "social",
2517
+ description: "TikTok-style captions with stroke and optional word highlighting",
2518
+ llmGuidance: 'Always keep strokeWidth>=2 for readability. highlightStyle="word" or "bounce" makes captions feel dynamic.'
2519
+ };
2520
+
2521
+ // src/components/composites/ThirdLowerBanner.tsx
2522
+ var import_remotion32 = require("remotion");
2523
+ var import_zod43 = require("zod");
2524
+ var import_jsx_runtime42 = require("react/jsx-runtime");
2525
+ var ThirdLowerBannerPropsSchema = import_zod43.z.object({
2526
+ name: import_zod43.z.string().max(50),
2527
+ title: import_zod43.z.string().max(100),
2528
+ backgroundColor: import_zod43.z.string().default("rgba(0,0,0,0.8)"),
2529
+ primaryColor: import_zod43.z.string().default("#FFFFFF"),
2530
+ secondaryColor: import_zod43.z.string().default("#CCCCCC"),
2531
+ accentColor: import_zod43.z.string().default("#FF0000"),
2532
+ showAvatar: import_zod43.z.boolean().default(false),
2533
+ avatarSrc: import_zod43.z.string().optional()
2534
+ });
2535
+ var resolveAsset13 = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion32.staticFile)(staticFileInputFromAssetPath(value));
2536
+ var ThirdLowerBanner = ({
2537
+ name,
2538
+ title,
2539
+ backgroundColor,
2540
+ primaryColor,
2541
+ secondaryColor,
2542
+ accentColor,
2543
+ showAvatar,
2544
+ avatarSrc
2545
+ }) => {
2546
+ const frame = (0, import_remotion32.useCurrentFrame)();
2547
+ const slide = (0, import_remotion32.interpolate)(frame, [0, 18], [-600, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2548
+ const opacity = (0, import_remotion32.interpolate)(frame, [0, 10], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2549
+ const avatar = showAvatar && typeof avatarSrc === "string" && avatarSrc.length > 0 ? avatarSrc : null;
2550
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(Fill, { children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
2551
+ "div",
2552
+ {
2553
+ style: {
2554
+ position: "absolute",
2555
+ left: 80,
2556
+ bottom: 80,
2557
+ width: 980,
2558
+ height: 160,
2559
+ transform: `translateX(${slide}px)`,
2560
+ opacity,
2561
+ display: "flex",
2562
+ overflow: "hidden",
2563
+ borderRadius: 18,
2564
+ backgroundColor
2565
+ },
2566
+ children: [
2567
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { style: { width: 10, backgroundColor: accentColor } }),
2568
+ avatar ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { style: { width: 160, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2569
+ import_remotion32.Img,
2570
+ {
2571
+ src: resolveAsset13(avatar),
2572
+ style: { width: 110, height: 110, borderRadius: 9999, objectFit: "cover" }
2573
+ }
2574
+ ) }) : null,
2575
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("div", { style: { padding: "28px 36px", display: "flex", flexDirection: "column", justifyContent: "center" }, children: [
2576
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { style: { color: primaryColor, fontSize: 54, fontWeight: 800, lineHeight: 1 }, children: name }),
2577
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("div", { style: { marginTop: 10, color: secondaryColor, fontSize: 28, fontWeight: 600 }, children: title })
2578
+ ] })
2579
+ ]
2580
+ }
2581
+ ) });
2582
+ };
2583
+ var ThirdLowerBannerComponentMetadata = {
2584
+ kind: "composite",
2585
+ category: "layout",
2586
+ description: "Broadcast-style lower-third banner with name/title and optional avatar",
2587
+ llmGuidance: "Use for speaker introductions. name = big label, title = smaller subtitle. showAvatar + avatarSrc for profile image.",
2588
+ examples: [
2589
+ { name: "Alex Chen", title: "Product Designer", accentColor: "#FF3B30" },
2590
+ { name: "Depths AI", title: "Waves v0.2.0", showAvatar: false }
2591
+ ]
2592
+ };
2593
+
2594
+ // src/components/composites/TypewriterText.tsx
2595
+ var import_remotion33 = require("remotion");
2596
+ var import_zod44 = require("zod");
2597
+ var import_jsx_runtime43 = require("react/jsx-runtime");
2598
+ var TypewriterTextPropsSchema = import_zod44.z.object({
2599
+ content: import_zod44.z.string().max(500),
2600
+ fontSize: import_zod44.z.number().min(8).max(200).default(48),
2601
+ color: import_zod44.z.string().default("#FFFFFF"),
2602
+ fontFamily: import_zod44.z.string().default("Inter"),
2603
+ position: import_zod44.z.enum(["top", "center", "bottom"]).default("center"),
2604
+ speed: import_zod44.z.number().min(0.5).max(5).default(2),
2605
+ showCursor: import_zod44.z.boolean().default(true),
2606
+ cursorColor: import_zod44.z.string().default("#FFFFFF"),
2607
+ maxWidthPct: import_zod44.z.number().min(0.1).max(1).default(0.9),
2608
+ safeInsetPct: import_zod44.z.number().min(0).max(0.25).default(0.06)
2609
+ });
2610
+ var positionStyles5 = (position, safeInsetPct) => {
2611
+ const inset = `${(safeInsetPct * 100).toFixed(2)}%`;
2612
+ if (position === "top") return { justifyContent: "center", alignItems: "flex-start", paddingTop: inset };
2613
+ if (position === "bottom") return { justifyContent: "center", alignItems: "flex-end", paddingBottom: inset };
2614
+ return { justifyContent: "center", alignItems: "center" };
2615
+ };
2616
+ var TypewriterText = ({
2617
+ content,
2618
+ fontSize,
2619
+ color,
2620
+ fontFamily,
2621
+ position,
2622
+ speed,
2623
+ showCursor,
2624
+ cursorColor,
2625
+ maxWidthPct,
2626
+ safeInsetPct
2627
+ }) => {
2628
+ const frame = (0, import_remotion33.useCurrentFrame)();
2629
+ const charCount = Math.min(content.length, Math.max(0, Math.floor(frame * speed)));
2630
+ const shown = content.slice(0, charCount);
2631
+ const cursorVisible = showCursor && charCount < content.length ? frame % 30 < 15 : false;
2632
+ const cursorOpacity = cursorVisible ? 1 : 0;
2633
+ const cursorFade = (0, import_remotion33.interpolate)(frame % 30, [0, 5], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2634
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(Fill, { style: { display: "flex", ...positionStyles5(position, safeInsetPct) }, children: /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { style: { fontSize, color, fontFamily, fontWeight: 600, textAlign: "center", whiteSpace: "pre-wrap", maxWidth: `${Math.round(maxWidthPct * 100)}%`, overflowWrap: "anywhere", wordBreak: "break-word" }, children: [
2635
+ shown,
2636
+ showCursor ? /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { style: { color: cursorColor, opacity: cursorOpacity * cursorFade }, children: "|" }) : null
2637
+ ] }) });
2638
+ };
2639
+ var TypewriterTextComponentMetadata = {
2640
+ kind: "composite",
2641
+ category: "text",
2642
+ description: "Character-by-character text reveal with optional blinking cursor",
2643
+ llmGuidance: "Use for dramatic reveals and terminal-style text. speed ~1-2 is readable; 3-5 is fast.",
2644
+ examples: [
2645
+ { content: "Hello Waves", speed: 2, position: "center", fontSize: 72 },
2646
+ { content: 'console.log("hi")', speed: 1.5, fontFamily: "monospace", position: "top" }
2647
+ ]
2648
+ };
2649
+
2650
+ // src/components/composites/TwitterCard.tsx
2651
+ var import_remotion34 = require("remotion");
2652
+ var import_zod45 = require("zod");
2653
+ var import_jsx_runtime44 = require("react/jsx-runtime");
2654
+ var TwitterCardPropsSchema = import_zod45.z.object({
2655
+ author: import_zod45.z.string().min(1),
2656
+ handle: import_zod45.z.string().min(1),
2657
+ avatarSrc: import_zod45.z.string().optional(),
2658
+ tweet: import_zod45.z.string().max(280),
2659
+ image: import_zod45.z.string().optional(),
2660
+ timestamp: import_zod45.z.string().optional(),
2661
+ verified: import_zod45.z.boolean().default(false)
2662
+ });
2663
+ var resolveAsset14 = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion34.staticFile)(staticFileInputFromAssetPath(value));
2664
+ var TwitterCard = ({ author, handle, avatarSrc, tweet, image, timestamp, verified }) => {
2665
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(Fill, { style: { backgroundColor: "#0B0F14", display: "flex", justifyContent: "center", alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
2666
+ "div",
2667
+ {
2668
+ style: {
2669
+ width: 1100,
2670
+ borderRadius: 28,
2671
+ backgroundColor: "#FFFFFF",
2672
+ padding: 48,
2673
+ boxSizing: "border-box",
2674
+ boxShadow: "0 30px 90px rgba(0,0,0,0.35)"
2675
+ },
2676
+ children: [
2677
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { style: { display: "flex", gap: 18, alignItems: "center" }, children: [
2678
+ avatarSrc ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_remotion34.Img, { src: resolveAsset14(avatarSrc), style: { width: 78, height: 78, borderRadius: 9999, objectFit: "cover" } }) : /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { style: { width: 78, height: 78, borderRadius: 9999, backgroundColor: "#E5E7EB" } }),
2679
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { style: { display: "flex", flexDirection: "column" }, children: [
2680
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { style: { display: "flex", gap: 10, alignItems: "center" }, children: [
2681
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { style: { fontSize: 34, fontWeight: 900, color: "#111827" }, children: author }),
2682
+ verified ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { style: { fontSize: 26, color: "#1D9BF0", fontWeight: 900 }, children: "\u2713" }) : null
2683
+ ] }),
2684
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { style: { fontSize: 26, color: "#6B7280", fontWeight: 800 }, children: handle })
2685
+ ] })
2686
+ ] }),
2687
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { style: { marginTop: 28, fontSize: 36, lineHeight: 1.25, color: "#111827", fontWeight: 700 }, children: tweet }),
2688
+ image ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { style: { marginTop: 28, width: "100%", height: 520, overflow: "hidden", borderRadius: 22, backgroundColor: "#F3F4F6" }, children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_remotion34.Img, { src: resolveAsset14(image), style: { width: "100%", height: "100%", objectFit: "cover" } }) }) : null,
2689
+ timestamp ? /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { style: { marginTop: 22, fontSize: 22, color: "#6B7280", fontWeight: 700 }, children: timestamp }) : null
2690
+ ]
2691
+ }
2692
+ ) });
2693
+ };
2694
+ var TwitterCardComponentMetadata = {
2695
+ kind: "composite",
2696
+ category: "social",
2697
+ description: "Twitter/X post card layout with author header and optional image",
2698
+ llmGuidance: "Use for announcements/testimonials. Keep tweet short for readability."
2699
+ };
2700
+
2701
+ // src/components/composites/Watermark.tsx
2702
+ var import_remotion35 = require("remotion");
2703
+ var import_zod46 = require("zod");
2704
+ var import_jsx_runtime45 = require("react/jsx-runtime");
2705
+ var WatermarkPropsSchema = import_zod46.z.object({
2706
+ type: import_zod46.z.enum(["logo", "text"]).default("logo"),
2707
+ src: import_zod46.z.string().optional(),
2708
+ text: import_zod46.z.string().optional(),
2709
+ position: import_zod46.z.enum(["topLeft", "topRight", "bottomLeft", "bottomRight"]).default("bottomRight"),
2710
+ opacity: import_zod46.z.number().min(0.1).max(1).default(0.5),
2711
+ size: import_zod46.z.number().min(30).max(150).default(60),
2712
+ color: import_zod46.z.string().default("#FFFFFF")
2713
+ });
2714
+ var resolveAsset15 = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion35.staticFile)(staticFileInputFromAssetPath(value));
2715
+ function posStyle(position) {
2716
+ const offset = 30;
2717
+ if (position === "topLeft") return { left: offset, top: offset };
2718
+ if (position === "topRight") return { right: offset, top: offset };
2719
+ if (position === "bottomLeft") return { left: offset, bottom: offset };
2720
+ return { right: offset, bottom: offset };
2721
+ }
2722
+ var Watermark = ({ type, src, text, position, opacity, size, color }) => {
2723
+ const style = {
2724
+ position: "absolute",
2725
+ ...posStyle(position),
2726
+ opacity,
2727
+ pointerEvents: "none"
2728
+ };
2729
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Fill, { children: type === "text" ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("div", { style: { ...style, fontSize: Math.round(size * 0.6), color, fontWeight: 700 }, children: text ?? "" }) : src ? /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
2730
+ import_remotion35.Img,
2731
+ {
2732
+ src: resolveAsset15(src),
2733
+ style: { ...style, width: size, height: size, objectFit: "contain" }
2734
+ }
2735
+ ) : null });
2736
+ };
2737
+ var WatermarkComponentMetadata = {
2738
+ kind: "composite",
2739
+ category: "branding",
2740
+ description: "Persistent logo/text watermark in a corner",
2741
+ llmGuidance: "Use subtle opacity (0.3-0.6). bottomRight is standard.",
2742
+ examples: [
2743
+ { type: "text", text: "@depths.ai", position: "bottomRight", opacity: 0.4, size: 60 },
2744
+ { type: "logo", src: "/assets/logo.svg", position: "topLeft", opacity: 0.5, size: 70 }
2745
+ ]
2746
+ };
2747
+
2748
+ // src/components/composites/WipeTransition.tsx
2749
+ var import_react9 = __toESM(require("react"));
2750
+ var import_remotion36 = require("remotion");
2751
+ var import_zod47 = require("zod");
2752
+ var import_jsx_runtime46 = require("react/jsx-runtime");
2753
+ var WipeTransitionPropsSchema = import_zod47.z.object({
2754
+ durationInFrames: import_zod47.z.number().int().min(10).max(60).default(30),
2755
+ direction: import_zod47.z.enum(["left", "right", "up", "down", "diagonal"]).default("right"),
2756
+ softEdge: import_zod47.z.boolean().default(false),
2757
+ phase: import_zod47.z.enum(["in", "out", "inOut"]).default("inOut")
2758
+ });
2759
+ function clipFor2(direction, p) {
2760
+ if (direction === "left") return `inset(0 ${100 * (1 - p)}% 0 0)`;
2761
+ if (direction === "right") return `inset(0 0 0 ${100 * (1 - p)}%)`;
2762
+ if (direction === "up") return `inset(0 0 ${100 * (1 - p)}% 0)`;
2763
+ if (direction === "down") return `inset(${100 * (1 - p)}% 0 0 0)`;
2764
+ return `polygon(0 0, ${100 * p}% 0, 0 ${100 * p}%)`;
2765
+ }
2766
+ var WipeTransition = ({
2767
+ durationInFrames,
2768
+ direction,
2769
+ softEdge,
2770
+ phase,
2771
+ children,
2772
+ __wavesDurationInFrames
2773
+ }) => {
2774
+ const layers = import_react9.default.Children.toArray(children);
2775
+ const frame = (0, import_remotion36.useCurrentFrame)();
2776
+ const total = Math.max(1, __wavesDurationInFrames ?? 1);
2777
+ const d = Math.min(durationInFrames, total);
2778
+ let clipPath;
2779
+ if (phase === "in" || phase === "inOut") {
2780
+ const t = (0, import_remotion36.interpolate)(frame, [0, d], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2781
+ clipPath = clipFor2(direction, t);
2782
+ }
2783
+ if (phase === "out" || phase === "inOut") {
2784
+ const start = Math.max(0, total - d);
2785
+ const t = (0, import_remotion36.interpolate)(frame, [start, total], [1, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2786
+ clipPath = clipFor2(direction, t);
2787
+ }
2788
+ return /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(Fill, { style: { clipPath, filter: softEdge ? "blur(0.4px)" : void 0 }, children: layers.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("div", { style: { position: "absolute", inset: 0 }, children: child }, i)) });
2789
+ };
2790
+ var WipeTransitionComponentMetadata = {
2791
+ kind: "composite",
2792
+ category: "transition",
2793
+ acceptsChildren: true,
2794
+ minChildren: 1,
2795
+ description: "Directional wipe reveal/hide wrapper transition",
2796
+ llmGuidance: "Use as a more stylized reveal. softEdge can make it feel less harsh."
2797
+ };
2798
+
2799
+ // src/components/composites/YouTubeThumbnail.tsx
2800
+ var import_remotion37 = require("remotion");
2801
+ var import_zod48 = require("zod");
2802
+ var import_jsx_runtime47 = require("react/jsx-runtime");
2803
+ var YouTubeThumbnailPropsSchema = import_zod48.z.object({
2804
+ backgroundImage: import_zod48.z.string().min(1),
2805
+ title: import_zod48.z.string().max(60),
2806
+ subtitle: import_zod48.z.string().max(40).optional(),
2807
+ thumbnailFace: import_zod48.z.string().optional(),
2808
+ accentColor: import_zod48.z.string().default("#FF0000"),
2809
+ style: import_zod48.z.enum(["bold", "minimal", "dramatic"]).default("bold")
2810
+ });
2811
+ var resolveAsset16 = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion37.staticFile)(staticFileInputFromAssetPath(value));
2812
+ var YouTubeThumbnail = ({
2813
+ backgroundImage,
2814
+ title,
2815
+ subtitle,
2816
+ thumbnailFace,
2817
+ accentColor,
2818
+ style
2819
+ }) => {
2820
+ return /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)(Fill, { children: [
2821
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(import_remotion37.Img, { src: resolveAsset16(backgroundImage), style: { width: "100%", height: "100%", objectFit: "cover" } }),
2822
+ style === "dramatic" ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { style: { position: "absolute", inset: 0, backgroundColor: "rgba(0,0,0,0.35)" } }) : null,
2823
+ thumbnailFace ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
2824
+ import_remotion37.Img,
2825
+ {
2826
+ src: resolveAsset16(thumbnailFace),
2827
+ style: { position: "absolute", right: 60, bottom: 0, width: 520, height: 720, objectFit: "cover" }
2828
+ }
2829
+ ) : null,
2830
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsxs)("div", { style: { position: "absolute", left: 70, top: 90, width: 1100 }, children: [
2831
+ /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
2832
+ "div",
2833
+ {
2834
+ style: {
2835
+ fontSize: style === "minimal" ? 86 : 110,
2836
+ fontWeight: 1e3,
2837
+ color: "#FFFFFF",
2838
+ textTransform: "uppercase",
2839
+ lineHeight: 0.95,
2840
+ WebkitTextStroke: style === "minimal" ? "0px transparent" : "10px #000000",
2841
+ textShadow: style === "minimal" ? "0 10px 40px rgba(0,0,0,0.6)" : void 0
2842
+ },
2843
+ children: title
2844
+ }
2845
+ ),
2846
+ subtitle ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { style: { marginTop: 26, fontSize: 52, fontWeight: 900, color: accentColor, textShadow: "0 10px 30px rgba(0,0,0,0.6)" }, children: subtitle }) : null
2847
+ ] }),
2848
+ style !== "minimal" ? /* @__PURE__ */ (0, import_jsx_runtime47.jsx)("div", { style: { position: "absolute", left: 70, bottom: 80, width: 260, height: 18, backgroundColor: accentColor, borderRadius: 9999 } }) : null
2849
+ ] });
2850
+ };
2851
+ var YouTubeThumbnailComponentMetadata = {
2852
+ kind: "composite",
2853
+ category: "social",
2854
+ description: "YouTube-style thumbnail layout (16:9) with bold title and optional face cutout",
2855
+ llmGuidance: 'Use 1280x720 video size. Keep title short and high-contrast. style="bold" is classic thumbnail.'
2856
+ };
2857
+
2858
+ // src/components/composites/VideoWithOverlay.tsx
2859
+ var import_remotion38 = require("remotion");
2860
+ var import_zod49 = require("zod");
2861
+ var import_jsx_runtime48 = require("react/jsx-runtime");
2862
+ var OverlaySchema = import_zod49.z.object({
2863
+ type: import_zod49.z.enum(["text", "logo", "gradient"]),
2864
+ content: import_zod49.z.string().optional(),
2865
+ opacity: import_zod49.z.number().min(0).max(1).default(0.3),
2866
+ position: import_zod49.z.enum(["top", "bottom", "center", "full"]).default("bottom")
2867
+ });
2868
+ var VideoWithOverlayPropsSchema = import_zod49.z.object({
2869
+ src: import_zod49.z.string().min(1),
2870
+ overlay: OverlaySchema.optional(),
2871
+ volume: import_zod49.z.number().min(0).max(1).default(1),
2872
+ playbackRate: import_zod49.z.number().min(0.5).max(2).default(1)
2873
+ });
2874
+ var resolveAsset17 = (value) => isRemoteAssetPath(value) ? value : (0, import_remotion38.staticFile)(staticFileInputFromAssetPath(value));
2875
+ var VideoWithOverlay = ({ src, overlay, volume, playbackRate }) => {
2876
+ const frame = (0, import_remotion38.useCurrentFrame)();
2877
+ const fade = (0, import_remotion38.interpolate)(frame, [0, 20], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2878
+ const overlayFill = { position: "absolute", inset: 0 };
2879
+ const overlayNode = overlay ? overlay.type === "gradient" ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
2880
+ "div",
2881
+ {
2882
+ style: {
2883
+ ...overlayFill,
2884
+ opacity: overlay.opacity,
2885
+ background: overlay.position === "top" ? "linear-gradient(rgba(0,0,0,0.85), transparent)" : overlay.position === "bottom" ? "linear-gradient(transparent, rgba(0,0,0,0.85))" : "linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6))"
2886
+ }
2887
+ }
2888
+ ) : overlay.type === "logo" && overlay.content ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { style: { ...overlayFill, display: "flex", justifyContent: "center", alignItems: "center", opacity: overlay.opacity }, children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_remotion38.Img, { src: resolveAsset17(overlay.content), style: { width: 220, height: 220, objectFit: "contain" } }) }) : overlay.type === "text" && overlay.content ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { style: { ...overlayFill, display: "flex", justifyContent: "center", alignItems: "center", opacity: overlay.opacity }, children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("div", { style: { color: "#FFFFFF", fontSize: 56, fontWeight: 900, textAlign: "center", padding: "0 80px" }, children: overlay.content }) }) : null : null;
2889
+ return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(Fill, { children: [
2890
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
2891
+ import_remotion38.Video,
2892
+ {
2893
+ src: resolveAsset17(src),
2894
+ style: { width: "100%", height: "100%", objectFit: "cover", opacity: fade },
2895
+ muted: volume === 0,
2896
+ playbackRate
2897
+ }
2898
+ ),
2899
+ overlayNode
2900
+ ] });
2901
+ };
2902
+ var VideoWithOverlayComponentMetadata = {
2903
+ kind: "composite",
2904
+ category: "media",
2905
+ description: "Video background with an optional overlay (text/logo/gradient)",
2906
+ llmGuidance: "Use gradient overlay to improve text readability. Set volume=0 to mute."
2907
+ };
2908
+
2909
+ // src/components/composites/ZoomTransition.tsx
2910
+ var import_react10 = __toESM(require("react"));
2911
+ var import_remotion39 = require("remotion");
2912
+ var import_zod50 = require("zod");
2913
+ var import_jsx_runtime49 = require("react/jsx-runtime");
2914
+ var ZoomTransitionPropsSchema = import_zod50.z.object({
2915
+ durationInFrames: import_zod50.z.number().int().min(10).max(60).default(30),
2916
+ type: import_zod50.z.enum(["zoomIn", "zoomOut"]).default("zoomIn"),
2917
+ phase: import_zod50.z.enum(["in", "out", "inOut"]).default("inOut")
2918
+ });
2919
+ var ZoomTransition = ({
2920
+ durationInFrames,
2921
+ type,
2922
+ phase,
2923
+ children,
2924
+ __wavesDurationInFrames
2925
+ }) => {
2926
+ const layers = import_react10.default.Children.toArray(children);
2927
+ const frame = (0, import_remotion39.useCurrentFrame)();
2928
+ const total = Math.max(1, __wavesDurationInFrames ?? 1);
2929
+ const d = Math.min(durationInFrames, total);
2930
+ let opacity = 1;
2931
+ let scale = 1;
2932
+ const enterFrom = type === "zoomIn" ? 1.2 : 0.85;
2933
+ const exitTo = type === "zoomIn" ? 1.25 : 0.75;
2934
+ if (phase === "in" || phase === "inOut") {
2935
+ const t = (0, import_remotion39.interpolate)(frame, [0, d], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2936
+ scale *= enterFrom + (1 - enterFrom) * t;
2937
+ opacity *= t;
2938
+ }
2939
+ if (phase === "out" || phase === "inOut") {
2940
+ const start = Math.max(0, total - d);
2941
+ const t = (0, import_remotion39.interpolate)(frame, [start, total], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
2942
+ scale *= 1 + (exitTo - 1) * t;
2943
+ opacity *= 1 - t;
2944
+ }
2945
+ return /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(Fill, { style: { opacity, transform: `scale(${scale})` }, children: layers.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime49.jsx)("div", { style: { position: "absolute", inset: 0 }, children: child }, i)) });
2946
+ };
2947
+ var ZoomTransitionComponentMetadata = {
2948
+ kind: "composite",
2949
+ category: "transition",
2950
+ acceptsChildren: true,
2951
+ minChildren: 1,
2952
+ description: "Zoom in/out wrapper transition",
2953
+ llmGuidance: 'Use for energetic cuts. type="zoomIn" feels punchy; type="zoomOut" feels calmer.'
2954
+ };
2955
+
2956
+ // src/components/registry.ts
2957
+ function registerBuiltInComponents() {
2958
+ if (!globalRegistry.has("Segment")) {
2959
+ globalRegistry.register({
2960
+ type: "Segment",
2961
+ component: Segment,
2962
+ propsSchema: SegmentPropsSchema,
2963
+ metadata: SegmentComponentMetadata
2964
+ });
2965
+ }
2966
+ if (!globalRegistry.has("Box")) {
2967
+ globalRegistry.register({
2968
+ type: "Box",
2969
+ component: Box,
2970
+ propsSchema: BoxPropsSchema,
2971
+ metadata: BoxComponentMetadata
2972
+ });
2973
+ }
2974
+ if (!globalRegistry.has("Frame")) {
2975
+ globalRegistry.register({
2976
+ type: "Frame",
2977
+ component: Frame,
2978
+ propsSchema: FramePropsSchema,
2979
+ metadata: FrameComponentMetadata
2980
+ });
2981
+ }
2982
+ if (!globalRegistry.has("Stack")) {
2983
+ globalRegistry.register({
2984
+ type: "Stack",
2985
+ component: Stack,
2986
+ propsSchema: StackPropsSchema,
2987
+ metadata: StackComponentMetadata
2988
+ });
2989
+ }
2990
+ if (!globalRegistry.has("Grid")) {
2991
+ globalRegistry.register({
2992
+ type: "Grid",
2993
+ component: Grid,
2994
+ propsSchema: GridPropsSchema,
2995
+ metadata: GridComponentMetadata
2996
+ });
2997
+ }
2998
+ if (!globalRegistry.has("Layers")) {
2999
+ globalRegistry.register({
3000
+ type: "Layers",
3001
+ component: Layers,
3002
+ propsSchema: LayersPropsSchema,
3003
+ metadata: LayersComponentMetadata
3004
+ });
3005
+ }
3006
+ if (!globalRegistry.has("Layer")) {
3007
+ globalRegistry.register({
3008
+ type: "Layer",
3009
+ component: Layer,
3010
+ propsSchema: LayerPropsSchema,
3011
+ metadata: LayerComponentMetadata
3012
+ });
3013
+ }
3014
+ if (!globalRegistry.has("Image")) {
3015
+ globalRegistry.register({
3016
+ type: "Image",
3017
+ component: Image,
3018
+ propsSchema: ImagePropsSchema,
3019
+ metadata: ImageComponentMetadata
3020
+ });
3021
+ }
3022
+ if (!globalRegistry.has("Video")) {
3023
+ globalRegistry.register({
3024
+ type: "Video",
3025
+ component: Video2,
3026
+ propsSchema: VideoPropsSchema,
3027
+ metadata: VideoComponentMetadata
3028
+ });
3029
+ }
3030
+ if (!globalRegistry.has("Shape")) {
3031
+ globalRegistry.register({
3032
+ type: "Shape",
3033
+ component: Shape,
3034
+ propsSchema: ShapePropsSchema,
3035
+ metadata: ShapeComponentMetadata
3036
+ });
3037
+ }
3038
+ if (!globalRegistry.has("TypewriterText")) {
3039
+ globalRegistry.register({
3040
+ type: "TypewriterText",
3041
+ component: TypewriterText,
3042
+ propsSchema: TypewriterTextPropsSchema,
3043
+ metadata: TypewriterTextComponentMetadata
3044
+ });
3045
+ }
3046
+ if (!globalRegistry.has("SplitText")) {
3047
+ globalRegistry.register({
3048
+ type: "SplitText",
3049
+ component: SplitText,
3050
+ propsSchema: SplitTextPropsSchema,
3051
+ metadata: SplitTextComponentMetadata
3052
+ });
3053
+ }
3054
+ if (!globalRegistry.has("KenBurnsImage")) {
3055
+ globalRegistry.register({
3056
+ type: "KenBurnsImage",
3057
+ component: KenBurnsImage,
3058
+ propsSchema: KenBurnsImagePropsSchema,
3059
+ metadata: KenBurnsImageComponentMetadata
3060
+ });
3061
+ }
3062
+ if (!globalRegistry.has("FadeTransition")) {
3063
+ globalRegistry.register({
3064
+ type: "FadeTransition",
3065
+ component: FadeTransition,
3066
+ propsSchema: FadeTransitionPropsSchema,
3067
+ metadata: FadeTransitionComponentMetadata
3068
+ });
3069
+ }
3070
+ if (!globalRegistry.has("SlideTransition")) {
3071
+ globalRegistry.register({
3072
+ type: "SlideTransition",
3073
+ component: SlideTransition,
3074
+ propsSchema: SlideTransitionPropsSchema,
3075
+ metadata: SlideTransitionComponentMetadata
3076
+ });
3077
+ }
3078
+ if (!globalRegistry.has("SplitScreen")) {
3079
+ globalRegistry.register({
3080
+ type: "SplitScreen",
3081
+ component: SplitScreen,
3082
+ propsSchema: SplitScreenPropsSchema,
3083
+ metadata: SplitScreenComponentMetadata
3084
+ });
3085
+ }
3086
+ if (!globalRegistry.has("ThirdLowerBanner")) {
3087
+ globalRegistry.register({
3088
+ type: "ThirdLowerBanner",
3089
+ component: ThirdLowerBanner,
3090
+ propsSchema: ThirdLowerBannerPropsSchema,
3091
+ metadata: ThirdLowerBannerComponentMetadata
3092
+ });
3093
+ }
3094
+ if (!globalRegistry.has("AnimatedCounter")) {
3095
+ globalRegistry.register({
3096
+ type: "AnimatedCounter",
3097
+ component: AnimatedCounter,
3098
+ propsSchema: AnimatedCounterPropsSchema,
3099
+ metadata: AnimatedCounterComponentMetadata
3100
+ });
3101
+ }
3102
+ if (!globalRegistry.has("LogoReveal")) {
3103
+ globalRegistry.register({
3104
+ type: "LogoReveal",
3105
+ component: LogoReveal,
3106
+ propsSchema: LogoRevealPropsSchema,
3107
+ metadata: LogoRevealComponentMetadata
3108
+ });
3109
+ }
3110
+ if (!globalRegistry.has("Watermark")) {
3111
+ globalRegistry.register({
3112
+ type: "Watermark",
3113
+ component: Watermark,
3114
+ propsSchema: WatermarkPropsSchema,
3115
+ metadata: WatermarkComponentMetadata
3116
+ });
3117
+ }
3118
+ if (!globalRegistry.has("GlitchText")) {
3119
+ globalRegistry.register({
3120
+ type: "GlitchText",
3121
+ component: GlitchText,
3122
+ propsSchema: GlitchTextPropsSchema,
3123
+ metadata: GlitchTextComponentMetadata
3124
+ });
3125
+ }
3126
+ if (!globalRegistry.has("OutlineText")) {
3127
+ globalRegistry.register({
3128
+ type: "OutlineText",
3129
+ component: OutlineText,
3130
+ propsSchema: OutlineTextPropsSchema,
3131
+ metadata: OutlineTextComponentMetadata
3132
+ });
3133
+ }
3134
+ if (!globalRegistry.has("ImageReveal")) {
3135
+ globalRegistry.register({
3136
+ type: "ImageReveal",
3137
+ component: ImageReveal,
3138
+ propsSchema: ImageRevealPropsSchema,
3139
+ metadata: ImageRevealComponentMetadata
3140
+ });
3141
+ }
3142
+ if (!globalRegistry.has("ImageCollage")) {
3143
+ globalRegistry.register({
3144
+ type: "ImageCollage",
3145
+ component: ImageCollage,
3146
+ propsSchema: ImageCollagePropsSchema,
3147
+ metadata: ImageCollageComponentMetadata
3148
+ });
3149
+ }
3150
+ if (!globalRegistry.has("ProgressBar")) {
3151
+ globalRegistry.register({
3152
+ type: "ProgressBar",
3153
+ component: ProgressBar,
3154
+ propsSchema: ProgressBarPropsSchema,
3155
+ metadata: ProgressBarComponentMetadata
3156
+ });
3157
+ }
3158
+ if (!globalRegistry.has("ProgressRing")) {
3159
+ globalRegistry.register({
3160
+ type: "ProgressRing",
3161
+ component: ProgressRing,
3162
+ propsSchema: ProgressRingPropsSchema,
3163
+ metadata: ProgressRingComponentMetadata
3164
+ });
3165
+ }
3166
+ if (!globalRegistry.has("BarChart")) {
3167
+ globalRegistry.register({
3168
+ type: "BarChart",
3169
+ component: BarChart,
3170
+ propsSchema: BarChartPropsSchema,
3171
+ metadata: BarChartComponentMetadata
3172
+ });
3173
+ }
3174
+ if (!globalRegistry.has("InstagramStory")) {
3175
+ globalRegistry.register({
3176
+ type: "InstagramStory",
3177
+ component: InstagramStory,
3178
+ propsSchema: InstagramStoryPropsSchema,
3179
+ metadata: InstagramStoryComponentMetadata
3180
+ });
3181
+ }
3182
+ if (!globalRegistry.has("TikTokCaption")) {
3183
+ globalRegistry.register({
3184
+ type: "TikTokCaption",
3185
+ component: TikTokCaption,
3186
+ propsSchema: TikTokCaptionPropsSchema,
3187
+ metadata: TikTokCaptionComponentMetadata
3188
+ });
3189
+ }
3190
+ if (!globalRegistry.has("IntroScene")) {
3191
+ globalRegistry.register({
3192
+ type: "IntroScene",
3193
+ component: IntroScene,
3194
+ propsSchema: IntroScenePropsSchema,
3195
+ metadata: IntroSceneComponentMetadata
3196
+ });
3197
+ }
3198
+ if (!globalRegistry.has("CountUpText")) {
3199
+ globalRegistry.register({
3200
+ type: "CountUpText",
3201
+ component: CountUpText,
3202
+ propsSchema: CountUpTextPropsSchema,
3203
+ metadata: CountUpTextComponentMetadata
3204
+ });
3205
+ }
3206
+ if (!globalRegistry.has("KineticTypography")) {
3207
+ globalRegistry.register({
3208
+ type: "KineticTypography",
3209
+ component: KineticTypography,
3210
+ propsSchema: KineticTypographyPropsSchema,
3211
+ metadata: KineticTypographyComponentMetadata
3212
+ });
3213
+ }
3214
+ if (!globalRegistry.has("SubtitleText")) {
3215
+ globalRegistry.register({
3216
+ type: "SubtitleText",
3217
+ component: SubtitleText,
3218
+ propsSchema: SubtitleTextPropsSchema,
3219
+ metadata: SubtitleTextComponentMetadata
3220
+ });
3221
+ }
3222
+ if (!globalRegistry.has("ImageSequence")) {
3223
+ globalRegistry.register({
3224
+ type: "ImageSequence",
3225
+ component: ImageSequence,
3226
+ propsSchema: ImageSequencePropsSchema,
3227
+ metadata: ImageSequenceComponentMetadata
3228
+ });
3229
+ }
3230
+ if (!globalRegistry.has("ImageWithCaption")) {
3231
+ globalRegistry.register({
3232
+ type: "ImageWithCaption",
3233
+ component: ImageWithCaption,
3234
+ propsSchema: ImageWithCaptionPropsSchema,
3235
+ metadata: ImageWithCaptionComponentMetadata
3236
+ });
3237
+ }
3238
+ if (!globalRegistry.has("VideoWithOverlay")) {
3239
+ globalRegistry.register({
3240
+ type: "VideoWithOverlay",
3241
+ component: VideoWithOverlay,
3242
+ propsSchema: VideoWithOverlayPropsSchema,
3243
+ metadata: VideoWithOverlayComponentMetadata
3244
+ });
3245
+ }
3246
+ if (!globalRegistry.has("ZoomTransition")) {
3247
+ globalRegistry.register({
3248
+ type: "ZoomTransition",
3249
+ component: ZoomTransition,
3250
+ propsSchema: ZoomTransitionPropsSchema,
3251
+ metadata: ZoomTransitionComponentMetadata
3252
+ });
3253
+ }
3254
+ if (!globalRegistry.has("WipeTransition")) {
3255
+ globalRegistry.register({
3256
+ type: "WipeTransition",
3257
+ component: WipeTransition,
3258
+ propsSchema: WipeTransitionPropsSchema,
3259
+ metadata: WipeTransitionComponentMetadata
3260
+ });
3261
+ }
3262
+ if (!globalRegistry.has("CircularReveal")) {
3263
+ globalRegistry.register({
3264
+ type: "CircularReveal",
3265
+ component: CircularReveal,
3266
+ propsSchema: CircularRevealPropsSchema,
3267
+ metadata: CircularRevealComponentMetadata
3268
+ });
3269
+ }
3270
+ if (!globalRegistry.has("CardStack")) {
3271
+ globalRegistry.register({
3272
+ type: "CardStack",
3273
+ component: CardStack,
3274
+ propsSchema: CardStackPropsSchema,
3275
+ metadata: CardStackComponentMetadata
3276
+ });
3277
+ }
3278
+ if (!globalRegistry.has("GridLayout")) {
3279
+ globalRegistry.register({
3280
+ type: "GridLayout",
3281
+ component: GridLayout,
3282
+ propsSchema: GridLayoutPropsSchema,
3283
+ metadata: GridLayoutComponentMetadata
3284
+ });
3285
+ }
3286
+ if (!globalRegistry.has("LineGraph")) {
3287
+ globalRegistry.register({
3288
+ type: "LineGraph",
3289
+ component: LineGraph,
3290
+ propsSchema: LineGraphPropsSchema,
3291
+ metadata: LineGraphComponentMetadata
3292
+ });
3293
+ }
3294
+ if (!globalRegistry.has("YouTubeThumbnail")) {
3295
+ globalRegistry.register({
3296
+ type: "YouTubeThumbnail",
3297
+ component: YouTubeThumbnail,
3298
+ propsSchema: YouTubeThumbnailPropsSchema,
3299
+ metadata: YouTubeThumbnailComponentMetadata
3300
+ });
3301
+ }
3302
+ if (!globalRegistry.has("TwitterCard")) {
3303
+ globalRegistry.register({
3304
+ type: "TwitterCard",
3305
+ component: TwitterCard,
3306
+ propsSchema: TwitterCardPropsSchema,
3307
+ metadata: TwitterCardComponentMetadata
3308
+ });
3309
+ }
3310
+ if (!globalRegistry.has("OutroScene")) {
3311
+ globalRegistry.register({
3312
+ type: "OutroScene",
3313
+ component: OutroScene,
3314
+ propsSchema: OutroScenePropsSchema,
3315
+ metadata: OutroSceneComponentMetadata
3316
+ });
329
3317
  }
330
- };
331
- var Text = ({ content, fontSize, color, position, animation }) => {
332
- const frame = (0, import_remotion3.useCurrentFrame)();
333
- const positionStyles = getPositionStyles(position);
334
- const animationStyles = getAnimationStyle(frame, animation);
335
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_remotion3.AbsoluteFill, { style: positionStyles, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
336
- "div",
337
- {
338
- style: {
339
- fontSize,
340
- color,
341
- fontWeight: 600,
342
- textAlign: "center",
343
- ...animationStyles
344
- },
345
- children: content
346
- }
347
- ) });
348
- };
349
- var TextComponentMetadata = {
350
- category: "primitive",
351
- description: "Displays animated text with positioning and animation options",
352
- llmGuidance: 'Use for titles, subtitles, captions. Keep content under 100 characters for readability. Position "center" works best for titles.',
353
- examples: [
354
- {
355
- content: "Welcome to Waves",
356
- fontSize: 72,
357
- color: "#FFFFFF",
358
- position: "center",
359
- animation: "fade"
360
- },
361
- {
362
- content: "Building the future of video",
363
- fontSize: 36,
364
- color: "#CCCCCC",
365
- position: "bottom",
366
- animation: "slide"
367
- }
368
- ]
369
- };
370
-
371
- // src/components/registry.ts
372
- function registerBuiltInComponents() {
373
3318
  if (!globalRegistry.has("Scene")) {
374
3319
  globalRegistry.register({
375
3320
  type: "Scene",
@@ -400,10 +3345,10 @@ function registerBuiltInComponents() {
400
3345
  var RULES = {
401
3346
  timing: [
402
3347
  "All timing is in frames (integers).",
403
- "Every component has timing: { from, durationInFrames }.",
404
- "Scenes must be sequential: scene[0].timing.from = 0 and each next scene starts where the previous ends (no gaps/overlaps).",
405
- "Sum of all scene durations must equal video.durationInFrames.",
406
- "Child components inside a Scene must fit within the Scene duration."
3348
+ 'Prefer authoring using "segments" (high-level). Each segment has { durationInFrames, root } and start times are derived by order.',
3349
+ 'Use "timeline" only for advanced cases (explicit timings, overlaps).',
3350
+ "If you provide timing on nodes, timing is { from, durationInFrames } and children timing is relative to the parent duration.",
3351
+ "Total duration must match video.durationInFrames."
407
3352
  ],
408
3353
  assets: [
409
3354
  'Asset paths must be either full URLs (http:// or https://) or absolute public paths starting with "/".',
@@ -420,9 +3365,10 @@ var RULES = {
420
3365
  ]
421
3366
  };
422
3367
  function getSystemPrompt(registeredTypes) {
423
- const typesLine = registeredTypes.length ? registeredTypes.join(", ") : "(none)";
3368
+ const maxInline = 40;
3369
+ const typesLine = registeredTypes.length <= maxInline ? registeredTypes.length ? registeredTypes.join(", ") : "(none)" : `${registeredTypes.length} types (see schemas.components keys). Example: ${registeredTypes.slice(0, 20).join(", ")}, ...`;
424
3370
  return [
425
- "You generate JSON for @depths/waves Video IR (version 1.0).",
3371
+ "You generate JSON for @depths/waves Video IR (version 2.0).",
426
3372
  "",
427
3373
  "Goal:",
428
3374
  "- Produce a single JSON object that conforms to the provided Video IR JSON Schema and uses only registered component types.",
@@ -443,14 +3389,35 @@ function getSystemPrompt(registeredTypes) {
443
3389
  function getPromptPayload(options) {
444
3390
  registerBuiltInComponents();
445
3391
  const registry = options?.registry ?? globalRegistry;
446
- const types = registry.getTypes().sort();
3392
+ const types = registry.getTypesForLLM().sort();
3393
+ const categories = {};
3394
+ const items = [];
3395
+ for (const t of types) {
3396
+ const reg = registry.get(t);
3397
+ if (!reg) continue;
3398
+ const cat = reg.metadata.category;
3399
+ if (!categories[cat]) categories[cat] = [];
3400
+ categories[cat].push(t);
3401
+ const llmGuidance = reg.metadata.llmGuidance;
3402
+ items.push({
3403
+ type: t,
3404
+ kind: reg.metadata.kind,
3405
+ category: cat,
3406
+ description: reg.metadata.description,
3407
+ ...typeof llmGuidance === "string" ? { llmGuidance } : {}
3408
+ });
3409
+ }
447
3410
  return {
448
3411
  package: "@depths/waves",
449
3412
  version: __wavesVersion,
450
- irVersion: "1.0",
3413
+ irVersion: "2.0",
451
3414
  systemPrompt: getSystemPrompt(types),
3415
+ catalog: {
3416
+ categories,
3417
+ items
3418
+ },
452
3419
  schemas: {
453
- videoIR: zodSchemaToJsonSchema(VideoIRSchema),
3420
+ videoIR: zodSchemaToJsonSchema(VideoIRv2AuthoringSchema),
454
3421
  components: registry.getJSONSchemaForLLM()
455
3422
  },
456
3423
  rules: {
@@ -462,8 +3429,70 @@ function getPromptPayload(options) {
462
3429
  };
463
3430
  }
464
3431
 
3432
+ // src/ir/migrations.ts
3433
+ function fillNodeTiming(node, parentDurationInFrames) {
3434
+ const timing = node.timing ?? { from: 0, durationInFrames: parentDurationInFrames };
3435
+ const children = node.children?.map((c) => fillNodeTiming(c, timing.durationInFrames));
3436
+ return {
3437
+ id: node.id,
3438
+ type: node.type,
3439
+ timing,
3440
+ props: node.props,
3441
+ metadata: node.metadata,
3442
+ children: children && children.length ? children : void 0
3443
+ };
3444
+ }
3445
+ function compileToRenderGraph(v2) {
3446
+ const parsed = VideoIRv2Schema.parse(v2);
3447
+ if (parsed.timeline) {
3448
+ const timeline2 = parsed.timeline.map((n) => {
3449
+ const timing = n.timing ?? { from: 0, durationInFrames: parsed.video.durationInFrames };
3450
+ return fillNodeTiming({ ...n, timing }, parsed.video.durationInFrames);
3451
+ });
3452
+ return {
3453
+ version: "2.0",
3454
+ video: parsed.video,
3455
+ audio: parsed.audio,
3456
+ timeline: timeline2
3457
+ };
3458
+ }
3459
+ const segments = parsed.segments ?? [];
3460
+ const timeline = [];
3461
+ let start = 0;
3462
+ let pendingEnter;
3463
+ for (let i = 0; i < segments.length; i++) {
3464
+ const seg = segments[i];
3465
+ const hasNext = i < segments.length - 1;
3466
+ const exit = hasNext ? seg.transitionToNext : void 0;
3467
+ const enter = pendingEnter;
3468
+ const rootFilled = fillNodeTiming(seg.root, seg.durationInFrames);
3469
+ timeline.push({
3470
+ id: `segment:${seg.id}`,
3471
+ type: "Segment",
3472
+ timing: { from: start, durationInFrames: seg.durationInFrames },
3473
+ props: {
3474
+ enterTransition: enter,
3475
+ exitTransition: exit
3476
+ },
3477
+ children: [rootFilled]
3478
+ });
3479
+ pendingEnter = exit;
3480
+ const overlap = exit?.durationInFrames ?? 0;
3481
+ start += seg.durationInFrames - overlap;
3482
+ }
3483
+ return {
3484
+ version: "2.0",
3485
+ video: parsed.video,
3486
+ audio: parsed.audio,
3487
+ timeline
3488
+ };
3489
+ }
3490
+
465
3491
  // src/core/validator.ts
466
3492
  var IRValidator = class {
3493
+ constructor(registry) {
3494
+ this.registry = registry;
3495
+ }
467
3496
  validateSchema(ir) {
468
3497
  const result = VideoIRSchema.safeParse(ir);
469
3498
  if (!result.success) {
@@ -478,41 +3507,75 @@ var IRValidator = class {
478
3507
  }
479
3508
  return { success: true, data: result.data };
480
3509
  }
481
- validateSemantics(ir) {
3510
+ validateSemantics(latest) {
482
3511
  const errors = [];
483
- const totalSceneDuration = ir.scenes.reduce((sum, scene) => sum + scene.timing.durationInFrames, 0);
484
- if (totalSceneDuration !== ir.video.durationInFrames) {
3512
+ if (latest.segments) {
3513
+ for (const [i, seg] of latest.segments.entries()) {
3514
+ const exit = seg.transitionToNext;
3515
+ const overlap = exit?.durationInFrames ?? 0;
3516
+ if (exit && i === latest.segments.length - 1) {
3517
+ errors.push({
3518
+ path: ["segments", String(i), "transitionToNext"],
3519
+ message: "transitionToNext is not allowed on the last segment",
3520
+ code: "TRANSITION_ON_LAST_SEGMENT"
3521
+ });
3522
+ continue;
3523
+ }
3524
+ if (overlap > 0 && i < latest.segments.length - 1) {
3525
+ const next = latest.segments[i + 1];
3526
+ if (overlap > seg.durationInFrames) {
3527
+ errors.push({
3528
+ path: ["segments", String(i), "transitionToNext", "durationInFrames"],
3529
+ message: `Transition overlap (${overlap}) exceeds segment duration (${seg.durationInFrames})`,
3530
+ code: "TRANSITION_OVERLAP_TOO_LARGE"
3531
+ });
3532
+ }
3533
+ if (overlap > next.durationInFrames) {
3534
+ errors.push({
3535
+ path: ["segments", String(i + 1), "durationInFrames"],
3536
+ message: `Transition overlap (${overlap}) exceeds next segment duration (${next.durationInFrames})`,
3537
+ code: "TRANSITION_OVERLAP_TOO_LARGE"
3538
+ });
3539
+ }
3540
+ }
3541
+ }
3542
+ }
3543
+ const compiled = compileToRenderGraph(latest);
3544
+ const maxEnd = getMaxEnd(compiled.timeline);
3545
+ if (maxEnd !== compiled.video.durationInFrames) {
485
3546
  errors.push({
486
- path: ["scenes"],
487
- message: `Sum of scene durations (${totalSceneDuration}) does not match video duration (${ir.video.durationInFrames})`,
3547
+ path: ["video", "durationInFrames"],
3548
+ message: `Max timeline end (${maxEnd}) does not match video duration (${compiled.video.durationInFrames})`,
488
3549
  code: "DURATION_MISMATCH"
489
3550
  });
490
3551
  }
491
- let expectedFrame = 0;
492
- for (const [i, scene] of ir.scenes.entries()) {
493
- if (scene.timing.from !== expectedFrame) {
3552
+ for (const [i, root] of compiled.timeline.entries()) {
3553
+ const end = root.timing.from + root.timing.durationInFrames;
3554
+ if (root.timing.from < 0 || root.timing.durationInFrames <= 0) {
494
3555
  errors.push({
495
- path: ["scenes", String(i), "timing", "from"],
496
- message: `Scene ${i} starts at frame ${scene.timing.from}, expected ${expectedFrame}`,
497
- code: "TIMING_GAP_OR_OVERLAP"
3556
+ path: ["timeline", String(i), "timing"],
3557
+ message: "Timing must have from>=0 and durationInFrames>0",
3558
+ code: "INVALID_TIMING"
498
3559
  });
499
3560
  }
500
- expectedFrame += scene.timing.durationInFrames;
501
- }
502
- for (const [i, scene] of ir.scenes.entries()) {
503
- if (scene.children && scene.children.length > 0) {
504
- this.validateComponentTimingRecursive(
505
- scene.children,
506
- scene.timing.durationInFrames,
507
- ["scenes", String(i)],
508
- errors
509
- );
3561
+ if (end > compiled.video.durationInFrames) {
3562
+ errors.push({
3563
+ path: ["timeline", String(i), "timing"],
3564
+ message: `Root node exceeds video duration (${end} > ${compiled.video.durationInFrames})`,
3565
+ code: "COMPONENT_EXCEEDS_VIDEO"
3566
+ });
3567
+ }
3568
+ if (root.children?.length) {
3569
+ this.validateComponentTimingRecursive(root.children, root.timing.durationInFrames, ["timeline", String(i)], errors);
510
3570
  }
511
3571
  }
3572
+ if (this.registry) {
3573
+ this.validateRegistryContracts(compiled, errors);
3574
+ }
512
3575
  if (errors.length > 0) {
513
3576
  return { success: false, errors };
514
3577
  }
515
- return { success: true };
3578
+ return { success: true, data: compiled };
516
3579
  }
517
3580
  validate(ir) {
518
3581
  const schemaResult = this.validateSchema(ir);
@@ -523,7 +3586,7 @@ var IRValidator = class {
523
3586
  if (!semanticsResult.success) {
524
3587
  return { success: false, errors: semanticsResult.errors ?? [] };
525
3588
  }
526
- return schemaResult;
3589
+ return semanticsResult;
527
3590
  }
528
3591
  validateComponentTimingRecursive(components, parentDuration, pathPrefix, errors) {
529
3592
  for (const [i, component] of components.entries()) {
@@ -535,7 +3598,7 @@ var IRValidator = class {
535
3598
  code: "COMPONENT_EXCEEDS_PARENT"
536
3599
  });
537
3600
  }
538
- if (component.type === "Scene" && component.children && component.children.length > 0) {
3601
+ if (component.children && component.children.length > 0) {
539
3602
  this.validateComponentTimingRecursive(
540
3603
  component.children,
541
3604
  component.timing.durationInFrames,
@@ -545,7 +3608,87 @@ var IRValidator = class {
545
3608
  }
546
3609
  }
547
3610
  }
3611
+ validateRegistryContracts(compiled, errors) {
3612
+ const registry = this.registry;
3613
+ const stack = compiled.timeline.map((n, i) => ({
3614
+ node: n,
3615
+ path: ["timeline", String(i)]
3616
+ }));
3617
+ while (stack.length) {
3618
+ const { node, path: path3 } = stack.pop();
3619
+ const reg = registry.get(node.type);
3620
+ if (!reg) {
3621
+ errors.push({
3622
+ path: [...path3, "type"],
3623
+ message: `Unknown component type: ${node.type}`,
3624
+ code: "UNKNOWN_COMPONENT_TYPE"
3625
+ });
3626
+ continue;
3627
+ }
3628
+ const rawProps = node.props ?? {};
3629
+ if (rawProps === null || typeof rawProps !== "object") {
3630
+ errors.push({
3631
+ path: [...path3, "props"],
3632
+ message: "Component props must be an object",
3633
+ code: "PROPS_NOT_OBJECT"
3634
+ });
3635
+ } else {
3636
+ const propsValidation = registry.validateProps(node.type, rawProps);
3637
+ if (!propsValidation.success) {
3638
+ for (const issue of propsValidation.error.issues) {
3639
+ errors.push({
3640
+ path: [...path3, "props", ...issue.path.map((p) => String(p))],
3641
+ message: issue.message,
3642
+ code: "PROPS_INVALID"
3643
+ });
3644
+ }
3645
+ } else {
3646
+ node.props = propsValidation.data;
3647
+ }
3648
+ }
3649
+ const childCount = node.children?.length ?? 0;
3650
+ const meta = reg.metadata;
3651
+ const acceptsChildren = Boolean(meta.acceptsChildren);
3652
+ if (childCount > 0 && !acceptsChildren) {
3653
+ errors.push({
3654
+ path: [...path3, "children"],
3655
+ message: `Component type "${node.type}" does not accept children`,
3656
+ code: "CHILDREN_NOT_ALLOWED"
3657
+ });
3658
+ }
3659
+ if (typeof meta.minChildren === "number" && childCount < meta.minChildren) {
3660
+ errors.push({
3661
+ path: [...path3, "children"],
3662
+ message: `Component requires at least ${meta.minChildren} children`,
3663
+ code: "TOO_FEW_CHILDREN"
3664
+ });
3665
+ }
3666
+ if (typeof meta.maxChildren === "number" && childCount > meta.maxChildren) {
3667
+ errors.push({
3668
+ path: [...path3, "children"],
3669
+ message: `Component allows at most ${meta.maxChildren} children`,
3670
+ code: "TOO_MANY_CHILDREN"
3671
+ });
3672
+ }
3673
+ if (node.children?.length) {
3674
+ for (let i = 0; i < node.children.length; i++) {
3675
+ stack.push({ node: node.children[i], path: [...path3, "children", String(i)] });
3676
+ }
3677
+ }
3678
+ }
3679
+ }
548
3680
  };
3681
+ function getMaxEnd(nodes) {
3682
+ let max = 0;
3683
+ const stack = [...nodes];
3684
+ while (stack.length) {
3685
+ const n = stack.pop();
3686
+ const end = n.timing.from + n.timing.durationInFrames;
3687
+ if (end > max) max = end;
3688
+ if (n.children?.length) stack.push(...n.children);
3689
+ }
3690
+ return max;
3691
+ }
549
3692
 
550
3693
  // src/core/engine.ts
551
3694
  var import_bundler = require("@remotion/bundler");
@@ -606,14 +3749,16 @@ var WavesEngine = class {
606
3749
  onProgress: () => void 0
607
3750
  });
608
3751
  const compositionId = validatedIR.video.id ?? "main";
3752
+ const inputProps = options.inputProps ?? {};
609
3753
  const composition = await (0, import_renderer.selectComposition)({
610
3754
  serveUrl: bundleLocation,
611
3755
  id: compositionId,
612
- inputProps: {}
3756
+ inputProps
613
3757
  });
614
3758
  await (0, import_renderer.renderMedia)({
615
3759
  composition,
616
3760
  serveUrl: bundleLocation,
3761
+ inputProps,
617
3762
  codec: options.codec ?? "h264",
618
3763
  outputLocation: options.outputPath,
619
3764
  crf: options.crf ?? null,
@@ -626,11 +3771,75 @@ var WavesEngine = class {
626
3771
  await import_promises.default.rm(tmpDir, { recursive: true, force: true });
627
3772
  }
628
3773
  }
3774
+ async renderStills(ir, options) {
3775
+ if (this.registry !== globalRegistry) {
3776
+ throw new WavesRenderError("WavesEngine currently requires using globalRegistry", {
3777
+ hint: "Use `registerBuiltInComponents()` + `globalRegistry` for both validation and rendering."
3778
+ });
3779
+ }
3780
+ const validationResult = this.validator.validate(ir);
3781
+ if (!validationResult.success) {
3782
+ throw new WavesRenderError("IR validation failed", { errors: validationResult.errors });
3783
+ }
3784
+ const validatedIR = validationResult.data;
3785
+ const requiredTypes = collectComponentTypes(validatedIR);
3786
+ for (const type of requiredTypes) {
3787
+ if (!this.registry.has(type)) {
3788
+ throw new WavesRenderError("Unknown component type", { type });
3789
+ }
3790
+ }
3791
+ const rootDir = options.rootDir ?? process.cwd();
3792
+ const tmpDir = await import_promises.default.mkdtemp(import_node_path.default.join(rootDir, ".waves-tmp-"));
3793
+ const entryPoint = import_node_path.default.join(tmpDir, "entry.tsx");
3794
+ try {
3795
+ await import_promises.default.writeFile(entryPoint, generateEntryPoint(validatedIR, options), "utf-8");
3796
+ await import_promises.default.mkdir(options.outputDir, { recursive: true });
3797
+ const bundleLocation = await (0, import_bundler.bundle)({
3798
+ entryPoint,
3799
+ rootDir,
3800
+ publicDir: options.publicDir ?? null,
3801
+ onProgress: () => void 0
3802
+ });
3803
+ const compositionId = validatedIR.video.id ?? "main";
3804
+ const inputProps = options.inputProps ?? {};
3805
+ const composition = await (0, import_renderer.selectComposition)({
3806
+ serveUrl: bundleLocation,
3807
+ id: compositionId,
3808
+ inputProps
3809
+ });
3810
+ const frames = Array.from(new Set(options.frames)).sort((a, b) => a - b);
3811
+ const written = [];
3812
+ for (const frame of frames) {
3813
+ const clamped = Math.max(0, Math.min(composition.durationInFrames - 1, Math.floor(frame)));
3814
+ const imageFormat = options.imageFormat ?? "png";
3815
+ const filename = `${compositionId}-frame-${String(clamped).padStart(6, "0")}.${imageFormat}`;
3816
+ const outPath = import_node_path.default.join(options.outputDir, filename);
3817
+ const stillOptions = {
3818
+ composition,
3819
+ serveUrl: bundleLocation,
3820
+ inputProps,
3821
+ frame: clamped,
3822
+ output: outPath,
3823
+ imageFormat,
3824
+ scale: options.scale ?? 1,
3825
+ overwrite: true,
3826
+ ...imageFormat === "jpeg" ? { jpegQuality: options.jpegQuality ?? 80 } : {}
3827
+ };
3828
+ await (0, import_renderer.renderStill)(stillOptions);
3829
+ written.push(outPath);
3830
+ }
3831
+ return written;
3832
+ } catch (error) {
3833
+ throw new WavesRenderError("Rendering failed", { originalError: error });
3834
+ } finally {
3835
+ await import_promises.default.rm(tmpDir, { recursive: true, force: true });
3836
+ }
3837
+ }
629
3838
  };
630
3839
  function collectComponentTypes(ir) {
631
3840
  const types = /* @__PURE__ */ new Set();
632
- for (const scene of ir.scenes) {
633
- walkComponent(scene, types);
3841
+ for (const node of ir.timeline) {
3842
+ walkComponent(node, types);
634
3843
  }
635
3844
  return types;
636
3845
  }
@@ -660,8 +3869,10 @@ registerBuiltInComponents();
660
3869
  const ir = ${JSON.stringify(ir, null, 2)};
661
3870
  const compositionId = ${JSON.stringify(compositionId)};
662
3871
 
663
- const Root = () => {
664
- return <WavesComposition ir={ir} registry={globalRegistry} />;
3872
+ const Root = (props) => {
3873
+ const debugBounds = Boolean(props?.__wavesDebugBounds);
3874
+ const debugLabels = Boolean(props?.__wavesDebugLabels);
3875
+ return <WavesComposition ir={ir} registry={globalRegistry} debug={{ bounds: debugBounds, labels: debugLabels }} />;
665
3876
  };
666
3877
 
667
3878
  export const RemotionRoot = () => {
@@ -708,7 +3919,7 @@ function parseArgs(argv) {
708
3919
  const hasInlineValue = eq >= 0;
709
3920
  const inlineValue = hasInlineValue ? arg.slice(eq + 1) : void 0;
710
3921
  const next = argv[i + 1];
711
- const isBoolean = key === "help" || key === "pretty";
3922
+ const isBoolean = key === "help" || key === "pretty" || key === "includeInternal" || key === "debugBounds" || key === "debugLabels";
712
3923
  if (isBoolean) {
713
3924
  flags[key] = true;
714
3925
  continue;
@@ -747,11 +3958,13 @@ Usage:
747
3958
  waves <command> [options]
748
3959
 
749
3960
  Commands:
3961
+ catalog Print the built-in component catalog
750
3962
  prompt Print an agent-ready system prompt + schema payload
751
3963
  schema Print JSON Schemas for IR/components
752
3964
  write-ir Write a starter IR JSON file
753
3965
  validate Validate an IR JSON file
754
3966
  render Render MP4 from an IR JSON file
3967
+ stills Render PNG/JPEG/WebP still frames from an IR JSON file
755
3968
 
756
3969
  Options:
757
3970
  -h, --help Show help
@@ -788,21 +4001,9 @@ async function importRegistrationModules(modules) {
788
4001
  function stringifyJSON(value, pretty) {
789
4002
  return JSON.stringify(value, null, pretty ? 2 : 0) + "\n";
790
4003
  }
791
- function collectComponentTypes2(ir) {
792
- const types = /* @__PURE__ */ new Set();
793
- for (const scene of ir.scenes) {
794
- walkComponent2(scene, types);
795
- }
796
- return types;
797
- }
798
- function walkComponent2(component, types) {
799
- types.add(component.type);
800
- const children = component.children;
801
- if (children?.length) {
802
- for (const child of children) {
803
- walkComponent2(child, types);
804
- }
805
- }
4004
+ function parseImageFormat(raw) {
4005
+ if (raw === "png" || raw === "jpeg" || raw === "webp") return raw;
4006
+ return null;
806
4007
  }
807
4008
  async function main(argv = process.argv.slice(2)) {
808
4009
  const parsed = parseArgs(argv);
@@ -831,6 +4032,66 @@ async function main(argv = process.argv.slice(2)) {
831
4032
  return EXIT_USAGE;
832
4033
  }
833
4034
  registerBuiltInComponents();
4035
+ if (parsed.command === "catalog") {
4036
+ const format = (getFlagString(parsed.flags, "format") ?? "text").toLowerCase();
4037
+ const includeInternal = Boolean(parsed.flags.includeInternal);
4038
+ if (format !== "text" && format !== "json") {
4039
+ process.stderr.write(`Invalid --format: ${format} (expected "text" or "json")
4040
+ `);
4041
+ return EXIT_USAGE;
4042
+ }
4043
+ const types = includeInternal ? globalRegistry.getTypes().sort() : globalRegistry.getTypesForLLM().sort();
4044
+ const byCategory = {};
4045
+ const items = [];
4046
+ for (const t of types) {
4047
+ const reg = globalRegistry.get(t);
4048
+ if (!reg) continue;
4049
+ const cat = reg.metadata.category;
4050
+ if (!byCategory[cat]) byCategory[cat] = [];
4051
+ byCategory[cat].push(t);
4052
+ items.push({ type: t, kind: reg.metadata.kind, category: cat, description: reg.metadata.description });
4053
+ }
4054
+ if (format === "json") {
4055
+ const payload = { categories: byCategory, items };
4056
+ const json = stringifyJSON(payload, pretty);
4057
+ const writeErr2 = await tryWriteOut(outPath, json);
4058
+ if (writeErr2) {
4059
+ process.stderr.write(`Failed to write ${outPath}: ${writeErr2}
4060
+ `);
4061
+ process.stdout.write(json);
4062
+ return EXIT_IO;
4063
+ }
4064
+ process.stdout.write(json);
4065
+ if (outPath) process.stderr.write(`Wrote ${outPath}
4066
+ `);
4067
+ return EXIT_OK;
4068
+ }
4069
+ const categories = Object.keys(byCategory).sort();
4070
+ const lines = [];
4071
+ lines.push(`waves catalog (v${__wavesVersion})`);
4072
+ lines.push("");
4073
+ for (const c of categories) {
4074
+ lines.push(`${c}:`);
4075
+ for (const t of byCategory[c].sort()) {
4076
+ lines.push(`- ${t}`);
4077
+ }
4078
+ lines.push("");
4079
+ }
4080
+ const text = lines.join("\n");
4081
+ const writeErr = await tryWriteOut(outPath, text.endsWith("\n") ? text : `${text}
4082
+ `);
4083
+ if (writeErr) {
4084
+ process.stderr.write(`Failed to write ${outPath}: ${writeErr}
4085
+ `);
4086
+ process.stdout.write(text);
4087
+ return EXIT_IO;
4088
+ }
4089
+ process.stdout.write(text.endsWith("\n") ? text : `${text}
4090
+ `);
4091
+ if (outPath) process.stderr.write(`Wrote ${outPath}
4092
+ `);
4093
+ return EXIT_OK;
4094
+ }
834
4095
  if (parsed.command === "prompt") {
835
4096
  const format = (getFlagString(parsed.flags, "format") ?? "text").toLowerCase();
836
4097
  const maxCharsRaw = getFlagString(parsed.flags, "maxChars");
@@ -874,12 +4135,12 @@ async function main(argv = process.argv.slice(2)) {
874
4135
  const kind = (getFlagString(parsed.flags, "kind") ?? "all").toLowerCase();
875
4136
  let output;
876
4137
  if (kind === "video-ir") {
877
- output = zodSchemaToJsonSchema(VideoIRSchema);
4138
+ output = zodSchemaToJsonSchema(VideoIRv2AuthoringSchema);
878
4139
  } else if (kind === "components") {
879
4140
  output = globalRegistry.getJSONSchemaForLLM();
880
4141
  } else if (kind === "all") {
881
4142
  output = {
882
- videoIR: zodSchemaToJsonSchema(VideoIRSchema),
4143
+ videoIR: zodSchemaToJsonSchema(VideoIRv2AuthoringSchema),
883
4144
  components: globalRegistry.getJSONSchemaForLLM()
884
4145
  };
885
4146
  } else {
@@ -908,33 +4169,75 @@ async function main(argv = process.argv.slice(2)) {
908
4169
  return EXIT_USAGE;
909
4170
  }
910
4171
  const ir = template === "basic" ? {
911
- version: "1.0",
912
- video: { id: "main", width: 1920, height: 1080, fps: 30, durationInFrames: 90 },
913
- scenes: [
4172
+ version: "2.0",
4173
+ video: { id: "main", width: 1920, height: 1080, fps: 30, durationInFrames: 165 },
4174
+ segments: [
914
4175
  {
915
4176
  id: "scene-1",
916
- type: "Scene",
917
- timing: { from: 0, durationInFrames: 90 },
918
- props: { background: { type: "color", value: "#000000" } },
919
- children: [
920
- {
921
- id: "title",
922
- type: "Text",
923
- timing: { from: 0, durationInFrames: 90 },
924
- props: { content: "Hello Waves", fontSize: 72, animation: "fade" }
925
- }
926
- ]
4177
+ durationInFrames: 90,
4178
+ transitionToNext: { type: "FadeTransition", durationInFrames: 15 },
4179
+ root: {
4180
+ id: "root",
4181
+ type: "Scene",
4182
+ props: { background: { type: "color", value: "#000000" } },
4183
+ children: [
4184
+ {
4185
+ id: "title",
4186
+ type: "SplitText",
4187
+ props: { content: "Waves v0.2.0", fontSize: 96, splitBy: "word", stagger: 3, animation: "slideUp" }
4188
+ },
4189
+ {
4190
+ id: "subtitle",
4191
+ type: "TypewriterText",
4192
+ props: { content: "Composite components + hybrid IR", fontSize: 48, position: "bottom", speed: 1.5 }
4193
+ },
4194
+ {
4195
+ id: "wm",
4196
+ type: "Watermark",
4197
+ props: { type: "text", text: "@depths.ai", position: "bottomRight", opacity: 0.4, size: 60 }
4198
+ }
4199
+ ]
4200
+ }
4201
+ },
4202
+ {
4203
+ id: "scene-2",
4204
+ durationInFrames: 90,
4205
+ root: {
4206
+ id: "root-2",
4207
+ type: "Scene",
4208
+ props: { background: { type: "color", value: "#0B1220" } },
4209
+ children: [
4210
+ {
4211
+ id: "lower-third",
4212
+ type: "ThirdLowerBanner",
4213
+ props: { name: "Waves", title: "v0.2.0 \u2014 Composites + transitions", accentColor: "#3B82F6" }
4214
+ },
4215
+ {
4216
+ id: "count",
4217
+ type: "AnimatedCounter",
4218
+ props: { from: 0, to: 35, suffix: " components", fontSize: 96, color: "#FFFFFF" }
4219
+ },
4220
+ {
4221
+ id: "wm-2",
4222
+ type: "Watermark",
4223
+ props: { type: "text", text: "waves", position: "topLeft", opacity: 0.25, size: 52 }
4224
+ }
4225
+ ]
4226
+ }
927
4227
  }
928
4228
  ]
929
4229
  } : template === "minimal" ? {
930
- version: "1.0",
4230
+ version: "2.0",
931
4231
  video: { id: "main", width: 1920, height: 1080, fps: 30, durationInFrames: 60 },
932
- scenes: [
4232
+ segments: [
933
4233
  {
934
4234
  id: "scene-1",
935
- type: "Scene",
936
- timing: { from: 0, durationInFrames: 60 },
937
- props: { background: { type: "color", value: "#000000" } }
4235
+ durationInFrames: 60,
4236
+ root: {
4237
+ id: "root",
4238
+ type: "Scene",
4239
+ props: { background: { type: "color", value: "#000000" } }
4240
+ }
938
4241
  }
939
4242
  ]
940
4243
  } : null;
@@ -943,9 +4246,9 @@ async function main(argv = process.argv.slice(2)) {
943
4246
  `);
944
4247
  return EXIT_USAGE;
945
4248
  }
946
- const schemaCheck = VideoIRSchema.safeParse(ir);
4249
+ const schemaCheck = VideoIRv2AuthoringSchema.safeParse(ir);
947
4250
  if (!schemaCheck.success) {
948
- process.stderr.write("Internal error: generated template does not validate against VideoIRSchema\n");
4251
+ process.stderr.write("Internal error: generated template does not validate against VideoIRv2AuthoringSchema\n");
949
4252
  return EXIT_INTERNAL;
950
4253
  }
951
4254
  const json = stringifyJSON(ir, pretty);
@@ -999,20 +4302,9 @@ async function main(argv = process.argv.slice(2)) {
999
4302
  }
1000
4303
  return EXIT_VALIDATE;
1001
4304
  }
1002
- const validator = new IRValidator();
4305
+ const validator = new IRValidator(globalRegistry);
1003
4306
  const result = validator.validate(json);
1004
4307
  const errors = result.success ? [] : result.errors;
1005
- if (result.success) {
1006
- const types = collectComponentTypes2(result.data);
1007
- const unknownTypes = [...types].filter((t) => !globalRegistry.has(t)).sort();
1008
- for (const t of unknownTypes) {
1009
- errors.push({
1010
- path: ["components", t],
1011
- message: `Unknown component type: ${t}`,
1012
- code: "UNKNOWN_COMPONENT_TYPE"
1013
- });
1014
- }
1015
- }
1016
4308
  const ok = errors.length === 0;
1017
4309
  if (format === "json") {
1018
4310
  process.stdout.write(stringifyJSON(ok ? { success: true } : { success: false, errors }, pretty));
@@ -1062,15 +4354,19 @@ async function main(argv = process.argv.slice(2)) {
1062
4354
  const crfRaw = getFlagString(parsed.flags, "crf");
1063
4355
  const concurrencyRaw = getFlagString(parsed.flags, "concurrency");
1064
4356
  const publicDir = getFlagString(parsed.flags, "publicDir");
4357
+ const debugBounds = Boolean(parsed.flags.debugBounds);
4358
+ const debugLabels = Boolean(parsed.flags.debugLabels);
1065
4359
  const crf = crfRaw ? Number(crfRaw) : void 0;
1066
4360
  const concurrency = concurrencyRaw ? /^[0-9]+$/.test(concurrencyRaw) ? Number(concurrencyRaw) : concurrencyRaw : void 0;
1067
- const engine = new WavesEngine(globalRegistry, new IRValidator());
4361
+ const engine = new WavesEngine(globalRegistry, new IRValidator(globalRegistry));
1068
4362
  try {
4363
+ const inputProps = debugBounds || debugLabels ? { __wavesDebugBounds: debugBounds, __wavesDebugLabels: debugLabels } : void 0;
1069
4364
  const opts = { outputPath, registrationModules };
1070
4365
  if (publicDir) opts.publicDir = publicDir;
1071
4366
  if (codec) opts.codec = codec;
1072
4367
  if (Number.isFinite(crf ?? Number.NaN)) opts.crf = crf;
1073
4368
  if (concurrency !== void 0) opts.concurrency = concurrency;
4369
+ if (inputProps) opts.inputProps = inputProps;
1074
4370
  await engine.render(json, opts);
1075
4371
  } catch (err) {
1076
4372
  const message = err instanceof Error ? err.message : String(err);
@@ -1085,6 +4381,84 @@ async function main(argv = process.argv.slice(2)) {
1085
4381
  `);
1086
4382
  return EXIT_OK;
1087
4383
  }
4384
+ if (parsed.command === "stills") {
4385
+ const inputPath = getFlagString(parsed.flags, "in");
4386
+ const outDir = getFlagString(parsed.flags, "outDir") ?? getFlagString(parsed.flags, "out");
4387
+ if (!inputPath) {
4388
+ process.stderr.write("Missing required --in <path>\n");
4389
+ return EXIT_USAGE;
4390
+ }
4391
+ if (!outDir) {
4392
+ process.stderr.write("Missing required --outDir <dir>\n");
4393
+ return EXIT_USAGE;
4394
+ }
4395
+ let raw;
4396
+ try {
4397
+ raw = await import_promises2.default.readFile(inputPath, "utf-8");
4398
+ } catch (err) {
4399
+ const message = err instanceof Error ? err.message : String(err);
4400
+ process.stderr.write(`Failed to read ${inputPath}: ${message}
4401
+ `);
4402
+ return EXIT_IO;
4403
+ }
4404
+ let json;
4405
+ try {
4406
+ json = JSON.parse(raw);
4407
+ } catch (err) {
4408
+ const message = err instanceof Error ? err.message : String(err);
4409
+ process.stderr.write(`Invalid JSON: ${message}
4410
+ `);
4411
+ return EXIT_VALIDATE;
4412
+ }
4413
+ const framesRaw = getFlagString(parsed.flags, "frames") ?? "0";
4414
+ const frames = framesRaw.split(/[,\s]+/g).map((v) => v.trim()).filter(Boolean).map((v) => Number(v)).filter((n) => Number.isFinite(n));
4415
+ if (frames.length === 0) {
4416
+ process.stderr.write('Invalid --frames (expected comma-separated frame numbers, e.g. "0,30,60")\n');
4417
+ return EXIT_USAGE;
4418
+ }
4419
+ const imageFormatRaw = (getFlagString(parsed.flags, "imageFormat") ?? "png").toLowerCase();
4420
+ const imageFormat = parseImageFormat(imageFormatRaw);
4421
+ if (!imageFormat) {
4422
+ process.stderr.write(`Invalid --imageFormat: ${imageFormatRaw} (expected png|jpeg|webp)
4423
+ `);
4424
+ return EXIT_USAGE;
4425
+ }
4426
+ const scaleRaw = getFlagString(parsed.flags, "scale");
4427
+ const jpegQualityRaw = getFlagString(parsed.flags, "jpegQuality");
4428
+ const publicDir = getFlagString(parsed.flags, "publicDir");
4429
+ const debugBounds = Boolean(parsed.flags.debugBounds);
4430
+ const debugLabels = Boolean(parsed.flags.debugLabels);
4431
+ const scale = scaleRaw ? Number(scaleRaw) : void 0;
4432
+ const jpegQuality = jpegQualityRaw ? Number(jpegQualityRaw) : void 0;
4433
+ const engine = new WavesEngine(globalRegistry, new IRValidator(globalRegistry));
4434
+ try {
4435
+ const inputProps = debugBounds || debugLabels ? { __wavesDebugBounds: debugBounds, __wavesDebugLabels: debugLabels } : void 0;
4436
+ const opts = {
4437
+ outputDir: outDir,
4438
+ frames,
4439
+ imageFormat,
4440
+ registrationModules
4441
+ };
4442
+ if (publicDir) opts.publicDir = publicDir;
4443
+ if (Number.isFinite(scale ?? Number.NaN)) opts.scale = scale;
4444
+ if (Number.isFinite(jpegQuality ?? Number.NaN)) opts.jpegQuality = jpegQuality;
4445
+ if (inputProps) opts.inputProps = inputProps;
4446
+ const written = await engine.renderStills(json, opts);
4447
+ for (const p of written) {
4448
+ process.stderr.write(`Wrote ${p}
4449
+ `);
4450
+ }
4451
+ } catch (err) {
4452
+ const message = err instanceof Error ? err.message : String(err);
4453
+ const context = err instanceof WavesRenderError ? err.context : void 0;
4454
+ const payload = context ? stringifyJSON({ error: message, context }, pretty) : null;
4455
+ process.stderr.write(`stills failed: ${message}
4456
+ `);
4457
+ if (payload) process.stderr.write(payload);
4458
+ return EXIT_RENDER;
4459
+ }
4460
+ return EXIT_OK;
4461
+ }
1088
4462
  process.stderr.write(`Unknown command: ${parsed.command}
1089
4463
  `);
1090
4464
  process.stderr.write(`Run: waves --help