@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
@@ -251,6 +251,30 @@ function recommendedNamesForProfile(profile: BlockContentProfile): string[] {
251
251
  return Array.from(names);
252
252
  }
253
253
 
254
+ /**
255
+ * Deterministic single-template choice for an unannotated heading block,
256
+ * from strong content signals only. Returns `undefined` when no signal is
257
+ * decisive — the caller keeps its structural default (`sectionHeader`).
258
+ *
259
+ * Precedence favors the most specific/structured content: a table beats
260
+ * an image beats a blockquote beats a stat-looking line. `blockIndex`
261
+ * alternates left/right feature composition for single-image blocks so a
262
+ * run of image sections doesn't stack the same layout.
263
+ *
264
+ * Used by `markdownToDoc`'s auto template picking (see its `autoTemplates`
265
+ * option); the multi-candidate `recommendTemplatesForBlock` below serves
266
+ * the editor's template-picker UI instead.
267
+ */
268
+ export function pickAutoTemplate(profile: BlockContentProfile, blockIndex = 0): string | undefined {
269
+ if (profile.hasTable) return 'dataTable';
270
+ if (profile.imageCount >= 2) return 'photoGrid';
271
+ if (profile.hasImage) return blockIndex % 2 === 0 ? 'leftFeature' : 'rightFeature';
272
+ if (profile.hasBlockquote) return 'quote';
273
+ if (profile.hasNumberHighlight) return 'statHighlight';
274
+ if (profile.hasList) return 'list';
275
+ return undefined;
276
+ }
277
+
254
278
  /**
255
279
  * Split a flat list of template names into a "Recommended" bucket
256
280
  * (matches the active block's content profile) and a "rest" bucket
@@ -20,7 +20,7 @@
20
20
  * - imageWithCaption: Image with text overlay
21
21
  */
22
22
 
23
- import type { Layer, Block, Transition, MapTileStyle, MapMarker } from './Doc.js';
23
+ import type { Layer, Block, Transition, MapTileStyle, MapMarker, MarkerStyle } from './Doc.js';
24
24
  import type { ViewportConfig, ViewportOrientation } from './Viewport.js';
25
25
  import type { LayoutHints } from './LayoutStrategy.js';
26
26
  import type { Theme } from './Theme.js';
@@ -110,6 +110,12 @@ interface BaseTemplateBlock {
110
110
  * Used with sourceStartTime to precisely position the block.
111
111
  */
112
112
  sourceDuration?: number;
113
+ /**
114
+ * Per-block override for the theme's photographic image grade
115
+ * (`theme.style.imageTreatment`). `'none'` opts this block's imagery out;
116
+ * a treatment type forces that grade regardless of the theme.
117
+ */
118
+ imageTreatment?: 'none' | 'mono' | 'duotone' | 'warm' | 'cool';
113
119
  }
114
120
 
