@json-render/remotion 0.4.0 → 0.4.2

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/index.js CHANGED
@@ -37,6 +37,7 @@ __export(index_exports, {
37
37
  standardComponents: () => standardComponents,
38
38
  standardEffectDefinitions: () => standardEffectDefinitions,
39
39
  standardTransitionDefinitions: () => standardTransitionDefinitions,
40
+ useMotion: () => useMotion,
40
41
  useTransition: () => useTransition
41
42
  });
42
43
  module.exports = __toCommonJS(index_exports);
@@ -61,8 +62,8 @@ function remotionPromptTemplate(context) {
61
62
  lines.push("");
62
63
  lines.push(`{"op":"set","path":"/composition","value":{"id":"intro","fps":30,"width":1920,"height":1080,"durationInFrames":300}}
63
64
  {"op":"set","path":"/tracks","value":[{"id":"main","name":"Main","type":"video","enabled":true},{"id":"overlay","name":"Overlay","type":"overlay","enabled":true}]}
64
- {"op":"set","path":"/clips/0","value":{"id":"clip-1","trackId":"main","component":"TitleCard","props":{"title":"Welcome","subtitle":"Getting Started"},"from":0,"durationInFrames":90,"transitionIn":{"type":"fade","durationInFrames":15},"transitionOut":{"type":"fade","durationInFrames":15}}}
65
- {"op":"set","path":"/clips/1","value":{"id":"clip-2","trackId":"main","component":"TitleCard","props":{"title":"Features"},"from":90,"durationInFrames":90}}
65
+ {"op":"set","path":"/clips/0","value":{"id":"clip-1","trackId":"main","component":"TitleCard","props":{"title":"Welcome","subtitle":"Getting Started"},"from":0,"durationInFrames":90,"transitionIn":{"type":"fade","durationInFrames":15},"transitionOut":{"type":"fade","durationInFrames":15},"motion":{"enter":{"opacity":0,"y":50,"scale":0.9,"duration":25},"spring":{"damping":15}}}}
66
+ {"op":"set","path":"/clips/1","value":{"id":"clip-2","trackId":"main","component":"TitleCard","props":{"title":"Features"},"from":90,"durationInFrames":90,"motion":{"enter":{"opacity":0,"x":-100,"duration":20},"exit":{"opacity":0,"x":100,"duration":15}}}}
66
67
  {"op":"set","path":"/audio","value":{"tracks":[]}}`);
67
68
  lines.push("");
68
69
  const catalogData = catalog;
@@ -87,6 +88,36 @@ function remotionPromptTemplate(context) {
87
88
  }
88
89
  lines.push("");
89
90
  }
91
+ lines.push("MOTION SYSTEM:");
92
+ lines.push(
93
+ "Clips can have a 'motion' field for declarative animations (optional, use for dynamic/engaging videos):"
94
+ );
95
+ lines.push("");
96
+ lines.push(
97
+ "- enter: {opacity?, scale?, x?, y?, rotate?, duration?} - animate FROM these values TO normal when clip starts"
98
+ );
99
+ lines.push(
100
+ "- exit: {opacity?, scale?, x?, y?, rotate?, duration?} - animate FROM normal TO these values when clip ends"
101
+ );
102
+ lines.push(
103
+ "- spring: {damping?, stiffness?, mass?} - physics config (lower damping = more bounce)"
104
+ );
105
+ lines.push(
106
+ '- loop: {property, from, to, duration, easing?} - continuous animation (property: "scale"|"rotate"|"x"|"y"|"opacity")'
107
+ );
108
+ lines.push("");
109
+ lines.push("Example motion configs:");
110
+ lines.push(' Fade up: {"enter":{"opacity":0,"y":30,"duration":20}}');
111
+ lines.push(
112
+ ' Scale pop: {"enter":{"scale":0.5,"opacity":0,"duration":15},"spring":{"damping":10}}'
113
+ );
114
+ lines.push(
115
+ ' Slide in/out: {"enter":{"x":-100,"duration":20},"exit":{"x":100,"duration":15}}'
116
+ );
117
+ lines.push(
118
+ ' Gentle pulse: {"loop":{"property":"scale","from":1,"to":1.05,"duration":60,"easing":"ease"}}'
119
+ );
120
+ lines.push("");
90
121
  lines.push("RULES:");
