@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
@@ -14,10 +14,28 @@
14
14
  * has 3-6 MP3 files (intro + sections), and blocks are grouped by segment.
15
15
  */
16
16
 
17
+ import type { Transition } from './Transitions.js';
18
+ export type { Transition, TransitionType, TransitionDirection } from './Transitions.js';
19
+
17
20
  // ============================================
18
21
  // Core Types
19
22
  // ============================================
20
23
 
24
+ /**
25
+ * A directed connection from one block to another.
26
+ *
27
+ * Populated from a heading's Pandoc-style `{connectsTo=…}` attribute
28
+ * (e.g. `## Step 1 {#step1 connectsTo=foo:flow,bar}`). Each item is
29
+ * `target` or `target:type`. Used by diagram-style layouts that draw
30
+ * edges between blocks.
31
+ */
32
+ export interface BlockConnection {
33
+ /** Target block id (matches another block's `id`). */
34
+ target: string;
35
+ /** Optional connection type/label (e.g., "flow", "requires"). */
36
+ type?: string;
37
+ }
38
+
21
39
  /**
22
40
  * Configuration for the Start/resting block shown before playback begins.
23
41
  */
@@ -38,6 +56,31 @@ export interface StartBlockConfig {
38
56
  heroLicense?: string;
39
57
  }
40
58
 
59
+ /**
60
+ * A structural problem detected while converting or validating a document.
61
+ *
62
+ * Diagnostics ride on `Doc.diagnostics` so any consumer — the editor, the
63
+ * CLI `validate` command, or an agent re-parsing its own output — gets the
64
+ * same feedback without watching the console. Conversion never throws for
65
+ * content problems; it degrades gracefully and records a diagnostic.
66
+ */
67
+ export interface DocDiagnostic {
68
+ /** `error` = the author's intent could not be honored (e.g. unparseable
69
+ * data fence); `warning` = something looks wrong but rendering proceeds
70
+ * with a fallback (e.g. unknown template name); `info` = nothing is
71
+ * broken, but the author may want to know (e.g. a redundant annotation). */
72
+ severity: 'error' | 'warning' | 'info';
73
+ /** Stable machine-readable code (e.g. `unknown-template`, `duplicate-id`,
74
+ * `unresolved-connection`, `data-fence-parse`, `missing-asset`). */
75
+ code: string;
76
+ /** Human-readable description, including a suggestion when one exists. */
77
+ message: string;
78
+ /** Id of the block the problem belongs to, when attributable. */
79
+ blockId?: string;
80
+ /** 1-based line number in the markdown source, when known. */
81
+ line?: number;
82
+ }
83
+
41
84
  /**
42
85
  * A complete visual doc for an article.
43
86
  */
@@ -88,6 +131,52 @@ export interface Doc {
88
131
  * Carries rendering hints like `document-render-as` and custom metadata.
89
132
  */
90
133
  frontmatter?: Record<string, unknown>;
134
+
135
+ /**
136
+ * User-defined block templates inlined into this doc.
137
+ *
138
+ * Populated from the markdown frontmatter key
139
+ * `squisq-custom-templates`. The template-expansion pipeline
140
+ * (`expandDocBlocks` in `doc/templates/index.ts`) merges these into
141
+ * the registry before walking blocks, so a heading annotated
142
+ * `{[myhero]}` resolves against a doc-defined template named `myhero`.
143
+ *
144
+ * Library templates (stored in localStorage on the editor side) are
145
+ * NOT auto-loaded here — applying a library template to a block
146
+ * copies its definition into this list so the doc remains
147
+ * self-sufficient for SSR and export.
148
+ */
149
+ customTemplates?: import('./CustomTemplates.js').CustomTemplateDefinition[];
150
+
151
+ /**
152
+ * User-defined themes inlined into this doc.
153
+ *
154
+ * The theme analog of {@link Doc.customTemplates}. Populated from the
155
+ * markdown frontmatter key `squisq-custom-themes`. Exactly one is active
156
+ * at a time — selected by id via `squisq-theme` / {@link Doc.themeId},
157
+ * the doc-level counterpart of a block's `{[name]}` template annotation —
158
+ * but the payload is a list so a doc can carry a small catalog and stay
159
+ * self-sufficient for SSR / export. `resolveThemeForDoc(doc, id)` resolves
160
+ * against this list first (pure, doc-scoped) before built-ins, mirroring
161
+ * how `buildRegistry` resolves custom templates.
162
+ */
163
+ customThemes?: import('./Theme.js').Theme[];
164
+
165
+ /**
166
+ * Structural problems found while building this doc (unknown templates,
167
+ * unparseable data fences, duplicate ids, unresolved connections, …).
168
+ * Populated by `markdownToDoc()`; extended by `validateMarkdownDoc()`.
169
+ * Absent when the document is clean.
170
+ */
171
+ diagnostics?: DocDiagnostic[];
172
+
173
+ /**
174
+ * Document-spanning timed media (e.g. a full-length narration MP3/MP4),
175
+ * authored as a media annotation in the preamble (before the first
176
+ * heading) with `anchor=document`. Each clip is timed from the document
177
+ * start and may play across every block. See `resolveMediaSchedule`.
178
+ */
179
+ documentMedia?: import('./Media.js').MediaClip[];
91
180
  }