115
121
  /**
@@ -470,6 +476,78 @@ export interface VideoPullQuoteInput extends BaseTemplateBlock {
470
476
  };
471
477
  }
472
478
 
479
+ /**
480
+ * Diagram block - renders child headings as a node-and-edge diagram.
481
+ *
482
+ * Unlike other templates, the diagram template reads its content from
483
+ * the parent block's `children` (passed via `context.children`) — each
484
+ * child heading becomes a node, positioned by its `x`/`y` and connected
485
+ * by its `connectsTo`. Per-diagram options on this input control overall
486
+ * appearance; per-node data lives on the child blocks themselves.
487
+ */
488
+ export interface DiagramBlockInput extends BaseTemplateBlock {
489
+ template: 'diagram';
490
+ /** Optional diagram title displayed above the canvas. */
491
+ title?: string;
492
+ /** Color scheme for nodes and edges. */
493
+ colorScheme?: ColorScheme;
494
+ /** Node shape style (default: 'rounded'). */
495
+ nodeShape?: 'rounded' | 'rect' | 'pill';
496
+ /** Edge routing (default: 'curved'). */
497
+ edgeStyle?: 'curved' | 'straight' | 'orthogonal';
498
+ /** Marker at each edge's start (default: 'none'). */
499
+ startStyle?: MarkerStyle;
500
+ /** Marker at each edge's end (default: 'arrow'). */
501
+ endStyle?: MarkerStyle;
502
+ /** Edge line style (default: 'solid'). */
503
+ lineStyle?: 'solid' | 'dashed' | 'dotted';
504
+ }
505
+
506
+ /**
507
+ * Free-form 2D canvas block (`layout`). Like `drawing`/`diagram`, the
508
+ * `layout` template is a children-driven container: each child heading is
509
+ * one absolutely-positioned layer (`{[text …]}` / `{[rectangle …]}` /
510
+ * `{[image …]}`), read from `context.children`. A text box's content is the
511
+ * child's body markdown. Backed by the `layoutBlock` template.
512
+ *
513
+ * (Legacy documents stored layers as a base64-JSON `layers=` param on the
514
+ * heading; the editor auto-migrates those to child sub-blocks on first edit.)
515
+ */
516
+ export interface RawLayersInput extends BaseTemplateBlock {
517
+ template: 'layout';
518
+ }
519
+
520
+ /**
521
+ * Drawing block — renders child headings as free-form shapes on a canvas.
522
+ *
523
+ * Like `diagram`, the drawing template reads its content from the parent
524
+ * block's `children` (passed via `context.children`). Each child heading
525
+ * is a shape: its `{[shape …]}` annotation names the primitive
526
+ * (`rectangle`/`rect`, `circle`, `line`, `arrow`, `path`, `text`) and
527
+ * carries geometry/style as params; its `{#id}` makes it referenceable;
528
+ * its heading text is the shape label and its body text an optional
529
+ * sublabel. Connectors (`line`/`arrow` with `from`/`to`) join shapes by id.
530
+ *
531
+ * Coordinates are author-defined units; the template fits the bounding box
532
+ * of all shapes to the viewport (same scaling as `diagram`), so authors
533
+ * don't have to think in absolute pixels.
534
+ *
535
+ * Legacy drawings authored visually persist their `Layer[]` as a base64
536
+ * `layers="…"` Pandoc param instead of children; the template decodes and
537
+ * returns those directly when no children are present (see `drawingBlock`).
538
+ */
539
+ export interface DrawingBlockInput extends BaseTemplateBlock {
540
+ template: 'drawing';
541
+ /** Optional title displayed above the canvas. */
542
+ title?: string;
543
+ /** Color scheme for shapes that don't pin their own fill/stroke. */
544
+ colorScheme?: ColorScheme;
545
+ /** Default fill for shapes that don't specify one (default: 'none'). */
546
+ fill?: string;
547
+ /** Default stroke for shapes that don't specify one (default: theme text). */
548
+ stroke?: string;
549
+ }
550
+
473
551
  /**
474
552
  * Data table - renders a themed table with headers and rows.
475
553
  * Ideal for structured data, comparisons, and reference information.
@@ -511,7 +589,10 @@ export type TemplateBlock =
511
589
  | PullQuoteInput
512
590
  | VideoWithCaptionInput
513
591
  | VideoPullQuoteInput
514
- | DataTableInput;
592
+ | DataTableInput
593
+ | DiagramBlockInput
594
+ | DrawingBlockInput
595
+ | RawLayersInput;
515
596
 
516
597
  /**
517
598
  * A block can be either a raw Block or a TemplateBlock.
@@ -548,6 +629,20 @@ export interface TemplateContext {
548
629
  orientation: ViewportOrientation;
549
630
  /** Layout hints for this orientation */
550
631
  layout: LayoutHints;
632
+ /**
633
+ * The block's direct children (set by `getLayers` when the block has
634
+ * `children`). Most templates ignore this; aggregate templates like
635
+ * `diagram` consume it to render each child as part of their output.
636
+ */
637
+ children?: Block[];
638
+ /**
639
+ * The source block being expanded. Used by user-defined custom
640
+ * templates so the token resolver can substitute `{title}`,
641
+ * `{content}`, `{children}`, and `{image:N}` against the block's
642
+ * data at render time. Built-in templates ignore this and rely on
643
+ * their typed `input` parameters instead.
644
+ */
645
+ block?: Block;
551
646
  }
552
647
 