91
122
  const baseRules = [
92
123
  "Output ONLY JSONL patches - one JSON object per line, no markdown, no code fences",
@@ -97,7 +128,9 @@ function remotionPromptTemplate(context) {
97
128
  "ONLY use components listed above",
98
129
  "fps is always 30 (1 second = 30 frames, 10 seconds = 300 frames)",
99
130
  `Clips on "main" track flow sequentially (from = previous clip's from + durationInFrames)`,
100
- 'Overlay clips (LowerThird, TextOverlay) go on "overlay" track'
131
+ 'Overlay clips (LowerThird, TextOverlay) go on "overlay" track',
132
+ "Use motion.enter for engaging clip entrances, motion.exit for smooth departures",
133
+ "Spring damping: 20=smooth, 10=bouncy, 5=very bouncy"
101
134
  ];
102
135
  const allRules = [...baseRules, ...customRules];
103
136
  allRules.forEach((rule, i) => {
@@ -159,6 +192,61 @@ var schema = (0, import_core.defineSchema)(
159
192
  transitionOut: s.object({
160
193
  type: s.ref("catalog.transitions"),
161
194
  durationInFrames: s.number()
195
+ }),
196
+ /** Declarative motion configuration for custom animations */
197
+ motion: s.object({
198
+ /** Enter animation - animates FROM these values TO neutral */
199
+ enter: s.object({
200
+ /** Starting opacity (0-1), animates to 1 */
201
+ opacity: s.number(),
202
+ /** Starting scale (e.g., 0.8 = 80%), animates to 1 */
203
+ scale: s.number(),
204
+ /** Starting X offset in pixels, animates to 0 */
205
+ x: s.number(),
206
+ /** Starting Y offset in pixels, animates to 0 */
207
+ y: s.number(),
208
+ /** Starting rotation in degrees, animates to 0 */
209
+ rotate: s.number(),
210
+ /** Duration of enter animation in frames (default: 20) */
211
+ duration: s.number()
212
+ }),
213
+ /** Exit animation - animates FROM neutral TO these values */
214
+ exit: s.object({
215
+ /** Ending opacity (0-1), animates from 1 */
216
+ opacity: s.number(),
217
+ /** Ending scale, animates from 1 */
218
+ scale: s.number(),
219
+ /** Ending X offset in pixels, animates from 0 */
220
+ x: s.number(),
221
+ /** Ending Y offset in pixels, animates from 0 */
222
+ y: s.number(),
223
+ /** Ending rotation in degrees, animates from 0 */
224
+ rotate: s.number(),
225
+ /** Duration of exit animation in frames (default: 20) */
226
+ duration: s.number()
227
+ }),
228
+ /** Spring physics configuration */
229
+ spring: s.object({
230
+ /** Damping coefficient (default: 20) */
231
+ damping: s.number(),
232
+ /** Stiffness (default: 100) */
233
+ stiffness: s.number(),
234
+ /** Mass (default: 1) */
235
+ mass: s.number()
236
+ }),
237
+ /** Continuous looping animation */
238
+ loop: s.object({
239
+ /** Property to animate: "scale" | "rotate" | "x" | "y" | "opacity" */
240
+ property: s.string(),
241
+ /** Starting value */
242
+ from: s.number(),
243
+ /** Ending value */
244
+ to: s.number(),
245
+ /** Duration of one cycle in frames */
246
+ duration: s.number(),
247
+ /** Easing type: "linear" | "ease" | "spring" (default: "ease") */
248
+ easing: s.string()
249
+ })
162
250
  })
163
251
  })
164
252
  ),
@@ -289,22 +377,158 @@ function useTransition(clip, frame) {
289
377
  }
290
378
  return { opacity, translateX, translateY, scale };
291
379
  }