92
181
 
93
182
  /**
@@ -98,15 +187,29 @@ export interface Doc {
98
187
  * - `sourceHeading` references the original MarkdownHeading node
99
188
  * - `contents` holds the body markdown between this heading and the next
100
189
  * - `children` holds sub-heading blocks (e.g., H2s under an H1)
190
+ *
191
+ * Four adjacent fields carry template information — do not confuse them:
192
+ * - `template` — the *resolved* template id this block renders with.
193
+ * - `templateOverrides` — raw `{[…]}` inline params (always strings).
194
+ * - `templateData` — structured inputs from a data fence / GFM table
195
+ * (parsed types, not strings).
196
+ * - `sourceAnnotation.template` — the raw *requested* template name for a
197
+ * standalone-annotation block (before alias resolution).
198
+ * Merge order at render time: template defaults → `templateData` →
199
+ * `templateOverrides`.
101
200
  */
102
201
  export interface Block {
103
- /** Unique identifier for this block */
202
+ /** Unique identifier for this block.
203
+ * Defaults to a slug derived from the heading text. May be overridden by
204
+ * a Pandoc-style `{#custom-id}` attribute on the heading. */
104
205
  id: string;
105
206
 
106
- /** When this block appears (seconds from start) */
207
+ /** When this block appears (seconds from start).
208
+ * Overridden by a heading attribute `startTime=…` when present. */
107
209
  startTime: number;
108
210
 
109
- /** How long this block is visible (seconds) */
211
+ /** How long this block is visible (seconds).
212
+ * Overridden by a heading attribute `duration=…` when present. */
110
213
  duration: number;
111
214
 
112
215
  /** Which audio segment this block belongs to (0-indexed) */
@@ -124,9 +227,39 @@ export interface Block {
124
227
  /** Entry transition from previous block */
125
228
  transition?: Transition;
126
229
 
127
- /** Template name that generated this block (for debugging) */
230
+ /** Resolved template id this block renders with (see the quartet note above). */
128
231
  template?: string;
129
232
 
233
+ /**
234
+ * True when `template` was chosen by content-aware auto-picking
235
+ * (`markdownToDoc`'s `autoTemplates`) rather than authored. Ephemeral:
236
+ * `docToMarkdown` does not materialize auto-picked templates as heading
237
+ * annotations, keeping the markdown round-trip lossless.
238
+ */
239
+ autoTemplate?: boolean;
240
+
241
+ /**
242
+ * True when this block was produced from a *standalone* `{[templateName …]}`
243
+ * annotation paragraph in another block's body (rather than from a heading).
244
+ * Such blocks have no `sourceHeading`; `docToMarkdown` re-emits their
245
+ * annotation as a synthesized paragraph before their `contents` so the
246
+ * markdown round-trips. See `annotationBlocks.ts`.
247
+ *
248
+ * Presence-marker: only ever `true` (never `false`). Test with
249
+ * `block.standaloneAnnotation` presence, not as a boolean toggle.
250
+ */
251
+ standaloneAnnotation?: true;
252
+
253
+ /**
254
+ * The annotation a standalone block was built from — the raw (un-resolved)
255
+ * template name and its params, in author order. Round-tripped by
256
+ * `docToMarkdown` (via `serializeAnnotation`) and read by validation as the
257
+ * raw requested template name (symmetric with
258
+ * `sourceHeading.templateAnnotation`). Present only when
259
+ * `standaloneAnnotation` is true.
260
+ */
261
+ sourceAnnotation?: { template?: string; params?: Record<string, string> };
262
+
130
263
  /**
131
264
  * Display title for template rendering.
132
265
  * Extracted from the sourceHeading when the block is created by markdownToDoc().
@@ -161,12 +294,63 @@ export interface Block {
161
294
  sourceHeading?: import('../markdown/types.js').MarkdownHeading;
162
295
 
163
296
  /**
164
- * Template overrides extracted from markdown directives.
165
- * For example, `### Data Section {template=chart colorScheme=blue}`
166
- * would produce `{ template: 'chart', colorScheme: 'blue' }`.
167
- * Allows per-block customization of template selection and parameters.
297
+ * Template overrides extracted from a heading's `{[templateName key=value]}`
298
+ * annotation. Only carries template-specific params (e.g. `colorScheme=blue`).
299
+ * Block-level metadata lives in `x` / `y` / `connectsTo` / `metadata` and is
300
+ * sourced from the Pandoc-style `{#id .class key=value}` attribute block instead.
168
301
  */
169
302
  templateOverrides?: Record<string, string>;
303
+
304
+ /**
305
+ * Structured template inputs sourced from the block's body content:
306
+ * a ```json data / ```yaml data fence under the heading, or — for the
307
+ * `dataTable` template — the first GFM table in the section. Unlike
308
+ * `templateOverrides` (always strings), values here keep their parsed
309
+ * types (arrays, numbers, nested objects). Merge order at render time:
310
+ * template defaults → `templateData` → `templateOverrides`.
311
+ */
312
+ templateData?: Record<string, unknown>;
313
+
314
+ // ── Block-level metadata from Pandoc-style `{#id .class key=value}` ──
315
+
316
+ /**
317
+ * X coordinate for diagram-style positioning (in author-defined units).
318
+ * Sourced from a heading attribute `x=…`.
319
+ */
320
+ x?: number;
321
+
322
+ /**
323
+ * Y coordinate for diagram-style positioning (in author-defined units).
324
+ * Sourced from a heading attribute `y=…`.
325
+ */
326
+ y?: number;
327
+
328
+ /**
329
+ * Outgoing connections to other blocks, by id. Sourced from a heading
330
+ * attribute `connectsTo=target1,target2:type,target3`.
331
+ */
332
+ connectsTo?: BlockConnection[];
333
+
334
+ /**
335
+ * CSS-style class tokens from a heading's Pandoc attribute (e.g. `{.important .v2}`).
336
+ * Parsed and stored; downstream consumers may use them for styling or filtering.
337
+ */
338
+ classes?: string[];
339
+
340
+ /**
341
+ * Free-form metadata for heading attribute keys outside the typed registry
342
+ * (anything that isn't `x` / `y` / `connectsTo` / `startTime` / `duration`).
343
+ * Values are raw strings exactly as authored.
344
+ */
345
+ metadata?: Record<string, string>;
346
+
347
+ /**
348
+ * Timed media clips attached to this block, authored as body-level
349
+ * `{[audio …]}` / `{[video …]}` annotations. Each clip is timed relative
350
+ * to this block's `startTime` (via `startAt`) and optionally spills past
351
+ * the block's end. See `MediaClip` / `resolveMediaSchedule`.
352
+ */
353
+ media?: import('./Media.js').MediaClip[];
170
354
  }
171
355
 
172
356
  // ============================================
@@ -177,7 +361,14 @@ export interface Block {
177
361
  * A visual element within a block.
178
362
  * Layers are composited back-to-front (first layer is background).
179
363
  */
180
- export type Layer = ImageLayer | TextLayer | ShapeLayer | MapLayer | VideoLayer | TableLayer;
364
+ export type Layer =
365
+ | ImageLayer
366
+ | TextLayer
367
+ | ShapeLayer
368
+ | PathLayer
369
+ | MapLayer
370
+ | VideoLayer
371
+ | TableLayer;
181
372
 
182
373
  interface BaseLayer {
183
374
  /** Unique identifier for this layer */
@@ -190,6 +381,25 @@ interface BaseLayer {
190
381
  animation?: Animation;
191
382
  }
192
383
 
384
+ /**
385
+ * Photographic treatment applied to an image layer — a theme-level "grade"
386
+ * so the same photo looks native in every theme. Rendered as CSS filter
387
+ * functions, which behave identically in the browser player and headless
388
+ * frame capture (video/still export).
389
+ */
390
+ export interface ImageTreatment {
391
+ /**
392
+ * mono: desaturate toward archival black & white.
393
+ * duotone: single-hue tint (hue taken from `color`).
394
+ * warm / cool: gentle temperature grade.
395
+ */
396
+ type: 'none' | 'mono' | 'duotone' | 'warm' | 'cool';
397
+ /** Blend strength 0..1. Default 0.6. */
398
+ strength?: number;
399
+ /** Duotone tint color (themes default this to their primary). */
400
+ color?: string;
401
+ }
402
+
193
403
  /**
194
404
  * Image layer - displays an image with optional Ken Burns effect.
195
405
  */
@@ -206,6 +416,10 @@ export interface ImageLayer extends BaseLayer {
206
416
  credit?: string;
207
417
  /** License identifier (e.g., 'CC BY-SA 4.0') */
208
418
  license?: string;
419
+ /** Theme-derived photographic grade (see ImageTreatment). */
420
+ treatment?: ImageTreatment;
421
+ /** Gaussian blur radius in px (background/atmosphere imagery). */
422
+ blur?: number;
209
423
  };
210
424
  }
211
425
 
@@ -215,16 +429,73 @@ export interface ImageLayer extends BaseLayer {
215
429
  export interface TextLayer extends BaseLayer {
216
430
  type: 'text';
217
431
  content: {
218
- /** Text to display (supports \n for line breaks) */
432
+ /**
433
+ * Plain text (supports \n for line breaks). Source of truth for plain
434
+ * consumers — PDF/markdown export, search, accessibility — and the SVG
435
+ * `<text>` fallback. When `html` is set, this is its plain-text
436
+ * projection and must be kept in sync.
437
+ */
219
438
  text: string;
439
+ /**
440
+ * Optional sanitized **inline** HTML for rich formatting (bold/italic/
441
+ * links, and — for layout textboxes — headings/lists). When present the
442
+ * renderer draws it via `<foreignObject>` instead of SVG `<text>`. Treated
443
+ * as untrusted and re-sanitized at render time. See `RichTextLayer` in
444
+ * `@bendyline/squisq-react`.
445
+ */
446
+ html?: string;
220
447
  /** Text styling */
221
448
  style: TextStyle;
222
449
  };
223
450
  }
224
451
 
452
+ /**
453
+ * A two-stop linear gradient fill, shared by shape/path fills and text
454
+ * backgrounds. When present it overrides the solid `fill` color.
455
+ *
456
+ * `angle` is in degrees: 0 = top→bottom, 90 = left→right, increasing
457
+ * clockwise. Defaults to 0. Rendered as an SVG `<linearGradient>` in
458
+ * objectBoundingBox units so it scales with the layer's box.
459
+ */
460
+ export interface LinearGradient {
461
+ from: string;
462
+ to: string;
463
+ angle?: number;
464
+ }
465
+
466
+ /** Border line style, mapped to an SVG stroke-dasharray by the renderer. */
467
+ export type BorderStyle = 'solid' | 'dashed' | 'dotted';
468
+
225
469
  /**
226
470
  * Shape layer - simple geometric shapes for visual accents.
227
471
  */
472
+ /**
473
+ * Repeating SVG pattern fill for a shape (dots, grid, diagonal lines).
474
+ * Rendered as a native `<pattern>` def — fully vector and export-safe.
475
+ */
476
+ export interface ShapePattern {
477
+ kind: 'dots' | 'grid' | 'diagonal';
478
+ /** Pattern ink color. */
479
+ color: string;
480
+ /** Tile size in px (default 24). */
481
+ size?: number;
482
+ /** Pattern opacity 0–1 (default 1; tint via a translucent color or this). */
483
+ opacity?: number;
484
+ }
485
+
486
+ /**
487
+ * Procedural filter applied to a shape. `noise` renders static film grain
488
+ * via SVG feTurbulence — deliberately not animated so frame capture and
489
+ * the live player agree.
490
+ */
491
+ export interface ShapeFilter {
492
+ type: 'noise';
493
+ /** feTurbulence base frequency (default 0.8 — fine grain). */
494
+ baseFrequency?: number;
495
+ /** Grain opacity 0–1 (default 0.05). */
496
+ opacity?: number;
497
+ }
498
+
228
499
  export interface ShapeLayer extends BaseLayer {
229
500
  type: 'shape';
230
501
  content: {
@@ -232,15 +503,88 @@ export interface ShapeLayer extends BaseLayer {
232
503
  shape: 'rect' | 'circle' | 'line';
233
504
  /** Fill color (CSS color or 'none') */
234
505
  fill?: string;
506
+ /** Fill opacity 0–1 (applies to solid and gradient fills). */
507
+ fillOpacity?: number;
508
+ /** Gradient fill; overrides `fill` when set. */
509
+ gradient?: LinearGradient;
510
+ /** Repeating pattern fill; overrides `fill`/`gradient` when set. */
511
+ pattern?: ShapePattern;
512
+ /** Procedural filter (film grain). */
513
+ filter?: ShapeFilter;
235
514
  /** Stroke color */
236
515
  stroke?: string;
237
516
  /** Stroke width in pixels */
238
517
  strokeWidth?: number;
518
+ /** Border line style (solid/dashed/dotted). Default solid. */
519
+ borderStyle?: BorderStyle;
239
520
  /** Corner radius for rect */
240
521
  borderRadius?: number;
241
522
  };
242
523
  }
243
524
 
525
+ /**
526
+ * End-of-line marker style for a path/connector endpoint.
527
+ * `arrow` is a filled triangle (the classic arrowhead); `open` is a stroked
528
+ * V; `diamond`/`circle`/`square` are filled glyphs; `none` draws nothing.
529
+ */
530
+ export type MarkerStyle = 'none' | 'arrow' | 'open' | 'diamond' | 'circle' | 'square';
531
+
532
+ /**
533
+ * Path layer - renders an SVG `<path>` for arbitrary curves, connectors,
534
+ * arrows, or filled regions. Used by the diagram template for edges
535
+ * between nodes, the drawing template for non-rect/circle/line shapes, and
536
+ * available to any template that needs a custom geometry.
537
+ *
538
+ * The `position` field carries the layer's bounding box (so animations
539
+ * and clipping work the same as for other layers), but the actual
540
+ * geometry is encoded in the `d` attribute using absolute SVG path
541
+ * coordinates relative to the block viewport.
542
+ */
543
+ export interface PathLayer extends BaseLayer {
544
+ type: 'path';
545
+ content: {
546
+ /** SVG path `d` attribute (e.g. "M 10 10 L 90 90"). Absolute viewBox coords. */
547
+ d: string;
548
+ /**
549
+ * Named shape kind (e.g. `'diamond'`, `'star'`, `'arrow-right'`) for
550
+ * path layers that represent one of Squisq's standard shapes. When
551
+ * set, the renderer re-derives `d` from the layer's `position` box
552
+ * (resolved against the viewport) instead of using the stored `d` —
553
+ * so the shape moves, resizes, and adapts to any aspect ratio like
554
+ * the native rect/circle/line layers do. Plain paths (connectors,
555
+ * freehand) leave this unset and keep their absolute `d`.
556
+ */
557
+ shapeKind?: string;
558
+ /** Stroke color (default: theme text color). */
559
+ stroke?: string;
560
+ /** Stroke width in pixels (default: 2). */
561
+ strokeWidth?: number;
562
+ /** Optional fill color (default: 'none' — pure connectors). */
563
+ fill?: string;
564
+ /** Fill opacity 0–1 (applies to solid and gradient fills). */
565
+ fillOpacity?: number;
566
+ /** Gradient fill; overrides `fill` when set. */
567
+ gradient?: LinearGradient;
568
+ /**
569
+ * Border line style (solid/dashed/dotted). A convenience over
570
+ * `dasharray` for named shapes; when set the renderer derives the
571
+ * dash pattern from it (scaled by stroke width).
572
+ */
573
+ borderStyle?: BorderStyle;
574
+ /** Optional stroke dash pattern (SVG `stroke-dasharray` syntax). */
575
+ dasharray?: string;
576
+ /**
577
+ * Legacy arrowhead flag. Prefer `startMarker`/`endMarker`. When those are
578
+ * unset, `'end'`/`'start'`/`'both'` render a filled-triangle arrowhead.
579
+ */
580
+ arrow?: 'none' | 'end' | 'start' | 'both';
581
+ /** Marker at the path start (overrides `arrow`). Default: derived from `arrow`. */
582
+ startMarker?: MarkerStyle;
583
+ /** Marker at the path end (overrides `arrow`). Default: derived from `arrow`. */
584
+ endMarker?: MarkerStyle;
585
+ };
586
+ }
587
+
244
588
  /**
245
589
  * Map layer - displays a geographic map with optional markers.
246
590
  *
@@ -293,6 +637,16 @@ export interface VideoLayer extends BaseLayer {
293
637
  clipEnd: number;
294
638
  /** Total source video duration (for validation) */
295
639
  sourceDuration?: number;
640
+ /**
641
+ * Seconds into the owning block before this video begins playing
642
+ * (the block-relative `startAt`). Default 0 — plays from block start.
643
+ */
644
+ startAt?: number;
645
+ /**
646
+ * When true, the video keeps playing past the block's end (the
647
+ * playback scheduler re-homes it to the player level). Default false.
648
+ */
649
+ spillover?: boolean;
296
650
  /** Video credit / artist name */
297
651
  credit?: string;
298
652
  /** License identifier (e.g., 'CC BY-SA 4.0') */
@@ -399,16 +753,34 @@ export interface TextStyle {
399
753
  fontFamily?: string;
400
754
  /** Font weight */
401
755
  fontWeight?: 'normal' | 'bold';
756
+ /** Font style. Also the inherited baseline for rich `content.html`. */
757
+ fontStyle?: 'normal' | 'italic';
402
758
  /** Text color (CSS color) */
403
759
  color: string;
404
- /** Text alignment */
760
+ /** Horizontal text alignment within the layer's position box. */
405
761
  textAlign?: 'left' | 'center' | 'right';
762
+ /**
763
+ * Vertical alignment of the text within the layer's position box.
764
+ * Requires a `position.height` to have an effect. When omitted, the
765
+ * vertical baseline is derived from `position.anchor` (legacy behavior).
766
+ */
767
+ verticalAlign?: 'top' | 'middle' | 'bottom';
406
768
  /** Line height multiplier */
407
769
  lineHeight?: number;
408
770
  /** Add drop shadow for readability over images */
409
771
  shadow?: boolean;
410
772
  /** Background color for text box */
411
773
  background?: string;
774
+ /** Background opacity 0–1 (applies to solid and gradient backgrounds). */
775
+ backgroundOpacity?: number;
776
+ /** Gradient background; overrides `background` when set. */
777
+ backgroundGradient?: LinearGradient;
778
+ /** Border (stroke) color for the text box. */
779
+ borderColor?: string;
780
+ /** Border width in pixels. A border renders when > 0 and a color is set. */
781
+ borderWidth?: number;
782
+ /** Border line style (solid/dashed/dotted). Default solid. */
783
+ borderStyle?: BorderStyle;
412
784
  /** Padding around text (pixels) */
413
785
  padding?: number;
414
786
  /** Maximum number of lines before truncation (adds "..." to last line) */
@@ -451,23 +823,6 @@ export type AnimationType =
451
823
  | 'panRight'
452
824
  | 'typewriter'; // Text appears letter by letter
453
825
 
454
- /**
455
- * Transition between blocks.
456
- */
457
- export interface Transition {
458
- /** Transition type */
459
- type: TransitionType;
460
- /** Duration in seconds */
461
- duration: number;
462
- }
463
-
464
- export type TransitionType =
465
- | 'cut' // Instant switch
466
- | 'fade' // Cross-fade
467
- | 'dissolve' // Soft dissolve
468
- | 'slideLeft' // New block enters from right
469
- | 'slideRight'; // New block enters from left
470
-
471
826
  // ============================================
472
827
  // Audio Configuration
473
828
  // ============================================
@@ -525,8 +880,10 @@ export interface CaptionPhrase {
525
880
  export interface CaptionTrack {
526
881
  /** Caption phrases in chronological order */
527
882
  phrases: CaptionPhrase[];
528
- /** When the captions were generated */
529
- generatedAt: string;
883
+ /** When the captions were generated. Optional so that conversion stays
884
+ * deterministic — `markdownToDoc()` only sets it when the caller
885
+ * supplies a timestamp via `captionsGeneratedAt`. */
886
+ generatedAt?: string;
530
887
  /** Algorithm version for regeneration detection */
531
888
  version: number;
532
889
  }
@@ -630,38 +987,3 @@ export function getCaptionAtTime(
630
987
  }
631
988
  return null;
632
989
  }
633
-
634
- /**
635
- * Validate a Doc for completeness.
636
- */
637
- export function validateDoc(script: Doc): string[] {
638
- const errors: string[] = [];
639
-
640
- if (!script.articleId) {
641
- errors.push('Missing articleId');
642
- }
643
-
644
- if (!script.blocks || script.blocks.length === 0) {
645
- errors.push('No blocks defined');
646
- }
647
-
648
- if (!script.audio || !script.audio.segments || script.audio.segments.length === 0) {
649
- errors.push('No audio segments defined');
650
- }
651
-
652
- // Check for gaps in block coverage
653
- const totalDuration = script.duration;
654
- let covered = 0;
655
- for (const block of script.blocks) {
656
- if (block.startTime > covered + 0.1) {
657
- errors.push(`Gap in blocks: ${covered}s to ${block.startTime}s`);
658
- }
659
- covered = Math.max(covered, block.startTime + block.duration);
660
- }
661
-
662
- if (covered < totalDuration - 0.1) {
663
- errors.push(`Blocks end at ${covered}s but audio is ${totalDuration}s`);
664
- }
665
-
666
- return errors;
667
- }
@@ -7,7 +7,7 @@
7
7
  * bytes in `assets/` and version snapshots in `.versions/`.
8
8
  *
9
9
  * Layers compose the existing core {@link Layer} types (image / text /
10
- * shape) so renderers can share infrastructure with the doc system.
10
+ * shape / path) so renderers can share infrastructure with the doc system.
11
11
  * Editor-specific metadata (name, visibility, lock, opacity, blendMode)
12
12
  * lives in {@link EditorLayerMeta} and is merged onto each layer. The
13
13
  * {@link Layer.animation} field is permitted but ignored by the image
@@ -17,7 +17,7 @@
17
17
  * relative to the sidecar root, keeping the document portable.
18
18
  */
19
19
 
20
- import type { ImageLayer, ShapeLayer, TextLayer } from './Doc.js';
20
+ import type { ImageLayer, PathLayer, ShapeLayer, TextLayer } from './Doc.js';
21
21
 
22
22
  // ============================================
23
23
  // Layer type
@@ -44,8 +44,15 @@ export interface EditorLayerMeta {
44
44
  blendMode?: GlobalCompositeOperation;
45
45
  }
46
46
 
47
- /** A layer in an {@link ImageEditDoc}. */
48
- export type ImageEditLayer = (ImageLayer | TextLayer | ShapeLayer) & EditorLayerMeta;
47
+ /**
48
+ * A layer in an {@link ImageEditDoc}.
49
+ *
50
+ * `path` layers carry the full drawing-shape vocabulary: when
51
+ * `content.shapeKind` is set (e.g. `'diamond'`, `'star'`, `'arrow-right'`),
52
+ * the renderer re-derives the SVG `d` from the layer's `position` box via
53
+ * `shapePath`, so the shape moves/resizes like the native rect/circle/line.
54
+ */
55
+ export type ImageEditLayer = (ImageLayer | TextLayer | ShapeLayer | PathLayer) & EditorLayerMeta;
49
56
 
50
57
  /** The layer kinds the image editor supports. */
51
58
  export type ImageEditLayerKind = ImageEditLayer['type'];