@bendyline/squisq 0.1.1
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/dist/Types-sh2VRxfo.d.ts +15 -0
- package/dist/chunk-7UDSRZKG.js +103 -0
- package/dist/chunk-7UDSRZKG.js.map +1 -0
- package/dist/chunk-O5HCIUAD.js +577 -0
- package/dist/chunk-O5HCIUAD.js.map +1 -0
- package/dist/chunk-PJ7AODIM.js +3040 -0
- package/dist/chunk-PJ7AODIM.js.map +1 -0
- package/dist/chunk-QWVRN6I4.js +81 -0
- package/dist/chunk-QWVRN6I4.js.map +1 -0
- package/dist/chunk-S4F2RY37.js +92 -0
- package/dist/chunk-S4F2RY37.js.map +1 -0
- package/dist/chunk-URU6QMLY.js +888 -0
- package/dist/chunk-URU6QMLY.js.map +1 -0
- package/dist/chunk-VJN7UB2Z.js +145 -0
- package/dist/chunk-VJN7UB2Z.js.map +1 -0
- package/dist/doc/index.d.ts +3 -0
- package/dist/doc/index.js +126 -0
- package/dist/doc/index.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +217 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown/index.d.ts +237 -0
- package/dist/markdown/index.js +33 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/schemas/index.d.ts +70 -0
- package/dist/schemas/index.js +61 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/spatial/index.d.ts +111 -0
- package/dist/spatial/index.js +25 -0
- package/dist/spatial/index.js.map +1 -0
- package/dist/storage/index.d.ts +91 -0
- package/dist/storage/index.js +11 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/story/index.d.ts +691 -0
- package/dist/story/index.js +126 -0
- package/dist/story/index.js.map +1 -0
- package/dist/themeLibrary-DySHPcgj.d.ts +1352 -0
- package/dist/types-DHiv_Pnm.d.ts +496 -0
- package/package.json +91 -0
- package/src/__tests__/animationUtils.test.ts +77 -0
- package/src/__tests__/geohash.test.ts +109 -0
- package/src/__tests__/getLayers.test.ts +200 -0
- package/src/__tests__/haversine.test.ts +63 -0
- package/src/__tests__/localForageAdapter.test.ts +155 -0
- package/src/__tests__/markdown.test.ts +806 -0
- package/src/__tests__/markdownToDoc.test.ts +374 -0
- package/src/__tests__/storage.test.ts +60 -0
- package/src/__tests__/templates.test.ts +247 -0
- package/src/doc/docToMarkdown.ts +98 -0
- package/src/doc/getLayers.ts +152 -0
- package/src/doc/index.ts +8 -0
- package/src/doc/markdownToDoc.ts +277 -0
- package/src/doc/templates/accentImage.ts +338 -0
- package/src/doc/templates/captionUtils.ts +33 -0
- package/src/doc/templates/comparisonBar.ts +140 -0
- package/src/doc/templates/coverBlock.ts +156 -0
- package/src/doc/templates/dateEvent.ts +122 -0
- package/src/doc/templates/definitionCard.ts +129 -0
- package/src/doc/templates/factCard.ts +114 -0
- package/src/doc/templates/fullBleedQuote.ts +59 -0
- package/src/doc/templates/imageWithCaption.ts +167 -0
- package/src/doc/templates/index.ts +571 -0
- package/src/doc/templates/listBlock.ts +101 -0
- package/src/doc/templates/mapBlock.ts +132 -0
- package/src/doc/templates/persistentLayers.ts +522 -0
- package/src/doc/templates/photoGrid.ts +199 -0
- package/src/doc/templates/pullQuote.ts +119 -0
- package/src/doc/templates/quoteBlock.ts +112 -0
- package/src/doc/templates/sectionHeader.ts +105 -0
- package/src/doc/templates/statHighlight.ts +113 -0
- package/src/doc/templates/titleBlock.ts +100 -0
- package/src/doc/templates/twoColumn.ts +239 -0
- package/src/doc/templates/videoPullQuote.ts +128 -0
- package/src/doc/templates/videoWithCaption.ts +126 -0
- package/src/doc/utils/animationUtils.ts +135 -0
- package/src/doc/utils/themeUtils.ts +139 -0
- package/src/index.ts +5 -0
- package/src/markdown/convert.ts +897 -0
- package/src/markdown/htmlParse.ts +212 -0
- package/src/markdown/index.ts +110 -0
- package/src/markdown/parse.ts +103 -0
- package/src/markdown/stringify.ts +122 -0
- package/src/markdown/types.ts +605 -0
- package/src/markdown/utils.ts +165 -0
- package/src/ngeohash.d.ts +11 -0
- package/src/schemas/BlockTemplates.ts +706 -0
- package/src/schemas/Doc.ts +565 -0
- package/src/schemas/LayoutStrategy.ts +178 -0
- package/src/schemas/MediaProvider.ts +70 -0
- package/src/schemas/Theme.ts +235 -0
- package/src/schemas/Types.ts +15 -0
- package/src/schemas/Viewport.ts +91 -0
- package/src/schemas/index.ts +14 -0
- package/src/schemas/themeLibrary.ts +492 -0
- package/src/spatial/Geohash.ts +168 -0
- package/src/spatial/Haversine.ts +59 -0
- package/src/spatial/index.ts +2 -0
- package/src/storage/LocalForageAdapter.ts +103 -0
- package/src/storage/LocalStorageAdapter.ts +57 -0
- package/src/storage/MemoryStorageAdapter.ts +37 -0
- package/src/storage/Storage.ts +28 -0
- package/src/storage/index.ts +5 -0
|
@@ -0,0 +1,1352 @@
|
|
|
1
|
+
import { M as MarkdownBlockNode, p as MarkdownHeading } from './types-DHiv_Pnm.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Viewport Configuration
|
|
5
|
+
*
|
|
6
|
+
* Defines viewport dimensions for doc rendering across different aspect ratios.
|
|
7
|
+
* Templates use this configuration to adapt their layouts appropriately.
|
|
8
|
+
*
|
|
9
|
+
* Supported aspect ratios:
|
|
10
|
+
* - landscape (16:9) - Default, standard video/presentation format
|
|
11
|
+
* - portrait (9:16) - Vertical video, mobile stories
|
|
12
|
+
* - square (1:1) - Social media posts
|
|
13
|
+
* - standard (4:3) - Legacy presentation format
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Viewport configuration for doc rendering.
|
|
17
|
+
*/
|
|
18
|
+
interface ViewportConfig {
|
|
19
|
+
/** Canonical width in virtual pixels */
|
|
20
|
+
width: number;
|
|
21
|
+
/** Canonical height in virtual pixels */
|
|
22
|
+
height: number;
|
|
23
|
+
/** Human-readable name for debugging/display */
|
|
24
|
+
name: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Standard viewport presets for common aspect ratios.
|
|
28
|
+
*/
|
|
29
|
+
declare const VIEWPORT_PRESETS: {
|
|
30
|
+
/** 16:9 landscape (default, 1080p) */
|
|
31
|
+
readonly landscape: {
|
|
32
|
+
readonly width: 1920;
|
|
33
|
+
readonly height: 1080;
|
|
34
|
+
readonly name: "16:9 Landscape";
|
|
35
|
+
};
|
|
36
|
+
/** 9:16 portrait (vertical video, stories) */
|
|
37
|
+
readonly portrait: {
|
|
38
|
+
readonly width: 1080;
|
|
39
|
+
readonly height: 1920;
|
|
40
|
+
readonly name: "9:16 Portrait";
|
|
41
|
+
};
|
|
42
|
+
/** 1:1 square (social media) */
|
|
43
|
+
readonly square: {
|
|
44
|
+
readonly width: 1080;
|
|
45
|
+
readonly height: 1080;
|
|
46
|
+
readonly name: "1:1 Square";
|
|
47
|
+
};
|
|
48
|
+
/** 4:3 standard (legacy) */
|
|
49
|
+
readonly standard: {
|
|
50
|
+
readonly width: 1440;
|
|
51
|
+
readonly height: 1080;
|
|
52
|
+
readonly name: "4:3 Standard";
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Viewport preset name.
|
|
57
|
+
*/
|
|
58
|
+
type ViewportPreset = keyof typeof VIEWPORT_PRESETS;
|
|
59
|
+
/**
|
|
60
|
+
* Viewport orientation derived from aspect ratio.
|
|
61
|
+
*/
|
|
62
|
+
type ViewportOrientation = 'landscape' | 'portrait' | 'square';
|
|
63
|
+
/**
|
|
64
|
+
* Get a viewport configuration from a preset name or return the config if already a ViewportConfig.
|
|
65
|
+
*/
|
|
66
|
+
declare function getViewport(viewport: ViewportPreset | ViewportConfig): ViewportConfig;
|
|
67
|
+
declare function calculateFontScale(viewport: ViewportConfig): number;
|
|
68
|
+
/**
|
|
69
|
+
* Determine if viewport is landscape, portrait, or square.
|
|
70
|
+
*/
|
|
71
|
+
declare function getViewportOrientation(viewport: ViewportConfig): ViewportOrientation;
|
|
72
|
+
/**
|
|
73
|
+
* Get aspect ratio as a string (e.g., "16:9", "9:16", "1:1").
|
|
74
|
+
*/
|
|
75
|
+
declare function getAspectRatioString(viewport: ViewportConfig): string;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Layout Strategy
|
|
79
|
+
*
|
|
80
|
+
* Provides orientation-specific layout hints for slide templates.
|
|
81
|
+
* Templates use these hints to position content appropriately for
|
|
82
|
+
* different aspect ratios (landscape, portrait, square).
|
|
83
|
+
*
|
|
84
|
+
* Key adaptations by orientation:
|
|
85
|
+
* - Portrait: Larger relative text, stacked layouts instead of side-by-side
|
|
86
|
+
* - Square: Balanced layout with moderate adjustments
|
|
87
|
+
* - Landscape: Default/reference layout
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Layout hints for template positioning based on orientation.
|
|
92
|
+
*/
|
|
93
|
+
interface LayoutHints {
|
|
94
|
+
/** Primary content Y position (percentage string, e.g., "35%") */
|
|
95
|
+
primaryY: string;
|
|
96
|
+
/** Secondary content Y position (percentage string) */
|
|
97
|
+
secondaryY: string;
|
|
98
|
+
/** Tertiary/detail Y position (percentage string) */
|
|
99
|
+
tertiaryY: string;
|
|
100
|
+
/** Title font size multiplier (relative to base) */
|
|
101
|
+
titleScale: number;
|
|
102
|
+
/** Body text font size multiplier (relative to base) */
|
|
103
|
+
bodyScale: number;
|
|
104
|
+
/** Two-column: left X position (percentage string) */
|
|
105
|
+
columnLeftX: string;
|
|
106
|
+
/** Two-column: right X position (percentage string) */
|
|
107
|
+
columnRightX: string;
|
|
108
|
+
/** Two-column in portrait: top Y position for stacked layout */
|
|
109
|
+
columnTopY: string;
|
|
110
|
+
/** Two-column in portrait: bottom Y position for stacked layout */
|
|
111
|
+
columnBottomY: string;
|
|
112
|
+
/** Whether two-column should stack vertically */
|
|
113
|
+
stackColumns: boolean;
|
|
114
|
+
/** Max text width (percentage string) for wrapping */
|
|
115
|
+
maxTextWidth: string;
|
|
116
|
+
/** Horizontal padding from edges (percentage string) */
|
|
117
|
+
horizontalPadding: string;
|
|
118
|
+
/** Caption position Y for imageWithCaption (percentage string) */
|
|
119
|
+
captionY: string;
|
|
120
|
+
/** Caption font size multiplier */
|
|
121
|
+
captionScale: number;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get layout hints for a given orientation.
|
|
125
|
+
*/
|
|
126
|
+
declare function getLayoutHints(orientation: ViewportOrientation): LayoutHints;
|
|
127
|
+
/**
|
|
128
|
+
* Calculate a scaled font size based on viewport and layout hints.
|
|
129
|
+
*
|
|
130
|
+
* @param basePx - Base font size in pixels (designed for 1920x1080)
|
|
131
|
+
* @param viewport - Target viewport configuration
|
|
132
|
+
* @param orientation - Viewport orientation
|
|
133
|
+
* @param isTitle - Whether this is title text (uses titleScale) or body (uses bodyScale)
|
|
134
|
+
*/
|
|
135
|
+
declare function scaledFontSize$1(basePx: number, viewport: ViewportConfig, orientation: ViewportOrientation, isTitle?: boolean): number;
|
|
136
|
+
/**
|
|
137
|
+
* Get position for two-column layout based on orientation.
|
|
138
|
+
* Returns { left: {x, y}, right: {x, y} } positions.
|
|
139
|
+
*/
|
|
140
|
+
declare function getTwoColumnPositions(orientation: ViewportOrientation): {
|
|
141
|
+
left: {
|
|
142
|
+
x: string;
|
|
143
|
+
y: string;
|
|
144
|
+
};
|
|
145
|
+
right: {
|
|
146
|
+
x: string;
|
|
147
|
+
y: string;
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Get safe text bounds for a given orientation.
|
|
152
|
+
* Returns margins to keep text away from edges.
|
|
153
|
+
*/
|
|
154
|
+
declare function getSafeTextBounds(orientation: ViewportOrientation): {
|
|
155
|
+
left: string;
|
|
156
|
+
right: string;
|
|
157
|
+
top: string;
|
|
158
|
+
bottom: string;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Theme System
|
|
163
|
+
*
|
|
164
|
+
* A Theme bundles color palette, typography, visual style, render-style
|
|
165
|
+
* algorithm, and per-block color schemes into one JSON-serializable object.
|
|
166
|
+
* Builders choose a theme from the built-in library or create a custom one
|
|
167
|
+
* via `createTheme(base, overrides)`.
|
|
168
|
+
*
|
|
169
|
+
* Design principles:
|
|
170
|
+
* - Fully JSON-serializable (no functions) — storable in config / APIs.
|
|
171
|
+
* - Doc carries an optional `themeId` pointer; resolution happens at render time.
|
|
172
|
+
* - `createTheme` deep-merges a base theme with partial overrides.
|
|
173
|
+
*/
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Core color palette for a theme. Every color is a CSS color string.
|
|
177
|
+
*/
|
|
178
|
+
interface ThemeColorPalette {
|
|
179
|
+
/** Primary accent color */
|
|
180
|
+
primary: string;
|
|
181
|
+
/** Secondary accent color */
|
|
182
|
+
secondary: string;
|
|
183
|
+
/** Background color (typically dark) */
|
|
184
|
+
background: string;
|
|
185
|
+
/** Lighter background for contrast panels */
|
|
186
|
+
backgroundLight: string;
|
|
187
|
+
/** Main text color */
|
|
188
|
+
text: string;
|
|
189
|
+
/** Muted/secondary text color */
|
|
190
|
+
textMuted: string;
|
|
191
|
+
/** Highlight/emphasis color */
|
|
192
|
+
highlight: string;
|
|
193
|
+
/** Warning/alert color */
|
|
194
|
+
warning: string;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* A named color scheme used by templates for per-block color variation
|
|
198
|
+
* (e.g., statHighlight or sectionHeader).
|
|
199
|
+
*/
|
|
200
|
+
interface ThemeColorScheme {
|
|
201
|
+
/** Background fill */
|
|
202
|
+
bg: string;
|
|
203
|
+
/** Primary text tint */
|
|
204
|
+
text: string;
|
|
205
|
+
/** Accent / secondary tint */
|
|
206
|
+
accent: string;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Typography settings for a theme.
|
|
210
|
+
*/
|
|
211
|
+
interface ThemeTypography {
|
|
212
|
+
/** Font family for body / description text */
|
|
213
|
+
bodyFontFamily: string;
|
|
214
|
+
/** Font family for titles and headings */
|
|
215
|
+
titleFontFamily: string;
|
|
216
|
+
/** Font family for code / monospaced text (optional) */
|
|
217
|
+
monoFontFamily?: string;
|
|
218
|
+
/** Multiplier applied to LayoutHints.titleScale (default 1.0) */
|
|
219
|
+
titleScale?: number;
|
|
220
|
+
/** Multiplier applied to LayoutHints.bodyScale (default 1.0) */
|
|
221
|
+
bodyScale?: number;
|
|
222
|
+
/** Default body line height (default 1.4) */
|
|
223
|
+
lineHeight?: number;
|
|
224
|
+
/** Default title line height */
|
|
225
|
+
titleLineHeight?: number;
|
|
226
|
+
/** Default title font weight */
|
|
227
|
+
titleWeight?: 'normal' | 'bold';
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Global visual-style knobs that templates consult.
|
|
231
|
+
*/
|
|
232
|
+
interface ThemeStyle {
|
|
233
|
+
/** Default border radius for cards / shapes (px) */
|
|
234
|
+
borderRadius?: number;
|
|
235
|
+
/** Whether templates should default to text shadows */
|
|
236
|
+
textShadow?: boolean;
|
|
237
|
+
/** Darkness of overlay on image-backed blocks (0–1) */
|
|
238
|
+
overlayOpacity?: number;
|
|
239
|
+
/** Multiplier on all animation durations (1.0 = normal, <1 faster, >1 slower) */
|
|
240
|
+
animationSpeed?: number;
|
|
241
|
+
/** Default horizontal padding for text (percentage string, e.g. "5%") */
|
|
242
|
+
blockPadding?: string;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Render-style algorithm preset. Controls layout tweaks, default animations,
|
|
246
|
+
* transitions, and per-template behavioral hints.
|
|
247
|
+
*/
|
|
248
|
+
interface RenderStyle {
|
|
249
|
+
/** Identifier for this algorithmic approach (e.g. "documentary", "magazine") */
|
|
250
|
+
name: string;
|
|
251
|
+
/** Partial overrides merged onto the orientation-based LayoutHints */
|
|
252
|
+
layoutOverrides?: Partial<LayoutHints>;
|
|
253
|
+
/** Default entrance animation for text layers */
|
|
254
|
+
defaultTextAnimation?: AnimationType;
|
|
255
|
+
/** Default animation for background / image layers */
|
|
256
|
+
defaultImageAnimation?: AnimationType;
|
|
257
|
+
/** Whether to apply Ken Burns ambient motion to images by default */
|
|
258
|
+
ambientMotion?: boolean;
|
|
259
|
+
/** Default block-to-block transition */
|
|
260
|
+
defaultTransition?: {
|
|
261
|
+
type: TransitionType;
|
|
262
|
+
duration?: number;
|
|
263
|
+
};
|
|
264
|
+
/**
|
|
265
|
+
* Per-template behavioral hints. Keys are template names, values are
|
|
266
|
+
* string/number/boolean maps that templates can read to vary their output.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```
|
|
270
|
+
* { statHighlight: { entrance: 'dramatic' }, titleBlock: { showAccentLine: false } }
|
|
271
|
+
* ```
|
|
272
|
+
*/
|
|
273
|
+
templateHints?: Record<string, Record<string, string | number | boolean>>;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* A complete, JSON-serializable theme definition.
|
|
277
|
+
*/
|
|
278
|
+
interface Theme {
|
|
279
|
+
/** Unique identifier (e.g. "documentary") */
|
|
280
|
+
id: string;
|
|
281
|
+
/** Human-readable display name */
|
|
282
|
+
name: string;
|
|
283
|
+
/** Short description for theme pickers */
|
|
284
|
+
description?: string;
|
|
285
|
+
/** Color palette */
|
|
286
|
+
colors: ThemeColorPalette;
|
|
287
|
+
/** Typography settings */
|
|
288
|
+
typography: ThemeTypography;
|
|
289
|
+
/** Global visual-style knobs */
|
|
290
|
+
style: ThemeStyle;
|
|
291
|
+
/** Algorithmic render-style preset */
|
|
292
|
+
renderStyle: RenderStyle;
|
|
293
|
+
/**
|
|
294
|
+
* Named color schemes for per-block color variation.
|
|
295
|
+
* Templates reference these by name (e.g. "blue", "warm").
|
|
296
|
+
*/
|
|
297
|
+
colorSchemes: Record<string, ThemeColorScheme>;
|
|
298
|
+
/** Optional persistent layers baked into the theme */
|
|
299
|
+
persistentLayers?: PersistentLayerConfig;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Recursively makes every property optional.
|
|
303
|
+
*/
|
|
304
|
+
type DeepPartial<T> = {
|
|
305
|
+
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
|
306
|
+
};
|
|
307
|
+
/**
|
|
308
|
+
* Create a new theme by deep-merging overrides onto a base theme.
|
|
309
|
+
* The returned theme gets its own `id` if one is provided in overrides,
|
|
310
|
+
* otherwise keeps the base theme's `id` suffixed with "-custom".
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* ```ts
|
|
314
|
+
* const myTheme = createTheme(THEMES.documentary, {
|
|
315
|
+
* colors: { primary: '#ff0000' },
|
|
316
|
+
* typography: { bodyFontFamily: '"Inter", sans-serif' },
|
|
317
|
+
* });
|
|
318
|
+
* ```
|
|
319
|
+
*/
|
|
320
|
+
declare function createTheme(base: Theme, overrides: DeepPartial<Theme>): Theme;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Block Templates Schema
|
|
324
|
+
*
|
|
325
|
+
* Defines a template system for AI-generated doc blocks. Instead of
|
|
326
|
+
* specifying low-level layers, positions, and animations, AI can use
|
|
327
|
+
* high-level templates like "titleBlock" or "statHighlight" with simple
|
|
328
|
+
* content parameters.
|
|
329
|
+
*
|
|
330
|
+
* Templates are expanded into full Block structures at render time,
|
|
331
|
+
* ensuring consistent styling and reducing AI generation errors.
|
|
332
|
+
*
|
|
333
|
+
* Template Types:
|
|
334
|
+
* - titleBlock: Doc intro with title + subtitle
|
|
335
|
+
* - sectionHeader: Colored section divider
|
|
336
|
+
* - statHighlight: Big number/stat with description
|
|
337
|
+
* - quoteBlock: Large centered quote
|
|
338
|
+
* - factCard: Key fact with explanation
|
|
339
|
+
* - twoColumn: Side-by-side comparison
|
|
340
|
+
* - dateEvent: Timeline-style date + description
|
|
341
|
+
* - imageWithCaption: Image with text overlay
|
|
342
|
+
*/
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Name of a color scheme defined in the active theme's `colorSchemes` map.
|
|
346
|
+
* Templates reference schemes by name (e.g. 'blue', 'green') and the theme
|
|
347
|
+
* resolves them to actual colors at render time via `resolveColorScheme()`.
|
|
348
|
+
*/
|
|
349
|
+
type ColorScheme = string;
|
|
350
|
+
/**
|
|
351
|
+
* Default font family for doc text.
|
|
352
|
+
* PT Serif provides a classic, readable appearance for documentary-style content.
|
|
353
|
+
* Used as a fallback in TextLayer when no font is specified.
|
|
354
|
+
*/
|
|
355
|
+
declare const DEFAULT_DOC_FONT = "\"PT Serif\", Georgia, \"Times New Roman\", serif";
|
|
356
|
+
/**
|
|
357
|
+
* Default font family for titles.
|
|
358
|
+
*/
|
|
359
|
+
declare const DEFAULT_TITLE_FONT = "\"PT Serif\", Georgia, \"Times New Roman\", serif";
|
|
360
|
+
/**
|
|
361
|
+
* Position options for accent images on text-based blocks.
|
|
362
|
+
* Accents are tasteful image additions that complement rather than overwhelm text.
|
|
363
|
+
*/
|
|
364
|
+
type AccentPosition = 'left-strip' | 'right-strip' | 'bottom-strip' | 'corner-inset';
|
|
365
|
+
/**
|
|
366
|
+
* Accent image configuration for text-based blocks.
|
|
367
|
+
* When present, the template adjusts layout to accommodate the image tastefully.
|
|
368
|
+
*/
|
|
369
|
+
interface AccentImage {
|
|
370
|
+
/** Path to image file (relative to article media dir) */
|
|
371
|
+
src: string;
|
|
372
|
+
/** Alt text for accessibility */
|
|
373
|
+
alt: string;
|
|
374
|
+
/** Where to position the accent image */
|
|
375
|
+
position: AccentPosition;
|
|
376
|
+
/** Ambient motion effect (subtle Ken Burns) */
|
|
377
|
+
ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';
|
|
378
|
+
/** Photo credit / artist name */
|
|
379
|
+
credit?: string;
|
|
380
|
+
/** License identifier (e.g., 'CC BY-SA 4.0') */
|
|
381
|
+
license?: string;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Base properties shared by all template blocks.
|
|
385
|
+
*/
|
|
386
|
+
interface BaseTemplateBlock {
|
|
387
|
+
/** Unique block ID */
|
|
388
|
+
id: string;
|
|
389
|
+
/** Block duration in seconds */
|
|
390
|
+
duration: number;
|
|
391
|
+
/** Which audio segment this block belongs to */
|
|
392
|
+
audioSegment: number;
|
|
393
|
+
/** Entry transition */
|
|
394
|
+
transition?: Transition;
|
|
395
|
+
/** Show doc's bottom layers (default: true) */
|
|
396
|
+
useBottomLayer?: boolean;
|
|
397
|
+
/** Show doc's top layers (default: true) */
|
|
398
|
+
useTopLayer?: boolean;
|
|
399
|
+
/**
|
|
400
|
+
* Start time within the audio segment when this content is spoken.
|
|
401
|
+
* Used to sync the block appearance with the narration.
|
|
402
|
+
* If not provided, blocks are distributed evenly across the segment.
|
|
403
|
+
*/
|
|
404
|
+
sourceStartTime?: number;
|
|
405
|
+
/**
|
|
406
|
+
* Duration of the spoken content this block represents.
|
|
407
|
+
* Used with sourceStartTime to precisely position the block.
|
|
408
|
+
*/
|
|
409
|
+
sourceDuration?: number;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Title block - doc intro with large title and subtitle.
|
|
413
|
+
*/
|
|
414
|
+
interface TitleBlockInput extends BaseTemplateBlock {
|
|
415
|
+
template: 'titleBlock';
|
|
416
|
+
/** Main title text */
|
|
417
|
+
title: string;
|
|
418
|
+
/** Subtitle or tagline (supports \n for line breaks) */
|
|
419
|
+
subtitle?: string;
|
|
420
|
+
/** Background color (defaults to theme primary) */
|
|
421
|
+
backgroundColor?: string;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Section header - section title card with optional background image.
|
|
425
|
+
* Displays the section title prominently, optionally over an image.
|
|
426
|
+
*/
|
|
427
|
+
interface SectionHeaderInput extends BaseTemplateBlock {
|
|
428
|
+
template: 'sectionHeader';
|
|
429
|
+
/** Section title */
|
|
430
|
+
title: string;
|
|
431
|
+
/** Color scheme for the section (used for fallback background) */
|
|
432
|
+
colorScheme?: ColorScheme;
|
|
433
|
+
/** Optional background image path */
|
|
434
|
+
imageSrc?: string;
|
|
435
|
+
/** Alt text for background image */
|
|
436
|
+
imageAlt?: string;
|
|
437
|
+
/** Ambient motion for background image */
|
|
438
|
+
ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Stat highlight - big number with description and optional detail.
|
|
442
|
+
*/
|
|
443
|
+
interface StatHighlightInput extends BaseTemplateBlock {
|
|
444
|
+
template: 'statHighlight';
|
|
445
|
+
/** The statistic (e.g., "89%", "2x", "7 miles") */
|
|
446
|
+
stat: string;
|
|
447
|
+
/** Description of what the stat means */
|
|
448
|
+
description: string;
|
|
449
|
+
/** Additional detail or context */
|
|
450
|
+
detail?: string;
|
|
451
|
+
/** Color scheme for the stat */
|
|
452
|
+
colorScheme?: ColorScheme;
|
|
453
|
+
/** Optional accent image to complement the text */
|
|
454
|
+
accentImage?: AccentImage;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Quote block - large centered quote text.
|
|
458
|
+
*/
|
|
459
|
+
interface QuoteBlockInput extends BaseTemplateBlock {
|
|
460
|
+
template: 'quoteBlock';
|
|
461
|
+
/** The quote text (supports \n for line breaks) */
|
|
462
|
+
quote: string;
|
|
463
|
+
/** Attribution (author, source) */
|
|
464
|
+
attribution?: string;
|
|
465
|
+
/** Optional accent image to complement the text */
|
|
466
|
+
accentImage?: AccentImage;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Fact card - key fact with explanation.
|
|
470
|
+
*/
|
|
471
|
+
interface FactCardInput extends BaseTemplateBlock {
|
|
472
|
+
template: 'factCard';
|
|
473
|
+
/** The main fact or statement */
|
|
474
|
+
fact: string;
|
|
475
|
+
/** Explanation or context */
|
|
476
|
+
explanation: string;
|
|
477
|
+
/** Optional source citation */
|
|
478
|
+
source?: string;
|
|
479
|
+
/** Optional accent image to complement the text */
|
|
480
|
+
accentImage?: AccentImage;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Two column - side-by-side comparison.
|
|
484
|
+
*/
|
|
485
|
+
interface TwoColumnInput extends BaseTemplateBlock {
|
|
486
|
+
template: 'twoColumn';
|
|
487
|
+
/** Left column content */
|
|
488
|
+
left: {
|
|
489
|
+
label: string;
|
|
490
|
+
sublabel?: string;
|
|
491
|
+
};
|
|
492
|
+
/** Right column content */
|
|
493
|
+
right: {
|
|
494
|
+
label: string;
|
|
495
|
+
sublabel?: string;
|
|
496
|
+
};
|
|
497
|
+
/** Optional header above columns */
|
|
498
|
+
header?: string;
|
|
499
|
+
/** Color schemes for left and right */
|
|
500
|
+
leftColor?: ColorScheme;
|
|
501
|
+
rightColor?: ColorScheme;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Date event - timeline-style date with description.
|
|
505
|
+
*/
|
|
506
|
+
interface DateEventInput extends BaseTemplateBlock {
|
|
507
|
+
template: 'dateEvent';
|
|
508
|
+
/** The date (e.g., "July 14, 1974") */
|
|
509
|
+
date: string;
|
|
510
|
+
/** Description of what happened (supports \n) */
|
|
511
|
+
description: string;
|
|
512
|
+
/** Optional footer text */
|
|
513
|
+
footer?: string;
|
|
514
|
+
/** Mood: 'neutral', 'somber', 'celebratory' */
|
|
515
|
+
mood?: 'neutral' | 'somber' | 'celebratory';
|
|
516
|
+
/** Optional accent image to complement the text */
|
|
517
|
+
accentImage?: AccentImage;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Image with caption - background image with text overlay.
|
|
521
|
+
*/
|
|
522
|
+
interface ImageWithCaptionInput extends BaseTemplateBlock {
|
|
523
|
+
template: 'imageWithCaption';
|
|
524
|
+
/** Path to image file */
|
|
525
|
+
imageSrc: string;
|
|
526
|
+
/** Alt text for accessibility */
|
|
527
|
+
imageAlt: string;
|
|
528
|
+
/** Caption text */
|
|
529
|
+
caption?: string;
|
|
530
|
+
/** Caption position */
|
|
531
|
+
captionPosition?: 'bottom' | 'top' | 'center';
|
|
532
|
+
/** Ambient motion effect: slow zoom or pan for visual interest */
|
|
533
|
+
ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';
|
|
534
|
+
/** When true, style caption as a title (larger, centered, prominent) */
|
|
535
|
+
isTitle?: boolean;
|
|
536
|
+
/** Optional subtitle shown below the title (only when isTitle is true) */
|
|
537
|
+
subtitle?: string;
|
|
538
|
+
/** Photo credit / artist name */
|
|
539
|
+
imageCredit?: string;
|
|
540
|
+
/** License identifier (e.g., 'CC BY-SA 4.0') */
|
|
541
|
+
imageLicense?: string;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Map block - geographic map showing article location.
|
|
545
|
+
*
|
|
546
|
+
* Displays a map centered on given coordinates with optional title,
|
|
547
|
+
* caption, and markers. Great for establishing geographic context.
|
|
548
|
+
* See docs/MAP_TILES.md for available tile styles and attribution.
|
|
549
|
+
*/
|
|
550
|
+
interface MapBlockInput extends BaseTemplateBlock {
|
|
551
|
+
template: 'mapBlock';
|
|
552
|
+
/** Map center coordinates */
|
|
553
|
+
center: {
|
|
554
|
+
lat: number;
|
|
555
|
+
lng: number;
|
|
556
|
+
};
|
|
557
|
+
/** Zoom level (4-16, typically 8-12 for regional context) */
|
|
558
|
+
zoom: number;
|
|
559
|
+
/** Map tile style (default: 'terrain') */
|
|
560
|
+
mapStyle?: MapTileStyle;
|
|
561
|
+
/** Optional title overlay at top */
|
|
562
|
+
title?: string;
|
|
563
|
+
/** Optional caption at bottom */
|
|
564
|
+
caption?: string;
|
|
565
|
+
/** Optional markers to show on map */
|
|
566
|
+
markers?: MapMarker[];
|
|
567
|
+
/** Ambient motion effect: slow zoom or pan for visual interest */
|
|
568
|
+
ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';
|
|
569
|
+
/** Pre-rendered static image path (set by prerender-maps command) */
|
|
570
|
+
staticSrc?: string;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Full-bleed quote - dramatic short text filling the viewport.
|
|
574
|
+
* Like a movie title card. For short, punchy sentences < 60 chars.
|
|
575
|
+
*/
|
|
576
|
+
interface FullBleedQuoteInput extends BaseTemplateBlock {
|
|
577
|
+
template: 'fullBleedQuote';
|
|
578
|
+
/** Short dramatic text (< 60 chars recommended) */
|
|
579
|
+
text: string;
|
|
580
|
+
/** Color scheme for text tint */
|
|
581
|
+
colorScheme?: ColorScheme;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* List block - 3-5 items stacked vertically with subtle numbering.
|
|
585
|
+
* Good for enumerations like "things to see" or "key features".
|
|
586
|
+
*/
|
|
587
|
+
interface ListBlockInput extends BaseTemplateBlock {
|
|
588
|
+
template: 'listBlock';
|
|
589
|
+
/** List items (3-5 recommended) */
|
|
590
|
+
items: string[];
|
|
591
|
+
/** Optional header above the list */
|
|
592
|
+
title?: string;
|
|
593
|
+
/** Color scheme for numbering accents */
|
|
594
|
+
colorScheme?: ColorScheme;
|
|
595
|
+
/** Optional accent image */
|
|
596
|
+
accentImage?: AccentImage;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Photo grid - 2-4 images in a tiled layout.
|
|
600
|
+
* Used when multiple images are available for visual variety.
|
|
601
|
+
*/
|
|
602
|
+
interface PhotoGridInput extends BaseTemplateBlock {
|
|
603
|
+
template: 'photoGrid';
|
|
604
|
+
/** Images to display (2-4) */
|
|
605
|
+
images: {
|
|
606
|
+
src: string;
|
|
607
|
+
alt: string;
|
|
608
|
+
credit?: string;
|
|
609
|
+
license?: string;
|
|
610
|
+
}[];
|
|
611
|
+
/** Optional caption below the grid */
|
|
612
|
+
caption?: string;
|
|
613
|
+
/** Ambient motion effect for the largest image */
|
|
614
|
+
ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Definition card - dictionary-style term with definition.
|
|
618
|
+
* Large term at top, definition below, optional origin note.
|
|
619
|
+
*/
|
|
620
|
+
interface DefinitionCardInput extends BaseTemplateBlock {
|
|
621
|
+
template: 'definitionCard';
|
|
622
|
+
/** The term being defined */
|
|
623
|
+
term: string;
|
|
624
|
+
/** The definition text */
|
|
625
|
+
definition: string;
|
|
626
|
+
/** Optional etymology or origin note */
|
|
627
|
+
origin?: string;
|
|
628
|
+
/** Color scheme for term accent */
|
|
629
|
+
colorScheme?: ColorScheme;
|
|
630
|
+
/** Optional accent image */
|
|
631
|
+
accentImage?: AccentImage;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Comparison bar - two horizontal bars showing relative numeric values.
|
|
635
|
+
* Good for side-by-side numeric comparisons with visual weight.
|
|
636
|
+
*/
|
|
637
|
+
interface ComparisonBarInput extends BaseTemplateBlock {
|
|
638
|
+
template: 'comparisonBar';
|
|
639
|
+
/** Left bar label */
|
|
640
|
+
leftLabel: string;
|
|
641
|
+
/** Left bar numeric value */
|
|
642
|
+
leftValue: number;
|
|
643
|
+
/** Right bar label */
|
|
644
|
+
rightLabel: string;
|
|
645
|
+
/** Right bar numeric value */
|
|
646
|
+
rightValue: number;
|
|
647
|
+
/** Unit label (e.g., "km", "people") */
|
|
648
|
+
unit?: string;
|
|
649
|
+
/** Color scheme */
|
|
650
|
+
colorScheme?: ColorScheme;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Pull quote - quote text over a full-bleed background image with dark overlay.
|
|
654
|
+
* Cinematic alternative to quoteBlock when a high-quality image is available.
|
|
655
|
+
*/
|
|
656
|
+
interface PullQuoteInput extends BaseTemplateBlock {
|
|
657
|
+
template: 'pullQuote';
|
|
658
|
+
/** Quote text */
|
|
659
|
+
text: string;
|
|
660
|
+
/** Optional attribution */
|
|
661
|
+
attribution?: string;
|
|
662
|
+
/** Background image (fills entire viewport) */
|
|
663
|
+
backgroundImage: {
|
|
664
|
+
src: string;
|
|
665
|
+
alt: string;
|
|
666
|
+
credit?: string;
|
|
667
|
+
license?: string;
|
|
668
|
+
};
|
|
669
|
+
/** Ambient motion for background image */
|
|
670
|
+
ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Video with caption - background video clip with text overlay.
|
|
674
|
+
* Plays a short clip from a source video, always muted (narration is the audio track).
|
|
675
|
+
* Mirrors ImageWithCaptionInput but with video-specific fields.
|
|
676
|
+
*/
|
|
677
|
+
interface VideoWithCaptionInput extends BaseTemplateBlock {
|
|
678
|
+
template: 'videoWithCaption';
|
|
679
|
+
/** Path to video file */
|
|
680
|
+
videoSrc: string;
|
|
681
|
+
/** Path to poster frame (shown before video loads) */
|
|
682
|
+
posterSrc?: string;
|
|
683
|
+
/** Alt text for accessibility */
|
|
684
|
+
videoAlt: string;
|
|
685
|
+
/** Start time within source video (seconds) */
|
|
686
|
+
clipStart: number;
|
|
687
|
+
/** End time within source video (seconds) */
|
|
688
|
+
clipEnd: number;
|
|
689
|
+
/** Source video total duration (for validation) */
|
|
690
|
+
sourceDuration?: number;
|
|
691
|
+
/** Caption text overlay */
|
|
692
|
+
caption?: string;
|
|
693
|
+
/** Caption position */
|
|
694
|
+
captionPosition?: 'bottom' | 'top' | 'center';
|
|
695
|
+
/** Video credit / artist name */
|
|
696
|
+
videoCredit?: string;
|
|
697
|
+
/** License identifier */
|
|
698
|
+
videoLicense?: string;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Video pull quote - quote text over a video clip background with dark overlay.
|
|
702
|
+
* Cinematic alternative to pullQuote when a video clip is available.
|
|
703
|
+
*/
|
|
704
|
+
interface VideoPullQuoteInput extends BaseTemplateBlock {
|
|
705
|
+
template: 'videoPullQuote';
|
|
706
|
+
/** Quote text */
|
|
707
|
+
text: string;
|
|
708
|
+
/** Optional attribution */
|
|
709
|
+
attribution?: string;
|
|
710
|
+
/** Background video clip */
|
|
711
|
+
backgroundVideo: {
|
|
712
|
+
src: string;
|
|
713
|
+
posterSrc?: string;
|
|
714
|
+
alt: string;
|
|
715
|
+
clipStart: number;
|
|
716
|
+
clipEnd: number;
|
|
717
|
+
credit?: string;
|
|
718
|
+
license?: string;
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Union of all template block types.
|
|
723
|
+
*/
|
|
724
|
+
type TemplateBlock = TitleBlockInput | SectionHeaderInput | StatHighlightInput | QuoteBlockInput | FactCardInput | TwoColumnInput | DateEventInput | ImageWithCaptionInput | MapBlockInput | FullBleedQuoteInput | ListBlockInput | PhotoGridInput | DefinitionCardInput | ComparisonBarInput | PullQuoteInput | VideoWithCaptionInput | VideoPullQuoteInput;
|
|
725
|
+
/**
|
|
726
|
+
* A block can be either a raw Block or a TemplateBlock.
|
|
727
|
+
*/
|
|
728
|
+
type DocBlock = Block | TemplateBlock;
|
|
729
|
+
/**
|
|
730
|
+
* Check if a block is a template block.
|
|
731
|
+
*/
|
|
732
|
+
declare function isTemplateBlock(block: DocBlock): block is TemplateBlock;
|
|
733
|
+
/**
|
|
734
|
+
* Template context passed to template functions.
|
|
735
|
+
* Includes viewport information for multi-aspect ratio support.
|
|
736
|
+
*/
|
|
737
|
+
interface TemplateContext {
|
|
738
|
+
/** Full theme (colors, typography, style, renderStyle, colorSchemes) */
|
|
739
|
+
theme: Theme;
|
|
740
|
+
/** Block index in the doc */
|
|
741
|
+
blockIndex: number;
|
|
742
|
+
/** Total number of blocks */
|
|
743
|
+
totalBlocks: number;
|
|
744
|
+
/** Target viewport configuration */
|
|
745
|
+
viewport: ViewportConfig;
|
|
746
|
+
/** Computed font scale factor for this viewport */
|
|
747
|
+
fontScale: number;
|
|
748
|
+
/** Viewport orientation (landscape, portrait, square) */
|
|
749
|
+
orientation: ViewportOrientation;
|
|
750
|
+
/** Layout hints for this orientation */
|
|
751
|
+
layout: LayoutHints;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Create a template context with viewport-derived values.
|
|
755
|
+
*/
|
|
756
|
+
declare function createTemplateContext(theme: Theme, blockIndex: number, totalBlocks: number, viewport?: ViewportConfig): TemplateContext;
|
|
757
|
+
/**
|
|
758
|
+
* Calculate a scaled font size for the given context.
|
|
759
|
+
* Use this in templates to ensure text scales appropriately.
|
|
760
|
+
*
|
|
761
|
+
* @param basePx - Base font size in pixels (designed for 1920x1080)
|
|
762
|
+
* @param context - Template context with viewport info
|
|
763
|
+
* @param isTitle - Whether this is title text (larger scale) or body text
|
|
764
|
+
*/
|
|
765
|
+
declare function scaledFontSize(basePx: number, context: TemplateContext, isTitle?: boolean): number;
|
|
766
|
+
/**
|
|
767
|
+
* Template function signature.
|
|
768
|
+
* Takes template input and returns layers array.
|
|
769
|
+
*/
|
|
770
|
+
type TemplateFunction<T extends TemplateBlock> = (input: T, context: TemplateContext) => Layer[];
|
|
771
|
+
/**
|
|
772
|
+
* Registry of all template functions.
|
|
773
|
+
*/
|
|
774
|
+
type TemplateRegistry = {
|
|
775
|
+
[K in TemplateBlock['template']]: TemplateFunction<Extract<TemplateBlock, {
|
|
776
|
+
template: K;
|
|
777
|
+
}>>;
|
|
778
|
+
};
|
|
779
|
+
/**
|
|
780
|
+
* Configuration for persistent doc-wide layers.
|
|
781
|
+
* Bottom layers render behind all block content, top layers on top.
|
|
782
|
+
*/
|
|
783
|
+
interface PersistentLayerConfig {
|
|
784
|
+
/** Layers rendered behind all block content */
|
|
785
|
+
bottomLayers?: PersistentLayer[];
|
|
786
|
+
/** Layers rendered on top of all block content */
|
|
787
|
+
topLayers?: PersistentLayer[];
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* A persistent layer - either a template or raw Layer.
|
|
791
|
+
*/
|
|
792
|
+
type PersistentLayer = PersistentLayerTemplate | Layer;
|
|
793
|
+
/**
|
|
794
|
+
* Check if a persistent layer is a template.
|
|
795
|
+
*/
|
|
796
|
+
declare function isPersistentLayerTemplate(layer: PersistentLayer): layer is PersistentLayerTemplate;
|
|
797
|
+
/**
|
|
798
|
+
* Template-based persistent layer for easy configuration.
|
|
799
|
+
*/
|
|
800
|
+
interface PersistentLayerTemplate {
|
|
801
|
+
template: PersistentLayerTemplateType;
|
|
802
|
+
config: PersistentLayerTemplateConfig;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Available persistent layer template types.
|
|
806
|
+
*/
|
|
807
|
+
type PersistentLayerTemplateType = 'solidBackground' | 'gradientBackground' | 'imageBackground' | 'patternBackground' | 'titleCaption' | 'cornerBranding' | 'progressIndicator';
|
|
808
|
+
/**
|
|
809
|
+
* Union of all persistent layer template configs.
|
|
810
|
+
*/
|
|
811
|
+
type PersistentLayerTemplateConfig = SolidBackgroundConfig | GradientBackgroundConfig | ImageBackgroundConfig | PatternBackgroundConfig | TitleCaptionConfig | CornerBrandingConfig | ProgressIndicatorConfig;
|
|
812
|
+
/**
|
|
813
|
+
* Solid color background.
|
|
814
|
+
*/
|
|
815
|
+
interface SolidBackgroundConfig {
|
|
816
|
+
type: 'solidBackground';
|
|
817
|
+
/** CSS color value */
|
|
818
|
+
color: string;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Gradient background (CSS gradient or preset).
|
|
822
|
+
*/
|
|
823
|
+
interface GradientBackgroundConfig {
|
|
824
|
+
type: 'gradientBackground';
|
|
825
|
+
/** Custom CSS gradient string */
|
|
826
|
+
gradient?: string;
|
|
827
|
+
/** Preset gradient name */
|
|
828
|
+
preset?: 'dark-vignette' | 'radial-dark' | 'warm-sunset' | 'cool-blue' | 'earth-tones';
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Image background (blurred/faded hero image).
|
|
832
|
+
*/
|
|
833
|
+
interface ImageBackgroundConfig {
|
|
834
|
+
type: 'imageBackground';
|
|
835
|
+
/** Path to image file */
|
|
836
|
+
src: string;
|
|
837
|
+
/** Blur radius in pixels (0-20) */
|
|
838
|
+
blur?: number;
|
|
839
|
+
/** Opacity (0-1) */
|
|
840
|
+
opacity?: number;
|
|
841
|
+
/** Ambient motion effect */
|
|
842
|
+
ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Pattern background (subtle repeating pattern).
|
|
846
|
+
*/
|
|
847
|
+
interface PatternBackgroundConfig {
|
|
848
|
+
type: 'patternBackground';
|
|
849
|
+
/** Pattern type */
|
|
850
|
+
pattern: 'dots' | 'grid' | 'diagonal' | 'noise';
|
|
851
|
+
/** Pattern color */
|
|
852
|
+
color?: string;
|
|
853
|
+
/** Pattern opacity (0-1) */
|
|
854
|
+
opacity?: number;
|
|
855
|
+
/** Pattern scale multiplier */
|
|
856
|
+
scale?: number;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Title caption overlay (article title in corner).
|
|
860
|
+
*/
|
|
861
|
+
interface TitleCaptionConfig {
|
|
862
|
+
type: 'titleCaption';
|
|
863
|
+
/** Title text */
|
|
864
|
+
title: string;
|
|
865
|
+
/** Subtitle text (e.g., short URL like "qual..la/slug") */
|
|
866
|
+
subtitle?: string;
|
|
867
|
+
/** Position on screen */
|
|
868
|
+
position: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
|
|
869
|
+
/** Font size in pixels (default: 18) */
|
|
870
|
+
fontSize?: number;
|
|
871
|
+
/** Show thumbnail alongside title */
|
|
872
|
+
showThumbnail?: boolean;
|
|
873
|
+
/** Thumbnail image path */
|
|
874
|
+
thumbnailSrc?: string;
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Corner branding overlay (logo or text badge).
|
|
878
|
+
*/
|
|
879
|
+
interface CornerBrandingConfig {
|
|
880
|
+
type: 'cornerBranding';
|
|
881
|
+
/** Text content or image path */
|
|
882
|
+
content: string;
|
|
883
|
+
/** Position on screen */
|
|
884
|
+
position: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
|
|
885
|
+
/** Whether content is an image path (vs text) */
|
|
886
|
+
isImage?: boolean;
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Progress indicator overlay (bar or dots showing position).
|
|
890
|
+
*/
|
|
891
|
+
interface ProgressIndicatorConfig {
|
|
892
|
+
type: 'progressIndicator';
|
|
893
|
+
/** Indicator style */
|
|
894
|
+
style: 'bar' | 'dots' | 'fraction';
|
|
895
|
+
/** Position on screen */
|
|
896
|
+
position: 'bottom' | 'top';
|
|
897
|
+
/** Indicator color */
|
|
898
|
+
color?: string;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Doc Schema
|
|
903
|
+
*
|
|
904
|
+
* Defines the JSON format for AI-scriptable visual docs that accompany
|
|
905
|
+
* audio narration. A Doc describes a sequence of animated blocks
|
|
906
|
+
* with text overlays, images, and transitions synchronized to audio.
|
|
907
|
+
*
|
|
908
|
+
* The format is designed to be:
|
|
909
|
+
* 1. AI-generatable - Claude can create scripts from article content
|
|
910
|
+
* 2. Browser-renderable - SVG + CSS animations (ES2015 compatible)
|
|
911
|
+
* 3. Video-exportable - Frame-by-frame capture via Playwright
|
|
912
|
+
*
|
|
913
|
+
* Timing: Blocks are timed to match narration audio segments. Each article
|
|
914
|
+
* has 3-6 MP3 files (intro + sections), and blocks are grouped by segment.
|
|
915
|
+
*/
|
|
916
|
+
/**
|
|
917
|
+
* Configuration for the Start/resting block shown before playback begins.
|
|
918
|
+
*/
|
|
919
|
+
interface StartBlockConfig {
|
|
920
|
+
/** Hero image path (relative to article media) */
|
|
921
|
+
heroSrc: string;
|
|
922
|
+
/** Alt text for the hero image */
|
|
923
|
+
heroAlt?: string;
|
|
924
|
+
/** Title to display over the hero */
|
|
925
|
+
title: string;
|
|
926
|
+
/** Optional subtitle */
|
|
927
|
+
subtitle?: string;
|
|
928
|
+
/** Ambient motion for the hero image */
|
|
929
|
+
ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';
|
|
930
|
+
/** Photo credit / artist name for the hero image */
|
|
931
|
+
heroCredit?: string;
|
|
932
|
+
/** License identifier for the hero image */
|
|
933
|
+
heroLicense?: string;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* A complete visual doc for an article.
|
|
937
|
+
*/
|
|
938
|
+
interface Doc {
|
|
939
|
+
/** Article ID this doc belongs to */
|
|
940
|
+
articleId: string;
|
|
941
|
+
/** Total duration in seconds (sum of all audio segments) */
|
|
942
|
+
duration: number;
|
|
943
|
+
/** Ordered list of blocks */
|
|
944
|
+
blocks: Block[];
|
|
945
|
+
/** Audio track configuration */
|
|
946
|
+
audio: AudioTrack;
|
|
947
|
+
/** Caption track for closed captions (generated by postprocess-doc) */
|
|
948
|
+
captions?: CaptionTrack;
|
|
949
|
+
/**
|
|
950
|
+
* Start/resting block shown before playback begins.
|
|
951
|
+
* Displays hero image with title - acts as a "poster" for the doc.
|
|
952
|
+
* The player shows this until the user starts playback.
|
|
953
|
+
*/
|
|
954
|
+
startBlock?: StartBlockConfig;
|
|
955
|
+
/**
|
|
956
|
+
* Persistent layers rendered on every block (behind and/or on top of content).
|
|
957
|
+
* Used for consistent branding overlays, background gradients, etc.
|
|
958
|
+
*/
|
|
959
|
+
persistentLayers?: PersistentLayerConfig;
|
|
960
|
+
/**
|
|
961
|
+
* Optional theme identifier. Resolved at render time via `resolveTheme()`.
|
|
962
|
+
* When omitted, the renderer uses the default theme ('documentary').
|
|
963
|
+
*/
|
|
964
|
+
themeId?: string;
|
|
965
|
+
/** Optional metadata */
|
|
966
|
+
meta?: {
|
|
967
|
+
generatedAt?: string;
|
|
968
|
+
generatedBy?: string;
|
|
969
|
+
version?: number;
|
|
970
|
+
};
|
|
971
|
+
/**
|
|
972
|
+
* YAML frontmatter from the source markdown.
|
|
973
|
+
* Carries rendering hints like `document-render-as` and custom metadata.
|
|
974
|
+
*/
|
|
975
|
+
frontmatter?: Record<string, unknown>;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* A single block in the doc.
|
|
979
|
+
*
|
|
980
|
+
* Blocks can be flat (audio-synced timeline) or nested (markdown-driven hierarchy).
|
|
981
|
+
* When derived from markdown, a block corresponds to a heading section:
|
|
982
|
+
* - `sourceHeading` references the original MarkdownHeading node
|
|
983
|
+
* - `contents` holds the body markdown between this heading and the next
|
|
984
|
+
* - `children` holds sub-heading blocks (e.g., H2s under an H1)
|
|
985
|
+
*/
|
|
986
|
+
interface Block {
|
|
987
|
+
/** Unique identifier for this block */
|
|
988
|
+
id: string;
|
|
989
|
+
/** When this block appears (seconds from start) */
|
|
990
|
+
startTime: number;
|
|
991
|
+
/** How long this block is visible (seconds) */
|
|
992
|
+
duration: number;
|
|
993
|
+
/** Which audio segment this block belongs to (0-indexed) */
|
|
994
|
+
audioSegment: number;
|
|
995
|
+
/**
|
|
996
|
+
* Pre-computed visual layers, rendered back-to-front.
|
|
997
|
+
*
|
|
998
|
+
* Optional — template-derived blocks typically omit this and compute
|
|
999
|
+
* layers on demand via `getLayers()` from `@bendyline/squisq/doc`.
|
|
1000
|
+
* Raw blocks (e.g., hand-crafted by AI) may provide layers directly.
|
|
1001
|
+
*/
|
|
1002
|
+
layers?: Layer[];
|
|
1003
|
+
/** Entry transition from previous block */
|
|
1004
|
+
transition?: Transition;
|
|
1005
|
+
/** Template name that generated this block (for debugging) */
|
|
1006
|
+
template?: string;
|
|
1007
|
+
/**
|
|
1008
|
+
* Nested sub-blocks (heading hierarchy).
|
|
1009
|
+
* When a doc is derived from markdown, sub-headings within this
|
|
1010
|
+
* heading's section become child blocks. For example, H2 blocks
|
|
1011
|
+
* under an H1 block appear here.
|
|
1012
|
+
*/
|
|
1013
|
+
children?: Block[];
|
|
1014
|
+
/**
|
|
1015
|
+
* Markdown body content for this block's section.
|
|
1016
|
+
* Contains the block-level markdown nodes (paragraphs, lists, code,
|
|
1017
|
+
* blockquotes, etc.) that appear between this heading and the next
|
|
1018
|
+
* heading or sub-heading. Empty for pure structural blocks.
|
|
1019
|
+
*/
|
|
1020
|
+
contents?: MarkdownBlockNode[];
|
|
1021
|
+
/**
|
|
1022
|
+
* Reference to the MarkdownHeading node that created this block.
|
|
1023
|
+
* Present when the block was derived from markdown via markdownToDoc().
|
|
1024
|
+
* Enables round-tripping back to markdown and provides heading depth.
|
|
1025
|
+
* Absent for the preamble block (content before the first heading).
|
|
1026
|
+
*/
|
|
1027
|
+
sourceHeading?: MarkdownHeading;
|
|
1028
|
+
/**
|
|
1029
|
+
* Template overrides extracted from markdown directives.
|
|
1030
|
+
* For example, `### Data Section {template=chart colorScheme=blue}`
|
|
1031
|
+
* would produce `{ template: 'chart', colorScheme: 'blue' }`.
|
|
1032
|
+
* Allows per-block customization of template selection and parameters.
|
|
1033
|
+
*/
|
|
1034
|
+
templateOverrides?: Record<string, string>;
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* A visual element within a block.
|
|
1038
|
+
* Layers are composited back-to-front (first layer is background).
|
|
1039
|
+
*/
|
|
1040
|
+
type Layer = ImageLayer | TextLayer | ShapeLayer | MapLayer | VideoLayer;
|
|
1041
|
+
interface BaseLayer {
|
|
1042
|
+
/** Unique identifier for this layer */
|
|
1043
|
+
id: string;
|
|
1044
|
+
/** Position and size */
|
|
1045
|
+
position: Position;
|
|
1046
|
+
/** Animation to apply */
|
|
1047
|
+
animation?: Animation;
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Image layer - displays an image with optional Ken Burns effect.
|
|
1051
|
+
*/
|
|
1052
|
+
interface ImageLayer extends BaseLayer {
|
|
1053
|
+
type: 'image';
|
|
1054
|
+
content: {
|
|
1055
|
+
/** Path to image file (relative to article media dir) */
|
|
1056
|
+
src: string;
|
|
1057
|
+
/** Alt text for accessibility */
|
|
1058
|
+
alt: string;
|
|
1059
|
+
/** How to fit image in bounds */
|
|
1060
|
+
fit?: 'cover' | 'contain' | 'fill';
|
|
1061
|
+
/** Photo credit / artist name */
|
|
1062
|
+
credit?: string;
|
|
1063
|
+
/** License identifier (e.g., 'CC BY-SA 4.0') */
|
|
1064
|
+
license?: string;
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Text layer - displays text with styling.
|
|
1069
|
+
*/
|
|
1070
|
+
interface TextLayer extends BaseLayer {
|
|
1071
|
+
type: 'text';
|
|
1072
|
+
content: {
|
|
1073
|
+
/** Text to display (supports \n for line breaks) */
|
|
1074
|
+
text: string;
|
|
1075
|
+
/** Text styling */
|
|
1076
|
+
style: TextStyle;
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Shape layer - simple geometric shapes for visual accents.
|
|
1081
|
+
*/
|
|
1082
|
+
interface ShapeLayer extends BaseLayer {
|
|
1083
|
+
type: 'shape';
|
|
1084
|
+
content: {
|
|
1085
|
+
/** Shape type */
|
|
1086
|
+
shape: 'rect' | 'circle' | 'line';
|
|
1087
|
+
/** Fill color (CSS color or 'none') */
|
|
1088
|
+
fill?: string;
|
|
1089
|
+
/** Stroke color */
|
|
1090
|
+
stroke?: string;
|
|
1091
|
+
/** Stroke width in pixels */
|
|
1092
|
+
strokeWidth?: number;
|
|
1093
|
+
/** Corner radius for rect */
|
|
1094
|
+
borderRadius?: number;
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Map layer - displays a geographic map with optional markers.
|
|
1099
|
+
*
|
|
1100
|
+
* Maps are rendered as composite tile images from free tile providers.
|
|
1101
|
+
* Supported styles use open-source/free tiles requiring attribution.
|
|
1102
|
+
*/
|
|
1103
|
+
interface MapLayer extends BaseLayer {
|
|
1104
|
+
type: 'map';
|
|
1105
|
+
content: {
|
|
1106
|
+
/** Center coordinates for the map */
|
|
1107
|
+
center: {
|
|
1108
|
+
lat: number;
|
|
1109
|
+
lng: number;
|
|
1110
|
+
};
|
|
1111
|
+
/** Zoom level (0-18, typically 8-14 for doc blocks) */
|
|
1112
|
+
zoom: number;
|
|
1113
|
+
/** Map tile style */
|
|
1114
|
+
style: MapTileStyle;
|
|
1115
|
+
/** Optional markers to display on the map */
|
|
1116
|
+
markers?: MapMarker[];
|
|
1117
|
+
/** Pre-rendered static image path (for video export reliability) */
|
|
1118
|
+
staticSrc?: string;
|
|
1119
|
+
/** Show attribution text (default: true) */
|
|
1120
|
+
showAttribution?: boolean;
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Video layer - displays a short video clip, always muted (narration provides audio).
|
|
1125
|
+
*
|
|
1126
|
+
* Rendered via an HTML5 <video> element inside <foreignObject>, similar to how
|
|
1127
|
+
* ImageLayer uses <img> for Ken Burns effects. clipStart/clipEnd define the window
|
|
1128
|
+
* within the source video to play. During Playwright frame capture (seekTo mode),
|
|
1129
|
+
* the video is seeked programmatically rather than playing in real time.
|
|
1130
|
+
*/
|
|
1131
|
+
interface VideoLayer extends BaseLayer {
|
|
1132
|
+
type: 'video';
|
|
1133
|
+
content: {
|
|
1134
|
+
/** Path to video file (relative to article media dir) */
|
|
1135
|
+
src: string;
|
|
1136
|
+
/** Path to poster frame (shown before video loads / during seek) */
|
|
1137
|
+
posterSrc?: string;
|
|
1138
|
+
/** Alt text for accessibility */
|
|
1139
|
+
alt: string;
|
|
1140
|
+
/** How to fit video in bounds */
|
|
1141
|
+
fit?: 'cover' | 'contain' | 'fill';
|
|
1142
|
+
/** Start time within the source video (seconds) */
|
|
1143
|
+
clipStart: number;
|
|
1144
|
+
/** End time within the source video (seconds) */
|
|
1145
|
+
clipEnd: number;
|
|
1146
|
+
/** Total source video duration (for validation) */
|
|
1147
|
+
sourceDuration?: number;
|
|
1148
|
+
/** Video credit / artist name */
|
|
1149
|
+
credit?: string;
|
|
1150
|
+
/** License identifier (e.g., 'CC BY-SA 4.0') */
|
|
1151
|
+
license?: string;
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Available map tile styles from free/open-source providers.
|
|
1156
|
+
*/
|
|
1157
|
+
type MapTileStyle = 'terrain' | 'satellite' | 'road' | 'toner' | 'watercolor';
|
|
1158
|
+
/**
|
|
1159
|
+
* A marker to display on the map.
|
|
1160
|
+
*/
|
|
1161
|
+
interface MapMarker {
|
|
1162
|
+
/** Marker latitude */
|
|
1163
|
+
lat: number;
|
|
1164
|
+
/** Marker longitude */
|
|
1165
|
+
lng: number;
|
|
1166
|
+
/** Optional label text */
|
|
1167
|
+
label?: string;
|
|
1168
|
+
/** Marker color (CSS color, default: #ef4444) */
|
|
1169
|
+
color?: string;
|
|
1170
|
+
/** Marker icon type */
|
|
1171
|
+
icon?: 'pin' | 'circle' | 'star';
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Position and dimensions for a layer.
|
|
1175
|
+
* Values can be pixels (number) or percentages (string like "50%").
|
|
1176
|
+
*/
|
|
1177
|
+
interface Position {
|
|
1178
|
+
/** X position (pixels or percentage) */
|
|
1179
|
+
x: number | string;
|
|
1180
|
+
/** Y position (pixels or percentage) */
|
|
1181
|
+
y: number | string;
|
|
1182
|
+
/** Width (pixels or percentage) */
|
|
1183
|
+
width?: number | string;
|
|
1184
|
+
/** Height (pixels or percentage) */
|
|
1185
|
+
height?: number | string;
|
|
1186
|
+
/** Anchor point for positioning */
|
|
1187
|
+
anchor?: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Text styling options.
|
|
1191
|
+
*/
|
|
1192
|
+
interface TextStyle {
|
|
1193
|
+
/** Font size in pixels */
|
|
1194
|
+
fontSize: number;
|
|
1195
|
+
/** Font family (defaults to system sans-serif) */
|
|
1196
|
+
fontFamily?: string;
|
|
1197
|
+
/** Font weight */
|
|
1198
|
+
fontWeight?: 'normal' | 'bold';
|
|
1199
|
+
/** Text color (CSS color) */
|
|
1200
|
+
color: string;
|
|
1201
|
+
/** Text alignment */
|
|
1202
|
+
textAlign?: 'left' | 'center' | 'right';
|
|
1203
|
+
/** Line height multiplier */
|
|
1204
|
+
lineHeight?: number;
|
|
1205
|
+
/** Add drop shadow for readability over images */
|
|
1206
|
+
shadow?: boolean;
|
|
1207
|
+
/** Background color for text box */
|
|
1208
|
+
background?: string;
|
|
1209
|
+
/** Padding around text (pixels) */
|
|
1210
|
+
padding?: number;
|
|
1211
|
+
/** Maximum number of lines before truncation (adds "..." to last line) */
|
|
1212
|
+
maxLines?: number;
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Animation to apply to a layer.
|
|
1216
|
+
*/
|
|
1217
|
+
interface Animation {
|
|
1218
|
+
/** Animation type */
|
|
1219
|
+
type: AnimationType;
|
|
1220
|
+
/** Duration in seconds (defaults to layer's block duration) */
|
|
1221
|
+
duration?: number;
|
|
1222
|
+
/** Delay before animation starts (seconds) */
|
|
1223
|
+
delay?: number;
|
|
1224
|
+
/** CSS easing function */
|
|
1225
|
+
easing?: string;
|
|
1226
|
+
/** For Ken Burns: zoom direction */
|
|
1227
|
+
direction?: 'in' | 'out';
|
|
1228
|
+
/** For pan animations: pan direction */
|
|
1229
|
+
panDirection?: 'left' | 'right' | 'up' | 'down';
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Available animation types.
|
|
1233
|
+
*/
|
|
1234
|
+
type AnimationType = 'none' | 'fadeIn' | 'fadeOut' | 'slowZoom' | 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight' | 'typewriter';
|
|
1235
|
+
/**
|
|
1236
|
+
* Transition between blocks.
|
|
1237
|
+
*/
|
|
1238
|
+
interface Transition {
|
|
1239
|
+
/** Transition type */
|
|
1240
|
+
type: TransitionType;
|
|
1241
|
+
/** Duration in seconds */
|
|
1242
|
+
duration: number;
|
|
1243
|
+
}
|
|
1244
|
+
type TransitionType = 'cut' | 'fade' | 'dissolve' | 'slideLeft' | 'slideRight';
|
|
1245
|
+
/**
|
|
1246
|
+
* Audio track configuration.
|
|
1247
|
+
* Articles have multiple MP3 segments (intro + sections).
|
|
1248
|
+
*/
|
|
1249
|
+
interface AudioTrack {
|
|
1250
|
+
/** Audio segments in playback order */
|
|
1251
|
+
segments: AudioSegment[];
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* A single caption phrase to display during playback.
|
|
1255
|
+
*/
|
|
1256
|
+
interface CaptionPhrase {
|
|
1257
|
+
/** The text to display */
|
|
1258
|
+
text: string;
|
|
1259
|
+
/** Start time in seconds (relative to doc start) */
|
|
1260
|
+
startTime: number;
|
|
1261
|
+
/** End time in seconds */
|
|
1262
|
+
endTime: number;
|
|
1263
|
+
/** Which audio segment this caption belongs to (0-indexed) */
|
|
1264
|
+
audioSegment: number;
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Caption track for a doc.
|
|
1268
|
+
*/
|
|
1269
|
+
interface CaptionTrack {
|
|
1270
|
+
/** Caption phrases in chronological order */
|
|
1271
|
+
phrases: CaptionPhrase[];
|
|
1272
|
+
/** When the captions were generated */
|
|
1273
|
+
generatedAt: string;
|
|
1274
|
+
/** Algorithm version for regeneration detection */
|
|
1275
|
+
version: number;
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* A single audio segment (one MP3 file).
|
|
1279
|
+
*/
|
|
1280
|
+
interface AudioSegment {
|
|
1281
|
+
/** Path to MP3 file (relative to article media dir) */
|
|
1282
|
+
src: string;
|
|
1283
|
+
/** Segment name (e.g., "intro", "history") */
|
|
1284
|
+
name: string;
|
|
1285
|
+
/** Duration in seconds */
|
|
1286
|
+
duration: number;
|
|
1287
|
+
/** Start time in overall timeline (calculated) */
|
|
1288
|
+
startTime: number;
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Calculate total duration from audio segments.
|
|
1292
|
+
*/
|
|
1293
|
+
declare function calculateDuration(audio: AudioTrack): number;
|
|
1294
|
+
/**
|
|
1295
|
+
* Find which audio segment is playing at a given time.
|
|
1296
|
+
*/
|
|
1297
|
+
declare function getSegmentAtTime(audio: AudioTrack, time: number): number;
|
|
1298
|
+
/**
|
|
1299
|
+
* Find which block should be visible at a given time.
|
|
1300
|
+
*/
|
|
1301
|
+
declare function getBlockAtTime(blocks: Block[], time: number): Block | null;
|
|
1302
|
+
/**
|
|
1303
|
+
* Find the caption phrase that should be displayed at a given time.
|
|
1304
|
+
*/
|
|
1305
|
+
declare function getCaptionAtTime(captions: CaptionTrack | undefined, time: number): CaptionPhrase | null;
|
|
1306
|
+
/**
|
|
1307
|
+
* Validate a Doc for completeness.
|
|
1308
|
+
*/
|
|
1309
|
+
declare function validateDoc(script: Doc): string[];
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* Built-in Theme Library
|
|
1313
|
+
*
|
|
1314
|
+
* Ships 8 curated themes covering documentary, editorial, minimal, and
|
|
1315
|
+
* cinematic styles. Each theme is fully JSON-serializable.
|
|
1316
|
+
*
|
|
1317
|
+
* Usage:
|
|
1318
|
+
* ```ts
|
|
1319
|
+
* import { THEMES, resolveTheme } from '@bendyline/squisq/schemas';
|
|
1320
|
+
*
|
|
1321
|
+
* const theme = resolveTheme('minimalist'); // look up by id
|
|
1322
|
+
* const theme = THEMES.documentary; // direct access
|
|
1323
|
+
* ```
|
|
1324
|
+
*/
|
|
1325
|
+
|
|
1326
|
+
/**
|
|
1327
|
+
* All built-in themes, keyed by id.
|
|
1328
|
+
*/
|
|
1329
|
+
declare const THEMES: Record<string, Theme>;
|
|
1330
|
+
/** The default theme id. */
|
|
1331
|
+
declare const DEFAULT_THEME_ID = "documentary";
|
|
1332
|
+
/** The default theme (documentary — migrated from old DEFAULT_THEME). */
|
|
1333
|
+
declare const DEFAULT_THEME: Theme;
|
|
1334
|
+
/**
|
|
1335
|
+
* Resolve a theme by id. Returns the default theme when `id` is
|
|
1336
|
+
* undefined or not found in the library.
|
|
1337
|
+
*/
|
|
1338
|
+
declare function resolveTheme(id: string | undefined): Theme;
|
|
1339
|
+
/**
|
|
1340
|
+
* Get all available theme ids.
|
|
1341
|
+
*/
|
|
1342
|
+
declare function getAvailableThemes(): string[];
|
|
1343
|
+
/**
|
|
1344
|
+
* Get a summary of all themes (id + name + description) for theme pickers.
|
|
1345
|
+
*/
|
|
1346
|
+
declare function getThemeSummaries(): Array<{
|
|
1347
|
+
id: string;
|
|
1348
|
+
name: string;
|
|
1349
|
+
description?: string;
|
|
1350
|
+
}>;
|
|
1351
|
+
|
|
1352
|
+
export { type TemplateContext as $, type AccentImage as A, type Block as B, type CaptionPhrase as C, DEFAULT_DOC_FONT as D, type PersistentLayerConfig as E, type FactCardInput as F, type GradientBackgroundConfig as G, type PersistentLayerTemplate as H, type ImageBackgroundConfig as I, type PersistentLayerTemplateConfig as J, type PersistentLayerTemplateType as K, type Layer as L, type MapBlockInput as M, type PhotoGridInput as N, type Position as O, type PatternBackgroundConfig as P, type ProgressIndicatorConfig as Q, type PullQuoteInput as R, type QuoteBlockInput as S, type RenderStyle as T, type SectionHeaderInput as U, type ShapeLayer as V, type SolidBackgroundConfig as W, type StartBlockConfig as X, type StatHighlightInput as Y, THEMES as Z, type TemplateBlock as _, type AccentPosition as a, type TemplateFunction as a0, type TemplateRegistry as a1, type TextLayer as a2, type TextStyle as a3, type Theme as a4, type ThemeColorPalette as a5, type ThemeColorScheme as a6, type ThemeStyle as a7, type ThemeTypography as a8, type TitleBlockInput as a9, isPersistentLayerTemplate as aA, isTemplateBlock as aB, scaledFontSize$1 as aC, resolveTheme as aD, scaledFontSize as aE, validateDoc as aF, type TitleCaptionConfig as aa, type Transition as ab, type TransitionType as ac, type TwoColumnInput as ad, VIEWPORT_PRESETS as ae, type VideoLayer as af, type VideoPullQuoteInput as ag, type VideoWithCaptionInput as ah, type ViewportConfig as ai, type ViewportOrientation as aj, type ViewportPreset as ak, calculateDuration as al, calculateFontScale as am, createTemplateContext as an, createTheme as ao, getAspectRatioString as ap, getAvailableThemes as aq, getBlockAtTime as ar, getCaptionAtTime as as, getLayoutHints as at, getSafeTextBounds as au, getSegmentAtTime as av, getThemeSummaries as aw, getTwoColumnPositions as ax, getViewport as ay, getViewportOrientation as az, type Animation as b, type AnimationType as c, type AudioSegment as d, type AudioTrack as e, type CaptionTrack as f, type ColorScheme as g, type ComparisonBarInput as h, type CornerBrandingConfig as i, DEFAULT_THEME as j, DEFAULT_THEME_ID as k, DEFAULT_TITLE_FONT as l, type DateEventInput as m, type DeepPartial as n, type DefinitionCardInput as o, type Doc as p, type DocBlock as q, type FullBleedQuoteInput as r, type ImageLayer as s, type ImageWithCaptionInput as t, type LayoutHints as u, type ListBlockInput as v, type MapLayer as w, type MapMarker as x, type MapTileStyle as y, type PersistentLayer as z };
|