@bendyline/squisq 2.4.2 → 2.4.3

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.
@@ -0,0 +1,750 @@
1
+ import {
2
+ defaultPageStyle
3
+ } from "./chunk-GAZKTT4R.js";
4
+ import {
5
+ FONT_FALLBACKS,
6
+ THEME_SCHEMA_VERSION,
7
+ assertTheme,
8
+ createTheme,
9
+ deriveScale,
10
+ isHex,
11
+ oklchDarken,
12
+ oklchLighten,
13
+ oklchSetChroma,
14
+ pickContrastingText,
15
+ relativeLuminance,
16
+ resolveFontFamily,
17
+ withAlpha
18
+ } from "./chunk-SBAX4ZPO.js";
19
+
20
+ // src/schemas/Doc.ts
21
+ function calculateDuration(audio) {
22
+ return audio.segments.reduce((sum, seg) => sum + seg.duration, 0);
23
+ }
24
+ function getSegmentAtTime(audio, time) {
25
+ let elapsed = 0;
26
+ for (let i = 0; i < audio.segments.length; i++) {
27
+ elapsed += audio.segments[i].duration;
28
+ if (time < elapsed) return i;
29
+ }
30
+ return audio.segments.length - 1;
31
+ }
32
+ function getBlockAtTime(blocks, time) {
33
+ for (let i = blocks.length - 1; i >= 0; i--) {
34
+ const block = blocks[i];
35
+ if (time >= block.startTime && time < block.startTime + block.duration) {
36
+ return block;
37
+ }
38
+ }
39
+ return blocks[0] || null;
40
+ }
41
+ function getCaptionAtTime(captions, time) {
42
+ if (!captions || !captions.phrases.length) return null;
43
+ for (const phrase of captions.phrases) {
44
+ if (time >= phrase.startTime && time < phrase.endTime) {
45
+ return phrase;
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+
51
+ // src/schemas/validateDoc.ts
52
+ var LAYER_TYPES = /* @__PURE__ */ new Set([
53
+ "image",
54
+ "text",
55
+ "shape",
56
+ "path",
57
+ "map",
58
+ "video",
59
+ "table",
60
+ "tree",
61
+ "mermaid"
62
+ ]);
63
+ function validateDocSchema(value) {
64
+ const issues = [];
65
+ if (!isRecord(value)) {
66
+ add(issues, "$", "must be an object", value);
67
+ return issues;
68
+ }
69
+ requiredString(value, "articleId", "articleId", issues);
70
+ requiredFiniteNumber(value, "duration", "duration", issues, { min: 0 });
71
+ const blocks = requiredArray(value, "blocks", "blocks", issues);
72
+ if (blocks) validateBlocks(blocks, "blocks", issues);
73
+ const audio = requiredRecord(value, "audio", "audio", issues);
74
+ if (audio) {
75
+ const segments = requiredArray(audio, "segments", "audio.segments", issues);
76
+ if (segments) validateAudioSegments(segments, issues);
77
+ }
78
+ optionalString(value, "themeId", "themeId", issues);
79
+ optionalRecord(value, "frontmatter", "frontmatter", issues);
80
+ optionalRecord(value, "persistentLayers", "persistentLayers", issues);
81
+ optionalRecord(value, "meta", "meta", issues);
82
+ optionalArray(value, "customTemplates", "customTemplates", issues);
83
+ optionalArray(value, "customThemes", "customThemes", issues);
84
+ if (value.startBlock !== void 0) validateStartBlock(value.startBlock, issues);
85
+ if (value.captions !== void 0) validateCaptions(value.captions, issues);
86
+ if (value.diagnostics !== void 0) validateDiagnostics(value.diagnostics, issues);
87
+ if (value.documentMedia !== void 0) {
88
+ validateMediaArray(value.documentMedia, "documentMedia", issues);
89
+ }
90
+ return issues;
91
+ }
92
+ function assertDocSchema(value) {
93
+ const issues = validateDocSchema(value);
94
+ if (issues.length === 0) return;
95
+ const detail = issues.map((issue) => `${issue.path} ${issue.message}`).join("; ");
96
+ throw new TypeError(`Invalid squisq Doc: ${detail}`);
97
+ }
98
+ function validateBlocks(values, path, issues) {
99
+ for (let index = 0; index < values.length; index += 1) {
100
+ const blockPath = `${path}[${index}]`;
101
+ const block = values[index];
102
+ if (!isRecord(block)) {
103
+ add(issues, blockPath, "must be an object", block);
104
+ continue;
105
+ }
106
+ requiredString(block, "id", `${blockPath}.id`, issues);
107
+ requiredFiniteNumber(block, "startTime", `${blockPath}.startTime`, issues, { min: 0 });
108
+ requiredFiniteNumber(block, "duration", `${blockPath}.duration`, issues, { min: 0 });
109
+ requiredInteger(block, "audioSegment", `${blockPath}.audioSegment`, issues, 0);
110
+ optionalString(block, "sourceBlockId", `${blockPath}.sourceBlockId`, issues);
111
+ optionalStringArray(block, "sourceBlockIds", `${blockPath}.sourceBlockIds`, issues);
112
+ optionalFiniteNumber(block, "sourceCharOffset", `${blockPath}.sourceCharOffset`, issues, {
113
+ min: 0
114
+ });
115
+ optionalString(block, "template", `${blockPath}.template`, issues);
116
+ optionalString(block, "title", `${blockPath}.title`, issues);
117
+ optionalBoolean(block, "autoTemplate", `${blockPath}.autoTemplate`, issues);
118
+ optionalFiniteNumber(block, "x", `${blockPath}.x`, issues);
119
+ optionalFiniteNumber(block, "y", `${blockPath}.y`, issues);
120
+ optionalStringArray(block, "classes", `${blockPath}.classes`, issues);
121
+ optionalStringRecord(block, "metadata", `${blockPath}.metadata`, issues);
122
+ optionalStringRecord(block, "templateOverrides", `${blockPath}.templateOverrides`, issues);
123
+ optionalRecord(block, "templateData", `${blockPath}.templateData`, issues);
124
+ optionalArray(block, "contents", `${blockPath}.contents`, issues);
125
+ if (block.layers !== void 0) validateLayers(block.layers, `${blockPath}.layers`, issues);
126
+ if (block.children !== void 0) {
127
+ if (Array.isArray(block.children)) {
128
+ validateBlocks(block.children, `${blockPath}.children`, issues);
129
+ } else {
130
+ add(issues, `${blockPath}.children`, "must be an array", block.children);
131
+ }
132
+ }
133
+ if (block.connectsTo !== void 0) {
134
+ validateConnections(block.connectsTo, `${blockPath}.connectsTo`, issues);
135
+ }
136
+ if (block.media !== void 0) {
137
+ validateMediaArray(block.media, `${blockPath}.media`, issues);
138
+ }
139
+ }
140
+ }
141
+ function validateLayers(value, path, issues) {
142
+ if (!Array.isArray(value)) {
143
+ add(issues, path, "must be an array", value);
144
+ return;
145
+ }
146
+ for (let index = 0; index < value.length; index += 1) {
147
+ const layerPath = `${path}[${index}]`;
148
+ const layer = value[index];
149
+ if (!isRecord(layer)) {
150
+ add(issues, layerPath, "must be an object", layer);
151
+ continue;
152
+ }
153
+ requiredString(layer, "id", `${layerPath}.id`, issues);
154
+ const type = requiredString(layer, "type", `${layerPath}.type`, issues);
155
+ if (type && !LAYER_TYPES.has(type)) {
156
+ issues.push({
157
+ path: `${layerPath}.type`,
158
+ message: `must be a known layer type (got ${quote(type)})`
159
+ });
160
+ }
161
+ validatePosition(layer.position, `${layerPath}.position`, issues);
162
+ const content = requiredRecord(layer, "content", `${layerPath}.content`, issues);
163
+ if (content && type) validateLayerContent(type, content, `${layerPath}.content`, issues);
164
+ if (layer.animation !== void 0 && !isRecord(layer.animation)) {
165
+ add(issues, `${layerPath}.animation`, "must be an object", layer.animation);
166
+ }
167
+ }
168
+ }
169
+ function validateLayerContent(type, content, path, issues) {
170
+ switch (type) {
171
+ case "image":
172
+ requiredString(content, "src", `${path}.src`, issues);
173
+ requiredString(content, "alt", `${path}.alt`, issues);
174
+ break;
175
+ case "text":
176
+ requiredString(content, "text", `${path}.text`, issues);
177
+ requiredRecord(content, "style", `${path}.style`, issues);
178
+ break;
179
+ case "shape":
180
+ requiredString(content, "shape", `${path}.shape`, issues);
181
+ break;
182
+ case "path":
183
+ requiredString(content, "d", `${path}.d`, issues);
184
+ break;
185
+ case "map": {
186
+ const center = requiredRecord(content, "center", `${path}.center`, issues);
187
+ if (center) {
188
+ requiredFiniteNumber(center, "lat", `${path}.center.lat`, issues);
189
+ requiredFiniteNumber(center, "lng", `${path}.center.lng`, issues);
190
+ }
191
+ requiredFiniteNumber(content, "zoom", `${path}.zoom`, issues);
192
+ requiredString(content, "style", `${path}.style`, issues);
193
+ break;
194
+ }
195
+ case "video":
196
+ requiredString(content, "src", `${path}.src`, issues);
197
+ requiredString(content, "alt", `${path}.alt`, issues);
198
+ requiredFiniteNumber(content, "clipStart", `${path}.clipStart`, issues, { min: 0 });
199
+ requiredFiniteNumber(content, "clipEnd", `${path}.clipEnd`, issues, { min: 0 });
200
+ break;
201
+ case "table":
202
+ requiredStringArray(content, "headers", `${path}.headers`, issues);
203
+ validateStringRows(content.rows, `${path}.rows`, issues);
204
+ requiredRecord(content, "style", `${path}.style`, issues);
205
+ break;
206
+ case "tree":
207
+ requiredArray(content, "items", `${path}.items`, issues);
208
+ requiredRecord(content, "style", `${path}.style`, issues);
209
+ break;
210
+ case "mermaid":
211
+ requiredString(content, "source", `${path}.source`, issues);
212
+ break;
213
+ }
214
+ }
215
+ function validatePosition(value, path, issues) {
216
+ if (!isRecord(value)) {
217
+ add(issues, path, "must be an object", value);
218
+ return;
219
+ }
220
+ requiredPositionValue(value, "x", `${path}.x`, issues);
221
+ requiredPositionValue(value, "y", `${path}.y`, issues);
222
+ optionalPositionValue(value, "width", `${path}.width`, issues);
223
+ optionalPositionValue(value, "height", `${path}.height`, issues);
224
+ }
225
+ function validateAudioSegments(values, issues) {
226
+ for (let index = 0; index < values.length; index += 1) {
227
+ const path = `audio.segments[${index}]`;
228
+ const segment = values[index];
229
+ if (!isRecord(segment)) {
230
+ add(issues, path, "must be an object", segment);
231
+ continue;
232
+ }
233
+ requiredString(segment, "src", `${path}.src`, issues);
234
+ requiredString(segment, "name", `${path}.name`, issues);
235
+ requiredFiniteNumber(segment, "duration", `${path}.duration`, issues, { min: 0 });
236
+ requiredFiniteNumber(segment, "startTime", `${path}.startTime`, issues, { min: 0 });
237
+ }
238
+ }
239
+ function validateConnections(value, path, issues) {
240
+ if (!Array.isArray(value)) {
241
+ add(issues, path, "must be an array", value);
242
+ return;
243
+ }
244
+ for (let index = 0; index < value.length; index += 1) {
245
+ const itemPath = `${path}[${index}]`;
246
+ const connection = value[index];
247
+ if (!isRecord(connection)) {
248
+ add(issues, itemPath, "must be an object", connection);
249
+ continue;
250
+ }
251
+ requiredString(connection, "target", `${itemPath}.target`, issues);
252
+ optionalString(connection, "type", `${itemPath}.type`, issues);
253
+ }
254
+ }
255
+ function validateMediaArray(value, path, issues) {
256
+ if (!Array.isArray(value)) {
257
+ add(issues, path, "must be an array", value);
258
+ return;
259
+ }
260
+ for (let index = 0; index < value.length; index += 1) {
261
+ const clipPath = `${path}[${index}]`;
262
+ const clip = value[index];
263
+ if (!isRecord(clip)) {
264
+ add(issues, clipPath, "must be an object", clip);
265
+ continue;
266
+ }
267
+ requiredString(clip, "id", `${clipPath}.id`, issues);
268
+ requiredString(clip, "src", `${clipPath}.src`, issues);
269
+ requiredString(clip, "kind", `${clipPath}.kind`, issues);
270
+ requiredFiniteNumber(clip, "startAt", `${clipPath}.startAt`, issues, { min: 0 });
271
+ requiredString(clip, "anchor", `${clipPath}.anchor`, issues);
272
+ optionalFiniteNumber(clip, "clipStart", `${clipPath}.clipStart`, issues, { min: 0 });
273
+ optionalFiniteNumber(clip, "clipEnd", `${clipPath}.clipEnd`, issues, { min: 0 });
274
+ }
275
+ }
276
+ function validateStartBlock(value, issues) {
277
+ if (!isRecord(value)) {
278
+ add(issues, "startBlock", "must be an object", value);
279
+ return;
280
+ }
281
+ requiredString(value, "title", "startBlock.title", issues);
282
+ optionalString(value, "heroSrc", "startBlock.heroSrc", issues);
283
+ optionalString(value, "heroAlt", "startBlock.heroAlt", issues);
284
+ optionalString(value, "subtitle", "startBlock.subtitle", issues);
285
+ }
286
+ function validateCaptions(value, issues) {
287
+ if (!isRecord(value)) {
288
+ add(issues, "captions", "must be an object", value);
289
+ return;
290
+ }
291
+ requiredInteger(value, "version", "captions.version", issues, 0);
292
+ const phrases = requiredArray(value, "phrases", "captions.phrases", issues);
293
+ if (!phrases) return;
294
+ for (let index = 0; index < phrases.length; index += 1) {
295
+ const path = `captions.phrases[${index}]`;
296
+ const phrase = phrases[index];
297
+ if (!isRecord(phrase)) {
298
+ add(issues, path, "must be an object", phrase);
299
+ continue;
300
+ }
301
+ requiredString(phrase, "text", `${path}.text`, issues);
302
+ requiredFiniteNumber(phrase, "startTime", `${path}.startTime`, issues, { min: 0 });
303
+ requiredFiniteNumber(phrase, "endTime", `${path}.endTime`, issues, { min: 0 });
304
+ requiredInteger(phrase, "audioSegment", `${path}.audioSegment`, issues, 0);
305
+ }
306
+ }
307
+ function validateDiagnostics(value, issues) {
308
+ if (!Array.isArray(value)) {
309
+ add(issues, "diagnostics", "must be an array", value);
310
+ return;
311
+ }
312
+ for (let index = 0; index < value.length; index += 1) {
313
+ const path = `diagnostics[${index}]`;
314
+ const diagnostic = value[index];
315
+ if (!isRecord(diagnostic)) {
316
+ add(issues, path, "must be an object", diagnostic);
317
+ continue;
318
+ }
319
+ requiredString(diagnostic, "severity", `${path}.severity`, issues);
320
+ requiredString(diagnostic, "code", `${path}.code`, issues);
321
+ requiredString(diagnostic, "message", `${path}.message`, issues);
322
+ }
323
+ }
324
+ function validateStringRows(value, path, issues) {
325
+ if (!Array.isArray(value)) {
326
+ add(issues, path, "must be an array", value);
327
+ return;
328
+ }
329
+ for (let index = 0; index < value.length; index += 1) {
330
+ if (!Array.isArray(value[index]) || value[index].some((cell) => typeof cell !== "string")) {
331
+ add(issues, `${path}[${index}]`, "must be an array of strings", value[index]);
332
+ }
333
+ }
334
+ }
335
+ function isRecord(value) {
336
+ return typeof value === "object" && value !== null && !Array.isArray(value);
337
+ }
338
+ function requiredRecord(object, key, path, issues) {
339
+ const value = object[key];
340
+ if (!isRecord(value)) {
341
+ add(issues, path, "must be an object", value);
342
+ return void 0;
343
+ }
344
+ return value;
345
+ }
346
+ function optionalRecord(object, key, path, issues) {
347
+ if (object[key] !== void 0 && !isRecord(object[key])) {
348
+ add(issues, path, "must be an object", object[key]);
349
+ }
350
+ }
351
+ function requiredArray(object, key, path, issues) {
352
+ const value = object[key];
353
+ if (!Array.isArray(value)) {
354
+ add(issues, path, "must be an array", value);
355
+ return void 0;
356
+ }
357
+ return value;
358
+ }
359
+ function optionalArray(object, key, path, issues) {
360
+ if (object[key] !== void 0 && !Array.isArray(object[key])) {
361
+ add(issues, path, "must be an array", object[key]);
362
+ }
363
+ }
364
+ function requiredString(object, key, path, issues) {
365
+ const value = object[key];
366
+ if (typeof value !== "string") {
367
+ add(issues, path, "must be a string", value);
368
+ return void 0;
369
+ }
370
+ return value;
371
+ }
372
+ function optionalString(object, key, path, issues) {
373
+ if (object[key] !== void 0 && typeof object[key] !== "string") {
374
+ add(issues, path, "must be a string", object[key]);
375
+ }
376
+ }
377
+ function requiredStringArray(object, key, path, issues) {
378
+ const value = object[key];
379
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
380
+ add(issues, path, "must be an array of strings", value);
381
+ }
382
+ }
383
+ function optionalStringArray(object, key, path, issues) {
384
+ if (object[key] !== void 0) requiredStringArray(object, key, path, issues);
385
+ }
386
+ function optionalStringRecord(object, key, path, issues) {
387
+ const value = object[key];
388
+ if (value === void 0) return;
389
+ if (!isRecord(value) || Object.values(value).some((item) => typeof item !== "string")) {
390
+ add(issues, path, "must be an object whose values are strings", value);
391
+ }
392
+ }
393
+ function requiredFiniteNumber(object, key, path, issues, rule = {}) {
394
+ const value = object[key];
395
+ if (typeof value !== "number" || !Number.isFinite(value)) {
396
+ add(issues, path, "must be a finite number", value);
397
+ } else if (rule.min !== void 0 && value < rule.min) {
398
+ issues.push({ path, message: `must be at least ${rule.min} (got ${value})` });
399
+ }
400
+ }
401
+ function optionalFiniteNumber(object, key, path, issues, rule = {}) {
402
+ if (object[key] !== void 0) requiredFiniteNumber(object, key, path, issues, rule);
403
+ }
404
+ function requiredInteger(object, key, path, issues, min) {
405
+ const value = object[key];
406
+ if (!Number.isSafeInteger(value) || value < min) {
407
+ add(issues, path, `must be an integer of at least ${min}`, value);
408
+ }
409
+ }
410
+ function optionalBoolean(object, key, path, issues) {
411
+ if (object[key] !== void 0 && typeof object[key] !== "boolean") {
412
+ add(issues, path, "must be a boolean", object[key]);
413
+ }
414
+ }
415
+ function requiredPositionValue(object, key, path, issues) {
416
+ const value = object[key];
417
+ if ((typeof value !== "number" || !Number.isFinite(value)) && typeof value !== "string") {
418
+ add(issues, path, "must be a finite number or string", value);
419
+ }
420
+ }
421
+ function optionalPositionValue(object, key, path, issues) {
422
+ if (object[key] !== void 0) requiredPositionValue(object, key, path, issues);
423
+ }
424
+ function add(issues, path, message, value) {
425
+ const missing = value === void 0 ? " (field is missing)" : ` (got ${describe(value)})`;
426
+ issues.push({ path, message: `${message}${missing}` });
427
+ }
428
+ function describe(value) {
429
+ if (value === null) return "null";
430
+ if (Array.isArray(value)) return "an array";
431
+ if (typeof value === "string") return quote(value);
432
+ if (typeof value === "number" && Number.isNaN(value)) return "NaN";
433
+ return typeof value;
434
+ }
435
+ function quote(value) {
436
+ return JSON.stringify(value);
437
+ }
438
+
439
+ // src/schemas/pipStyle.ts
440
+ var DEFAULT_SHADOW = "0 0.75em 2em rgba(0, 0, 0, 0.34)";
441
+ var DERIVED_BORDER_WIDTH = "max(1px, 0.12vw)";
442
+ function cssLength(value) {
443
+ return typeof value === "number" ? `${value}px` : value;
444
+ }
445
+ function resolveRadius(theme) {
446
+ const explicit = theme.style.pip?.cornerRadius;
447
+ if (explicit != null) {
448
+ if (typeof explicit === "number") return explicit <= 0 ? "0" : `${explicit}px`;
449
+ return explicit;
450
+ }
451
+ const br = theme.style.borderRadius ?? 0;
452
+ if (br <= 0) return "0";
453
+ return `${Math.min(28, Math.max(6, Math.round(br)))}%`;
454
+ }
455
+ function resolveBorder(theme) {
456
+ const border = theme.style.pip?.border;
457
+ if (border === "none") return "none";
458
+ if (border && typeof border === "object") {
459
+ const width = border.width == null ? DERIVED_BORDER_WIDTH : cssLength(border.width);
460
+ const color = border.color ?? withAlpha(theme.colors.text, 0.35);
461
+ return `${width} solid ${color}`;
462
+ }
463
+ return `${DERIVED_BORDER_WIDTH} solid ${withAlpha(theme.colors.text, 0.35)}`;
464
+ }
465
+ function resolveShadow(theme) {
466
+ const shadow = theme.style.pip?.shadow;
467
+ if (shadow === false || shadow === "none") return "none";
468
+ if (typeof shadow === "string") return shadow;
469
+ return DEFAULT_SHADOW;
470
+ }
471
+ function pipStyleVars(theme) {
472
+ return {
473
+ "--squisq-pip-radius": resolveRadius(theme),
474
+ "--squisq-pip-border": resolveBorder(theme),
475
+ "--squisq-pip-shadow": resolveShadow(theme)
476
+ };
477
+ }
478
+
479
+ // src/schemas/themeCompile.ts
480
+ var STARTER_BODY_FONT = { stackId: "system-sans" };
481
+ var STARTER_TITLE_FONT = { stackId: "system-serif" };
482
+ var STARTER_MONO_FONT = { stackId: "system-mono" };
483
+ var STARTER_COLOR_SCHEMES = {
484
+ blue: { bg: "#1a365d", text: "#63b3ed", accent: "#90cdf4" },
485
+ green: { bg: "#22543d", text: "#9ae6b4", accent: "#68d391" },
486
+ purple: { bg: "#44337a", text: "#d6bcfa", accent: "#b794f4" },
487
+ red: { bg: "#742a2a", text: "#fc8181", accent: "#feb2b2" },
488
+ orange: { bg: "#744210", text: "#fbd38d", accent: "#f6ad55" },
489
+ teal: { bg: "#234e52", text: "#81e6d9", accent: "#4fd1c5" }
490
+ };
491
+ var STARTER_THEME = {
492
+ schemaVersion: THEME_SCHEMA_VERSION,
493
+ id: "custom",
494
+ name: "Custom Theme",
495
+ description: "Customizer starter \u2014 gets overridden by user choices.",
496
+ colors: {
497
+ primary: "#3182ce",
498
+ secondary: "#4a5568",
499
+ background: "#1a202c",
500
+ backgroundLight: "#2d3748",
501
+ text: "#f7fafc",
502
+ textMuted: "#a0aec0",
503
+ highlight: "#4299e1",
504
+ warning: "#fc8181"
505
+ },
506
+ typography: {
507
+ bodyFont: STARTER_BODY_FONT,
508
+ titleFont: STARTER_TITLE_FONT,
509
+ monoFont: STARTER_MONO_FONT,
510
+ titleWeight: "bold"
511
+ },
512
+ style: {
513
+ textShadow: true,
514
+ overlayOpacity: 0.45,
515
+ animationSpeed: 1,
516
+ borderRadius: 6
517
+ },
518
+ renderStyle: {
519
+ name: "standard",
520
+ defaultTextAnimation: "fadeIn",
521
+ defaultImageAnimation: "slowZoom",
522
+ ambientMotion: true,
523
+ defaultTransition: { type: "fade", duration: 0.7 }
524
+ },
525
+ colorSchemes: STARTER_COLOR_SCHEMES
526
+ };
527
+ function deriveColorPalette(seeds, partialColors = {}, opts = {}) {
528
+ const spread = opts.contrast === "high" ? 0.22 : opts.contrast === "subtle" ? 0.08 : 0.15;
529
+ const primary = seeds.primary;
530
+ const secondary = seeds.secondary ?? oklchSetChroma(oklchLighten(primary, 0.05), 0.5);
531
+ const accent = seeds.accent ?? oklchLighten(primary, spread);
532
+ const bgSeed = partialColors.background ?? seeds.background;
533
+ let background;
534
+ if (bgSeed) {
535
+ background = bgSeed;
536
+ } else {
537
+ background = relativeLuminance(primary) > 0.5 ? "#0a0a0a" : "#1a202c";
538
+ }
539
+ const isLightSurface = relativeLuminance(background) > 0.5;
540
+ const backgroundLight = partialColors.backgroundLight ?? (isLightSurface ? oklchDarken(background, 0.04) : oklchLighten(background, 0.04));
541
+ const text = partialColors.text ?? seeds.text ?? pickContrastingText(background, "#f7fafc", "#1a202c");
542
+ const textMuted = partialColors.textMuted ?? (isLightSurface ? oklchLighten(text, 0.25) : oklchDarken(text, 0.25));
543
+ const highlight = partialColors.highlight ?? accent;
544
+ const warning = partialColors.warning ?? "#fc8181";
545
+ return {
546
+ primary: partialColors.primary ?? primary,
547
+ secondary: partialColors.secondary ?? secondary,
548
+ background,
549
+ backgroundLight,
550
+ text,
551
+ textMuted,
552
+ highlight,
553
+ warning
554
+ };
555
+ }
556
+ function accentToColorScheme(accent) {
557
+ if (!isHex(accent)) return { bg: "#1a202c", text: "#e2e8f0", accent: "#63b3ed" };
558
+ const scale = deriveScale(accent, 0.3);
559
+ return { bg: scale.darker2, text: scale.lighter2, accent: scale.base };
560
+ }
561
+ function compileTheme(partial, opts = {}) {
562
+ const base = opts.base ?? STARTER_THEME;
563
+ const merged = createTheme(base, partial);
564
+ merged.schemaVersion = THEME_SCHEMA_VERSION;
565
+ if (opts.base && !merged.basedOn) merged.basedOn = opts.base.id;
566
+ const partialTypography = partial.typography;
567
+ if (partialTypography) {
568
+ if (partialTypography.titleFont !== void 0) {
569
+ merged.typography.titleFont = partialTypography.titleFont;
570
+ }
571
+ if (partialTypography.bodyFont !== void 0) {
572
+ merged.typography.bodyFont = partialTypography.bodyFont;
573
+ }
574
+ if (partialTypography.monoFont !== void 0) {
575
+ merged.typography.monoFont = partialTypography.monoFont;
576
+ }
577
+ }
578
+ if (partial.colorSchemes !== void 0) {
579
+ merged.colorSchemes = partial.colorSchemes;
580
+ }
581
+ if (merged.seedColors) {
582
+ const partialColors = partial.colors ?? {};
583
+ merged.colors = deriveColorPalette(merged.seedColors, partialColors, {
584
+ contrast: opts.contrast
585
+ });
586
+ }
587
+ if (!merged.pageStyle) {
588
+ merged.pageStyle = defaultPageStyle(merged);
589
+ }
590
+ return assertTheme(merged, `compiled theme "${merged.id}"`);
591
+ }
592
+ function parseTheme(json) {
593
+ let parsed;
594
+ try {
595
+ parsed = JSON.parse(json);
596
+ } catch (err) {
597
+ const msg = err instanceof Error ? err.message : String(err);
598
+ throw new Error(`Invalid theme JSON: ${msg}`);
599
+ }
600
+ return assertTheme(parsed, "parsed theme");
601
+ }
602
+ function serializeTheme(theme) {
603
+ return JSON.stringify(theme, null, 2);
604
+ }
605
+
606
+ // src/schemas/mermaidTheme.ts
607
+ var MERMAID_CHART_COLOR_COUNT = 12;
608
+ function uniqueColors(colors) {
609
+ return colors.filter((color, index) => color && colors.indexOf(color) === index);
610
+ }
611
+ function chartPalette(theme) {
612
+ const schemes = Object.values(theme.colorSchemes);
613
+ const seeds = uniqueColors([
614
+ theme.colors.primary,
615
+ theme.colors.secondary,
616
+ theme.colors.highlight,
617
+ ...schemes.map((scheme) => scheme.accent),
618
+ theme.colors.warning,
619
+ ...schemes.map((scheme) => scheme.bg),
620
+ theme.colors.textMuted,
621
+ theme.colors.backgroundLight
622
+ ]);
623
+ const fallback = theme.colors.primary;
624
+ return Array.from(
625
+ { length: MERMAID_CHART_COLOR_COUNT },
626
+ (_, index) => seeds[index % Math.max(1, seeds.length)] ?? fallback
627
+ );
628
+ }
629
+ function textOn(theme, background) {
630
+ return pickContrastingText(background, theme.colors.text, theme.colors.background);
631
+ }
632
+ function buildMermaidThemeVariables(theme) {
633
+ const { colors } = theme;
634
+ const primaryText = textOn(theme, colors.primary);
635
+ const secondaryText = textOn(theme, colors.secondary);
636
+ const highlightText = textOn(theme, colors.highlight);
637
+ const palette = chartPalette(theme);
638
+ const variables = {
639
+ darkMode: relativeLuminance(colors.background) < 0.5,
640
+ background: colors.background,
641
+ fontFamily: resolveFontFamily(theme.typography.bodyFont, FONT_FALLBACKS.sans),
642
+ primaryColor: colors.primary,
643
+ primaryTextColor: primaryText,
644
+ primaryBorderColor: colors.highlight,
645
+ secondaryColor: colors.secondary,
646
+ secondaryTextColor: secondaryText,
647
+ secondaryBorderColor: colors.primary,
648
+ tertiaryColor: colors.backgroundLight,
649
+ tertiaryTextColor: colors.text,
650
+ tertiaryBorderColor: colors.textMuted,
651
+ textColor: colors.text,
652
+ titleColor: colors.text,
653
+ lineColor: colors.textMuted,
654
+ arrowheadColor: colors.textMuted,
655
+ defaultLinkColor: colors.textMuted,
656
+ mainBkg: colors.primary,
657
+ nodeBkg: colors.primary,
658
+ nodeTextColor: primaryText,
659
+ nodeBorder: colors.highlight,
660
+ clusterBkg: colors.backgroundLight,
661
+ clusterBorder: colors.secondary,
662
+ edgeLabelBackground: colors.background,
663
+ actorBkg: colors.backgroundLight,
664
+ actorBorder: colors.primary,
665
+ actorTextColor: colors.text,
666
+ actorLineColor: colors.textMuted,
667
+ signalColor: colors.textMuted,
668
+ signalTextColor: colors.text,
669
+ labelBoxBkgColor: colors.backgroundLight,
670
+ labelBoxBorderColor: colors.secondary,
671
+ labelTextColor: colors.text,
672
+ loopTextColor: colors.text,
673
+ activationBkgColor: colors.secondary,
674
+ activationBorderColor: colors.primary,
675
+ sequenceNumberColor: primaryText,
676
+ noteBkgColor: colors.highlight,
677
+ noteBorderColor: colors.primary,
678
+ noteTextColor: highlightText,
679
+ sectionBkgColor: colors.backgroundLight,
680
+ altSectionBkgColor: colors.background,
681
+ sectionBkgColor2: colors.primary,
682
+ taskBkgColor: colors.primary,
683
+ taskBorderColor: colors.highlight,
684
+ taskTextColor: primaryText,
685
+ taskTextLightColor: colors.text,
686
+ taskTextDarkColor: colors.text,
687
+ taskTextOutsideColor: colors.text,
688
+ taskTextClickableColor: colors.highlight,
689
+ activeTaskBkgColor: colors.secondary,
690
+ activeTaskBorderColor: colors.highlight,
691
+ doneTaskBkgColor: colors.backgroundLight,
692
+ doneTaskBorderColor: colors.textMuted,
693
+ critBkgColor: colors.warning,
694
+ critBorderColor: colors.highlight,
695
+ todayLineColor: colors.warning,
696
+ gridColor: colors.textMuted,
697
+ pieTitleTextColor: colors.text,
698
+ pieSectionTextColor: colors.text,
699
+ pieLegendTextColor: colors.text,
700
+ pieStrokeColor: colors.background,
701
+ pieOuterStrokeColor: colors.textMuted,
702
+ quadrantPointFill: colors.highlight,
703
+ quadrantPointTextFill: highlightText,
704
+ quadrantXAxisTextFill: colors.text,
705
+ quadrantYAxisTextFill: colors.text,
706
+ quadrantTitleFill: colors.text,
707
+ requirementBackground: colors.backgroundLight,
708
+ requirementBorderColor: colors.primary,
709
+ requirementTextColor: colors.text,
710
+ relationColor: colors.textMuted,
711
+ relationLabelBackground: colors.background,
712
+ relationLabelColor: colors.text,
713
+ commitLabelColor: colors.text,
714
+ commitLabelBackground: colors.backgroundLight,
715
+ tagLabelColor: primaryText,
716
+ tagLabelBackground: colors.primary,
717
+ tagLabelBorder: colors.highlight
718
+ };
719
+ palette.forEach((color, index) => {
720
+ const label = textOn(theme, color);
721
+ variables[`cScale${index}`] = color;
722
+ variables[`cScaleInv${index}`] = label;
723
+ variables[`cScaleLabel${index}`] = label;
724
+ variables[`cScalePeer${index}`] = label;
725
+ variables[`pie${index + 1}`] = color;
726
+ if (index < 8) {
727
+ variables[`git${index}`] = color;
728
+ variables[`gitInv${index}`] = label;
729
+ variables[`gitBranchLabel${index}`] = label;
730
+ }
731
+ });
732
+ return variables;
733
+ }
734
+
735
+ export {
736
+ calculateDuration,
737
+ getSegmentAtTime,
738
+ getBlockAtTime,
739
+ getCaptionAtTime,
740
+ validateDocSchema,
741
+ assertDocSchema,
742
+ pipStyleVars,
743
+ STARTER_THEME,
744
+ deriveColorPalette,
745
+ accentToColorScheme,
746
+ compileTheme,
747
+ parseTheme,
748
+ serializeTheme,
749
+ buildMermaidThemeVariables
750
+ };
@@ -979,8 +979,9 @@ declare function pullQuote(input: PullQuoteInput, context: TemplateContext): Lay
979
979
  * Video With Caption Template
980
980
  *
981
981
  * Full-screen background video clip with text overlay. Mirrors the structure of
982
- * imageWithCaption but uses a VideoLayer instead of an ImageLayer. The video
983
- * plays muted narration audio is the only sound track.
982
+ * imageWithCaption but uses a VideoLayer instead of an ImageLayer. Interactive
983
+ * players may play the video's own audio; muted and render-mode players keep it
984
+ * silent.
984
985
  *
985
986
  * Adapts caption positioning and font sizes for different viewports.
986
987
  *
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { B as BoundingBox, C as Coordinates } from './Types-sh2VRxfo.js';
2
2
  export { A as AccentImage, a as AccentPosition, b as AmbientGradientConfig, c as Animation, d as AnimationType, e as AreaChartInput, f as AudioBookmark, g as AudioSegment, h as AudioTimingData, i as AudioTrack, B as BarChartInput, j as Block, k as BlockConnection, l as BorderStyle, C as CaptionPhrase, m as CaptionTrack, n as CaptionWord, o as ChartBaseFields, p as ChartTemplateInput, q as ColorScheme, r as ColumnChartInput, s as ComparisonBarInput, t as ContentBlockInput, u as CornerBrandingConfig, v as CustomTemplateDefinition, w as CustomTemplateLayer, x as CustomTemplateValidationError, y as CustomTemplateValidationResult, D as DARK_SURFACE, z as DEFAULT_DOC_FONT, E as DEFAULT_TITLE_FONT, F as DataTableInput, G as DateEventInput, H as DeepPartial, I as DefinitionCardInput, J as DiagramBlockInput, K as DiagramEdgeAnchor, L as DiagramTemplateEdge, M as DiagramTemplateNode, N as Doc, O as DocBlock, P as DocDiagnostic, Q as DonutChartInput, R as DrawingBlockInput, S as FRONTMATTER_CUSTOM_TEMPLATES_KEY, T as FRONTMATTER_CUSTOM_THEMES_KEY, U as FactCardInput, V as FontFamily, W as FontFamilyKind, X as FullBleedQuoteInput, Y as GradientBackgroundConfig, Z as ImageBackgroundConfig, _ as ImageLayer, $ as ImageTreatment, a0 as ImageWithCaptionInput, a1 as LIGHT_SURFACE, a2 as Layer, a3 as LayerRepeat, a4 as LayoutHints, a5 as LeftFeatureInput, a6 as LineChartInput, a7 as LinearGradient, a8 as ListBlockInput, a9 as MapBlockInput, aa as MapLayer, ab as MapMarker, ac as MapTileStyle, ad as MarkerStyle, ae as MediaClip, af as MediaScheduleOptions, ag as MermaidLayer, ah as PAGE_ACCENT_STRATEGIES, ai as PAGE_BACKGROUNDS, aj as PAGE_BACKGROUND_RHYTHMS, ak as PAGE_DESIGN_FAMILIES, al as PAGE_DIVIDERS, am as PAGE_EMPHASES, an as PAGE_EYEBROWS, ao as PAGE_HEADING_CASES, ap as PAGE_HEADING_SCALES, aq as PAGE_HEADING_UNDERLINES, ar as PAGE_HERO_STYLES, as as PAGE_IMAGE_FRAMINGS, at as PAGE_NUMERAL_STYLES, au as PAGE_PATTERNS, av as PAGE_QUOTE_MARKS, aw as PAGE_SECTION_KINDS, ax as PAGE_SECTION_SPACINGS, ay as PAGE_SHADOWS, az as PageAccentRotation, aA as PageBackground, aB as PageDesignFamily, aC as PageEmphasis, aD as PageHeadingTreatment, aE as PageSectionKind, aF as PageSectionOverride, aG as PageTokens, aH as PathLayer, aI as PatternBackgroundConfig, aJ as PersistentLayer, aK as PersistentLayerConfig, aL as PersistentLayerTemplate, aM as PersistentLayerTemplateConfig, aN as PersistentLayerTemplateType, aO as PhotoGridInput, aP as PieChartInput, aQ as PipStyle, aR as Position, aS as ProgressIndicatorConfig, aT as PromotedBodyAnnotation, aU as PullQuoteInput, aV as QuoteBlockInput, aW as RawLayersInput, aX as RenderStyle, aY as RightFeatureInput, aZ as ScatterChartInput, a_ as ScheduledClip, a$ as SectionHeaderInput, b0 as ShapeFilter, b1 as ShapeLayer, b2 as ShapePattern, b3 as SolidBackgroundConfig, b4 as StartBlockConfig, b5 as StatHighlightInput, b6 as SurfaceScheme, b7 as THEME_SCHEMA_VERSION, b8 as TableLayer, b9 as TableLayerStyle, ba as TemplateBlock, bb as TemplateContext, bc as TemplateFunction, bd as TemplateRegistry, be as TextLayer, bf as TextStyle, bg as Theme, bh as ThemeColorPalette, bi as ThemeColorScheme, bj as ThemePageStyle, bk as ThemeRegistry, bl as ThemeSchemaVersion, bm as ThemeSeedColors, bn as ThemeStyle, bo as ThemeTypography, bp as TimelineBlockInput, bq as TimelineTemplateEvent, br as TimelineTemplateLink, bs as TimelineTemplateTrack, bt as TitleBlockInput, bu as TitleCaptionConfig, bv as TreeBlockInput, bw as TreeLayer, bx as TreeLayerItem, by as TreeLayerStyle, bz as TreeTemplateItem, bA as TwoColumnInput, bB as VIEWPORT_PRESETS, bC as VideoLayer, bD as VideoPipPosition, bE as VideoPipShape, bF as VideoPipSize, bG as VideoPlacement, bH as VideoPresentation, bI as VideoPullQuoteInput, bJ as VideoWithCaptionInput, bK as ViewportConfig, bL as ViewportOrientation, bM as ViewportPreset, bN as VignetteConfig, bO as applySurface, bP as calculateDuration, bQ as calculateFontScale, bR as createTemplateContext, bS as createTheme, bT as createThemeRegistry, bU as getAspectRatioString, bV as getBlockAtTime, bW as getCaptionAtTime, bX as getDocPlaybackDuration, bY as getLayoutHints, bZ as getSafeTextBounds, b_ as getSegmentAtTime, b$ as getTwoColumnPositions, c0 as getViewport, c1 as getViewportOrientation, c2 as isCustomTemplateDefinition, c3 as isPersistentLayerTemplate, c4 as isTemplateBlock, c5 as layoutScaledFontSize, c6 as resolveMediaSchedule, c7 as scaledFontSize, c8 as validateCustomTemplateDefinition } from './Doc-BKKcPjfe.js';
3
+ export { AVAILABLE_FONT_STACKS, CompileOptions, ContrastPreset, DocSchemaIssue, FONT_FALLBACKS, FontStack, MermaidThemeVariables, PipStyleVars, STARTER_THEME, ValidationError, ValidationResult, accentToColorScheme, assertDocSchema, assertTheme, buildGoogleFontsUrl, buildMermaidThemeVariables, compileTheme, contrastRatio, defaultPageStyle, deriveColorPalette, deriveScale, fontStack, getFontStack, guessFontFallback, hexHueDegrees, isHex, matchFontFamily, oklchDarken, oklchLighten, oklchSetChroma, parseTheme, pickContrastingText, pipStyleVars, relativeLuminance, resolveFontFamily, serializeTheme, validateDocSchema, validateTheme, withAlpha } from './schemas/index.js';
3
4
  export { D as DEFAULT_MARKDOWN_SAFETY_LIMITS, a as DEFAULT_TRANSITION_DURATION_SECONDS, H as HeadingAttributes, b as HeadingTemplateAnnotation, c as HtmlComment, d as HtmlElement, e as HtmlNode, f as HtmlText, M as MarkdownBlockNode, g as MarkdownBlockquote, h as MarkdownBreak, i as MarkdownCodeBlock, j as MarkdownContainerDirective, k as MarkdownDefinitionDescription, l as MarkdownDefinitionList, m as MarkdownDefinitionTerm, n as MarkdownDocument, o as MarkdownEmphasis, p as MarkdownFootnoteDefinition, q as MarkdownFootnoteReference, r as MarkdownHeading, s as MarkdownHtmlBlock, t as MarkdownImage, u as MarkdownImageReference, v as MarkdownInlineCode, w as MarkdownInlineHtml, x as MarkdownInlineIcon, y as MarkdownInlineMath, z as MarkdownInlineNode, A as MarkdownLeafDirective, B as MarkdownLimitError, C as MarkdownLink, E as MarkdownLinkDefinition, F as MarkdownLinkReference, G as MarkdownList, I as MarkdownListItem, J as MarkdownMathBlock, K as MarkdownMention, L as MarkdownNode, N as MarkdownParagraph, O as MarkdownPoint, P as MarkdownSafetyLimits, Q as MarkdownSourcePosition, R as MarkdownStrikethrough, S as MarkdownStrong, T as MarkdownTable, U as MarkdownTableCell, V as MarkdownTableRow, W as MarkdownText, X as MarkdownTextDirective, Y as MarkdownThematicBreak, Z as ParseOptions, _ as StringifyOptions, $ as TRANSITION_DIRECTIONS, a0 as TRANSITION_TYPES, a1 as Transition, a2 as TransitionDirection, a3 as TransitionType, a4 as assertMarkdownDocumentWithinLimits, a5 as assertMarkdownSourceWithinLimits, a6 as isTransitionType, a7 as normalizeTransitionDirection, a8 as normalizeTransitionType, a9 as resolveBlockTransition, aa as resolveMarkdownSafetyLimits, ab as resolveTransitionDuration } from './types-CcrDFdWH.js';
4
- export { AVAILABLE_FONT_STACKS, CompileOptions, ContrastPreset, FONT_FALLBACKS, FontStack, MermaidThemeVariables, PipStyleVars, STARTER_THEME, ValidationError, ValidationResult, accentToColorScheme, assertTheme, buildGoogleFontsUrl, buildMermaidThemeVariables, compileTheme, contrastRatio, defaultPageStyle, deriveColorPalette, deriveScale, fontStack, getFontStack, guessFontFallback, hexHueDegrees, isHex, matchFontFamily, oklchDarken, oklchLighten, oklchSetChroma, parseTheme, pickContrastingText, pipStyleVars, relativeLuminance, resolveFontFamily, serializeTheme, validateTheme, withAlpha } from './schemas/index.js';
5
5
  export { D as DEFAULT_THEME, a as DEFAULT_THEME_ID, T as THEMES, g as getAvailableThemes, b as getThemeSummaries, r as resolveTheme } from './themeLibrary-BMXXLZBU.js';
6
6
  export { M as MediaEntry, a as MediaProvider } from './MediaProvider-wpSe21B3.js';
7
7
  export { E as EditorLayerMeta, I as ImageEditCanvas, a as ImageEditDoc, b as ImageEditLayer, c as ImageEditLayerKind, d as ImageEditMeta } from './ImageEditDoc-CU1cXxRd.js';
package/dist/index.js CHANGED
@@ -112,6 +112,7 @@ import {
112
112
  import {
113
113
  STARTER_THEME,
114
114
  accentToColorScheme,
115
+ assertDocSchema,
115
116
  buildMermaidThemeVariables,
116
117
  calculateDuration,
117
118
  compileTheme,
@@ -121,8 +122,9 @@ import {
121
122
  getSegmentAtTime,
122
123
  parseTheme,
123
124
  pipStyleVars,
124
- serializeTheme
125
- } from "./chunk-GODLNXO4.js";
125
+ serializeTheme,
126
+ validateDocSchema
127
+ } from "./chunk-24SENDJY.js";
126
128
  import {
127
129
  calculateBearing,
128
130
  decodeGeohash,
@@ -623,6 +625,7 @@ export {
623
625
  asciiDiagramFromTemplateData,
624
626
  asciiDiagramToTemplateData,
625
627
  asciiTimelineToTemplateData,
628
+ assertDocSchema,
626
629
  assertMarkdownDocumentWithinLimits,
627
630
  assertMarkdownSourceWithinLimits,
628
631
  assertTheme,
@@ -966,6 +969,7 @@ export {
966
969
  updateLayer,
967
970
  vadStep,
968
971
  validateCustomTemplateDefinition,
972
+ validateDocSchema,
969
973
  validateMarkdownDoc,
970
974
  validateMarkdownSource,
971
975
  validateTheme,
@@ -16,7 +16,7 @@ import {
16
16
  setByPointer,
17
17
  toPointer
18
18
  } from "../chunk-DAMIRKTO.js";
19
- import "../chunk-GODLNXO4.js";
19
+ import "../chunk-24SENDJY.js";
20
20
  import "../chunk-GAZKTT4R.js";
21
21
  import "../chunk-SBAX4ZPO.js";
22
22
  import "../chunk-BAOV476U.js";
@@ -1,11 +1,30 @@
1
1
  export { B as BoundingBox, C as Coordinates } from '../Types-sh2VRxfo.js';
2
- import { bg as Theme, bj as ThemePageStyle, bi as ThemeColorScheme, H as DeepPartial, bm as ThemeSeedColors, bh as ThemeColorPalette, W as FontFamilyKind, V as FontFamily } from '../Doc-BKKcPjfe.js';
3
- export { A as AccentImage, a as AccentPosition, b as AmbientGradientConfig, c as Animation, d as AnimationType, e as AreaChartInput, f as AudioBookmark, g as AudioSegment, h as AudioTimingData, i as AudioTrack, B as BarChartInput, j as Block, k as BlockConnection, l as BorderStyle, C as CaptionPhrase, m as CaptionTrack, n as CaptionWord, o as ChartBaseFields, p as ChartTemplateInput, q as ColorScheme, r as ColumnChartInput, s as ComparisonBarInput, t as ContentBlockInput, u as CornerBrandingConfig, v as CustomTemplateDefinition, w as CustomTemplateLayer, x as CustomTemplateValidationError, y as CustomTemplateValidationResult, D as DARK_SURFACE, z as DEFAULT_DOC_FONT, E as DEFAULT_TITLE_FONT, F as DataTableInput, G as DateEventInput, I as DefinitionCardInput, J as DiagramBlockInput, K as DiagramEdgeAnchor, L as DiagramTemplateEdge, M as DiagramTemplateNode, N as Doc, O as DocBlock, P as DocDiagnostic, Q as DonutChartInput, R as DrawingBlockInput, S as FRONTMATTER_CUSTOM_TEMPLATES_KEY, T as FRONTMATTER_CUSTOM_THEMES_KEY, U as FactCardInput, X as FullBleedQuoteInput, Y as GradientBackgroundConfig, Z as ImageBackgroundConfig, _ as ImageLayer, $ as ImageTreatment, a0 as ImageWithCaptionInput, a1 as LIGHT_SURFACE, a2 as Layer, a3 as LayerRepeat, a4 as LayoutHints, a5 as LeftFeatureInput, a6 as LineChartInput, a7 as LinearGradient, a8 as ListBlockInput, a9 as MapBlockInput, aa as MapLayer, ab as MapMarker, ac as MapTileStyle, ad as MarkerStyle, ae as MediaClip, af as MediaScheduleOptions, ag as MermaidLayer, ah as PAGE_ACCENT_STRATEGIES, ai as PAGE_BACKGROUNDS, aj as PAGE_BACKGROUND_RHYTHMS, ak as PAGE_DESIGN_FAMILIES, al as PAGE_DIVIDERS, am as PAGE_EMPHASES, an as PAGE_EYEBROWS, ao as PAGE_HEADING_CASES, ap as PAGE_HEADING_SCALES, aq as PAGE_HEADING_UNDERLINES, ar as PAGE_HERO_STYLES, as as PAGE_IMAGE_FRAMINGS, at as PAGE_NUMERAL_STYLES, au as PAGE_PATTERNS, av as PAGE_QUOTE_MARKS, aw as PAGE_SECTION_KINDS, ax as PAGE_SECTION_SPACINGS, ay as PAGE_SHADOWS, az as PageAccentRotation, aA as PageBackground, aB as PageDesignFamily, aC as PageEmphasis, aD as PageHeadingTreatment, aE as PageSectionKind, aF as PageSectionOverride, aG as PageTokens, aH as PathLayer, aI as PatternBackgroundConfig, aJ as PersistentLayer, aK as PersistentLayerConfig, aL as PersistentLayerTemplate, aM as PersistentLayerTemplateConfig, aN as PersistentLayerTemplateType, aO as PhotoGridInput, aP as PieChartInput, aQ as PipStyle, aR as Position, aS as ProgressIndicatorConfig, aT as PromotedBodyAnnotation, aU as PullQuoteInput, aV as QuoteBlockInput, aW as RawLayersInput, aX as RenderStyle, aY as RightFeatureInput, aZ as ScatterChartInput, a_ as ScheduledClip, a$ as SectionHeaderInput, b0 as ShapeFilter, b1 as ShapeLayer, b2 as ShapePattern, b3 as SolidBackgroundConfig, b4 as StartBlockConfig, b5 as StatHighlightInput, b6 as SurfaceScheme, b7 as THEME_SCHEMA_VERSION, b8 as TableLayer, b9 as TableLayerStyle, ba as TemplateBlock, bb as TemplateContext, bc as TemplateFunction, bd as TemplateRegistry, be as TextLayer, bf as TextStyle, bk as ThemeRegistry, bl as ThemeSchemaVersion, bn as ThemeStyle, bo as ThemeTypography, bp as TimelineBlockInput, bq as TimelineTemplateEvent, br as TimelineTemplateLink, bs as TimelineTemplateTrack, bt as TitleBlockInput, bu as TitleCaptionConfig, bv as TreeBlockInput, bw as TreeLayer, bx as TreeLayerItem, by as TreeLayerStyle, bz as TreeTemplateItem, bA as TwoColumnInput, bB as VIEWPORT_PRESETS, bC as VideoLayer, bD as VideoPipPosition, bE as VideoPipShape, bF as VideoPipSize, bG as VideoPlacement, bH as VideoPresentation, bI as VideoPullQuoteInput, bJ as VideoWithCaptionInput, bK as ViewportConfig, bL as ViewportOrientation, bM as ViewportPreset, bN as VignetteConfig, bO as applySurface, bP as calculateDuration, bQ as calculateFontScale, bR as createTemplateContext, bS as createTheme, bT as createThemeRegistry, bU as getAspectRatioString, bV as getBlockAtTime, bW as getCaptionAtTime, bX as getDocPlaybackDuration, bY as getLayoutHints, bZ as getSafeTextBounds, b_ as getSegmentAtTime, b$ as getTwoColumnPositions, c0 as getViewport, c1 as getViewportOrientation, c2 as isCustomTemplateDefinition, c3 as isPersistentLayerTemplate, c4 as isTemplateBlock, c5 as layoutScaledFontSize, c6 as resolveMediaSchedule, c7 as scaledFontSize, c8 as validateCustomTemplateDefinition } from '../Doc-BKKcPjfe.js';
2
+ import { N as Doc, bg as Theme, bj as ThemePageStyle, bi as ThemeColorScheme, H as DeepPartial, bm as ThemeSeedColors, bh as ThemeColorPalette, W as FontFamilyKind, V as FontFamily } from '../Doc-BKKcPjfe.js';
3
+ export { A as AccentImage, a as AccentPosition, b as AmbientGradientConfig, c as Animation, d as AnimationType, e as AreaChartInput, f as AudioBookmark, g as AudioSegment, h as AudioTimingData, i as AudioTrack, B as BarChartInput, j as Block, k as BlockConnection, l as BorderStyle, C as CaptionPhrase, m as CaptionTrack, n as CaptionWord, o as ChartBaseFields, p as ChartTemplateInput, q as ColorScheme, r as ColumnChartInput, s as ComparisonBarInput, t as ContentBlockInput, u as CornerBrandingConfig, v as CustomTemplateDefinition, w as CustomTemplateLayer, x as CustomTemplateValidationError, y as CustomTemplateValidationResult, D as DARK_SURFACE, z as DEFAULT_DOC_FONT, E as DEFAULT_TITLE_FONT, F as DataTableInput, G as DateEventInput, I as DefinitionCardInput, J as DiagramBlockInput, K as DiagramEdgeAnchor, L as DiagramTemplateEdge, M as DiagramTemplateNode, O as DocBlock, P as DocDiagnostic, Q as DonutChartInput, R as DrawingBlockInput, S as FRONTMATTER_CUSTOM_TEMPLATES_KEY, T as FRONTMATTER_CUSTOM_THEMES_KEY, U as FactCardInput, X as FullBleedQuoteInput, Y as GradientBackgroundConfig, Z as ImageBackgroundConfig, _ as ImageLayer, $ as ImageTreatment, a0 as ImageWithCaptionInput, a1 as LIGHT_SURFACE, a2 as Layer, a3 as LayerRepeat, a4 as LayoutHints, a5 as LeftFeatureInput, a6 as LineChartInput, a7 as LinearGradient, a8 as ListBlockInput, a9 as MapBlockInput, aa as MapLayer, ab as MapMarker, ac as MapTileStyle, ad as MarkerStyle, ae as MediaClip, af as MediaScheduleOptions, ag as MermaidLayer, ah as PAGE_ACCENT_STRATEGIES, ai as PAGE_BACKGROUNDS, aj as PAGE_BACKGROUND_RHYTHMS, ak as PAGE_DESIGN_FAMILIES, al as PAGE_DIVIDERS, am as PAGE_EMPHASES, an as PAGE_EYEBROWS, ao as PAGE_HEADING_CASES, ap as PAGE_HEADING_SCALES, aq as PAGE_HEADING_UNDERLINES, ar as PAGE_HERO_STYLES, as as PAGE_IMAGE_FRAMINGS, at as PAGE_NUMERAL_STYLES, au as PAGE_PATTERNS, av as PAGE_QUOTE_MARKS, aw as PAGE_SECTION_KINDS, ax as PAGE_SECTION_SPACINGS, ay as PAGE_SHADOWS, az as PageAccentRotation, aA as PageBackground, aB as PageDesignFamily, aC as PageEmphasis, aD as PageHeadingTreatment, aE as PageSectionKind, aF as PageSectionOverride, aG as PageTokens, aH as PathLayer, aI as PatternBackgroundConfig, aJ as PersistentLayer, aK as PersistentLayerConfig, aL as PersistentLayerTemplate, aM as PersistentLayerTemplateConfig, aN as PersistentLayerTemplateType, aO as PhotoGridInput, aP as PieChartInput, aQ as PipStyle, aR as Position, aS as ProgressIndicatorConfig, aT as PromotedBodyAnnotation, aU as PullQuoteInput, aV as QuoteBlockInput, aW as RawLayersInput, aX as RenderStyle, aY as RightFeatureInput, aZ as ScatterChartInput, a_ as ScheduledClip, a$ as SectionHeaderInput, b0 as ShapeFilter, b1 as ShapeLayer, b2 as ShapePattern, b3 as SolidBackgroundConfig, b4 as StartBlockConfig, b5 as StatHighlightInput, b6 as SurfaceScheme, b7 as THEME_SCHEMA_VERSION, b8 as TableLayer, b9 as TableLayerStyle, ba as TemplateBlock, bb as TemplateContext, bc as TemplateFunction, bd as TemplateRegistry, be as TextLayer, bf as TextStyle, bk as ThemeRegistry, bl as ThemeSchemaVersion, bn as ThemeStyle, bo as ThemeTypography, bp as TimelineBlockInput, bq as TimelineTemplateEvent, br as TimelineTemplateLink, bs as TimelineTemplateTrack, bt as TitleBlockInput, bu as TitleCaptionConfig, bv as TreeBlockInput, bw as TreeLayer, bx as TreeLayerItem, by as TreeLayerStyle, bz as TreeTemplateItem, bA as TwoColumnInput, bB as VIEWPORT_PRESETS, bC as VideoLayer, bD as VideoPipPosition, bE as VideoPipShape, bF as VideoPipSize, bG as VideoPlacement, bH as VideoPresentation, bI as VideoPullQuoteInput, bJ as VideoWithCaptionInput, bK as ViewportConfig, bL as ViewportOrientation, bM as ViewportPreset, bN as VignetteConfig, bO as applySurface, bP as calculateDuration, bQ as calculateFontScale, bR as createTemplateContext, bS as createTheme, bT as createThemeRegistry, bU as getAspectRatioString, bV as getBlockAtTime, bW as getCaptionAtTime, bX as getDocPlaybackDuration, bY as getLayoutHints, bZ as getSafeTextBounds, b_ as getSegmentAtTime, b$ as getTwoColumnPositions, c0 as getViewport, c1 as getViewportOrientation, c2 as isCustomTemplateDefinition, c3 as isPersistentLayerTemplate, c4 as isTemplateBlock, c5 as layoutScaledFontSize, c6 as resolveMediaSchedule, c7 as scaledFontSize, c8 as validateCustomTemplateDefinition } from '../Doc-BKKcPjfe.js';
4
4
  export { a as DEFAULT_TRANSITION_DURATION_SECONDS, $ as TRANSITION_DIRECTIONS, a0 as TRANSITION_TYPES, a1 as Transition, a2 as TransitionDirection, a3 as TransitionType, a6 as isTransitionType, a7 as normalizeTransitionDirection, a8 as normalizeTransitionType, a9 as resolveBlockTransition, ab as resolveTransitionDuration } from '../types-CcrDFdWH.js';
5
5
  export { D as DEFAULT_THEME, a as DEFAULT_THEME_ID, T as THEMES, g as getAvailableThemes, b as getThemeSummaries, r as resolveTheme } from '../themeLibrary-BMXXLZBU.js';
6
6
  export { M as MediaEntry, a as MediaProvider } from '../MediaProvider-wpSe21B3.js';
7
7
  export { E as EditorLayerMeta, I as ImageEditCanvas, a as ImageEditDoc, b as ImageEditLayer, c as ImageEditLayerKind, d as ImageEditMeta } from '../ImageEditDoc-CU1cXxRd.js';
8
8
 
9
+ /**
10
+ * Runtime validation for the canonical {@link Doc} JSON shape.
11
+ *
12
+ * TypeScript types disappear at a JSON boundary. This validator deliberately
13
+ * lives next to the schema so every importer can apply the same structural
14
+ * contract instead of maintaining a partial, consumer-specific guard.
15
+ */
16
+
17
+ interface DocSchemaIssue {
18
+ /** JavaScript-style path to the invalid value. */
19
+ path: string;
20
+ /** Human-readable statement of the violated schema rule. */
21
+ message: string;
22
+ }
23
+ /** Return every structural problem found in a prospective Doc value. */
24
+ declare function validateDocSchema(value: unknown): DocSchemaIssue[];
25
+ /** Assert that a value implements the canonical Doc JSON schema. */
26
+ declare function assertDocSchema(value: unknown): asserts value is Doc;
27
+
9
28
  /**
10
29
  * Theme-driven picture-in-picture framing.
11
30
  *
@@ -291,4 +310,4 @@ type MermaidThemeVariables = Record<string, string | number | boolean>;
291
310
  /** Build complete, contrast-aware Mermaid theme variables from a Squisq theme. */
292
311
  declare function buildMermaidThemeVariables(theme: Theme): MermaidThemeVariables;
293
312
 
294
- export { AVAILABLE_FONT_STACKS, type CompileOptions, type ContrastPreset, DeepPartial, FONT_FALLBACKS, FontFamily, FontFamilyKind, type FontStack, type MermaidThemeVariables, type PipStyleVars, STARTER_THEME, Theme, ThemeColorPalette, ThemeColorScheme, ThemePageStyle, ThemeSeedColors, type ValidationError, type ValidationResult, accentToColorScheme, assertTheme, buildGoogleFontsUrl, buildMermaidThemeVariables, compileTheme, contrastRatio, defaultPageStyle, deriveColorPalette, deriveScale, fontStack, getFontStack, guessFontFallback, hexHueDegrees, isHex, matchFontFamily, oklchDarken, oklchLighten, oklchSetChroma, parseTheme, pickContrastingText, pipStyleVars, relativeLuminance, resolveFontFamily, serializeTheme, validateTheme, withAlpha };
313
+ export { AVAILABLE_FONT_STACKS, type CompileOptions, type ContrastPreset, DeepPartial, Doc, type DocSchemaIssue, FONT_FALLBACKS, FontFamily, FontFamilyKind, type FontStack, type MermaidThemeVariables, type PipStyleVars, STARTER_THEME, Theme, ThemeColorPalette, ThemeColorScheme, ThemePageStyle, ThemeSeedColors, type ValidationError, type ValidationResult, accentToColorScheme, assertDocSchema, assertTheme, buildGoogleFontsUrl, buildMermaidThemeVariables, compileTheme, contrastRatio, defaultPageStyle, deriveColorPalette, deriveScale, fontStack, getFontStack, guessFontFallback, hexHueDegrees, isHex, matchFontFamily, oklchDarken, oklchLighten, oklchSetChroma, parseTheme, pickContrastingText, pipStyleVars, relativeLuminance, resolveFontFamily, serializeTheme, validateDocSchema, validateTheme, withAlpha };
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  STARTER_THEME,
3
3
  accentToColorScheme,
4
+ assertDocSchema,
4
5
  buildMermaidThemeVariables,
5
6
  calculateDuration,
6
7
  compileTheme,
@@ -10,8 +11,9 @@ import {
10
11
  getSegmentAtTime,
11
12
  parseTheme,
12
13
  pipStyleVars,
13
- serializeTheme
14
- } from "../chunk-GODLNXO4.js";
14
+ serializeTheme,
15
+ validateDocSchema
16
+ } from "../chunk-24SENDJY.js";
15
17
  import {
16
18
  defaultPageStyle,
17
19
  getDocPlaybackDuration,
@@ -138,6 +140,7 @@ export {
138
140
  VIEWPORT_PRESETS,
139
141
  accentToColorScheme,
140
142
  applySurface,
143
+ assertDocSchema,
141
144
  assertTheme,
142
145
  buildGoogleFontsUrl,
143
146
  buildMermaidThemeVariables,
@@ -191,6 +194,7 @@ export {
191
194
  scaledFontSize2 as scaledFontSize,
192
195
  serializeTheme,
193
196
  validateCustomTemplateDefinition,
197
+ validateDocSchema,
194
198
  validateTheme,
195
199
  withAlpha
196
200
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bendyline/squisq",
3
- "version": "2.4.2",
3
+ "version": "2.4.3",
4
4
  "description": "Headless utilities for doc/block rendering, spatial math, Markdown, and storage",
5
5
  "license": "MIT",
6
6
  "author": "Bendyline",
@@ -1,360 +0,0 @@
1
- import {
2
- defaultPageStyle
3
- } from "./chunk-GAZKTT4R.js";
4
- import {
5
- FONT_FALLBACKS,
6
- THEME_SCHEMA_VERSION,
7
- assertTheme,
8
- createTheme,
9
- deriveScale,
10
- isHex,
11
- oklchDarken,
12
- oklchLighten,
13
- oklchSetChroma,
14
- pickContrastingText,
15
- relativeLuminance,
16
- resolveFontFamily,
17
- withAlpha
18
- } from "./chunk-SBAX4ZPO.js";
19
-
20
- // src/schemas/Doc.ts
21
- function calculateDuration(audio) {
22
- return audio.segments.reduce((sum, seg) => sum + seg.duration, 0);
23
- }
24
- function getSegmentAtTime(audio, time) {
25
- let elapsed = 0;
26
- for (let i = 0; i < audio.segments.length; i++) {
27
- elapsed += audio.segments[i].duration;
28
- if (time < elapsed) return i;
29
- }
30
- return audio.segments.length - 1;
31
- }
32
- function getBlockAtTime(blocks, time) {
33
- for (let i = blocks.length - 1; i >= 0; i--) {
34
- const block = blocks[i];
35
- if (time >= block.startTime && time < block.startTime + block.duration) {
36
- return block;
37
- }
38
- }
39
- return blocks[0] || null;
40
- }
41
- function getCaptionAtTime(captions, time) {
42
- if (!captions || !captions.phrases.length) return null;
43
- for (const phrase of captions.phrases) {
44
- if (time >= phrase.startTime && time < phrase.endTime) {
45
- return phrase;
46
- }
47
- }
48
- return null;
49
- }
50
-
51
- // src/schemas/pipStyle.ts
52
- var DEFAULT_SHADOW = "0 0.75em 2em rgba(0, 0, 0, 0.34)";
53
- var DERIVED_BORDER_WIDTH = "max(1px, 0.12vw)";
54
- function cssLength(value) {
55
- return typeof value === "number" ? `${value}px` : value;
56
- }
57
- function resolveRadius(theme) {
58
- const explicit = theme.style.pip?.cornerRadius;
59
- if (explicit != null) {
60
- if (typeof explicit === "number") return explicit <= 0 ? "0" : `${explicit}px`;
61
- return explicit;
62
- }
63
- const br = theme.style.borderRadius ?? 0;
64
- if (br <= 0) return "0";
65
- return `${Math.min(28, Math.max(6, Math.round(br)))}%`;
66
- }
67
- function resolveBorder(theme) {
68
- const border = theme.style.pip?.border;
69
- if (border === "none") return "none";
70
- if (border && typeof border === "object") {
71
- const width = border.width == null ? DERIVED_BORDER_WIDTH : cssLength(border.width);
72
- const color = border.color ?? withAlpha(theme.colors.text, 0.35);
73
- return `${width} solid ${color}`;
74
- }
75
- return `${DERIVED_BORDER_WIDTH} solid ${withAlpha(theme.colors.text, 0.35)}`;
76
- }
77
- function resolveShadow(theme) {
78
- const shadow = theme.style.pip?.shadow;
79
- if (shadow === false || shadow === "none") return "none";
80
- if (typeof shadow === "string") return shadow;
81
- return DEFAULT_SHADOW;
82
- }
83
- function pipStyleVars(theme) {
84
- return {
85
- "--squisq-pip-radius": resolveRadius(theme),
86
- "--squisq-pip-border": resolveBorder(theme),
87
- "--squisq-pip-shadow": resolveShadow(theme)
88
- };
89
- }
90
-
91
- // src/schemas/themeCompile.ts
92
- var STARTER_BODY_FONT = { stackId: "system-sans" };
93
- var STARTER_TITLE_FONT = { stackId: "system-serif" };
94
- var STARTER_MONO_FONT = { stackId: "system-mono" };
95
- var STARTER_COLOR_SCHEMES = {
96
- blue: { bg: "#1a365d", text: "#63b3ed", accent: "#90cdf4" },
97
- green: { bg: "#22543d", text: "#9ae6b4", accent: "#68d391" },
98
- purple: { bg: "#44337a", text: "#d6bcfa", accent: "#b794f4" },
99
- red: { bg: "#742a2a", text: "#fc8181", accent: "#feb2b2" },
100
- orange: { bg: "#744210", text: "#fbd38d", accent: "#f6ad55" },
101
- teal: { bg: "#234e52", text: "#81e6d9", accent: "#4fd1c5" }
102
- };
103
- var STARTER_THEME = {
104
- schemaVersion: THEME_SCHEMA_VERSION,
105
- id: "custom",
106
- name: "Custom Theme",
107
- description: "Customizer starter \u2014 gets overridden by user choices.",
108
- colors: {
109
- primary: "#3182ce",
110
- secondary: "#4a5568",
111
- background: "#1a202c",
112
- backgroundLight: "#2d3748",
113
- text: "#f7fafc",
114
- textMuted: "#a0aec0",
115
- highlight: "#4299e1",
116
- warning: "#fc8181"
117
- },
118
- typography: {
119
- bodyFont: STARTER_BODY_FONT,
120
- titleFont: STARTER_TITLE_FONT,
121
- monoFont: STARTER_MONO_FONT,
122
- titleWeight: "bold"
123
- },
124
- style: {
125
- textShadow: true,
126
- overlayOpacity: 0.45,
127
- animationSpeed: 1,
128
- borderRadius: 6
129
- },
130
- renderStyle: {
131
- name: "standard",
132
- defaultTextAnimation: "fadeIn",
133
- defaultImageAnimation: "slowZoom",
134
- ambientMotion: true,
135
- defaultTransition: { type: "fade", duration: 0.7 }
136
- },
137
- colorSchemes: STARTER_COLOR_SCHEMES
138
- };
139
- function deriveColorPalette(seeds, partialColors = {}, opts = {}) {
140
- const spread = opts.contrast === "high" ? 0.22 : opts.contrast === "subtle" ? 0.08 : 0.15;
141
- const primary = seeds.primary;
142
- const secondary = seeds.secondary ?? oklchSetChroma(oklchLighten(primary, 0.05), 0.5);
143
- const accent = seeds.accent ?? oklchLighten(primary, spread);
144
- const bgSeed = partialColors.background ?? seeds.background;
145
- let background;
146
- if (bgSeed) {
147
- background = bgSeed;
148
- } else {
149
- background = relativeLuminance(primary) > 0.5 ? "#0a0a0a" : "#1a202c";
150
- }
151
- const isLightSurface = relativeLuminance(background) > 0.5;
152
- const backgroundLight = partialColors.backgroundLight ?? (isLightSurface ? oklchDarken(background, 0.04) : oklchLighten(background, 0.04));
153
- const text = partialColors.text ?? seeds.text ?? pickContrastingText(background, "#f7fafc", "#1a202c");
154
- const textMuted = partialColors.textMuted ?? (isLightSurface ? oklchLighten(text, 0.25) : oklchDarken(text, 0.25));
155
- const highlight = partialColors.highlight ?? accent;
156
- const warning = partialColors.warning ?? "#fc8181";
157
- return {
158
- primary: partialColors.primary ?? primary,
159
- secondary: partialColors.secondary ?? secondary,
160
- background,
161
- backgroundLight,
162
- text,
163
- textMuted,
164
- highlight,
165
- warning
166
- };
167
- }
168
- function accentToColorScheme(accent) {
169
- if (!isHex(accent)) return { bg: "#1a202c", text: "#e2e8f0", accent: "#63b3ed" };
170
- const scale = deriveScale(accent, 0.3);
171
- return { bg: scale.darker2, text: scale.lighter2, accent: scale.base };
172
- }
173
- function compileTheme(partial, opts = {}) {
174
- const base = opts.base ?? STARTER_THEME;
175
- const merged = createTheme(base, partial);
176
- merged.schemaVersion = THEME_SCHEMA_VERSION;
177
- if (opts.base && !merged.basedOn) merged.basedOn = opts.base.id;
178
- const partialTypography = partial.typography;
179
- if (partialTypography) {
180
- if (partialTypography.titleFont !== void 0) {
181
- merged.typography.titleFont = partialTypography.titleFont;
182
- }
183
- if (partialTypography.bodyFont !== void 0) {
184
- merged.typography.bodyFont = partialTypography.bodyFont;
185
- }
186
- if (partialTypography.monoFont !== void 0) {
187
- merged.typography.monoFont = partialTypography.monoFont;
188
- }
189
- }
190
- if (partial.colorSchemes !== void 0) {
191
- merged.colorSchemes = partial.colorSchemes;
192
- }
193
- if (merged.seedColors) {
194
- const partialColors = partial.colors ?? {};
195
- merged.colors = deriveColorPalette(merged.seedColors, partialColors, {
196
- contrast: opts.contrast
197
- });
198
- }
199
- if (!merged.pageStyle) {
200
- merged.pageStyle = defaultPageStyle(merged);
201
- }
202
- return assertTheme(merged, `compiled theme "${merged.id}"`);
203
- }
204
- function parseTheme(json) {
205
- let parsed;
206
- try {
207
- parsed = JSON.parse(json);
208
- } catch (err) {
209
- const msg = err instanceof Error ? err.message : String(err);
210
- throw new Error(`Invalid theme JSON: ${msg}`);
211
- }
212
- return assertTheme(parsed, "parsed theme");
213
- }
214
- function serializeTheme(theme) {
215
- return JSON.stringify(theme, null, 2);
216
- }
217
-
218
- // src/schemas/mermaidTheme.ts
219
- var MERMAID_CHART_COLOR_COUNT = 12;
220
- function uniqueColors(colors) {
221
- return colors.filter((color, index) => color && colors.indexOf(color) === index);
222
- }
223
- function chartPalette(theme) {
224
- const schemes = Object.values(theme.colorSchemes);
225
- const seeds = uniqueColors([
226
- theme.colors.primary,
227
- theme.colors.secondary,
228
- theme.colors.highlight,
229
- ...schemes.map((scheme) => scheme.accent),
230
- theme.colors.warning,
231
- ...schemes.map((scheme) => scheme.bg),
232
- theme.colors.textMuted,
233
- theme.colors.backgroundLight
234
- ]);
235
- const fallback = theme.colors.primary;
236
- return Array.from(
237
- { length: MERMAID_CHART_COLOR_COUNT },
238
- (_, index) => seeds[index % Math.max(1, seeds.length)] ?? fallback
239
- );
240
- }
241
- function textOn(theme, background) {
242
- return pickContrastingText(background, theme.colors.text, theme.colors.background);
243
- }
244
- function buildMermaidThemeVariables(theme) {
245
- const { colors } = theme;
246
- const primaryText = textOn(theme, colors.primary);
247
- const secondaryText = textOn(theme, colors.secondary);
248
- const highlightText = textOn(theme, colors.highlight);
249
- const palette = chartPalette(theme);
250
- const variables = {
251
- darkMode: relativeLuminance(colors.background) < 0.5,
252
- background: colors.background,
253
- fontFamily: resolveFontFamily(theme.typography.bodyFont, FONT_FALLBACKS.sans),
254
- primaryColor: colors.primary,
255
- primaryTextColor: primaryText,
256
- primaryBorderColor: colors.highlight,
257
- secondaryColor: colors.secondary,
258
- secondaryTextColor: secondaryText,
259
- secondaryBorderColor: colors.primary,
260
- tertiaryColor: colors.backgroundLight,
261
- tertiaryTextColor: colors.text,
262
- tertiaryBorderColor: colors.textMuted,
263
- textColor: colors.text,
264
- titleColor: colors.text,
265
- lineColor: colors.textMuted,
266
- arrowheadColor: colors.textMuted,
267
- defaultLinkColor: colors.textMuted,
268
- mainBkg: colors.primary,
269
- nodeBkg: colors.primary,
270
- nodeTextColor: primaryText,
271
- nodeBorder: colors.highlight,
272
- clusterBkg: colors.backgroundLight,
273
- clusterBorder: colors.secondary,
274
- edgeLabelBackground: colors.background,
275
- actorBkg: colors.backgroundLight,
276
- actorBorder: colors.primary,
277
- actorTextColor: colors.text,
278
- actorLineColor: colors.textMuted,
279
- signalColor: colors.textMuted,
280
- signalTextColor: colors.text,
281
- labelBoxBkgColor: colors.backgroundLight,
282
- labelBoxBorderColor: colors.secondary,
283
- labelTextColor: colors.text,
284
- loopTextColor: colors.text,
285
- activationBkgColor: colors.secondary,
286
- activationBorderColor: colors.primary,
287
- sequenceNumberColor: primaryText,
288
- noteBkgColor: colors.highlight,
289
- noteBorderColor: colors.primary,
290
- noteTextColor: highlightText,
291
- sectionBkgColor: colors.backgroundLight,
292
- altSectionBkgColor: colors.background,
293
- sectionBkgColor2: colors.primary,
294
- taskBkgColor: colors.primary,
295
- taskBorderColor: colors.highlight,
296
- taskTextColor: primaryText,
297
- taskTextLightColor: colors.text,
298
- taskTextDarkColor: colors.text,
299
- taskTextOutsideColor: colors.text,
300
- taskTextClickableColor: colors.highlight,
301
- activeTaskBkgColor: colors.secondary,
302
- activeTaskBorderColor: colors.highlight,
303
- doneTaskBkgColor: colors.backgroundLight,
304
- doneTaskBorderColor: colors.textMuted,
305
- critBkgColor: colors.warning,
306
- critBorderColor: colors.highlight,
307
- todayLineColor: colors.warning,
308
- gridColor: colors.textMuted,
309
- pieTitleTextColor: colors.text,
310
- pieSectionTextColor: colors.text,
311
- pieLegendTextColor: colors.text,
312
- pieStrokeColor: colors.background,
313
- pieOuterStrokeColor: colors.textMuted,
314
- quadrantPointFill: colors.highlight,
315
- quadrantPointTextFill: highlightText,
316
- quadrantXAxisTextFill: colors.text,
317
- quadrantYAxisTextFill: colors.text,
318
- quadrantTitleFill: colors.text,
319
- requirementBackground: colors.backgroundLight,
320
- requirementBorderColor: colors.primary,
321
- requirementTextColor: colors.text,
322
- relationColor: colors.textMuted,
323
- relationLabelBackground: colors.background,
324
- relationLabelColor: colors.text,
325
- commitLabelColor: colors.text,
326
- commitLabelBackground: colors.backgroundLight,
327
- tagLabelColor: primaryText,
328
- tagLabelBackground: colors.primary,
329
- tagLabelBorder: colors.highlight
330
- };
331
- palette.forEach((color, index) => {
332
- const label = textOn(theme, color);
333
- variables[`cScale${index}`] = color;
334
- variables[`cScaleInv${index}`] = label;
335
- variables[`cScaleLabel${index}`] = label;
336
- variables[`cScalePeer${index}`] = label;
337
- variables[`pie${index + 1}`] = color;
338
- if (index < 8) {
339
- variables[`git${index}`] = color;
340
- variables[`gitInv${index}`] = label;
341
- variables[`gitBranchLabel${index}`] = label;
342
- }
343
- });
344
- return variables;
345
- }
346
-
347
- export {
348
- calculateDuration,
349
- getSegmentAtTime,
350
- getBlockAtTime,
351
- getCaptionAtTime,
352
- pipStyleVars,
353
- STARTER_THEME,
354
- deriveColorPalette,
355
- accentToColorScheme,
356
- compileTheme,
357
- parseTheme,
358
- serializeTheme,
359
- buildMermaidThemeVariables
360
- };