@bendyline/squisq 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/dist/{Doc-BeSeH2iX.d.ts → Doc-BsMkmzH8.d.ts} +838 -268
  2. package/dist/{ImageEditDoc-BTIvw0oq.d.ts → ImageEditDoc-DvoeHMP9.d.ts} +11 -4
  3. package/dist/{chunk-ZTX4PFFT.js → chunk-2OIBZYKP.js} +75 -2
  4. package/dist/chunk-2OIBZYKP.js.map +1 -0
  5. package/dist/chunk-2UQOHYE2.js +45 -0
  6. package/dist/chunk-2UQOHYE2.js.map +1 -0
  7. package/dist/{chunk-B4EEON3N.js → chunk-4AKUQPEE.js} +536 -103
  8. package/dist/chunk-4AKUQPEE.js.map +1 -0
  9. package/dist/chunk-4ZFRZBU2.js +6582 -0
  10. package/dist/chunk-4ZFRZBU2.js.map +1 -0
  11. package/dist/{chunk-57CVQCFX.js → chunk-7MQGNR76.js} +79 -3
  12. package/dist/chunk-7MQGNR76.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-MYIH7FWD.js → chunk-FBKP5CAO.js} +1 -1
  16. package/dist/chunk-FBKP5CAO.js.map +1 -0
  17. package/dist/chunk-KZNXZ5SM.js +333 -0
  18. package/dist/chunk-KZNXZ5SM.js.map +1 -0
  19. package/dist/{chunk-WMOI7FN3.js → chunk-LRCV7N7F.js} +12 -2
  20. package/dist/{chunk-WMOI7FN3.js.map → chunk-LRCV7N7F.js.map} +1 -1
  21. package/dist/{chunk-3K5OG6KI.js → chunk-OH43RPTP.js} +403 -187
  22. package/dist/chunk-OH43RPTP.js.map +1 -0
  23. package/dist/{chunk-H3AXU4MJ.js → chunk-OKJOQXK4.js} +149 -2
  24. package/dist/chunk-OKJOQXK4.js.map +1 -0
  25. package/dist/chunk-PGGGKYWG.js +1 -0
  26. package/dist/chunk-PGGGKYWG.js.map +1 -0
  27. package/dist/{chunk-7UDSRZKG.js → chunk-Q3ROPT5H.js} +35 -14
  28. package/dist/chunk-Q3ROPT5H.js.map +1 -0
  29. package/dist/chunk-QE7TWGVT.js +178 -0
  30. package/dist/chunk-QE7TWGVT.js.map +1 -0
  31. package/dist/chunk-RUDYOTA6.js +345 -0
  32. package/dist/chunk-RUDYOTA6.js.map +1 -0
  33. package/dist/chunk-TVSGLUID.js +596 -0
  34. package/dist/chunk-TVSGLUID.js.map +1 -0
  35. package/dist/chunk-ZLQKNOZQ.js +1 -0
  36. package/dist/chunk-ZLQKNOZQ.js.map +1 -0
  37. package/dist/doc/index.d.ts +4 -4
  38. package/dist/doc/index.js +124 -6
  39. package/dist/generate/index.d.ts +9 -2
  40. package/dist/generate/index.js +1 -1
  41. package/dist/icons/index.d.ts +3 -26
  42. package/dist/icons/index.js +13 -0
  43. package/dist/icons/inlineIconMarker.d.ts +1 -0
  44. package/dist/icons/inlineIconMarker.js +15 -0
  45. package/dist/icons/inlineIconMarker.js.map +1 -0
  46. package/dist/imageEdit/index.d.ts +11 -5
  47. package/dist/imageEdit/index.js +4 -1
  48. package/dist/index.d.ts +11 -8
  49. package/dist/index.js +243 -24
  50. package/dist/inlineIconMarker-CiE8Hlvz.d.ts +59 -0
  51. package/dist/markdown/index.d.ts +212 -3
  52. package/dist/markdown/index.js +40 -7
  53. package/dist/recommend/index.d.ts +1 -1
  54. package/dist/recommend/index.js +3 -2
  55. package/dist/schemas/index.d.ts +28 -8
  56. package/dist/schemas/index.js +35 -5
  57. package/dist/spatial/index.d.ts +9 -4
  58. package/dist/spatial/index.js +1 -1
  59. package/dist/story/index.d.ts +906 -40
  60. package/dist/story/index.js +124 -6
  61. package/dist/{themeLibrary-BHjJ0Qx6.d.ts → themeLibrary-DQQKuRMx.d.ts} +1 -1
  62. package/dist/transform/index.d.ts +48 -10
  63. package/dist/transform/index.js +17 -481
  64. package/dist/transform/index.js.map +1 -1
  65. package/dist/{types-BdZkdJ3z.d.ts → types-DlAZ7MW4.d.ts} +115 -1
  66. package/package.json +6 -1
  67. package/src/__tests__/animationUtils.test.ts +40 -0
  68. package/src/__tests__/annotationCoercion.test.ts +243 -0
  69. package/src/__tests__/applyRenderStyle.test.ts +146 -0
  70. package/src/__tests__/atmosphereLayers.test.ts +108 -0
  71. package/src/__tests__/attrTokens.test.ts +162 -0
  72. package/src/__tests__/customTemplateSample.test.ts +83 -0
  73. package/src/__tests__/customTemplatesFrontmatter.test.ts +205 -0
  74. package/src/__tests__/customThemesFrontmatter.test.ts +118 -0
  75. package/src/__tests__/diagramBlock.test.ts +122 -0
  76. package/src/__tests__/diagramLayout.test.ts +134 -0
  77. package/src/__tests__/drawing.test.ts +109 -0
  78. package/src/__tests__/drawingBlock.test.ts +124 -0
  79. package/src/__tests__/drawingLayout.test.ts +171 -0
  80. package/src/__tests__/geohash.test.ts +6 -5
  81. package/src/__tests__/getLayers.test.ts +6 -2
  82. package/src/__tests__/getLayersFallback.test.ts +103 -0
  83. package/src/__tests__/imageEditExportSvg.test.ts +70 -0
  84. package/src/__tests__/imageEditVersions.test.ts +72 -0
  85. package/src/__tests__/inlineIconMarker.test.ts +36 -0
  86. package/src/__tests__/layoutBlock.test.ts +83 -0
  87. package/src/__tests__/layoutLayout.test.ts +169 -0
  88. package/src/__tests__/markdown.test.ts +23 -0
  89. package/src/__tests__/markdownSanitize.test.ts +64 -0
  90. package/src/__tests__/markdownToDoc.test.ts +223 -2
  91. package/src/__tests__/mediaAnnotations.test.ts +64 -0
  92. package/src/__tests__/mediaSchedule.test.ts +118 -0
  93. package/src/__tests__/pandocAttrParse.test.ts +187 -0
  94. package/src/__tests__/resolveDocTheme.test.ts +55 -0
  95. package/src/__tests__/rootExports.test.ts +43 -0
  96. package/src/__tests__/shapeGeometry.test.ts +89 -0
  97. package/src/__tests__/shapeVocabulary.test.ts +133 -0
  98. package/src/__tests__/structuredData.test.ts +179 -0
  99. package/src/__tests__/templateAnnotationParse.test.ts +108 -0
  100. package/src/__tests__/templateMetadata.test.ts +21 -0
  101. package/src/__tests__/templates.test.ts +145 -4
  102. package/src/__tests__/themeCompile.test.ts +42 -0
  103. package/src/__tests__/themeValidator.test.ts +6 -0
  104. package/src/__tests__/transformV2.test.ts +137 -0
  105. package/src/__tests__/transitionNormalize.test.ts +61 -0
  106. package/src/__tests__/validateDoc.test.ts +100 -0
  107. package/src/doc/customTemplatesFrontmatter.ts +308 -0
  108. package/src/doc/customThemesFrontmatter.ts +90 -0
  109. package/src/doc/docToMarkdown.ts +73 -4
  110. package/src/doc/getLayers.ts +40 -17
  111. package/src/doc/index.ts +35 -1
  112. package/src/doc/markdownToDoc.ts +333 -12
  113. package/src/doc/mediaAnnotations.ts +118 -0
  114. package/src/doc/resolveDocTheme.ts +55 -0
  115. package/src/doc/structuredData.ts +230 -0
  116. package/src/doc/templateInputs.ts +240 -0
  117. package/src/doc/templates/__tests__/customTemplate.test.ts +151 -0
  118. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +110 -0
  119. package/src/doc/templates/accentImage.ts +30 -6
  120. package/src/doc/templates/captionUtils.ts +23 -0
  121. package/src/doc/templates/comparisonBar.ts +26 -18
  122. package/src/doc/templates/coverBlock.ts +7 -4
  123. package/src/doc/templates/customTemplate.ts +45 -0
  124. package/src/doc/templates/dataTable.ts +37 -19
  125. package/src/doc/templates/dateEvent.ts +41 -20
  126. package/src/doc/templates/definitionCard.ts +26 -18
  127. package/src/doc/templates/diagramBlock.ts +204 -0
  128. package/src/doc/templates/diagramLayout.ts +159 -0
  129. package/src/doc/templates/drawingBlock.ts +325 -0
  130. package/src/doc/templates/drawingLayout.ts +539 -0
  131. package/src/doc/templates/factCard.ts +51 -25
  132. package/src/doc/templates/fallbackBlock.ts +123 -0
  133. package/src/doc/templates/featureBlock.ts +20 -13
  134. package/src/doc/templates/fullBleedQuote.ts +18 -8
  135. package/src/doc/templates/imageWithCaption.ts +22 -9
  136. package/src/doc/templates/index.ts +215 -46
  137. package/src/doc/templates/layoutBlock.ts +43 -0
  138. package/src/doc/templates/layoutLayout.ts +243 -0
  139. package/src/doc/templates/listBlock.ts +56 -30
  140. package/src/doc/templates/mapBlock.ts +15 -12
  141. package/src/doc/templates/metadata.ts +120 -0
  142. package/src/doc/templates/persistentLayers.ts +129 -11
  143. package/src/doc/templates/photoGrid.ts +23 -12
  144. package/src/doc/templates/pullQuote.ts +30 -12
  145. package/src/doc/templates/quoteBlock.ts +54 -29
  146. package/src/doc/templates/sectionHeader.ts +25 -11
  147. package/src/doc/templates/statHighlight.ts +39 -22
  148. package/src/doc/templates/titleBlock.ts +59 -21
  149. package/src/doc/templates/tokens/__tests__/resolveTokens.test.ts +221 -0
  150. package/src/doc/templates/tokens/resolveTokens.ts +0 -0
  151. package/src/doc/templates/twoColumn.ts +48 -30
  152. package/src/doc/templates/videoPullQuote.ts +22 -12
  153. package/src/doc/templates/videoWithCaption.ts +13 -8
  154. package/src/doc/utils/animationUtils.ts +267 -3
  155. package/src/doc/utils/applyRenderStyle.ts +113 -0
  156. package/src/doc/utils/imageTreatment.ts +70 -0
  157. package/src/doc/utils/shapeGeometry.ts +464 -0
  158. package/src/doc/utils/themeUtils.ts +75 -1
  159. package/src/doc/validate.ts +476 -0
  160. package/src/generate/slideshowGenerator.ts +7 -0
  161. package/src/icons/index.ts +8 -0
  162. package/src/icons/inlineIconMarker.ts +73 -0
  163. package/src/imageEdit/export.ts +85 -2
  164. package/src/imageEdit/index.ts +1 -0
  165. package/src/imageEdit/persistence.ts +51 -0
  166. package/src/index.ts +2 -0
  167. package/src/markdown/annotationCoercion.ts +295 -0
  168. package/src/markdown/attrTokens.ts +222 -0
  169. package/src/markdown/convert.ts +229 -26
  170. package/src/markdown/index.ts +36 -0
  171. package/src/markdown/sanitize.ts +298 -0
  172. package/src/markdown/stringify.ts +84 -3
  173. package/src/markdown/types.ts +66 -0
  174. package/src/markdown/utils.ts +49 -1
  175. package/src/recommend/templates.ts +24 -0
  176. package/src/schemas/BlockTemplates.ts +133 -4
  177. package/src/schemas/CustomTemplates.ts +74 -0
  178. package/src/schemas/Doc.ts +353 -29
  179. package/src/schemas/ImageEditDoc.ts +11 -4
  180. package/src/schemas/Media.ts +169 -0
  181. package/src/schemas/Theme.ts +23 -1
  182. package/src/schemas/Transitions.ts +224 -0
  183. package/src/schemas/colorUtils.ts +33 -0
  184. package/src/schemas/fontStacks.ts +5 -2
  185. package/src/schemas/index.ts +3 -0
  186. package/src/schemas/themeCompile.ts +23 -4
  187. package/src/schemas/themeValidator.ts +49 -11
  188. package/src/schemas/themes/cinematic.json +69 -12
  189. package/src/schemas/themes/documentary.json +58 -11
  190. package/src/schemas/themes/gezellig.json +60 -11
  191. package/src/schemas/themes/magazine.json +54 -9
  192. package/src/schemas/themes/morning-light.json +52 -9
  193. package/src/schemas/themes/standard-dark.json +48 -13
  194. package/src/schemas/themes/standard.json +44 -12
  195. package/src/schemas/themes/tech-dark.json +65 -12
  196. package/src/spatial/Geohash.ts +52 -21
  197. package/src/transform/applyTransform.ts +43 -4
  198. package/src/transform/index.ts +1 -0
  199. package/src/transform/registry.ts +44 -13
  200. package/src/transform/styles/dataDriven.ts +2 -1
  201. package/src/transform/styles/documentary.ts +2 -1
  202. package/src/transform/styles/magazine.ts +3 -1
  203. package/src/transform/styles/minimal.ts +2 -1
  204. package/src/transform/styles/narrative.ts +3 -1
  205. package/src/transform/templateSelector.ts +87 -11
  206. package/src/transform/types.ts +35 -2
  207. package/dist/chunk-3K5OG6KI.js.map +0 -1
  208. package/dist/chunk-57CVQCFX.js.map +0 -1
  209. package/dist/chunk-5WFRKURX.js.map +0 -1
  210. package/dist/chunk-7UDSRZKG.js.map +0 -1
  211. package/dist/chunk-B4EEON3N.js.map +0 -1
  212. package/dist/chunk-H3AXU4MJ.js.map +0 -1
  213. package/dist/chunk-KSWJR66U.js +0 -3706
  214. package/dist/chunk-KSWJR66U.js.map +0 -1
  215. package/dist/chunk-MYIH7FWD.js.map +0 -1
  216. package/dist/chunk-ZTX4PFFT.js.map +0 -1
