@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,385 @@
|
|
|
1
|
+
import {
|
|
2
|
+
VIDEO_PROTOCOL_VERSION,
|
|
3
|
+
VIDEO_SCHEMA_VERSION
|
|
4
|
+
} from "./chunk-EE6PK6XC.js";
|
|
5
|
+
import {
|
|
6
|
+
validateVideoBrand
|
|
7
|
+
} from "./chunk-XGRA2MUV.js";
|
|
8
|
+
|
|
9
|
+
// src/protocol/warnings.ts
|
|
10
|
+
var VIDEO_WARNING_CATEGORIES = {
|
|
11
|
+
scene_duration_adjusted: "readability",
|
|
12
|
+
scene_omitted_unreadable: "readability",
|
|
13
|
+
scene_omitted_for_closer: "readability",
|
|
14
|
+
scene_patch_rejected_readability: "readability",
|
|
15
|
+
chart_scale_imbalance: "readability",
|
|
16
|
+
provider_warning: "provider",
|
|
17
|
+
provider_diagnostics_unavailable: "provider"
|
|
18
|
+
};
|
|
19
|
+
var MAX_PUBLIC_DIAGNOSTIC_LENGTH = 160;
|
|
20
|
+
var SECRET_ASSIGNMENT = /\b(?:api[_-]?key|authorization|secret|token|password)\s*[:=]\s*\S+/gi;
|
|
21
|
+
var BEARER_TOKEN = /\bBearer\s+\S+/gi;
|
|
22
|
+
var URL = /https?:\/\/\S+/gi;
|
|
23
|
+
function safePublicDiagnostic(value, fallback) {
|
|
24
|
+
if (typeof value !== "string") return fallback;
|
|
25
|
+
const withoutControls = Array.from(value, (character) => {
|
|
26
|
+
const code = character.codePointAt(0) ?? 0;
|
|
27
|
+
return code < 32 || code === 127 ? " " : character;
|
|
28
|
+
}).join("");
|
|
29
|
+
const redacted = withoutControls.replace(SECRET_ASSIGNMENT, "[redacted]").replace(BEARER_TOKEN, "[redacted]").replace(URL, "[redacted url]").replace(/\s+/g, " ").trim();
|
|
30
|
+
if (!redacted) return fallback;
|
|
31
|
+
if (redacted.length <= MAX_PUBLIC_DIAGNOSTIC_LENGTH) return redacted;
|
|
32
|
+
return `${redacted.slice(0, MAX_PUBLIC_DIAGNOSTIC_LENGTH - 1).trimEnd()}\u2026`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/protocol/validation.ts
|
|
36
|
+
var CORE_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
37
|
+
"response.start",
|
|
38
|
+
"audio.set",
|
|
39
|
+
"scene.add",
|
|
40
|
+
"scene.patch",
|
|
41
|
+
"asset.patch",
|
|
42
|
+
"response.warning",
|
|
43
|
+
"response.complete",
|
|
44
|
+
"response.error",
|
|
45
|
+
"response.abort"
|
|
46
|
+
]);
|
|
47
|
+
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
48
|
+
function record(value, path) {
|
|
49
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
50
|
+
throw new Error(`${path} must be an object`);
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
function allowedKeys(value, allowed, path) {
|
|
55
|
+
const permitted = new Set(allowed);
|
|
56
|
+
for (const key of Object.keys(value)) {
|
|
57
|
+
if (!permitted.has(key)) throw new Error(`${path} contains unsupported field ${key}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function string(value, path) {
|
|
61
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(`${path} must be a non-empty string`);
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
function publicDiagnostic(value, path) {
|
|
65
|
+
const result = string(value, path);
|
|
66
|
+
if (result.length > MAX_PUBLIC_DIAGNOSTIC_LENGTH) {
|
|
67
|
+
throw new Error(`${path} must be at most ${MAX_PUBLIC_DIAGNOSTIC_LENGTH} characters`);
|
|
68
|
+
}
|
|
69
|
+
if (Array.from(result).some((character) => {
|
|
70
|
+
const code = character.codePointAt(0) ?? 0;
|
|
71
|
+
return code < 32 || code === 127;
|
|
72
|
+
})) throw new Error(`${path} contains control characters`);
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
function finite(value, path) {
|
|
76
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${path} must be finite`);
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
function nonNegativeInteger(value, path) {
|
|
80
|
+
if (!Number.isInteger(value) || Number(value) < 0) {
|
|
81
|
+
throw new Error(`${path} must be a non-negative integer`);
|
|
82
|
+
}
|
|
83
|
+
return Number(value);
|
|
84
|
+
}
|
|
85
|
+
function jsonValue(value, path, seen = /* @__PURE__ */ new Set()) {
|
|
86
|
+
if (value == null || typeof value === "string" || typeof value === "boolean") return;
|
|
87
|
+
if (typeof value === "number") {
|
|
88
|
+
finite(value, path);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (typeof value !== "object" || seen.has(value)) throw new Error(`${path} must be JSON-serializable`);
|
|
92
|
+
seen.add(value);
|
|
93
|
+
if (Array.isArray(value)) {
|
|
94
|
+
value.forEach((item, index) => jsonValue(item, `${path}[${index}]`, seen));
|
|
95
|
+
} else {
|
|
96
|
+
for (const [key, child] of Object.entries(value)) {
|
|
97
|
+
if (DANGEROUS_KEYS.has(key)) throw new Error(`${path} contains unsupported field ${key}`);
|
|
98
|
+
jsonValue(child, `${path}.${key}`, seen);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
seen.delete(value);
|
|
102
|
+
}
|
|
103
|
+
function timing(value, path) {
|
|
104
|
+
const result = record(value, path);
|
|
105
|
+
allowedKeys(result, [
|
|
106
|
+
"beatStart",
|
|
107
|
+
"beatEnd",
|
|
108
|
+
"startTime",
|
|
109
|
+
"endTime",
|
|
110
|
+
"fixedDuration",
|
|
111
|
+
"durationWeight"
|
|
112
|
+
], path);
|
|
113
|
+
for (const [key, child] of Object.entries(result)) finite(child, `${path}.${key}`);
|
|
114
|
+
if (result.fixedDuration != null && Number(result.fixedDuration) <= 0) {
|
|
115
|
+
throw new Error(`${path}.fixedDuration must be positive`);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
function scene(value, path) {
|
|
120
|
+
const result = record(value, path);
|
|
121
|
+
allowedKeys(result, [
|
|
122
|
+
"id",
|
|
123
|
+
"templateId",
|
|
124
|
+
"variables",
|
|
125
|
+
"textArchetype",
|
|
126
|
+
"backgroundEffect",
|
|
127
|
+
"timing"
|
|
128
|
+
], path);
|
|
129
|
+
string(result.id, `${path}.id`);
|
|
130
|
+
string(result.templateId, `${path}.templateId`);
|
|
131
|
+
record(result.variables, `${path}.variables`);
|
|
132
|
+
jsonValue(result.variables, `${path}.variables`);
|
|
133
|
+
timing(result.timing, `${path}.timing`);
|
|
134
|
+
if (result.textArchetype != null) string(result.textArchetype, `${path}.textArchetype`);
|
|
135
|
+
if (result.backgroundEffect != null) string(result.backgroundEffect, `${path}.backgroundEffect`);
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
function scenePatch(value, path) {
|
|
139
|
+
const patch = record(value, path);
|
|
140
|
+
allowedKeys(patch, [
|
|
141
|
+
"variables",
|
|
142
|
+
"textArchetype",
|
|
143
|
+
"backgroundEffect",
|
|
144
|
+
"timing"
|
|
145
|
+
], path);
|
|
146
|
+
if (patch.variables != null) {
|
|
147
|
+
record(patch.variables, `${path}.variables`);
|
|
148
|
+
jsonValue(patch.variables, `${path}.variables`);
|
|
149
|
+
}
|
|
150
|
+
if (patch.timing != null) timing(patch.timing, `${path}.timing`);
|
|
151
|
+
if (patch.textArchetype != null) string(patch.textArchetype, `${path}.textArchetype`);
|
|
152
|
+
if (patch.backgroundEffect != null) string(patch.backgroundEffect, `${path}.backgroundEffect`);
|
|
153
|
+
}
|
|
154
|
+
function capabilities(value, path) {
|
|
155
|
+
const result = record(value, path);
|
|
156
|
+
allowedKeys(result, ["templates", "extensions"], path);
|
|
157
|
+
for (const key of ["templates", "extensions"]) {
|
|
158
|
+
const entries = result[key];
|
|
159
|
+
if (entries == null) continue;
|
|
160
|
+
if (!Array.isArray(entries)) throw new Error(`${path}.${key} must be an array`);
|
|
161
|
+
entries.forEach((entry, index) => {
|
|
162
|
+
const value2 = string(entry, `${path}.${key}[${index}]`);
|
|
163
|
+
if (key === "extensions" && (!value2.startsWith("data.") || value2 === "data.")) {
|
|
164
|
+
throw new Error(`${path}.${key}[${index}] must use the data.* namespace`);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
function style(value, path) {
|
|
171
|
+
const result = record(value, path);
|
|
172
|
+
allowedKeys(result, [
|
|
173
|
+
"brand",
|
|
174
|
+
"preset",
|
|
175
|
+
"defaultBackgroundEffect",
|
|
176
|
+
"defaultTextArchetype",
|
|
177
|
+
"defaultTransition",
|
|
178
|
+
"density",
|
|
179
|
+
"motion"
|
|
180
|
+
], path);
|
|
181
|
+
validateVideoBrand(result.brand, `${path}.brand`);
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
function audio(value, path) {
|
|
185
|
+
const result = record(value, path);
|
|
186
|
+
allowedKeys(result, [
|
|
187
|
+
"trackId",
|
|
188
|
+
"audioUrl",
|
|
189
|
+
"sourceDuration",
|
|
190
|
+
"duration",
|
|
191
|
+
"beatDetection",
|
|
192
|
+
"beatMarkers",
|
|
193
|
+
"volume",
|
|
194
|
+
"fadeOutMs"
|
|
195
|
+
], path);
|
|
196
|
+
string(result.trackId, `${path}.trackId`);
|
|
197
|
+
string(result.audioUrl, `${path}.audioUrl`);
|
|
198
|
+
if (finite(result.duration, `${path}.duration`) <= 0) throw new Error(`${path}.duration must be positive`);
|
|
199
|
+
const detection = record(result.beatDetection, `${path}.beatDetection`);
|
|
200
|
+
allowedKeys(detection, ["sensitivity", "targetBeats", "minInterval"], `${path}.beatDetection`);
|
|
201
|
+
const sensitivity = finite(detection.sensitivity, `${path}.beatDetection.sensitivity`);
|
|
202
|
+
if (sensitivity < 0 || sensitivity > 1) throw new Error(`${path}.beatDetection.sensitivity must be 0\u20131`);
|
|
203
|
+
if (detection.targetBeats != null && (!Number.isInteger(detection.targetBeats) || Number(detection.targetBeats) <= 0)) {
|
|
204
|
+
throw new Error(`${path}.beatDetection.targetBeats must be a positive integer`);
|
|
205
|
+
}
|
|
206
|
+
if (detection.minInterval != null && finite(detection.minInterval, `${path}.beatDetection.minInterval`) <= 0) {
|
|
207
|
+
throw new Error(`${path}.beatDetection.minInterval must be positive`);
|
|
208
|
+
}
|
|
209
|
+
if (!Array.isArray(result.beatMarkers)) throw new Error(`${path}.beatMarkers must be an array`);
|
|
210
|
+
result.beatMarkers.forEach((value2, index) => {
|
|
211
|
+
const marker = record(value2, `${path}.beatMarkers[${index}]`);
|
|
212
|
+
allowedKeys(marker, ["time", "manual", "energy"], `${path}.beatMarkers[${index}]`);
|
|
213
|
+
if (finite(marker.time, `${path}.beatMarkers[${index}].time`) < 0) {
|
|
214
|
+
throw new Error(`${path}.beatMarkers[${index}].time must be non-negative`);
|
|
215
|
+
}
|
|
216
|
+
if (marker.manual != null && typeof marker.manual !== "boolean") {
|
|
217
|
+
throw new Error(`${path}.beatMarkers[${index}].manual must be boolean`);
|
|
218
|
+
}
|
|
219
|
+
if (marker.energy != null && !["high", "medium", "low"].includes(String(marker.energy))) {
|
|
220
|
+
throw new Error(`${path}.beatMarkers[${index}].energy is unsupported`);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
if (result.sourceDuration != null && finite(result.sourceDuration, `${path}.sourceDuration`) <= 0) {
|
|
224
|
+
throw new Error(`${path}.sourceDuration must be positive`);
|
|
225
|
+
}
|
|
226
|
+
if (result.volume != null) {
|
|
227
|
+
const volume = finite(result.volume, `${path}.volume`);
|
|
228
|
+
if (volume < 0 || volume > 1) throw new Error(`${path}.volume must be 0\u20131`);
|
|
229
|
+
}
|
|
230
|
+
if (result.fadeOutMs != null && finite(result.fadeOutMs, `${path}.fadeOutMs`) < 0) {
|
|
231
|
+
throw new Error(`${path}.fadeOutMs must be non-negative`);
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
function config(value, path) {
|
|
236
|
+
const result = record(value, path);
|
|
237
|
+
allowedKeys(result, ["schemaVersion", "orientation", "audio", "scenes", "style", "meta"], path);
|
|
238
|
+
if (result.schemaVersion !== VIDEO_SCHEMA_VERSION) {
|
|
239
|
+
throw new Error(`${path}.schemaVersion must be ${VIDEO_SCHEMA_VERSION}`);
|
|
240
|
+
}
|
|
241
|
+
if (!Array.isArray(result.scenes)) throw new Error(`${path}.scenes must be an array`);
|
|
242
|
+
result.scenes.forEach((item, index) => scene(item, `${path}.scenes[${index}]`));
|
|
243
|
+
style(result.style, `${path}.style`);
|
|
244
|
+
if (result.orientation != null && result.orientation !== "portrait" && result.orientation !== "landscape") {
|
|
245
|
+
throw new Error(`${path}.orientation is unsupported`);
|
|
246
|
+
}
|
|
247
|
+
if (result.audio != null) audio(result.audio, `${path}.audio`);
|
|
248
|
+
if (result.meta != null) jsonValue(record(result.meta, `${path}.meta`), `${path}.meta`);
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
function validateCoreData(type, value) {
|
|
252
|
+
const data = record(value, "event.data");
|
|
253
|
+
if (type === "response.start") {
|
|
254
|
+
allowedKeys(data, ["requestId", "format", "style", "meta", "capabilities"], "event.data");
|
|
255
|
+
string(data.requestId, "event.data.requestId");
|
|
256
|
+
const format = record(data.format, "event.data.format");
|
|
257
|
+
allowedKeys(format, ["orientation"], "event.data.format");
|
|
258
|
+
if (format.orientation !== "portrait" && format.orientation !== "landscape") {
|
|
259
|
+
throw new Error("event.data.format.orientation is unsupported");
|
|
260
|
+
}
|
|
261
|
+
style(data.style, "event.data.style");
|
|
262
|
+
if (data.meta != null) jsonValue(record(data.meta, "event.data.meta"), "event.data.meta");
|
|
263
|
+
if (data.capabilities != null) capabilities(data.capabilities, "event.data.capabilities");
|
|
264
|
+
} else if (type === "audio.set") {
|
|
265
|
+
allowedKeys(data, ["audio"], "event.data");
|
|
266
|
+
audio(data.audio, "event.data.audio");
|
|
267
|
+
} else if (type === "scene.add") {
|
|
268
|
+
allowedKeys(data, ["scene", "position", "revision"], "event.data");
|
|
269
|
+
scene(data.scene, "event.data.scene");
|
|
270
|
+
nonNegativeInteger(data.position, "event.data.position");
|
|
271
|
+
nonNegativeInteger(data.revision, "event.data.revision");
|
|
272
|
+
} else if (type === "scene.patch") {
|
|
273
|
+
allowedKeys(data, ["sceneId", "revision", "patch"], "event.data");
|
|
274
|
+
string(data.sceneId, "event.data.sceneId");
|
|
275
|
+
nonNegativeInteger(data.revision, "event.data.revision");
|
|
276
|
+
scenePatch(data.patch, "event.data.patch");
|
|
277
|
+
} else if (type === "asset.patch") {
|
|
278
|
+
allowedKeys(data, ["sceneId", "revision", "variables"], "event.data");
|
|
279
|
+
string(data.sceneId, "event.data.sceneId");
|
|
280
|
+
nonNegativeInteger(data.revision, "event.data.revision");
|
|
281
|
+
record(data.variables, "event.data.variables");
|
|
282
|
+
jsonValue(data.variables, "event.data.variables");
|
|
283
|
+
} else if (type === "response.complete") {
|
|
284
|
+
allowedKeys(data, ["finishReason", "snapshot", "checksum"], "event.data");
|
|
285
|
+
if (!["stop", "length", "content-filter", "error", "other"].includes(String(data.finishReason))) {
|
|
286
|
+
throw new Error("event.data.finishReason is unsupported");
|
|
287
|
+
}
|
|
288
|
+
config(data.snapshot, "event.data.snapshot");
|
|
289
|
+
string(data.checksum, "event.data.checksum");
|
|
290
|
+
} else if (type === "response.warning") {
|
|
291
|
+
allowedKeys(data, ["warning"], "event.data");
|
|
292
|
+
const warning = record(data.warning, "event.data.warning");
|
|
293
|
+
allowedKeys(warning, ["code", "category", "message", "sceneId", "recoverable"], "event.data.warning");
|
|
294
|
+
if (!(String(warning.code) in VIDEO_WARNING_CATEGORIES)) {
|
|
295
|
+
throw new Error("event.data.warning.code is unsupported");
|
|
296
|
+
}
|
|
297
|
+
const expectedCategory = VIDEO_WARNING_CATEGORIES[warning.code];
|
|
298
|
+
if (warning.category !== expectedCategory) {
|
|
299
|
+
throw new Error(`event.data.warning.category must be ${expectedCategory} for ${String(warning.code)}`);
|
|
300
|
+
}
|
|
301
|
+
publicDiagnostic(warning.message, "event.data.warning.message");
|
|
302
|
+
if (warning.sceneId != null) {
|
|
303
|
+
const sceneId = string(warning.sceneId, "event.data.warning.sceneId");
|
|
304
|
+
if (sceneId.length > 128) throw new Error("event.data.warning.sceneId must be at most 128 characters");
|
|
305
|
+
}
|
|
306
|
+
if (typeof warning.recoverable !== "boolean") throw new Error("event.data.warning.recoverable must be boolean");
|
|
307
|
+
} else if (type === "response.error") {
|
|
308
|
+
allowedKeys(data, ["error", "terminal", "snapshot"], "event.data");
|
|
309
|
+
const error = record(data.error, "event.data.error");
|
|
310
|
+
allowedKeys(error, ["code", "message", "recoverable"], "event.data.error");
|
|
311
|
+
if (!["generation_failed", "invalid_generated_part"].includes(String(error.code))) {
|
|
312
|
+
throw new Error("event.data.error.code is unsupported");
|
|
313
|
+
}
|
|
314
|
+
publicDiagnostic(error.message, "event.data.error.message");
|
|
315
|
+
if (typeof error.recoverable !== "boolean") throw new Error("event.data.error.recoverable must be boolean");
|
|
316
|
+
if (typeof data.terminal !== "boolean") throw new Error("event.data.terminal must be boolean");
|
|
317
|
+
if (data.snapshot != null) config(data.snapshot, "event.data.snapshot");
|
|
318
|
+
} else if (type === "response.abort") {
|
|
319
|
+
allowedKeys(data, ["reason", "snapshot"], "event.data");
|
|
320
|
+
publicDiagnostic(data.reason, "event.data.reason");
|
|
321
|
+
if (data.snapshot != null) config(data.snapshot, "event.data.snapshot");
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function parseVideoEvent(value) {
|
|
325
|
+
const event = record(value, "event");
|
|
326
|
+
allowedKeys(event, ["protocolVersion", "runId", "sequence", "eventId", "type", "data"], "event");
|
|
327
|
+
if (event.protocolVersion !== VIDEO_PROTOCOL_VERSION) {
|
|
328
|
+
throw new Error(`event.protocolVersion must be ${VIDEO_PROTOCOL_VERSION}`);
|
|
329
|
+
}
|
|
330
|
+
const runId = string(event.runId, "event.runId");
|
|
331
|
+
const sequence = nonNegativeInteger(event.sequence, "event.sequence");
|
|
332
|
+
const eventId = string(event.eventId, "event.eventId");
|
|
333
|
+
if (eventId !== `${runId}:${sequence}`) {
|
|
334
|
+
throw new Error("event.eventId must equal `${runId}:${sequence}`");
|
|
335
|
+
}
|
|
336
|
+
const type = string(event.type, "event.type");
|
|
337
|
+
if (CORE_EVENT_TYPES.has(type)) {
|
|
338
|
+
validateCoreData(type, event.data);
|
|
339
|
+
} else if (type.startsWith("data.") && type.length > "data.".length) {
|
|
340
|
+
jsonValue(event.data, "event.data");
|
|
341
|
+
} else {
|
|
342
|
+
throw new Error(`event.type ${type} is unsupported`);
|
|
343
|
+
}
|
|
344
|
+
return event;
|
|
345
|
+
}
|
|
346
|
+
function parseVideoPlanPart(value) {
|
|
347
|
+
const part = record(value, "plan part");
|
|
348
|
+
const type = string(part.type, "plan part.type");
|
|
349
|
+
if (type === "scene.add") {
|
|
350
|
+
allowedKeys(part, ["type", "scene"], "plan part");
|
|
351
|
+
scene(part.scene, "plan part.scene");
|
|
352
|
+
} else if (type === "scene.patch") {
|
|
353
|
+
allowedKeys(part, ["type", "sceneId", "patch"], "plan part");
|
|
354
|
+
string(part.sceneId, "plan part.sceneId");
|
|
355
|
+
scenePatch(part.patch, "plan part.patch");
|
|
356
|
+
} else if (type === "asset.patch") {
|
|
357
|
+
allowedKeys(part, ["type", "sceneId", "variables"], "plan part");
|
|
358
|
+
string(part.sceneId, "plan part.sceneId");
|
|
359
|
+
record(part.variables, "plan part.variables");
|
|
360
|
+
jsonValue(part.variables, "plan part.variables");
|
|
361
|
+
} else if (type === "plan.complete") {
|
|
362
|
+
allowedKeys(part, ["type", "finishReason"], "plan part");
|
|
363
|
+
if (part.finishReason != null && !["stop", "length", "content-filter", "other"].includes(String(part.finishReason))) {
|
|
364
|
+
throw new Error("plan part.finishReason is unsupported");
|
|
365
|
+
}
|
|
366
|
+
} else if (type === "plan.error") {
|
|
367
|
+
allowedKeys(part, ["type", "error"], "plan part");
|
|
368
|
+
const error = record(part.error, "plan part.error");
|
|
369
|
+
allowedKeys(error, ["message", "code", "recoverable"], "plan part.error");
|
|
370
|
+
string(error.message, "plan part.error.message");
|
|
371
|
+
if (error.code != null) string(error.code, "plan part.error.code");
|
|
372
|
+
if (error.recoverable != null && typeof error.recoverable !== "boolean") {
|
|
373
|
+
throw new Error("plan part.error.recoverable must be boolean");
|
|
374
|
+
}
|
|
375
|
+
} else {
|
|
376
|
+
throw new Error(`plan part.type ${type} is unsupported`);
|
|
377
|
+
}
|
|
378
|
+
return part;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export {
|
|
382
|
+
safePublicDiagnostic,
|
|
383
|
+
parseVideoEvent,
|
|
384
|
+
parseVideoPlanPart
|
|
385
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// src/server/pacing.ts
|
|
2
|
+
var DEFAULT_SCENE_DURATION_SEC = 5;
|
|
3
|
+
var MINIMUM_CLOSER_RESERVE_SEC = 3;
|
|
4
|
+
var PACING_PLANNER_RULES = [
|
|
5
|
+
"Use each template's preferredDuration when no explicit duration is needed, and never request less than minDuration.",
|
|
6
|
+
"Track the cumulative duration budget before emitting every scene; the runtime omits scenes that cannot remain readable.",
|
|
7
|
+
`Reserve at least ${MINIMUM_CLOSER_RESERVE_SEC} seconds for a final ask when the catalog includes jobs:[ask], and emit that ask last.`
|
|
8
|
+
];
|
|
9
|
+
var WORDS_PER_SECOND = 4.5;
|
|
10
|
+
var CHARACTERS_PER_SECOND = 12.5;
|
|
11
|
+
var ITEM_BASE_SECONDS = 2;
|
|
12
|
+
var SECONDS_PER_ITEM = 1;
|
|
13
|
+
var DURATION_QUANTUM_SEC = 0.25;
|
|
14
|
+
function roundDuration(value) {
|
|
15
|
+
return Math.ceil(value / DURATION_QUANTUM_SEC) * DURATION_QUANTUM_SEC;
|
|
16
|
+
}
|
|
17
|
+
function mergeSchemaDefaults(variables, metadata) {
|
|
18
|
+
const merged = { ...variables };
|
|
19
|
+
for (const [name, property] of Object.entries(metadata?.schema?.properties ?? {})) {
|
|
20
|
+
if (merged[name] === void 0 && property.default !== void 0) merged[name] = property.default;
|
|
21
|
+
}
|
|
22
|
+
return merged;
|
|
23
|
+
}
|
|
24
|
+
function fieldValue(value, path) {
|
|
25
|
+
return path.split(".").reduce((current, segment) => {
|
|
26
|
+
if (!current || typeof current !== "object" || Array.isArray(current)) return void 0;
|
|
27
|
+
return current[segment];
|
|
28
|
+
}, value);
|
|
29
|
+
}
|
|
30
|
+
function stringsIn(value) {
|
|
31
|
+
if (typeof value === "string") return value.trim() ? [value.trim()] : [];
|
|
32
|
+
if (Array.isArray(value)) return value.flatMap(stringsIn);
|
|
33
|
+
if (value && typeof value === "object") return Object.values(value).flatMap(stringsIn);
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
function itemCount(value) {
|
|
37
|
+
if (Array.isArray(value)) return value.length;
|
|
38
|
+
if (value && typeof value === "object") return Object.keys(value).length;
|
|
39
|
+
return value == null || value === "" ? 0 : 1;
|
|
40
|
+
}
|
|
41
|
+
function getReadableSceneDuration(scene, metadata) {
|
|
42
|
+
const minimum = Math.max(1e-3, metadata?.minDuration ?? 1);
|
|
43
|
+
const timing = metadata?.timing;
|
|
44
|
+
if (!timing) return roundDuration(minimum);
|
|
45
|
+
const variables = mergeSchemaDefaults(scene.variables, metadata);
|
|
46
|
+
const values = timing.contentFields.map((field) => fieldValue(variables, field));
|
|
47
|
+
let estimate;
|
|
48
|
+
if (timing.contentUnit === "items") {
|
|
49
|
+
estimate = ITEM_BASE_SECONDS + values.reduce(
|
|
50
|
+
(total, value) => total + itemCount(value),
|
|
51
|
+
0
|
|
52
|
+
) * SECONDS_PER_ITEM;
|
|
53
|
+
} else {
|
|
54
|
+
const content = values.flatMap(stringsIn);
|
|
55
|
+
const units = timing.contentUnit === "words" ? content.reduce((total, value) => total + (value.match(/\S+/gu)?.length ?? 0), 0) : content.reduce((total, value) => total + [...value].length, 0);
|
|
56
|
+
estimate = 1 + units / (timing.contentUnit === "words" ? WORDS_PER_SECOND : CHARACTERS_PER_SECOND);
|
|
57
|
+
}
|
|
58
|
+
return roundDuration(Math.max(minimum, estimate));
|
|
59
|
+
}
|
|
60
|
+
function getCloserReserve(templateIds, getTemplatePacing) {
|
|
61
|
+
if (!templateIds || !getTemplatePacing) return 0;
|
|
62
|
+
const askDurations = templateIds.map((id) => getTemplatePacing(id)).filter((metadata) => metadata?.jobs?.includes("ask")).map((metadata) => Math.max(
|
|
63
|
+
metadata?.minDuration ?? MINIMUM_CLOSER_RESERVE_SEC,
|
|
64
|
+
metadata?.preferredDuration ?? 0
|
|
65
|
+
));
|
|
66
|
+
return askDurations.length === 0 ? 0 : Math.max(MINIMUM_CLOSER_RESERVE_SEC, ...askDurations);
|
|
67
|
+
}
|
|
68
|
+
function requestedStart(scene, audio, priorEnd) {
|
|
69
|
+
const beatStart = scene.timing.beatStart == null ? void 0 : audio?.beatMarkers[scene.timing.beatStart]?.time;
|
|
70
|
+
return scene.timing.startTime ?? beatStart ?? priorEnd;
|
|
71
|
+
}
|
|
72
|
+
function requestedDuration(scene, metadata, audio, priorEnd) {
|
|
73
|
+
const beatEnd = scene.timing.beatEnd == null ? void 0 : audio?.beatMarkers[scene.timing.beatEnd]?.time;
|
|
74
|
+
const statedStart = requestedStart(scene, audio, priorEnd);
|
|
75
|
+
const statedEnd = scene.timing.endTime ?? beatEnd;
|
|
76
|
+
if (statedEnd != null) return Math.max(1e-3, statedEnd - statedStart);
|
|
77
|
+
return Math.max(1e-3, scene.timing.fixedDuration ?? metadata?.preferredDuration ?? DEFAULT_SCENE_DURATION_SEC);
|
|
78
|
+
}
|
|
79
|
+
function warning(code, message, sceneId) {
|
|
80
|
+
return { code, category: "readability", message, sceneId, recoverable: true };
|
|
81
|
+
}
|
|
82
|
+
function paceScene(scene, options) {
|
|
83
|
+
const metadata = options.getTemplatePacing?.(scene.templateId);
|
|
84
|
+
const isAsk = metadata?.jobs?.includes("ask") === true;
|
|
85
|
+
const priorEnd = options.previousScenes.at(-1)?.timing.endTime ?? 0;
|
|
86
|
+
const ceiling = isAsk ? options.maxDurationSec : Math.max(0, options.maxDurationSec - options.closerReserveSec);
|
|
87
|
+
const remaining = Math.max(0, ceiling - priorEnd);
|
|
88
|
+
const readableMinimum = getReadableSceneDuration(scene, metadata);
|
|
89
|
+
if (remaining < readableMinimum) {
|
|
90
|
+
const reservedForCloser = !isAsk && options.closerReserveSec > 0 && options.maxDurationSec - priorEnd >= readableMinimum;
|
|
91
|
+
return {
|
|
92
|
+
warnings: [warning(
|
|
93
|
+
reservedForCloser ? "scene_omitted_for_closer" : "scene_omitted_unreadable",
|
|
94
|
+
reservedForCloser ? "Scene was omitted to preserve room for a readable final action." : "Scene was omitted because it could not fit at a readable duration.",
|
|
95
|
+
scene.id
|
|
96
|
+
)]
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const requested = requestedDuration(scene, metadata, options.audio, priorEnd);
|
|
100
|
+
const duration = Math.min(Math.max(requested, readableMinimum), remaining);
|
|
101
|
+
const adjusted = Math.abs(duration - requested) > 1e-6 || Math.abs(requestedStart(scene, options.audio, priorEnd) - priorEnd) > 1e-6;
|
|
102
|
+
return {
|
|
103
|
+
scene: {
|
|
104
|
+
...scene,
|
|
105
|
+
timing: {
|
|
106
|
+
...scene.timing,
|
|
107
|
+
startTime: priorEnd,
|
|
108
|
+
endTime: priorEnd + duration,
|
|
109
|
+
...scene.timing.fixedDuration != null ? { fixedDuration: duration } : {}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
warnings: adjusted ? [warning(
|
|
113
|
+
"scene_duration_adjusted",
|
|
114
|
+
"Scene duration was adjusted to preserve readable pacing.",
|
|
115
|
+
scene.id
|
|
116
|
+
)] : []
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export {
|
|
121
|
+
PACING_PLANNER_RULES,
|
|
122
|
+
getReadableSceneDuration,
|
|
123
|
+
getCloserReserve,
|
|
124
|
+
paceScene
|
|
125
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// src/visual-system/scene-templates/tokens.ts
|
|
2
|
+
var TOKEN_DEFAULTS = {
|
|
3
|
+
/** Primary brand colour — CTA, highlights. */
|
|
4
|
+
primary: "#00E5A0",
|
|
5
|
+
/** Deepest background surface. */
|
|
6
|
+
surface: "#0A0A14",
|
|
7
|
+
/** Elevated card / panel surface. */
|
|
8
|
+
surfaceElevated: "#14152A",
|
|
9
|
+
/** Primary text color. */
|
|
10
|
+
foreground: "#FFFFFF",
|
|
11
|
+
/** Muted text — labels, footers, supporting copy. */
|
|
12
|
+
muted: "#A7A6B0",
|
|
13
|
+
/** Primary sans font family (first name of the stack). */
|
|
14
|
+
font: "Inter",
|
|
15
|
+
/** Script accent font family for handwritten callouts. */
|
|
16
|
+
scriptFont: "Caveat"
|
|
17
|
+
};
|
|
18
|
+
function fontStack(styleFont) {
|
|
19
|
+
return `${(styleFont || "").split(",")[0].trim() || TOKEN_DEFAULTS.font}, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif`;
|
|
20
|
+
}
|
|
21
|
+
var STYLE_PRESETS = {
|
|
22
|
+
bold: {
|
|
23
|
+
id: "bold",
|
|
24
|
+
useWhen: "The default. Drifting two-color brand mesh, heavy tight headlines at the top. Launches, hype, product moments \u2014 the loudest of the three.",
|
|
25
|
+
background: "mesh",
|
|
26
|
+
titlePlacement: "title-top",
|
|
27
|
+
type: { weightDelta: 0, trackingDeltaEm: 0, sizeScale: 1 }
|
|
28
|
+
},
|
|
29
|
+
editorial: {
|
|
30
|
+
id: "editorial",
|
|
31
|
+
useWhen: "Calm vertical wash, lighter and wider-tracked headlines, centered. Reviews, thoughtful updates, premium or B2B brands \u2014 when the copy should feel considered rather than shouted.",
|
|
32
|
+
background: "wash",
|
|
33
|
+
titlePlacement: "title-center",
|
|
34
|
+
type: { weightDelta: -200, trackingDeltaEm: 0.01, sizeScale: 1.08 }
|
|
35
|
+
},
|
|
36
|
+
stark: {
|
|
37
|
+
id: "stark",
|
|
38
|
+
useWhen: "Single hard spotlight on near-black, uppercase and tightly tracked. Dev tools, technical claims, high-contrast statements \u2014 maximum weight on very few words.",
|
|
39
|
+
background: "spotlight",
|
|
40
|
+
titlePlacement: "title-top",
|
|
41
|
+
type: { weightDelta: 100, trackingDeltaEm: -0.01, sizeScale: 1, transform: "uppercase" }
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var DEFAULT_PRESET_ID = "bold";
|
|
45
|
+
var PRESET_IDS = Object.keys(STYLE_PRESETS);
|
|
46
|
+
function resolvePreset(id) {
|
|
47
|
+
return id && STYLE_PRESETS[id] || STYLE_PRESETS[DEFAULT_PRESET_ID];
|
|
48
|
+
}
|
|
49
|
+
var DENSITY_SCALES = {
|
|
50
|
+
airy: {
|
|
51
|
+
id: "airy",
|
|
52
|
+
useWhen: "Smaller headlines held further off the frame edges. Premium, considered, editorial \u2014 when the copy should have room to breathe.",
|
|
53
|
+
typeScale: 0.92,
|
|
54
|
+
safeZoneScale: 1.3
|
|
55
|
+
},
|
|
56
|
+
normal: {
|
|
57
|
+
id: "normal",
|
|
58
|
+
useWhen: "The default. No change to type size or frame padding.",
|
|
59
|
+
typeScale: 1,
|
|
60
|
+
safeZoneScale: 1
|
|
61
|
+
},
|
|
62
|
+
packed: {
|
|
63
|
+
id: "packed",
|
|
64
|
+
useWhen: "Bigger headlines pushed closer to the edges. Dense, urgent, information-heavy \u2014 when the frame should feel full.",
|
|
65
|
+
typeScale: 1.08,
|
|
66
|
+
safeZoneScale: 0.8
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var MOTION_SCALES = {
|
|
70
|
+
calm: {
|
|
71
|
+
id: "calm",
|
|
72
|
+
useWhen: "Slower entrances and exits \u2014 text eases in rather than arriving. Founder stories, sober data, anything reflective.",
|
|
73
|
+
phaseScale: 1.4
|
|
74
|
+
},
|
|
75
|
+
normal: {
|
|
76
|
+
id: "normal",
|
|
77
|
+
useWhen: "The default. Archetype timings as authored.",
|
|
78
|
+
phaseScale: 1
|
|
79
|
+
},
|
|
80
|
+
punchy: {
|
|
81
|
+
id: "punchy",
|
|
82
|
+
useWhen: "Snappier entrances and exits \u2014 text lands fast and clears fast. Hype, launches, hot takes.",
|
|
83
|
+
phaseScale: 0.7
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var DEFAULT_DENSITY_ID = "normal";
|
|
87
|
+
var DEFAULT_MOTION_ID = "normal";
|
|
88
|
+
var DENSITY_IDS = Object.keys(DENSITY_SCALES);
|
|
89
|
+
var MOTION_IDS = Object.keys(MOTION_SCALES);
|
|
90
|
+
function resolveDensity(id) {
|
|
91
|
+
return id && DENSITY_SCALES[id] || DENSITY_SCALES[DEFAULT_DENSITY_ID];
|
|
92
|
+
}
|
|
93
|
+
function resolveMotion(id) {
|
|
94
|
+
return id && MOTION_SCALES[id] || MOTION_SCALES[DEFAULT_MOTION_ID];
|
|
95
|
+
}
|
|
96
|
+
function resolveTokens(style) {
|
|
97
|
+
const brand = style.brand;
|
|
98
|
+
const preset = resolvePreset(style.preset);
|
|
99
|
+
const density = resolveDensity(style.density);
|
|
100
|
+
const motion = resolveMotion(style.motion);
|
|
101
|
+
const composedPreset = {
|
|
102
|
+
...preset,
|
|
103
|
+
type: {
|
|
104
|
+
...preset.type,
|
|
105
|
+
sizeScale: preset.type.sizeScale * density.typeScale,
|
|
106
|
+
phaseScale: motion.phaseScale
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
primary: brand.colors.primary,
|
|
111
|
+
secondary: brand.colors.secondary,
|
|
112
|
+
background: brand.background,
|
|
113
|
+
surface: brand.colors.surface,
|
|
114
|
+
surfaceElevated: brand.colors.surfaceElevated,
|
|
115
|
+
foreground: brand.colors.foreground,
|
|
116
|
+
muted: brand.colors.muted,
|
|
117
|
+
font: fontStack(brand.font),
|
|
118
|
+
scriptFont: brand.scriptFont,
|
|
119
|
+
logoUrl: brand.logoUrl,
|
|
120
|
+
name: brand.name,
|
|
121
|
+
preset: composedPreset,
|
|
122
|
+
density,
|
|
123
|
+
motion
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function darken(hex, factor) {
|
|
127
|
+
if (!hex.startsWith("#") || hex.length !== 4 && hex.length !== 7) {
|
|
128
|
+
return hex;
|
|
129
|
+
}
|
|
130
|
+
const full = hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
|
|
131
|
+
const ch = (i) => Math.max(0, Math.round(parseInt(full.slice(i, i + 2), 16) * (1 - factor))).toString(16).padStart(2, "0");
|
|
132
|
+
return `#${ch(1)}${ch(3)}${ch(5)}`;
|
|
133
|
+
}
|
|
134
|
+
function lighten(hex, factor) {
|
|
135
|
+
if (!hex.startsWith("#") || hex.length !== 4 && hex.length !== 7) {
|
|
136
|
+
return hex;
|
|
137
|
+
}
|
|
138
|
+
const full = hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
|
|
139
|
+
const r = parseInt(full.slice(1, 3), 16);
|
|
140
|
+
const g = parseInt(full.slice(3, 5), 16);
|
|
141
|
+
const b = parseInt(full.slice(5, 7), 16);
|
|
142
|
+
const lr = Math.min(255, Math.round(r + (255 - r) * factor));
|
|
143
|
+
const lg = Math.min(255, Math.round(g + (255 - g) * factor));
|
|
144
|
+
const lb = Math.min(255, Math.round(b + (255 - b) * factor));
|
|
145
|
+
return `#${lr.toString(16).padStart(2, "0")}${lg.toString(16).padStart(2, "0")}${lb.toString(16).padStart(2, "0")}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export {
|
|
149
|
+
TOKEN_DEFAULTS,
|
|
150
|
+
fontStack,
|
|
151
|
+
resolveDensity,
|
|
152
|
+
resolveTokens,
|
|
153
|
+
darken,
|
|
154
|
+
lighten
|
|
155
|
+
};
|