@bendyline/squisq 1.4.1 → 1.5.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.
Files changed (254) hide show
  1. package/README.md +14 -1
  2. package/dist/{ContentContainer-DNx460y8.d.ts → ContentContainer-BXUlIf18.d.ts} +6 -2
  3. package/dist/{Doc-BeSeH2iX.d.ts → Doc-BBVGq1_9.d.ts} +922 -273
  4. package/dist/{ImageEditDoc-BTIvw0oq.d.ts → ImageEditDoc-FuyGtt6o.d.ts} +11 -4
  5. package/dist/{chunk-ZTX4PFFT.js → chunk-2OIBZYKP.js} +75 -2
  6. package/dist/chunk-2OIBZYKP.js.map +1 -0
  7. package/dist/chunk-2UQOHYE2.js +45 -0
  8. package/dist/chunk-2UQOHYE2.js.map +1 -0
  9. package/dist/chunk-6ATE2PSM.js +344 -0
  10. package/dist/chunk-6ATE2PSM.js.map +1 -0
  11. package/dist/{chunk-57CVQCFX.js → chunk-A4O7GIWE.js} +74 -23
  12. package/dist/chunk-A4O7GIWE.js.map +1 -0
  13. package/dist/{chunk-5WFRKURX.js → chunk-DLXZMS5K.js} +4 -2
  14. package/dist/chunk-DLXZMS5K.js.map +1 -0
  15. package/dist/chunk-ET53IIEP.js +1 -0
  16. package/dist/chunk-ET53IIEP.js.map +1 -0
  17. package/dist/chunk-HWVFJAAH.js +362 -0
  18. package/dist/chunk-HWVFJAAH.js.map +1 -0
  19. package/dist/{chunk-2TMAWB4Y.js → chunk-IMSCRLLV.js} +1 -1
  20. package/dist/chunk-IMSCRLLV.js.map +1 -0
  21. package/dist/{chunk-B4EEON3N.js → chunk-KKNUBQ6Y.js} +536 -103
  22. package/dist/chunk-KKNUBQ6Y.js.map +1 -0
  23. package/dist/chunk-LDQ2HJIX.js +596 -0
  24. package/dist/chunk-LDQ2HJIX.js.map +1 -0
  25. package/dist/chunk-LH7I6SH7.js +7208 -0
  26. package/dist/chunk-LH7I6SH7.js.map +1 -0
  27. package/dist/chunk-PGGGKYWG.js +1 -0
  28. package/dist/chunk-PGGGKYWG.js.map +1 -0
  29. package/dist/{chunk-7UDSRZKG.js → chunk-Q3ROPT5H.js} +35 -14
  30. package/dist/chunk-Q3ROPT5H.js.map +1 -0
  31. package/dist/chunk-QE7TWGVT.js +178 -0
  32. package/dist/chunk-QE7TWGVT.js.map +1 -0
  33. package/dist/chunk-RUDYOTA6.js +345 -0
  34. package/dist/chunk-RUDYOTA6.js.map +1 -0
  35. package/dist/{chunk-K32VJONL.js → chunk-T5UK6YOB.js} +41 -2
  36. package/dist/chunk-T5UK6YOB.js.map +1 -0
  37. package/dist/{chunk-3K5OG6KI.js → chunk-TEEEILMP.js} +412 -186
  38. package/dist/chunk-TEEEILMP.js.map +1 -0
  39. package/dist/{chunk-FR2RBTKO.js → chunk-TQWLI6S2.js} +93 -27
  40. package/dist/chunk-TQWLI6S2.js.map +1 -0
  41. package/dist/{chunk-WMOI7FN3.js → chunk-UY7KGQ5R.js} +12 -2
  42. package/dist/{chunk-WMOI7FN3.js.map → chunk-UY7KGQ5R.js.map} +1 -1
  43. package/dist/{chunk-3E5F2XMR.js → chunk-VWUFZ6ZG.js} +29 -2
  44. package/dist/chunk-VWUFZ6ZG.js.map +1 -0
  45. package/dist/chunk-ZLQKNOZQ.js +1 -0
  46. package/dist/chunk-ZLQKNOZQ.js.map +1 -0
  47. package/dist/doc/index.d.ts +5 -5
  48. package/dist/doc/index.js +135 -8
  49. package/dist/generate/index.d.ts +3 -74
  50. package/dist/generate/index.js +2 -7
  51. package/dist/icons/index.d.ts +3 -26
  52. package/dist/icons/index.js +13 -0
  53. package/dist/icons/inlineIconMarker.d.ts +1 -0
  54. package/dist/icons/inlineIconMarker.js +15 -0
  55. package/dist/icons/inlineIconMarker.js.map +1 -0
  56. package/dist/imageEdit/index.d.ts +12 -6
  57. package/dist/imageEdit/index.js +4 -1
  58. package/dist/index.d.ts +14 -11
  59. package/dist/index.js +265 -37
  60. package/dist/inlineIconMarker-CiE8Hlvz.d.ts +59 -0
  61. package/dist/jsonForm/index.d.ts +42 -1
  62. package/dist/jsonForm/index.js +9 -1
  63. package/dist/markdown/index.d.ts +228 -3
  64. package/dist/markdown/index.js +42 -7
  65. package/dist/recommend/index.d.ts +1 -1
  66. package/dist/recommend/index.js +3 -2
  67. package/dist/schemas/index.d.ts +28 -8
  68. package/dist/schemas/index.js +36 -8
  69. package/dist/spatial/index.d.ts +9 -4
  70. package/dist/spatial/index.js +1 -1
  71. package/dist/storage/index.d.ts +2 -2
  72. package/dist/storage/index.js +1 -1
  73. package/dist/story/index.d.ts +1021 -41
  74. package/dist/story/index.js +135 -8
  75. package/dist/{themeLibrary-BHjJ0Qx6.d.ts → themeLibrary-CehcJhzz.d.ts} +1 -1
  76. package/dist/timing/index.js +3 -5
  77. package/dist/transform/index.d.ts +48 -10
  78. package/dist/transform/index.js +17 -481
  79. package/dist/transform/index.js.map +1 -1
  80. package/dist/{types-BdZkdJ3z.d.ts → types-DlAZ7MW4.d.ts} +115 -1
  81. package/dist/versions/index.d.ts +1 -1
  82. package/package.json +6 -1
  83. package/src/__tests__/animationUtils.test.ts +40 -0
  84. package/src/__tests__/annotationCoercion.test.ts +243 -0
  85. package/src/__tests__/applyRenderStyle.test.ts +146 -0
  86. package/src/__tests__/atmosphereLayers.test.ts +108 -0
  87. package/src/__tests__/attrTokens.test.ts +162 -0
  88. package/src/__tests__/contentExtractor.test.ts +60 -0
  89. package/src/__tests__/coverBlock.test.ts +46 -1
  90. package/src/__tests__/customTemplateSample.test.ts +144 -0
  91. package/src/__tests__/customTemplatesFrontmatter.test.ts +236 -0
  92. package/src/__tests__/customThemesFrontmatter.test.ts +145 -0
  93. package/src/__tests__/diagramBlock.test.ts +122 -0
  94. package/src/__tests__/diagramLayout.test.ts +134 -0
  95. package/src/__tests__/drawing.test.ts +109 -0
  96. package/src/__tests__/drawingBlock.test.ts +124 -0
  97. package/src/__tests__/drawingLayout.test.ts +171 -0
  98. package/src/__tests__/geohash.test.ts +6 -5
  99. package/src/__tests__/getLayers.test.ts +6 -2
  100. package/src/__tests__/getLayersFallback.test.ts +103 -0
  101. package/src/__tests__/imageEditExportSvg.test.ts +70 -0
  102. package/src/__tests__/imageEditVersions.test.ts +72 -0
  103. package/src/__tests__/inlineIconMarker.test.ts +36 -0
  104. package/src/__tests__/inputDescriptors.test.ts +162 -0
  105. package/src/__tests__/jsonForm.tokens.test.ts +84 -0
  106. package/src/__tests__/layoutBlock.test.ts +83 -0
  107. package/src/__tests__/layoutLayout.test.ts +169 -0
  108. package/src/__tests__/markdown.test.ts +87 -0
  109. package/src/__tests__/markdownSanitize.test.ts +79 -0
  110. package/src/__tests__/markdownToDoc.test.ts +391 -2
  111. package/src/__tests__/mediaAnnotations.test.ts +91 -0
  112. package/src/__tests__/mediaSchedule.test.ts +118 -0
  113. package/src/__tests__/pandocAttrParse.test.ts +187 -0
  114. package/src/__tests__/resolveDocTheme.test.ts +55 -0
  115. package/src/__tests__/rootExports.test.ts +43 -0
  116. package/src/__tests__/shapeGeometry.test.ts +89 -0
  117. package/src/__tests__/shapeVocabulary.test.ts +133 -0
  118. package/src/__tests__/structuredData.test.ts +249 -0
  119. package/src/__tests__/templateAnnotationParse.test.ts +108 -0
  120. package/src/__tests__/templateMetadata.test.ts +31 -0
  121. package/src/__tests__/templates.test.ts +257 -4
  122. package/src/__tests__/themeCompile.test.ts +42 -0
  123. package/src/__tests__/themeValidator.test.ts +6 -0
  124. package/src/__tests__/transformV2.test.ts +137 -0
  125. package/src/__tests__/transitionNormalize.test.ts +61 -0
  126. package/src/__tests__/validateDoc.test.ts +260 -0
  127. package/src/doc/annotationBlocks.ts +117 -0
  128. package/src/doc/customTemplatesFrontmatter.ts +314 -0
  129. package/src/doc/customThemesFrontmatter.ts +95 -0
  130. package/src/doc/docToMarkdown.ts +104 -5
  131. package/src/doc/getLayers.ts +49 -17
  132. package/src/doc/index.ts +47 -1
  133. package/src/doc/markdownToDoc.ts +411 -13
  134. package/src/doc/mediaAnnotations.ts +100 -0
  135. package/src/doc/resolveDocTheme.ts +55 -0
  136. package/src/doc/standaloneAnnotation.ts +58 -0
  137. package/src/doc/structuredData.ts +301 -0
  138. package/src/doc/templateInputs.ts +240 -0
  139. package/src/doc/templates/__tests__/customTemplate.test.ts +170 -0
  140. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +110 -0
  141. package/src/doc/templates/accentImage.ts +30 -6
  142. package/src/doc/templates/captionUtils.ts +23 -0
  143. package/src/doc/templates/comparisonBar.ts +33 -18
  144. package/src/doc/templates/coverBlock.ts +35 -5
  145. package/src/doc/templates/customTemplate.ts +51 -0
  146. package/src/doc/templates/dataTable.ts +37 -19
  147. package/src/doc/templates/dateEvent.ts +41 -20
  148. package/src/doc/templates/definitionCard.ts +26 -18
  149. package/src/doc/templates/diagramBlock.ts +204 -0
  150. package/src/doc/templates/diagramLayout.ts +159 -0
  151. package/src/doc/templates/drawingBlock.ts +325 -0
  152. package/src/doc/templates/drawingLayout.ts +539 -0
  153. package/src/doc/templates/factCard.ts +54 -26
  154. package/src/doc/templates/fallbackBlock.ts +123 -0
  155. package/src/doc/templates/featureBlock.ts +20 -13
  156. package/src/doc/templates/fullBleedQuote.ts +18 -8
  157. package/src/doc/templates/imageWithCaption.ts +30 -15
  158. package/src/doc/templates/index.ts +225 -46
  159. package/src/doc/templates/inputDescriptors.ts +432 -0
  160. package/src/doc/templates/layoutBlock.ts +43 -0
  161. package/src/doc/templates/layoutLayout.ts +243 -0
  162. package/src/doc/templates/listBlock.ts +56 -30
  163. package/src/doc/templates/mapBlock.ts +15 -12
  164. package/src/doc/templates/metadata.ts +120 -0
  165. package/src/doc/templates/persistentLayers.ts +129 -11
  166. package/src/doc/templates/photoGrid.ts +22 -34
  167. package/src/doc/templates/pullQuote.ts +30 -12
  168. package/src/doc/templates/quoteBlock.ts +54 -29
  169. package/src/doc/templates/sectionHeader.ts +25 -11
  170. package/src/doc/templates/statHighlight.ts +39 -22
  171. package/src/doc/templates/titleBlock.ts +59 -21
  172. package/src/doc/templates/tokens/__tests__/resolveTokens.test.ts +487 -0
  173. package/src/doc/templates/tokens/resolveTokens.ts +453 -0
  174. package/src/doc/templates/twoColumn.ts +48 -30
  175. package/src/doc/templates/videoPullQuote.ts +22 -12
  176. package/src/doc/templates/videoWithCaption.ts +21 -15
  177. package/src/doc/utils/animationUtils.ts +267 -3
  178. package/src/doc/utils/applyRenderStyle.ts +113 -0
  179. package/src/doc/utils/imageTreatment.ts +70 -0
  180. package/src/doc/utils/nearest.ts +59 -0
  181. package/src/doc/utils/shapeGeometry.ts +464 -0
  182. package/src/doc/utils/themeUtils.ts +75 -1
  183. package/src/doc/validate.ts +575 -0
  184. package/src/generate/contentExtractor.ts +148 -35
  185. package/src/generate/index.ts +0 -3
  186. package/src/icons/index.ts +8 -0
  187. package/src/icons/inlineIconMarker.ts +73 -0
  188. package/src/imageEdit/export.ts +85 -2
  189. package/src/imageEdit/index.ts +1 -0
  190. package/src/imageEdit/persistence.ts +51 -0
  191. package/src/index.ts +2 -0
  192. package/src/jsonForm/index.ts +2 -0
  193. package/src/jsonForm/tokens.ts +78 -0
  194. package/src/markdown/annotationCoercion.ts +295 -0
  195. package/src/markdown/attrTokens.ts +241 -0
  196. package/src/markdown/convert.ts +229 -26
  197. package/src/markdown/index.ts +37 -0
  198. package/src/markdown/sanitize.ts +318 -0
  199. package/src/markdown/stringify.ts +96 -6
  200. package/src/markdown/types.ts +66 -0
  201. package/src/markdown/utils.ts +111 -2
  202. package/src/recommend/templates.ts +24 -0
  203. package/src/schemas/BlockTemplates.ts +133 -4
  204. package/src/schemas/CustomTemplates.ts +126 -0
  205. package/src/schemas/Doc.ts +387 -65
  206. package/src/schemas/ImageEditDoc.ts +11 -4
  207. package/src/schemas/Media.ts +169 -0
  208. package/src/schemas/Theme.ts +23 -1
  209. package/src/schemas/Transitions.ts +224 -0
  210. package/src/schemas/colorUtils.ts +33 -0
  211. package/src/schemas/fontStacks.ts +5 -2
  212. package/src/schemas/index.ts +3 -0
  213. package/src/schemas/themeCompile.ts +24 -5
  214. package/src/schemas/themeValidator.ts +49 -11
  215. package/src/schemas/themes/cinematic.json +69 -12
  216. package/src/schemas/themes/documentary.json +58 -11
  217. package/src/schemas/themes/gezellig.json +60 -11
  218. package/src/schemas/themes/magazine.json +54 -9
  219. package/src/schemas/themes/morning-light.json +52 -9
  220. package/src/schemas/themes/standard-dark.json +48 -13
  221. package/src/schemas/themes/standard.json +44 -12
  222. package/src/schemas/themes/tech-dark.json +65 -12
  223. package/src/spatial/Geohash.ts +52 -21
  224. package/src/storage/ContentContainer.ts +6 -2
  225. package/src/transform/applyTransform.ts +43 -4
  226. package/src/transform/index.ts +1 -0
  227. package/src/transform/registry.ts +44 -13
  228. package/src/transform/styles/dataDriven.ts +2 -1
  229. package/src/transform/styles/documentary.ts +2 -1
  230. package/src/transform/styles/magazine.ts +3 -1
  231. package/src/transform/styles/minimal.ts +2 -1
  232. package/src/transform/styles/narrative.ts +3 -1
  233. package/src/transform/templateSelector.ts +87 -11
  234. package/src/transform/types.ts +35 -2
  235. package/dist/chunk-2TMAWB4Y.js.map +0 -1
  236. package/dist/chunk-3E5F2XMR.js.map +0 -1
  237. package/dist/chunk-3K5OG6KI.js.map +0 -1
  238. package/dist/chunk-4X3JQXNM.js +0 -35
  239. package/dist/chunk-4X3JQXNM.js.map +0 -1
  240. package/dist/chunk-57CVQCFX.js.map +0 -1
  241. package/dist/chunk-5WFRKURX.js.map +0 -1
  242. package/dist/chunk-7UDSRZKG.js.map +0 -1
  243. package/dist/chunk-B4EEON3N.js.map +0 -1
  244. package/dist/chunk-FR2RBTKO.js.map +0 -1
  245. package/dist/chunk-H3AXU4MJ.js +0 -179
  246. package/dist/chunk-H3AXU4MJ.js.map +0 -1
  247. package/dist/chunk-K32VJONL.js.map +0 -1
  248. package/dist/chunk-KSWJR66U.js +0 -3706
  249. package/dist/chunk-KSWJR66U.js.map +0 -1
  250. package/dist/chunk-MYIH7FWD.js +0 -145
  251. package/dist/chunk-MYIH7FWD.js.map +0 -1
  252. package/dist/chunk-ZTX4PFFT.js.map +0 -1
  253. package/src/__tests__/slideshowGenerator.test.ts +0 -128
  254. package/src/generate/slideshowGenerator.ts +0 -273
