@vanillaskyai/video 0.5.5 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/{bg-confetti-572VDSYM.js → bg-confetti-SHQI7ATB.js} +2 -2
  3. package/dist/{bg-emoji-T4IEJ6U2.js → bg-emoji-XYFRA63Q.js} +2 -2
  4. package/dist/{bg-media-5YR4OLFG.js → bg-media-FEBATBT2.js} +8 -6
  5. package/dist/{brand-message-7524B6HC.js → brand-message-P56WEJPG.js} +5 -3
  6. package/dist/{chart-bar-5UMZXTON.js → chart-bar-ZB45ZRKH.js} +5 -3
  7. package/dist/{chart-counter-QUVLAX42.js → chart-counter-HFYNE4HT.js} +8 -6
  8. package/dist/{chart-progress-ring-IYCNRU7T.js → chart-progress-ring-ZKMLRGSB.js} +5 -3
  9. package/dist/check-runtime.js +3 -1
  10. package/dist/{chunk-EHXDKCYR.js → chunk-6M6DATLW.js} +26 -75
  11. package/dist/chunk-C6WVCZRW.js +19 -0
  12. package/dist/chunk-HT6BGERF.js +167 -0
  13. package/dist/{chunk-3QEKIC6C.js → chunk-NGY3TNND.js} +121 -11
  14. package/dist/{chunk-MFYFZQHM.js → chunk-W3VDIBGR.js} +1 -0
  15. package/dist/chunk-WJ6FLKNC.js +15 -0
  16. package/dist/cli.js +9 -0
  17. package/dist/{cta-logo-PREL7MLE.js → cta-logo-S4OMTNXD.js} +5 -3
  18. package/dist/{cta-media-YWTYVLR6.js → cta-media-EROTHDRR.js} +5 -3
  19. package/dist/{incoming-call-C3FX4SFR.js → incoming-call-5IWJ2TAS.js} +5 -3
  20. package/dist/{infographic-before-after-FQXCQ2CY.js → infographic-before-after-2I7YZVDD.js} +2 -2
  21. package/dist/{infographic-feature-list-WWX5AGU4.js → infographic-feature-list-7SK65U3K.js} +5 -3
  22. package/dist/{infographic-problem-solution-3JBBGNXU.js → infographic-problem-solution-IW35S6BR.js} +5 -3
  23. package/dist/{infographic-stat-row-7X6KEEUD.js → infographic-stat-row-XHUZZPDM.js} +5 -3
  24. package/dist/{infographic-steps-FI5LN4SS.js → infographic-steps-JDDEDI7X.js} +5 -3
  25. package/dist/{preload-media-IYFMPMLB.js → preload-media-LJXWKTWG.js} +2 -0
  26. package/dist/{prompt-input-NNXF6WVS.js → prompt-input-6ZWXEL3V.js} +5 -3
  27. package/dist/react.js +30 -28
  28. package/dist/{reaction-DH6M6LSQ.js → reaction-Z3JIM3MQ.js} +5 -3
  29. package/dist/scene-video-backdrop-IL4F2SVD.js +9 -0
  30. package/dist/{showcase-code-QDJM7E2Z.js → showcase-code-NWQAAJIA.js} +5 -3
  31. package/dist/{showcase-phone-IIYR34CF.js → showcase-phone-5UW45ATT.js} +5 -3
  32. package/dist/{showcase-terminal-3253HMTE.js → showcase-terminal-UVOD25UF.js} +5 -3
  33. package/dist/{showcase-web-H7GB3FBH.js → showcase-web-3AW6VHIG.js} +5 -3
  34. package/dist/{social-milestone-MLWAWF6X.js → social-milestone-KPKHFOB5.js} +8 -6
  35. package/dist/{social-notification-2UQKKOB3.js → social-notification-EDOU6TZB.js} +5 -3
  36. package/dist/{social-review-stack-VHJPE6GM.js → social-review-stack-EZOO33SV.js} +5 -3
  37. package/dist/{social-testimonial-QUNCY52L.js → social-testimonial-DLOINMMY.js} +5 -3
  38. package/dist/{social-tweet-33HIL4RR.js → social-tweet-6JUM323G.js} +5 -3
  39. package/examples/nextjs-quickstart/package.json +1 -1
  40. package/package.json +1 -1
  41. package/registry/items/backgrounds.json +19 -1
  42. package/registry/items/barChart.json +2 -2
  43. package/registry/items/beforeAfter.json +1 -1
  44. package/registry/items/bigNumber.json +2 -2
  45. package/registry/items/brandMessage.json +2 -2
  46. package/registry/items/cardList.json +2 -2
  47. package/registry/items/codeEditor.json +2 -2
  48. package/registry/items/confetti.json +1 -1
  49. package/registry/items/ctaLogo.json +2 -2
  50. package/registry/items/ctaMedia.json +2 -2
  51. package/registry/items/emojiBurst.json +1 -1
  52. package/registry/items/incomingCall.json +2 -2
  53. package/registry/items/media.json +2 -2
  54. package/registry/items/milestone.json +2 -2
  55. package/registry/items/notification.json +2 -2
  56. package/registry/items/phoneMockup.json +2 -2
  57. package/registry/items/problemSolution.json +2 -2
  58. package/registry/items/progressRing.json +2 -2
  59. package/registry/items/promptInput.json +2 -2
  60. package/registry/items/reaction.json +2 -2
  61. package/registry/items/reviewStack.json +2 -2
  62. package/registry/items/steps.json +2 -2
  63. package/registry/items/terminal.json +2 -2
  64. package/registry/items/testimonial.json +2 -2
  65. package/registry/items/tripleStats.json +2 -2
  66. package/registry/items/tweet.json +2 -2
  67. package/registry/items/webMockup.json +2 -2
package/dist/react.js CHANGED
@@ -24,7 +24,8 @@ import {
24
24
  import {
25
25
  VideoFrame,
26
26
  getDimensions
27
- } from "./chunk-3QEKIC6C.js";
27
+ } from "./chunk-NGY3TNND.js";
28
+ import "./chunk-C6WVCZRW.js";
28
29
  import "./chunk-224QNWRA.js";
