@vanillaskyai/video 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/LICENSE +201 -0
- package/PUBLIC-API.md +276 -0
- package/README.md +152 -0
- package/SECURITY.md +22 -0
- package/SUPPORT.md +28 -0
- package/bin/vanillasky.js +3 -0
- package/dist/bg-confetti-WNQXS7ZS.js +89 -0
- package/dist/bg-emoji-ETI47GLS.js +150 -0
- package/dist/bg-media-SVUZAMGX.js +99 -0
- package/dist/brand-message-F3RD4P3P.js +253 -0
- package/dist/builtin-server-F7YXRVCH.js +8 -0
- package/dist/catalog-types-BIhSpOWK.d.ts +74 -0
- package/dist/chart-bar-CNSC7BNK.js +304 -0
- package/dist/chart-counter-MCRHKN77.js +299 -0
- package/dist/chart-progress-ring-RMI4A7ZK.js +296 -0
- package/dist/check-runtime.d.ts +15 -0
- package/dist/check-runtime.js +102 -0
- package/dist/chunk-2E6T633S.js +27 -0
- package/dist/chunk-352ZZCET.js +83 -0
- package/dist/chunk-73NTSFFI.js +81 -0
- package/dist/chunk-A2X45FET.js +46 -0
- package/dist/chunk-ABPKM4GK.js +187 -0
- package/dist/chunk-ABZACD4G.js +216 -0
- package/dist/chunk-BCRUUJ6A.js +756 -0
- package/dist/chunk-CHLGW22F.js +3551 -0
- package/dist/chunk-CJ7F2CE6.js +104 -0
- package/dist/chunk-CR7FE7BW.js +109 -0
- package/dist/chunk-E7CL7UPB.js +109 -0
- package/dist/chunk-EE6PK6XC.js +18 -0
- package/dist/chunk-ERY4S6QV.js +385 -0
- package/dist/chunk-EVG2ZYIY.js +125 -0
- package/dist/chunk-FNAEQ7QO.js +155 -0
- package/dist/chunk-FQ47WXAJ.js +38 -0
- package/dist/chunk-FSG2PYWG.js +336 -0
- package/dist/chunk-GHJEAP4O.js +504 -0
- package/dist/chunk-GRMUV7QY.js +262 -0
- package/dist/chunk-IFOW65Z6.js +205 -0
- package/dist/chunk-IXCUY3B3.js +786 -0
- package/dist/chunk-L5BTTCZV.js +97 -0
- package/dist/chunk-LMG3GHLL.js +107 -0
- package/dist/chunk-OOPIETLW.js +101 -0
- package/dist/chunk-P22HHFDK.js +43 -0
- package/dist/chunk-QAAO7OAY.js +19 -0
- package/dist/chunk-S7HFLYNN.js +35 -0
- package/dist/chunk-SPVTJH3F.js +24 -0
- package/dist/chunk-W2XJKG6C.js +3177 -0
- package/dist/chunk-XB7LH4J2.js +301 -0
- package/dist/chunk-XGRA2MUV.js +242 -0
- package/dist/chunk-XTOXFMLM.js +105 -0
- package/dist/chunk-YC5MFXA5.js +132 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1681 -0
- package/dist/compose-video-CN3VSWST.js +13 -0
- package/dist/cta-logo-SVA5GP2A.js +209 -0
- package/dist/cta-media-E7VLLK42.js +215 -0
- package/dist/events-_wfjbmEp.d.ts +81 -0
- package/dist/incoming-call-62MFZL3Z.js +320 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +14 -0
- package/dist/infographic-before-after-AXCDAWND.js +460 -0
- package/dist/infographic-feature-list-GW5GUE6O.js +266 -0
- package/dist/infographic-problem-solution-7NURJANA.js +340 -0
- package/dist/infographic-stat-row-J7IZR6ZN.js +241 -0
- package/dist/infographic-steps-VSZM6F5M.js +400 -0
- package/dist/kit-BMNcHqa9.d.ts +79 -0
- package/dist/prompt-input-LOUCRXCY.js +236 -0
- package/dist/react.d.ts +81 -0
- package/dist/react.js +835 -0
- package/dist/reaction-QG4CYZLQ.js +119 -0
- package/dist/server.d.ts +141 -0
- package/dist/server.js +498 -0
- package/dist/showcase-code-RUTFZR3H.js +351 -0
- package/dist/showcase-phone-6CP6XEU5.js +272 -0
- package/dist/showcase-terminal-MGAEQWPR.js +306 -0
- package/dist/showcase-web-Y4W2HVRJ.js +390 -0
- package/dist/social-conversation-75SD2637.js +695 -0
- package/dist/social-milestone-XI2QE5BO.js +292 -0
- package/dist/social-notification-2UOHQCUY.js +244 -0
- package/dist/social-review-stack-FDYZGZTJ.js +239 -0
- package/dist/social-testimonial-OQ7N52GJ.js +284 -0
- package/dist/social-tweet-5OLCNPPC.js +295 -0
- package/dist/state-DZcKuS32.d.ts +3 -0
- package/dist/system-prompt-A6UXNTPK.js +8 -0
- package/dist/template-catalog.d.ts +24 -0
- package/dist/template-catalog.js +6 -0
- package/dist/templates.d.ts +25 -0
- package/dist/templates.js +19 -0
- package/dist/test.d.ts +149 -0
- package/dist/test.js +272 -0
- package/dist/text-stream-6XHC7J47.js +10 -0
- package/dist/types-B-PZR6G_.d.ts +208 -0
- package/dist/validate-T7GBU2YF.js +8 -0
- package/docs/agent-integration.md +39 -0
- package/docs/architecture.md +84 -0
- package/docs/branding-and-personalization.md +87 -0
- package/docs/concepts.md +106 -0
- package/docs/custom-templates.md +329 -0
- package/docs/customization.md +141 -0
- package/docs/errors.md +70 -0
- package/docs/getting-started.md +111 -0
- package/docs/input-and-first-scene.md +41 -0
- package/docs/integrate-nextjs.md +79 -0
- package/docs/media-and-audio.md +69 -0
- package/docs/motion-and-effects.md +107 -0
- package/docs/persistence.md +95 -0
- package/docs/production.md +122 -0
- package/docs/prompt-and-input.md +185 -0
- package/docs/provider-integration.md +115 -0
- package/docs/reference/protocol.md +96 -0
- package/docs/reference/provider-adapters.md +158 -0
- package/docs/responsive-orientation.md +37 -0
- package/docs/security.md +38 -0
- package/docs/streaming-protocol.md +15 -0
- package/docs/testing.md +137 -0
- package/docs/use-cases.md +59 -0
- package/examples/custom-template/README.md +19 -0
- package/examples/custom-template/minimal-text.tsx +82 -0
- package/examples/custom-template/structured-data.tsx +104 -0
- package/examples/custom-template/supplied-media.tsx +92 -0
- package/examples/nextjs-quickstart/.env.example +2 -0
- package/examples/nextjs-quickstart/README.md +22 -0
- package/examples/nextjs-quickstart/next-env.d.ts +4 -0
- package/examples/nextjs-quickstart/package.json +24 -0
- package/examples/nextjs-quickstart/src/app/api/video/route.ts +22 -0
- package/examples/nextjs-quickstart/src/app/layout.tsx +5 -0
- package/examples/nextjs-quickstart/src/app/page.tsx +31 -0
- package/examples/nextjs-quickstart/tsconfig.json +26 -0
- package/package.json +167 -0
- package/registry/items/backgrounds.json +52 -0
- package/registry/items/barChart.json +237 -0
- package/registry/items/beforeAfter.json +180 -0
- package/registry/items/bigNumber.json +225 -0
- package/registry/items/brandMessage.json +158 -0
- package/registry/items/cardList.json +206 -0
- package/registry/items/chatMessenger.json +151 -0
- package/registry/items/chatWhatsapp.json +151 -0
- package/registry/items/codeEditor.json +184 -0
- package/registry/items/confetti.json +107 -0
- package/registry/items/ctaLogo.json +160 -0
- package/registry/items/ctaMedia.json +193 -0
- package/registry/items/emojiBurst.json +101 -0
- package/registry/items/incomingCall.json +170 -0
- package/registry/items/media.json +179 -0
- package/registry/items/milestone.json +205 -0
- package/registry/items/motion.json +45 -0
- package/registry/items/notification.json +182 -0
- package/registry/items/phoneMockup.json +254 -0
- package/registry/items/problemSolution.json +186 -0
- package/registry/items/progressRing.json +199 -0
- package/registry/items/promptInput.json +148 -0
- package/registry/items/reaction.json +170 -0
- package/registry/items/reviewStack.json +224 -0
- package/registry/items/steps.json +207 -0
- package/registry/items/template-context.json +31 -0
- package/registry/items/terminal.json +212 -0
- package/registry/items/testimonial.json +164 -0
- package/registry/items/theme.json +47 -0
- package/registry/items/tripleStats.json +228 -0
- package/registry/items/tweet.json +203 -0
- package/registry/items/typography.json +47 -0
- package/registry/items/webMockup.json +273 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseVideoEvent
|
|
3
|
+
} from "./chunk-ERY4S6QV.js";
|
|
4
|
+
import {
|
|
5
|
+
VIDEO_SCHEMA_VERSION
|
|
6
|
+
} from "./chunk-EE6PK6XC.js";
|
|
7
|
+
|
|
8
|
+
// src/protocol/stable-json.ts
|
|
9
|
+
function stableJson(value) {
|
|
10
|
+
if (value == null || typeof value !== "object") return JSON.stringify(value);
|
|
11
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
12
|
+
const entries = Object.entries(value).filter(([, child]) => child !== void 0).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
13
|
+
return `{${entries.map(([key, child]) => `${JSON.stringify(key)}:${stableJson(child)}`).join(",")}}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/protocol/checksum.ts
|
|
17
|
+
function checksumVideo(config) {
|
|
18
|
+
const input = stableJson(config);
|
|
19
|
+
let hash = 2166136261;
|
|
20
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
21
|
+
hash ^= input.charCodeAt(index);
|
|
22
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
23
|
+
}
|
|
24
|
+
return `fnv1a32:${hash.toString(16).padStart(8, "0")}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/protocol/state.ts
|
|
28
|
+
function createVideoState() {
|
|
29
|
+
return {
|
|
30
|
+
status: "idle",
|
|
31
|
+
lastSequence: -1,
|
|
32
|
+
sceneRevisions: {},
|
|
33
|
+
appliedEvents: {},
|
|
34
|
+
extensions: [],
|
|
35
|
+
errors: [],
|
|
36
|
+
warnings: []
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function applyVideoEvent(state, untrustedEvent, options = {}) {
|
|
40
|
+
const event = parseVideoEvent(untrustedEvent);
|
|
41
|
+
const signature = stableJson(event);
|
|
42
|
+
const priorSignature = state.appliedEvents[event.eventId];
|
|
43
|
+
if (priorSignature) {
|
|
44
|
+
if (priorSignature !== signature) throw new Error(`Event ${event.eventId} has a conflicting replay`);
|
|
45
|
+
return state;
|
|
46
|
+
}
|
|
47
|
+
if (event.sequence !== state.lastSequence + 1) {
|
|
48
|
+
throw new Error(`Video response expected sequence ${state.lastSequence + 1}, received ${event.sequence}`);
|
|
49
|
+
}
|
|
50
|
+
if (state.runId && event.runId !== state.runId) {
|
|
51
|
+
throw new Error(`Video response run changed from ${state.runId} to ${event.runId}`);
|
|
52
|
+
}
|
|
53
|
+
if (["complete", "error", "aborted"].includes(state.status)) {
|
|
54
|
+
throw new Error(`Cannot apply ${event.type} after ${state.status}`);
|
|
55
|
+
}
|
|
56
|
+
const base = {
|
|
57
|
+
...state,
|
|
58
|
+
runId: event.runId,
|
|
59
|
+
lastSequence: event.sequence,
|
|
60
|
+
lastEventId: event.eventId,
|
|
61
|
+
appliedEvents: { ...state.appliedEvents, [event.eventId]: signature }
|
|
62
|
+
};
|
|
63
|
+
if (event.type === "response.start") {
|
|
64
|
+
if (state.status !== "idle" || event.sequence !== 0) {
|
|
65
|
+
throw new Error("response.start must be the first event");
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
...base,
|
|
69
|
+
status: "streaming",
|
|
70
|
+
requestId: event.data.requestId,
|
|
71
|
+
capabilities: event.data.capabilities,
|
|
72
|
+
config: {
|
|
73
|
+
schemaVersion: VIDEO_SCHEMA_VERSION,
|
|
74
|
+
orientation: event.data.format.orientation,
|
|
75
|
+
scenes: [],
|
|
76
|
+
style: event.data.style,
|
|
77
|
+
...event.data.meta ? { meta: event.data.meta } : {}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (state.status === "idle" || !state.config) throw new Error("response.start is required first");
|
|
82
|
+
if (event.type === "audio.set") {
|
|
83
|
+
if (state.config.scenes.length > 0) throw new Error("audio.set must arrive before the first scene");
|
|
84
|
+
if (state.config.audio) throw new Error("audio.set can only be applied once");
|
|
85
|
+
return { ...base, config: { ...state.config, audio: event.data.audio } };
|
|
86
|
+
}
|
|
87
|
+
if (event.type === "scene.add") {
|
|
88
|
+
if (state.capabilities?.templates != null && !state.capabilities.templates.includes(event.data.scene.templateId)) {
|
|
89
|
+
throw new Error(`Scene template ${event.data.scene.templateId} was not negotiated`);
|
|
90
|
+
}
|
|
91
|
+
if (event.data.position !== state.config.scenes.length) {
|
|
92
|
+
throw new Error(`scene.add position must be ${state.config.scenes.length}`);
|
|
93
|
+
}
|
|
94
|
+
if (event.data.revision !== 0) throw new Error("A new scene must start at revision 0");
|
|
95
|
+
if (state.sceneRevisions[event.data.scene.id] != null) {
|
|
96
|
+
throw new Error(`Scene ${event.data.scene.id} already exists`);
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
...base,
|
|
100
|
+
config: { ...state.config, scenes: [...state.config.scenes, event.data.scene] },
|
|
101
|
+
sceneRevisions: { ...state.sceneRevisions, [event.data.scene.id]: 0 }
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (event.type === "scene.patch" || event.type === "asset.patch") {
|
|
105
|
+
const sceneId = event.data.sceneId;
|
|
106
|
+
const currentRevision = state.sceneRevisions[sceneId];
|
|
107
|
+
if (currentRevision == null) throw new Error(`Scene ${sceneId} does not exist`);
|
|
108
|
+
if (options.mutableSceneIds && !options.mutableSceneIds.has(sceneId)) {
|
|
109
|
+
throw new Error(`Cannot patch scene ${sceneId} after it has already played`);
|
|
110
|
+
}
|
|
111
|
+
if (event.data.revision !== currentRevision + 1) {
|
|
112
|
+
throw new Error(`Scene ${sceneId} expected revision ${currentRevision + 1}`);
|
|
113
|
+
}
|
|
114
|
+
const sceneIndex = state.config.scenes.findIndex((item) => item.id === sceneId);
|
|
115
|
+
const currentScene = state.config.scenes[sceneIndex];
|
|
116
|
+
const nextScene = event.type === "scene.patch" ? {
|
|
117
|
+
...currentScene,
|
|
118
|
+
...event.data.patch,
|
|
119
|
+
variables: event.data.patch.variables ? { ...currentScene.variables, ...event.data.patch.variables } : currentScene.variables,
|
|
120
|
+
timing: event.data.patch.timing ? { ...currentScene.timing, ...event.data.patch.timing } : currentScene.timing
|
|
121
|
+
} : {
|
|
122
|
+
...currentScene,
|
|
123
|
+
variables: { ...currentScene.variables, ...event.data.variables }
|
|
124
|
+
};
|
|
125
|
+
const scenes = [...state.config.scenes];
|
|
126
|
+
scenes[sceneIndex] = nextScene;
|
|
127
|
+
return {
|
|
128
|
+
...base,
|
|
129
|
+
config: { ...state.config, scenes },
|
|
130
|
+
sceneRevisions: { ...state.sceneRevisions, [sceneId]: event.data.revision }
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (event.type === "response.complete") {
|
|
134
|
+
const expectedChecksum = checksumVideo(event.data.snapshot);
|
|
135
|
+
if (event.data.checksum !== expectedChecksum) {
|
|
136
|
+
throw new Error(`response.complete checksum mismatch: expected ${expectedChecksum}`);
|
|
137
|
+
}
|
|
138
|
+
if (stableJson(event.data.snapshot) !== stableJson(state.config)) {
|
|
139
|
+
throw new Error("response.complete snapshot does not match the reduced event stream");
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
...base,
|
|
143
|
+
status: "complete",
|
|
144
|
+
config: event.data.snapshot,
|
|
145
|
+
finishReason: event.data.finishReason,
|
|
146
|
+
checksum: event.data.checksum
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (event.type === "response.error") {
|
|
150
|
+
if (event.data.snapshot && stableJson(event.data.snapshot) !== stableJson(state.config)) {
|
|
151
|
+
throw new Error("response.error snapshot does not match the reduced event stream");
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
...base,
|
|
155
|
+
status: event.data.terminal ? "error" : "streaming",
|
|
156
|
+
...event.data.snapshot ? { config: event.data.snapshot } : {},
|
|
157
|
+
errors: [...state.errors, event.data.error]
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (event.type === "response.warning") {
|
|
161
|
+
return { ...base, warnings: [...state.warnings, event.data.warning] };
|
|
162
|
+
}
|
|
163
|
+
if (event.type === "response.abort") {
|
|
164
|
+
if (event.data.snapshot && stableJson(event.data.snapshot) !== stableJson(state.config)) {
|
|
165
|
+
throw new Error("response.abort snapshot does not match the reduced event stream");
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
...base,
|
|
169
|
+
status: "aborted",
|
|
170
|
+
...event.data.snapshot ? { config: event.data.snapshot } : {},
|
|
171
|
+
abortReason: event.data.reason
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (event.type.startsWith("data.")) {
|
|
175
|
+
if (!state.capabilities?.extensions?.includes(event.type)) {
|
|
176
|
+
throw new Error(`Extension ${event.type} was not negotiated`);
|
|
177
|
+
}
|
|
178
|
+
return { ...base, extensions: [...state.extensions, event] };
|
|
179
|
+
}
|
|
180
|
+
throw new Error(`Reducer does not yet support ${event.type}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export {
|
|
184
|
+
checksumVideo,
|
|
185
|
+
createVideoState,
|
|
186
|
+
applyVideoEvent
|
|
187
|
+
};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import {
|
|
2
|
+
interpolate
|
|
3
|
+
} from "./chunk-XTOXFMLM.js";
|
|
4
|
+
import {
|
|
5
|
+
darken,
|
|
6
|
+
resolveTokens
|
|
7
|
+
} from "./chunk-FNAEQ7QO.js";
|
|
8
|
+
|
|
9
|
+
// src/visual-system/theme/colors.ts
|
|
10
|
+
function withOpacity(hex, opacity) {
|
|
11
|
+
const match = hex.match(/^#([0-9a-f]{3,8})$/i);
|
|
12
|
+
if (!match) return hex;
|
|
13
|
+
const h = match[1];
|
|
14
|
+
let r;
|
|
15
|
+
let g;
|
|
16
|
+
let b;
|
|
17
|
+
if (h.length === 3) {
|
|
18
|
+
r = parseInt(h[0] + h[0], 16);
|
|
19
|
+
g = parseInt(h[1] + h[1], 16);
|
|
20
|
+
b = parseInt(h[2] + h[2], 16);
|
|
21
|
+
} else if (h.length >= 6) {
|
|
22
|
+
r = parseInt(h.slice(0, 2), 16);
|
|
23
|
+
g = parseInt(h.slice(2, 4), 16);
|
|
24
|
+
b = parseInt(h.slice(4, 6), 16);
|
|
25
|
+
} else {
|
|
26
|
+
return hex;
|
|
27
|
+
}
|
|
28
|
+
return `rgba(${r},${g},${b},${opacity})`;
|
|
29
|
+
}
|
|
30
|
+
function orderDarkToLight(a, b) {
|
|
31
|
+
const luminance = (hex) => {
|
|
32
|
+
const match = hex.match(/^#([0-9a-f]{6})$/i);
|
|
33
|
+
if (!match) return 0;
|
|
34
|
+
const h = match[1];
|
|
35
|
+
return 0.299 * parseInt(h.slice(0, 2), 16) + 0.587 * parseInt(h.slice(2, 4), 16) + 0.114 * parseInt(h.slice(4, 6), 16);
|
|
36
|
+
};
|
|
37
|
+
return luminance(a) <= luminance(b) ? [a, b] : [b, a];
|
|
38
|
+
}
|
|
39
|
+
function relativeLuminance(hex) {
|
|
40
|
+
const match = hex.match(/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i);
|
|
41
|
+
if (!match) return void 0;
|
|
42
|
+
const value = match[1].length === 3 ? match[1].split("").map((character) => character + character).join("") : match[1].slice(0, 6);
|
|
43
|
+
const channel = (offset) => {
|
|
44
|
+
const encoded = Number.parseInt(value.slice(offset, offset + 2), 16) / 255;
|
|
45
|
+
return encoded <= 0.04045 ? encoded / 12.92 : ((encoded + 0.055) / 1.055) ** 2.4;
|
|
46
|
+
};
|
|
47
|
+
return 0.2126 * channel(0) + 0.7152 * channel(2) + 0.0722 * channel(4);
|
|
48
|
+
}
|
|
49
|
+
function contrastRatio(first, second) {
|
|
50
|
+
const firstLuminance = relativeLuminance(first);
|
|
51
|
+
const secondLuminance = relativeLuminance(second);
|
|
52
|
+
if (firstLuminance == null || secondLuminance == null) return 0;
|
|
53
|
+
return (Math.max(firstLuminance, secondLuminance) + 0.05) / (Math.min(firstLuminance, secondLuminance) + 0.05);
|
|
54
|
+
}
|
|
55
|
+
function accessibleTextColor(background, preferred) {
|
|
56
|
+
if (preferred && contrastRatio(background, preferred) >= 4.5) return preferred;
|
|
57
|
+
return contrastRatio(background, "#FFFFFF") >= contrastRatio(background, "#000000") ? "#FFFFFF" : "#000000";
|
|
58
|
+
}
|
|
59
|
+
function autoTextColor(background) {
|
|
60
|
+
return accessibleTextColor(background);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/visual-system/scene-templates/color-utils.ts
|
|
64
|
+
import React from "react";
|
|
65
|
+
var TOP_TEXT_AREA_RATIO = 0.32;
|
|
66
|
+
function gradientBackground({
|
|
67
|
+
colorA,
|
|
68
|
+
colorB,
|
|
69
|
+
solidBg,
|
|
70
|
+
progress,
|
|
71
|
+
sceneDuration,
|
|
72
|
+
seed,
|
|
73
|
+
family
|
|
74
|
+
}) {
|
|
75
|
+
if (solidBg) return solidBg;
|
|
76
|
+
const normalizedProgress = Math.max(0, Math.min(1, progress));
|
|
77
|
+
const atBoundary = normalizedProgress === 0 || normalizedProgress === 1;
|
|
78
|
+
const easedLoop = normalizedProgress * normalizedProgress * (3 - 2 * normalizedProgress);
|
|
79
|
+
const seedEnvelope = atBoundary ? 0 : Math.sin(normalizedProgress * Math.PI) ** 2;
|
|
80
|
+
void sceneDuration;
|
|
81
|
+
const seededRandom = (s) => {
|
|
82
|
+
const x = Math.sin(s * 12.9898 + s * 78.233) * 43758.5453;
|
|
83
|
+
return x - Math.floor(x);
|
|
84
|
+
};
|
|
85
|
+
const phase = seededRandom(seed) * Math.PI * 2 * seedEnvelope;
|
|
86
|
+
const t = (atBoundary ? 0 : easedLoop * Math.PI * 2) + phase;
|
|
87
|
+
const [topColor, bottomColor] = orderDarkToLight(colorA, colorB);
|
|
88
|
+
if (family === "wash") {
|
|
89
|
+
const shift = Math.sin(t) * 6;
|
|
90
|
+
return `linear-gradient(${175 + shift}deg, ${topColor} 0%, ${bottomColor} 100%)`;
|
|
91
|
+
}
|
|
92
|
+
if (family === "spotlight") {
|
|
93
|
+
const x = 50 + Math.sin(t) * 8;
|
|
94
|
+
const y = 28 + Math.cos(t + 1) * 5;
|
|
95
|
+
return `
|
|
96
|
+
radial-gradient(ellipse 70% 55% at ${x}% ${y}%, ${colorA}66 0%, ${colorA}1a 45%, transparent 72%),
|
|
97
|
+
linear-gradient(180deg, ${darken(topColor, 0.55)} 0%, ${darken(bottomColor, 0.7)} 100%)
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
const x1 = 25 + Math.sin(t) * 25;
|
|
101
|
+
const y1 = 30 + Math.cos(t + 1) * 25;
|
|
102
|
+
const x2 = 75 + Math.sin(t + 2) * 25;
|
|
103
|
+
const y2 = 70 + Math.cos(t + 3.5) * 25;
|
|
104
|
+
return `
|
|
105
|
+
radial-gradient(ellipse 60% 60% at ${x1}% ${y1}%, ${colorA}4d 0%, transparent 65%),
|
|
106
|
+
radial-gradient(ellipse 60% 60% at ${x2}% ${y2}%, ${colorB}4d 0%, transparent 65%),
|
|
107
|
+
linear-gradient(180deg, ${topColor} 0%, ${bottomColor} 100%)
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
var BrandGradientOverlay = ({ style: globalStyle, progress, sceneDuration, seed }) => {
|
|
111
|
+
const tokens = resolveTokens(globalStyle);
|
|
112
|
+
const [first, second] = tokens.background.type === "gradient" ? tokens.background.colors : [tokens.background.color, tokens.background.color];
|
|
113
|
+
const solidBg = tokens.background.type === "solid" ? tokens.background.color : void 0;
|
|
114
|
+
return React.createElement("div", {
|
|
115
|
+
style: {
|
|
116
|
+
position: "absolute",
|
|
117
|
+
inset: 0,
|
|
118
|
+
background: gradientBackground({ colorA: first, colorB: second, solidBg, progress, sceneDuration, seed, family: tokens.preset.background }),
|
|
119
|
+
pointerEvents: "none"
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// src/visual-system/scene-templates/background-effect.ts
|
|
125
|
+
var DIRECTIONS = ["right", "left", "up", "down"];
|
|
126
|
+
function getKenBurnsTransform(progress, direction) {
|
|
127
|
+
const scale = interpolate(progress, [0, 1], [1.05, 1.15]);
|
|
128
|
+
const mainAxis = direction === "right" || direction === "left";
|
|
129
|
+
const sign = direction === "right" || direction === "down" ? 1 : -1;
|
|
130
|
+
const primary = interpolate(progress, [0, 1], [-2 * sign, 2 * sign]);
|
|
131
|
+
const secondary = interpolate(progress, [0, 1], [-0.5, 0.5]);
|
|
132
|
+
const x = mainAxis ? primary : secondary;
|
|
133
|
+
const y = mainAxis ? secondary : primary;
|
|
134
|
+
return {
|
|
135
|
+
transform: `scale(${scale}) translate(${x}%, ${y}%)`,
|
|
136
|
+
transformOrigin: "center"
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function getDriftTransform(progress, direction) {
|
|
140
|
+
const horizontal = direction === "right" || direction === "left";
|
|
141
|
+
const sign = direction === "right" || direction === "down" ? 1 : -1;
|
|
142
|
+
const offset = interpolate(progress, [0, 1], [-2 * sign, 2 * sign]);
|
|
143
|
+
return {
|
|
144
|
+
transform: horizontal ? `scale(1.1) translateX(${offset}%)` : `scale(1.1) translateY(${offset}%)`,
|
|
145
|
+
transformOrigin: "center"
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function getBackgroundTransform(effect, progress, beatIntensity = 0, sceneIndex = 0) {
|
|
149
|
+
const dir = DIRECTIONS[sceneIndex % DIRECTIONS.length];
|
|
150
|
+
const resolved = effect ?? "slow-zoom-in";
|
|
151
|
+
switch (resolved) {
|
|
152
|
+
case "slow-zoom-in":
|
|
153
|
+
return {
|
|
154
|
+
transform: `scale(${interpolate(progress, [0, 1], [1, 1.12])})`,
|
|
155
|
+
transformOrigin: "center"
|
|
156
|
+
};
|
|
157
|
+
case "slow-zoom-out":
|
|
158
|
+
return {
|
|
159
|
+
transform: `scale(${interpolate(progress, [0, 1], [1.12, 1])})`,
|
|
160
|
+
transformOrigin: "center"
|
|
161
|
+
};
|
|
162
|
+
case "ken-burns":
|
|
163
|
+
return getKenBurnsTransform(progress, dir);
|
|
164
|
+
case "drift":
|
|
165
|
+
return getDriftTransform(progress, dir);
|
|
166
|
+
case "pulse": {
|
|
167
|
+
const base = 1 + beatIntensity * 0.04;
|
|
168
|
+
const breathe = 1 + Math.sin(progress * Math.PI * 2) * 0.02;
|
|
169
|
+
return {
|
|
170
|
+
transform: `scale(${base * breathe})`,
|
|
171
|
+
transformOrigin: "center"
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
case "breathe": {
|
|
175
|
+
const s = 1 + Math.sin(progress * Math.PI) * 0.03;
|
|
176
|
+
const y = Math.sin(progress * Math.PI) * 0.5;
|
|
177
|
+
return {
|
|
178
|
+
transform: `scale(${s}) translateY(${y}%)`,
|
|
179
|
+
transformOrigin: "center"
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
case "slow-tilt": {
|
|
183
|
+
const angle = interpolate(progress, [0, 1], [0, 2.5]);
|
|
184
|
+
return {
|
|
185
|
+
transform: `scale(1.08) rotate(${angle}deg)`,
|
|
186
|
+
transformOrigin: "center"
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
case "camera-shake": {
|
|
190
|
+
const t = progress * 40;
|
|
191
|
+
const sx = Math.sin(t * 2.3) * 0.4 + Math.sin(t * 5.7) * 0.2;
|
|
192
|
+
const sy = Math.cos(t * 3.1) * 0.3 + Math.cos(t * 4.9) * 0.15;
|
|
193
|
+
const sr = Math.sin(t * 1.7) * 0.3;
|
|
194
|
+
return {
|
|
195
|
+
transform: `scale(1.05) translate(${sx}%, ${sy}%) rotate(${sr}deg)`,
|
|
196
|
+
transformOrigin: "center"
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
case "static":
|
|
200
|
+
default:
|
|
201
|
+
return {
|
|
202
|
+
transform: "none",
|
|
203
|
+
transformOrigin: "center"
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export {
|
|
209
|
+
withOpacity,
|
|
210
|
+
accessibleTextColor,
|
|
211
|
+
autoTextColor,
|
|
212
|
+
TOP_TEXT_AREA_RATIO,
|
|
213
|
+
gradientBackground,
|
|
214
|
+
BrandGradientOverlay,
|
|
215
|
+
getBackgroundTransform
|
|
216
|
+
};
|