@@ -1,4 +1,89 @@
1
- import { M as MarkdownBlockNode, p as MarkdownHeading } from './types-BdZkdJ3z.js';
1
+ import { Z as Transition, $ as TransitionType, M as MarkdownBlockNode, q as MarkdownHeading } from './types-DlAZ7MW4.js';
2
+
3
+ /**
4
+ * Media-clip timing model.
5
+ *
6
+ * An additive layer on top of the legacy sequential `Doc.audio.segments[]`
7
+ * narration track. A {@link MediaClip} is a piece of audio or video whose
8
+ * timing is expressed *relative to its parent block* (or the whole document),
9
+ * with an optional `startAt` offset and an optional `spillover` past the
10
+ * block's end. {@link resolveMediaSchedule} flattens these into absolute,
11
+ * doc-timeline `ScheduledClip`s that playback and export consume.
12
+ *
13
+ * Authoring: a body-level `{[audio …]}` / `{[video …]}` annotation inside a
14
+ * block becomes a `block.media` clip; the same annotation in the preamble
15
+ * (before the first heading) with `anchor=document` becomes a
16
+ * `doc.documentMedia` clip that spans the whole timeline.
17
+ */
18
+
19
+ /**
20
+ * A timed piece of media attached to a block (or the document).
21
+ */
22
+ interface MediaClip {
23
+ /** Stable id (for React keys and timeline selection). */
24
+ id: string;
25
+ /** Source path (mp3/mp4/…), relative to the article media dir. */
26
+ src: string;
27
+ kind: 'audio' | 'video';
28
+ /**
29
+ * Seconds from the parent block's start when this clip begins. For an
30
+ * `anchor='document'` clip, seconds from the document start. Default 0.
31
+ */
32
+ startAt: number;
33
+ /** Source in-point within the file (seconds). Default 0. */
34
+ clipStart?: number;
35
+ /** Source out-point within the file (seconds). Default: file/block end. */
36
+ clipEnd?: number;
37
+ /**
38
+ * When false (default) the clip stops at its block's end. When true it
39
+ * keeps playing past the block boundary until the clip itself finishes.
40
+ * Ignored for `anchor='document'` clips (they already span the timeline).
41
+ */
42
+ spillover?: boolean;
43
+ /**
44
+ * `'block'` (default) — timed relative to the parent block. `'document'` —
45
+ * timed relative to the document start and able to span every block.
46
+ */
47
+ anchor: 'block' | 'document';
48
+ /**
49
+ * 1-based source line of the authoring annotation, when derived from
50
+ * markdown. Enables round-tripping edits (e.g. the timeline editor rewriting
51
+ * `startAt`) back to the exact line. Absent for programmatically built clips.
52
+ */
53
+ sourceLine?: number;
54
+ }
55
+ /**
56
+ * A {@link MediaClip} resolved to absolute document-timeline coordinates.
57
+ * Produced by {@link resolveMediaSchedule}; consumed by the playback
58
+ * scheduler and the MP4 export.
59
+ */
60
+ interface ScheduledClip {
61
+ id: string;
62
+ src: string;
63
+ kind: 'audio' | 'video';
64
+ /** Absolute doc-timeline second the clip starts. */
65
+ absoluteStart: number;
66
+ /** Absolute doc-timeline second the clip ends (exclusive). */
67
+ absoluteEnd: number;
68
+ /** Source in-point to seek to at `absoluteStart`. */
69
+ sourceIn: number;
70
+ anchor: 'block' | 'document';
71
+ /** Owning block id for block-anchored clips (for video re-homing/export). */
72
+ blockId?: string;
73
+ /** 1-based source line of the authoring annotation, when known. */
74
+ sourceLine?: number;
75
+ }
76
+ /**
77
+ * Flatten every block clip and document clip into absolute-timed
78
+ * `ScheduledClip`s. Pure — depends only on `doc`.
79
+ */
80
+ declare function resolveMediaSchedule(doc: Doc): ScheduledClip[];
81
+ /**
82
+ * Total playback duration including any media that spills past the last
83
+ * block (or audio segment). Export uses this for the frame count and the
84
+ * player for its effective timeline length.
85
+ */
86
+ declare function getDocPlaybackDuration(doc: Doc): number;
2
87
 