@@ -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,30 @@ 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). */
71
+ severity: 'error' | 'warning';
72
+ /** Stable machine-readable code (e.g. `unknown-template`, `duplicate-id`,
73
+ * `unresolved-connection`, `data-fence-parse`, `missing-asset`). */
74
+ code: string;
75
+ /** Human-readable description, including a suggestion when one exists. */
76
+ message: string;
77
+ /** Id of the block the problem belongs to, when attributable. */
78
+ blockId?: string;
79
+ /** 1-based line number in the markdown source, when known. */
80
+ line?: number;
81
+ }
82
+
41
83
  /**
42
84
  * A complete visual doc for an article.
43
85
  */
@@ -88,6 +130,52 @@ export interface Doc {
88
130
  * Carries rendering hints like `document-render-as` and custom metadata.
89
131
  */
90
132
  frontmatter?: Record<string, unknown>;
133
+
134
+ /**
135
+ * User-defined block templates inlined into this doc.
136
+ *
137
+ * Populated from the markdown frontmatter key
138
+ * `squisq-custom-templates`. The template-expansion pipeline
139
+ * (`expandDocBlocks`) merges these into the registry before walking
140
+ * blocks, so a heading annotated `{[myhero]}` resolves against a
141
+ * doc-defined template named `myhero`.
142
+ *
143
+ * Library templates (stored in localStorage on the editor side) are
144
+ * NOT auto-loaded here — applying a library template to a block
145
+ * copies its definition into this list so the doc remains
146
+ * self-sufficient for SSR and export.
147
+ */
148
+ customTemplates?: import('./CustomTemplates.js').CustomTemplateDefinition[];
149
+
150
+ /**
151
+ * User-defined themes inlined into this doc.
152
+ *
153
+ * The theme analog of {@link Doc.customTemplates}. Populated from the
154
+ * markdown frontmatter key `squisq-custom-themes`. Exactly one is active
155
+ * at a time — selected by id via `squisq-theme` / {@link Doc.themeId},
156
+ * the doc-level counterpart of a block's `{[name]}` template annotation —
157
+ * but the payload is a list so a doc can carry a small catalog and stay
158
+ * self-sufficient for SSR / export. `resolveThemeForDoc(doc, id)` resolves
159
+ * against this list first (pure, doc-scoped) before built-ins, mirroring
160
+ * how `buildRegistry` resolves custom templates.
161
+ */
162
+ customThemes?: import('./Theme.js').Theme[];
163
+
164
+ /**
165
+ * Structural problems found while building this doc (unknown templates,
166
+ * unparseable data fences, duplicate ids, unresolved connections, …).
167
+ * Populated by `markdownToDoc()`; extended by `validateMarkdownDoc()`.
168
+ * Absent when the document is clean.
169
+ */
170
+ diagnostics?: DocDiagnostic[];
171
+
172
+ /**
173
+ * Document-spanning timed media (e.g. a full-length narration MP3/MP4),
174
+ * authored as a media annotation in the preamble (before the first
175
+ * heading) with `anchor=document`. Each clip is timed from the document
176
+ * start and may play across every block. See `resolveMediaSchedule`.
177
+ */
178
+ documentMedia?: import('./Media.js').MediaClip[];
91
179
  }