380
+ function useMotion(clip, frame) {
381
+ const { fps } = (0, import_remotion.useVideoConfig)();
382
+ const relativeFrame = frame - clip.from;
383
+ const clipEnd = clip.durationInFrames;
384
+ let opacity = 1;
385
+ let translateX = 0;
386
+ let translateY = 0;
387
+ let scale = 1;
388
+ let rotate = 0;
389
+ const motion = clip.motion;
390
+ if (!motion) {
391
+ return { opacity, translateX, translateY, scale, rotate };
392
+ }
393
+ const springConfig = {
394
+ damping: motion.spring?.damping ?? 20,
395
+ stiffness: motion.spring?.stiffness ?? 100,
396
+ mass: motion.spring?.mass ?? 1
397
+ };
398
+ if (motion.enter) {
399
+ const enterDuration = motion.enter.duration ?? 20;
400
+ if (relativeFrame < enterDuration) {
401
+ const progress = (0, import_remotion.spring)({
402
+ frame: relativeFrame,
403
+ fps,
404
+ config: springConfig,
405
+ durationInFrames: enterDuration
406
+ });
407
+ if (motion.enter.opacity !== void 0) {
408
+ opacity = (0, import_remotion.interpolate)(progress, [0, 1], [motion.enter.opacity, 1]);
409
+ }
410
+ if (motion.enter.scale !== void 0) {
411
+ scale = (0, import_remotion.interpolate)(progress, [0, 1], [motion.enter.scale, 1]);
412
+ }
413
+ if (motion.enter.x !== void 0) {
414
+ translateX = (0, import_remotion.interpolate)(progress, [0, 1], [motion.enter.x, 0]);
415
+ }
416
+ if (motion.enter.y !== void 0) {
417
+ translateY = (0, import_remotion.interpolate)(progress, [0, 1], [motion.enter.y, 0]);
418
+ }
419
+ if (motion.enter.rotate !== void 0) {
420
+ rotate = (0, import_remotion.interpolate)(progress, [0, 1], [motion.enter.rotate, 0]);
421
+ }
422
+ }
423
+ }
424
+ if (motion.exit) {
425
+ const exitDuration = motion.exit.duration ?? 20;
426
+ const exitStart = clipEnd - exitDuration;
427
+ if (relativeFrame >= exitStart) {
428
+ const exitFrame = relativeFrame - exitStart;
429
+ const progress = (0, import_remotion.spring)({
430
+ frame: exitFrame,
431
+ fps,
432
+ config: springConfig,
433
+ durationInFrames: exitDuration
434
+ });
435
+ if (motion.exit.opacity !== void 0) {
436
+ const exitOpacity = (0, import_remotion.interpolate)(
437
+ progress,
438
+ [0, 1],
439
+ [1, motion.exit.opacity]
440
+ );
441
+ opacity = Math.min(opacity, exitOpacity);
442
+ }
443
+ if (motion.exit.scale !== void 0) {
444
+ const exitScale = (0, import_remotion.interpolate)(progress, [0, 1], [1, motion.exit.scale]);
445
+ scale = scale * exitScale;
446
+ }
447
+ if (motion.exit.x !== void 0) {
448
+ const exitX = (0, import_remotion.interpolate)(progress, [0, 1], [0, motion.exit.x]);
449
+ translateX = translateX + exitX;
450
+ }
451
+ if (motion.exit.y !== void 0) {
452
+ const exitY = (0, import_remotion.interpolate)(progress, [0, 1], [0, motion.exit.y]);
453
+ translateY = translateY + exitY;
454
+ }
455
+ if (motion.exit.rotate !== void 0) {
456
+ const exitRotate = (0, import_remotion.interpolate)(
457
+ progress,
458
+ [0, 1],
459
+ [0, motion.exit.rotate]
460
+ );
461
+ rotate = rotate + exitRotate;
462
+ }
463
+ }
464
+ }
465
+ if (motion.loop) {
466
+ const { property, from, to, duration, easing = "ease" } = motion.loop;
467
+ const loopFrame = relativeFrame % duration;
468
+ let loopProgress;
469
+ switch (easing) {
470
+ case "linear":
471
+ loopProgress = loopFrame / duration;
472
+ break;
473
+ case "spring":
474
+ loopProgress = (0, import_remotion.spring)({
475
+ frame: loopFrame,
476
+ fps,
477
+ config: springConfig,
478
+ durationInFrames: duration
479
+ });
480
+ break;
481
+ case "ease":
482
+ default:
483
+ loopProgress = (0, import_remotion.interpolate)(
484
+ loopFrame,
485
+ [0, duration / 2, duration],
486
+ [0, 1, 0],
487
+ { extrapolateRight: "clamp" }
488
+ );
489
+ break;
490
+ }
491
+ const loopValue = (0, import_remotion.interpolate)(loopProgress, [0, 1], [from, to]);
492
+ switch (property) {
493
+ case "opacity":
494
+ opacity = opacity * loopValue;
495
+ break;
496
+ case "scale":
497
+ scale = scale * loopValue;
498
+ break;
499
+ case "x":
500
+ translateX = translateX + loopValue;
501
+ break;
502
+ case "y":
503
+ translateY = translateY + loopValue;
504
+ break;
505
+ case "rotate":
506
+ rotate = rotate + loopValue;
507
+ break;
508
+ }
509
+ }
510
+ return { opacity, translateX, translateY, scale, rotate };
511
+ }
292
512
 
293
513
  // src/components/ClipWrapper.tsx