3
88
  /**
4
89
  * Viewport Configuration
@@ -158,237 +243,6 @@ declare function getSafeTextBounds(orientation: ViewportOrientation): {
158
243
  bottom: string;
159
244
  };
160
245
 
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
- *
167
- * Built-in themes and customizer-authored themes share this exact schema.
168
- * Customizer-authored themes additionally specify `seedColors` so the few
169
- * fields the user picked can be re-edited later; built-ins typically omit
170
- * `seedColors` and ship with explicit `colors` already filled in.
171
- *
172
- * Design principles:
173
- * - Fully JSON-serializable. No functions; no raw CSS font strings.
174
- * - Font families are structured `FontFamily` references resolved by
175
- * `resolveFontFamily()` at render time.
176
- * - Doc carries an optional `themeId`; resolution happens at render time
177
- * via `resolveTheme(id)`.
178
- * - `createTheme` deep-merges a base theme with partial overrides;
179
- * `compileTheme` fills in defaults and derives missing color slots from
180
- * `seedColors`; both produce a complete `Theme`.
181
- */
182
-
183
- /** Current Theme schema version. Bump on breaking changes; loader migrates. */
184
- declare const THEME_SCHEMA_VERSION: "1";
185
- type ThemeSchemaVersion = typeof THEME_SCHEMA_VERSION;
186
- /**
187
- * Core color palette for a theme. Every color is a `#rrggbb` hex string.
188
- */
189
- interface ThemeColorPalette {
190
- /** Primary accent color */
191
- primary: string;
192
- /** Secondary accent color */
193
- secondary: string;
194
- /** Background color (typically dark) */
195
- background: string;
196
- /** Lighter background for contrast panels */
197
- backgroundLight: string;
198
- /** Main text color */
199
- text: string;
200
- /** Muted/secondary text color */
201
- textMuted: string;
202
- /** Highlight/emphasis color */
203
- highlight: string;
204
- /** Warning/alert color */
205
- warning: string;
206
- }
207
- /**
208
- * A named color scheme used by templates for per-block color variation
209
- * (e.g., statHighlight or sectionHeader).
210
- */
211
- interface ThemeColorScheme {
212
- /** Background fill */
213
- bg: string;
214
- /** Primary text tint */
215
- text: string;
216
- /** Accent / secondary tint */
217
- accent: string;
218
- }
219
- /**
220
- * Optional authoring metadata. When present and `colors` is partial,
221
- * `compileTheme` derives missing color slots from these seeds via OKLCh
222
- * lightening / darkening. Round-trips a customizer-authored theme back
223
- * into editable form.
224
- */
225
- interface ThemeSeedColors {
226
- primary: string;
227
- secondary?: string;
228
- accent?: string;
229
- background?: string;
230
- text?: string;
231
- }
232
- /** Categorical bucket for a font family — drives default fallback. */
233
- type FontFamilyKind = 'sans' | 'serif' | 'mono' | 'display';
234
- /**
235
- * Structured reference to a font family. Either a curated stack id (looked
236
- * up in `AVAILABLE_FONT_STACKS`) or a custom user-supplied name with a
237
- * structured fallback bucket. Themes never carry raw CSS family strings.
238
- */
239
- type FontFamily = {
240
- stackId: string;
241
- } | {
242
- custom: {
243
- name: string;
244
- fallback: 'serif' | 'sans-serif' | 'monospace' | 'system-ui';
245
- };
246
- };
247
- /**
248
- * Typography settings for a theme.
249
- */
250
- interface ThemeTypography {
251
- /** Font family for body / description text */
252
- bodyFont: FontFamily;
253
- /** Font family for titles and headings */
254
- titleFont: FontFamily;
255
- /** Font family for code / monospaced text (optional) */
256
- monoFont?: FontFamily;
257
- /** Multiplier applied to LayoutHints.titleScale (default 1.0) */
258
- titleScale?: number;
259
- /** Multiplier applied to LayoutHints.bodyScale (default 1.0) */
260
- bodyScale?: number;
261
- /** Default body line height (default 1.4) */
262
- lineHeight?: number;
263
- /** Default title line height */
264
- titleLineHeight?: number;
265
- /** Default title font weight */
266
- titleWeight?: 'normal' | 'bold';
267
- }
268
- /**
269
- * Global visual-style knobs that templates consult.
270
- */
271
- interface ThemeStyle {
272
- /** Default border radius for cards / shapes (px) */
273
- borderRadius?: number;
274
- /** Whether templates should default to text shadows */
275
- textShadow?: boolean;
276
- /** Darkness of overlay on image-backed blocks (0–1) */
277
- overlayOpacity?: number;
278
- /** Multiplier on all animation durations (1.0 = normal, <1 faster, >1 slower) */
279
- animationSpeed?: number;
280
- /** Default horizontal padding for text (percentage string, e.g. "5%") */
281
- blockPadding?: string;
282
- }
283
- /**
284
- * Render-style algorithm preset. Controls layout tweaks, default animations,
285
- * transitions, and per-template behavioral hints.
286
- */
287
- interface RenderStyle {
288
- /** Identifier for this algorithmic approach (e.g. "documentary", "magazine") */
289
- name: string;
290
- /** Partial overrides merged onto the orientation-based LayoutHints */
291
- layoutOverrides?: Partial<LayoutHints>;
292
- /** Default entrance animation for text layers */
293
- defaultTextAnimation?: AnimationType;
294
- /** Default animation for background / image layers */
295
- defaultImageAnimation?: AnimationType;
296
- /** Whether to apply Ken Burns ambient motion to images by default */
297
- ambientMotion?: boolean;
298
- /** Default block-to-block transition */
299
- defaultTransition?: {
300
- type: TransitionType;
301
- duration?: number;
302
- };
303
- /**
304
- * Per-template behavioral hints. Keys are template names, values are
305
- * string/number/boolean maps that templates can read to vary their output.
306
- * Templates that consume hints publish a `hintSchema` export documenting
307
- * the keys they recognise.
308
- */
309
- templateHints?: Record<string, Record<string, string | number | boolean>>;
310
- }
311
- /**
312
- * A complete, JSON-serializable theme definition. Built-in and customizer-
313
- * authored themes share this exact shape.
314
- */
315
- interface Theme {
316
- /** Schema version. Always '1' for now. */
317
- schemaVersion: ThemeSchemaVersion;
318
- /** Unique identifier (e.g. "documentary") */
319
- id: string;
320
- /** Human-readable display name */
321
- name: string;
322
- /** Short description for theme pickers */
323
- description?: string;
324
- /** Optional authoring seeds (customizer fills these; built-ins typically omit) */
325
- seedColors?: ThemeSeedColors;
326
- /** Color palette */
327
- colors: ThemeColorPalette;
328
- /** Typography settings */
329
- typography: ThemeTypography;
330
- /** Global visual-style knobs */
331
- style: ThemeStyle;
332
- /** Algorithmic render-style preset */
333
- renderStyle: RenderStyle;
334
- /**
335
- * Named color schemes for per-block color variation.
336
- * Templates reference these by name (e.g. "blue", "warm").
337
- */
338
- colorSchemes: Record<string, ThemeColorScheme>;
339
- /** Optional persistent layers baked into the theme */
340
- persistentLayers?: PersistentLayerConfig;
341
- }
342
- /**
343
- * Recursively makes every property optional.
344
- */
345
- type DeepPartial<T> = {
346
- [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
347
- };
348
- /**
349
- * Create a new theme by deep-merging overrides onto a base theme.
350
- * The returned theme gets its own `id` if one is provided in overrides,
351
- * otherwise keeps the base theme's `id` suffixed with "-custom".
352
- */
353
- declare function createTheme(base: Theme, overrides: DeepPartial<Theme>): Theme;
354
- /**
355
- * A Surface Scheme is the light/dark axis, orthogonal to the editorial
356
- * identity of a Theme. A Theme chooses voice (serif vs sans, muted vs
357
- * bold, documentary vs magazine); a SurfaceScheme chooses what the paper
358
- * looks like. Any theme can render on either surface.
359
- */
360
- interface SurfaceScheme {
361
- /** Identifier — 'light', 'dark', or a custom id. */
362
- id: string;
363
- background: string;
364
- backgroundLight: string;
365
- text: string;
366
- textMuted: string;
367
- }
368
- /** Near-white paper with dark text — standard light mode. */
369
- declare const LIGHT_SURFACE: SurfaceScheme;
370
- /** Dark charcoal paper with light text — standard dark mode. */
371
- declare const DARK_SURFACE: SurfaceScheme;
372
- /**
373
- * Overlay a SurfaceScheme's surface colors onto a Theme's palette,
374
- * leaving everything else (primary, highlight, warning, typography,
375
- * style, renderStyle, colorSchemes) untouched.
376
- */
377
- declare function applySurface(theme: Theme, surface: SurfaceScheme): Theme;
378
- /**
379
- * Register a Theme so it can be looked up by id via `resolveTheme(id)`.
380
- * Lets `Doc.themeId` round-trip through Doc serialization for custom themes.
381
- *
382
- * Registered themes take precedence over built-ins with the same id.
383
- */
384
- declare function registerTheme(theme: Theme): void;
385
- /** Remove a previously registered theme. */
386
- declare function unregisterTheme(id: string): void;
387
- /** Snapshot of all currently registered (non-built-in) themes. */
388
- declare function getRegisteredThemes(): Theme[];
389
- /** @internal — used by themeLibrary's `resolveTheme` to check the registry first. */
390
- declare function lookupRegisteredTheme(id: string | undefined): Theme | undefined;
391
-
392
246
  /**
393
247
  * Block Templates Schema
394
248
  *
@@ -477,6 +331,12 @@ interface BaseTemplateBlock {
477
331
  * Used with sourceStartTime to precisely position the block.
478
332
  */
479
333
  sourceDuration?: number;
334
+ /**
335
+ * Per-block override for the theme's photographic image grade
336
+ * (`theme.style.imageTreatment`). `'none'` opts this block's imagery out;
337
+ * a treatment type forces that grade regardless of the theme.
338
+ */
339
+ imageTreatment?: 'none' | 'mono' | 'duotone' | 'warm' | 'cool';
480
340
  }
481
341
  /**
482
342
  * Title block - doc intro with large title and subtitle.
@@ -826,6 +686,75 @@ interface VideoPullQuoteInput extends BaseTemplateBlock {
826
686
  license?: string;
827
687
  };
828
688
  }
689
+ /**
690
+ * Diagram block - renders child headings as a node-and-edge diagram.
691
+ *
692
+ * Unlike other templates, the diagram template reads its content from
693
+ * the parent block's `children` (passed via `context.children`) — each
694
+ * child heading becomes a node, positioned by its `x`/`y` and connected
695
+ * by its `connectsTo`. Per-diagram options on this input control overall
696
+ * appearance; per-node data lives on the child blocks themselves.
697
+ */
698
+ interface DiagramBlockInput extends BaseTemplateBlock {
699
+ template: 'diagram';
700
+ /** Optional diagram title displayed above the canvas. */
701
+ title?: string;
702
+ /** Color scheme for nodes and edges. */
703
+ colorScheme?: ColorScheme;
704
+ /** Node shape style (default: 'rounded'). */
705
+ nodeShape?: 'rounded' | 'rect' | 'pill';
706
+ /** Edge routing (default: 'curved'). */
707
+ edgeStyle?: 'curved' | 'straight' | 'orthogonal';
708
+ /** Marker at each edge's start (default: 'none'). */
709
+ startStyle?: MarkerStyle;
710
+ /** Marker at each edge's end (default: 'arrow'). */
711
+ endStyle?: MarkerStyle;
712
+ /** Edge line style (default: 'solid'). */
713
+ lineStyle?: 'solid' | 'dashed' | 'dotted';
714
+ }
715
+ /**
716
+ * Free-form 2D canvas block (`layout`). Like `drawing`/`diagram`, the
717
+ * `layout` template is a children-driven container: each child heading is
718
+ * one absolutely-positioned layer (`{[text …]}` / `{[rectangle …]}` /
719
+ * `{[image …]}`), read from `context.children`. A text box's content is the
720
+ * child's body markdown. Backed by the `layoutBlock` template.
721
+ *
722
+ * (Legacy documents stored layers as a base64-JSON `layers=` param on the
723
+ * heading; the editor auto-migrates those to child sub-blocks on first edit.)
724
+ */
725
+ interface RawLayersInput extends BaseTemplateBlock {
726
+ template: 'layout';
727
+ }
728
+ /**
729
+ * Drawing block — renders child headings as free-form shapes on a canvas.
730
+ *
731
+ * Like `diagram`, the drawing template reads its content from the parent
732
+ * block's `children` (passed via `context.children`). Each child heading
733
+ * is a shape: its `{[shape …]}` annotation names the primitive
734
+ * (`rectangle`/`rect`, `circle`, `line`, `arrow`, `path`, `text`) and
735
+ * carries geometry/style as params; its `{#id}` makes it referenceable;
736
+ * its heading text is the shape label and its body text an optional
737
+ * sublabel. Connectors (`line`/`arrow` with `from`/`to`) join shapes by id.
738
+ *
739
+ * Coordinates are author-defined units; the template fits the bounding box
740
+ * of all shapes to the viewport (same scaling as `diagram`), so authors
741
+ * don't have to think in absolute pixels.
742
+ *
743
+ * Legacy drawings authored visually persist their `Layer[]` as a base64
744
+ * `layers="…"` Pandoc param instead of children; the template decodes and
745
+ * returns those directly when no children are present (see `drawingBlock`).
746
+ */
747
+ interface DrawingBlockInput extends BaseTemplateBlock {
748
+ template: 'drawing';
749
+ /** Optional title displayed above the canvas. */
750
+ title?: string;
751
+ /** Color scheme for shapes that don't pin their own fill/stroke. */
752
+ colorScheme?: ColorScheme;
753
+ /** Default fill for shapes that don't specify one (default: 'none'). */
754
+ fill?: string;
755
+ /** Default stroke for shapes that don't specify one (default: theme text). */
756
+ stroke?: string;
757
+ }
829
758
  /**
830
759
  * Data table - renders a themed table with headers and rows.
831
760
  * Ideal for structured data, comparisons, and reference information.
@@ -846,7 +775,7 @@ interface DataTableInput extends BaseTemplateBlock {
846
775
  /**
847
776
  * Union of all template block types.
848
777
  */