553
648
  /**
@@ -560,6 +655,10 @@ export function createTemplateContext(
560
655
  viewport: ViewportConfig = VIEWPORT_PRESETS.landscape,
561
656
  ): TemplateContext {
562
657
  const orientation = getViewportOrientation(viewport);
658
+ // Theme renderStyle may override individual layout hints (e.g. a theme
659
+ // that wants a lower primaryY across every template).
660
+ const overrides = theme.renderStyle.layoutOverrides;
661
+ const baseLayout = getLayoutHints(orientation);
563
662
  return {
564
663
  theme,
565
664
  blockIndex,
@@ -567,7 +666,7 @@ export function createTemplateContext(
567
666
  viewport,
568
667
  fontScale: calculateFontScale(viewport),
569
668
  orientation,
570
- layout: getLayoutHints(orientation),
669
+ layout: overrides ? { ...baseLayout, ...overrides } : baseLayout,
571
670
  };
572
671
  }
573
672
 
@@ -647,7 +746,9 @@ export type PersistentLayerTemplateType =
647
746
  | 'solidBackground' // Solid color fill
648
747
  | 'gradientBackground' // CSS gradient or preset
649
748
  | 'imageBackground' // Blurred/faded hero image
650
- | 'patternBackground' // Subtle pattern (dots, grid)
749
+ | 'patternBackground' // Subtle pattern (dots, grid, diagonal, noise/grain)
750
+ | 'vignette' // Soft radial edge darkening (top layer)
751
+ | 'ambientGradient' // Slowly drifting surface gradient (bottom layer)
651
752
  | 'titleCaption' // Article title in corner
652
753
  | 'cornerBranding' // Logo or text badge
653
754
  | 'progressIndicator'; // Bar/dots showing position
@@ -660,6 +761,8 @@ export type PersistentLayerTemplateConfig =
660
761
  | GradientBackgroundConfig
661
762
  | ImageBackgroundConfig
662
763
  | PatternBackgroundConfig
764
+ | VignetteConfig
765
+ | AmbientGradientConfig
663
766
  | TitleCaptionConfig
664
767
  | CornerBrandingConfig
665
768
  | ProgressIndicatorConfig;
@@ -718,6 +821,32 @@ export interface PatternBackgroundConfig {
718
821
  scale?: number;
719
822
  }
720
823
 
824
+ /**
825
+ * Soft radial vignette (top layer) — transparent center darkening toward
826
+ * the frame edges. The classic film-look framing device.
827
+ */
828
+ export interface VignetteConfig {
829
+ type: 'vignette';
830
+ /** Edge darkness 0-1 (default 0.3). */
831
+ strength?: number;
832
+ /** Vignette color (default black). */
833
+ color?: string;
834
+ }
835
+
836
+ /**
837
+ * Slowly drifting surface gradient (bottom layer). Colors default to the
838
+ * theme's surface family at expansion time.
839
+ */
840
+ export interface AmbientGradientConfig {
841
+ type: 'ambientGradient';
842
+ /** Gradient start color. */
843
+ from?: string;
844
+ /** Gradient end color. */
845
+ to?: string;
846
+ /** Drift loop duration in seconds (default 40). */
847
+ duration?: number;
848
+ }
849
+
721
850
  // ============================================
722
851
  // Overlay Layer Configs