294
514
  var import_remotion2 = require("remotion");
295
515
  var import_jsx_runtime = require("react/jsx-runtime");
296
516
  function ClipWrapper({ clip, children }) {
297
517
  const frame = (0, import_remotion2.useCurrentFrame)();
298
- const { opacity, translateX, translateY, scale } = useTransition(
299
- clip,
300
- frame + clip.from
301
- );
518
+ const absoluteFrame = frame + clip.from;
519
+ const transition = useTransition(clip, absoluteFrame);
520
+ const motion = useMotion(clip, absoluteFrame);
521
+ const composedOpacity = transition.opacity * motion.opacity;
522
+ const composedTranslateX = transition.translateX + motion.translateX;
523
+ const composedTranslateY = transition.translateY + motion.translateY;
524
+ const composedScale = transition.scale * motion.scale;
525
+ const composedRotate = motion.rotate;
302
526
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
303
527
  import_remotion2.AbsoluteFill,
304
528
  {
305
529
  style: {
306
- opacity,
307
- transform: `translateX(${translateX}%) translateY(${translateY}%) scale(${scale})`
530
+ opacity: composedOpacity,
531
+ transform: `translateX(${composedTranslateX}%) translateY(${composedTranslateY}%) scale(${composedScale}) rotate(${composedRotate}deg)`
308
532
  },
309
533
  children
310
534
  }
@@ -420,13 +644,15 @@ function SplitScreen({ clip }) {
420
644
  ] }) });
421
645
  }
422
646
  function QuoteCard({ clip }) {
423
- const { quote, author, backgroundColor } = clip.props;
647
+ const { quote, author, backgroundColor, textColor, transparent } = clip.props;
648
+ const bgColor = transparent ? "transparent" : backgroundColor || "#1a1a2e";
649
+ const color = textColor || "#ffffff";
424
650
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ClipWrapper, { clip, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
425
651
  import_remotion3.AbsoluteFill,
426
652
  {
427
653
  style: {
428
- backgroundColor: backgroundColor || "#1a1a2e",
429
- color: "#ffffff",
654
+ backgroundColor: bgColor,
655
+ color,
430
656
  display: "flex",
431
657
  flexDirection: "column",
432
658
  alignItems: "center",
@@ -441,7 +667,8 @@ function QuoteCard({ clip }) {
441
667
  fontSize: 48,
442
668
  fontStyle: "italic",
443
669
  textAlign: "center",
444
- marginBottom: 24
670
+ marginBottom: 24,
671
+ textShadow: transparent ? "2px 2px 8px rgba(0,0,0,0.8)" : "none"
445
672
  },
446
673
  children: [
447
674
  "\u201C",
@@ -450,10 +677,20 @@ function QuoteCard({ clip }) {
450
677
  ]
451
678
  }
452
679
  ),
453
- author && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { fontSize: 28, opacity: 0.7 }, children: [
454
- "- ",
455
- author
456
- ] })
680
+ author && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
681
+ "div",
682
+ {
683
+ style: {
684
+ fontSize: 28,
685
+ opacity: 0.9,
686
+ textShadow: transparent ? "1px 1px 4px rgba(0,0,0,0.8)" : "none"
687
+ },
688
+ children: [
689
+ "- ",
690
+ author
691
+ ]
692
+ }
693
+ )
457
694
  ]
458
695
  }
459
696
  ) });
@@ -753,11 +990,13 @@ var standardComponentDefinitions = {
753
990
  props: import_zod.z.object({
754
991
  quote: import_zod.z.string(),
755
992
  author: import_zod.z.string().nullable(),
756
- backgroundColor: import_zod.z.string().nullable()
993
+ backgroundColor: import_zod.z.string().nullable(),
994
+ textColor: import_zod.z.string().nullable(),
995
+ transparent: import_zod.z.boolean().nullable()
757
996
  }),
758
997
  type: "scene",
759
998
  defaultDuration: 150,
760
- description: "Quote display with attribution. Use for testimonials."
999
+ description: "Quote display with author. Props: quote, author, textColor, backgroundColor. Set transparent:true when using as overlay on images."
761
1000
  },
762
1001
  StatCard: {
763
1002
  props: import_zod.z.object({
@@ -908,6 +1147,7 @@ var standardEffectDefinitions = {
908
1147
  standardComponents,
909
1148
  standardEffectDefinitions,
910
1149
  standardTransitionDefinitions,
1150
+ useMotion,
911
1151
  useTransition
912
1152
  });
913
1153
  //# sourceMappingURL=index.js.map