29
30
  import {
30
31
  fontStack
@@ -34,6 +35,7 @@ import {
34
35
  resolveVideoTimeline
35
36
  } from "./chunk-SPVTJH3F.js";
36
37
  import "./chunk-XGRA2MUV.js";
38
+ import "./chunk-WJ6FLKNC.js";
37
39
  import "./chunk-73NTSFFI.js";
38
40
 
39
41
  // src/player/video-player.tsx
@@ -77,34 +79,34 @@ import { createElement } from "react";
77
79
 
78
80
  // src/visual-system/catalog/builtin-loaders.generated.ts
79
81
  var GENERATED_BUILTIN_TEMPLATE_LOADERS = {
80
- "media": () => import("./bg-media-5YR4OLFG.js").then((module) => ({ default: module.BgMediaTemplate })),
81
- "reaction": () => import("./reaction-DH6M6LSQ.js").then((module) => ({ default: module.ReactionTemplate })),
82
- "confetti": () => import("./bg-confetti-572VDSYM.js").then((module) => ({ default: module.BgConfettiTemplate })),
83
- "emojiBurst": () => import("./bg-emoji-T4IEJ6U2.js").then((module) => ({ default: module.BgEmojiTemplate })),
84
- "bigNumber": () => import("./chart-counter-QUVLAX42.js").then((module) => ({ default: module.ChartCounterTemplate })),
85
- "barChart": () => import("./chart-bar-5UMZXTON.js").then((module) => ({ default: module.ChartBarTemplate })),
86
- "progressRing": () => import("./chart-progress-ring-IYCNRU7T.js").then((module) => ({ default: module.ChartProgressRingTemplate })),
87
- "phoneMockup": () => import("./showcase-phone-IIYR34CF.js").then((module) => ({ default: module.ShowcasePhoneTemplate })),
88
- "webMockup": () => import("./showcase-web-H7GB3FBH.js").then((module) => ({ default: module.ShowcaseWebTemplate })),
89
- "codeEditor": () => import("./showcase-code-QDJM7E2Z.js").then((module) => ({ default: module.ShowcaseCodeTemplate })),
90
- "terminal": () => import("./showcase-terminal-3253HMTE.js").then((module) => ({ default: module.ShowcaseTerminalTemplate })),
91
- "tweet": () => import("./social-tweet-33HIL4RR.js").then((module) => ({ default: module.SocialTweetTemplate })),
92
- "notification": () => import("./social-notification-2UQKKOB3.js").then((module) => ({ default: module.SocialNotificationTemplate })),
82
+ "media": () => import("./bg-media-FEBATBT2.js").then((module) => ({ default: module.BgMediaTemplate })),
83
+ "reaction": () => import("./reaction-Z3JIM3MQ.js").then((module) => ({ default: module.ReactionTemplate })),
84
+ "confetti": () => import("./bg-confetti-SHQI7ATB.js").then((module) => ({ default: module.BgConfettiTemplate })),
85
+ "emojiBurst": () => import("./bg-emoji-XYFRA63Q.js").then((module) => ({ default: module.BgEmojiTemplate })),
86
+ "bigNumber": () => import("./chart-counter-HFYNE4HT.js").then((module) => ({ default: module.ChartCounterTemplate })),
87
+ "barChart": () => import("./chart-bar-ZB45ZRKH.js").then((module) => ({ default: module.ChartBarTemplate })),
88
+ "progressRing": () => import("./chart-progress-ring-ZKMLRGSB.js").then((module) => ({ default: module.ChartProgressRingTemplate })),
89
+ "phoneMockup": () => import("./showcase-phone-5UW45ATT.js").then((module) => ({ default: module.ShowcasePhoneTemplate })),
90
+ "webMockup": () => import("./showcase-web-3AW6VHIG.js").then((module) => ({ default: module.ShowcaseWebTemplate })),
91
+ "codeEditor": () => import("./showcase-code-NWQAAJIA.js").then((module) => ({ default: module.ShowcaseCodeTemplate })),
92
+ "terminal": () => import("./showcase-terminal-UVOD25UF.js").then((module) => ({ default: module.ShowcaseTerminalTemplate })),
93
+ "tweet": () => import("./social-tweet-6JUM323G.js").then((module) => ({ default: module.SocialTweetTemplate })),
94
+ "notification": () => import("./social-notification-EDOU6TZB.js").then((module) => ({ default: module.SocialNotificationTemplate })),
93
95
  "chatMessenger": () => import("./social-conversation-75SD2637.js").then((module) => ({ default: module.SocialConversationTemplate })),
94
96
  "chatWhatsapp": () => import("./social-conversation-75SD2637.js").then((module) => ({ default: module.SocialConversationTemplate })),
95
- "milestone": () => import("./social-milestone-MLWAWF6X.js").then((module) => ({ default: module.SocialMilestoneTemplate })),
96
- "reviewStack": () => import("./social-review-stack-VHJPE6GM.js").then((module) => ({ default: module.SocialReviewStackTemplate })),
97
- "testimonial": () => import("./social-testimonial-QUNCY52L.js").then((module) => ({ default: module.SocialTestimonialTemplate })),
98
- "incomingCall": () => import("./incoming-call-C3FX4SFR.js").then((module) => ({ default: module.IncomingCallTemplate })),
99
- "brandMessage": () => import("./brand-message-7524B6HC.js").then((module) => ({ default: module.BrandMessageTemplate })),
100
- "promptInput": () => import("./prompt-input-NNXF6WVS.js").then((module) => ({ default: module.PromptInputTemplate })),
101
- "beforeAfter": () => import("./infographic-before-after-FQXCQ2CY.js").then((module) => ({ default: module.InfographicBeforeAfterTemplate })),
102
- "tripleStats": () => import("./infographic-stat-row-7X6KEEUD.js").then((module) => ({ default: module.InfographicStatRowTemplate })),
103
- "problemSolution": () => import("./infographic-problem-solution-3JBBGNXU.js").then((module) => ({ default: module.InfographicProblemSolutionTemplate })),
104
- "cardList": () => import("./infographic-feature-list-WWX5AGU4.js").then((module) => ({ default: module.InfographicFeatureListTemplate })),
105
- "steps": () => import("./infographic-steps-FI5LN4SS.js").then((module) => ({ default: module.InfographicStepsTemplate })),
106
- "ctaLogo": () => import("./cta-logo-PREL7MLE.js").then((module) => ({ default: module.CtaLogoTemplate })),
107
- "ctaMedia": () => import("./cta-media-YWTYVLR6.js").then((module) => ({ default: module.CtaMediaTemplate }))
97
+ "milestone": () => import("./social-milestone-KPKHFOB5.js").then((module) => ({ default: module.SocialMilestoneTemplate })),
98
+ "reviewStack": () => import("./social-review-stack-EZOO33SV.js").then((module) => ({ default: module.SocialReviewStackTemplate })),
99
+ "testimonial": () => import("./social-testimonial-DLOINMMY.js").then((module) => ({ default: module.SocialTestimonialTemplate })),
100
+ "incomingCall": () => import("./incoming-call-5IWJ2TAS.js").then((module) => ({ default: module.IncomingCallTemplate })),
101
+ "brandMessage": () => import("./brand-message-P56WEJPG.js").then((module) => ({ default: module.BrandMessageTemplate })),
102
+ "promptInput": () => import("./prompt-input-6ZWXEL3V.js").then((module) => ({ default: module.PromptInputTemplate })),
103
+ "beforeAfter": () => import("./infographic-before-after-2I7YZVDD.js").then((module) => ({ default: module.InfographicBeforeAfterTemplate })),
104
+ "tripleStats": () => import("./infographic-stat-row-XHUZZPDM.js").then((module) => ({ default: module.InfographicStatRowTemplate })),
105
+ "problemSolution": () => import("./infographic-problem-solution-IW35S6BR.js").then((module) => ({ default: module.InfographicProblemSolutionTemplate })),
106
+ "cardList": () => import("./infographic-feature-list-7SK65U3K.js").then((module) => ({ default: module.InfographicFeatureListTemplate })),
107
+ "steps": () => import("./infographic-steps-JDDEDI7X.js").then((module) => ({ default: module.InfographicStepsTemplate })),
108
+ "ctaLogo": () => import("./cta-logo-S4OMTNXD.js").then((module) => ({ default: module.CtaLogoTemplate })),
109
+ "ctaMedia": () => import("./cta-media-EROTHDRR.js").then((module) => ({ default: module.CtaMediaTemplate }))
108
110
  };
109
111
 
110
112
  // src/visual-system/catalog/builtin-player.generated.ts
@@ -704,7 +706,7 @@ function preloadBuiltinTemplate(id) {
704
706
 
705
707
  // src/player/warm-scene-media.ts
706
708
  function warmSceneMedia(variables) {
707
- void import("./preload-media-IYFMPMLB.js").then((module) => module.preloadSceneMedia(variables)).catch(() => {
709
+ void import("./preload-media-LJXWKTWG.js").then((module) => module.preloadSceneMedia(variables)).catch(() => {
708
710
  });
709
711
  }
710
712
 
@@ -1,12 +1,14 @@
1
1
  import {
2
2
  SceneBackground
3
- } from "./chunk-EHXDKCYR.js";
3
+ } from "./chunk-6M6DATLW.js";
4
4
  import {
5
5
  TemplateText
6
6
  } from "./chunk-TCEHK2BW.js";
7
7
  import "./chunk-L5BTTCZV.js";
8
- import "./chunk-MFYFZQHM.js";
8
+ import "./chunk-HT6BGERF.js";
9
+ import "./chunk-W3VDIBGR.js";
9
10
  import "./chunk-XTOXFMLM.js";
11
+ import "./chunk-C6WVCZRW.js";
10
12
  import "./chunk-224QNWRA.js";
11
13
  import {
12
14
  resolveTokens
@@ -66,7 +68,7 @@ var ReactionTemplate = ({
66
68
  const color = foreground;
67
69
  const mediaUrl = String(variables.mediaUrl || "");
68
70
  const hasClip = mediaUrl.trim().length > 0;
69
- return /* @__PURE__ */ jsxs("div", { style: { width, height, backgroundColor: "#000", position: "relative", overflow: "hidden", fontFamily: font }, children: [
71
+ return /* @__PURE__ */ jsxs("div", { style: { width, height, backgroundColor: "var(--vanillasky-template-surface, #000)", position: "relative", overflow: "hidden", fontFamily: font }, children: [
70
72
  /* @__PURE__ */ jsx(
71
73
  SceneBackground,
72
74
  {
@@ -0,0 +1,9 @@
1
+ import {
2
+ SceneVideoBackdrop
3
+ } from "./chunk-HT6BGERF.js";
4
+ import "./chunk-W3VDIBGR.js";
5
+ import "./chunk-XTOXFMLM.js";
6
+ import "./chunk-XWWLKRNU.js";
7
+ export {
8
+ SceneVideoBackdrop
9
+ };
@@ -5,16 +5,18 @@ import {
5
5
  import {
6
6
  SceneBackground,
7
7
  getMediaBackgroundProps
8
- } from "./chunk-EHXDKCYR.js";
8
+ } from "./chunk-6M6DATLW.js";
9
9
  import {
10
10
  TemplateText
11
11
  } from "./chunk-TCEHK2BW.js";
12
12
  import "./chunk-L5BTTCZV.js";
13
- import "./chunk-MFYFZQHM.js";
13
+ import "./chunk-HT6BGERF.js";
14
+ import "./chunk-W3VDIBGR.js";
14
15
  import {
15
16
  interpolate,
16
17
  spring
17
18
  } from "./chunk-XTOXFMLM.js";
19
+ import "./chunk-C6WVCZRW.js";
18
20
  import {
19
21
  hasSceneMedia
20
22
  } from "./chunk-224QNWRA.js";
@@ -293,7 +295,7 @@ var ShowcaseCodeTemplate = ({
293
295
  style: {
294
296
  width,
295
297
  height,
296
- backgroundColor: "#000",
298
+ backgroundColor: "var(--vanillasky-template-surface, #000)",
297
299
  position: "relative",
298
300
  overflow: "hidden",
299
301
  fontFamily: font
@@ -7,17 +7,19 @@ import "./chunk-S7HFLYNN.js";
7
7
  import {
8
8
  SceneBackground,
9
9
  getMediaBackgroundProps
10
- } from "./chunk-EHXDKCYR.js";
10
+ } from "./chunk-6M6DATLW.js";
11
11
  import {
12
12
  TemplateText
13
13
  } from "./chunk-TCEHK2BW.js";
14
14
  import "./chunk-L5BTTCZV.js";
15
- import "./chunk-MFYFZQHM.js";
15
+ import "./chunk-HT6BGERF.js";
16
+ import "./chunk-W3VDIBGR.js";
16
17
  import {
17
18
  SPRING_SMOOTH,
18
19
  interpolate,
19
20
  spring
20
21
  } from "./chunk-XTOXFMLM.js";
22
+ import "./chunk-C6WVCZRW.js";
21
23
  import {
22
24
  hasSceneMedia
23
25
  } from "./chunk-224QNWRA.js";
@@ -203,7 +205,7 @@ var ShowcasePhoneTemplate = ({
203
205
  style: {
204
206
  width,
205
207
  height,
206
- backgroundColor: "#000",
208
+ backgroundColor: "var(--vanillasky-template-surface, #000)",
207
209
  position: "relative",
208
210
  overflow: "hidden",
209
211
  fontFamily: font
@@ -7,16 +7,18 @@ import {
7
7
  import {
8
8
  SceneBackground,
9
9
  getMediaBackgroundProps
10
- } from "./chunk-EHXDKCYR.js";
10
+ } from "./chunk-6M6DATLW.js";
11
11
  import {
12
12
  TemplateText
13
13
  } from "./chunk-TCEHK2BW.js";
14
14
  import "./chunk-L5BTTCZV.js";
15
- import "./chunk-MFYFZQHM.js";
15
+ import "./chunk-HT6BGERF.js";
16
+ import "./chunk-W3VDIBGR.js";
16
17
  import {
17
18
  interpolate,
18
19
  spring
19
20
  } from "./chunk-XTOXFMLM.js";
21
+ import "./chunk-C6WVCZRW.js";
20
22
  import {
21
23
  hasSceneMedia
22
24
  } from "./chunk-224QNWRA.js";
@@ -247,7 +249,7 @@ var ShowcaseTerminalTemplate = ({
247
249
  style: {
248
250
  width,
249
251
  height,
250
- backgroundColor: "#000",
252
+ backgroundColor: "var(--vanillasky-template-surface, #000)",
251
253
  position: "relative",
252
254
  overflow: "hidden",
253
255
  fontFamily: font
@@ -9,17 +9,19 @@ import {
9
9
  import {
10
10
  SceneBackground,
11
11
  getMediaBackgroundProps
12
- } from "./chunk-EHXDKCYR.js";
12
+ } from "./chunk-6M6DATLW.js";
13
13
  import {
14
14
  TemplateText
15
15
  } from "./chunk-TCEHK2BW.js";
16
16
  import "./chunk-L5BTTCZV.js";
17
- import "./chunk-MFYFZQHM.js";
17
+ import "./chunk-HT6BGERF.js";
18
+ import "./chunk-W3VDIBGR.js";
18
19
  import {
19
20
  SPRING_SMOOTH,
20
21
  interpolate,
21
22
  spring
22
23
  } from "./chunk-XTOXFMLM.js";
24
+ import "./chunk-C6WVCZRW.js";
23
25
  import {
24
26
  hasSceneMedia
25
27
  } from "./chunk-224QNWRA.js";
@@ -320,7 +322,7 @@ var ShowcaseWebTemplate = ({
320
322
  style: {
321
323
  width,
322
324
  height,
323
- backgroundColor: "#000",
325
+ backgroundColor: "var(--vanillasky-template-surface, #000)",
324
326
  position: "relative",
325
327
  overflow: "hidden",
326
328
  fontFamily: font
@@ -1,28 +1,30 @@
1
1
  import {
2
2
  stripPipe
3
3
  } from "./chunk-S7HFLYNN.js";
4
- import {
5
- SceneBackground,
6
- getMediaBackgroundProps
7
- } from "./chunk-EHXDKCYR.js";
8
4
  import {
9
5
  ConfettiLayer
10
6
  } from "./chunk-CJ7F2CE6.js";
11
7
  import "./chunk-QAAO7OAY.js";
8
+ import {
9
+ SceneBackground,
10
+ getMediaBackgroundProps
11
+ } from "./chunk-6M6DATLW.js";
12
12
  import {
13
13
  Emoji,
14
14
  renderWithEmoji
15
15
  } from "./chunk-L5BTTCZV.js";
16
+ import "./chunk-HT6BGERF.js";
16
17
  import {
17
18
  accessibleTextColor,
18
19
  withOpacity
19
- } from "./chunk-MFYFZQHM.js";
20
+ } from "./chunk-W3VDIBGR.js";
20
21
  import {
21
22
  SPRING_BOUNCY,
22
23
  SPRING_SMOOTH,
23
24
  interpolate,
24
25
  spring
25
26
  } from "./chunk-XTOXFMLM.js";
27
+ import "./chunk-C6WVCZRW.js";
26
28
  import "./chunk-224QNWRA.js";
27
29
  import {
28
30
  MEDIA_TEXT_SHADOW,
@@ -242,7 +244,7 @@ var SocialMilestoneTemplate = ({
242
244
  style: {
243
245
  width,
244
246
  height,
245
- backgroundColor: "#000",
247
+ backgroundColor: "var(--vanillasky-template-surface, #000)",
246
248
  position: "relative",
247
249
  overflow: "hidden",
248
250
  fontFamily: font,
@@ -4,17 +4,19 @@ import {
4
4
  import {
5
5
  SceneBackground,
6
6
  getMediaBackgroundProps
7
- } from "./chunk-EHXDKCYR.js";
7
+ } from "./chunk-6M6DATLW.js";
8
8
  import {
9
9
  Emoji,
10
10
  renderWithEmoji
11
11
  } from "./chunk-L5BTTCZV.js";
12
- import "./chunk-MFYFZQHM.js";
12
+ import "./chunk-HT6BGERF.js";
13
+ import "./chunk-W3VDIBGR.js";
13
14
  import {
14
15
  SPRING_SMOOTH,
15
16
  interpolate,
16
17
  spring
17
18
  } from "./chunk-XTOXFMLM.js";
19
+ import "./chunk-C6WVCZRW.js";
18
20
  import "./chunk-224QNWRA.js";
19
21
  import {
20
22
  resolveTokens
@@ -202,7 +204,7 @@ var SocialNotificationTemplate = ({
202
204
  style: {
203
205
  width,
204
206
  height,
205
- backgroundColor: "#000",
207
+ backgroundColor: "var(--vanillasky-template-surface, #000)",
206
208
  position: "relative",
207
209
  overflow: "hidden",
208
210
  fontFamily: font
@@ -4,16 +4,18 @@ import {
4
4
  import {
5
5
  SceneBackground,
6
6
  getMediaBackgroundProps
7
- } from "./chunk-EHXDKCYR.js";
7
+ } from "./chunk-6M6DATLW.js";
8
+ import "./chunk-HT6BGERF.js";
8
9
  import {
9
10
  accessibleTextColor,
10
11
  withOpacity
11
- } from "./chunk-MFYFZQHM.js";
12
+ } from "./chunk-W3VDIBGR.js";
12
13
  import {
13
14
  SPRING_SNAPPY,
14
15
  interpolate,
15
16
  spring
16
17
  } from "./chunk-XTOXFMLM.js";
18
+ import "./chunk-C6WVCZRW.js";
17
19
  import "./chunk-224QNWRA.js";
18
20
  import {
19
21
  lighten,
@@ -194,7 +196,7 @@ var SocialReviewStackTemplate = ({
194
196
  style: {
195
197
  width,
196
198
  height,
197
- backgroundColor: "#000",
199
+ backgroundColor: "var(--vanillasky-template-surface, #000)",
198
200
  position: "relative",
199
201
  overflow: "hidden",
200
202
  fontFamily: font,
@@ -4,18 +4,20 @@ import {
4
4
  import {
5
5
  SceneBackground,
6
6
  getMediaBackgroundProps
7
- } from "./chunk-EHXDKCYR.js";
7
+ } from "./chunk-6M6DATLW.js";
8
+ import "./chunk-HT6BGERF.js";
8
9
  import {
9
10
  accessibleTextColor,
10
11
  autoTextColor,
11
12
  withOpacity
12
- } from "./chunk-MFYFZQHM.js";
13
+ } from "./chunk-W3VDIBGR.js";
13
14
  import {
14
15
  SPRING_SMOOTH,
15
16
  SPRING_SNAPPY,
16
17
  interpolate,
17
18
  spring
18
19
  } from "./chunk-XTOXFMLM.js";
20
+ import "./chunk-C6WVCZRW.js";
19
21
  import "./chunk-224QNWRA.js";
20
22
  import {
21
23
  TOKEN_DEFAULTS,
@@ -220,7 +222,7 @@ var SocialTestimonialTemplate = ({
220
222
  style: {
221
223
  width,
222
224
  height,
223
- backgroundColor: "#000",
225
+ backgroundColor: "var(--vanillasky-template-surface, #000)",
224
226
  position: "relative",
225
227
  overflow: "hidden",
226
228
  fontFamily: font,
@@ -4,18 +4,20 @@ import {
4
4
  import {
5
5
  SceneBackground,
6
6
  getMediaBackgroundProps
7
- } from "./chunk-EHXDKCYR.js";
7
+ } from "./chunk-6M6DATLW.js";
8
8
  import {
9
9
  renderWithEmoji
10
10
  } from "./chunk-L5BTTCZV.js";
11
+ import "./chunk-HT6BGERF.js";
11
12
  import {
12
13
  autoTextColor
13
- } from "./chunk-MFYFZQHM.js";
14
+ } from "./chunk-W3VDIBGR.js";
14
15
  import {
15
16
  SPRING_SMOOTH,
16
17
  interpolate,
17
18
  spring
18
19
  } from "./chunk-XTOXFMLM.js";
20
+ import "./chunk-C6WVCZRW.js";
19
21
  import "./chunk-224QNWRA.js";
20
22
  import {
21
23
  resolveTokens
@@ -249,7 +251,7 @@ var SocialTweetTemplate = ({
249
251
  style: {
250
252
  width,
251
253
  height,
252
- backgroundColor: "#000",
254
+ backgroundColor: "var(--vanillasky-template-surface, #000)",
253
255
  position: "relative",
254
256
  overflow: "hidden",
255
257
  fontFamily: "'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif"
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "dependencies": {
11
11
  "@ai-sdk/anthropic": "^4.0.41",
12
- "@vanillaskyai/video": "0.5.5",
12
+ "@vanillaskyai/video": "0.5.7",
13
13
  "ai": "^7.0.77",
14
14
  "next": "16.3.2",
15
15
  "react": "19.2.8",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanillaskyai/video",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "description": "Open-source video response SDK for personalized AI applications.",
5
5
  "keywords": [
6
6
  "generative-video",
@@ -24,11 +24,29 @@
24
24
  "target": "vanillasky/scene-templates/background-effect.ts",
25
25
  "content": "/**\n * Background motion effects for templates.\n *\n * Applied to the background layer (media, gradient) throughout the entire scene.\n * Continuous motion — not tied to text timing.\n *\n * Templates with usesGlobalBackgroundEffect: true consume the video-level default.\n *\n * 9 effects, each with a distinct mood:\n * - static: No motion — intentional stillness\n * - slow-zoom-in: Draw attention inward, build focus\n * - slow-zoom-out: Reveal, establish, pull back\n * - ken-burns: Classic cinematic photo motion (zoom + pan, direction alternates per scene)\n * - drift: Gentle lateral pan (direction alternates per scene)\n * - pulse: Beat-synced energy\n * - breathe: Ambient dreamy float\n * - slow-tilt: Tension, Dutch angle drift\n * - camera-shake: Intensity, handheld drama\n */\n\nimport { interpolate } from \"../motion\";\n\nexport interface BackgroundTransform {\n transform: string;\n transformOrigin: string;\n}\n\nexport const BACKGROUND_EFFECTS = [\n \"static\",\n \"slow-zoom-in\",\n \"slow-zoom-out\",\n \"ken-burns\",\n \"drift\",\n \"pulse\",\n \"breathe\",\n \"slow-tilt\",\n \"camera-shake\",\n] as const;\n\n// ─── Directional variant helpers ────────────────────────────────\n// Ken Burns and Drift have internal direction variants that alternate\n// by scene index to create visual variety across a video.\n\ntype Direction = \"right\" | \"left\" | \"up\" | \"down\";\nconst DIRECTIONS: Direction[] = [\"right\", \"left\", \"up\", \"down\"];\n\nfunction getKenBurnsTransform(progress: number, direction: Direction): BackgroundTransform {\n const scale = interpolate(progress, [0, 1], [1.05, 1.15]);\n const mainAxis = direction === \"right\" || direction === \"left\";\n const sign = direction === \"right\" || direction === \"down\" ? 1 : -1;\n const primary = interpolate(progress, [0, 1], [-2 * sign, 2 * sign]);\n const secondary = interpolate(progress, [0, 1], [-0.5, 0.5]);\n\n const x = mainAxis ? primary : secondary;\n const y = mainAxis ? secondary : primary;\n\n return {\n transform: `scale(${scale}) translate(${x}%, ${y}%)`,\n transformOrigin: \"center\",\n };\n}\n\nfunction getDriftTransform(progress: number, direction: Direction): BackgroundTransform {\n const horizontal = direction === \"right\" || direction === \"left\";\n const sign = direction === \"right\" || direction === \"down\" ? 1 : -1;\n const offset = interpolate(progress, [0, 1], [-2 * sign, 2 * sign]);\n\n return {\n transform: horizontal\n ? `scale(1.1) translateX(${offset}%)`\n : `scale(1.1) translateY(${offset}%)`,\n transformOrigin: \"center\",\n };\n}\n\n// ─── Public API ─────────────────────────────────────────────────\n\n/**\n * Get background transform for the current progress.\n *\n * @param effect — background effect name\n * @param progress — scene progress 0→1\n * @param beatIntensity — 0→1 for beat-reactive effects\n * @param sceneIndex — used to alternate direction for ken-burns/drift\n */\nexport function getBackgroundTransform(\n effect: string | undefined,\n progress: number,\n beatIntensity = 0,\n sceneIndex = 0,\n): BackgroundTransform {\n const dir = DIRECTIONS[sceneIndex % DIRECTIONS.length];\n\n // Camera motion is opt-in. In particular, a scene-relative zoom restarts\n // at every cut and can look like the incoming media snaps backwards.\n // Footage already contains motion, while photo scenes that benefit from a\n // Ken Burns treatment can request one explicitly.\n const resolved = effect ?? \"static\";\n\n switch (resolved) {\n case \"slow-zoom-in\":\n return {\n transform: `scale(${interpolate(progress, [0, 1], [1, 1.12])})`,\n transformOrigin: \"center\",\n };\n\n case \"slow-zoom-out\":\n return {\n transform: `scale(${interpolate(progress, [0, 1], [1.12, 1])})`,\n transformOrigin: \"center\",\n };\n\n case \"ken-burns\":\n return getKenBurnsTransform(progress, dir);\n\n case \"drift\":\n return getDriftTransform(progress, dir);\n\n case \"pulse\": {\n // Subtle scale pulse synced to beat\n const base = 1 + beatIntensity * 0.04;\n const breathe = 1 + Math.sin(progress * Math.PI * 2) * 0.02;\n return {\n transform: `scale(${base * breathe})`,\n transformOrigin: \"center\",\n };\n }\n\n case \"breathe\": {\n // Slow ambient float — gentle scale + vertical drift, not beat-synced\n const s = 1 + Math.sin(progress * Math.PI) * 0.03;\n const y = Math.sin(progress * Math.PI) * 0.5;\n return {\n transform: `scale(${s}) translateY(${y}%)`,\n transformOrigin: \"center\",\n };\n }\n\n case \"slow-tilt\": {\n // Subtle Dutch angle drift\n const angle = interpolate(progress, [0, 1], [0, 2.5]);\n return {\n transform: `scale(1.08) rotate(${angle}deg)`,\n transformOrigin: \"center\",\n };\n }\n\n case \"camera-shake\": {\n // Rapid small offsets for handheld/dramatic feel\n // Deterministic noise based on progress for reproducibility\n const t = progress * 40;\n const sx = Math.sin(t * 2.3) * 0.4 + Math.sin(t * 5.7) * 0.2;\n const sy = Math.cos(t * 3.1) * 0.3 + Math.cos(t * 4.9) * 0.15;\n const sr = Math.sin(t * 1.7) * 0.3;\n return {\n transform: `scale(1.05) translate(${sx}%, ${sy}%) rotate(${sr}deg)`,\n transformOrigin: \"center\",\n };\n }\n\n case \"static\":\n default:\n return {\n transform: \"none\",\n transformOrigin: \"center\",\n };\n }\n}\n"
26
26
  },
27
+ {
28
+ "path": "src/visual-system/scene-templates/external-video-backdrop.tsx",
29
+ "type": "registry:lib",
30
+ "target": "vanillasky/scene-templates/external-video-backdrop.tsx",
31
+ "content": "import React from \"react\";\n\n// Source-owned templates may live in a consumer's tree while VideoFrame comes\n// from the package. Both copies must observe the same internal context or the\n// consumer template would mount a second video over the player-owned plane.\nexport type ExternalVideoBackdropMode = false | \"pending\" | \"ready\" | \"fallback\";\n\nconst sharedContext = globalThis as typeof globalThis & {\n __vanillaskyExternalVideoBackdropContext?: React.Context<ExternalVideoBackdropMode>;\n};\nconst ExternalVideoBackdropContext = sharedContext.__vanillaskyExternalVideoBackdropContext\n ??= React.createContext<ExternalVideoBackdropMode>(false);\n\nexport function ExternalVideoBackdropProvider({\n mode,\n children,\n}: {\n mode: ExternalVideoBackdropMode;\n children: React.ReactNode;\n}) {\n return (\n <ExternalVideoBackdropContext.Provider value={mode}>\n {children}\n </ExternalVideoBackdropContext.Provider>\n );\n}\n\nexport function useExternalVideoBackdrop(): ExternalVideoBackdropMode {\n return React.useContext(ExternalVideoBackdropContext);\n}\n"
32
+ },
33
+ {
34
+ "path": "src/visual-system/scene-templates/media-position.ts",
35
+ "type": "registry:lib",
36
+ "target": "vanillasky/scene-templates/media-position.ts",
37
+ "content": "export type MediaPosition = \"center\" | \"top\" | \"bottom\" | \"left\" | \"right\";\n\nconst MEDIA_POSITIONS: Record<MediaPosition, string> = {\n center: \"center center\",\n top: \"center top\",\n bottom: \"center bottom\",\n left: \"left center\",\n right: \"right center\",\n};\n\nexport function resolveMediaPosition(value: string): string {\n return MEDIA_POSITIONS[value as MediaPosition] ?? MEDIA_POSITIONS.center;\n}\n"
38
+ },
39
+ {
40
+ "path": "src/visual-system/scene-templates/scene-video-backdrop.tsx",
41
+ "type": "registry:lib",
42
+ "target": "vanillasky/scene-templates/scene-video-backdrop.tsx",
43
+ "content": "import React, { useEffect, useRef, useState } from \"react\";\nimport { getBackgroundTransform } from \"../backgrounds\";\nimport { resolveMediaPosition } from \"./media-position\";\n\nexport interface SceneVideoBackdropProps {\n mediaUrl: string;\n mediaPoster?: string;\n mediaPosition?: string;\n backgroundEffect?: string;\n progress: number;\n beatIntensity?: number;\n isPlaying: boolean;\n playbackId?: string;\n retainPoster?: boolean;\n persistent?: boolean;\n preparedPoster?: {\n presentationKey: string;\n mediaPoster: string;\n mediaPosition: string;\n backgroundEffect?: string;\n /** Existing global transition progress. On decoder-constrained Safari,\n * this fades the decoded incoming still above the outgoing video before\n * the single video element changes source. */\n opacity?: number;\n };\n onReady?: () => void;\n onError?: () => void;\n}\n\nexport const SceneVideoBackdrop: React.FC<SceneVideoBackdropProps> = ({\n mediaUrl,\n mediaPoster,\n mediaPosition = \"center\",\n backgroundEffect,\n progress,\n beatIntensity = 0,\n isPlaying,\n playbackId = mediaUrl,\n retainPoster = false,\n persistent = false,\n preparedPoster,\n onReady,\n onError,\n}) => {\n const resolvedPosition = resolveMediaPosition(mediaPosition);\n const bgTransform = getBackgroundTransform(backgroundEffect, progress, beatIntensity);\n const [decodedVideoUrl, setDecodedVideoUrl] = useState<string>();\n const videoRef = useRef<HTMLVideoElement>(null);\n const startedVideoUrl = useRef<string | undefined>(undefined);\n const startedPlaybackId = useRef<string | undefined>(undefined);\n const videoPresentationKey = `${playbackId}\\0${mediaUrl}`;\n\n useEffect(() => {\n const video = videoRef.current;\n if (!video) return;\n if (!isPlaying) {\n video.pause();\n startedVideoUrl.current = undefined;\n startedPlaybackId.current = undefined;\n return;\n }\n if (startedPlaybackId.current === playbackId) return;\n const changingSource = startedVideoUrl.current !== undefined && startedVideoUrl.current !== mediaUrl;\n video.playbackRate = 1;\n if (!changingSource && video.currentTime > 0) video.currentTime = 0;\n video.play().catch(() => {});\n startedVideoUrl.current = mediaUrl;\n startedPlaybackId.current = playbackId;\n }, [isPlaying, mediaUrl, playbackId]);\n\n useEffect(() => {\n const video = videoRef.current;\n return () => {\n if (!video) return;\n video.pause();\n video.removeAttribute(\"src\");\n video.load();\n };\n }, []);\n\n const mediaStyle: React.CSSProperties = {\n position: \"absolute\",\n inset: 0,\n width: \"100%\",\n height: \"100%\",\n objectFit: \"cover\",\n objectPosition: resolvedPosition,\n transform: bgTransform.transform,\n transformOrigin: bgTransform.transformOrigin,\n zIndex: persistent ? 1 : undefined,\n };\n const preparedPosition = preparedPoster\n ? resolveMediaPosition(preparedPoster.mediaPosition)\n : resolvedPosition;\n const preparedTransform = getBackgroundTransform(preparedPoster?.backgroundEffect, 0, 0);\n const posterPlanes = [\n ...(persistent && mediaPoster ? [{\n presentationKey: videoPresentationKey,\n mediaPoster,\n mediaPosition: resolvedPosition,\n transform: bgTransform.transform,\n transformOrigin: bgTransform.transformOrigin,\n opacity: 1,\n zIndex: 0,\n role: \"current\",\n }] : []),\n ...(preparedPoster && preparedPoster.presentationKey !== videoPresentationKey ? [{\n presentationKey: preparedPoster.presentationKey,\n mediaPoster: preparedPoster.mediaPoster,\n mediaPosition: preparedPosition,\n transform: preparedTransform.transform,\n transformOrigin: preparedTransform.transformOrigin,\n opacity: preparedPoster.opacity ?? 0,\n zIndex: 2,\n role: \"prepared\",\n }] : []),\n ];\n\n return (\n <>\n {posterPlanes.map((posterPlane) => (\n <img\n key={posterPlane.presentationKey}\n src={posterPlane.mediaPoster}\n alt=\"\"\n aria-hidden=\"true\"\n draggable={false}\n data-video-poster-plane={posterPlane.role}\n data-video-poster-visible={posterPlane.opacity > 0 ? \"true\" : \"false\"}\n style={{\n position: \"absolute\",\n inset: 0,\n width: \"100%\",\n height: \"100%\",\n objectFit: \"cover\",\n objectPosition: posterPlane.mediaPosition,\n transform: posterPlane.transform,\n transformOrigin: posterPlane.transformOrigin,\n zIndex: posterPlane.zIndex,\n opacity: posterPlane.opacity,\n pointerEvents: \"none\",\n }}\n />\n ))}\n <video\n ref={videoRef}\n src={mediaUrl}\n poster={retainPoster || decodedVideoUrl !== mediaUrl ? mediaPoster || undefined : undefined}\n muted\n loop\n playsInline\n preload=\"auto\"\n onLoadedData={(event) => {\n const video = event.currentTarget;\n const markPresented = () => {\n if (!video.isConnected) return;\n onReady?.();\n if (!retainPoster) setDecodedVideoUrl(mediaUrl);\n };\n if (video.requestVideoFrameCallback) {\n video.requestVideoFrameCallback(markPresented);\n return;\n }\n markPresented();\n }}\n onError={onError}\n data-media-position={mediaPosition}\n data-video-backdrop={persistent ? \"persistent\" : \"scene\"}\n style={mediaStyle}\n />\n </>\n );\n};\n"
44
+ },
27
45
  {
28
46
  "path": "src/visual-system/scene-templates/color-utils.ts",
29
47
  "type": "registry:lib",
30
48
  "target": "vanillasky/scene-templates/color-utils.ts",
31
- "content": "/**\n * Color utilities for scene templates.\n *\n * Provides safe color manipulation that works with hex colors\n * and graceful fallbacks for non-hex inputs.\n */\nimport React from \"react\";\nimport { resolveTokens, darken, orderDarkToLight, type BackgroundFamily } from \"../theme\";\n\n// ─── Layout constants ───────────────────────────────────────\n//\n// Shared vertical layout for templates that place a TextOverlay at the top of\n// the scene (showcase-*, chart-*, infographic-*). Using a single constant\n// guarantees the headline sits in the same place across every template, so\n// scene-to-scene cuts don't visually jump.\n//\n// TextOverlay vertically centers itself inside its container, so the visual\n// midline of the headline = TOP_TEXT_AREA_RATIO / 2 of the scene height.\nexport const TOP_TEXT_AREA_RATIO = 0.32;\n\n/**\n * Gradient background — clean 2-color linear gradient with a subtle\n * animated breathing overlay. Matches the look of a plain CSS\n * `linear-gradient(90deg, colorA, colorB)` so palette chips preview\n * exactly what templates render.\n *\n * The animation is deliberately minimal: two low-opacity radial blobs\n * of the same two colors drift slowly, adding just enough motion for\n * video without introducing a third \"midtone\" color that muddies\n * the gradient (a common source of unwanted purple/brown tints).\n */\nexport function gradientBackground({\n colorA,\n colorB,\n solidBg,\n progress,\n sceneDuration,\n seed,\n family,\n}: {\n colorA: string;\n colorB: string;\n /**\n * When set, the function returns this color as a solid background\n * (no gradient, no animated radial overlays). Used when the user has\n * a scraped brand whose darkest color reads as a real surface — the\n * accent stays on text/CTAs, but the bg goes flat instead of pairing\n * with an unrelated brand color and producing a noisy gradient.\n */\n solidBg?: string;\n progress: number;\n sceneDuration?: number;\n seed: number;\n /**\n * Background family from the style preset. Omitted = \"mesh\", the original\n * look — so existing configs are untouched. All families are pure CSS\n * background strings (no `filter`, which the SVG export can't rasterize)\n * and stay deterministic in `progress`.\n */\n family?: BackgroundFamily;\n}): string {\n if (solidBg) return solidBg;\n // Every scene owns its foreground motion clock, but consecutive scenes that\n // use the same brand backdrop must not visibly reset that backdrop. Run one\n // eased closed loop per scene: the gradient has zero velocity and the exact\n // same pixels at progress 0 and 1, regardless of scene duration or content\n // seed. Seeded variation is strongest mid-scene and converges to zero at\n // both handoff edges.\n const normalizedProgress = Math.max(0, Math.min(1, progress));\n const atBoundary = normalizedProgress === 0 || normalizedProgress === 1;\n const easedLoop = normalizedProgress * normalizedProgress * (3 - 2 * normalizedProgress);\n const seedEnvelope = atBoundary ? 0 : Math.sin(normalizedProgress * Math.PI) ** 2;\n void sceneDuration;\n\n const seededRandom = (s: number): number => {\n const x = Math.sin(s * 12.9898 + s * 78.233) * 43758.5453;\n return x - Math.floor(x);\n };\n const phase = seededRandom(seed) * Math.PI * 2 * seedEnvelope;\n const t = (atBoundary ? 0 : easedLoop * Math.PI * 2) + phase;\n\n // Darker of the two colors anchors the top of the frame so text and logos\n // sit over the stronger value. 180deg = top→bottom.\n const [topColor, bottomColor] = orderDarkToLight(colorA, colorB);\n\n if (family === \"wash\") {\n // No radials: one calm vertical ramp that drifts a few percent over the\n // scene. Reads considered rather than energetic.\n const shift = Math.sin(t) * 6;\n return `linear-gradient(${175 + shift}deg, ${topColor} 0%, ${bottomColor} 100%)`;\n }\n\n if (family === \"spotlight\") {\n // A single hard pool of accent high in the frame over a near-black\n // surround — high contrast, very little colour area.\n const x = 50 + Math.sin(t) * 8;\n const y = 28 + Math.cos(t + 1) * 5;\n return `\n radial-gradient(ellipse 70% 55% at ${x}% ${y}%, ${colorA}66 0%, ${colorA}1a 45%, transparent 72%),\n linear-gradient(180deg, ${darken(topColor, 0.55)} 0%, ${darken(bottomColor, 0.7)} 100%)\n `;\n }\n\n const x1 = 25 + Math.sin(t) * 25;\n const y1 = 30 + Math.cos(t + 1.0) * 25;\n const x2 = 75 + Math.sin(t + 2.0) * 25;\n const y2 = 70 + Math.cos(t + 3.5) * 25;\n\n return `\n radial-gradient(ellipse 60% 60% at ${x1}% ${y1}%, ${colorA}4d 0%, transparent 65%),\n radial-gradient(ellipse 60% 60% at ${x2}% ${y2}%, ${colorB}4d 0%, transparent 65%),\n linear-gradient(180deg, ${topColor} 0%, ${bottomColor} 100%)\n `;\n}\n\n/**\n * Animated brand-background overlay. Foreground semantic colours never alter\n * this visual background.\n */\nexport const BrandGradientOverlay: React.FC<{\n style: Parameters<typeof resolveTokens>[0];\n progress: number;\n sceneDuration?: number;\n seed: number;\n}> = ({ style: globalStyle, progress, sceneDuration, seed }) => {\n const tokens = resolveTokens(globalStyle);\n const [first, second] = tokens.background.type === \"gradient\"\n ? tokens.background.colors\n : [tokens.background.color, tokens.background.color];\n const solidBg = tokens.background.type === \"solid\" ? tokens.background.color : undefined;\n\n return React.createElement(\"div\", {\n style: {\n position: \"absolute\" as const,\n inset: 0,\n background: gradientBackground({ colorA: first, colorB: second, solidBg, progress, sceneDuration, seed, family: tokens.preset.background }),\n pointerEvents: \"none\" as const,\n },\n });\n};\n"
49
+ "content": "/**\n * Color utilities for scene templates.\n *\n * Provides safe color manipulation that works with hex colors\n * and graceful fallbacks for non-hex inputs.\n */\nimport React from \"react\";\nimport { resolveTokens, darken, orderDarkToLight, type BackgroundFamily } from \"../theme\";\n\n// ─── Layout constants ───────────────────────────────────────\n//\n// Shared vertical layout for templates that place a TextOverlay at the top of\n// the scene (showcase-*, chart-*, infographic-*). Using a single constant\n// guarantees the headline sits in the same place across every template, so\n// scene-to-scene cuts don't visually jump.\n//\n// TextOverlay vertically centers itself inside its container, so the visual\n// midline of the headline = TOP_TEXT_AREA_RATIO / 2 of the scene height.\nexport const TOP_TEXT_AREA_RATIO = 0.32;\n\n/**\n * Gradient background — clean 2-color linear gradient with a subtle\n * animated breathing overlay. Matches the look of a plain CSS\n * `linear-gradient(90deg, colorA, colorB)` so palette chips preview\n * exactly what templates render.\n *\n * The animation is deliberately minimal: two low-opacity radial blobs\n * of the same two colors drift slowly, adding just enough motion for\n * video without introducing a third \"midtone\" color that muddies\n * the gradient (a common source of unwanted purple/brown tints).\n */\nexport function gradientBackground({\n colorA,\n colorB,\n solidBg,\n progress,\n sceneDuration,\n seed,\n family,\n}: {\n colorA: string;\n colorB: string;\n /**\n * When set, the function returns this color as a solid background\n * (no gradient, no animated radial overlays). Used when the user has\n * a scraped brand whose darkest color reads as a real surface — the\n * accent stays on text/CTAs, but the bg goes flat instead of pairing\n * with an unrelated brand color and producing a noisy gradient.\n */\n solidBg?: string;\n progress: number;\n sceneDuration?: number;\n seed: number;\n /**\n * Background family from the style preset. Omitted = \"mesh\", the original\n * look — so existing configs are untouched. All families are pure CSS\n * background strings (no `filter`, which the SVG export can't rasterize)\n * and stay deterministic in `progress`.\n */\n family?: BackgroundFamily;\n}): string {\n if (solidBg) return solidBg;\n // Every scene owns its foreground motion clock, but consecutive scenes that\n // use the same brand backdrop must not visibly reset that backdrop. Run one\n // eased closed loop per scene: the gradient has zero velocity and the exact\n // same pixels at progress 0 and 1, regardless of scene duration or content\n // seed. Seeded variation is strongest mid-scene and converges to zero at\n // both handoff edges.\n const normalizedProgress = Math.max(0, Math.min(1, progress));\n const atBoundary = normalizedProgress === 0 || normalizedProgress === 1;\n const easedLoop = normalizedProgress * normalizedProgress * (3 - 2 * normalizedProgress);\n const seedEnvelope = atBoundary ? 0 : Math.sin(normalizedProgress * Math.PI) ** 2;\n void sceneDuration;\n\n const seededRandom = (s: number): number => {\n const x = Math.sin(s * 12.9898 + s * 78.233) * 43758.5453;\n return x - Math.floor(x);\n };\n const phase = seededRandom(seed) * Math.PI * 2 * seedEnvelope;\n const t = (atBoundary ? 0 : easedLoop * Math.PI * 2) + phase;\n\n // Darker of the two colors anchors the top of the frame so text and logos\n // sit over the stronger value. 180deg = top→bottom.\n const [topColor, bottomColor] = orderDarkToLight(colorA, colorB);\n\n if (family === \"wash\") {\n // No radials: one calm vertical ramp that drifts a few percent over the\n // scene. Reads considered rather than energetic.\n const shift = Math.sin(t) * 6;\n return `linear-gradient(${175 + shift}deg, ${topColor} 0%, ${bottomColor} 100%)`;\n }\n\n if (family === \"spotlight\") {\n // A single hard pool of accent high in the frame over a near-black\n // surround — high contrast, very little colour area.\n const x = 50 + Math.sin(t) * 8;\n const y = 28 + Math.cos(t + 1) * 5;\n return `\n radial-gradient(ellipse 70% 55% at ${x}% ${y}%, ${colorA}66 0%, ${colorA}1a 45%, transparent 72%),\n linear-gradient(180deg, ${darken(topColor, 0.55)} 0%, ${darken(bottomColor, 0.7)} 100%)\n `;\n }\n\n const x1 = 25 + Math.sin(t) * 25;\n const y1 = 30 + Math.cos(t + 1.0) * 25;\n const x2 = 75 + Math.sin(t + 2.0) * 25;\n const y2 = 70 + Math.cos(t + 3.5) * 25;\n\n return `\n radial-gradient(ellipse 60% 60% at ${x1}% ${y1}%, ${colorA}4d 0%, transparent 65%),\n radial-gradient(ellipse 60% 60% at ${x2}% ${y2}%, ${colorB}4d 0%, transparent 65%),\n linear-gradient(180deg, ${topColor} 0%, ${bottomColor} 100%)\n `;\n}\n\n/**\n * Animated brand-background overlay. Foreground semantic colours never alter\n * this visual background.\n */\nexport const BrandGradientOverlay: React.FC<{\n style: Parameters<typeof resolveTokens>[0];\n progress: number;\n sceneDuration?: number;\n seed: number;\n}> = ({ style: globalStyle, progress, sceneDuration, seed }) => {\n const tokens = resolveTokens(globalStyle);\n const [first, second] = tokens.background.type === \"gradient\"\n ? tokens.background.colors\n : [tokens.background.color, tokens.background.color];\n const solidBg = tokens.background.type === \"solid\" ? tokens.background.color : undefined;\n\n return React.createElement(\"div\", {\n \"data-brand-gradient\": \"true\",\n style: {\n position: \"absolute\" as const,\n inset: 0,\n background: gradientBackground({ colorA: first, colorB: second, solidBg, progress, sceneDuration, seed, family: tokens.preset.background }),\n pointerEvents: \"none\" as const,\n },\n });\n};\n"
32
50
  }
33
51
  ],
34
52
  "meta": {