723
852
  // ============================================
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Custom Templates Schema
3
+ *
4
+ * User-defined block layout templates. The author designs a visual
5
+ * layout once (in the editor's TemplateDesigner) and saves it as a
6
+ * `CustomTemplateDefinition`. The definition lives either in the
7
+ * document's frontmatter (per-doc, portable, ships with the markdown)
8
+ * or in the user's local library (cross-doc reuse via localStorage).
9
+ *
10
+ * A custom template's content is just a `Layer[]` — the same shape that
11
+ * built-in templates emit at expansion time. The novel bit is
12
+ * *placeholder tokens*: TextLayer / ImageLayer content fields may carry
13
+ * strings like `{title}`, `{content}`, `{children}`, or `{image:N}`
14
+ * that the resolver (see `doc/templates/tokens/resolveTokens.ts`)
15
+ * substitutes against the source Block at render time.
16
+ *
17
+ * Responsiveness model: every numeric position field in `layers` is
18
+ * stored as a `%`-string relative to `viewport`, so the same template
19
+ * renders correctly at any of Squisq's viewport presets (landscape,
20
+ * portrait, square). The `viewport` field itself is purely an
21
+ * authoring-time reference — it tells the designer canvas which aspect
22
+ * ratio to mount with, and the thumbnail renderer which viewBox to use.
23
+ */
24
+
25
+ import type { Layer } from './Doc.js';
26
+
27
+ /**
28
+ * Iteration directive for a custom-template layer.
29
+ *
30
+ * A layer that carries a `repeat` is cloned once per item of a source
31
+ * collection at render time (see `resolveTokens`). The clones are laid
32
+ * out in a row or column, offset from the base position by the layer's
33
+ * own width/height plus `gap`, and each clone resolves per-item tokens
34
+ * (`{item}`, `{item:src}`, `{item:label}`, `{index}`) against its item.
35
+ */
36
+ export interface LayerRepeat {
37
+ /**
38
+ * Which of the source Block's collections to iterate:
39
+ * - `images` — every image in the block body (alt + url)
40
+ * - `children` — child block titles
41
+ * - `listItems` — top-level list-item text in the block body
42
+ */
43
+ source: 'images' | 'children' | 'listItems';
44
+
45
+ /**
46
+ * Layout axis for the clones. `column` (default) stacks them
47
+ * vertically (offsets `position.y`); `row` lays them out horizontally
48
+ * (offsets `position.x`).
49
+ */
50
+ direction?: 'row' | 'column';
51
+
52
+ /**
53
+ * Extra spacing added between consecutive clones, in the same unit as
54
+ * the layer's `width`/`height`. Defaults to 24.
55
+ */
56
+ gap?: number;
57
+
58
+ /** Cap on the number of clones rendered (omit for "all items"). */
59
+ max?: number;
60
+ }
61
+
62
+ /**
63
+ * A custom-template layer: a normal render `Layer` that may additionally
64
+ * carry a `repeat` directive. This is a strict superset of the core
65
+ * `Layer` type used only inside `CustomTemplateDefinition.layers`; the
66
+ * resolver strips `repeat` before emitting the final `Layer[]`, so the
67
+ * core `Layer` type is never widened.
68
+ */
69
+ export type CustomTemplateLayer = Layer & { repeat?: LayerRepeat };
70
+
71
+ /**
72
+ * A single user-defined template. Stored in `Doc.customTemplates`
73
+ * (frontmatter-backed) or in the user's local library.
74
+ */
75
+ export interface CustomTemplateDefinition {
76
+ /**
77
+ * Stable id used in heading annotations (`### Foo {[name]}`). Must be
78
+ * a slug — lowercase alphanumerics, hyphens. The picker enforces
79
+ * uniqueness within a (doc + library) merged namespace.
80
+ */
81
+ name: string;
82
+
83
+ /** Human-readable label shown in the template picker. */
84
+ label: string;
85
+
86
+ /** Optional one-sentence description for the picker card. */
87
+ description?: string;
88
+
89
+ /**
90
+ * Authoring viewport — the canvas size the designer drew against.
91
+ * Stored so the designer and thumbnail renderer can reproduce the
92
+ * same canvas. Layer positions are stored as `%`-strings, so the
93
+ * actual render-time viewport can differ from this without breaking
94
+ * the layout. Defaults to 1920×1080 landscape.
95
+ */
96
+ viewport: {
97
+ width: number;
98
+ height: number;
99
+ };
100
+
101
+ /**
102
+ * The visual content. Every `position.x` / `position.y` /
103
+ * `position.width` / `position.height` should be a `%`-string at
104
+ * save time (the designer's normalizePositions step enforces this).
105
+ *
106
+ * Tokens supported (grammar v2):
107
+ * - TextLayer.content.text — `{title}`, `{content}`, `{children}`,
108
+ * `{image:N}` (Nth image's alt text), `{attr:key}` (block
109
+ * attribute), plus a pipe default on any token
110
+ * (`{attr:subtitle|Untitled}`).
111
+ * - ImageLayer.content.src — `{image:N}` (the URL) and `{attr:key}`,
112
+ * with pipe defaults (`{image:1|fallback.jpg}`).
113
+ * - Inside a layer with `repeat`, per-item tokens: `{item}`,
114
+ * `{item:src}`, `{item:label}`, `{index}` (1-based).
115
+ *
116
+ * A layer may carry a `repeat` directive (see {@link LayerRepeat}) to
117
+ * be cloned once per item of a source collection.
118
+ */
119
+ layers: CustomTemplateLayer[];
120
+ }
121
+
122
+ /**
123
+ * Tag value for the YAML frontmatter key that carries inlined custom
124
+ * template definitions. Exported so the markdown bridge stays in sync.
125
+ */
126
+ export const FRONTMATTER_CUSTOM_TEMPLATES_KEY = 'squisq-custom-templates';