@vanillaskyai/video 0.5.6 → 0.5.8
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/CHANGELOG.md +14 -0
- package/dist/{bg-confetti-572VDSYM.js → bg-confetti-SHQI7ATB.js} +2 -2
- package/dist/{bg-emoji-T4IEJ6U2.js → bg-emoji-XYFRA63Q.js} +2 -2
- package/dist/{bg-media-2D5SQSJX.js → bg-media-FEBATBT2.js} +8 -6
- package/dist/{brand-message-2JCTRIGS.js → brand-message-P56WEJPG.js} +5 -3
- package/dist/{chart-bar-GAJMUB2E.js → chart-bar-ZB45ZRKH.js} +5 -3
- package/dist/{chart-counter-HYGBGXNX.js → chart-counter-HFYNE4HT.js} +8 -6
- package/dist/{chart-progress-ring-DUUVMJAJ.js → chart-progress-ring-ZKMLRGSB.js} +5 -3
- package/dist/check-runtime.js +3 -1
- package/dist/{chunk-CTEYFVUY.js → chunk-6M6DATLW.js} +26 -82
- package/dist/chunk-C6WVCZRW.js +19 -0
- package/dist/chunk-HT6BGERF.js +167 -0
- package/dist/{chunk-3QEKIC6C.js → chunk-NGY3TNND.js} +121 -11
- package/dist/{chunk-MFYFZQHM.js → chunk-W3VDIBGR.js} +1 -0
- package/dist/chunk-WJ6FLKNC.js +15 -0
- package/dist/cli.js +9 -0
- package/dist/{cta-logo-JIG22SWA.js → cta-logo-S4OMTNXD.js} +5 -3
- package/dist/{cta-media-GIHQTPPK.js → cta-media-EROTHDRR.js} +5 -3
- package/dist/{incoming-call-2O5NPJ7B.js → incoming-call-5IWJ2TAS.js} +5 -3
- package/dist/{infographic-before-after-FQXCQ2CY.js → infographic-before-after-2I7YZVDD.js} +2 -2
- package/dist/{infographic-feature-list-MVJ3GQVK.js → infographic-feature-list-7SK65U3K.js} +5 -3
- package/dist/{infographic-problem-solution-ZUF54SNF.js → infographic-problem-solution-IW35S6BR.js} +5 -3
- package/dist/{infographic-stat-row-4DLC5NSC.js → infographic-stat-row-XHUZZPDM.js} +5 -3
- package/dist/{infographic-steps-IT23RFV6.js → infographic-steps-JDDEDI7X.js} +5 -3
- package/dist/{preload-media-IYFMPMLB.js → preload-media-LJXWKTWG.js} +2 -0
- package/dist/{prompt-input-IL7HP5WX.js → prompt-input-6ZWXEL3V.js} +5 -3
- package/dist/react.js +30 -28
- package/dist/{reaction-IDFJZ7HI.js → reaction-Z3JIM3MQ.js} +5 -3
- package/dist/scene-video-backdrop-IL4F2SVD.js +9 -0
- package/dist/{showcase-code-EIGXDZE3.js → showcase-code-NWQAAJIA.js} +5 -3
- package/dist/{showcase-phone-STUZJRXV.js → showcase-phone-5UW45ATT.js} +5 -3
- package/dist/{showcase-terminal-SYP6TKP2.js → showcase-terminal-UVOD25UF.js} +5 -3
- package/dist/{showcase-web-57R4RMTG.js → showcase-web-3AW6VHIG.js} +5 -3
- package/dist/{social-milestone-J6QVX4YJ.js → social-milestone-KPKHFOB5.js} +8 -6
- package/dist/{social-notification-2RYS7NM4.js → social-notification-EDOU6TZB.js} +5 -3
- package/dist/{social-review-stack-25C6JJ4W.js → social-review-stack-EZOO33SV.js} +5 -3
- package/dist/{social-testimonial-SH54T4CW.js → social-testimonial-DLOINMMY.js} +5 -3
- package/dist/{social-tweet-T7RTFQ6T.js → social-tweet-6JUM323G.js} +5 -3
- package/examples/nextjs-quickstart/package.json +1 -1
- package/package.json +1 -1
- package/registry/items/backgrounds.json +19 -1
- package/registry/items/barChart.json +2 -2
- package/registry/items/beforeAfter.json +1 -1
- package/registry/items/bigNumber.json +2 -2
- package/registry/items/brandMessage.json +2 -2
- package/registry/items/cardList.json +2 -2
- package/registry/items/codeEditor.json +2 -2
- package/registry/items/confetti.json +1 -1
- package/registry/items/ctaLogo.json +2 -2
- package/registry/items/ctaMedia.json +2 -2
- package/registry/items/emojiBurst.json +1 -1
- package/registry/items/incomingCall.json +2 -2
- package/registry/items/media.json +2 -2
- package/registry/items/milestone.json +2 -2
- package/registry/items/notification.json +2 -2
- package/registry/items/phoneMockup.json +2 -2
- package/registry/items/problemSolution.json +2 -2
- package/registry/items/progressRing.json +2 -2
- package/registry/items/promptInput.json +2 -2
- package/registry/items/reaction.json +2 -2
- package/registry/items/reviewStack.json +2 -2
- package/registry/items/steps.json +2 -2
- package/registry/items/terminal.json +2 -2
- package/registry/items/testimonial.json +2 -2
- package/registry/items/tripleStats.json +2 -2
- package/registry/items/tweet.json +2 -2
- 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-
|
|
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-
|
|
81
|
-
"reaction": () => import("./reaction-
|
|
82
|
-
"confetti": () => import("./bg-confetti-
|
|
83
|
-
"emojiBurst": () => import("./bg-emoji-
|
|
84
|
-
"bigNumber": () => import("./chart-counter-
|
|
85
|
-
"barChart": () => import("./chart-bar-
|
|
86
|
-
"progressRing": () => import("./chart-progress-ring-
|
|
87
|
-
"phoneMockup": () => import("./showcase-phone-
|
|
88
|
-
"webMockup": () => import("./showcase-web-
|
|
89
|
-
"codeEditor": () => import("./showcase-code-
|
|
90
|
-
"terminal": () => import("./showcase-terminal-
|
|
91
|
-
"tweet": () => import("./social-tweet-
|
|
92
|
-
"notification": () => import("./social-notification-
|
|
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-
|
|
96
|
-
"reviewStack": () => import("./social-review-stack-
|
|
97
|
-
"testimonial": () => import("./social-testimonial-
|
|
98
|
-
"incomingCall": () => import("./incoming-call-
|
|
99
|
-
"brandMessage": () => import("./brand-message-
|
|
100
|
-
"promptInput": () => import("./prompt-input-
|
|
101
|
-
"beforeAfter": () => import("./infographic-before-after-
|
|
102
|
-
"tripleStats": () => import("./infographic-stat-row-
|
|
103
|
-
"problemSolution": () => import("./infographic-problem-solution-
|
|
104
|
-
"cardList": () => import("./infographic-feature-list-
|
|
105
|
-
"steps": () => import("./infographic-steps-
|
|
106
|
-
"ctaLogo": () => import("./cta-logo-
|
|
107
|
-
"ctaMedia": () => import("./cta-media-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
{
|
|
@@ -5,16 +5,18 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
SceneBackground,
|
|
7
7
|
getMediaBackgroundProps
|
|
8
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-CTEYFVUY.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-
|
|
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-
|
|
7
|
+
} from "./chunk-6M6DATLW.js";
|
|
8
8
|
import {
|
|
9
9
|
Emoji,
|
|
10
10
|
renderWithEmoji
|
|
11
11
|
} from "./chunk-L5BTTCZV.js";
|
|
12
|
-
import "./chunk-
|
|
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-
|
|
7
|
+
} from "./chunk-6M6DATLW.js";
|
|
8
|
+
import "./chunk-HT6BGERF.js";
|
|
8
9
|
import {
|
|
9
10
|
accessibleTextColor,
|
|
10
11
|
withOpacity
|
|
11
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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"
|
package/package.json
CHANGED
|
@@ -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": {
|