92
180
 
93
181
  /**
@@ -100,13 +188,17 @@ export interface Doc {
100
188
  * - `children` holds sub-heading blocks (e.g., H2s under an H1)
101
189
  */
102
190
  export interface Block {
103
- /** Unique identifier for this block */
191
+ /** Unique identifier for this block.
192
+ * Defaults to a slug derived from the heading text. May be overridden by
193
+ * a Pandoc-style `{#custom-id}` attribute on the heading. */
104
194
  id: string;
105
195
 
106
- /** When this block appears (seconds from start) */
196
+ /** When this block appears (seconds from start).
197
+ * Overridden by a heading attribute `startTime=…` when present. */
107
198
  startTime: number;
108
199
 
109
- /** How long this block is visible (seconds) */
200
+ /** How long this block is visible (seconds).
201
+ * Overridden by a heading attribute `duration=…` when present. */
110
202
  duration: number;
111
203
 
112
204
  /** Which audio segment this block belongs to (0-indexed) */
@@ -127,6 +219,14 @@ export interface Block {
127
219
  /** Template name that generated this block (for debugging) */
128
220
  template?: string;
129
221
 
222
+ /**
223
+ * True when `template` was chosen by content-aware auto-picking
224
+ * (`markdownToDoc`'s `autoTemplates`) rather than authored. Ephemeral:
225
+ * `docToMarkdown` does not materialize auto-picked templates as heading
226
+ * annotations, keeping the markdown round-trip lossless.
227
+ */
228
+ autoTemplate?: boolean;
229
+
130
230
  /**
131
231
  * Display title for template rendering.
132
232
  * Extracted from the sourceHeading when the block is created by markdownToDoc().
@@ -161,12 +261,63 @@ export interface Block {
161
261
  sourceHeading?: import('../markdown/types.js').MarkdownHeading;
162
262
 
163
263
  /**
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.
264
+ * Template overrides extracted from a heading's `{[templateName key=value]}`
265
+ * annotation. Only carries template-specific params (e.g. `colorScheme=blue`).
266
+ * Block-level metadata lives in `x` / `y` / `connectsTo` / `metadata` and is
267
+ * sourced from the Pandoc-style `{#id .class key=value}` attribute block instead.
168
268
  */
169
269
  templateOverrides?: Record<string, string>;
270
+
271
+ /**
272
+ * Structured template inputs sourced from the block's body content:
273
+ * a ```json data / ```yaml data fence under the heading, or — for the
274
+ * `dataTable` template — the first GFM table in the section. Unlike
275
+ * `templateOverrides` (always strings), values here keep their parsed
276
+ * types (arrays, numbers, nested objects). Merge order at render time:
277
+ * template defaults → `templateData` → `templateOverrides`.
278
+ */
279
+ templateData?: Record<string, unknown>;
280
+
281
+ // ── Block-level metadata from Pandoc-style `{#id .class key=value}` ──
282
+
283
+ /**
284
+ * X coordinate for diagram-style positioning (in author-defined units).
285
+ * Sourced from a heading attribute `x=…`.
286
+ */
287
+ x?: number;
288
+
289
+ /**
290
+ * Y coordinate for diagram-style positioning (in author-defined units).
291
+ * Sourced from a heading attribute `y=…`.
292
+ */
293
+ y?: number;
294
+
295
+ /**
296
+ * Outgoing connections to other blocks, by id. Sourced from a heading
297
+ * attribute `connectsTo=target1,target2:type,target3`.
298
+ */
299
+ connectsTo?: BlockConnection[];
300
+
301
+ /**
302
+ * CSS-style class tokens from a heading's Pandoc attribute (e.g. `{.important .v2}`).
303
+ * Parsed and stored; downstream consumers may use them for styling or filtering.
304
+ */
305
+ classes?: string[];
306
+
307
+ /**
308
+ * Free-form metadata for heading attribute keys outside the typed registry
309
+ * (anything that isn't `x` / `y` / `connectsTo` / `startTime` / `duration`).
310
+ * Values are raw strings exactly as authored.
311
+ */
312
+ metadata?: Record<string, string>;
313
+
314
+ /**
315
+ * Timed media clips attached to this block, authored as body-level
316
+ * `{[audio …]}` / `{[video …]}` annotations. Each clip is timed relative
317
+ * to this block's `startTime` (via `startAt`) and optionally spills past
318
+ * the block's end. See `MediaClip` / `resolveMediaSchedule`.
319
+ */
320
+ media?: import('./Media.js').MediaClip[];
170
321
  }
171
322
 
172
323
  // ============================================
@@ -177,7 +328,14 @@ export interface Block {
177
328
  * A visual element within a block.
178
329
  * Layers are composited back-to-front (first layer is background).
179
330
  */
180
- export type Layer = ImageLayer | TextLayer | ShapeLayer | MapLayer | VideoLayer | TableLayer;
331
+ export type Layer =
332
+ | ImageLayer
333
+ | TextLayer
334
+ | ShapeLayer
335
+ | PathLayer
336
+ | MapLayer
337
+ | VideoLayer
338
+ | TableLayer;
181
339
 
182
340
  interface BaseLayer {
183
341
  /** Unique identifier for this layer */
@@ -190,6 +348,25 @@ interface BaseLayer {
190
348
  animation?: Animation;
191
349
  }
192
350
 
351
+ /**
352
+ * Photographic treatment applied to an image layer — a theme-level "grade"
353
+ * so the same photo looks native in every theme. Rendered as CSS filter
354
+ * functions, which behave identically in the browser player and headless
355
+ * frame capture (video/still export).
356
+ */
357
+ export interface ImageTreatment {
358
+ /**
359
+ * mono: desaturate toward archival black & white.
360
+ * duotone: single-hue tint (hue taken from `color`).
361
+ * warm / cool: gentle temperature grade.
362
+ */
363
+ type: 'none' | 'mono' | 'duotone' | 'warm' | 'cool';
364
+ /** Blend strength 0..1. Default 0.6. */
365
+ strength?: number;
366
+ /** Duotone tint color (themes default this to their primary). */
367
+ color?: string;
368
+ }
369
+
193
370
  /**
194
371
  * Image layer - displays an image with optional Ken Burns effect.
195
372
  */
@@ -206,6 +383,10 @@ export interface ImageLayer extends BaseLayer {
206
383
  credit?: string;
207
384
  /** License identifier (e.g., 'CC BY-SA 4.0') */
208
385
  license?: string;
386
+ /** Theme-derived photographic grade (see ImageTreatment). */
387
+ treatment?: ImageTreatment;
388
+ /** Gaussian blur radius in px (background/atmosphere imagery). */
389
+ blur?: number;
209
390
  };
210
391
  }
211
392
 
@@ -215,16 +396,73 @@ export interface ImageLayer extends BaseLayer {
215
396
  export interface TextLayer extends BaseLayer {
216
397
  type: 'text';
217
398
  content: {
218
- /** Text to display (supports \n for line breaks) */
399
+ /**
400
+ * Plain text (supports \n for line breaks). Source of truth for plain
401
+ * consumers — PDF/markdown export, search, accessibility — and the SVG
402
+ * `<text>` fallback. When `html` is set, this is its plain-text
403
+ * projection and must be kept in sync.
404
+ */
219
405
  text: string;
406
+ /**
407
+ * Optional sanitized **inline** HTML for rich formatting (bold/italic/
408
+ * links, and — for layout textboxes — headings/lists). When present the
409
+ * renderer draws it via `<foreignObject>` instead of SVG `<text>`. Treated
410
+ * as untrusted and re-sanitized at render time. See `RichTextLayer` in
411
+ * `@bendyline/squisq-react`.
412
+ */
413
+ html?: string;
220
414
  /** Text styling */
221
415
  style: TextStyle;
222
416
  };
223
417
  }
224
418
 
419
+ /**
420
+ * A two-stop linear gradient fill, shared by shape/path fills and text
421
+ * backgrounds. When present it overrides the solid `fill` color.
422
+ *
423
+ * `angle` is in degrees: 0 = top→bottom, 90 = left→right, increasing
424
+ * clockwise. Defaults to 0. Rendered as an SVG `<linearGradient>` in
425
+ * objectBoundingBox units so it scales with the layer's box.
426
+ */
427
+ export interface LinearGradient {
428
+ from: string;
429
+ to: string;
430
+ angle?: number;
431
+ }
432
+
433
+ /** Border line style, mapped to an SVG stroke-dasharray by the renderer. */
434
+ export type BorderStyle = 'solid' | 'dashed' | 'dotted';
435
+
225
436
  /**
226
437
  * Shape layer - simple geometric shapes for visual accents.
227
438
  */
439
+ /**
440
+ * Repeating SVG pattern fill for a shape (dots, grid, diagonal lines).
441
+ * Rendered as a native `<pattern>` def — fully vector and export-safe.
442
+ */
443
+ export interface ShapePattern {
444
+ kind: 'dots' | 'grid' | 'diagonal';
445
+ /** Pattern ink color. */
446
+ color: string;
447
+ /** Tile size in px (default 24). */
448
+ size?: number;
449
+ /** Pattern opacity 0–1 (default 1; tint via a translucent color or this). */
450
+ opacity?: number;
451
+ }
452
+
453
+ /**
454
+ * Procedural filter applied to a shape. `noise` renders static film grain
455
+ * via SVG feTurbulence — deliberately not animated so frame capture and
456
+ * the live player agree.
457
+ */
458
+ export interface ShapeFilter {
459
+ type: 'noise';
460
+ /** feTurbulence base frequency (default 0.8 — fine grain). */
461
+ baseFrequency?: number;
462
+ /** Grain opacity 0–1 (default 0.05). */
463
+ opacity?: number;
464
+ }
465
+
228
466
  export interface ShapeLayer extends BaseLayer {
229
467
  type: 'shape';
230
468
  content: {
@@ -232,15 +470,88 @@ export interface ShapeLayer extends BaseLayer {
232
470
  shape: 'rect' | 'circle' | 'line';
233
471
  /** Fill color (CSS color or 'none') */
234
472
  fill?: string;
473
+ /** Fill opacity 0–1 (applies to solid and gradient fills). */
474
+ fillOpacity?: number;
475
+ /** Gradient fill; overrides `fill` when set. */
476
+ gradient?: LinearGradient;
477
+ /** Repeating pattern fill; overrides `fill`/`gradient` when set. */
478
+ pattern?: ShapePattern;
479
+ /** Procedural filter (film grain). */
480
+ filter?: ShapeFilter;
235
481
  /** Stroke color */
236
482
  stroke?: string;
237
483
  /** Stroke width in pixels */
238
484
  strokeWidth?: number;
485
+ /** Border line style (solid/dashed/dotted). Default solid. */
486
+ borderStyle?: BorderStyle;
239
487
  /** Corner radius for rect */
240
488
  borderRadius?: number;
241
489
  };
242
490
  }
243
491
 
492
+ /**
493
+ * End-of-line marker style for a path/connector endpoint.
494
+ * `arrow` is a filled triangle (the classic arrowhead); `open` is a stroked
495
+ * V; `diamond`/`circle`/`square` are filled glyphs; `none` draws nothing.
496
+ */
497
+ export type MarkerStyle = 'none' | 'arrow' | 'open' | 'diamond' | 'circle' | 'square';
498
+
499
+ /**
500
+ * Path layer - renders an SVG `<path>` for arbitrary curves, connectors,
501
+ * arrows, or filled regions. Used by the diagram template for edges
502
+ * between nodes, the drawing template for non-rect/circle/line shapes, and
503
+ * available to any template that needs a custom geometry.
504
+ *
505
+ * The `position` field carries the layer's bounding box (so animations
506
+ * and clipping work the same as for other layers), but the actual
507
+ * geometry is encoded in the `d` attribute using absolute SVG path
508
+ * coordinates relative to the block viewport.
509
+ */
510
+ export interface PathLayer extends BaseLayer {
511
+ type: 'path';
512
+ content: {
513
+ /** SVG path `d` attribute (e.g. "M 10 10 L 90 90"). Absolute viewBox coords. */
514
+ d: string;
515
+ /**
516
+ * Named shape kind (e.g. `'diamond'`, `'star'`, `'arrow-right'`) for
517
+ * path layers that represent one of Squisq's standard shapes. When
518
+ * set, the renderer re-derives `d` from the layer's `position` box
519
+ * (resolved against the viewport) instead of using the stored `d` —
520
+ * so the shape moves, resizes, and adapts to any aspect ratio like
521
+ * the native rect/circle/line layers do. Plain paths (connectors,
522
+ * freehand) leave this unset and keep their absolute `d`.
523
+ */
524
+ shapeKind?: string;
525
+ /** Stroke color (default: theme text color). */
526
+ stroke?: string;
527
+ /** Stroke width in pixels (default: 2). */
528
+ strokeWidth?: number;
529
+ /** Optional fill color (default: 'none' — pure connectors). */
530
+ fill?: string;
531
+ /** Fill opacity 0–1 (applies to solid and gradient fills). */
532
+ fillOpacity?: number;
533
+ /** Gradient fill; overrides `fill` when set. */
534
+ gradient?: LinearGradient;
535
+ /**
536
+ * Border line style (solid/dashed/dotted). A convenience over
537
+ * `dasharray` for named shapes; when set the renderer derives the
538
+ * dash pattern from it (scaled by stroke width).
539
+ */
540
+ borderStyle?: BorderStyle;
541
+ /** Optional stroke dash pattern (SVG `stroke-dasharray` syntax). */
542
+ dasharray?: string;
543
+ /**
544
+ * Legacy arrowhead flag. Prefer `startMarker`/`endMarker`. When those are
545
+ * unset, `'end'`/`'start'`/`'both'` render a filled-triangle arrowhead.
546
+ */
547
+ arrow?: 'none' | 'end' | 'start' | 'both';
548
+ /** Marker at the path start (overrides `arrow`). Default: derived from `arrow`. */
549
+ startMarker?: MarkerStyle;
550
+ /** Marker at the path end (overrides `arrow`). Default: derived from `arrow`. */
551
+ endMarker?: MarkerStyle;
552
+ };
553
+ }
554
+
244
555
  /**
245
556
  * Map layer - displays a geographic map with optional markers.
246
557
  *
@@ -293,6 +604,16 @@ export interface VideoLayer extends BaseLayer {
293
604
  clipEnd: number;
294
605
  /** Total source video duration (for validation) */
295
606
  sourceDuration?: number;
607
+ /**
608
+ * Seconds into the owning block before this video begins playing
609
+ * (the block-relative `startAt`). Default 0 — plays from block start.
610
+ */
611
+ startAt?: number;
612
+ /**
613
+ * When true, the video keeps playing past the block's end (the
614
+ * playback scheduler re-homes it to the player level). Default false.
615
+ */
616
+ spillover?: boolean;
296
617
  /** Video credit / artist name */
297
618
  credit?: string;
298
619
  /** License identifier (e.g., 'CC BY-SA 4.0') */
@@ -399,16 +720,34 @@ export interface TextStyle {
399
720
  fontFamily?: string;
400
721
  /** Font weight */
401
722
  fontWeight?: 'normal' | 'bold';
723
+ /** Font style. Also the inherited baseline for rich `content.html`. */
724
+ fontStyle?: 'normal' | 'italic';
402
725
  /** Text color (CSS color) */
403
726
  color: string;
404
- /** Text alignment */
727
+ /** Horizontal text alignment within the layer's position box. */
405
728
  textAlign?: 'left' | 'center' | 'right';
729
+ /**
730
+ * Vertical alignment of the text within the layer's position box.
731
+ * Requires a `position.height` to have an effect. When omitted, the
732
+ * vertical baseline is derived from `position.anchor` (legacy behavior).
733
+ */
734
+ verticalAlign?: 'top' | 'middle' | 'bottom';
406
735
  /** Line height multiplier */
407
736
  lineHeight?: number;
408
737
  /** Add drop shadow for readability over images */
409
738
  shadow?: boolean;
410
739
  /** Background color for text box */
411
740
  background?: string;
741
+ /** Background opacity 0–1 (applies to solid and gradient backgrounds). */
742
+ backgroundOpacity?: number;
743
+ /** Gradient background; overrides `background` when set. */
744
+ backgroundGradient?: LinearGradient;
745
+ /** Border (stroke) color for the text box. */
746
+ borderColor?: string;
747
+ /** Border width in pixels. A border renders when > 0 and a color is set. */
748
+ borderWidth?: number;
749
+ /** Border line style (solid/dashed/dotted). Default solid. */
750
+ borderStyle?: BorderStyle;
412
751
  /** Padding around text (pixels) */
413
752
  padding?: number;
414
753
  /** Maximum number of lines before truncation (adds "..." to last line) */
@@ -451,23 +790,6 @@ export type AnimationType =
451
790
  | 'panRight'
452
791
  | 'typewriter'; // Text appears letter by letter
453
792
 
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
793
  // ============================================
472
794
  // Audio Configuration
473
795
  // ============================================
@@ -525,8 +847,10 @@ export interface CaptionPhrase {
525
847
  export interface CaptionTrack {
526
848
  /** Caption phrases in chronological order */
527
849
  phrases: CaptionPhrase[];
528
- /** When the captions were generated */
529
- generatedAt: string;
850
+ /** When the captions were generated. Optional so that conversion stays
851
+ * deterministic — `markdownToDoc()` only sets it when the caller
852
+ * supplies a timestamp via `captionsGeneratedAt`. */
853
+ generatedAt?: string;
530
854
  /** Algorithm version for regeneration detection */
531
855
  version: number;
532
856
  }
@@ -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'];