849
- type TemplateBlock = TitleBlockInput | SectionHeaderInput | StatHighlightInput | QuoteBlockInput | FactCardInput | TwoColumnInput | DateEventInput | ImageWithCaptionInput | LeftFeatureInput | RightFeatureInput | MapBlockInput | FullBleedQuoteInput | ListBlockInput | PhotoGridInput | DefinitionCardInput | ComparisonBarInput | PullQuoteInput | VideoWithCaptionInput | VideoPullQuoteInput | DataTableInput;
778
+ type TemplateBlock = TitleBlockInput | SectionHeaderInput | StatHighlightInput | QuoteBlockInput | FactCardInput | TwoColumnInput | DateEventInput | ImageWithCaptionInput | LeftFeatureInput | RightFeatureInput | MapBlockInput | FullBleedQuoteInput | ListBlockInput | PhotoGridInput | DefinitionCardInput | ComparisonBarInput | PullQuoteInput | VideoWithCaptionInput | VideoPullQuoteInput | DataTableInput | DiagramBlockInput | DrawingBlockInput | RawLayersInput;
850
779
  /**
851
780
  * A block can be either a raw Block or a TemplateBlock.
852
781
  */
@@ -874,6 +803,20 @@ interface TemplateContext {
874
803
  orientation: ViewportOrientation;
875
804
  /** Layout hints for this orientation */
876
805
  layout: LayoutHints;
806
+ /**
807
+ * The block's direct children (set by `getLayers` when the block has
808
+ * `children`). Most templates ignore this; aggregate templates like
809
+ * `diagram` consume it to render each child as part of their output.
810
+ */
811
+ children?: Block[];
812
+ /**
813
+ * The source block being expanded. Used by user-defined custom
814
+ * templates so the token resolver can substitute `{title}`,
815
+ * `{content}`, `{children}`, and `{image:N}` against the block's
816
+ * data at render time. Built-in templates ignore this and rely on
817
+ * their typed `input` parameters instead.
818
+ */
819
+ block?: Block;
877
820
  }
878
821
  /**
879
822
  * Create a template context with viewport-derived values.
@@ -929,11 +872,11 @@ interface PersistentLayerTemplate {
929
872
  /**
930
873
  * Available persistent layer template types.
931
874
  */
932
- type PersistentLayerTemplateType = 'solidBackground' | 'gradientBackground' | 'imageBackground' | 'patternBackground' | 'titleCaption' | 'cornerBranding' | 'progressIndicator';
875
+ type PersistentLayerTemplateType = 'solidBackground' | 'gradientBackground' | 'imageBackground' | 'patternBackground' | 'vignette' | 'ambientGradient' | 'titleCaption' | 'cornerBranding' | 'progressIndicator';
933
876
  /**
934
877
  * Union of all persistent layer template configs.
935
878
  */
936
- type PersistentLayerTemplateConfig = SolidBackgroundConfig | GradientBackgroundConfig | ImageBackgroundConfig | PatternBackgroundConfig | TitleCaptionConfig | CornerBrandingConfig | ProgressIndicatorConfig;
879
+ type PersistentLayerTemplateConfig = SolidBackgroundConfig | GradientBackgroundConfig | ImageBackgroundConfig | PatternBackgroundConfig | VignetteConfig | AmbientGradientConfig | TitleCaptionConfig | CornerBrandingConfig | ProgressIndicatorConfig;
937
880
  /**
938
881
  * Solid color background.
939
882
  */
@@ -980,6 +923,30 @@ interface PatternBackgroundConfig {
980
923
  /** Pattern scale multiplier */
981
924
  scale?: number;
982
925
  }
926
+ /**
927
+ * Soft radial vignette (top layer) — transparent center darkening toward
928
+ * the frame edges. The classic film-look framing device.
929
+ */
930
+ interface VignetteConfig {
931
+ type: 'vignette';
932
+ /** Edge darkness 0-1 (default 0.3). */
933
+ strength?: number;
934
+ /** Vignette color (default black). */
935
+ color?: string;
936
+ }
937
+ /**
938
+ * Slowly drifting surface gradient (bottom layer). Colors default to the
939
+ * theme's surface family at expansion time.
940
+ */
941
+ interface AmbientGradientConfig {
942
+ type: 'ambientGradient';
943
+ /** Gradient start color. */
944
+ from?: string;
945
+ /** Gradient end color. */
946
+ to?: string;
947
+ /** Drift loop duration in seconds (default 40). */
948
+ duration?: number;
949
+ }
983
950
  /**
984
951
  * Title caption overlay (article title in corner).
985
952
  */
@@ -1024,20 +991,388 @@ interface ProgressIndicatorConfig {
1024
991
  }
1025
992
 
1026
993
  /**
1027
- * Doc Schema
994
+ * Theme System
995
+ *
996
+ * A Theme bundles color palette, typography, visual style, render-style
997
+ * algorithm, and per-block color schemes into one JSON-serializable object.
998
+ *
999
+ * Built-in themes and customizer-authored themes share this exact schema.
1000
+ * Customizer-authored themes additionally specify `seedColors` so the few
1001
+ * fields the user picked can be re-edited later; built-ins typically omit
1002
+ * `seedColors` and ship with explicit `colors` already filled in.
1003
+ *
1004
+ * Design principles:
1005
+ * - Fully JSON-serializable. No functions; no raw CSS font strings.
1006
+ * - Font families are structured `FontFamily` references resolved by
1007
+ * `resolveFontFamily()` at render time.
1008
+ * - Doc carries an optional `themeId`; resolution happens at render time
1009
+ * via `resolveTheme(id)`.
1010
+ * - `createTheme` deep-merges a base theme with partial overrides;
1011
+ * `compileTheme` fills in defaults and derives missing color slots from
1012
+ * `seedColors`; both produce a complete `Theme`.
1013
+ */
1014
+
1015
+ /** Current Theme schema version. Bump on breaking changes; loader migrates. */
1016
+ declare const THEME_SCHEMA_VERSION: "1";
1017
+ type ThemeSchemaVersion = typeof THEME_SCHEMA_VERSION;
1018
+ /**
1019
+ * Frontmatter key under which a document inlines its custom Theme
1020
+ * definitions. The theme analog of `FRONTMATTER_CUSTOM_TEMPLATES_KEY`
1021
+ * (custom block templates). See `doc/customThemesFrontmatter.ts` for the
1022
+ * codec and `doc/resolveDocTheme.ts` for doc-scoped resolution.
1023
+ */
1024
+ declare const FRONTMATTER_CUSTOM_THEMES_KEY = "squisq-custom-themes";
1025
+ /**
1026
+ * Core color palette for a theme. Every color is a `#rrggbb` hex string.
1027
+ */
1028
+ interface ThemeColorPalette {
1029
+ /** Primary accent color */
1030
+ primary: string;
1031
+ /** Secondary accent color */
1032
+ secondary: string;
1033
+ /** Background color (typically dark) */
1034
+ background: string;
1035
+ /** Lighter background for contrast panels */
1036
+ backgroundLight: string;
1037
+ /** Main text color */
1038
+ text: string;
1039
+ /** Muted/secondary text color */
1040
+ textMuted: string;
1041
+ /** Highlight/emphasis color */
1042
+ highlight: string;
1043
+ /** Warning/alert color */
1044
+ warning: string;
1045
+ }
1046
+ /**
1047
+ * A named color scheme used by templates for per-block color variation
1048
+ * (e.g., statHighlight or sectionHeader).
1049
+ */
1050
+ interface ThemeColorScheme {
1051
+ /** Background fill */
1052
+ bg: string;
1053
+ /** Primary text tint */
1054
+ text: string;
1055
+ /** Accent / secondary tint */
1056
+ accent: string;
1057
+ }
1058
+ /**
1059
+ * Optional authoring metadata. When present and `colors` is partial,
1060
+ * `compileTheme` derives missing color slots from these seeds via OKLCh
1061
+ * lightening / darkening. Round-trips a customizer-authored theme back
1062
+ * into editable form.
1063
+ */
1064
+ interface ThemeSeedColors {
1065
+ primary: string;
1066
+ secondary?: string;
1067
+ accent?: string;
1068
+ background?: string;
1069
+ text?: string;
1070
+ }
1071
+ /** Categorical bucket for a font family — drives default fallback. */
1072
+ type FontFamilyKind = 'sans' | 'serif' | 'mono' | 'display';
1073
+ /**
1074
+ * Structured reference to a font family. Either a curated stack id (looked
1075
+ * up in `AVAILABLE_FONT_STACKS`) or a custom user-supplied name with a
1076
+ * structured fallback bucket. Themes never carry raw CSS family strings.
1077
+ */
1078
+ type FontFamily = {
1079
+ stackId: string;
1080
+ } | {
1081
+ custom: {
1082
+ name: string;
1083
+ fallback: 'serif' | 'sans-serif' | 'monospace' | 'system-ui';
1084
+ };
1085
+ };
1086
+ /**
1087
+ * Typography settings for a theme.
1088
+ */
1089
+ interface ThemeTypography {
1090
+ /** Font family for body / description text */
1091
+ bodyFont: FontFamily;
1092
+ /** Font family for titles and headings */
1093
+ titleFont: FontFamily;
1094
+ /** Font family for code / monospaced text (optional) */
1095
+ monoFont?: FontFamily;
1096
+ /** Multiplier applied to LayoutHints.titleScale (default 1.0) */
1097
+ titleScale?: number;
1098
+ /** Multiplier applied to LayoutHints.bodyScale (default 1.0) */
1099
+ bodyScale?: number;
1100
+ /** Default body line height (default 1.4) */
1101
+ lineHeight?: number;
1102
+ /** Default title line height */
1103
+ titleLineHeight?: number;
1104
+ /** Default title font weight */
1105
+ titleWeight?: 'normal' | 'bold';
1106
+ }
1107
+ /**
1108
+ * Global visual-style knobs that templates consult.
1109
+ */
1110
+ interface ThemeStyle {
1111
+ /** Default border radius for cards / shapes (px) */
1112
+ borderRadius?: number;
1113
+ /** Whether templates should default to text shadows */
1114
+ textShadow?: boolean;
1115
+ /** Darkness of overlay on image-backed blocks (0–1) */
1116
+ overlayOpacity?: number;
1117
+ /** Multiplier on all animation durations (1.0 = normal, <1 faster, >1 slower) */
1118
+ animationSpeed?: number;
1119
+ /** Default horizontal padding for text (percentage string, e.g. "5%") */
1120
+ blockPadding?: string;
1121
+ /**
1122
+ * Photographic grade applied to template imagery (full-bleed photos,
1123
+ * feature halves, accent strips) so photos look native to the theme.
1124
+ * Duotone tints default to the theme primary. Blocks opt out per
1125
+ * template via `imageTreatment: 'none'`.
1126
+ */
1127
+ imageTreatment?: ImageTreatment;
1128
+ }
1129
+ /**
1130
+ * Render-style algorithm preset. Controls layout tweaks, default animations,
1131
+ * transitions, and per-template behavioral hints.
1132
+ */
1133
+ interface RenderStyle {
1134
+ /** Identifier for this algorithmic approach (e.g. "documentary", "magazine") */
1135
+ name: string;
1136
+ /** Partial overrides merged onto the orientation-based LayoutHints */
1137
+ layoutOverrides?: Partial<LayoutHints>;
1138
+ /** Default entrance animation for text layers */
1139
+ defaultTextAnimation?: AnimationType;
1140
+ /** Default animation for background / image layers */
1141
+ defaultImageAnimation?: AnimationType;
1142
+ /** Whether to apply Ken Burns ambient motion to images by default */
1143
+ ambientMotion?: boolean;
1144
+ /** Default block-to-block transition */
1145
+ defaultTransition?: {
1146
+ type: TransitionType;
1147
+ duration?: number;
1148
+ };
1149
+ /**
1150
+ * Per-template behavioral hints. Keys are template names, values are
1151
+ * string/number/boolean maps that templates can read to vary their output.
1152
+ * Templates that consume hints publish a `hintSchema` export documenting
1153
+ * the keys they recognise.
1154
+ */
1155
+ templateHints?: Record<string, Record<string, string | number | boolean>>;
1156
+ }
1157
+ /**
1158
+ * A complete, JSON-serializable theme definition. Built-in and customizer-
1159
+ * authored themes share this exact shape.
1160
+ */
1161
+ interface Theme {
1162
+ /** Schema version. Always '1' for now. */
1163
+ schemaVersion: ThemeSchemaVersion;
1164
+ /** Unique identifier (e.g. "documentary") */
1165
+ id: string;
1166
+ /** Human-readable display name */
1167
+ name: string;
1168
+ /** Short description for theme pickers */
1169
+ description?: string;
1170
+ /**
1171
+ * Id of the theme this custom theme was derived from, if any. Recorded
1172
+ * by `compileTheme({ base })` so a customizer can re-open the theme and
1173
+ * re-inherit the base's render style / color schemes / typography.
1174
+ * Absent on built-ins.
1175
+ */
1176
+ basedOn?: string;
1177
+ /** Optional authoring seeds (customizer fills these; built-ins typically omit) */
1178
+ seedColors?: ThemeSeedColors;
1179
+ /** Color palette */
1180
+ colors: ThemeColorPalette;
1181
+ /** Typography settings */
1182
+ typography: ThemeTypography;
1183
+ /** Global visual-style knobs */
1184
+ style: ThemeStyle;
1185
+ /** Algorithmic render-style preset */
1186
+ renderStyle: RenderStyle;
1187
+ /**
1188
+ * Named color schemes for per-block color variation.
1189
+ * Templates reference these by name (e.g. "blue", "warm").
1190
+ */
1191
+ colorSchemes: Record<string, ThemeColorScheme>;
1192
+ /** Optional persistent layers baked into the theme */
1193
+ persistentLayers?: PersistentLayerConfig;
1194
+ }
1195
+ /**
1196
+ * Recursively makes every property optional.
1197
+ */
1198
+ type DeepPartial<T> = {
1199
+ [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
1200
+ };
1201
+ /**
1202
+ * Create a new theme by deep-merging overrides onto a base theme.
1203
+ * The returned theme gets its own `id` if one is provided in overrides,
1204
+ * otherwise keeps the base theme's `id` suffixed with "-custom".
1205
+ */
1206
+ declare function createTheme(base: Theme, overrides: DeepPartial<Theme>): Theme;
1207
+ /**
1208
+ * A Surface Scheme is the light/dark axis, orthogonal to the editorial
1209
+ * identity of a Theme. A Theme chooses voice (serif vs sans, muted vs
1210
+ * bold, documentary vs magazine); a SurfaceScheme chooses what the paper
1211
+ * looks like. Any theme can render on either surface.
1212
+ */
1213
+ interface SurfaceScheme {
1214
+ /** Identifier — 'light', 'dark', or a custom id. */
1215
+ id: string;
1216
+ background: string;
1217
+ backgroundLight: string;
1218
+ text: string;
1219
+ textMuted: string;
1220
+ }
1221
+ /** Near-white paper with dark text — standard light mode. */
1222
+ declare const LIGHT_SURFACE: SurfaceScheme;
1223
+ /** Dark charcoal paper with light text — standard dark mode. */
1224
+ declare const DARK_SURFACE: SurfaceScheme;
1225
+ /**
1226
+ * Overlay a SurfaceScheme's surface colors onto a Theme's palette,
1227
+ * leaving everything else (primary, highlight, warning, typography,
1228
+ * style, renderStyle, colorSchemes) untouched.
1229
+ */
1230
+ declare function applySurface(theme: Theme, surface: SurfaceScheme): Theme;
1231
+ /**
1232
+ * Register a Theme so it can be looked up by id via `resolveTheme(id)`.
1233
+ * Lets `Doc.themeId` round-trip through Doc serialization for custom themes.
1234
+ *
1235
+ * Registered themes take precedence over built-ins with the same id.
1236
+ */
1237
+ declare function registerTheme(theme: Theme): void;
1238
+ /** Remove a previously registered theme. */
1239
+ declare function unregisterTheme(id: string): void;
1240
+ /** Snapshot of all currently registered (non-built-in) themes. */
1241
+ declare function getRegisteredThemes(): Theme[];
1242
+ /** @internal — used by themeLibrary's `resolveTheme` to check the registry first. */
1243
+ declare function lookupRegisteredTheme(id: string | undefined): Theme | undefined;
1244
+
1245
+ /**
1246
+ * Custom Templates Schema
1028
1247
  *
1029
- * Defines the JSON format for AI-scriptable visual docs that accompany
1030
- * audio narration. A Doc describes a sequence of animated blocks
1031
- * with text overlays, images, and transitions synchronized to audio.
1248
+ * User-defined block layout templates. The author designs a visual
1249
+ * layout once (in the editor's TemplateDesigner) and saves it as a
1250
+ * `CustomTemplateDefinition`. The definition lives either in the
1251
+ * document's frontmatter (per-doc, portable, ships with the markdown)
1252
+ * or in the user's local library (cross-doc reuse via localStorage).
1032
1253
  *
1033
- * The format is designed to be:
1034
- * 1. AI-generatable - Claude can create scripts from article content
1035
- * 2. Browser-renderable - SVG + CSS animations (ES2015 compatible)
1036
- * 3. Video-exportable - Frame-by-frame capture via Playwright
1254
+ * A custom template's content is just a `Layer[]` — the same shape that
1255
+ * built-in templates emit at expansion time. The novel bit is
1256
+ * *placeholder tokens*: TextLayer / ImageLayer content fields may carry
1257
+ * strings like `{title}`, `{content}`, `{children}`, or `{image:N}`
1258
+ * that the resolver (see `doc/templates/tokens/resolveTokens.ts`)
1259
+ * substitutes against the source Block at render time.
1260
+ *
1261
+ * Responsiveness model: every numeric position field in `layers` is
1262
+ * stored as a `%`-string relative to `viewport`, so the same template
1263
+ * renders correctly at any of Squisq's viewport presets (landscape,
1264
+ * portrait, square). The `viewport` field itself is purely an
1265
+ * authoring-time reference — it tells the designer canvas which aspect
1266
+ * ratio to mount with, and the thumbnail renderer which viewBox to use.
1267
+ */
1268
+
1269
+ /**
1270
+ * Iteration directive for a custom-template layer.
1271
+ *
1272
+ * A layer that carries a `repeat` is cloned once per item of a source
1273
+ * collection at render time (see `resolveTokens`). The clones are laid
1274
+ * out in a row or column, offset from the base position by the layer's
1275
+ * own width/height plus `gap`, and each clone resolves per-item tokens
1276
+ * (`{item}`, `{item:src}`, `{item:label}`, `{index}`) against its item.
1277
+ */
1278
+ interface LayerRepeat {
1279
+ /**
1280
+ * Which of the source Block's collections to iterate:
1281
+ * - `images` — every image in the block body (alt + url)
1282
+ * - `children` — child block titles
1283
+ * - `listItems` — top-level list-item text in the block body
1284
+ */
1285
+ source: 'images' | 'children' | 'listItems';
1286
+ /**
1287
+ * Layout axis for the clones. `column` (default) stacks them
1288
+ * vertically (offsets `position.y`); `row` lays them out horizontally
1289
+ * (offsets `position.x`).
1290
+ */
1291
+ direction?: 'row' | 'column';
1292
+ /**
1293
+ * Extra spacing added between consecutive clones, in the same unit as
1294
+ * the layer's `width`/`height`. Defaults to 24.
1295
+ */
1296
+ gap?: number;
1297
+ /** Cap on the number of clones rendered (omit for "all items"). */
1298
+ max?: number;
1299
+ }
1300
+ /**
1301
+ * A custom-template layer: a normal render `Layer` that may additionally
1302
+ * carry a `repeat` directive. This is a strict superset of the core
1303
+ * `Layer` type used only inside `CustomTemplateDefinition.layers`; the
1304
+ * resolver strips `repeat` before emitting the final `Layer[]`, so the
1305
+ * core `Layer` type is never widened.
1306
+ */
1307
+ type CustomTemplateLayer = Layer & {
1308
+ repeat?: LayerRepeat;
1309
+ };
1310
+ /**
1311
+ * A single user-defined template. Stored in `Doc.customTemplates`
1312
+ * (frontmatter-backed) or in the user's local library.
1313
+ */
1314
+ interface CustomTemplateDefinition {
1315
+ /**
1316
+ * Stable id used in heading annotations (`### Foo {[name]}`). Must be
1317
+ * a slug — lowercase alphanumerics, hyphens. The picker enforces
1318
+ * uniqueness within a (doc + library) merged namespace.
1319
+ */
1320
+ name: string;
1321
+ /** Human-readable label shown in the template picker. */
1322
+ label: string;
1323
+ /** Optional one-sentence description for the picker card. */
1324
+ description?: string;
1325
+ /**
1326
+ * Authoring viewport — the canvas size the designer drew against.
1327
+ * Stored so the designer and thumbnail renderer can reproduce the
1328
+ * same canvas. Layer positions are stored as `%`-strings, so the
1329
+ * actual render-time viewport can differ from this without breaking
1330
+ * the layout. Defaults to 1920×1080 landscape.
1331
+ */
1332
+ viewport: {
1333
+ width: number;
1334
+ height: number;
1335
+ };
1336
+ /**
1337
+ * The visual content. Every `position.x` / `position.y` /
1338
+ * `position.width` / `position.height` should be a `%`-string at
1339
+ * save time (the designer's normalizePositions step enforces this).
1340
+ *
1341
+ * Tokens supported (grammar v2):
1342
+ * - TextLayer.content.text — `{title}`, `{content}`, `{children}`,
1343
+ * `{image:N}` (Nth image's alt text), `{attr:key}` (block
1344
+ * attribute), plus a pipe default on any token
1345
+ * (`{attr:subtitle|Untitled}`).
1346
+ * - ImageLayer.content.src — `{image:N}` (the URL) and `{attr:key}`,
1347
+ * with pipe defaults (`{image:1|fallback.jpg}`).
1348
+ * - Inside a layer with `repeat`, per-item tokens: `{item}`,
1349
+ * `{item:src}`, `{item:label}`, `{index}` (1-based).
1350
+ *
1351
+ * A layer may carry a `repeat` directive (see {@link LayerRepeat}) to
1352
+ * be cloned once per item of a source collection.
1353
+ */
1354
+ layers: CustomTemplateLayer[];
1355
+ }
1356
+ /**
1357
+ * Tag value for the YAML frontmatter key that carries inlined custom
1358
+ * template definitions. Exported so the markdown bridge stays in sync.
1359
+ */
1360
+ declare const FRONTMATTER_CUSTOM_TEMPLATES_KEY = "squisq-custom-templates";
1361
+
1362
+ /**
1363
+ * A directed connection from one block to another.
1037
1364
  *
1038
- * Timing: Blocks are timed to match narration audio segments. Each article
1039
- * has 3-6 MP3 files (intro + sections), and blocks are grouped by segment.
1365
+ * Populated from a heading's Pandoc-style `{connectsTo=…}` attribute
1366
+ * (e.g. `## Step 1 {#step1 connectsTo=foo:flow,bar}`). Each item is
1367
+ * `target` or `target:type`. Used by diagram-style layouts that draw
1368
+ * edges between blocks.
1040
1369
  */
1370
+ interface BlockConnection {
1371
+ /** Target block id (matches another block's `id`). */
1372
+ target: string;
1373
+ /** Optional connection type/label (e.g., "flow", "requires"). */
1374
+ type?: string;
1375
+ }
1041
1376
  /**
1042
1377
  * Configuration for the Start/resting block shown before playback begins.
1043
1378
  */
@@ -1057,6 +1392,30 @@ interface StartBlockConfig {
1057
1392
  /** License identifier for the hero image */
1058
1393
  heroLicense?: string;
1059
1394
  }
1395
+ /**
1396
+ * A structural problem detected while converting or validating a document.
1397
+ *
1398
+ * Diagnostics ride on `Doc.diagnostics` so any consumer — the editor, the
1399
+ * CLI `validate` command, or an agent re-parsing its own output — gets the
1400
+ * same feedback without watching the console. Conversion never throws for
1401
+ * content problems; it degrades gracefully and records a diagnostic.
1402
+ */
1403
+ interface DocDiagnostic {
1404
+ /** `error` = the author's intent could not be honored (e.g. unparseable
1405
+ * data fence); `warning` = something looks wrong but rendering proceeds
1406
+ * with a fallback (e.g. unknown template name); `info` = nothing is
1407
+ * broken, but the author may want to know (e.g. a redundant annotation). */
1408
+ severity: 'error' | 'warning' | 'info';
1409
+ /** Stable machine-readable code (e.g. `unknown-template`, `duplicate-id`,
1410
+ * `unresolved-connection`, `data-fence-parse`, `missing-asset`). */
1411
+ code: string;
1412
+ /** Human-readable description, including a suggestion when one exists. */
1413
+ message: string;
1414
+ /** Id of the block the problem belongs to, when attributable. */
1415
+ blockId?: string;
1416
+ /** 1-based line number in the markdown source, when known. */
1417
+ line?: number;
1418
+ }
1060
1419
  /**
1061
1420
  * A complete visual doc for an article.
1062
1421
  */
@@ -1098,6 +1457,48 @@ interface Doc {
1098
1457
  * Carries rendering hints like `document-render-as` and custom metadata.
1099
1458
  */
1100
1459
  frontmatter?: Record<string, unknown>;
1460
+ /**
1461
+ * User-defined block templates inlined into this doc.
1462
+ *
1463
+ * Populated from the markdown frontmatter key
1464
+ * `squisq-custom-templates`. The template-expansion pipeline
1465
+ * (`expandDocBlocks` in `doc/templates/index.ts`) merges these into
1466
+ * the registry before walking blocks, so a heading annotated
1467
+ * `{[myhero]}` resolves against a doc-defined template named `myhero`.
1468
+ *
1469
+ * Library templates (stored in localStorage on the editor side) are
1470
+ * NOT auto-loaded here — applying a library template to a block
1471
+ * copies its definition into this list so the doc remains
1472
+ * self-sufficient for SSR and export.
1473
+ */
1474
+ customTemplates?: CustomTemplateDefinition[];
1475
+ /**
1476
+ * User-defined themes inlined into this doc.
1477
+ *
1478
+ * The theme analog of {@link Doc.customTemplates}. Populated from the
1479
+ * markdown frontmatter key `squisq-custom-themes`. Exactly one is active
1480
+ * at a time — selected by id via `squisq-theme` / {@link Doc.themeId},
1481
+ * the doc-level counterpart of a block's `{[name]}` template annotation —
1482
+ * but the payload is a list so a doc can carry a small catalog and stay
1483
+ * self-sufficient for SSR / export. `resolveThemeForDoc(doc, id)` resolves
1484
+ * against this list first (pure, doc-scoped) before built-ins, mirroring
1485
+ * how `buildRegistry` resolves custom templates.
1486
+ */
1487
+ customThemes?: Theme[];
1488
+ /**
1489
+ * Structural problems found while building this doc (unknown templates,
1490
+ * unparseable data fences, duplicate ids, unresolved connections, …).
1491
+ * Populated by `markdownToDoc()`; extended by `validateMarkdownDoc()`.
1492
+ * Absent when the document is clean.
1493
+ */
1494
+ diagnostics?: DocDiagnostic[];
1495
+ /**
1496
+ * Document-spanning timed media (e.g. a full-length narration MP3/MP4),
1497
+ * authored as a media annotation in the preamble (before the first
1498
+ * heading) with `anchor=document`. Each clip is timed from the document
1499
+ * start and may play across every block. See `resolveMediaSchedule`.
1500
+ */
1501
+ documentMedia?: MediaClip[];
1101
1502
  }
1102
1503
  /**
1103
1504
  * A single block in the doc.
@@ -1107,13 +1508,27 @@ interface Doc {
1107
1508
  * - `sourceHeading` references the original MarkdownHeading node
1108
1509
  * - `contents` holds the body markdown between this heading and the next
1109
1510
  * - `children` holds sub-heading blocks (e.g., H2s under an H1)
1511
+ *
1512
+ * Four adjacent fields carry template information — do not confuse them:
1513
+ * - `template` — the *resolved* template id this block renders with.
1514
+ * - `templateOverrides` — raw `{[…]}` inline params (always strings).
1515
+ * - `templateData` — structured inputs from a data fence / GFM table
1516
+ * (parsed types, not strings).
1517
+ * - `sourceAnnotation.template` — the raw *requested* template name for a
1518
+ * standalone-annotation block (before alias resolution).
1519
+ * Merge order at render time: template defaults → `templateData` →
1520
+ * `templateOverrides`.
1110
1521
  */
1111
1522
  interface Block {
1112
- /** Unique identifier for this block */
1523
+ /** Unique identifier for this block.
1524
+ * Defaults to a slug derived from the heading text. May be overridden by
1525
+ * a Pandoc-style `{#custom-id}` attribute on the heading. */
1113
1526
  id: string;
1114
- /** When this block appears (seconds from start) */
1527
+ /** When this block appears (seconds from start).
1528
+ * Overridden by a heading attribute `startTime=…` when present. */
1115
1529
  startTime: number;
1116
- /** How long this block is visible (seconds) */
1530
+ /** How long this block is visible (seconds).
1531
+ * Overridden by a heading attribute `duration=…` when present. */
1117
1532
  duration: number;
1118
1533
  /** Which audio segment this block belongs to (0-indexed) */
1119
1534
  audioSegment: number;
@@ -1127,8 +1542,38 @@ interface Block {
1127
1542
  layers?: Layer[];
1128
1543
  /** Entry transition from previous block */
1129
1544
  transition?: Transition;
1130
- /** Template name that generated this block (for debugging) */
1545
+ /** Resolved template id this block renders with (see the quartet note above). */
1131
1546
  template?: string;
1547
+ /**
1548
+ * True when `template` was chosen by content-aware auto-picking
1549
+ * (`markdownToDoc`'s `autoTemplates`) rather than authored. Ephemeral:
1550
+ * `docToMarkdown` does not materialize auto-picked templates as heading
1551
+ * annotations, keeping the markdown round-trip lossless.
1552
+ */
1553
+ autoTemplate?: boolean;
1554
+ /**
1555
+ * True when this block was produced from a *standalone* `{[templateName …]}`
1556
+ * annotation paragraph in another block's body (rather than from a heading).
1557
+ * Such blocks have no `sourceHeading`; `docToMarkdown` re-emits their
1558
+ * annotation as a synthesized paragraph before their `contents` so the
1559
+ * markdown round-trips. See `annotationBlocks.ts`.
1560
+ *
1561
+ * Presence-marker: only ever `true` (never `false`). Test with
1562
+ * `block.standaloneAnnotation` presence, not as a boolean toggle.
1563
+ */
1564
+ standaloneAnnotation?: true;
1565
+ /**
1566
+ * The annotation a standalone block was built from — the raw (un-resolved)
1567
+ * template name and its params, in author order. Round-tripped by
1568
+ * `docToMarkdown` (via `serializeAnnotation`) and read by validation as the
1569
+ * raw requested template name (symmetric with
1570
+ * `sourceHeading.templateAnnotation`). Present only when
1571
+ * `standaloneAnnotation` is true.
1572
+ */
1573
+ sourceAnnotation?: {
1574
+ template?: string;
1575
+ params?: Record<string, string>;
1576
+ };
1132
1577
  /**
1133
1578
  * Display title for template rendering.
1134
1579
  * Extracted from the sourceHeading when the block is created by markdownToDoc().
@@ -1157,18 +1602,60 @@ interface Block {
1157
1602
  */
1158
1603
  sourceHeading?: MarkdownHeading;
1159
1604
  /**
1160
- * Template overrides extracted from markdown directives.
1161
- * For example, `### Data Section {template=chart colorScheme=blue}`
1162
- * would produce `{ template: 'chart', colorScheme: 'blue' }`.
1163
- * Allows per-block customization of template selection and parameters.
1605
+ * Template overrides extracted from a heading's `{[templateName key=value]}`
1606
+ * annotation. Only carries template-specific params (e.g. `colorScheme=blue`).
1607
+ * Block-level metadata lives in `x` / `y` / `connectsTo` / `metadata` and is
1608
+ * sourced from the Pandoc-style `{#id .class key=value}` attribute block instead.
1164
1609
  */
1165
1610
  templateOverrides?: Record<string, string>;
1611
+ /**
1612
+ * Structured template inputs sourced from the block's body content:
1613
+ * a ```json data / ```yaml data fence under the heading, or — for the
1614
+ * `dataTable` template — the first GFM table in the section. Unlike
1615
+ * `templateOverrides` (always strings), values here keep their parsed
1616
+ * types (arrays, numbers, nested objects). Merge order at render time:
1617
+ * template defaults → `templateData` → `templateOverrides`.
1618
+ */
1619
+ templateData?: Record<string, unknown>;
1620
+ /**
1621
+ * X coordinate for diagram-style positioning (in author-defined units).
1622
+ * Sourced from a heading attribute `x=…`.
1623
+ */
1624
+ x?: number;
1625
+ /**
1626
+ * Y coordinate for diagram-style positioning (in author-defined units).
1627
+ * Sourced from a heading attribute `y=…`.
1628
+ */
1629
+ y?: number;
1630
+ /**
1631
+ * Outgoing connections to other blocks, by id. Sourced from a heading
1632
+ * attribute `connectsTo=target1,target2:type,target3`.
1633
+ */
1634
+ connectsTo?: BlockConnection[];
1635
+ /**
1636
+ * CSS-style class tokens from a heading's Pandoc attribute (e.g. `{.important .v2}`).
1637
+ * Parsed and stored; downstream consumers may use them for styling or filtering.
1638
+ */
1639
+ classes?: string[];
1640
+ /**
1641
+ * Free-form metadata for heading attribute keys outside the typed registry
1642
+ * (anything that isn't `x` / `y` / `connectsTo` / `startTime` / `duration`).
1643
+ * Values are raw strings exactly as authored.
1644
+ */
1645
+ metadata?: Record<string, string>;
1646
+ /**
1647
+ * Timed media clips attached to this block, authored as body-level
1648
+ * `{[audio …]}` / `{[video …]}` annotations. Each clip is timed relative
1649
+ * to this block's `startTime` (via `startAt`) and optionally spills past
1650
+ * the block's end. See `MediaClip` / `resolveMediaSchedule`.
1651
+ */
1652
+ media?: MediaClip[];
1166
1653
  }
1167
1654
  /**
1168
1655
  * A visual element within a block.
1169
1656
  * Layers are composited back-to-front (first layer is background).
1170
1657
  */
1171
- type Layer = ImageLayer | TextLayer | ShapeLayer | MapLayer | VideoLayer | TableLayer;
1658
+ type Layer = ImageLayer | TextLayer | ShapeLayer | PathLayer | MapLayer | VideoLayer | TableLayer;
1172
1659
  interface BaseLayer {
1173
1660
  /** Unique identifier for this layer */
1174
1661
  id: string;
@@ -1177,6 +1664,24 @@ interface BaseLayer {
1177
1664
  /** Animation to apply */
1178
1665
  animation?: Animation;
1179
1666
  }
1667
+ /**
1668
+ * Photographic treatment applied to an image layer — a theme-level "grade"
1669
+ * so the same photo looks native in every theme. Rendered as CSS filter
1670
+ * functions, which behave identically in the browser player and headless
1671
+ * frame capture (video/still export).
1672
+ */
1673
+ interface ImageTreatment {
1674
+ /**
1675
+ * mono: desaturate toward archival black & white.
1676
+ * duotone: single-hue tint (hue taken from `color`).
1677
+ * warm / cool: gentle temperature grade.
1678
+ */
1679
+ type: 'none' | 'mono' | 'duotone' | 'warm' | 'cool';
1680
+ /** Blend strength 0..1. Default 0.6. */
1681
+ strength?: number;
1682
+ /** Duotone tint color (themes default this to their primary). */
1683
+ color?: string;
1684
+ }
1180
1685
  /**
1181
1686
  * Image layer - displays an image with optional Ken Burns effect.
1182
1687
  */
@@ -1193,6 +1698,10 @@ interface ImageLayer extends BaseLayer {
1193
1698
  credit?: string;
1194
1699
  /** License identifier (e.g., 'CC BY-SA 4.0') */
1195
1700
  license?: string;
1701
+ /** Theme-derived photographic grade (see ImageTreatment). */
1702
+ treatment?: ImageTreatment;
1703
+ /** Gaussian blur radius in px (background/atmosphere imagery). */
1704
+ blur?: number;
1196
1705
  };
1197
1706
  }
1198
1707
  /**
@@ -1201,15 +1710,68 @@ interface ImageLayer extends BaseLayer {
1201
1710
  interface TextLayer extends BaseLayer {
1202
1711
  type: 'text';
1203
1712
  content: {
1204
- /** Text to display (supports \n for line breaks) */
1713
+ /**
1714
+ * Plain text (supports \n for line breaks). Source of truth for plain
1715
+ * consumers — PDF/markdown export, search, accessibility — and the SVG
1716
+ * `<text>` fallback. When `html` is set, this is its plain-text
1717
+ * projection and must be kept in sync.
1718
+ */
1205
1719
  text: string;
1720
+ /**
1721
+ * Optional sanitized **inline** HTML for rich formatting (bold/italic/
1722
+ * links, and — for layout textboxes — headings/lists). When present the
1723
+ * renderer draws it via `<foreignObject>` instead of SVG `<text>`. Treated
1724
+ * as untrusted and re-sanitized at render time. See `RichTextLayer` in
1725
+ * `@bendyline/squisq-react`.
1726
+ */
1727
+ html?: string;
1206
1728
  /** Text styling */
1207
1729
  style: TextStyle;
1208
1730
  };
1209
1731
  }
1732
+ /**
1733
+ * A two-stop linear gradient fill, shared by shape/path fills and text
1734
+ * backgrounds. When present it overrides the solid `fill` color.
1735
+ *
1736
+ * `angle` is in degrees: 0 = top→bottom, 90 = left→right, increasing
1737
+ * clockwise. Defaults to 0. Rendered as an SVG `<linearGradient>` in
1738
+ * objectBoundingBox units so it scales with the layer's box.
1739
+ */
1740
+ interface LinearGradient {
1741
+ from: string;
1742
+ to: string;
1743
+ angle?: number;
1744
+ }
1745
+ /** Border line style, mapped to an SVG stroke-dasharray by the renderer. */
1746
+ type BorderStyle = 'solid' | 'dashed' | 'dotted';
1210
1747
  /**
1211
1748
  * Shape layer - simple geometric shapes for visual accents.
1212
1749
  */
1750
+ /**
1751
+ * Repeating SVG pattern fill for a shape (dots, grid, diagonal lines).
1752
+ * Rendered as a native `<pattern>` def — fully vector and export-safe.
1753
+ */
1754
+ interface ShapePattern {
1755
+ kind: 'dots' | 'grid' | 'diagonal';
1756
+ /** Pattern ink color. */
1757
+ color: string;
1758
+ /** Tile size in px (default 24). */
1759
+ size?: number;
1760
+ /** Pattern opacity 0–1 (default 1; tint via a translucent color or this). */
1761
+ opacity?: number;
1762
+ }
1763
+ /**
1764
+ * Procedural filter applied to a shape. `noise` renders static film grain
1765
+ * via SVG feTurbulence — deliberately not animated so frame capture and
1766
+ * the live player agree.
1767
+ */
1768
+ interface ShapeFilter {
1769
+ type: 'noise';
1770
+ /** feTurbulence base frequency (default 0.8 — fine grain). */
1771
+ baseFrequency?: number;
1772
+ /** Grain opacity 0–1 (default 0.05). */
1773
+ opacity?: number;
1774
+ }
1213
1775
  interface ShapeLayer extends BaseLayer {
1214
1776
  type: 'shape';
1215
1777
  content: {
@@ -1217,14 +1779,85 @@ interface ShapeLayer extends BaseLayer {
1217
1779
  shape: 'rect' | 'circle' | 'line';
1218
1780
  /** Fill color (CSS color or 'none') */
1219
1781
  fill?: string;
1782
+ /** Fill opacity 0–1 (applies to solid and gradient fills). */
1783
+ fillOpacity?: number;
1784
+ /** Gradient fill; overrides `fill` when set. */
1785
+ gradient?: LinearGradient;
1786
+ /** Repeating pattern fill; overrides `fill`/`gradient` when set. */
1787
+ pattern?: ShapePattern;
1788
+ /** Procedural filter (film grain). */
1789
+ filter?: ShapeFilter;
1220
1790
  /** Stroke color */
1221
1791
  stroke?: string;
1222
1792
  /** Stroke width in pixels */
1223
1793
  strokeWidth?: number;
1794
+ /** Border line style (solid/dashed/dotted). Default solid. */
1795
+ borderStyle?: BorderStyle;
1224
1796
  /** Corner radius for rect */
1225
1797
  borderRadius?: number;
1226
1798
  };
1227
1799
  }
1800
+ /**
1801
+ * End-of-line marker style for a path/connector endpoint.
1802
+ * `arrow` is a filled triangle (the classic arrowhead); `open` is a stroked
1803
+ * V; `diamond`/`circle`/`square` are filled glyphs; `none` draws nothing.
1804
+ */
1805
+ type MarkerStyle = 'none' | 'arrow' | 'open' | 'diamond' | 'circle' | 'square';
1806
+ /**
1807
+ * Path layer - renders an SVG `<path>` for arbitrary curves, connectors,
1808
+ * arrows, or filled regions. Used by the diagram template for edges
1809
+ * between nodes, the drawing template for non-rect/circle/line shapes, and
1810
+ * available to any template that needs a custom geometry.
1811
+ *
1812
+ * The `position` field carries the layer's bounding box (so animations
1813
+ * and clipping work the same as for other layers), but the actual
1814
+ * geometry is encoded in the `d` attribute using absolute SVG path
1815
+ * coordinates relative to the block viewport.
1816
+ */
1817
+ interface PathLayer extends BaseLayer {
1818
+ type: 'path';
1819
+ content: {
1820
+ /** SVG path `d` attribute (e.g. "M 10 10 L 90 90"). Absolute viewBox coords. */
1821
+ d: string;
1822
+ /**
1823
+ * Named shape kind (e.g. `'diamond'`, `'star'`, `'arrow-right'`) for
1824
+ * path layers that represent one of Squisq's standard shapes. When
1825
+ * set, the renderer re-derives `d` from the layer's `position` box
1826
+ * (resolved against the viewport) instead of using the stored `d` —
1827
+ * so the shape moves, resizes, and adapts to any aspect ratio like
1828
+ * the native rect/circle/line layers do. Plain paths (connectors,
1829
+ * freehand) leave this unset and keep their absolute `d`.
1830
+ */
1831
+ shapeKind?: string;
1832
+ /** Stroke color (default: theme text color). */
1833
+ stroke?: string;
1834
+ /** Stroke width in pixels (default: 2). */
1835
+ strokeWidth?: number;
1836
+ /** Optional fill color (default: 'none' — pure connectors). */
1837
+ fill?: string;
1838
+ /** Fill opacity 0–1 (applies to solid and gradient fills). */
1839
+ fillOpacity?: number;
1840
+ /** Gradient fill; overrides `fill` when set. */
1841
+ gradient?: LinearGradient;
1842
+ /**
1843
+ * Border line style (solid/dashed/dotted). A convenience over
1844
+ * `dasharray` for named shapes; when set the renderer derives the
1845
+ * dash pattern from it (scaled by stroke width).
1846
+ */
1847
+ borderStyle?: BorderStyle;
1848
+ /** Optional stroke dash pattern (SVG `stroke-dasharray` syntax). */
1849
+ dasharray?: string;
1850
+ /**
1851
+ * Legacy arrowhead flag. Prefer `startMarker`/`endMarker`. When those are
1852
+ * unset, `'end'`/`'start'`/`'both'` render a filled-triangle arrowhead.
1853
+ */
1854
+ arrow?: 'none' | 'end' | 'start' | 'both';
1855
+ /** Marker at the path start (overrides `arrow`). Default: derived from `arrow`. */
1856
+ startMarker?: MarkerStyle;
1857
+ /** Marker at the path end (overrides `arrow`). Default: derived from `arrow`. */
1858
+ endMarker?: MarkerStyle;
1859
+ };
1860
+ }
1228
1861
  /**
1229
1862
  * Map layer - displays a geographic map with optional markers.
1230
1863
  *
@@ -1276,6 +1909,16 @@ interface VideoLayer extends BaseLayer {
1276
1909
  clipEnd: number;
1277
1910
  /** Total source video duration (for validation) */
1278
1911
  sourceDuration?: number;
1912
+ /**
1913
+ * Seconds into the owning block before this video begins playing
1914
+ * (the block-relative `startAt`). Default 0 — plays from block start.
1915
+ */
1916
+ startAt?: number;
1917
+ /**
1918
+ * When true, the video keeps playing past the block's end (the
1919
+ * playback scheduler re-homes it to the player level). Default false.
1920
+ */
1921
+ spillover?: boolean;
1279
1922
  /** Video credit / artist name */
1280
1923
  credit?: string;
1281
1924
  /** License identifier (e.g., 'CC BY-SA 4.0') */
@@ -1367,16 +2010,34 @@ interface TextStyle {
1367
2010
  fontFamily?: string;
1368
2011
  /** Font weight */
1369
2012
  fontWeight?: 'normal' | 'bold';
2013
+ /** Font style. Also the inherited baseline for rich `content.html`. */
2014
+ fontStyle?: 'normal' | 'italic';
1370
2015
  /** Text color (CSS color) */
1371
2016
  color: string;
1372
- /** Text alignment */
2017
+ /** Horizontal text alignment within the layer's position box. */
1373
2018
  textAlign?: 'left' | 'center' | 'right';
2019
+ /**
2020
+ * Vertical alignment of the text within the layer's position box.
2021
+ * Requires a `position.height` to have an effect. When omitted, the
2022
+ * vertical baseline is derived from `position.anchor` (legacy behavior).
2023
+ */
2024
+ verticalAlign?: 'top' | 'middle' | 'bottom';
1374
2025
  /** Line height multiplier */
1375
2026
  lineHeight?: number;
1376
2027
  /** Add drop shadow for readability over images */
1377
2028
  shadow?: boolean;
1378
2029
  /** Background color for text box */
1379
2030
  background?: string;
2031
+ /** Background opacity 0–1 (applies to solid and gradient backgrounds). */
2032
+ backgroundOpacity?: number;
2033
+ /** Gradient background; overrides `background` when set. */
2034
+ backgroundGradient?: LinearGradient;
2035
+ /** Border (stroke) color for the text box. */
2036
+ borderColor?: string;
2037
+ /** Border width in pixels. A border renders when > 0 and a color is set. */
2038
+ borderWidth?: number;
2039
+ /** Border line style (solid/dashed/dotted). Default solid. */
2040
+ borderStyle?: BorderStyle;
1380
2041
  /** Padding around text (pixels) */
1381
2042
  padding?: number;
1382
2043
  /** Maximum number of lines before truncation (adds "..." to last line) */
@@ -1403,16 +2064,6 @@ interface Animation {
1403
2064
  * Available animation types.
1404
2065
  */
1405
2066
  type AnimationType = 'none' | 'fadeIn' | 'fadeOut' | 'slowZoom' | 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight' | 'typewriter';
1406
- /**
1407
- * Transition between blocks.
1408
- */
1409
- interface Transition {
1410
- /** Transition type */
1411
- type: TransitionType;
1412
- /** Duration in seconds */
1413
- duration: number;
1414
- }
1415
- type TransitionType = 'cut' | 'fade' | 'dissolve' | 'slideLeft' | 'slideRight';
1416
2067
  /**
1417
2068
  * Audio track configuration.
1418
2069
  * Articles have multiple MP3 segments (intro + sections).
@@ -1459,8 +2110,10 @@ interface CaptionPhrase {
1459
2110
  interface CaptionTrack {
1460
2111
  /** Caption phrases in chronological order */
1461
2112
  phrases: CaptionPhrase[];
1462
- /** When the captions were generated */
1463
- generatedAt: string;
2113
+ /** When the captions were generated. Optional so that conversion stays
2114
+ * deterministic — `markdownToDoc()` only sets it when the caller
2115
+ * supplies a timestamp via `captionsGeneratedAt`. */
2116
+ generatedAt?: string;
1464
2117
  /** Algorithm version for regeneration detection */
1465
2118
  version: number;
1466
2119
  }
@@ -1520,9 +2173,5 @@ declare function getBlockAtTime(blocks: Block[], time: number): Block | null;
1520
2173
  * Find the caption phrase that should be displayed at a given time.
1521
2174
  */
1522
2175
  declare function getCaptionAtTime(captions: CaptionTrack | undefined, time: number): CaptionPhrase | null;
1523
- /**
1524
- * Validate a Doc for completeness.
1525
- */
1526
- declare function validateDoc(script: Doc): string[];
1527
2176
 
1528
- export { type RightFeatureInput as $, type AccentImage as A, type Block as B, type CaptionPhrase as C, DARK_SURFACE as D, type LayoutHints as E, type FactCardInput as F, type GradientBackgroundConfig as G, type LeftFeatureInput as H, type ImageBackgroundConfig as I, type ListBlockInput as J, type MapLayer as K, LIGHT_SURFACE as L, type MapBlockInput as M, type MapMarker as N, type MapTileStyle as O, type PatternBackgroundConfig as P, type PersistentLayer as Q, type PersistentLayerConfig as R, type PersistentLayerTemplate as S, type PersistentLayerTemplateConfig as T, type PersistentLayerTemplateType as U, type PhotoGridInput as V, type Position as W, type ProgressIndicatorConfig as X, type PullQuoteInput as Y, type QuoteBlockInput as Z, type RenderStyle as _, type AccentPosition as a, type SectionHeaderInput as a0, type ShapeLayer as a1, type SolidBackgroundConfig as a2, type StartBlockConfig as a3, type StatHighlightInput as a4, type SurfaceScheme as a5, THEME_SCHEMA_VERSION as a6, type TableLayer as a7, type TableLayerStyle as a8, type TemplateBlock as a9, calculateFontScale as aA, createTemplateContext as aB, createTheme as aC, getAspectRatioString as aD, getBlockAtTime as aE, getCaptionAtTime as aF, getLayoutHints as aG, getRegisteredThemes as aH, getSafeTextBounds as aI, getSegmentAtTime as aJ, getTwoColumnPositions as aK, getViewport as aL, getViewportOrientation as aM, isPersistentLayerTemplate as aN, isTemplateBlock as aO, scaledFontSize$1 as aP, lookupRegisteredTheme as aQ, registerTheme as aR, scaledFontSize as aS, unregisterTheme as aT, validateDoc as aU, type TemplateContext as aa, type TemplateFunction as ab, type TemplateRegistry as ac, type TextLayer as ad, type TextStyle as ae, type Theme as af, type ThemeColorPalette as ag, type ThemeColorScheme as ah, type ThemeSchemaVersion as ai, type ThemeSeedColors as aj, type ThemeStyle as ak, type ThemeTypography as al, type TitleBlockInput as am, type TitleCaptionConfig as an, type Transition as ao, type TransitionType as ap, type TwoColumnInput as aq, VIEWPORT_PRESETS as ar, type VideoLayer as as, type VideoPullQuoteInput as at, type VideoWithCaptionInput as au, type ViewportConfig as av, type ViewportOrientation as aw, type ViewportPreset as ax, applySurface as ay, calculateDuration as az, type Animation as b, type AnimationType as c, type AudioBookmark as d, type AudioSegment as e, type AudioTimingData as f, type AudioTrack as g, type CaptionTrack as h, type CaptionWord as i, type ColorScheme as j, type ComparisonBarInput as k, type CornerBrandingConfig as l, DEFAULT_DOC_FONT as m, DEFAULT_TITLE_FONT as n, type DataTableInput as o, type DateEventInput as p, type DeepPartial as q, type DefinitionCardInput as r, type Doc as s, type DocBlock as t, type FontFamily as u, type FontFamilyKind as v, type FullBleedQuoteInput as w, type ImageLayer as x, type ImageWithCaptionInput as y, type Layer as z };
2177
+ export { type MapTileStyle as $, type AccentImage as A, type Block as B, type CaptionPhrase as C, DARK_SURFACE as D, type DocDiagnostic as E, type DrawingBlockInput as F, FRONTMATTER_CUSTOM_TEMPLATES_KEY as G, FRONTMATTER_CUSTOM_THEMES_KEY as H, type FactCardInput as I, type FontFamily as J, type FontFamilyKind as K, type FullBleedQuoteInput as L, type GradientBackgroundConfig as M, type ImageBackgroundConfig as N, type ImageLayer as O, type ImageTreatment as P, type ImageWithCaptionInput as Q, LIGHT_SURFACE as R, type Layer as S, type LayerRepeat as T, type LayoutHints as U, type LeftFeatureInput as V, type LinearGradient as W, type ListBlockInput as X, type MapBlockInput as Y, type MapLayer as Z, type MapMarker as _, type AccentPosition as a, getRegisteredThemes as a$, type MarkerStyle as a0, type MediaClip as a1, type PathLayer as a2, type PatternBackgroundConfig as a3, type PersistentLayer as a4, type PersistentLayerConfig as a5, type PersistentLayerTemplate as a6, type PersistentLayerTemplateConfig as a7, type PersistentLayerTemplateType as a8, type PhotoGridInput as a9, type ThemeColorPalette as aA, type ThemeColorScheme as aB, type ThemeSchemaVersion as aC, type ThemeSeedColors as aD, type ThemeStyle as aE, type ThemeTypography as aF, type TitleBlockInput as aG, type TitleCaptionConfig as aH, type TwoColumnInput as aI, VIEWPORT_PRESETS as aJ, type VideoLayer as aK, type VideoPullQuoteInput as aL, type VideoWithCaptionInput as aM, type ViewportConfig as aN, type ViewportOrientation as aO, type ViewportPreset as aP, type VignetteConfig as aQ, applySurface as aR, calculateDuration as aS, calculateFontScale as aT, createTemplateContext as aU, createTheme as aV, getAspectRatioString as aW, getBlockAtTime as aX, getCaptionAtTime as aY, getDocPlaybackDuration as aZ, getLayoutHints as a_, type Position as aa, type ProgressIndicatorConfig as ab, type PullQuoteInput as ac, type QuoteBlockInput as ad, type RawLayersInput as ae, type RenderStyle as af, type RightFeatureInput as ag, type ScheduledClip as ah, type SectionHeaderInput as ai, type ShapeFilter as aj, type ShapeLayer as ak, type ShapePattern as al, type SolidBackgroundConfig as am, type StartBlockConfig as an, type StatHighlightInput as ao, type SurfaceScheme as ap, THEME_SCHEMA_VERSION as aq, type TableLayer as ar, type TableLayerStyle as as, type TemplateBlock as at, type TemplateContext as au, type TemplateFunction as av, type TemplateRegistry as aw, type TextLayer as ax, type TextStyle as ay, type Theme as az, type AmbientGradientConfig as b, getSafeTextBounds as b0, getSegmentAtTime as b1, getTwoColumnPositions as b2, getViewport as b3, getViewportOrientation as b4, isPersistentLayerTemplate as b5, isTemplateBlock as b6, scaledFontSize$1 as b7, lookupRegisteredTheme as b8, registerTheme as b9, resolveMediaSchedule as ba, scaledFontSize as bb, unregisterTheme as bc, type Animation as c, type AnimationType as d, type AudioBookmark as e, type AudioSegment as f, type AudioTimingData as g, type AudioTrack as h, type BlockConnection as i, type BorderStyle as j, type CaptionTrack as k, type CaptionWord as l, type ColorScheme as m, type ComparisonBarInput as n, type CornerBrandingConfig as o, type CustomTemplateDefinition as p, type CustomTemplateLayer as q, DEFAULT_DOC_FONT as r, DEFAULT_TITLE_FONT as s, type DataTableInput as t, type DateEventInput as u, type DeepPartial as v, type DefinitionCardInput as w, type DiagramBlockInput as x, type Doc as y, type DocBlock as z };