@bendyline/squisq 2.1.0 → 2.3.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 (442) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +24 -0
  3. package/README.md +8 -8
  4. package/dist/{ContentContainer-BXUlIf18.d.ts → ContentContainer-B2w9sUoL.d.ts} +15 -0
  5. package/dist/{Doc-DynJleo-.d.ts → Doc-COe-rPQB.d.ts} +7 -5
  6. package/dist/{ImageEditDoc-Ca4Tr9uN.d.ts → ImageEditDoc-dtKahVyD.d.ts} +1 -1
  7. package/dist/{annotationCoercion-Dr_DY9Hx.d.ts → annotationCoercion-PtRQhk5V.d.ts} +1 -1
  8. package/dist/{chunk-VWCJ2WXC.js → chunk-2ZWIXGAC.js} +0 -1
  9. package/dist/{chunk-VYFRI7R6.js → chunk-32R2RZAO.js} +72 -3
  10. package/dist/chunk-3IGPAPQ7.js +645 -0
  11. package/dist/{chunk-QE7TWGVT.js → chunk-4VOD55SX.js} +0 -1
  12. package/dist/{chunk-3E5F2XMR.js → chunk-67L6WRJN.js} +0 -1
  13. package/dist/{chunk-5DKBFXPY.js → chunk-6ECMNUIP.js} +26 -18
  14. package/dist/{chunk-DBS6RIFL.js → chunk-7N4G32LG.js} +0 -1
  15. package/dist/{chunk-JOQYPRDZ.js → chunk-7YEZFHLS.js} +10 -8
  16. package/dist/{chunk-2UQOHYE2.js → chunk-7ZAAICW4.js} +0 -1
  17. package/dist/{chunk-66QO7XFB.js → chunk-ACZOVWX3.js} +117 -7
  18. package/dist/{chunk-5J4HKRJF.js → chunk-BAOV476U.js} +0 -1
  19. package/dist/chunk-BCCXTMN5.js +699 -0
  20. package/dist/{chunk-JHZWYRSW.js → chunk-C33GTPUZ.js} +11 -4
  21. package/dist/{chunk-XNLB57UD.js → chunk-EK446BUE.js} +18 -2
  22. package/dist/{chunk-VR7UPJ7C.js → chunk-FCG3BIZN.js} +12 -1
  23. package/dist/{chunk-JI77MGAK.js → chunk-GIIOY6XR.js} +2178 -1291
  24. package/dist/{chunk-Q7LROV2H.js → chunk-NKNMGIIN.js} +0 -1
  25. package/dist/chunk-OPO5K4DZ.js +241 -0
  26. package/dist/{chunk-TUWMK62B.js → chunk-PUS54YU6.js} +45 -2
  27. package/dist/{chunk-OTKVFVL6.js → chunk-QNTQIEIV.js} +130 -47
  28. package/dist/{chunk-5UYI3RJB.js → chunk-QWCFK5FN.js} +55 -40
  29. package/dist/{chunk-GZY66SDN.js → chunk-R3KTTOMS.js} +0 -1
  30. package/dist/{chunk-ILWDXLUK.js → chunk-RS5AP3J4.js} +49 -10
  31. package/dist/{chunk-QUUPYOSP.js → chunk-SPTY4C6F.js} +136 -5
  32. package/dist/{chunk-4X3JQXNM.js → chunk-XOFT65EX.js} +1 -2
  33. package/dist/{chunk-FVTQSNR7.js → chunk-XRW6P7IF.js} +13 -5
  34. package/dist/{chunk-R2YZTCLO.js → chunk-ZQKZSJAX.js} +1 -2
  35. package/dist/doc/index.d.ts +311 -13
  36. package/dist/doc/index.js +25 -17
  37. package/dist/generate/index.d.ts +2 -2
  38. package/dist/generate/index.js +1 -3
  39. package/dist/icons/index.js +2 -4
  40. package/dist/icons/inlineIconMarker.js +1 -2
  41. package/dist/imageEdit/index.d.ts +48 -17
  42. package/dist/imageEdit/index.js +3 -4
  43. package/dist/index.d.ts +13 -13
  44. package/dist/index.js +67 -33
  45. package/dist/jsonForm/index.d.ts +10 -3
  46. package/dist/jsonForm/index.js +8 -7
  47. package/dist/markdown/index.d.ts +75 -11
  48. package/dist/markdown/index.js +32 -7
  49. package/dist/{materializePageSection-DDzubQXo.d.ts → materializePageSection-BSMOocve.d.ts} +2 -2
  50. package/dist/narration/index.d.ts +16 -3
  51. package/dist/narration/index.js +12 -14
  52. package/dist/random/index.d.ts +11 -2
  53. package/dist/random/index.js +1 -3
  54. package/dist/recommend/index.d.ts +1 -1
  55. package/dist/recommend/index.js +2 -4
  56. package/dist/schemas/index.d.ts +18 -6
  57. package/dist/schemas/index.js +7 -6
  58. package/dist/spatial/index.d.ts +2 -1
  59. package/dist/spatial/index.js +1 -2
  60. package/dist/storage/index.d.ts +4 -2
  61. package/dist/storage/index.js +1 -2
  62. package/dist/{themeLibrary-B72m0W1y.d.ts → themeLibrary-BUTWjufL.d.ts} +1 -1
  63. package/dist/timing/index.js +2 -4
  64. package/dist/transform/index.d.ts +3 -3
  65. package/dist/transform/index.js +6 -7
  66. package/dist/{types-C4kiRn_k.d.ts → types-8QjefM9J.d.ts} +38 -2
  67. package/dist/{types-BwMPl2CB.d.ts → types-CUNc9biN.d.ts} +24 -1
  68. package/dist/versions/index.d.ts +14 -11
  69. package/dist/versions/index.js +2 -3
  70. package/package.json +8 -3
  71. package/dist/chunk-2UQOHYE2.js.map +0 -1
  72. package/dist/chunk-3E5F2XMR.js.map +0 -1
  73. package/dist/chunk-4X3JQXNM.js.map +0 -1
  74. package/dist/chunk-5DKBFXPY.js.map +0 -1
  75. package/dist/chunk-5J4HKRJF.js.map +0 -1
  76. package/dist/chunk-5UYI3RJB.js.map +0 -1
  77. package/dist/chunk-66QO7XFB.js.map +0 -1
  78. package/dist/chunk-AMIR72JP.js +0 -380
  79. package/dist/chunk-AMIR72JP.js.map +0 -1
  80. package/dist/chunk-DBS6RIFL.js.map +0 -1
  81. package/dist/chunk-DNJKBAI7.js +0 -1
  82. package/dist/chunk-DNJKBAI7.js.map +0 -1
  83. package/dist/chunk-ET53IIEP.js +0 -1
  84. package/dist/chunk-ET53IIEP.js.map +0 -1
  85. package/dist/chunk-FVTQSNR7.js.map +0 -1
  86. package/dist/chunk-GZY66SDN.js.map +0 -1
  87. package/dist/chunk-ILWDXLUK.js.map +0 -1
  88. package/dist/chunk-JHZWYRSW.js.map +0 -1
  89. package/dist/chunk-JI77MGAK.js.map +0 -1
  90. package/dist/chunk-JOQYPRDZ.js.map +0 -1
  91. package/dist/chunk-OTKVFVL6.js.map +0 -1
  92. package/dist/chunk-PGGGKYWG.js +0 -1
  93. package/dist/chunk-PGGGKYWG.js.map +0 -1
  94. package/dist/chunk-Q7LROV2H.js.map +0 -1
  95. package/dist/chunk-QE7TWGVT.js.map +0 -1
  96. package/dist/chunk-QUUPYOSP.js.map +0 -1
  97. package/dist/chunk-R2GC6RLD.js +0 -344
  98. package/dist/chunk-R2GC6RLD.js.map +0 -1
  99. package/dist/chunk-R2YZTCLO.js.map +0 -1
  100. package/dist/chunk-S7A5D7ZJ.js +0 -174
  101. package/dist/chunk-S7A5D7ZJ.js.map +0 -1
  102. package/dist/chunk-TUWMK62B.js.map +0 -1
  103. package/dist/chunk-V543AI2F.js +0 -1
  104. package/dist/chunk-V543AI2F.js.map +0 -1
  105. package/dist/chunk-VR7UPJ7C.js.map +0 -1
  106. package/dist/chunk-VWCJ2WXC.js.map +0 -1
  107. package/dist/chunk-VYFRI7R6.js.map +0 -1
  108. package/dist/chunk-XNLB57UD.js.map +0 -1
  109. package/dist/chunk-ZLQKNOZQ.js +0 -1
  110. package/dist/chunk-ZLQKNOZQ.js.map +0 -1
  111. package/dist/doc/index.js.map +0 -1
  112. package/dist/generate/index.js.map +0 -1
  113. package/dist/icons/index.js.map +0 -1
  114. package/dist/icons/inlineIconMarker.js.map +0 -1
  115. package/dist/imageEdit/index.js.map +0 -1
  116. package/dist/index.js.map +0 -1
  117. package/dist/jsonForm/index.js.map +0 -1
  118. package/dist/markdown/index.js.map +0 -1
  119. package/dist/narration/index.js.map +0 -1
  120. package/dist/random/index.js.map +0 -1
  121. package/dist/recommend/index.js.map +0 -1
  122. package/dist/schemas/index.js.map +0 -1
  123. package/dist/spatial/index.js.map +0 -1
  124. package/dist/storage/index.js.map +0 -1
  125. package/dist/timing/index.js.map +0 -1
  126. package/dist/transform/index.js.map +0 -1
  127. package/dist/versions/index.js.map +0 -1
  128. package/src/__tests__/animationUtils.test.ts +0 -117
  129. package/src/__tests__/annotationCoercion.test.ts +0 -243
  130. package/src/__tests__/applyNarrationTiming.test.ts +0 -190
  131. package/src/__tests__/applyRenderStyle.test.ts +0 -146
  132. package/src/__tests__/asciiDiagramBattle.test.ts +0 -246
  133. package/src/__tests__/asciiDiagramDetect.test.ts +0 -162
  134. package/src/__tests__/asciiDiagramMapping.test.ts +0 -38
  135. package/src/__tests__/asciiDiagramParse.test.ts +0 -233
  136. package/src/__tests__/asciiDiagramPipeline.test.ts +0 -273
  137. package/src/__tests__/asciiDiagramRender.test.ts +0 -162
  138. package/src/__tests__/asciiDiagramRepair.test.ts +0 -84
  139. package/src/__tests__/asciiTimelineDetect.test.ts +0 -157
  140. package/src/__tests__/asciiTimelineParse.test.ts +0 -205
  141. package/src/__tests__/asciiTimelinePipeline.test.ts +0 -349
  142. package/src/__tests__/asciiTimelineRender.test.ts +0 -376
  143. package/src/__tests__/atmosphereLayers.test.ts +0 -108
  144. package/src/__tests__/attrTokens.test.ts +0 -167
  145. package/src/__tests__/colorUtils.test.ts +0 -112
  146. package/src/__tests__/contentContainer.test.ts +0 -325
  147. package/src/__tests__/contentExtractor.test.ts +0 -243
  148. package/src/__tests__/coverBlock.test.ts +0 -310
  149. package/src/__tests__/customTemplateSample.test.ts +0 -144
  150. package/src/__tests__/customTemplatesFrontmatter.test.ts +0 -221
  151. package/src/__tests__/customThemesFrontmatter.test.ts +0 -146
  152. package/src/__tests__/dataTable.test.ts +0 -145
  153. package/src/__tests__/diagramBlock.test.ts +0 -122
  154. package/src/__tests__/diagramLayout.test.ts +0 -134
  155. package/src/__tests__/diagramText.test.ts +0 -26
  156. package/src/__tests__/docToMarkdownMedia.test.ts +0 -83
  157. package/src/__tests__/drawing.test.ts +0 -109
  158. package/src/__tests__/drawingBlock.test.ts +0 -124
  159. package/src/__tests__/drawingLayout.test.ts +0 -171
  160. package/src/__tests__/fixtures/asciiDiagramBattle.ts +0 -512
  161. package/src/__tests__/fixtures/asciiDiagrams.ts +0 -287
  162. package/src/__tests__/fixtures/asciiTimelines.ts +0 -35
  163. package/src/__tests__/fixtures/treeviewFixtures.ts +0 -243
  164. package/src/__tests__/geohash.test.ts +0 -110
  165. package/src/__tests__/getLayers.test.ts +0 -243
  166. package/src/__tests__/getLayersFallback.test.ts +0 -103
  167. package/src/__tests__/haversine.test.ts +0 -63
  168. package/src/__tests__/iconResolve.test.ts +0 -122
  169. package/src/__tests__/imageEditExportSvg.test.ts +0 -70
  170. package/src/__tests__/imageEditState.test.ts +0 -74
  171. package/src/__tests__/imageEditVersions.test.ts +0 -243
  172. package/src/__tests__/inferDocumentTitle.test.ts +0 -50
  173. package/src/__tests__/inlineIconMarker.test.ts +0 -36
  174. package/src/__tests__/inputDescriptors.test.ts +0 -162
  175. package/src/__tests__/jsonForm.chooseControl.test.ts +0 -86
  176. package/src/__tests__/jsonForm.evaluateWhen.test.ts +0 -59
  177. package/src/__tests__/jsonForm.inferSchema.test.ts +0 -25
  178. package/src/__tests__/jsonForm.pointer.test.ts +0 -149
  179. package/src/__tests__/jsonForm.tokens.test.ts +0 -84
  180. package/src/__tests__/layoutBlock.test.ts +0 -83
  181. package/src/__tests__/layoutLayout.test.ts +0 -169
  182. package/src/__tests__/localForageAdapter.test.ts +0 -155
  183. package/src/__tests__/majorApiTypes.test.ts +0 -18
  184. package/src/__tests__/markdown.test.ts +0 -1009
  185. package/src/__tests__/markdownIcons.test.ts +0 -104
  186. package/src/__tests__/markdownSanitize.test.ts +0 -81
  187. package/src/__tests__/markdownToDoc.test.ts +0 -957
  188. package/src/__tests__/matchFontFamily.test.ts +0 -114
  189. package/src/__tests__/materializeBlockLayers.test.ts +0 -167
  190. package/src/__tests__/materializePageSections.test.ts +0 -404
  191. package/src/__tests__/materializeTestUtils.ts +0 -17
  192. package/src/__tests__/mediaAnnotations.test.ts +0 -91
  193. package/src/__tests__/mediaSchedule.test.ts +0 -118
  194. package/src/__tests__/narrationAlign.test.ts +0 -150
  195. package/src/__tests__/narrationFeatures.test.ts +0 -71
  196. package/src/__tests__/narrationNuclei.test.ts +0 -45
  197. package/src/__tests__/narrationPacing.test.ts +0 -189
  198. package/src/__tests__/narrationScript.test.ts +0 -149
  199. package/src/__tests__/narrationSessionRepro.test.ts +0 -62
  200. package/src/__tests__/narrationSidecar.test.ts +0 -88
  201. package/src/__tests__/narrationSyllables.test.ts +0 -65
  202. package/src/__tests__/narrationTestSignals.ts +0 -167
  203. package/src/__tests__/narrationVad.test.ts +0 -66
  204. package/src/__tests__/pageStyle.test.ts +0 -258
  205. package/src/__tests__/pandocAttrParse.test.ts +0 -187
  206. package/src/__tests__/recommendTemplates.test.ts +0 -183
  207. package/src/__tests__/resolveDocTheme.test.ts +0 -69
  208. package/src/__tests__/rootExports.test.ts +0 -61
  209. package/src/__tests__/scopedContentContainer.test.ts +0 -109
  210. package/src/__tests__/seededRandom.test.ts +0 -142
  211. package/src/__tests__/shapeGeometry.test.ts +0 -138
  212. package/src/__tests__/shapeVocabulary.test.ts +0 -133
  213. package/src/__tests__/storage.test.ts +0 -60
  214. package/src/__tests__/structuredData.test.ts +0 -249
  215. package/src/__tests__/surface.test.ts +0 -68
  216. package/src/__tests__/templateAnnotationParse.test.ts +0 -173
  217. package/src/__tests__/templateInputs.test.ts +0 -69
  218. package/src/__tests__/templateMetadata.test.ts +0 -32
  219. package/src/__tests__/templates.test.ts +0 -710
  220. package/src/__tests__/themeCompile.test.ts +0 -225
  221. package/src/__tests__/themeValidator.test.ts +0 -131
  222. package/src/__tests__/timing.test.ts +0 -167
  223. package/src/__tests__/transformProvenance.test.ts +0 -160
  224. package/src/__tests__/transformV2.test.ts +0 -163
  225. package/src/__tests__/transitionNormalize.test.ts +0 -61
  226. package/src/__tests__/treeviewBattle.test.ts +0 -76
  227. package/src/__tests__/treeviewDetect.test.ts +0 -168
  228. package/src/__tests__/treeviewParse.test.ts +0 -118
  229. package/src/__tests__/treeviewPipeline.test.ts +0 -183
  230. package/src/__tests__/treeviewRender.test.ts +0 -91
  231. package/src/__tests__/validateDoc.test.ts +0 -260
  232. package/src/__tests__/versions.test.ts +0 -459
  233. package/src/doc/annotationBlocks.ts +0 -117
  234. package/src/doc/applyNarrationTiming.ts +0 -244
  235. package/src/doc/asciiDiagram/chars.ts +0 -210
  236. package/src/doc/asciiDiagram/detect.ts +0 -175
  237. package/src/doc/asciiDiagram/index.ts +0 -38
  238. package/src/doc/asciiDiagram/mapping.ts +0 -225
  239. package/src/doc/asciiDiagram/parse.ts +0 -1095
  240. package/src/doc/asciiDiagram/render.ts +0 -963
  241. package/src/doc/asciiDiagram/repair.ts +0 -400
  242. package/src/doc/asciiDiagram/types.ts +0 -65
  243. package/src/doc/asciiTimeline/detect.ts +0 -138
  244. package/src/doc/asciiTimeline/index.ts +0 -26
  245. package/src/doc/asciiTimeline/mapping.ts +0 -43
  246. package/src/doc/asciiTimeline/parse.ts +0 -870
  247. package/src/doc/asciiTimeline/render.ts +0 -427
  248. package/src/doc/asciiTimeline/types.ts +0 -73
  249. package/src/doc/asciiTimeline/wrappedFlow.ts +0 -427
  250. package/src/doc/audioMapping.ts +0 -442
  251. package/src/doc/customTemplatesFrontmatter.ts +0 -273
  252. package/src/doc/customThemesFrontmatter.ts +0 -95
  253. package/src/doc/docToMarkdown.ts +0 -284
  254. package/src/doc/index.ts +0 -161
  255. package/src/doc/markdownToDoc.ts +0 -1054
  256. package/src/doc/materializeBlockLayers.ts +0 -465
  257. package/src/doc/mediaAnnotations.ts +0 -120
  258. package/src/doc/page/PageSection.ts +0 -174
  259. package/src/doc/page/materializePageSection.ts +0 -616
  260. package/src/doc/page/resolvePageBlock.ts +0 -108
  261. package/src/doc/page/sectionExtractors.ts +0 -466
  262. package/src/doc/pageCss.ts +0 -628
  263. package/src/doc/resolveDocTheme.ts +0 -56
  264. package/src/doc/standaloneAnnotation.ts +0 -58
  265. package/src/doc/structuredData.ts +0 -301
  266. package/src/doc/templateInputs.ts +0 -424
  267. package/src/doc/templates/__tests__/customTemplate.test.ts +0 -170
  268. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +0 -124
  269. package/src/doc/templates/__tests__/listBlock.test.ts +0 -64
  270. package/src/doc/templates/accentImage.ts +0 -362
  271. package/src/doc/templates/captionUtils.ts +0 -56
  272. package/src/doc/templates/comparisonBar.ts +0 -173
  273. package/src/doc/templates/coverBlock.ts +0 -255
  274. package/src/doc/templates/customTemplate.ts +0 -51
  275. package/src/doc/templates/dataTable.ts +0 -102
  276. package/src/doc/templates/dateEvent.ts +0 -147
  277. package/src/doc/templates/definitionCard.ts +0 -140
  278. package/src/doc/templates/diagramBlock.ts +0 -403
  279. package/src/doc/templates/diagramLayout.ts +0 -159
  280. package/src/doc/templates/drawingBlock.ts +0 -325
  281. package/src/doc/templates/drawingLayout.ts +0 -539
  282. package/src/doc/templates/factCard.ts +0 -146
  283. package/src/doc/templates/fallbackBlock.ts +0 -123
  284. package/src/doc/templates/featureBlock.ts +0 -209
  285. package/src/doc/templates/fullBleedQuote.ts +0 -83
  286. package/src/doc/templates/imageWithCaption.ts +0 -186
  287. package/src/doc/templates/index.ts +0 -642
  288. package/src/doc/templates/inputDescriptors.ts +0 -451
  289. package/src/doc/templates/layoutBlock.ts +0 -43
  290. package/src/doc/templates/layoutLayout.ts +0 -243
  291. package/src/doc/templates/listBlock.ts +0 -207
  292. package/src/doc/templates/mapBlock.ts +0 -138
  293. package/src/doc/templates/metadata.ts +0 -130
  294. package/src/doc/templates/persistentLayers.ts +0 -526
  295. package/src/doc/templates/photoGrid.ts +0 -190
  296. package/src/doc/templates/pullQuote.ts +0 -141
  297. package/src/doc/templates/quoteBlock.ts +0 -141
  298. package/src/doc/templates/registry.ts +0 -88
  299. package/src/doc/templates/sectionHeader.ts +0 -120
  300. package/src/doc/templates/statHighlight.ts +0 -146
  301. package/src/doc/templates/templateNames.ts +0 -22
  302. package/src/doc/templates/timelineBlock.ts +0 -613
  303. package/src/doc/templates/titleBlock.ts +0 -150
  304. package/src/doc/templates/tokens/__tests__/resolveTokens.test.ts +0 -487
  305. package/src/doc/templates/tokens/resolveTokens.ts +0 -453
  306. package/src/doc/templates/treeBlock.ts +0 -131
  307. package/src/doc/templates/twoColumn.ts +0 -263
  308. package/src/doc/templates/videoPullQuote.ts +0 -142
  309. package/src/doc/templates/videoWithCaption.ts +0 -135
  310. package/src/doc/treeview/chars.ts +0 -72
  311. package/src/doc/treeview/detect.ts +0 -145
  312. package/src/doc/treeview/index.ts +0 -30
  313. package/src/doc/treeview/mapping.ts +0 -95
  314. package/src/doc/treeview/parse.ts +0 -201
  315. package/src/doc/treeview/render.ts +0 -41
  316. package/src/doc/treeview/types.ts +0 -43
  317. package/src/doc/utils/animationUtils.ts +0 -399
  318. package/src/doc/utils/applyRenderStyle.ts +0 -106
  319. package/src/doc/utils/diagramText.ts +0 -78
  320. package/src/doc/utils/imageTreatment.ts +0 -70
  321. package/src/doc/utils/nearest.ts +0 -59
  322. package/src/doc/utils/shapeGeometry.ts +0 -614
  323. package/src/doc/utils/themeUtils.ts +0 -243
  324. package/src/doc/validate.ts +0 -575
  325. package/src/generate/contentExtractor.ts +0 -921
  326. package/src/generate/index.ts +0 -18
  327. package/src/generate/templateMapper.ts +0 -177
  328. package/src/icons/iconData.ts +0 -31
  329. package/src/icons/index.ts +0 -26
  330. package/src/icons/inlineIconMarker.ts +0 -73
  331. package/src/icons/resolve.ts +0 -139
  332. package/src/imageEdit/ImageEditVersionManager.ts +0 -73
  333. package/src/imageEdit/export.ts +0 -410
  334. package/src/imageEdit/index.ts +0 -72
  335. package/src/imageEdit/persistence.ts +0 -113
  336. package/src/imageEdit/state.ts +0 -112
  337. package/src/imageEdit/versionPaths.ts +0 -44
  338. package/src/imageEdit/versions.ts +0 -152
  339. package/src/index.ts +0 -15
  340. package/src/internal/immutable.ts +0 -58
  341. package/src/jsonForm/chooseControl.ts +0 -88
  342. package/src/jsonForm/evaluateWhen.ts +0 -59
  343. package/src/jsonForm/index.ts +0 -32
  344. package/src/jsonForm/inferSchema.ts +0 -25
  345. package/src/jsonForm/pointer.ts +0 -154
  346. package/src/jsonForm/tokens.ts +0 -78
  347. package/src/jsonForm/types.ts +0 -167
  348. package/src/markdown/annotationCoercion.ts +0 -295
  349. package/src/markdown/attrTokens.ts +0 -243
  350. package/src/markdown/convert.ts +0 -1353
  351. package/src/markdown/htmlParse.ts +0 -212
  352. package/src/markdown/index.ts +0 -152
  353. package/src/markdown/parse.ts +0 -103
  354. package/src/markdown/sanitize.ts +0 -318
  355. package/src/markdown/stringify.ts +0 -207
  356. package/src/markdown/types.ts +0 -710
  357. package/src/markdown/utils.ts +0 -459
  358. package/src/narration/align.ts +0 -514
  359. package/src/narration/features.ts +0 -231
  360. package/src/narration/index.ts +0 -86
  361. package/src/narration/nuclei.ts +0 -118
  362. package/src/narration/pacing.ts +0 -211
  363. package/src/narration/script.ts +0 -239
  364. package/src/narration/session.ts +0 -100
  365. package/src/narration/sidecar.ts +0 -187
  366. package/src/narration/syllables.ts +0 -85
  367. package/src/narration/trace.ts +0 -50
  368. package/src/narration/types.ts +0 -299
  369. package/src/narration/vad.ts +0 -131
  370. package/src/ngeohash.d.ts +0 -11
  371. package/src/random/SeededRandom.ts +0 -127
  372. package/src/random/index.ts +0 -1
  373. package/src/recommend/index.ts +0 -16
  374. package/src/recommend/numberHighlight.ts +0 -25
  375. package/src/recommend/templates.ts +0 -393
  376. package/src/schemas/BlockTemplates.ts +0 -1046
  377. package/src/schemas/CustomTemplates.ts +0 -126
  378. package/src/schemas/Doc.ts +0 -1073
  379. package/src/schemas/ImageEditDoc.ts +0 -102
  380. package/src/schemas/LayoutStrategy.ts +0 -178
  381. package/src/schemas/Media.ts +0 -179
  382. package/src/schemas/MediaProvider.ts +0 -70
  383. package/src/schemas/PageStyle.ts +0 -213
  384. package/src/schemas/Theme.ts +0 -413
  385. package/src/schemas/Transitions.ts +0 -224
  386. package/src/schemas/Types.ts +0 -15
  387. package/src/schemas/Viewport.ts +0 -91
  388. package/src/schemas/colorUtils.ts +0 -219
  389. package/src/schemas/fontStacks.ts +0 -339
  390. package/src/schemas/index.ts +0 -31
  391. package/src/schemas/pageStyleDefaults.ts +0 -204
  392. package/src/schemas/themeCompile.ts +0 -244
  393. package/src/schemas/themeConstants.ts +0 -2
  394. package/src/schemas/themeLibrary.ts +0 -60
  395. package/src/schemas/themeValidator.ts +0 -609
  396. package/src/schemas/themes/bold.json +0 -77
  397. package/src/schemas/themes/cinematic.json +0 -143
  398. package/src/schemas/themes/documentary.json +0 -125
  399. package/src/schemas/themes/gezellig.json +0 -123
  400. package/src/schemas/themes/index.ts +0 -53
  401. package/src/schemas/themes/magazine.json +0 -126
  402. package/src/schemas/themes/minimalist.json +0 -74
  403. package/src/schemas/themes/morning-light.json +0 -117
  404. package/src/schemas/themes/standard-dark.json +0 -109
  405. package/src/schemas/themes/standard.json +0 -98
  406. package/src/schemas/themes/tech-dark.json +0 -143
  407. package/src/schemas/themes/warm-earth.json +0 -74
  408. package/src/spatial/Geohash.ts +0 -196
  409. package/src/spatial/Haversine.ts +0 -59
  410. package/src/spatial/index.ts +0 -2
  411. package/src/storage/ContentContainer.ts +0 -220
  412. package/src/storage/LocalForageAdapter.ts +0 -103
  413. package/src/storage/LocalStorageAdapter.ts +0 -57
  414. package/src/storage/MediaProviderFromContainer.ts +0 -92
  415. package/src/storage/MemoryStorageAdapter.ts +0 -37
  416. package/src/storage/ScopedContentContainer.ts +0 -143
  417. package/src/storage/Storage.ts +0 -28
  418. package/src/storage/index.ts +0 -9
  419. package/src/timing/index.ts +0 -17
  420. package/src/timing/narrationTiming.ts +0 -220
  421. package/src/timing/readingTime.ts +0 -94
  422. package/src/transform/applyTransform.ts +0 -122
  423. package/src/transform/blockAnalyzer.ts +0 -181
  424. package/src/transform/index.ts +0 -23
  425. package/src/transform/registry.ts +0 -217
  426. package/src/transform/styles/dataDriven.ts +0 -18
  427. package/src/transform/styles/documentary.ts +0 -18
  428. package/src/transform/styles/index.ts +0 -5
  429. package/src/transform/styles/magazine.ts +0 -19
  430. package/src/transform/styles/minimal.ts +0 -18
  431. package/src/transform/styles/narrative.ts +0 -19
  432. package/src/transform/templateSelector.ts +0 -389
  433. package/src/transform/timingAllocator.ts +0 -257
  434. package/src/transform/types.ts +0 -158
  435. package/src/versions/DocumentVersionManager.ts +0 -70
  436. package/src/versions/gitignore.ts +0 -34
  437. package/src/versions/index.ts +0 -56
  438. package/src/versions/operations.ts +0 -144
  439. package/src/versions/paths.ts +0 -51
  440. package/src/versions/snapshotStore.ts +0 -202
  441. package/src/versions/timestamp.ts +0 -48
  442. package/src/versions/types.ts +0 -93
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  anchorPoint,
3
+ base64ToUtf8,
3
4
  connectorPath,
4
5
  lineStyleDasharray,
5
6
  shapePath,
6
7
  snapEndpoints
7
- } from "./chunk-TUWMK62B.js";
8
+ } from "./chunk-PUS54YU6.js";
8
9
  import {
9
10
  detectAsciiDiagram,
10
11
  detectAsciiTimeline,
@@ -19,7 +20,7 @@ import {
19
20
  parseAsciiTimeline,
20
21
  pickAutoTemplate,
21
22
  profileBlockContents
22
- } from "./chunk-5UYI3RJB.js";
23
+ } from "./chunk-QWCFK5FN.js";
23
24
  import {
24
25
  DEFAULT_THEME,
25
26
  FRONTMATTER_CUSTOM_TEMPLATES_KEY,
@@ -30,16 +31,17 @@ import {
30
31
  resolveFontFamily,
31
32
  validateTheme,
32
33
  withAlpha
33
- } from "./chunk-JHZWYRSW.js";
34
+ } from "./chunk-C33GTPUZ.js";
34
35
  import {
35
36
  VIEWPORT_PRESETS,
36
37
  cloneData,
37
38
  createTemplateContext,
38
39
  getTwoColumnPositions,
40
+ getViewportOrientation,
39
41
  isPersistentLayerTemplate,
40
42
  isTemplateBlock,
41
43
  scaledFontSize2 as scaledFontSize
42
- } from "./chunk-5J4HKRJF.js";
44
+ } from "./chunk-BAOV476U.js";
43
45
  import {
44
46
  KNOWN_BLOCK_META_KEYS,
45
47
  coerceAnnotationValues,
@@ -48,25 +50,25 @@ import {
48
50
  parseTimeSeconds,
49
51
  splitKeyValueToken,
50
52
  tokenizeAttrTokens
51
- } from "./chunk-R2YZTCLO.js";
53
+ } from "./chunk-ZQKZSJAX.js";
52
54
  import {
53
55
  extractPlainText,
54
56
  readFrontmatterThemeId,
55
57
  walkMarkdownTree
56
- } from "./chunk-AMIR72JP.js";
58
+ } from "./chunk-BCCXTMN5.js";
57
59
  import {
58
60
  resolveBlockTransition
59
- } from "./chunk-QE7TWGVT.js";
61
+ } from "./chunk-4VOD55SX.js";
60
62
  import {
61
63
  estimateReadingTime
62
- } from "./chunk-4X3JQXNM.js";
64
+ } from "./chunk-XOFT65EX.js";
63
65
  import {
64
66
  estimateSpokenWordCount
65
- } from "./chunk-3E5F2XMR.js";
67
+ } from "./chunk-67L6WRJN.js";
66
68
  import {
67
69
  SeededRandom,
68
70
  hashString
69
- } from "./chunk-FVTQSNR7.js";
71
+ } from "./chunk-XRW6P7IF.js";
70
72
 
71
73
  // src/doc/templates/persistentLayers.ts
72
74
  var GRADIENT_PRESETS = {
@@ -198,14 +200,19 @@ function expandTitleCaption(config) {
198
200
  const isBottom = config.position.includes("bottom");
199
201
  const isLeft = config.position.includes("left");
200
202
  const pad = 3;
203
+ const thumbPad = 1.5;
201
204
  const pillHeight = hasSubtitle ? "15%" : "7%";
202
205
  const bgYPos = isBottom ? hasSubtitle ? "78%" : "84%" : "2%";
203
- const bgXPos = isLeft ? `${pad}%` : "68%";
204
- const thumbW = hasSubtitle ? "6%" : "5%";
206
+ const pillW = config.showThumbnail ? 30 : 28;
207
+ const pillX = isLeft ? pad : 100 - pad - pillW;
208
+ const bgXPos = `${pillX}%`;
209
+ const thumbWNum = hasSubtitle ? 6 : 5;
210
+ const thumbW = `${thumbWNum}%`;
205
211
  const thumbH = hasSubtitle ? "10%" : "5%";
206
- const thumbPad = 1.5;
207
- const textX = config.showThumbnail && isLeft ? `${pad + thumbPad + 6 + thumbPad}%` : `${pad + thumbPad}%`;
208
- const textWidth = config.showThumbnail ? "19%" : "24%";
212
+ const innerX = pillX + thumbPad;
213
+ const innerW = pillW - thumbPad * 2;
214
+ const textX = config.showThumbnail && isLeft ? `${innerX + thumbWNum + thumbPad}%` : `${innerX}%`;
215
+ const textWidth = config.showThumbnail ? `${innerW - thumbWNum - thumbPad}%` : `${innerW}%`;
209
216
  const titleYPos = isBottom ? hasSubtitle ? "80%" : "86%" : "4%";
210
217
  layers.push({
211
218
  type: "shape",
@@ -218,12 +225,12 @@ function expandTitleCaption(config) {
218
225
  position: {
219
226
  x: bgXPos,
220
227
  y: bgYPos,
221
- width: config.showThumbnail ? "30%" : "28%",
228
+ width: `${pillW}%`,
222
229
  height: pillHeight
223
230
  }
224
231
  });
225
232
  if (config.showThumbnail && config.thumbnailSrc) {
226
- const thumbX = isLeft ? `${pad + thumbPad}%` : "93%";
233
+ const thumbX = isLeft ? `${innerX}%` : `${pillX + pillW - thumbPad - thumbWNum}%`;
227
234
  const thumbY = isBottom ? hasSubtitle ? `${78 + thumbPad}%` : "85%" : "3%";
228
235
  layers.push({
229
236
  type: "image",
@@ -620,84 +627,6 @@ function titleBlock(input, context) {
620
627
  return layers;
621
628
  }
622
629
 
623
- // src/doc/templates/sectionHeader.ts
624
- function sectionHeader(input, context) {
625
- const { title = "", colorScheme = "blue", imageSrc, imageAlt, ambientMotion } = input;
626
- const { theme, layout } = context;
627
- const treatment = themedImageTreatment(context, input.imageTreatment);
628
- const colors = resolveColorScheme(context, colorScheme);
629
- const titleFontSize = themedFontSize(84, context, true);
630
- const layers = [];
631
- if (imageSrc) {
632
- layers.push({
633
- type: "image",
634
- id: "bg-image",
635
- content: {
636
- src: imageSrc,
637
- alt: imageAlt || title,
638
- fit: "cover",
639
- ...treatment ? { treatment } : {}
640
- },
641
- position: { x: 0, y: 0, width: "100%", height: "100%" },
642
- animation: ambientMotion ? { type: ambientMotion, duration: 8 } : { type: "slowZoom", duration: 8, direction: "in" }
643
- });
644
- layers.push({
645
- type: "shape",
646
- id: "overlay",
647
- content: {
648
- shape: "rect",
649
- fill: themedScrim(context)
650
- },
651
- position: { x: 0, y: 0, width: "100%", height: "100%" }
652
- });
653
- } else {
654
- layers.push({
655
- type: "shape",
656
- id: "bg",
657
- content: {
658
- shape: "rect",
659
- fill: colors.bg
660
- },
661
- position: { x: 0, y: 0, width: "100%", height: "100%" }
662
- });
663
- layers.push({
664
- type: "shape",
665
- id: "line-top",
666
- content: { shape: "rect", fill: `${colors.text}33` },
667
- position: { x: "50%", y: "40%", width: "20%", height: "2px", anchor: "center" }
668
- });
669
- layers.push({
670
- type: "shape",
671
- id: "line-bottom",
672
- content: { shape: "rect", fill: `${colors.text}33` },
673
- position: { x: "50%", y: "60%", width: "20%", height: "2px", anchor: "center" }
674
- });
675
- }
676
- layers.push({
677
- type: "text",
678
- id: "title",
679
- content: {
680
- text: title,
681
- style: {
682
- fontSize: titleFontSize,
683
- fontFamily: getThemeFont(context, "title"),
684
- fontWeight: "bold",
685
- color: imageSrc ? theme.colors.text : colors.text,
686
- textAlign: "center",
687
- shadow: shouldUseShadow(context)
688
- }
689
- },
690
- position: {
691
- x: "50%",
692
- y: "50%",
693
- anchor: "center",
694
- width: layout.maxTextWidth
695
- },
696
- animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1.5 })
697
- });
698
- return layers;
699
- }
700
-
701
630
  // src/doc/templates/accentImage.ts
702
631
  var PERCENTAGE_RE = /^(\d+(?:\.\d+)?)\s*%?$/;
703
632
  var DEFAULT_LAYOUT = {
@@ -928,6 +857,92 @@ function adjustY(originalY, layout) {
928
857
  return yStr;
929
858
  }
930
859
 
860
+ // src/doc/templates/sectionHeader.ts
861
+ function sectionHeader(input, context) {
862
+ const { title = "", colorScheme = "blue", imageSrc, imageAlt, ambientMotion } = input;
863
+ const { theme, layout } = context;
864
+ const treatment = themedImageTreatment(context, input.imageTreatment);
865
+ const colors = resolveColorScheme(context, colorScheme);
866
+ const titleFontSize = themedFontSize(84, context, true);
867
+ const layers = [];
868
+ if (imageSrc) {
869
+ layers.push({
870
+ type: "image",
871
+ id: "bg-image",
872
+ content: {
873
+ src: imageSrc,
874
+ alt: imageAlt || title,
875
+ fit: "cover",
876
+ ...treatment ? { treatment } : {}
877
+ },
878
+ position: { x: 0, y: 0, width: "100%", height: "100%" },
879
+ // Route through mapAmbientMotion like every other image template: the
880
+ // authored vocabulary (`zoomIn`/`panLeft`/…) are NOT AnimationTypes, so
881
+ // passing them raw produced an unknown animation here while the same
882
+ // input animated correctly on imageWithCaption/coverBlock/accentImage.
883
+ animation: mapAmbientMotion(ambientMotion, 8) ?? {
884
+ type: "slowZoom",
885
+ duration: 8,
886
+ direction: "in"
887
+ }
888
+ });
889
+ layers.push({
890
+ type: "shape",
891
+ id: "overlay",
892
+ content: {
893
+ shape: "rect",
894
+ fill: themedScrim(context)
895
+ },
896
+ position: { x: 0, y: 0, width: "100%", height: "100%" }
897
+ });
898
+ } else {
899
+ layers.push({
900
+ type: "shape",
901
+ id: "bg",
902
+ content: {
903
+ shape: "rect",
904
+ fill: colors.bg
905
+ },
906
+ position: { x: 0, y: 0, width: "100%", height: "100%" }
907
+ });
908
+ layers.push({
909
+ type: "shape",
910
+ id: "line-top",
911
+ content: { shape: "rect", fill: withAlpha(colors.text, 0.2) },
912
+ position: { x: "50%", y: "40%", width: "20%", height: "2px", anchor: "center" }
913
+ });
914
+ layers.push({
915
+ type: "shape",
916
+ id: "line-bottom",
917
+ content: { shape: "rect", fill: withAlpha(colors.text, 0.2) },
918
+ position: { x: "50%", y: "60%", width: "20%", height: "2px", anchor: "center" }
919
+ });
920
+ }
921
+ layers.push({
922
+ type: "text",
923
+ id: "title",
924
+ content: {
925
+ text: title,
926
+ style: {
927
+ fontSize: titleFontSize,
928
+ fontFamily: getThemeFont(context, "title"),
929
+ fontWeight: "bold",
930
+ color: imageSrc ? theme.colors.text : colors.text,
931
+ textAlign: "center",
932
+ shadow: shouldUseShadow(context)
933
+ }
934
+ },
935
+ position: {
936
+ x: "50%",
937
+ y: "50%",
938
+ anchor: "center",
939
+ width: layout.maxTextWidth
940
+ },
941
+ animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1.5 })
942
+ });
943
+ return layers;
944
+ }
945
+
931
946
  // src/doc/templates/statHighlight.ts
932
947
  function statHighlight(input, context) {
933
948
  const { stat, description, detail, colorScheme = "blue", accentImage } = input;
@@ -1020,10 +1035,11 @@ function statHighlight(input, context) {
1020
1035
 
1021
1036
  // src/doc/templates/quoteBlock.ts
1022
1037
  function quoteBlock(input, context) {
1023
- const { quote, attribution, accentImage } = input;
1038
+ const { title, quote, attribution, accentImage } = input;
1024
1039
  const { theme, viewport } = context;
1025
1040
  const accentLayout = accentImage ? getAccentLayout(accentImage.position) : DEFAULT_LAYOUT;
1026
1041
  const quoteFontSize = themedFontSize(48, context, true);
1042
+ const titleFontSize = themedFontSize(34, context, true);
1027
1043
  const attrFontSize = themedFontSize(26, context, false);
1028
1044
  const quoteLineHeight = Math.max(theme.typography.titleLineHeight ?? 1.4, 1.25);
1029
1045
  const decorativeQuoteFontSize = themedFontSize(280, context, true);
@@ -1037,6 +1053,30 @@ function quoteBlock(input, context) {
1037
1053
  )
1038
1054
  );
1039
1055
  }
1056
+ if (title) {
1057
+ layers.push({
1058
+ type: "text",
1059
+ id: "quote-title",
1060
+ content: {
1061
+ text: title,
1062
+ style: {
1063
+ fontSize: titleFontSize,
1064
+ fontFamily: getThemeFont(context, "body"),
1065
+ fontWeight: "bold",
1066
+ color: theme.colors.textMuted,
1067
+ textAlign: "center",
1068
+ shadow: shouldUseShadow(context)
1069
+ }
1070
+ },
1071
+ position: {
1072
+ x: accentLayout.textCenterX,
1073
+ y: adjustY("14%", accentLayout),
1074
+ width: accentLayout.textWidth,
1075
+ anchor: "center"
1076
+ },
1077
+ animation: { type: "fadeIn", duration: 0.8 }
1078
+ });
1079
+ }
1040
1080
  layers.push({
1041
1081
  type: "text",
1042
1082
  id: "deco-quote",
@@ -1055,7 +1095,7 @@ function quoteBlock(input, context) {
1055
1095
  anchor: "center"
1056
1096
  }
1057
1097
  });
1058
- const quoteYPct = attribution ? 45 : 50;
1098
+ const quoteYPct = attribution ? 45 : title ? 52 : 50;
1059
1099
  layers.push({
1060
1100
  type: "text",
1061
1101
  id: "quote",
@@ -1520,7 +1560,7 @@ function imageWithCaption(input, context) {
1520
1560
  imageSrc,
1521
1561
  imageAlt,
1522
1562
  caption: rawCaption,
1523
- captionPosition: _captionPosition = "bottom",
1563
+ captionPosition = "bottom",
1524
1564
  ambientMotion,
1525
1565
  isTitle,
1526
1566
  subtitle,
@@ -1612,6 +1652,8 @@ function imageWithCaption(input, context) {
1612
1652
  return layers;
1613
1653
  }
1614
1654
  if (caption) {
1655
+ const captionBgY = captionPosition === "top" ? "7%" : captionPosition === "center" ? "42%" : "68%";
1656
+ const captionY = captionPosition === "top" ? "13%" : captionPosition === "center" ? "48%" : "74%";
1615
1657
  layers.push({
1616
1658
  type: "shape",
1617
1659
  id: "caption-gradient",
@@ -1621,7 +1663,7 @@ function imageWithCaption(input, context) {
1621
1663
  },
1622
1664
  position: {
1623
1665
  x: 0,
1624
- y: "68%",
1666
+ y: captionBgY,
1625
1667
  width: "100%",
1626
1668
  height: "16%"
1627
1669
  }
@@ -1643,7 +1685,7 @@ function imageWithCaption(input, context) {
1643
1685
  },
1644
1686
  position: {
1645
1687
  x: "50%",
1646
- y: "74%",
1688
+ y: captionY,
1647
1689
  anchor: "center",
1648
1690
  width: "78%"
1649
1691
  },
@@ -2158,6 +2200,8 @@ function getGridPositions(count, gridHeight, stacked) {
2158
2200
  const halfW = 50 - GAP / 2;
2159
2201
  const halfH = gridHeight / 2 - GAP / 2;
2160
2202
  switch (count) {
2203
+ case 1:
2204
+ return [{ x: 0, y: 0, w: 100, h: gridHeight }];
2161
2205
  case 2:
2162
2206
  if (stacked) {
2163
2207
  return [
@@ -2322,12 +2366,14 @@ function comparisonBar(input, context) {
2322
2366
  const barHeight = 6;
2323
2367
  const topBarY = 36;
2324
2368
  const bottomBarY = 58;
2325
- const maxValue = Math.max(leftValue, rightValue, 1);
2369
+ const leftExtent = Math.max(0, leftValue);
2370
+ const rightExtent = Math.max(0, rightValue);
2371
+ const maxValue = Math.max(leftExtent, rightExtent, 1);
2326
2372
  const longestLabelPx = Math.max(leftDisplay.length, rightDisplay.length) * valueFontSize * 0.58;
2327
2373
  const labelWidthPct = longestLabelPx / viewport.width * 100;
2328
2374
  const maxBarWidth = Math.max(20, Math.min(65, 96 - barStartX - labelWidthPct - 2));
2329
- const leftBarWidth = leftValue / maxValue * maxBarWidth;
2330
- const rightBarWidth = rightValue / maxValue * maxBarWidth;
2375
+ const leftBarWidth = leftExtent / maxValue * maxBarWidth;
2376
+ const rightBarWidth = rightExtent / maxValue * maxBarWidth;
2331
2377
  return [
2332
2378
  // Background
2333
2379
  {
@@ -2461,7 +2507,10 @@ function pullQuote(input, context) {
2461
2507
  ...treatment ? { treatment } : {}
2462
2508
  },
2463
2509
  position: { x: 0, y: 0, width: "100%", height: "100%" },
2464
- animation: ambientMotion ? { type: ambientMotion, duration: 15 } : void 0
2510
+ // Same authored `ambientMotion` vocabulary as every other image
2511
+ // template — map it to a real Ken Burns animation rather than passing
2512
+ // the token straight through as an animation type.
2513
+ animation: mapAmbientMotion(ambientMotion, 15)
2465
2514
  },
2466
2515
  // Dark overlay for text readability
2467
2516
  {
@@ -2553,7 +2602,7 @@ function videoWithCaption(input, context) {
2553
2602
  videoSrc,
2554
2603
  posterSrc,
2555
2604
  videoAlt,
2556
- clipStart,
2605
+ clipStart = 0,
2557
2606
  clipEnd,
2558
2607
  sourceDuration,
2559
2608
  caption: rawCaption,
@@ -2563,6 +2612,7 @@ function videoWithCaption(input, context) {
2563
2612
  } = input;
2564
2613
  const caption = rawCaption ? cleanCaption(rawCaption) : rawCaption;
2565
2614
  const { theme } = context;
2615
+ const resolvedClipEnd = Math.max(clipStart, clipEnd ?? clipStart + Math.max(0, input.duration));
2566
2616
  const captionFontSize = themedFontSize(30, context, false);
2567
2617
  const creditFontSize = themedFontSize(16, context, false);
2568
2618
  const layers = [
@@ -2576,7 +2626,7 @@ function videoWithCaption(input, context) {
2576
2626
  alt: videoAlt,
2577
2627
  fit: "cover",
2578
2628
  clipStart,
2579
- clipEnd,
2629
+ clipEnd: resolvedClipEnd,
2580
2630
  sourceDuration,
2581
2631
  credit: videoCredit,
2582
2632
  license: videoLicense
@@ -4920,6 +4970,7 @@ var TEMPLATE_INPUT_DESCRIPTORS = {
4920
4970
  { key: "colorScheme", description: "Color scheme for the stat", valueHint: COLOR_SCHEME_HINT }
4921
4971
  ],
4922
4972
  quote: [
4973
+ { key: "title", description: "Optional heading above the quote" },
4923
4974
  { key: "quote", description: "The quote text" },
4924
4975
  { key: "attribution", description: "Attribution (author, source)" }
4925
4976
  ],
@@ -5227,7 +5278,7 @@ function fallbackBlockLayers(block, context, notice) {
5227
5278
  style: {
5228
5279
  fontSize: themedFontSize(18, context),
5229
5280
  fontFamily: getThemeFont(context, "body"),
5230
- color: `${colors.text}99`,
5281
+ color: withAlpha(colors.text, 0.6),
5231
5282
  textAlign: "center"
5232
5283
  }
5233
5284
  },
@@ -5242,1287 +5293,2111 @@ function extractBodyText(block) {
5242
5293
  return `${text.slice(0, MAX_BODY_CHARS).trimEnd()}\u2026`;
5243
5294
  }
5244
5295
 
5245
- // src/doc/materializeBlockLayers.ts
5246
- function materializeBlockLayers(block, options = {}) {
5247
- const registry = options.customTemplates && options.customTemplates.length > 0 ? buildRegistry(options.customTemplates) : templateRegistry;
5248
- return materializeBlockLayersWithRuntime(block, options, { registry });
5296
+ // src/doc/asciiDiagram/types.ts
5297
+ var ASCII_CHAR_W = 14;
5298
+ var ASCII_CHAR_H = 28;
5299
+
5300
+ // src/doc/asciiDiagram/mapping.ts
5301
+ function asciiCellToCanvas(col, row) {
5302
+ return { x: col * ASCII_CHAR_W, y: row * ASCII_CHAR_H };
5249
5303
  }
5250
- function materializeBlockLayersWithRuntime(block, options, runtime) {
5251
- const theme = options.theme ?? DEFAULT_THEME;
5252
- const viewport = options.viewport ?? VIEWPORT_PRESETS.landscape;
5253
- const blockIndex = options.blockIndex ?? 0;
5254
- const totalBlocks = options.totalBlocks ?? 1;
5255
- const effectivePersistentLayers = options.persistentLayers === false ? void 0 : options.persistentLayers ?? theme.persistentLayers;
5256
- const existingLayers = block.layers;
5257
- if (existingLayers && existingLayers.length > 0 && !isTemplateBlock(block)) {
5258
- const layers = appendMermaidLayers(cloneData(existingLayers), block, theme, viewport);
5259
- return {
5260
- layers: injectPersistentLayers(
5261
- layers,
5262
- block,
5263
- theme,
5264
- effectivePersistentLayers,
5265
- runtime.expandedPersistentLayers
5266
- ),
5267
- source: "authored"
5268
- };
5269
- }
5270
- if (!isTemplateBlock(block)) {
5271
- const layers = appendMermaidLayers([], block, theme, viewport);
5272
- return {
5273
- layers: injectPersistentLayers(
5274
- layers,
5275
- block,
5276
- theme,
5277
- effectivePersistentLayers,
5278
- runtime.expandedPersistentLayers
5279
- ),
5280
- source: layers.length > 0 ? "rich-content" : "empty"
5281
- };
5282
- }
5283
- const context = runtime.templateContext ? cloneData(runtime.templateContext) : createTemplateContext(theme, blockIndex, totalBlocks, viewport);
5284
- context.block = cloneData(block);
5285
- const execution = executeTemplateMaterialization(block, context, runtime.registry);
5286
- if (execution.status === "ok") {
5287
- const styledLayers = runtime.applyRenderStyle !== false ? applyRenderStyleToLayers(execution.layers, block, theme) : execution.layers;
5288
- const layers = appendMermaidLayers(styledLayers, block, theme, viewport);
5289
- return {
5290
- layers: injectPersistentLayers(
5291
- layers,
5292
- block,
5293
- theme,
5294
- effectivePersistentLayers,
5295
- runtime.expandedPersistentLayers
5296
- ),
5297
- source: "template"
5298
- };
5299
- }
5300
- const diagnostic = toLayerMaterializationDiagnostic(block.template, execution);
5301
- if ((options.failureMode ?? "fallback") === "empty") {
5302
- const layers = appendMermaidLayers([], block, theme, viewport);
5303
- return {
5304
- layers: injectPersistentLayers(
5305
- layers,
5306
- block,
5307
- theme,
5308
- effectivePersistentLayers,
5309
- runtime.expandedPersistentLayers
5310
- ),
5311
- source: layers.length > 0 ? "rich-content" : "empty",
5312
- diagnostic
5313
- };
5314
- }
5315
- const fallbackLayers = appendMermaidLayers(
5316
- fallbackBlockLayers(block, context, diagnostic.message),
5317
- block,
5318
- theme,
5319
- viewport
5320
- );
5304
+ function canvasToAsciiCell(x, y) {
5321
5305
  return {
5322
- layers: injectPersistentLayers(
5323
- fallbackLayers,
5324
- block,
5325
- theme,
5326
- effectivePersistentLayers,
5327
- runtime.expandedPersistentLayers
5328
- ),
5329
- source: "fallback",
5330
- diagnostic
5306
+ col: Math.max(0, Math.round(x / ASCII_CHAR_W)),
5307
+ row: Math.max(0, Math.round(y / ASCII_CHAR_H))
5331
5308
  };
5332
5309
  }
5333
- function mermaidSources(block) {
5334
- return (block.contents ?? []).filter(
5335
- (node) => node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid"
5336
- ).map((node) => node.value).filter((source) => source.trim().length > 0);
5337
- }
5338
- function resolvePositionValue(value, dimension) {
5339
- if (value === void 0) return 0;
5340
- if (typeof value === "number") return value;
5341
- const percent = value.match(/^\s*(-?\d+(?:\.\d+)?)%\s*$/);
5342
- if (percent) return Number(percent[1]) / 100 * dimension;
5343
- const numeric = Number.parseFloat(value);
5344
- return Number.isFinite(numeric) ? numeric : 0;
5345
- }
5346
- function positionRect(position, viewport, fallbackWidth, fallbackHeight) {
5347
- const width = position.width ? resolvePositionValue(position.width, viewport.width) : fallbackWidth;
5348
- const height = position.height ? resolvePositionValue(position.height, viewport.height) : fallbackHeight;
5349
- let x = resolvePositionValue(position.x, viewport.width);
5350
- let y = resolvePositionValue(position.y, viewport.height);
5351
- const anchor = position.anchor ?? "top-left";
5352
- if (anchor === "center") {
5353
- x -= width / 2;
5354
- y -= height / 2;
5355
- } else {
5356
- if (anchor.endsWith("right")) x -= width;
5357
- if (anchor.startsWith("bottom")) y -= height;
5358
- }
5359
- return { x, y, width, height };
5360
- }
5361
- function overlapArea(a, b) {
5362
- const width = Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x));
5363
- const height = Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));
5364
- return width * height;
5365
- }
5366
- function mermaidRegion(layers, viewport) {
5367
- const occupied = layers.filter((layer) => layer.type !== "shape" && layer.type !== "path" && layer.type !== "mermaid").map(
5368
- (layer) => positionRect(
5369
- layer.position,
5370
- viewport,
5371
- layer.type === "text" ? viewport.width * 0.45 : viewport.width * 0.5,
5372
- layer.type === "text" ? viewport.height * 0.2 : viewport.height * 0.55
5373
- )
5374
- );
5375
- if (occupied.length === 0) {
5376
- return {
5377
- x: viewport.width * 0.06,
5378
- y: viewport.height * 0.08,
5379
- width: viewport.width * 0.88,
5380
- height: viewport.height * 0.84
5381
- };
5382
- }
5383
- const candidates = [
5384
- {
5385
- x: viewport.width * 0.52,
5386
- y: viewport.height * 0.12,
5387
- width: viewport.width * 0.44,
5388
- height: viewport.height * 0.76
5389
- },
5390
- {
5391
- x: viewport.width * 0.04,
5392
- y: viewport.height * 0.12,
5393
- width: viewport.width * 0.44,
5394
- height: viewport.height * 0.76
5395
- },
5396
- {
5397
- x: viewport.width * 0.15,
5398
- y: viewport.height * 0.53,
5399
- width: viewport.width * 0.7,
5400
- height: viewport.height * 0.41
5401
- }
5402
- ];
5403
- return candidates.reduce((best, candidate) => {
5404
- const score = occupied.reduce((sum, rect) => sum + overlapArea(candidate, rect), 0);
5405
- const bestScore = occupied.reduce((sum, rect) => sum + overlapArea(best, rect), 0);
5406
- return score < bestScore ? candidate : best;
5407
- });
5408
- }
5409
- function appendMermaidLayers(layers, block, theme, viewport) {
5410
- const sources = mermaidSources(block);
5411
- if (sources.length === 0 || layers.some((layer) => layer.type === "mermaid")) return layers;
5412
- const region = mermaidRegion(layers, viewport);
5413
- const columns = sources.length === 1 ? 1 : 2;
5414
- const rows = Math.ceil(sources.length / columns);
5415
- const gap = Math.min(viewport.width, viewport.height) * 0.018;
5416
- const width = (region.width - gap * (columns - 1)) / columns;
5417
- const height = (region.height - gap * (rows - 1)) / rows;
5418
- const mermaidLayers = sources.map((source, index) => ({
5419
- id: `${block.id}-mermaid-${index + 1}`,
5420
- type: "mermaid",
5421
- position: {
5422
- x: region.x + index % columns * (width + gap),
5423
- y: region.y + Math.floor(index / columns) * (height + gap),
5424
- width,
5425
- height
5426
- },
5427
- content: {
5428
- source,
5429
- background: theme.colors.backgroundLight,
5430
- foreground: theme.colors.text,
5431
- padding: Math.max(12, Math.round(Math.min(width, height) * 0.025))
5432
- }
5310
+ function asciiDiagramToTemplateData(diagram) {
5311
+ const nodes = diagram.nodes.map((n) => ({
5312
+ id: n.id,
5313
+ label: n.label,
5314
+ ...asciiCellToCanvas(n.col, n.row),
5315
+ w: n.wCols * ASCII_CHAR_W,
5316
+ h: n.hRows * ASCII_CHAR_H,
5317
+ ...n.containerId ? { container: n.containerId } : {}
5318
+ }));
5319
+ const anchors = inferEdgeAnchors(nodes, diagram.edges);
5320
+ const edges = diagram.edges.map((e, index) => ({
5321
+ source: e.source,
5322
+ target: e.target,
5323
+ ...e.label ? { label: e.label } : {},
5324
+ ...e.directed ? {} : { directed: false },
5325
+ ...anchors[index],
5326
+ routing: "orthogonal"
5433
5327
  }));
5434
- return [...layers, ...mermaidLayers];
5328
+ return { nodes, edges };
5435
5329
  }
5436
- function executeTemplateMaterialization(templateBlock, context, registry) {
5437
- const safeTemplateBlock = cloneData(templateBlock);
5438
- const safeContext = cloneData(context);
5439
- const maybeChildren = safeTemplateBlock.children;
5440
- if ((!safeContext.children || safeContext.children.length === 0) && maybeChildren?.length) {
5441
- safeContext.children = maybeChildren;
5442
- }
5443
- const templateFn = registry[resolveTemplateName(safeTemplateBlock.template)];
5444
- if (!templateFn) return { status: "unknown-template", layers: [] };
5445
- const { templateData, templateOverrides } = safeTemplateBlock;
5446
- const input = templateData || templateOverrides ? {
5447
- ...safeTemplateBlock,
5448
- ...templateData,
5449
- ...coerceTemplateParams(safeTemplateBlock.template, templateOverrides ?? {}).input
5450
- } : safeTemplateBlock;
5451
- try {
5452
- const renderedLayers = templateFn(input, safeContext);
5453
- if (!Array.isArray(renderedLayers)) {
5330
+ function inferEdgeAnchors(nodes, edges) {
5331
+ const nodeById = new Map(nodes.map((node) => [node.id, node]));
5332
+ const pairs = edges.map((edge) => {
5333
+ const source = nodeById.get(edge.source);
5334
+ const target = nodeById.get(edge.target);
5335
+ if (!source || !target) {
5454
5336
  return {
5455
- status: "invalid-result",
5456
- layers: [],
5457
- receivedType: typeof renderedLayers
5337
+ sourceAnchor: { side: "right", offset: 0.5 },
5338
+ targetAnchor: { side: "left", offset: 0.5 }
5458
5339
  };
5459
5340
  }
5460
- return { status: "ok", layers: cloneData(renderedLayers) };
5461
- } catch (error) {
5462
- return { status: "error", layers: [], error };
5463
- }
5464
- }
5465
- function toLayerMaterializationDiagnostic(template, execution) {
5466
- if (execution.status === "unknown-template") {
5467
- return {
5468
- code: "unknown-template",
5469
- template,
5470
- message: `Unknown template "${template}"`
5341
+ const sourceW = source.w ?? ASCII_CHAR_W * 12;
5342
+ const sourceH = source.h ?? ASCII_CHAR_H * 3;
5343
+ const targetW = target.w ?? ASCII_CHAR_W * 12;
5344
+ const targetH = target.h ?? ASCII_CHAR_H * 3;
5345
+ const dx = target.x + targetW / 2 - (source.x + sourceW / 2);
5346
+ const dy = target.y + targetH / 2 - (source.y + sourceH / 2);
5347
+ const horizontalDistance = Math.abs(dx) / Math.max(1, (sourceW + targetW) / 2);
5348
+ const verticalDistance = Math.abs(dy) / Math.max(1, (sourceH + targetH) / 2);
5349
+ if (verticalDistance >= horizontalDistance) {
5350
+ return dy >= 0 ? {
5351
+ sourceAnchor: { side: "bottom", offset: 0.5 },
5352
+ targetAnchor: { side: "top", offset: 0.5 }
5353
+ } : {
5354
+ sourceAnchor: { side: "top", offset: 0.5 },
5355
+ targetAnchor: { side: "bottom", offset: 0.5 }
5356
+ };
5357
+ }
5358
+ return dx >= 0 ? {
5359
+ sourceAnchor: { side: "right", offset: 0.5 },
5360
+ targetAnchor: { side: "left", offset: 0.5 }
5361
+ } : {
5362
+ sourceAnchor: { side: "left", offset: 0.5 },
5363
+ targetAnchor: { side: "right", offset: 0.5 }
5471
5364
  };
5365
+ });
5366
+ distributeOffsets(pairs, edges, nodes, "source");
5367
+ distributeOffsets(pairs, edges, nodes, "target");
5368
+ return pairs;
5369
+ }
5370
+ function distributeOffsets(pairs, edges, nodes, endpoint) {
5371
+ const nodeById = new Map(nodes.map((node) => [node.id, node]));
5372
+ const groups = /* @__PURE__ */ new Map();
5373
+ for (let index = 0; index < edges.length; index++) {
5374
+ const edge = edges[index];
5375
+ const anchor = endpoint === "source" ? pairs[index].sourceAnchor : pairs[index].targetAnchor;
5376
+ const nodeId = endpoint === "source" ? edge.source : edge.target;
5377
+ const key = `${nodeId}:${anchor.side}`;
5378
+ const group = groups.get(key) ?? [];
5379
+ group.push(index);
5380
+ groups.set(key, group);
5472
5381
  }
5473
- if (execution.status === "invalid-result") {
5382
+ for (const indexes of groups.values()) {
5383
+ if (indexes.length < 2) continue;
5384
+ const side = endpoint === "source" ? pairs[indexes[0]].sourceAnchor.side : pairs[indexes[0]].targetAnchor.side;
5385
+ indexes.sort((a, b) => {
5386
+ const oppositeA = nodeById.get(endpoint === "source" ? edges[a].target : edges[a].source);
5387
+ const oppositeB = nodeById.get(endpoint === "source" ? edges[b].target : edges[b].source);
5388
+ const axisA = side === "top" || side === "bottom" ? oppositeA?.x ?? 0 : oppositeA?.y ?? 0;
5389
+ const axisB = side === "top" || side === "bottom" ? oppositeB?.x ?? 0 : oppositeB?.y ?? 0;
5390
+ return axisA - axisB || a - b;
5391
+ });
5392
+ indexes.forEach((edgeIndex, rank) => {
5393
+ const anchor = endpoint === "source" ? pairs[edgeIndex].sourceAnchor : pairs[edgeIndex].targetAnchor;
5394
+ anchor.offset = (rank + 1) / (indexes.length + 1);
5395
+ });
5396
+ }
5397
+ }
5398
+ function asciiDiagramFromTemplateData(nodes, edges, options = {}) {
5399
+ const ids = new Set(nodes.map((n) => n.id));
5400
+ const outNodes = nodes.map((n) => {
5401
+ const { col, row } = canvasToAsciiCell(n.x, n.y);
5402
+ const labelLines = n.label.length > 0 ? n.label.split("\n") : [];
5403
+ const innerW = labelLines.reduce((w, l) => Math.max(w, l.length), 0);
5404
+ const wCols = typeof n.w === "number" ? Math.max(3, Math.round(n.w / ASCII_CHAR_W)) : innerW + 4;
5405
+ const hRows = typeof n.h === "number" ? Math.max(3, Math.round(n.h / ASCII_CHAR_H)) : labelLines.length + 2;
5474
5406
  return {
5475
- code: "invalid-template-result",
5476
- template,
5477
- receivedType: execution.receivedType,
5478
- message: `Template "${template}" returned ${execution.receivedType}, not a layer array`
5407
+ id: n.id,
5408
+ label: n.label,
5409
+ col,
5410
+ row,
5411
+ wCols: Math.max(wCols, 3),
5412
+ hRows: Math.max(hRows, 3),
5413
+ ...n.container && ids.has(n.container) && n.container !== n.id ? { containerId: n.container } : {}
5479
5414
  };
5480
- }
5415
+ });
5481
5416
  return {
5482
- code: "template-error",
5483
- template,
5484
- cause: execution.error,
5485
- message: `Template "${template}" failed`
5417
+ nodes: outNodes,
5418
+ edges: edges.filter((e) => ids.has(e.source) && ids.has(e.target)).map((e) => ({
5419
+ source: e.source,
5420
+ target: e.target,
5421
+ ...e.label ? { label: e.label } : {},
5422
+ directed: e.directed !== false
5423
+ })),
5424
+ style: options.style ?? "unicode",
5425
+ warnings: []
5486
5426
  };
5487
5427
  }
5488
- function injectPersistentLayers(layers, block, theme, persistentLayers, expanded) {
5489
- if (!persistentLayers) return layers;
5490
- const bottomLayers = expanded?.bottomLayers ?? expandPersistentLayers(persistentLayers.bottomLayers, theme);
5491
- const topLayers = expanded?.topLayers ?? expandPersistentLayers(persistentLayers.topLayers, theme);
5492
- return wrapWithPersistentLayers(layers, block, bottomLayers, topLayers);
5428
+ function asciiDiagramFromBlocks(children, options = {}) {
5429
+ const layout = computeDiagramLayout(children);
5430
+ const nodes = layout.nodes.map((n) => ({
5431
+ id: n.id,
5432
+ label: n.label,
5433
+ x: n.x,
5434
+ y: n.y
5435
+ }));
5436
+ const edges = layout.edges.map((e) => ({
5437
+ source: e.source,
5438
+ target: e.target,
5439
+ ...e.type ? { label: e.type } : {}
5440
+ }));
5441
+ const diagram = asciiDiagramFromTemplateData(nodes, edges, options);
5442
+ diagram.warnings.push(...layout.warnings);
5443
+ return diagram;
5493
5444
  }
5494
5445
 
5495
- // src/doc/templates/metadata.ts
5496
- var TEMPLATE_METADATA = {
5497
- title: {
5498
- label: "Title",
5499
- description: "A bold opening slide with large title text, perfect for covers and chapters."
5500
- },
5501
- sectionHeader: {
5502
- label: "Section Header",
5503
- description: "A clean section break with a prominent title and optional subtitle."
5504
- },
5505
- statHighlight: {
5506
- label: "Stat Highlight",
5507
- description: "Showcases a single key number or metric with supporting context."
5446
+ // src/doc/asciiTimeline/mapping.ts
5447
+ function asciiTimelineToTemplateData(timeline) {
5448
+ const globalStart = timeline.tracks.length > 0 ? Math.min(...timeline.tracks.map((track) => track.startColumn)) : 0;
5449
+ const globalEnd = timeline.tracks.length > 0 ? Math.max(...timeline.tracks.map((track) => track.endColumn)) : 1;
5450
+ const span = Math.max(1, globalEnd - globalStart);
5451
+ const tracks = timeline.tracks.map((track) => ({
5452
+ id: track.id,
5453
+ ...track.label ? { label: track.label } : {},
5454
+ ...track.endLabel ? { endLabel: track.endLabel } : {},
5455
+ events: track.events.map(
5456
+ (event) => ({
5457
+ id: event.id,
5458
+ label: event.label,
5459
+ ...event.description ? { description: event.description } : {},
5460
+ position: clamp012((event.column - globalStart) / span),
5461
+ ...event.side ? { side: event.side } : {},
5462
+ ...event.descriptionSide ? { descriptionSide: event.descriptionSide } : {},
5463
+ ...event.callout === false ? { callout: false } : {},
5464
+ ...event.marker ? { marker: event.marker } : {}
5465
+ })
5466
+ )
5467
+ }));
5468
+ const links = timeline.links.map((link) => ({ ...link }));
5469
+ return { tracks, links };
5470
+ }
5471
+ function clamp012(value) {
5472
+ return Math.max(0, Math.min(1, value));
5473
+ }
5474
+
5475
+ // src/doc/treeview/mapping.ts
5476
+ function treeToTemplateData(tree) {
5477
+ return { items: tree.roots.map(toItem) };
5478
+ }
5479
+ function toItem(node) {
5480
+ return {
5481
+ id: node.id,
5482
+ label: node.label,
5483
+ ...node.isDir ? { isDir: true } : {},
5484
+ ...node.comment ? { comment: node.comment } : {},
5485
+ children: node.children.map(toItem)
5486
+ };
5487
+ }
5488
+ function treeFromTemplateData(items, options = {}) {
5489
+ const roots = items.map(fromItem);
5490
+ return { roots, style: options.style ?? "unicode", warnings: [] };
5491
+ }
5492
+ function fromItem(item) {
5493
+ const children = Array.isArray(item.children) ? item.children.map(fromItem) : [];
5494
+ return {
5495
+ id: typeof item.id === "string" && item.id ? item.id : "node",
5496
+ label: typeof item.label === "string" ? item.label : "",
5497
+ children,
5498
+ isDir: item.isDir === true || children.length > 0,
5499
+ ...item.comment ? { comment: item.comment } : {}
5500
+ };
5501
+ }
5502
+ function treeFromMarkdownList(list) {
5503
+ const usedIds = /* @__PURE__ */ new Map();
5504
+ const slugify2 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "node";
5505
+ const idFor = (label) => {
5506
+ const base = slugify2(label.replace(/\/$/, ""));
5507
+ const count = usedIds.get(base) ?? 0;
5508
+ usedIds.set(base, count + 1);
5509
+ return count === 0 ? base : `${base}-${count + 1}`;
5510
+ };
5511
+ const itemToNode = (item) => {
5512
+ const labelNode = item.children.find((c) => c.type !== "list");
5513
+ const label = labelNode ? extractPlainText(labelNode).trim() : "";
5514
+ const childLists = item.children.filter((c) => c.type === "list");
5515
+ const children = [];
5516
+ for (const cl of childLists) for (const ci of cl.children) children.push(itemToNode(ci));
5517
+ return {
5518
+ id: idFor(label),
5519
+ label,
5520
+ children,
5521
+ isDir: label.endsWith("/") || children.length > 0
5522
+ };
5523
+ };
5524
+ return { roots: list.children.map(itemToNode), style: "unicode", warnings: [] };
5525
+ }
5526
+ function findFirstList(contents) {
5527
+ return contents?.find((n) => n.type === "list");
5528
+ }
5529
+
5530
+ // src/doc/templateInputs.ts
5531
+ var VIDEO_FILE_RE = /\.(?:webm|mp4|mov|m4v|ogv)(?:[?#].*)?$/i;
5532
+ function extractBodyPlainText(contents) {
5533
+ if (!contents || contents.length === 0) return "";
5534
+ return contents.filter((node) => !(node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")).map((n) => extractPlainText(n)).join("\n").trim();
5535
+ }
5536
+ function extractListItems(contents) {
5537
+ if (!contents) return [];
5538
+ const items = [];
5539
+ for (const node of contents) {
5540
+ if (node.type === "list") {
5541
+ for (const item of node.children) {
5542
+ const text = extractPlainText(item).trim();
5543
+ if (text) items.push(text);
5544
+ }
5545
+ }
5546
+ }
5547
+ return items;
5548
+ }
5549
+ function parseDim(raw) {
5550
+ if (raw === void 0) return void 0;
5551
+ const n = parseFloat(raw);
5552
+ return Number.isFinite(n) && n > 0 ? n : void 0;
5553
+ }
5554
+ function extractImages(contents, limit = Infinity) {
5555
+ if (!contents || contents.length === 0) return [];
5556
+ const found = [];
5557
+ function fromHtml(nodes) {
5558
+ for (const node of nodes) {
5559
+ if (found.length >= limit) return;
5560
+ if (!node || typeof node !== "object") continue;
5561
+ const n = node;
5562
+ if (n.type === "htmlElement" && n.tagName === "img") {
5563
+ const attrs = n.attributes;
5564
+ if (attrs && typeof attrs.src === "string" && attrs.src) {
5565
+ found.push({
5566
+ src: attrs.src,
5567
+ alt: typeof attrs.alt === "string" ? attrs.alt : "",
5568
+ width: parseDim(attrs.width),
5569
+ height: parseDim(attrs.height)
5570
+ });
5571
+ }
5572
+ }
5573
+ if (Array.isArray(n.children)) fromHtml(n.children);
5574
+ }
5575
+ }
5576
+ function walk(node) {
5577
+ if (found.length >= limit) return;
5578
+ if (!node || typeof node !== "object") return;
5579
+ const n = node;
5580
+ if (n.type === "image" && typeof n.url === "string" && n.url) {
5581
+ found.push({ src: n.url, alt: typeof n.alt === "string" ? n.alt : "" });
5582
+ return;
5583
+ }
5584
+ if ((n.type === "htmlBlock" || n.type === "htmlInline") && Array.isArray(n.htmlChildren)) {
5585
+ fromHtml(n.htmlChildren);
5586
+ }
5587
+ if (Array.isArray(n.children)) {
5588
+ for (const child of n.children) walk(child);
5589
+ }
5590
+ }
5591
+ for (const node of contents) {
5592
+ if (found.length >= limit) break;
5593
+ walk(node);
5594
+ }
5595
+ return found;
5596
+ }
5597
+ function extractFirstImage(contents) {
5598
+ return extractImages(contents, 1)[0] ?? null;
5599
+ }
5600
+ function extractEmbeddedVideos(contents, limit = Infinity) {
5601
+ if (!contents || contents.length === 0) return [];
5602
+ const found = [];
5603
+ const seen = /* @__PURE__ */ new Set();
5604
+ const add = (video) => {
5605
+ if (!video.src || found.length >= limit || seen.has(video.src)) return;
5606
+ seen.add(video.src);
5607
+ found.push(video);
5608
+ };
5609
+ function nestedSource(children) {
5610
+ if (!Array.isArray(children)) return void 0;
5611
+ for (const child of children) {
5612
+ if (!child || typeof child !== "object") continue;
5613
+ const node = child;
5614
+ if (node.tagName === "source") {
5615
+ const attrs = node.attributes;
5616
+ if (typeof attrs?.src === "string" && attrs.src) return attrs.src;
5617
+ }
5618
+ const nested = nestedSource(node.children);
5619
+ if (nested) return nested;
5620
+ }
5621
+ return void 0;
5622
+ }
5623
+ function walk(node) {
5624
+ if (found.length >= limit || !node || typeof node !== "object") return;
5625
+ const n = node;
5626
+ if (n.type === "htmlElement" && n.tagName === "video") {
5627
+ const attrs = n.attributes;
5628
+ const src = attrs?.src || nestedSource(n.children);
5629
+ if (src) {
5630
+ add({
5631
+ src,
5632
+ ...attrs?.poster ? { posterSrc: attrs.poster } : {},
5633
+ alt: attrs?.["aria-label"] || attrs?.title || attrs?.alt || ""
5634
+ });
5635
+ }
5636
+ } else if ((n.type === "link" || n.type === "image") && typeof n.url === "string" && VIDEO_FILE_RE.test(n.url)) {
5637
+ add({
5638
+ src: n.url,
5639
+ alt: typeof n.alt === "string" ? n.alt : extractPlainText(n).trim()
5640
+ });
5641
+ }
5642
+ if (Array.isArray(n.children)) n.children.forEach(walk);
5643
+ if (Array.isArray(n.htmlChildren)) n.htmlChildren.forEach(walk);
5644
+ }
5645
+ contents.forEach(walk);
5646
+ return found;
5647
+ }
5648
+ function extractFirstEmbeddedVideo(contents) {
5649
+ return extractEmbeddedVideos(contents, 1)[0] ?? null;
5650
+ }
5651
+ function extractTableFromContents(contents) {
5652
+ if (!contents) return null;
5653
+ for (const node of contents) {
5654
+ if (node.type === "table") {
5655
+ const table = node;
5656
+ const [headerRow, ...bodyRows] = table.children;
5657
+ if (!headerRow) return null;
5658
+ const headers = headerRow.children.map((cell) => extractPlainText(cell).trim());
5659
+ const rows = bodyRows.map((row) => row.children.map((cell) => extractPlainText(cell).trim()));
5660
+ return { headers, rows, align: table.align };
5661
+ }
5662
+ }
5663
+ return null;
5664
+ }
5665
+ function extractBlockquoteText(contents) {
5666
+ if (!contents) return "";
5667
+ for (const node of contents) {
5668
+ if (node.type === "blockquote") return extractPlainText(node).trim();
5669
+ }
5670
+ return "";
5671
+ }
5672
+ function firstParagraph(contents) {
5673
+ if (!contents) return null;
5674
+ const index = contents.findIndex((node) => node.type === "paragraph");
5675
+ if (index < 0) return null;
5676
+ return {
5677
+ node: contents[index],
5678
+ index
5679
+ };
5680
+ }
5681
+ function leadingStrongStat(paragraph) {
5682
+ for (const child of paragraph.children) {
5683
+ if (child.type === "text" && child.value.trim() === "") continue;
5684
+ if (child.type !== "strong") return null;
5685
+ const text = extractPlainText(child).trim();
5686
+ const match = text.match(/^(?:[$€£¥]\s*)?[+-]?\d+(?:[.,]\d+)*(?:\s?(?:[%‰x×]|[A-Za-z]+))?/);
5687
+ return match?.[0].trim() || null;
5688
+ }
5689
+ return null;
5690
+ }
5691
+ function removeStatPhrase(text, start, end) {
5692
+ const before = text.slice(0, start).trim();
5693
+ let after = text.slice(end);
5694
+ if (!before) after = after.replace(/^\s*(?:[-–—:]\s*)?/, "");
5695
+ else after = after.trim();
5696
+ return [before, after].filter(Boolean).join(" ").trim();
5697
+ }
5698
+ function bodyWithoutParagraphStat(contents, paragraphIndex, paragraphText, start, end) {
5699
+ if (!contents) return "";
5700
+ return contents.map(
5701
+ (node, index) => index === paragraphIndex ? removeStatPhrase(paragraphText, start, end) : extractPlainText(node).trim()
5702
+ ).filter(Boolean).join("\n").trim();
5703
+ }
5704
+ function deriveStatHighlightInputs(headingText, contents, bodyText3) {
5705
+ const paragraph = firstParagraph(contents);
5706
+ if (paragraph) {
5707
+ const paragraphText = extractPlainText(paragraph.node).trim();
5708
+ const strongStat = leadingStrongStat(paragraph.node);
5709
+ if (strongStat) {
5710
+ const start = paragraphText.indexOf(strongStat);
5711
+ const description = bodyWithoutParagraphStat(
5712
+ contents,
5713
+ paragraph.index,
5714
+ paragraphText,
5715
+ start,
5716
+ start + strongStat.length
5717
+ );
5718
+ return {
5719
+ stat: strongStat,
5720
+ description: description || headingText || strongStat
5721
+ };
5722
+ }
5723
+ }
5724
+ const trimmedHeading = headingText.trim();
5725
+ const headingStat = matchNumberHighlight(trimmedHeading);
5726
+ if (headingStat && headingStat.index === 0 && headingStat.end === trimmedHeading.length) {
5727
+ return { stat: headingText, description: bodyText3 || headingText };
5728
+ }
5729
+ if (paragraph) {
5730
+ const paragraphText = extractPlainText(paragraph.node).trim();
5731
+ const bodyStat = matchNumberHighlight(paragraphText);
5732
+ if (bodyStat) {
5733
+ const description = bodyWithoutParagraphStat(
5734
+ contents,
5735
+ paragraph.index,
5736
+ paragraphText,
5737
+ bodyStat.index,
5738
+ bodyStat.end
5739
+ );
5740
+ return {
5741
+ stat: bodyStat.value,
5742
+ description: description || headingText || bodyStat.value
5743
+ };
5744
+ }
5745
+ }
5746
+ return { stat: headingText, description: bodyText3 || headingText };
5747
+ }
5748
+ function normalizeCoverageText(value) {
5749
+ return value.replace(/\s+/g, " ").trim();
5750
+ }
5751
+ function autoTemplatePreservesContent(templateName, contents) {
5752
+ const nodes = contents ?? [];
5753
+ switch (templateName) {
5754
+ case "quote":
5755
+ return nodes.length === 1 && nodes[0]?.type === "blockquote";
5756
+ case "dataTable":
5757
+ return nodes.length === 1 && nodes[0]?.type === "table";
5758
+ case "list":
5759
+ return nodes.length === 1 && nodes[0]?.type === "list";
5760
+ case "diagram":
5761
+ case "timeline":
5762
+ return nodes.length === 1 && nodes[0]?.type === "code";
5763
+ case "tree":
5764
+ return nodes.length === 1 && (nodes[0]?.type === "code" || nodes[0]?.type === "list");
5765
+ case "photoGrid": {
5766
+ const images = extractImages(nodes);
5767
+ if (images.length < 2 || images.length > 4) return false;
5768
+ const bodyText3 = normalizeCoverageText(extractBodyPlainText(nodes));
5769
+ const representedText = normalizeCoverageText(
5770
+ images.map((image) => image.alt).filter(Boolean).join(" ")
5771
+ );
5772
+ return bodyText3 === "" || bodyText3 === representedText;
5773
+ }
5774
+ case "videoWithCaption": {
5775
+ const videos = extractEmbeddedVideos(nodes);
5776
+ if (videos.length !== 1) return false;
5777
+ const bodyText3 = normalizeCoverageText(extractBodyPlainText(nodes));
5778
+ return bodyText3 === normalizeCoverageText(videos[0]?.alt ?? "");
5779
+ }
5780
+ case "leftFeature":
5781
+ case "rightFeature":
5782
+ case "statHighlight":
5783
+ return true;
5784
+ default:
5785
+ return false;
5786
+ }
5787
+ }
5788
+ function deriveTemplateInputs(templateName, headingText, contents, options = {}) {
5789
+ const placeholders = options.placeholders === true;
5790
+ const bodyText3 = extractBodyPlainText(contents);
5791
+ switch (templateName) {
5792
+ case "statHighlight": {
5793
+ const inputs = deriveStatHighlightInputs(headingText, contents, bodyText3);
5794
+ if (options.preserveSourceHeading && headingText && inputs.stat !== headingText && inputs.description !== headingText) {
5795
+ return {
5796
+ ...inputs,
5797
+ description: headingText,
5798
+ ...inputs.description ? { detail: inputs.description } : {}
5799
+ };
5800
+ }
5801
+ return inputs;
5802
+ }
5803
+ case "quote": {
5804
+ const quote = extractBlockquoteText(contents) || bodyText3;
5805
+ if (quote) return { quote, ...headingText ? { title: headingText } : {} };
5806
+ return { quote: headingText };
5807
+ }
5808
+ case "fullBleedQuote":
5809
+ case "pullQuote": {
5810
+ const text = extractBlockquoteText(contents) || bodyText3 || headingText;
5811
+ return { text };
5812
+ }
5813
+ case "factCard":
5814
+ return { fact: headingText, explanation: bodyText3 || headingText };
5815
+ case "comparisonBar":
5816
+ return placeholders ? { leftLabel: "A", leftValue: 60, rightLabel: "B", rightValue: 40 } : null;
5817
+ case "list": {
5818
+ const items = extractListItems(contents);
5819
+ if (items.length > 0) return { items, ...headingText ? { title: headingText } : {} };
5820
+ return placeholders ? { items: ["Item 1", "Item 2", "Item 3"] } : null;
5821
+ }
5822
+ case "definitionCard":
5823
+ return { term: headingText, definition: bodyText3 || headingText };
5824
+ case "dateEvent":
5825
+ return { date: headingText, description: bodyText3 || headingText };
5826
+ case "dataTable": {
5827
+ const tableData = extractTableFromContents(contents);
5828
+ if (tableData) return { ...tableData, ...headingText ? { title: headingText } : {} };
5829
+ return placeholders ? { headers: ["Column"], rows: [["Data"]] } : null;
5830
+ }
5831
+ case "imageWithCaption": {
5832
+ const img = extractFirstImage(contents);
5833
+ if (!img) return placeholders ? { caption: headingText } : null;
5834
+ return { imageSrc: img.src, imageAlt: img.alt || headingText, caption: headingText };
5835
+ }
5836
+ case "photoGrid": {
5837
+ const images = extractImages(contents, 4);
5838
+ if (images.length < 2) return placeholders ? {} : null;
5839
+ return {
5840
+ images: images.map((i) => ({ src: i.src, alt: i.alt })),
5841
+ caption: headingText
5842
+ };
5843
+ }
5844
+ case "videoWithCaption": {
5845
+ const video = extractFirstEmbeddedVideo(contents);
5846
+ if (!video) return placeholders ? { caption: headingText } : null;
5847
+ return {
5848
+ videoSrc: video.src,
5849
+ posterSrc: video.posterSrc,
5850
+ videoAlt: video.alt || headingText,
5851
+ caption: headingText
5852
+ };
5853
+ }
5854
+ case "leftFeature":
5855
+ case "rightFeature": {
5856
+ const img = extractFirstImage(contents);
5857
+ if (!img && !placeholders) return null;
5858
+ return {
5859
+ imageSrc: img?.src ?? "",
5860
+ imageAlt: img?.alt || headingText,
5861
+ imageWidth: img?.width,
5862
+ imageHeight: img?.height,
5863
+ title: headingText,
5864
+ body: bodyText3
5865
+ };
5866
+ }
5867
+ case "diagram": {
5868
+ const fences = (contents ?? []).filter((n) => n.type === "code");
5869
+ const fence = fences.length === 1 && isEligibleAsciiFenceLang(fences[0].lang) ? fences[0] : void 0;
5870
+ const detection = fence ? detectAsciiDiagram(fence.value, { explicit: isExplicitDiagramLang(fence.lang) }) : void 0;
5871
+ if (!detection?.isDiagram || !detection.diagram) return placeholders ? {} : null;
5872
+ const { nodes, edges } = asciiDiagramToTemplateData(detection.diagram);
5873
+ return { nodes, edges, ...headingText ? { title: headingText } : {} };
5874
+ }
5875
+ case "timeline": {
5876
+ const fences = (contents ?? []).filter((n) => n.type === "code");
5877
+ const fence = fences.length === 1 && isEligibleAsciiTimelineFenceLang(fences[0].lang) ? fences[0] : void 0;
5878
+ const detection = fence ? detectAsciiTimeline(fence.value, { explicit: true }) : void 0;
5879
+ if (!detection?.isTimeline || !detection.timeline) return placeholders ? {} : null;
5880
+ const { tracks, links } = asciiTimelineToTemplateData(detection.timeline);
5881
+ return { tracks, links, ...headingText ? { title: headingText } : {} };
5882
+ }
5883
+ case "tree": {
5884
+ const fences = (contents ?? []).filter((n) => n.type === "code");
5885
+ const fence = fences.length === 1 && isEligibleTreeFenceLang(fences[0].lang) ? fences[0] : void 0;
5886
+ if (fence) {
5887
+ const detection = detectTree(fence.value, { explicit: isExplicitTreeLang(fence.lang) });
5888
+ if (detection.isTree && detection.tree) {
5889
+ const { items } = treeToTemplateData(detection.tree);
5890
+ return { items, ...headingText ? { title: headingText } : {} };
5891
+ }
5892
+ }
5893
+ const list = findFirstList(contents);
5894
+ if (list) {
5895
+ const { items } = treeToTemplateData(treeFromMarkdownList(list));
5896
+ if (items.length > 0) return { items, ...headingText ? { title: headingText } : {} };
5897
+ }
5898
+ return placeholders ? {} : null;
5899
+ }
5900
+ default:
5901
+ return placeholders ? {} : null;
5902
+ }
5903
+ }
5904
+
5905
+ // src/doc/templates/mediaLayoutPolicy.ts
5906
+ var TITLE_VARIANTS = {
5907
+ landscape: {
5908
+ wide: "title-stack",
5909
+ tall: "split",
5910
+ square: "title-stack",
5911
+ multiple: "title-stack"
5912
+ },
5913
+ square: {
5914
+ wide: "title-stack",
5915
+ tall: "split",
5916
+ square: "title-stack",
5917
+ multiple: "title-stack"
5918
+ },
5919
+ portrait: {
5920
+ wide: "title-stack",
5921
+ tall: "title-stack",
5922
+ square: "title-stack",
5923
+ multiple: "title-stack"
5924
+ }
5925
+ };
5926
+ var TEXT_VARIANTS = {
5927
+ landscape: {
5928
+ wide: "split",
5929
+ tall: "split",
5930
+ square: "split",
5931
+ multiple: "split"
5932
+ },
5933
+ square: {
5934
+ wide: "text-stack",
5935
+ tall: "split",
5936
+ square: "text-stack",
5937
+ multiple: "text-stack"
5938
+ },
5939
+ portrait: {
5940
+ wide: "text-stack",
5941
+ tall: "text-stack",
5942
+ square: "text-stack",
5943
+ multiple: "text-stack"
5944
+ }
5945
+ };
5946
+ var DENSE_VARIANTS = {
5947
+ landscape: {
5948
+ wide: "dense-stack",
5949
+ tall: "dense-stack",
5950
+ square: "dense-stack",
5951
+ multiple: "dense-stack"
5952
+ },
5953
+ square: {
5954
+ wide: "dense-stack",
5955
+ tall: "dense-stack",
5956
+ square: "dense-stack",
5957
+ multiple: "dense-stack"
5958
+ },
5959
+ portrait: {
5960
+ wide: "dense-stack",
5961
+ tall: "dense-stack",
5962
+ square: "dense-stack",
5963
+ multiple: "dense-stack"
5964
+ }
5965
+ };
5966
+ var BLOCK_MEDIA_LAYOUT_POLICIES = {
5967
+ title: {
5968
+ summary: "Text-only by default; supplemental media gets a title band plus a reserved field.",
5969
+ noMedia: "template-default",
5970
+ ownership: "supplemental",
5971
+ unconsumedMedia: "reserved-slot",
5972
+ variants: TITLE_VARIANTS
5973
+ },
5974
+ sectionHeader: {
5975
+ summary: "Uses its authored background image when present; otherwise supplemental media receives a title composition.",
5976
+ noMedia: "template-without-optional-media",
5977
+ ownership: "optional-native",
5978
+ nativeLayout: "background-image",
5979
+ additionalMediaLayout: "overlay-inset",
5980
+ unconsumedMedia: "reserve-when-no-native-media",
5981
+ variants: TITLE_VARIANTS
5982
+ },
5983
+ statHighlight: {
5984
+ summary: "Uses its optional accent image natively; otherwise supplemental media occupies a companion field.",
5985
+ noMedia: "template-without-optional-media",
5986
+ ownership: "optional-native",
5987
+ nativeLayout: "accent-strip-or-inset",
5988
+ additionalMediaLayout: "overlay-inset",
5989
+ unconsumedMedia: "reserve-when-no-native-media",
5990
+ variants: TEXT_VARIANTS
5508
5991
  },
5509
5992
  quote: {
5510
- label: "Quote",
5511
- description: "Displays a stylized pull quote with decorative marks and attribution."
5993
+ summary: "Uses its optional accent image natively; otherwise supplemental media is paired with the quote.",
5994
+ noMedia: "template-without-optional-media",
5995
+ ownership: "optional-native",
5996
+ nativeLayout: "accent-strip-or-inset",
5997
+ additionalMediaLayout: "overlay-inset",
5998
+ unconsumedMedia: "reserve-when-no-native-media",
5999
+ variants: TEXT_VARIANTS
5512
6000
  },
5513
6001
  factCard: {
5514
- label: "Fact Card",
5515
- description: "Presents a focused fact or insight with a labeled header and body text."
6002
+ summary: "Uses its optional accent image natively; otherwise supplemental media is paired with the fact card.",
6003
+ noMedia: "template-without-optional-media",
6004
+ ownership: "optional-native",
6005
+ nativeLayout: "accent-strip-or-inset",
6006
+ additionalMediaLayout: "overlay-inset",
6007
+ unconsumedMedia: "reserve-when-no-native-media",
6008
+ variants: TEXT_VARIANTS
5516
6009
  },
5517
6010
  twoColumn: {
5518
- label: "Two Column",
5519
- description: "Divides the slide into two equal side-by-side content columns."
6011
+ summary: "Text comparison by default; supplemental media stacks below the dense two-column panel.",
6012
+ noMedia: "template-default",
6013
+ ownership: "supplemental",
6014
+ unconsumedMedia: "reserved-slot",
6015
+ variants: DENSE_VARIANTS
5520
6016
  },
5521
6017
  dateEvent: {
5522
- label: "Date Event",
5523
- description: "Highlights a date-based event or milestone with a prominent date display."
6018
+ summary: "Uses its optional accent image natively; otherwise supplemental media is paired with the event.",
6019
+ noMedia: "template-without-optional-media",
6020
+ ownership: "optional-native",
6021
+ nativeLayout: "accent-strip-or-inset",
6022
+ additionalMediaLayout: "overlay-inset",
6023
+ unconsumedMedia: "reserve-when-no-native-media",
6024
+ variants: TEXT_VARIANTS
5524
6025
  },
5525
6026
  imageWithCaption: {
5526
- label: "Image with Caption",
5527
- description: "Displays a full-bleed image with an optional caption below."
6027
+ summary: "Requires one primary image and owns its full-bleed image/caption geometry.",
6028
+ noMedia: "unsupported",
6029
+ ownership: "required-native",
6030
+ nativeLayout: "full-bleed-image",
6031
+ additionalMediaLayout: "overlay-inset",
6032
+ unconsumedMedia: "retain-native-layout"
5528
6033
  },
5529
6034
  leftFeature: {
5530
- label: "Left Feature",
5531
- description: "Image on the left, title and body text stacked on the right."
6035
+ summary: "Requires a primary image in the left feature cell; text occupies the right cell.",
6036
+ noMedia: "unsupported",
6037
+ ownership: "required-native",
6038
+ nativeLayout: "feature-left",
6039
+ additionalMediaLayout: "overlay-inset",
6040
+ unconsumedMedia: "retain-native-layout"
5532
6041
  },
5533
6042
  rightFeature: {
5534
- label: "Right Feature",
5535
- description: "Image on the right, title and body text stacked on the left."
6043
+ summary: "Requires a primary image in the right feature cell; text occupies the left cell.",
6044
+ noMedia: "unsupported",
6045
+ ownership: "required-native",
6046
+ nativeLayout: "feature-right",
6047
+ additionalMediaLayout: "overlay-inset",
6048
+ unconsumedMedia: "retain-native-layout"
5536
6049
  },
5537
6050
  map: {
5538
- label: "Map",
5539
- description: "Embeds an interactive map centered on a geographic location."
6051
+ summary: "The map canvas is the primary visual; other media must not reflow its geography.",
6052
+ noMedia: "intrinsic-visual",
6053
+ ownership: "intrinsic-visual",
6054
+ nativeLayout: "map-canvas",
6055
+ additionalMediaLayout: "overlay-inset",
6056
+ unconsumedMedia: "retain-native-layout"
5540
6057
  },
5541
6058
  fullBleedQuote: {
5542
- label: "Full Bleed Quote",
5543
- description: "A full-width quote that spans the entire slide for maximum impact."
6059
+ summary: "Despite its name this is text-only; supplemental media receives the same dramatic title treatment.",
6060
+ noMedia: "template-default",
6061
+ ownership: "supplemental",
6062
+ unconsumedMedia: "reserved-slot",
6063
+ variants: TITLE_VARIANTS
5544
6064
  },
5545
6065
  list: {
5546
- label: "List",
5547
- description: "Renders a bulleted or numbered list in a clean, card-style layout."
6066
+ summary: "Uses its optional accent image natively; otherwise supplemental media is paired with the list.",
6067
+ noMedia: "template-without-optional-media",
6068
+ ownership: "optional-native",
6069
+ nativeLayout: "accent-strip-or-inset",
6070
+ additionalMediaLayout: "overlay-inset",
6071
+ unconsumedMedia: "reserve-when-no-native-media",
6072
+ variants: TEXT_VARIANTS
5548
6073
  },
5549
6074
  photoGrid: {
5550
- label: "Photo Grid",
5551
- description: "Arranges multiple photos in a 2\xD72 or 3\xD73 mosaic grid."
6075
+ summary: "Requires a primary image collection and owns the complete tiled media field.",
6076
+ noMedia: "unsupported",
6077
+ ownership: "required-native",
6078
+ nativeLayout: "photo-grid",
6079
+ additionalMediaLayout: "overlay-inset",
6080
+ unconsumedMedia: "retain-native-layout"
5552
6081
  },
5553
6082
  definitionCard: {
5554
- label: "Definition Card",
5555
- description: "Shows a term and its definition in a structured, dictionary-style card."
5556
- },
5557
- comparisonBar: {
5558
- label: "Comparison Bar",
5559
- description: "Visualizes two or more values side-by-side with labeled horizontal bars."
5560
- },
5561
- pullQuote: {
5562
- label: "Pull Quote",
5563
- description: "A stylized pull quote with large decorative marks and centered text."
5564
- },
5565
- videoWithCaption: {
5566
- label: "Video with Caption",
5567
- description: "Embeds a video player with an optional caption below."
5568
- },
5569
- videoPullQuote: {
5570
- label: "Video Pull Quote",
5571
- description: "Combines a video panel with a highlighted pull quote side-by-side."
5572
- },
5573
- dataTable: {
5574
- label: "Data Table",
5575
- description: "Renders tabular data in a clean, styled table with a header row."
5576
- },
5577
- diagram: {
5578
- label: "Diagram",
5579
- description: "Renders sub-headings as connected nodes \u2014 edit visually as a node-and-edge diagram."
6083
+ summary: "Uses its optional accent image natively; otherwise supplemental media is paired with the definition.",
6084
+ noMedia: "template-without-optional-media",
6085
+ ownership: "optional-native",
6086
+ nativeLayout: "accent-strip-or-inset",
6087
+ additionalMediaLayout: "overlay-inset",
6088
+ unconsumedMedia: "reserve-when-no-native-media",
6089
+ variants: TEXT_VARIANTS
5580
6090
  },
5581
- tree: {
5582
- label: "Tree",
5583
- description: "Renders an ASCII file-tree / outline fence as a filesystem-style treeview \u2014 edit as an outline."
5584
- },
5585
- timeline: {
5586
- label: "Timeline",
5587
- description: "Plots events across one or more horizontal tracks with callouts and optional branching links."
5588
- },
5589
- layout: {
5590
- label: "Layout",
5591
- description: "Free-form 2D canvas \u2014 drag layers into place. Use for one-off block layouts that don't fit a template."
5592
- },
5593
- drawing: {
5594
- label: "Drawing",
5595
- description: "Free-form sketches \u2014 draw shapes, paths, and text directly on a 2D surface."
5596
- }
5597
- };
5598
-
5599
- // src/doc/templates/coverBlock.ts
5600
- var COVER_TITLE_BASE_PX = 120;
5601
- var COVER_TITLE_COMFORTABLE_CHARS = 26;
5602
- var COVER_TITLE_MIN_BASE_PX = 62;
5603
- function fitCoverTitleSize(title) {
5604
- const len = title.trim().length;
5605
- if (len <= COVER_TITLE_COMFORTABLE_CHARS) return COVER_TITLE_BASE_PX;
5606
- const scaled = COVER_TITLE_BASE_PX * Math.sqrt(COVER_TITLE_COMFORTABLE_CHARS / len);
5607
- return Math.max(COVER_TITLE_MIN_BASE_PX, Math.round(scaled));
5608
- }
5609
- function isStandardLightCover(context) {
5610
- const { theme } = context;
5611
- return theme.id === "standard" && relativeLuminance(theme.colors.background) > 0.85;
5612
- }
5613
- function coverBlock(input, context) {
5614
- const treatment = themedImageTreatment(context, input.imageTreatment);
5615
- const { heroSrc, heroAlt, title, subtitle, ambientMotion, heroCredit, heroLicense } = input;
5616
- const { theme, layout } = context;
5617
- const standardLightCover = !heroSrc && isStandardLightCover(context);
5618
- const titleFontSize = themedFontSize(fitCoverTitleSize(title), context, true);
5619
- const subtitleFontSize = themedFontSize(40, context, false);
5620
- const layers = [];
5621
- if (heroSrc) {
5622
- const imageAnimation = mapAmbientMotion(ambientMotion);
5623
- layers.push(
5624
- {
5625
- type: "image",
5626
- id: "cover-hero",
5627
- content: {
5628
- src: heroSrc,
5629
- alt: heroAlt || title,
5630
- fit: "cover",
5631
- credit: heroCredit,
5632
- license: heroLicense,
5633
- ...treatment ? { treatment } : {}
5634
- },
5635
- position: { x: 0, y: 0, width: "100%", height: "100%" },
5636
- animation: imageAnimation
5637
- },
5638
- {
5639
- type: "shape",
5640
- id: "cover-gradient",
5641
- content: {
5642
- shape: "rect",
5643
- fill: "linear-gradient(0deg, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.4) 40%, rgba(0,0,0,0.1) 70%, transparent 100%)"
5644
- },
5645
- position: { x: 0, y: 0, width: "100%", height: "100%" }
5646
- }
5647
- );
5648
- } else {
5649
- if (standardLightCover) {
5650
- layers.push(
5651
- {
5652
- type: "shape",
5653
- id: "cover-bg",
5654
- content: {
5655
- shape: "rect",
5656
- fill: theme.colors.background
5657
- },
5658
- position: { x: 0, y: 0, width: "100%", height: "100%" }
5659
- },
5660
- {
5661
- type: "shape",
5662
- id: "cover-bg-tint",
5663
- content: {
5664
- shape: "rect",
5665
- fill: `radial-gradient(ellipse at 50% 40%, ${withAlpha(theme.colors.primary, 0.16)} 0%, ${withAlpha(theme.colors.primary, 0.07)} 38%, ${withAlpha(theme.colors.primary, 0)} 78%)`
5666
- },
5667
- position: { x: 0, y: 0, width: "100%", height: "100%" }
5668
- }
5669
- );
5670
- } else {
5671
- layers.push({
5672
- type: "shape",
5673
- id: "cover-bg",
5674
- content: {
5675
- shape: "rect",
5676
- fill: `radial-gradient(ellipse at 50% 40%, ${theme.colors.primary} 0%, ${theme.colors.background} 100%)`
5677
- },
5678
- position: { x: 0, y: 0, width: "100%", height: "100%" }
5679
- });
5680
- }
5681
- layers.push({
5682
- type: "shape",
5683
- id: "cover-accent",
5684
- content: {
5685
- shape: "rect",
5686
- fill: standardLightCover ? withAlpha(theme.colors.primary, 0.24) : "rgba(255, 255, 255, 0.2)"
5687
- },
5688
- position: {
5689
- x: "35%",
5690
- y: subtitle ? "42%" : "58%",
5691
- width: "30%",
5692
- height: "2px"
5693
- }
5694
- });
5695
- }
5696
- layers.push({
5697
- type: "text",
5698
- id: "cover-title",
5699
- content: {
5700
- text: title,
5701
- style: {
5702
- fontSize: titleFontSize,
5703
- fontFamily: getThemeFont(context, "title"),
5704
- fontWeight: "bold",
5705
- color: theme.colors.text,
5706
- textAlign: "center",
5707
- shadow: !standardLightCover
5708
- }
5709
- },
5710
- position: {
5711
- x: "50%",
5712
- y: heroSrc ? subtitle ? "70%" : "75%" : subtitle ? layout.primaryY : "50%",
5713
- anchor: "center",
5714
- width: layout.maxTextWidth
5715
- },
5716
- // No animation delay - shown immediately at rest
5717
- animation: { type: "fadeIn", duration: 0.8 }
5718
- });
5719
- if (subtitle) {
5720
- layers.push({
5721
- type: "text",
5722
- id: "cover-subtitle",
5723
- content: {
5724
- text: subtitle,
5725
- style: {
5726
- fontSize: subtitleFontSize,
5727
- fontFamily: getThemeFont(context, "body"),
5728
- fontWeight: standardLightCover ? "bold" : "normal",
5729
- color: standardLightCover ? withAlpha(theme.colors.text, 0.78) : theme.colors.textMuted,
5730
- textAlign: "center",
5731
- lineHeight: 1.5
5732
- }
5733
- },
5734
- position: {
5735
- x: "50%",
5736
- y: heroSrc ? "82%" : layout.secondaryY,
5737
- anchor: "center",
5738
- width: layout.maxTextWidth
5739
- },
5740
- animation: { type: "fadeIn", duration: 0.8, delay: 0.2 }
5741
- });
6091
+ comparisonBar: {
6092
+ summary: "Data bars are primary; supplemental media stacks below to preserve horizontal scale.",
6093
+ noMedia: "template-default",
6094
+ ownership: "supplemental",
6095
+ unconsumedMedia: "reserved-slot",
6096
+ variants: DENSE_VARIANTS
6097
+ },
6098
+ pullQuote: {
6099
+ summary: "Requires and owns a full-bleed background image behind the quote lockup.",
6100
+ noMedia: "unsupported",
6101
+ ownership: "required-native",
6102
+ nativeLayout: "full-bleed-image",
6103
+ additionalMediaLayout: "overlay-inset",
6104
+ unconsumedMedia: "retain-native-layout"
6105
+ },
6106
+ videoWithCaption: {
6107
+ summary: "Requires one primary video and owns its full-bleed video/caption geometry.",
6108
+ noMedia: "unsupported",
6109
+ ownership: "required-native",
6110
+ nativeLayout: "full-bleed-video",
6111
+ additionalMediaLayout: "overlay-inset",
6112
+ unconsumedMedia: "retain-native-layout"
6113
+ },
6114
+ videoPullQuote: {
6115
+ summary: "Requires and owns a full-bleed video behind the quote lockup.",
6116
+ noMedia: "unsupported",
6117
+ ownership: "required-native",
6118
+ nativeLayout: "full-bleed-video",
6119
+ additionalMediaLayout: "overlay-inset",
6120
+ unconsumedMedia: "retain-native-layout"
6121
+ },
6122
+ dataTable: {
6123
+ summary: "The table remains wide; supplemental media stacks below the dense data region.",
6124
+ noMedia: "template-default",
6125
+ ownership: "supplemental",
6126
+ unconsumedMedia: "reserved-slot",
6127
+ variants: DENSE_VARIANTS
6128
+ },
6129
+ diagram: {
6130
+ summary: "The node-and-edge canvas is intrinsic media and retains its spatial coordinate system.",
6131
+ noMedia: "intrinsic-visual",
6132
+ ownership: "intrinsic-visual",
6133
+ nativeLayout: "spatial-canvas",
6134
+ additionalMediaLayout: "overlay-inset",
6135
+ unconsumedMedia: "retain-native-layout"
6136
+ },
6137
+ tree: {
6138
+ summary: "The interactive tree canvas is intrinsic media and retains its full layout region.",
6139
+ noMedia: "intrinsic-visual",
6140
+ ownership: "intrinsic-visual",
6141
+ nativeLayout: "spatial-canvas",
6142
+ additionalMediaLayout: "overlay-inset",
6143
+ unconsumedMedia: "retain-native-layout"
6144
+ },
6145
+ timeline: {
6146
+ summary: "The track canvas is intrinsic media and retains its shared horizontal time scale.",
6147
+ noMedia: "intrinsic-visual",
6148
+ ownership: "intrinsic-visual",
6149
+ nativeLayout: "spatial-canvas",
6150
+ additionalMediaLayout: "overlay-inset",
6151
+ unconsumedMedia: "retain-native-layout"
6152
+ },
6153
+ layout: {
6154
+ summary: "The authored layer canvas is intrinsic media and must preserve absolute placement.",
6155
+ noMedia: "intrinsic-visual",
6156
+ ownership: "intrinsic-visual",
6157
+ nativeLayout: "freeform-canvas",
6158
+ additionalMediaLayout: "overlay-inset",
6159
+ unconsumedMedia: "retain-native-layout"
6160
+ },
6161
+ drawing: {
6162
+ summary: "The drawing canvas is intrinsic media and must preserve authored geometry.",
6163
+ noMedia: "intrinsic-visual",
6164
+ ownership: "intrinsic-visual",
6165
+ nativeLayout: "freeform-canvas",
6166
+ additionalMediaLayout: "overlay-inset",
6167
+ unconsumedMedia: "retain-native-layout"
5742
6168
  }
5743
- return layers;
6169
+ };
6170
+ function getBlockMediaLayoutPolicy(template) {
6171
+ if (!template) return void 0;
6172
+ return BLOCK_MEDIA_LAYOUT_POLICIES[template];
5744
6173
  }
5745
- function startBlockToCoverInput(config) {
6174
+
6175
+ // src/doc/richMediaLayout.ts
6176
+ function rect(viewport, x, y, width, height) {
5746
6177
  return {
5747
- heroSrc: config.heroSrc,
5748
- heroAlt: config.heroAlt,
5749
- title: config.title,
5750
- subtitle: config.subtitle,
5751
- ambientMotion: config.ambientMotion,
5752
- heroCredit: config.heroCredit,
5753
- heroLicense: config.heroLicense
6178
+ x: viewport.width * x,
6179
+ y: viewport.height * y,
6180
+ width: viewport.width * width,
6181
+ height: viewport.height * height
5754
6182
  };
5755
6183
  }
5756
- function expandCoverBlock(config, context) {
5757
- const input = startBlockToCoverInput(config);
5758
- return coverBlock(input, context);
6184
+ function mediaShape(mediaCount, aspectRatios) {
6185
+ if (mediaCount > 1) return "multiple";
6186
+ const aspect = aspectRatios[0] ?? 16 / 9;
6187
+ if (aspect < 0.8) return "tall";
6188
+ if (aspect > 1.25) return "wide";
6189
+ return "square";
5759
6190
  }
5760
-
5761
- // src/doc/templates/index.ts
5762
- function materializeScheduledBlock(sourceBlock, blockIndex, totalBlocks, theme, viewport, persistentLayers, expandedPersistentLayers, registry, failureMode, onDiagnostic) {
5763
- const materialized = materializeBlockLayersWithRuntime(
5764
- sourceBlock,
5765
- {
5766
- theme,
5767
- viewport,
5768
- persistentLayers: persistentLayers ?? false,
5769
- blockIndex,
5770
- totalBlocks,
5771
- failureMode
5772
- },
5773
- { registry, expandedPersistentLayers }
5774
- );
5775
- if (materialized.diagnostic) {
5776
- onDiagnostic?.(materialized.diagnostic, sourceBlock, blockIndex);
6191
+ function stackedTitleLayout(viewport, orientation) {
6192
+ if (orientation === "portrait") {
6193
+ return {
6194
+ contentRect: rect(viewport, 0.06, 0.04, 0.88, 0.3),
6195
+ mediaRect: rect(viewport, 0.06, 0.39, 0.88, 0.55),
6196
+ fontScale: 0.76
6197
+ };
5777
6198
  }
5778
- const expandedBlock = isTemplateBlock(sourceBlock) ? {
5779
- id: sourceBlock.id,
5780
- startTime: 0,
5781
- duration: sourceBlock.duration,
5782
- audioSegment: sourceBlock.audioSegment,
5783
- ...materialized.layers.length > 0 ? { layers: materialized.layers } : {},
5784
- transition: sourceBlock.transition,
5785
- template: sourceBlock.template
5786
- } : {
5787
- ...cloneData(sourceBlock),
5788
- ...materialized.layers.length > 0 ? { layers: materialized.layers } : {}
6199
+ if (orientation === "square") {
6200
+ return {
6201
+ contentRect: rect(viewport, 0.05, 0.04, 0.9, 0.33),
6202
+ mediaRect: rect(viewport, 0.07, 0.42, 0.86, 0.51),
6203
+ fontScale: 0.72
6204
+ };
6205
+ }
6206
+ return {
6207
+ contentRect: rect(viewport, 0.04, 0.03, 0.92, 0.32),
6208
+ mediaRect: rect(viewport, 0.08, 0.4, 0.84, 0.52),
6209
+ fontScale: 0.72
5789
6210
  };
5790
- const transition = resolveBlockTransition(expandedBlock, theme, blockIndex);
5791
- if (transition !== expandedBlock.transition) {
5792
- expandedBlock.transition = transition;
6211
+ }
6212
+ function splitLayout(viewport, orientation) {
6213
+ const square = orientation === "square";
6214
+ return {
6215
+ contentRect: rect(viewport, square ? 0.05 : 0.04, 0.05, square ? 0.42 : 0.43, 0.9),
6216
+ mediaRect: rect(viewport, square ? 0.52 : 0.53, 0.07, square ? 0.43 : 0.43, 0.86),
6217
+ fontScale: 0.72
6218
+ };
6219
+ }
6220
+ function stackedTextLayout(viewport, orientation, dense) {
6221
+ if (orientation === "portrait") {
6222
+ return {
6223
+ contentRect: rect(viewport, 0.06, 0.04, 0.88, dense ? 0.41 : 0.36),
6224
+ mediaRect: rect(viewport, 0.06, dense ? 0.5 : 0.45, 0.88, dense ? 0.44 : 0.49),
6225
+ fontScale: dense ? 0.42 : 0.44
6226
+ };
5793
6227
  }
5794
- return expandedBlock;
6228
+ if (orientation === "square") {
6229
+ return {
6230
+ contentRect: rect(viewport, 0.05, 0.04, 0.9, dense ? 0.43 : 0.36),
6231
+ mediaRect: rect(viewport, 0.07, dense ? 0.52 : 0.45, 0.86, dense ? 0.41 : 0.48),
6232
+ fontScale: dense ? 0.45 : 0.46
6233
+ };
6234
+ }
6235
+ return {
6236
+ contentRect: rect(viewport, 0.05, 0.03, 0.9, dense ? 0.46 : 0.35),
6237
+ mediaRect: rect(viewport, 0.07, dense ? 0.55 : 0.43, 0.86, dense ? 0.38 : 0.49),
6238
+ fontScale: dense ? 0.5 : 0.48
6239
+ };
5795
6240
  }
5796
- function expandDocBlocks(blocks, options = {}) {
5797
- const opts = options;
5798
- const theme = opts.theme ?? DEFAULT_THEME;
5799
- const viewport = opts.viewport ?? VIEWPORT_PRESETS.landscape;
5800
- const {
5801
- persistentLayers,
5802
- audioSegments,
5803
- customTemplates,
5804
- failureMode = "fallback",
5805
- onDiagnostic
5806
- } = opts;
5807
- const totalBlocks = blocks.length;
5808
- const registry = customTemplates && customTemplates.length > 0 ? buildRegistry(customTemplates) : templateRegistry;
5809
- const effectivePersistentLayers = persistentLayers === false ? void 0 : persistentLayers ?? theme.persistentLayers;
5810
- const expandedPersistentLayers = effectivePersistentLayers ? {
5811
- bottomLayers: expandPersistentLayers(effectivePersistentLayers.bottomLayers, theme),
5812
- topLayers: expandPersistentLayers(effectivePersistentLayers.topLayers, theme)
5813
- } : void 0;
5814
- if (!audioSegments || audioSegments.length === 0) {
5815
- let currentTime = 0;
5816
- return blocks.map((block, index) => {
5817
- const expandedBlock = materializeScheduledBlock(
5818
- block,
5819
- index,
5820
- totalBlocks,
5821
- theme,
5822
- viewport,
5823
- effectivePersistentLayers,
5824
- expandedPersistentLayers,
5825
- registry,
5826
- failureMode,
5827
- onDiagnostic
5828
- );
5829
- expandedBlock.startTime = currentTime;
5830
- currentTime += expandedBlock.duration;
5831
- return expandedBlock;
5832
- });
6241
+ function overlayMediaRect(viewport, shape) {
6242
+ const orientation = getViewportOrientation(viewport);
6243
+ if (orientation === "portrait") {
6244
+ switch (shape) {
6245
+ case "tall":
6246
+ return rect(viewport, 0.55, 0.5, 0.38, 0.44);
6247
+ case "square":
6248
+ return rect(viewport, 0.49, 0.58, 0.44, 0.35);
6249
+ case "multiple":
6250
+ return rect(viewport, 0.07, 0.67, 0.86, 0.27);
6251
+ case "wide":
6252
+ return rect(viewport, 0.07, 0.71, 0.86, 0.23);
6253
+ }
6254
+ }
6255
+ if (orientation === "square") {
6256
+ switch (shape) {
6257
+ case "tall":
6258
+ return rect(viewport, 0.68, 0.1, 0.27, 0.8);
6259
+ case "square":
6260
+ return rect(viewport, 0.55, 0.52, 0.4, 0.4);
6261
+ case "multiple":
6262
+ return rect(viewport, 0.08, 0.62, 0.84, 0.31);
6263
+ case "wide":
6264
+ return rect(viewport, 0.08, 0.68, 0.84, 0.27);
6265
+ }
6266
+ }
6267
+ switch (shape) {
6268
+ case "tall":
6269
+ return rect(viewport, 0.7, 0.1, 0.26, 0.8);
6270
+ case "square":
6271
+ return rect(viewport, 0.62, 0.22, 0.34, 0.56);
6272
+ case "multiple":
6273
+ return rect(viewport, 0.52, 0.5, 0.44, 0.42);
6274
+ case "wide":
6275
+ return rect(viewport, 0.54, 0.58, 0.42, 0.34);
6276
+ }
6277
+ }
6278
+ function designedLayout(variant, viewport) {
6279
+ const orientation = getViewportOrientation(viewport);
6280
+ switch (variant) {
6281
+ case "title-stack":
6282
+ return stackedTitleLayout(viewport, orientation);
6283
+ case "split":
6284
+ return splitLayout(viewport, orientation);
6285
+ case "text-stack":
6286
+ return stackedTextLayout(viewport, orientation, false);
6287
+ case "dense-stack":
6288
+ return stackedTextLayout(viewport, orientation, true);
5833
6289
  }
5834
- const blocksBySegment = /* @__PURE__ */ new Map();
5835
- blocks.forEach((block, index) => {
5836
- const segmentIndex = block.audioSegment ?? 0;
5837
- if (!blocksBySegment.has(segmentIndex)) {
5838
- blocksBySegment.set(segmentIndex, []);
5839
- }
5840
- blocksBySegment.get(segmentIndex).push({ block, originalIndex: index });
5841
- });
5842
- const expandedBlocks = new Array(blocks.length);
5843
- for (const [segmentIndex, segmentBlocks] of blocksBySegment) {
5844
- const audioSegment = audioSegments[segmentIndex];
5845
- if (!audioSegment) {
5846
- let offsetTime = 0;
5847
- for (const { block, originalIndex } of segmentBlocks) {
5848
- const expandedBlock = materializeScheduledBlock(
5849
- block,
5850
- originalIndex,
5851
- totalBlocks,
5852
- theme,
5853
- viewport,
5854
- effectivePersistentLayers,
5855
- expandedPersistentLayers,
5856
- registry,
5857
- failureMode,
5858
- onDiagnostic
5859
- );
5860
- expandedBlock.startTime = offsetTime;
5861
- offsetTime += expandedBlock.duration;
5862
- expandedBlocks[originalIndex] = expandedBlock;
5863
- }
5864
- continue;
5865
- }
5866
- const sectionHeaderBlocks = [];
5867
- const contentBlocks = [];
5868
- for (const item of segmentBlocks) {
5869
- const templateBlock = item.block;
5870
- if (templateBlock.template === "sectionHeader") {
5871
- sectionHeaderBlocks.push(item);
5872
- } else {
5873
- contentBlocks.push(item);
5874
- }
5875
- }
5876
- const fixedDuration = sectionHeaderBlocks.reduce((sum, { block }) => {
5877
- return sum + (block.duration ?? 0);
5878
- }, 0);
5879
- const contentBlockDuration = contentBlocks.reduce((sum, { block }) => {
5880
- return sum + (block.duration ?? 0);
5881
- }, 0);
5882
- const remainingDuration = audioSegment.duration - fixedDuration;
5883
- const scaleFactor = contentBlockDuration > 0 && remainingDuration > 0 ? remainingDuration / contentBlockDuration : 1;
5884
- const expandedInfos = [];
5885
- for (const { block, originalIndex } of segmentBlocks) {
5886
- const expandedBlock = materializeScheduledBlock(
5887
- block,
5888
- originalIndex,
5889
- totalBlocks,
5890
- theme,
5891
- viewport,
5892
- effectivePersistentLayers,
5893
- expandedPersistentLayers,
5894
- registry,
5895
- failureMode,
5896
- onDiagnostic
5897
- );
5898
- const templateBlock = block;
5899
- expandedInfos.push({
5900
- block: expandedBlock,
5901
- originalIndex,
5902
- templateBlock,
5903
- hasSourceTiming: typeof templateBlock.sourceStartTime === "number"
5904
- });
5905
- }
5906
- const hasAnySourceTiming = expandedInfos.some((info) => info.hasSourceTiming);
5907
- expandedInfos.sort((a, b) => {
5908
- if (hasAnySourceTiming) {
5909
- const aIsHeader = a.templateBlock.template === "sectionHeader";
5910
- const bIsHeader = b.templateBlock.template === "sectionHeader";
5911
- if (aIsHeader && !bIsHeader) return -1;
5912
- if (!aIsHeader && bIsHeader) return 1;
5913
- if (a.hasSourceTiming && b.hasSourceTiming) {
5914
- return a.templateBlock.sourceStartTime - b.templateBlock.sourceStartTime;
5915
- }
5916
- if (a.hasSourceTiming && !b.hasSourceTiming) return -1;
5917
- if (!a.hasSourceTiming && b.hasSourceTiming) return 1;
5918
- }
5919
- return a.originalIndex - b.originalIndex;
5920
- });
5921
- let offsetWithinSegment = 0;
5922
- for (let i = 0; i < expandedInfos.length; i++) {
5923
- const info = expandedInfos[i];
5924
- const { block: expandedBlock, templateBlock } = info;
5925
- if (info.hasSourceTiming) {
5926
- const sourceStart = templateBlock.sourceStartTime;
5927
- expandedBlock.startTime = audioSegment.startTime + sourceStart;
5928
- if (typeof templateBlock.sourceDuration === "number") {
5929
- expandedBlock.duration = Math.max(5, templateBlock.sourceDuration);
5930
- }
5931
- offsetWithinSegment = sourceStart + expandedBlock.duration;
5932
- } else if (templateBlock.template === "sectionHeader") {
5933
- expandedBlock.startTime = audioSegment.startTime + offsetWithinSegment;
5934
- offsetWithinSegment += expandedBlock.duration;
5935
- } else {
5936
- expandedBlock.startTime = audioSegment.startTime + offsetWithinSegment;
5937
- const scaledDuration = expandedBlock.duration * scaleFactor;
5938
- expandedBlock.duration = scaledDuration;
5939
- offsetWithinSegment += scaledDuration;
5940
- }
5941
- expandedBlocks[info.originalIndex] = expandedBlock;
5942
- }
5943
- const segmentExpandedBlocks = expandedInfos.map((info) => expandedBlocks[info.originalIndex]).sort((a, b) => a.startTime - b.startTime);
5944
- for (let i = 0; i < segmentExpandedBlocks.length - 1; i++) {
5945
- const current = segmentExpandedBlocks[i];
5946
- const next = segmentExpandedBlocks[i + 1];
5947
- const currentEnd = current.startTime + current.duration;
5948
- if (currentEnd > next.startTime) {
5949
- current.duration = next.startTime - current.startTime;
5950
- } else if (currentEnd < next.startTime - 0.5) {
5951
- current.duration = Math.max(0.1, next.startTime - current.startTime);
5952
- }
5953
- }
5954
- const MIN_TRANSITION_GAP = 5;
5955
- const segmentEnd = audioSegment.startTime + audioSegment.duration;
5956
- while (segmentExpandedBlocks.length > 1) {
5957
- const lastBlock = segmentExpandedBlocks[segmentExpandedBlocks.length - 1];
5958
- const timeFromLastToEnd = segmentEnd - lastBlock.startTime;
5959
- if (timeFromLastToEnd < MIN_TRANSITION_GAP && lastBlock.template !== "sectionHeader") {
5960
- const prevBlock = segmentExpandedBlocks[segmentExpandedBlocks.length - 2];
5961
- prevBlock.duration = segmentEnd - prevBlock.startTime;
5962
- lastBlock.duration = 0;
5963
- lastBlock.startTime = segmentEnd;
5964
- segmentExpandedBlocks.pop();
5965
- } else {
5966
- if (lastBlock.startTime + lastBlock.duration < segmentEnd) {
5967
- lastBlock.duration = segmentEnd - lastBlock.startTime;
6290
+ }
6291
+ function resolvePositionValue(value, dimension) {
6292
+ if (value === void 0) return 0;
6293
+ if (typeof value === "number") return value;
6294
+ const percent = value.match(/^\s*(-?\d+(?:\.\d+)?)%\s*$/);
6295
+ if (percent) return Number(percent[1]) / 100 * dimension;
6296
+ const numeric = Number.parseFloat(value);
6297
+ return Number.isFinite(numeric) ? numeric : 0;
6298
+ }
6299
+ function isZero(value) {
6300
+ return value === 0 || value === "0" || value === "0%";
6301
+ }
6302
+ function isFullViewportBackground(layer, viewport) {
6303
+ if (layer.type !== "shape" && layer.type !== "image") return false;
6304
+ const { position } = layer;
6305
+ const fullWidth = position.width === "100%" || position.width === viewport.width;
6306
+ const fullHeight = position.height === "100%" || position.height === viewport.height;
6307
+ return isZero(position.x) && isZero(position.y) && fullWidth && fullHeight;
6308
+ }
6309
+ function mapPosition(position, contentRect, viewport) {
6310
+ const mapped = {
6311
+ ...position,
6312
+ x: contentRect.x + resolvePositionValue(position.x, viewport.width) / viewport.width * contentRect.width,
6313
+ y: contentRect.y + resolvePositionValue(position.y, viewport.height) / viewport.height * contentRect.height
6314
+ };
6315
+ if (position.width !== void 0) {
6316
+ mapped.width = resolvePositionValue(position.width, viewport.width) / viewport.width * contentRect.width;
6317
+ }
6318
+ if (position.height !== void 0) {
6319
+ mapped.height = resolvePositionValue(position.height, viewport.height) / viewport.height * contentRect.height;
6320
+ }
6321
+ return mapped;
6322
+ }
6323
+ function mapForegroundLayers(layers, contentRect, viewport, fontScale, template) {
6324
+ const mapped = layers.map((layer) => {
6325
+ if (isFullViewportBackground(layer, viewport)) return layer;
6326
+ const position = mapPosition(layer.position, contentRect, viewport);
6327
+ if (layer.type === "text") {
6328
+ return {
6329
+ ...layer,
6330
+ position,
6331
+ content: {
6332
+ ...layer.content,
6333
+ style: {
6334
+ ...layer.content.style,
6335
+ fontSize: Math.max(12, Math.round(layer.content.style.fontSize * fontScale)),
6336
+ ...layer.content.style.padding !== void 0 ? { padding: Math.round(layer.content.style.padding * fontScale) } : {}
6337
+ }
5968
6338
  }
5969
- break;
5970
- }
6339
+ };
5971
6340
  }
5972
- if (segmentExpandedBlocks.length === 1) {
5973
- const onlyBlock = segmentExpandedBlocks[0];
5974
- if (onlyBlock.startTime + onlyBlock.duration < segmentEnd) {
5975
- onlyBlock.duration = segmentEnd - onlyBlock.startTime;
6341
+ return { ...layer, position };
6342
+ });
6343
+ return arrangeTitleFamily(mapped, template, contentRect, viewport);
6344
+ }
6345
+ function arrangeTitleFamily(layers, template, contentRect, viewport) {
6346
+ const centerX = contentRect.x + contentRect.width / 2;
6347
+ const centerY = contentRect.y + contentRect.height / 2;
6348
+ if (template === "sectionHeader") {
6349
+ return layers.map((layer) => {
6350
+ if (layer.id === "title") {
6351
+ return {
6352
+ ...layer,
6353
+ position: {
6354
+ ...layer.position,
6355
+ x: centerX,
6356
+ y: centerY,
6357
+ width: contentRect.width * 0.9,
6358
+ anchor: "center"
6359
+ }
6360
+ };
5976
6361
  }
6362
+ if (layer.id === "line-top" || layer.id === "line-bottom") {
6363
+ const direction = layer.id === "line-top" ? -1 : 1;
6364
+ return {
6365
+ ...layer,
6366
+ position: {
6367
+ ...layer.position,
6368
+ x: centerX,
6369
+ y: centerY + direction * contentRect.height * 0.28,
6370
+ width: Math.min(contentRect.width * 0.22, viewport.width * 0.2),
6371
+ height: 2,
6372
+ anchor: "center"
6373
+ }
6374
+ };
6375
+ }
6376
+ return layer;
6377
+ });
6378
+ }
6379
+ if (template !== "title") return layers;
6380
+ const title = layers.find((layer) => layer.type === "text" && layer.id === "title");
6381
+ const subtitle = layers.find((layer) => layer.type === "text" && layer.id === "subtitle");
6382
+ if (!title || title.type !== "text") return layers;
6383
+ const subtitleText = subtitle?.type === "text" ? subtitle : void 0;
6384
+ const maxWidth = contentRect.width * 0.9;
6385
+ const decorationGap = Math.max(12, Math.min(viewport.width, viewport.height) * 0.018);
6386
+ const textGap = subtitleText ? decorationGap : 0;
6387
+ const availableHeight = contentRect.height * 0.78;
6388
+ let titleFontSize = title.content.style.fontSize;
6389
+ let subtitleFontSize = subtitleText?.content.style.fontSize ?? 0;
6390
+ const measure = () => {
6391
+ const titleHeight = estimateTextHeight(
6392
+ title.content.text,
6393
+ titleFontSize,
6394
+ maxWidth,
6395
+ title.content.style.lineHeight ?? 1.15
6396
+ );
6397
+ const subtitleHeight = subtitleText ? estimateTextHeight(
6398
+ subtitleText.content.text,
6399
+ subtitleFontSize,
6400
+ maxWidth,
6401
+ subtitleText.content.style.lineHeight ?? 1.5
6402
+ ) : 0;
6403
+ return { titleHeight, subtitleHeight, total: titleHeight + textGap + subtitleHeight };
6404
+ };
6405
+ let measured = measure();
6406
+ if (measured.total > availableHeight) {
6407
+ const fit = Math.max(0.5, availableHeight / measured.total);
6408
+ titleFontSize = Math.max(18, Math.round(titleFontSize * fit));
6409
+ subtitleFontSize = Math.max(14, Math.round(subtitleFontSize * fit));
6410
+ measured = measure();
6411
+ }
6412
+ const top = contentRect.y + (contentRect.height - measured.total) / 2;
6413
+ const titleY = top + measured.titleHeight / 2;
6414
+ const subtitleY = top + measured.titleHeight + textGap + measured.subtitleHeight / 2;
6415
+ return layers.map((layer) => {
6416
+ if (layer.id === "title" && layer.type === "text") {
6417
+ return {
6418
+ ...layer,
6419
+ position: { ...layer.position, x: centerX, y: titleY, width: maxWidth, anchor: "center" },
6420
+ content: {
6421
+ ...layer.content,
6422
+ style: { ...layer.content.style, fontSize: titleFontSize }
6423
+ }
6424
+ };
5977
6425
  }
5978
- let changed = true;
5979
- while (changed) {
5980
- changed = false;
5981
- for (let i = segmentExpandedBlocks.length - 1; i >= 1; i--) {
5982
- const block = segmentExpandedBlocks[i];
5983
- if (block.duration > 0 && block.duration < MIN_TRANSITION_GAP) {
5984
- const prev = segmentExpandedBlocks[i - 1];
5985
- const blockEnd = block.startTime + block.duration;
5986
- prev.duration = blockEnd - prev.startTime;
5987
- block.duration = 0;
5988
- block.startTime = segmentEnd;
5989
- segmentExpandedBlocks.splice(i, 1);
5990
- changed = true;
5991
- break;
6426
+ if (layer.id === "subtitle" && layer.type === "text") {
6427
+ return {
6428
+ ...layer,
6429
+ position: {
6430
+ ...layer.position,
6431
+ x: centerX,
6432
+ y: subtitleY,
6433
+ width: maxWidth,
6434
+ anchor: "center"
6435
+ },
6436
+ content: {
6437
+ ...layer.content,
6438
+ style: { ...layer.content.style, fontSize: subtitleFontSize }
5992
6439
  }
5993
- }
6440
+ };
5994
6441
  }
5995
- const MAX_BLOCK_DURATION = 20;
5996
- for (let i = 0; i < segmentExpandedBlocks.length; i++) {
5997
- const block = segmentExpandedBlocks[i];
5998
- if (block.duration > MAX_BLOCK_DURATION) {
5999
- const numParts = Math.ceil(block.duration / MAX_BLOCK_DURATION);
6000
- const partDuration = block.duration / numParts;
6001
- if (partDuration >= MIN_TRANSITION_GAP) {
6002
- block.duration = partDuration;
6003
- for (let p = 1; p < numParts; p++) {
6004
- const splitBlock = {
6005
- id: `${block.id}-split-${p}`,
6006
- startTime: block.startTime + p * partDuration,
6007
- duration: partDuration,
6008
- audioSegment: block.audioSegment,
6009
- layers: (block.layers ?? []).map((layer) => ({
6010
- ...cloneData(layer),
6011
- id: `${layer.id}-split-${p}`
6012
- })),
6013
- transition: { type: "dissolve", duration: 1 },
6014
- template: block.template
6015
- };
6016
- expandedBlocks.push(splitBlock);
6017
- }
6442
+ if (layer.id === "accent-line") {
6443
+ return {
6444
+ ...layer,
6445
+ position: {
6446
+ ...layer.position,
6447
+ x: centerX,
6448
+ y: Math.max(contentRect.y + 2, top - decorationGap * 0.75),
6449
+ width: Math.min(contentRect.width * 0.18, viewport.width * 0.12),
6450
+ height: 3,
6451
+ anchor: "center"
6018
6452
  }
6019
- }
6453
+ };
6020
6454
  }
6021
- }
6022
- return expandedBlocks.filter((block) => block && block.duration > 0);
6455
+ return layer;
6456
+ });
6023
6457
  }
6024
- function getAvailableTemplates() {
6025
- return Object.keys(templateRegistry);
6458
+ function positionRect(position, viewport, fallbackWidth, fallbackHeight) {
6459
+ const width = position.width ? resolvePositionValue(position.width, viewport.width) : fallbackWidth;
6460
+ const height = position.height ? resolvePositionValue(position.height, viewport.height) : fallbackHeight;
6461
+ let x = resolvePositionValue(position.x, viewport.width);
6462
+ let y = resolvePositionValue(position.y, viewport.height);
6463
+ const anchor = position.anchor ?? "top-left";
6464
+ if (anchor === "center") {
6465
+ x -= width / 2;
6466
+ y -= height / 2;
6467
+ } else {
6468
+ if (anchor.endsWith("right")) x -= width;
6469
+ if (anchor.startsWith("bottom")) y -= height;
6470
+ }
6471
+ return { x, y, width, height };
6026
6472
  }
6027
- function hasTemplate(name) {
6028
- return resolveTemplateName(name) in templateRegistry;
6473
+ function overlapArea(a, b) {
6474
+ const width = Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x));
6475
+ const height = Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));
6476
+ return width * height;
6029
6477
  }
6030
-
6031
- // src/doc/asciiDiagram/types.ts
6032
- var ASCII_CHAR_W = 14;
6033
- var ASCII_CHAR_H = 28;
6034
-
6035
- // src/doc/asciiDiagram/mapping.ts
6036
- function asciiCellToCanvas(col, row) {
6037
- return { x: col * ASCII_CHAR_W, y: row * ASCII_CHAR_H };
6478
+ function leastObstructedMediaRect(layers, viewport) {
6479
+ const occupied = layers.filter((layer) => layer.type !== "shape" && layer.type !== "path" && layer.type !== "mermaid").map(
6480
+ (layer) => positionRect(
6481
+ layer.position,
6482
+ viewport,
6483
+ layer.type === "text" ? viewport.width * 0.45 : viewport.width * 0.5,
6484
+ layer.type === "text" ? viewport.height * 0.2 : viewport.height * 0.55
6485
+ )
6486
+ );
6487
+ if (occupied.length === 0) return rect(viewport, 0.06, 0.08, 0.88, 0.84);
6488
+ const candidates = [
6489
+ rect(viewport, 0.52, 0.12, 0.44, 0.76),
6490
+ rect(viewport, 0.04, 0.12, 0.44, 0.76),
6491
+ rect(viewport, 0.15, 0.53, 0.7, 0.41)
6492
+ ];
6493
+ return candidates.reduce((best, candidate) => {
6494
+ const score = occupied.reduce((sum, item) => sum + overlapArea(candidate, item), 0);
6495
+ const bestScore = occupied.reduce((sum, item) => sum + overlapArea(best, item), 0);
6496
+ return score < bestScore ? candidate : best;
6497
+ });
6038
6498
  }
6039
- function canvasToAsciiCell(x, y) {
6499
+ function resolveSupplementalMediaLayout(layers, template, viewport, mediaCount, aspectRatios) {
6500
+ const policy = getBlockMediaLayoutPolicy(template);
6501
+ const shape = mediaShape(mediaCount, aspectRatios);
6502
+ const alreadyOwnsMedia = layers.some(
6503
+ (layer) => layer.type === "image" || layer.type === "video" || layer.type === "map" || layer.type === "mermaid"
6504
+ );
6505
+ const retainNativeLayout = policy?.unconsumedMedia === "retain-native-layout" || policy?.unconsumedMedia === "reserve-when-no-native-media" && alreadyOwnsMedia;
6506
+ if (policy?.additionalMediaLayout === "overlay-inset" && retainNativeLayout) {
6507
+ return {
6508
+ layers,
6509
+ mediaRect: overlayMediaRect(viewport, shape),
6510
+ framed: true
6511
+ };
6512
+ }
6513
+ const canReserve = policy?.unconsumedMedia === "reserved-slot" || policy?.unconsumedMedia === "reserve-when-no-native-media" && !alreadyOwnsMedia;
6514
+ if (!policy || !canReserve || !policy.variants) {
6515
+ return {
6516
+ layers,
6517
+ mediaRect: leastObstructedMediaRect(layers, viewport),
6518
+ framed: false
6519
+ };
6520
+ }
6521
+ const orientation = getViewportOrientation(viewport);
6522
+ const layout = designedLayout(policy.variants[orientation][shape], viewport);
6040
6523
  return {
6041
- col: Math.max(0, Math.round(x / ASCII_CHAR_W)),
6042
- row: Math.max(0, Math.round(y / ASCII_CHAR_H))
6524
+ layers: mapForegroundLayers(
6525
+ layers,
6526
+ layout.contentRect,
6527
+ viewport,
6528
+ layout.fontScale,
6529
+ template ?? ""
6530
+ ),
6531
+ mediaRect: layout.mediaRect,
6532
+ framed: true
6043
6533
  };
6044
6534
  }
6045
- function asciiDiagramToTemplateData(diagram) {
6046
- const nodes = diagram.nodes.map((n) => ({
6047
- id: n.id,
6048
- label: n.label,
6049
- ...asciiCellToCanvas(n.col, n.row),
6050
- w: n.wCols * ASCII_CHAR_W,
6051
- h: n.hRows * ASCII_CHAR_H,
6052
- ...n.containerId ? { container: n.containerId } : {}
6053
- }));
6054
- const anchors = inferEdgeAnchors(nodes, diagram.edges);
6055
- const edges = diagram.edges.map((e, index) => ({
6056
- source: e.source,
6057
- target: e.target,
6058
- ...e.label ? { label: e.label } : {},
6059
- ...e.directed ? {} : { directed: false },
6060
- ...anchors[index],
6061
- routing: "orthogonal"
6062
- }));
6063
- return { nodes, edges };
6535
+
6536
+ // src/doc/materializeBlockLayers.ts
6537
+ function materializeBlockLayers(block, options = {}) {
6538
+ const registry = options.customTemplates && options.customTemplates.length > 0 ? buildRegistry(options.customTemplates) : templateRegistry;
6539
+ return materializeBlockLayersWithRuntime(block, options, { registry });
6064
6540
  }
6065
- function inferEdgeAnchors(nodes, edges) {
6066
- const nodeById = new Map(nodes.map((node) => [node.id, node]));
6067
- const pairs = edges.map((edge) => {
6068
- const source = nodeById.get(edge.source);
6069
- const target = nodeById.get(edge.target);
6070
- if (!source || !target) {
6071
- return {
6072
- sourceAnchor: { side: "right", offset: 0.5 },
6073
- targetAnchor: { side: "left", offset: 0.5 }
6074
- };
6075
- }
6076
- const sourceW = source.w ?? ASCII_CHAR_W * 12;
6077
- const sourceH = source.h ?? ASCII_CHAR_H * 3;
6078
- const targetW = target.w ?? ASCII_CHAR_W * 12;
6079
- const targetH = target.h ?? ASCII_CHAR_H * 3;
6080
- const dx = target.x + targetW / 2 - (source.x + sourceW / 2);
6081
- const dy = target.y + targetH / 2 - (source.y + sourceH / 2);
6082
- const horizontalDistance = Math.abs(dx) / Math.max(1, (sourceW + targetW) / 2);
6083
- const verticalDistance = Math.abs(dy) / Math.max(1, (sourceH + targetH) / 2);
6084
- if (verticalDistance >= horizontalDistance) {
6085
- return dy >= 0 ? {
6086
- sourceAnchor: { side: "bottom", offset: 0.5 },
6087
- targetAnchor: { side: "top", offset: 0.5 }
6088
- } : {
6089
- sourceAnchor: { side: "top", offset: 0.5 },
6090
- targetAnchor: { side: "bottom", offset: 0.5 }
6091
- };
6092
- }
6093
- return dx >= 0 ? {
6094
- sourceAnchor: { side: "right", offset: 0.5 },
6095
- targetAnchor: { side: "left", offset: 0.5 }
6096
- } : {
6097
- sourceAnchor: { side: "left", offset: 0.5 },
6098
- targetAnchor: { side: "right", offset: 0.5 }
6541
+ function materializeBlockLayersWithRuntime(block, options, runtime) {
6542
+ const theme = options.theme ?? DEFAULT_THEME;
6543
+ const viewport = options.viewport ?? VIEWPORT_PRESETS.landscape;
6544
+ const blockIndex = options.blockIndex ?? 0;
6545
+ const totalBlocks = options.totalBlocks ?? 1;
6546
+ const effectivePersistentLayers = options.persistentLayers === false ? void 0 : options.persistentLayers ?? theme.persistentLayers;
6547
+ const existingLayers = block.layers;
6548
+ if (existingLayers && existingLayers.length > 0 && !isTemplateBlock(block)) {
6549
+ const layers = appendRichContentLayers(
6550
+ cloneData(existingLayers),
6551
+ block,
6552
+ theme,
6553
+ viewport
6554
+ );
6555
+ return {
6556
+ layers: injectPersistentLayers(
6557
+ layers,
6558
+ block,
6559
+ theme,
6560
+ effectivePersistentLayers,
6561
+ runtime.expandedPersistentLayers
6562
+ ),
6563
+ source: "authored"
6564
+ };
6565
+ }
6566
+ if (!isTemplateBlock(block)) {
6567
+ const layers = appendRichContentLayers([], block, theme, viewport);
6568
+ return {
6569
+ layers: injectPersistentLayers(
6570
+ layers,
6571
+ block,
6572
+ theme,
6573
+ effectivePersistentLayers,
6574
+ runtime.expandedPersistentLayers
6575
+ ),
6576
+ source: layers.length > 0 ? "rich-content" : "empty"
6099
6577
  };
6100
- });
6101
- distributeOffsets(pairs, edges, nodes, "source");
6102
- distributeOffsets(pairs, edges, nodes, "target");
6103
- return pairs;
6104
- }
6105
- function distributeOffsets(pairs, edges, nodes, endpoint) {
6106
- const nodeById = new Map(nodes.map((node) => [node.id, node]));
6107
- const groups = /* @__PURE__ */ new Map();
6108
- for (let index = 0; index < edges.length; index++) {
6109
- const edge = edges[index];
6110
- const anchor = endpoint === "source" ? pairs[index].sourceAnchor : pairs[index].targetAnchor;
6111
- const nodeId = endpoint === "source" ? edge.source : edge.target;
6112
- const key = `${nodeId}:${anchor.side}`;
6113
- const group = groups.get(key) ?? [];
6114
- group.push(index);
6115
- groups.set(key, group);
6116
6578
  }
6117
- for (const indexes of groups.values()) {
6118
- if (indexes.length < 2) continue;
6119
- const side = endpoint === "source" ? pairs[indexes[0]].sourceAnchor.side : pairs[indexes[0]].targetAnchor.side;
6120
- indexes.sort((a, b) => {
6121
- const oppositeA = nodeById.get(endpoint === "source" ? edges[a].target : edges[a].source);
6122
- const oppositeB = nodeById.get(endpoint === "source" ? edges[b].target : edges[b].source);
6123
- const axisA = side === "top" || side === "bottom" ? oppositeA?.x ?? 0 : oppositeA?.y ?? 0;
6124
- const axisB = side === "top" || side === "bottom" ? oppositeB?.x ?? 0 : oppositeB?.y ?? 0;
6125
- return axisA - axisB || a - b;
6126
- });
6127
- indexes.forEach((edgeIndex, rank) => {
6128
- const anchor = endpoint === "source" ? pairs[edgeIndex].sourceAnchor : pairs[edgeIndex].targetAnchor;
6129
- anchor.offset = (rank + 1) / (indexes.length + 1);
6130
- });
6579
+ const context = runtime.templateContext ? cloneData(runtime.templateContext) : createTemplateContext(theme, blockIndex, totalBlocks, viewport);
6580
+ context.block = cloneData(block);
6581
+ const execution = executeTemplateMaterialization(block, context, runtime.registry);
6582
+ if (execution.status === "ok") {
6583
+ const styledLayers = runtime.applyRenderStyle !== false ? applyRenderStyleToLayers(execution.layers, block, theme) : execution.layers;
6584
+ const layers = appendRichContentLayers(styledLayers, block, theme, viewport);
6585
+ return {
6586
+ layers: injectPersistentLayers(
6587
+ layers,
6588
+ block,
6589
+ theme,
6590
+ effectivePersistentLayers,
6591
+ runtime.expandedPersistentLayers
6592
+ ),
6593
+ source: "template"
6594
+ };
6131
6595
  }
6132
- }
6133
- function asciiDiagramFromTemplateData(nodes, edges, options = {}) {
6134
- const ids = new Set(nodes.map((n) => n.id));
6135
- const outNodes = nodes.map((n) => {
6136
- const { col, row } = canvasToAsciiCell(n.x, n.y);
6137
- const labelLines = n.label.length > 0 ? n.label.split("\n") : [];
6138
- const innerW = labelLines.reduce((w, l) => Math.max(w, l.length), 0);
6139
- const wCols = typeof n.w === "number" ? Math.max(3, Math.round(n.w / ASCII_CHAR_W)) : innerW + 4;
6140
- const hRows = typeof n.h === "number" ? Math.max(3, Math.round(n.h / ASCII_CHAR_H)) : labelLines.length + 2;
6596
+ const diagnostic = toLayerMaterializationDiagnostic(block.template, execution);
6597
+ if ((options.failureMode ?? "fallback") === "empty") {
6598
+ const layers = appendRichContentLayers([], block, theme, viewport);
6141
6599
  return {
6142
- id: n.id,
6143
- label: n.label,
6144
- col,
6145
- row,
6146
- wCols: Math.max(wCols, 3),
6147
- hRows: Math.max(hRows, 3),
6148
- ...n.container && ids.has(n.container) && n.container !== n.id ? { containerId: n.container } : {}
6600
+ layers: injectPersistentLayers(
6601
+ layers,
6602
+ block,
6603
+ theme,
6604
+ effectivePersistentLayers,
6605
+ runtime.expandedPersistentLayers
6606
+ ),
6607
+ source: layers.length > 0 ? "rich-content" : "empty",
6608
+ diagnostic
6149
6609
  };
6150
- });
6610
+ }
6611
+ const fallbackLayers = appendRichContentLayers(
6612
+ fallbackBlockLayers(block, context, diagnostic.message),
6613
+ block,
6614
+ theme,
6615
+ viewport
6616
+ );
6151
6617
  return {
6152
- nodes: outNodes,
6153
- edges: edges.filter((e) => ids.has(e.source) && ids.has(e.target)).map((e) => ({
6154
- source: e.source,
6155
- target: e.target,
6156
- ...e.label ? { label: e.label } : {},
6157
- directed: e.directed !== false
6158
- })),
6159
- style: options.style ?? "unicode",
6160
- warnings: []
6618
+ layers: injectPersistentLayers(
6619
+ fallbackLayers,
6620
+ block,
6621
+ theme,
6622
+ effectivePersistentLayers,
6623
+ runtime.expandedPersistentLayers
6624
+ ),
6625
+ source: "fallback",
6626
+ diagnostic
6161
6627
  };
6162
6628
  }
6163
- function asciiDiagramFromBlocks(children, options = {}) {
6164
- const layout = computeDiagramLayout(children);
6165
- const nodes = layout.nodes.map((n) => ({
6166
- id: n.id,
6167
- label: n.label,
6168
- x: n.x,
6169
- y: n.y
6170
- }));
6171
- const edges = layout.edges.map((e) => ({
6172
- source: e.source,
6173
- target: e.target,
6174
- ...e.type ? { label: e.type } : {}
6175
- }));
6176
- const diagram = asciiDiagramFromTemplateData(nodes, edges, options);
6177
- diagram.warnings.push(...layout.warnings);
6178
- return diagram;
6629
+ function mermaidSources(block) {
6630
+ return (block.contents ?? []).filter(
6631
+ (node) => node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid"
6632
+ ).map((node) => node.value).filter((source) => source.trim().length > 0);
6179
6633
  }
6180
-
6181
- // src/doc/asciiTimeline/mapping.ts
6182
- function asciiTimelineToTemplateData(timeline) {
6183
- const globalStart = timeline.tracks.length > 0 ? Math.min(...timeline.tracks.map((track) => track.startColumn)) : 0;
6184
- const globalEnd = timeline.tracks.length > 0 ? Math.max(...timeline.tracks.map((track) => track.endColumn)) : 1;
6185
- const span = Math.max(1, globalEnd - globalStart);
6186
- const tracks = timeline.tracks.map((track) => ({
6187
- id: track.id,
6188
- ...track.label ? { label: track.label } : {},
6189
- ...track.endLabel ? { endLabel: track.endLabel } : {},
6190
- events: track.events.map(
6191
- (event) => ({
6192
- id: event.id,
6193
- label: event.label,
6194
- ...event.description ? { description: event.description } : {},
6195
- position: clamp012((event.column - globalStart) / span),
6196
- ...event.side ? { side: event.side } : {},
6197
- ...event.descriptionSide ? { descriptionSide: event.descriptionSide } : {},
6198
- ...event.callout === false ? { callout: false } : {},
6199
- ...event.marker ? { marker: event.marker } : {}
6634
+ function collectRichMediaItems(layers, block) {
6635
+ const existingImages = new Set(
6636
+ layers.filter((layer) => layer.type === "image").map((layer) => layer.content.src)
6637
+ );
6638
+ const existingVideos = new Set(
6639
+ layers.filter((layer) => layer.type === "video").map((layer) => layer.content.src)
6640
+ );
6641
+ const existingMermaid = new Set(
6642
+ layers.filter((layer) => layer.type === "mermaid").map((layer) => layer.content.source)
6643
+ );
6644
+ const embeddedVideos = extractEmbeddedVideos(block.contents);
6645
+ const videos = embeddedVideos.filter((video) => !existingVideos.has(video.src));
6646
+ const videoSources = new Set(embeddedVideos.map((video) => video.src));
6647
+ const seenImages = new Set(existingImages);
6648
+ const images = extractImages(block.contents).filter((image) => {
6649
+ if (videoSources.has(image.src) || seenImages.has(image.src)) return false;
6650
+ seenImages.add(image.src);
6651
+ return true;
6652
+ });
6653
+ const sources = mermaidSources(block).filter((source) => !existingMermaid.has(source));
6654
+ return [
6655
+ ...images.map(
6656
+ (image) => ({
6657
+ kind: "image",
6658
+ src: image.src,
6659
+ alt: image.alt,
6660
+ ...image.width && image.height ? { aspectRatio: image.width / image.height } : {}
6200
6661
  })
6201
- )
6202
- }));
6203
- const links = timeline.links.map((link) => ({ ...link }));
6204
- return { tracks, links };
6205
- }
6206
- function clamp012(value) {
6207
- return Math.max(0, Math.min(1, value));
6208
- }
6209
-
6210
- // src/doc/treeview/mapping.ts
6211
- function treeToTemplateData(tree) {
6212
- return { items: tree.roots.map(toItem) };
6662
+ ),
6663
+ ...videos.map(
6664
+ (video) => ({
6665
+ kind: "video",
6666
+ src: video.src,
6667
+ ...video.posterSrc ? { posterSrc: video.posterSrc } : {},
6668
+ alt: video.alt,
6669
+ aspectRatio: 16 / 9
6670
+ })
6671
+ ),
6672
+ ...sources.map((source) => ({ kind: "mermaid", source, aspectRatio: 16 / 9 }))
6673
+ ];
6213
6674
  }
6214
- function toItem(node) {
6215
- return {
6216
- id: node.id,
6217
- label: node.label,
6218
- ...node.isDir ? { isDir: true } : {},
6219
- ...node.comment ? { comment: node.comment } : {},
6220
- children: node.children.map(toItem)
6675
+ function gridCells(region, count, viewport, inset) {
6676
+ const unit = Math.min(viewport.width, viewport.height);
6677
+ const padding = inset ? unit * 0.025 : 0;
6678
+ const gap = count > 1 ? unit * 0.018 : 0;
6679
+ const inner = {
6680
+ x: region.x + padding,
6681
+ y: region.y + padding,
6682
+ width: Math.max(0, region.width - padding * 2),
6683
+ height: Math.max(0, region.height - padding * 2)
6221
6684
  };
6685
+ const columns = Math.min(
6686
+ count,
6687
+ Math.max(1, Math.ceil(Math.sqrt(count * (inner.width / Math.max(1, inner.height)))))
6688
+ );
6689
+ const rows = Math.ceil(count / columns);
6690
+ const width = (inner.width - gap * (columns - 1)) / columns;
6691
+ const height = (inner.height - gap * (rows - 1)) / rows;
6692
+ return Array.from({ length: count }, (_, index) => ({
6693
+ x: inner.x + index % columns * (width + gap),
6694
+ y: inner.y + Math.floor(index / columns) * (height + gap),
6695
+ width,
6696
+ height
6697
+ }));
6222
6698
  }
6223
- function treeFromTemplateData(items, options = {}) {
6224
- const roots = items.map(fromItem);
6225
- return { roots, style: options.style ?? "unicode", warnings: [] };
6226
- }
6227
- function fromItem(item) {
6228
- const children = Array.isArray(item.children) ? item.children.map(fromItem) : [];
6229
- return {
6230
- id: typeof item.id === "string" && item.id ? item.id : "node",
6231
- label: typeof item.label === "string" ? item.label : "",
6232
- children,
6233
- isDir: item.isDir === true || children.length > 0,
6234
- ...item.comment ? { comment: item.comment } : {}
6235
- };
6699
+ function effectiveImageTreatment(theme, block) {
6700
+ const override = block.imageTreatment;
6701
+ if (override === "none") return void 0;
6702
+ const base = override ? { type: override, strength: theme.style.imageTreatment?.strength } : theme.style.imageTreatment;
6703
+ if (!base || base.type === "none") return void 0;
6704
+ return base.type === "duotone" && !base.color ? { ...base, color: theme.colors.primary } : base;
6236
6705
  }
6237
- function treeFromMarkdownList(list) {
6238
- const usedIds = /* @__PURE__ */ new Map();
6239
- const slugify2 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "node";
6240
- const idFor = (label) => {
6241
- const base = slugify2(label.replace(/\/$/, ""));
6242
- const count = usedIds.get(base) ?? 0;
6243
- usedIds.set(base, count + 1);
6244
- return count === 0 ? base : `${base}-${count + 1}`;
6245
- };
6246
- const itemToNode = (item) => {
6247
- const labelNode = item.children.find((c) => c.type !== "list");
6248
- const label = labelNode ? extractPlainText(labelNode).trim() : "";
6249
- const childLists = item.children.filter((c) => c.type === "list");
6250
- const children = [];
6251
- for (const cl of childLists) for (const ci of cl.children) children.push(itemToNode(ci));
6706
+ function richMediaLayer(item, position, block, theme, kindIndex) {
6707
+ if (item.kind === "image") {
6708
+ const treatment = effectiveImageTreatment(theme, block);
6252
6709
  return {
6253
- id: idFor(label),
6254
- label,
6255
- children,
6256
- isDir: label.endsWith("/") || children.length > 0
6710
+ id: `${block.id}-embedded-image-${kindIndex}`,
6711
+ type: "image",
6712
+ position,
6713
+ content: {
6714
+ src: item.src,
6715
+ alt: item.alt || block.title || "Embedded image",
6716
+ fit: "contain",
6717
+ ...treatment ? { treatment } : {}
6718
+ }
6257
6719
  };
6258
- };
6259
- return { roots: list.children.map(itemToNode), style: "unicode", warnings: [] };
6260
- }
6261
- function findFirstList(contents) {
6262
- return contents?.find((n) => n.type === "list");
6263
- }
6264
-
6265
- // src/doc/templateInputs.ts
6266
- function extractBodyPlainText(contents) {
6267
- if (!contents || contents.length === 0) return "";
6268
- return contents.filter((node) => !(node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")).map((n) => extractPlainText(n)).join("\n").trim();
6269
- }
6270
- function extractListItems(contents) {
6271
- if (!contents) return [];
6272
- const items = [];
6273
- for (const node of contents) {
6274
- if (node.type === "list") {
6275
- for (const item of node.children) {
6276
- const text = extractPlainText(item).trim();
6277
- if (text) items.push(text);
6720
+ }
6721
+ if (item.kind === "video") {
6722
+ return {
6723
+ id: `${block.id}-embedded-video-${kindIndex}`,
6724
+ type: "video",
6725
+ position,
6726
+ content: {
6727
+ src: item.src,
6728
+ ...item.posterSrc ? { posterSrc: item.posterSrc } : {},
6729
+ alt: item.alt || block.title || "Embedded video",
6730
+ fit: "contain",
6731
+ clipStart: 0,
6732
+ clipEnd: Math.max(0, block.duration)
6278
6733
  }
6279
- }
6734
+ };
6280
6735
  }
6281
- return items;
6282
- }
6283
- function parseDim(raw) {
6284
- if (raw === void 0) return void 0;
6285
- const n = parseFloat(raw);
6286
- return Number.isFinite(n) && n > 0 ? n : void 0;
6736
+ return {
6737
+ id: `${block.id}-mermaid-${kindIndex}`,
6738
+ type: "mermaid",
6739
+ position,
6740
+ content: {
6741
+ source: item.source,
6742
+ background: theme.colors.backgroundLight,
6743
+ foreground: theme.colors.text,
6744
+ padding: Math.max(12, Math.round(Math.min(position.width, position.height) * 0.025))
6745
+ }
6746
+ };
6287
6747
  }
6288
- function extractImages(contents, limit = Infinity) {
6289
- if (!contents || contents.length === 0) return [];
6290
- const found = [];
6291
- function fromHtml(nodes) {
6292
- for (const node of nodes) {
6293
- if (found.length >= limit) return;
6294
- if (!node || typeof node !== "object") continue;
6295
- const n = node;
6296
- if (n.type === "htmlElement" && n.tagName === "img") {
6297
- const attrs = n.attributes;
6298
- if (attrs && typeof attrs.src === "string" && attrs.src) {
6299
- found.push({
6300
- src: attrs.src,
6301
- alt: typeof attrs.alt === "string" ? attrs.alt : "",
6302
- width: parseDim(attrs.width),
6303
- height: parseDim(attrs.height)
6304
- });
6305
- }
6306
- }
6307
- if (Array.isArray(n.children)) fromHtml(n.children);
6748
+ function appendRichContentLayers(layers, block, theme, viewport) {
6749
+ const items = collectRichMediaItems(layers, block);
6750
+ if (items.length === 0) return layers;
6751
+ const layout = resolveSupplementalMediaLayout(
6752
+ layers,
6753
+ block.template ? resolveTemplateName(block.template) : void 0,
6754
+ viewport,
6755
+ items.length,
6756
+ items.map((item) => item.aspectRatio)
6757
+ );
6758
+ const cells = gridCells(layout.mediaRect, items.length, viewport, layout.framed);
6759
+ const counters = { image: 0, video: 0, mermaid: 0 };
6760
+ const mediaLayers = items.map((item, index) => {
6761
+ counters[item.kind] += 1;
6762
+ return richMediaLayer(item, cells[index], block, theme, counters[item.kind]);
6763
+ });
6764
+ const frame = layout.framed ? {
6765
+ id: `${block.id}-rich-media-frame`,
6766
+ type: "shape",
6767
+ position: layout.mediaRect,
6768
+ content: {
6769
+ shape: "rect",
6770
+ fill: theme.colors.backgroundLight,
6771
+ stroke: withAlpha(theme.colors.text, 0.14),
6772
+ strokeWidth: 2,
6773
+ borderRadius: Math.max(12, Math.round(Math.min(viewport.width, viewport.height) * 0.018))
6308
6774
  }
6775
+ } : void 0;
6776
+ return [...layout.layers, ...frame ? [frame] : [], ...mediaLayers];
6777
+ }
6778
+ function executeTemplateMaterialization(templateBlock, context, registry) {
6779
+ const safeTemplateBlock = cloneData(templateBlock);
6780
+ const safeContext = cloneData(context);
6781
+ const maybeChildren = safeTemplateBlock.children;
6782
+ if ((!safeContext.children || safeContext.children.length === 0) && maybeChildren?.length) {
6783
+ safeContext.children = maybeChildren;
6309
6784
  }
6310
- function walk(node) {
6311
- if (found.length >= limit) return;
6312
- if (!node || typeof node !== "object") return;
6313
- const n = node;
6314
- if (n.type === "image" && typeof n.url === "string" && n.url) {
6315
- found.push({ src: n.url, alt: typeof n.alt === "string" ? n.alt : "" });
6316
- return;
6317
- }
6318
- if ((n.type === "htmlBlock" || n.type === "htmlInline") && Array.isArray(n.htmlChildren)) {
6319
- fromHtml(n.htmlChildren);
6785
+ const templateFn = registry[resolveTemplateName(safeTemplateBlock.template)];
6786
+ if (!templateFn) return { status: "unknown-template", layers: [] };
6787
+ const { templateData, templateOverrides } = safeTemplateBlock;
6788
+ const sourceBlock = safeTemplateBlock;
6789
+ const derivedInputs = sourceBlock.sourceHeading ? deriveTemplateInputs(
6790
+ safeTemplateBlock.template,
6791
+ sourceBlock.title ?? "",
6792
+ sourceBlock.contents
6793
+ ) : null;
6794
+ const coercedOverrides = coerceTemplateParams(
6795
+ safeTemplateBlock.template,
6796
+ templateOverrides ?? {}
6797
+ ).input;
6798
+ const input = derivedInputs || templateData || templateOverrides ? {
6799
+ ...derivedInputs,
6800
+ ...safeTemplateBlock,
6801
+ ...templateData,
6802
+ ...coercedOverrides
6803
+ } : safeTemplateBlock;
6804
+ const explicitQuote = Object.prototype.hasOwnProperty.call(coercedOverrides, "quote") ? coercedOverrides.quote : Object.prototype.hasOwnProperty.call(templateData ?? {}, "quote") ? templateData?.quote : Object.prototype.hasOwnProperty.call(safeTemplateBlock, "quote") ? safeTemplateBlock.quote : void 0;
6805
+ if (resolveTemplateName(safeTemplateBlock.template) === "quote" && derivedInputs && !Object.prototype.hasOwnProperty.call(derivedInputs, "title") && !(typeof explicitQuote === "string" && explicitQuote.trim())) {
6806
+ const quoteInput = input;
6807
+ quoteInput.quote = derivedInputs.quote;
6808
+ if (!Object.prototype.hasOwnProperty.call(templateData ?? {}, "title") && !Object.prototype.hasOwnProperty.call(coercedOverrides, "title")) {
6809
+ delete quoteInput.title;
6320
6810
  }
6321
- if (Array.isArray(n.children)) {
6322
- for (const child of n.children) walk(child);
6811
+ }
6812
+ try {
6813
+ const renderedLayers = templateFn(input, safeContext);
6814
+ if (!Array.isArray(renderedLayers)) {
6815
+ return {
6816
+ status: "invalid-result",
6817
+ layers: [],
6818
+ receivedType: typeof renderedLayers
6819
+ };
6323
6820
  }
6821
+ return { status: "ok", layers: cloneData(renderedLayers) };
6822
+ } catch (error) {
6823
+ return { status: "error", layers: [], error };
6324
6824
  }
6325
- for (const node of contents) {
6326
- if (found.length >= limit) break;
6327
- walk(node);
6825
+ }
6826
+ function toLayerMaterializationDiagnostic(template, execution) {
6827
+ if (execution.status === "unknown-template") {
6828
+ return {
6829
+ code: "unknown-template",
6830
+ template,
6831
+ message: `Unknown template "${template}"`
6832
+ };
6328
6833
  }
6329
- return found;
6834
+ if (execution.status === "invalid-result") {
6835
+ return {
6836
+ code: "invalid-template-result",
6837
+ template,
6838
+ receivedType: execution.receivedType,
6839
+ message: `Template "${template}" returned ${execution.receivedType}, not a layer array`
6840
+ };
6841
+ }
6842
+ return {
6843
+ code: "template-error",
6844
+ template,
6845
+ cause: execution.error,
6846
+ message: `Template "${template}" failed`
6847
+ };
6330
6848
  }
6331
- function extractFirstImage(contents) {
6332
- return extractImages(contents, 1)[0] ?? null;
6849
+ function injectPersistentLayers(layers, block, theme, persistentLayers, expanded) {
6850
+ if (!persistentLayers) return layers;
6851
+ const bottomLayers = expanded?.bottomLayers ?? expandPersistentLayers(persistentLayers.bottomLayers, theme);
6852
+ const topLayers = expanded?.topLayers ?? expandPersistentLayers(persistentLayers.topLayers, theme);
6853
+ return wrapWithPersistentLayers(layers, block, bottomLayers, topLayers);
6333
6854
  }
6334
- function extractTableFromContents(contents) {
6335
- if (!contents) return null;
6336
- for (const node of contents) {
6337
- if (node.type === "table") {
6338
- const table = node;
6339
- const [headerRow, ...bodyRows] = table.children;
6340
- if (!headerRow) return null;
6341
- const headers = headerRow.children.map((cell) => extractPlainText(cell).trim());
6342
- const rows = bodyRows.map((row) => row.children.map((cell) => extractPlainText(cell).trim()));
6343
- return { headers, rows, align: table.align };
6855
+
6856
+ // src/doc/templates/metadata.ts
6857
+ var TEMPLATE_METADATA = {
6858
+ title: {
6859
+ label: "Title",
6860
+ description: "A bold opening slide with large title text, perfect for covers and chapters."
6861
+ },
6862
+ sectionHeader: {
6863
+ label: "Section Header",
6864
+ description: "A clean section break with a prominent title and optional subtitle."
6865
+ },
6866
+ statHighlight: {
6867
+ label: "Stat Highlight",
6868
+ description: "Showcases a single key number or metric with supporting context."
6869
+ },
6870
+ quote: {
6871
+ label: "Quote",
6872
+ description: "Displays a stylized pull quote with decorative marks and attribution."
6873
+ },
6874
+ factCard: {
6875
+ label: "Fact Card",
6876
+ description: "Presents a focused fact or insight with a labeled header and body text."
6877
+ },
6878
+ twoColumn: {
6879
+ label: "Two Column",
6880
+ description: "Divides the slide into two equal side-by-side content columns."
6881
+ },
6882
+ dateEvent: {
6883
+ label: "Date Event",
6884
+ description: "Highlights a date-based event or milestone with a prominent date display."
6885
+ },
6886
+ imageWithCaption: {
6887
+ label: "Image with Caption",
6888
+ description: "Displays a full-bleed image with an optional caption below."
6889
+ },
6890
+ leftFeature: {
6891
+ label: "Left Feature",
6892
+ description: "Image on the left, title and body text stacked on the right."
6893
+ },
6894
+ rightFeature: {
6895
+ label: "Right Feature",
6896
+ description: "Image on the right, title and body text stacked on the left."
6897
+ },
6898
+ map: {
6899
+ label: "Map",
6900
+ description: "Embeds an interactive map centered on a geographic location."
6901
+ },
6902
+ fullBleedQuote: {
6903
+ label: "Full Bleed Quote",
6904
+ description: "A full-width quote that spans the entire slide for maximum impact."
6905
+ },
6906
+ list: {
6907
+ label: "List",
6908
+ description: "Renders a bulleted or numbered list in a clean, card-style layout."
6909
+ },
6910
+ photoGrid: {
6911
+ label: "Photo Grid",
6912
+ description: "Arranges multiple photos in a 2\xD72 or 3\xD73 mosaic grid."
6913
+ },
6914
+ definitionCard: {
6915
+ label: "Definition Card",
6916
+ description: "Shows a term and its definition in a structured, dictionary-style card."
6917
+ },
6918
+ comparisonBar: {
6919
+ label: "Comparison Bar",
6920
+ description: "Visualizes two or more values side-by-side with labeled horizontal bars."
6921
+ },
6922
+ pullQuote: {
6923
+ label: "Pull Quote",
6924
+ description: "A stylized pull quote with large decorative marks and centered text."
6925
+ },
6926
+ videoWithCaption: {
6927
+ label: "Video with Caption",
6928
+ description: "Embeds a video player with an optional caption below."
6929
+ },
6930
+ videoPullQuote: {
6931
+ label: "Video Pull Quote",
6932
+ description: "Combines a video panel with a highlighted pull quote side-by-side."
6933
+ },
6934
+ dataTable: {
6935
+ label: "Data Table",
6936
+ description: "Renders tabular data in a clean, styled table with a header row."
6937
+ },
6938
+ diagram: {
6939
+ label: "Diagram",
6940
+ description: "Renders sub-headings as connected nodes \u2014 edit visually as a node-and-edge diagram."
6941
+ },
6942
+ tree: {
6943
+ label: "Tree",
6944
+ description: "Renders an ASCII file-tree / outline fence as a filesystem-style treeview \u2014 edit as an outline."
6945
+ },
6946
+ timeline: {
6947
+ label: "Timeline",
6948
+ description: "Plots events across one or more horizontal tracks with callouts and optional branching links."
6949
+ },
6950
+ layout: {
6951
+ label: "Layout",
6952
+ description: "Free-form 2D canvas \u2014 drag layers into place. Use for one-off block layouts that don't fit a template."
6953
+ },
6954
+ drawing: {
6955
+ label: "Drawing",
6956
+ description: "Free-form sketches \u2014 draw shapes, paths, and text directly on a 2D surface."
6957
+ }
6958
+ };
6959
+
6960
+ // src/doc/templates/coverBlock.ts
6961
+ var COVER_TITLE_BASE_PX = 120;
6962
+ var COVER_TITLE_COMFORTABLE_CHARS = 26;
6963
+ var COVER_TITLE_MIN_BASE_PX = 62;
6964
+ function fitCoverTitleSize(title) {
6965
+ const len = title.trim().length;
6966
+ if (len <= COVER_TITLE_COMFORTABLE_CHARS) return COVER_TITLE_BASE_PX;
6967
+ const scaled = COVER_TITLE_BASE_PX * Math.sqrt(COVER_TITLE_COMFORTABLE_CHARS / len);
6968
+ return Math.max(COVER_TITLE_MIN_BASE_PX, Math.round(scaled));
6969
+ }
6970
+ function isStandardLightCover(context) {
6971
+ const { theme } = context;
6972
+ return theme.id === "standard" && relativeLuminance(theme.colors.background) > 0.85;
6973
+ }
6974
+ var COVER_SCRIM_STOPS = [
6975
+ { pos: 0, alpha: 0.95 },
6976
+ { pos: 38, alpha: 0.86 },
6977
+ { pos: 58, alpha: 0.4 },
6978
+ { pos: 78, alpha: 0.1 },
6979
+ { pos: 100, alpha: 0 }
6980
+ ];
6981
+ function coverScrimGradient(background) {
6982
+ const stops = COVER_SCRIM_STOPS.map((s) => `${withAlpha(background, s.alpha)} ${s.pos}%`);
6983
+ return `linear-gradient(0deg, ${stops.join(", ")})`;
6984
+ }
6985
+ function coverBlock(input, context) {
6986
+ const treatment = themedImageTreatment(context, input.imageTreatment);
6987
+ const { heroSrc, heroAlt, title, subtitle, ambientMotion, heroCredit, heroLicense } = input;
6988
+ const { theme, layout } = context;
6989
+ const standardLightCover = !heroSrc && isStandardLightCover(context);
6990
+ const titleFontSize = themedFontSize(fitCoverTitleSize(title), context, true);
6991
+ const subtitleFontSize = themedFontSize(40, context, false);
6992
+ const layers = [];
6993
+ if (heroSrc) {
6994
+ const imageAnimation = mapAmbientMotion(ambientMotion);
6995
+ layers.push(
6996
+ {
6997
+ type: "image",
6998
+ id: "cover-hero",
6999
+ content: {
7000
+ src: heroSrc,
7001
+ alt: heroAlt || title,
7002
+ fit: "cover",
7003
+ credit: heroCredit,
7004
+ license: heroLicense,
7005
+ ...treatment ? { treatment } : {}
7006
+ },
7007
+ position: { x: 0, y: 0, width: "100%", height: "100%" },
7008
+ animation: imageAnimation
7009
+ },
7010
+ {
7011
+ type: "shape",
7012
+ id: "cover-gradient",
7013
+ content: {
7014
+ shape: "rect",
7015
+ fill: coverScrimGradient(theme.colors.background)
7016
+ },
7017
+ position: { x: 0, y: 0, width: "100%", height: "100%" }
7018
+ }
7019
+ );
7020
+ } else {
7021
+ if (standardLightCover) {
7022
+ layers.push(
7023
+ {
7024
+ type: "shape",
7025
+ id: "cover-bg",
7026
+ content: {
7027
+ shape: "rect",
7028
+ fill: theme.colors.background
7029
+ },
7030
+ position: { x: 0, y: 0, width: "100%", height: "100%" }
7031
+ },
7032
+ {
7033
+ type: "shape",
7034
+ id: "cover-bg-tint",
7035
+ content: {
7036
+ shape: "rect",
7037
+ fill: `radial-gradient(ellipse at 50% 40%, ${withAlpha(theme.colors.primary, 0.16)} 0%, ${withAlpha(theme.colors.primary, 0.07)} 38%, ${withAlpha(theme.colors.primary, 0)} 78%)`
7038
+ },
7039
+ position: { x: 0, y: 0, width: "100%", height: "100%" }
7040
+ }
7041
+ );
7042
+ } else {
7043
+ layers.push({
7044
+ type: "shape",
7045
+ id: "cover-bg",
7046
+ content: {
7047
+ shape: "rect",
7048
+ fill: `radial-gradient(ellipse at 50% 40%, ${theme.colors.primary} 0%, ${theme.colors.background} 100%)`
7049
+ },
7050
+ position: { x: 0, y: 0, width: "100%", height: "100%" }
7051
+ });
6344
7052
  }
7053
+ layers.push({
7054
+ type: "shape",
7055
+ id: "cover-accent",
7056
+ content: {
7057
+ shape: "rect",
7058
+ fill: standardLightCover ? withAlpha(theme.colors.primary, 0.24) : "rgba(255, 255, 255, 0.2)"
7059
+ },
7060
+ position: {
7061
+ x: "35%",
7062
+ y: subtitle ? "42%" : "58%",
7063
+ width: "30%",
7064
+ height: "2px"
7065
+ }
7066
+ });
6345
7067
  }
6346
- return null;
6347
- }
6348
- function extractBlockquoteText(contents) {
6349
- if (!contents) return "";
6350
- for (const node of contents) {
6351
- if (node.type === "blockquote") return extractPlainText(node).trim();
7068
+ layers.push({
7069
+ type: "text",
7070
+ id: "cover-title",
7071
+ content: {
7072
+ text: title,
7073
+ style: {
7074
+ fontSize: titleFontSize,
7075
+ fontFamily: getThemeFont(context, "title"),
7076
+ fontWeight: "bold",
7077
+ color: theme.colors.text,
7078
+ textAlign: "center",
7079
+ shadow: !standardLightCover
7080
+ }
7081
+ },
7082
+ position: {
7083
+ x: "50%",
7084
+ y: heroSrc ? subtitle ? "70%" : "75%" : subtitle ? layout.primaryY : "50%",
7085
+ anchor: "center",
7086
+ width: layout.maxTextWidth
7087
+ },
7088
+ // No animation delay - shown immediately at rest
7089
+ animation: { type: "fadeIn", duration: 0.8 }
7090
+ });
7091
+ if (subtitle) {
7092
+ layers.push({
7093
+ type: "text",
7094
+ id: "cover-subtitle",
7095
+ content: {
7096
+ text: subtitle,
7097
+ style: {
7098
+ fontSize: subtitleFontSize,
7099
+ fontFamily: getThemeFont(context, "body"),
7100
+ fontWeight: standardLightCover ? "bold" : "normal",
7101
+ color: standardLightCover ? withAlpha(theme.colors.text, 0.78) : theme.colors.textMuted,
7102
+ textAlign: "center",
7103
+ lineHeight: 1.5
7104
+ }
7105
+ },
7106
+ position: {
7107
+ x: "50%",
7108
+ y: heroSrc ? "82%" : layout.secondaryY,
7109
+ anchor: "center",
7110
+ width: layout.maxTextWidth
7111
+ },
7112
+ animation: { type: "fadeIn", duration: 0.8, delay: 0.2 }
7113
+ });
6352
7114
  }
6353
- return "";
7115
+ return layers;
6354
7116
  }
6355
- function firstParagraph(contents) {
6356
- if (!contents) return null;
6357
- const index = contents.findIndex((node) => node.type === "paragraph");
6358
- if (index < 0) return null;
7117
+ function startBlockToCoverInput(config) {
6359
7118
  return {
6360
- node: contents[index],
6361
- index
7119
+ heroSrc: config.heroSrc,
7120
+ heroAlt: config.heroAlt,
7121
+ title: config.title,
7122
+ subtitle: config.subtitle,
7123
+ ambientMotion: config.ambientMotion,
7124
+ heroCredit: config.heroCredit,
7125
+ heroLicense: config.heroLicense
6362
7126
  };
6363
7127
  }
6364
- function leadingStrongStat(paragraph) {
6365
- for (const child of paragraph.children) {
6366
- if (child.type === "text" && child.value.trim() === "") continue;
6367
- if (child.type !== "strong") return null;
6368
- const text = extractPlainText(child).trim();
6369
- const match = text.match(/^(?:[$€£¥]\s*)?[+-]?\d+(?:[.,]\d+)*(?:\s?(?:[%‰x×]|[A-Za-z]+))?/);
6370
- return match?.[0].trim() || null;
6371
- }
6372
- return null;
6373
- }
6374
- function removeStatPhrase(text, start, end) {
6375
- const before = text.slice(0, start).trim();
6376
- let after = text.slice(end);
6377
- if (!before) after = after.replace(/^\s*(?:[-–—:]\s*)?/, "");
6378
- else after = after.trim();
6379
- return [before, after].filter(Boolean).join(" ").trim();
6380
- }
6381
- function bodyWithoutParagraphStat(contents, paragraphIndex, paragraphText, start, end) {
6382
- if (!contents) return "";
6383
- return contents.map(
6384
- (node, index) => index === paragraphIndex ? removeStatPhrase(paragraphText, start, end) : extractPlainText(node).trim()
6385
- ).filter(Boolean).join("\n").trim();
7128
+ function expandCoverBlock(config, context) {
7129
+ const input = startBlockToCoverInput(config);
7130
+ return coverBlock(input, context);
6386
7131
  }
6387
- function deriveStatHighlightInputs(headingText, contents, bodyText3) {
6388
- const paragraph = firstParagraph(contents);
6389
- if (paragraph) {
6390
- const paragraphText = extractPlainText(paragraph.node).trim();
6391
- const strongStat = leadingStrongStat(paragraph.node);
6392
- if (strongStat) {
6393
- const start = paragraphText.indexOf(strongStat);
6394
- const description = bodyWithoutParagraphStat(
6395
- contents,
6396
- paragraph.index,
6397
- paragraphText,
6398
- start,
6399
- start + strongStat.length
6400
- );
6401
- return {
6402
- stat: strongStat,
6403
- description: description || headingText || strongStat
6404
- };
6405
- }
7132
+
7133
+ // src/doc/templates/index.ts
7134
+ function materializeScheduledBlock(sourceBlock, blockIndex, totalBlocks, theme, viewport, persistentLayers, expandedPersistentLayers, registry, failureMode, onDiagnostic) {
7135
+ const materialized = materializeBlockLayersWithRuntime(
7136
+ sourceBlock,
7137
+ {
7138
+ theme,
7139
+ viewport,
7140
+ persistentLayers: persistentLayers ?? false,
7141
+ blockIndex,
7142
+ totalBlocks,
7143
+ failureMode
7144
+ },
7145
+ { registry, expandedPersistentLayers }
7146
+ );
7147
+ if (materialized.diagnostic) {
7148
+ onDiagnostic?.(materialized.diagnostic, sourceBlock, blockIndex);
6406
7149
  }
6407
- const trimmedHeading = headingText.trim();
6408
- const headingStat = matchNumberHighlight(trimmedHeading);
6409
- if (headingStat && headingStat.index === 0 && headingStat.end === trimmedHeading.length) {
6410
- return { stat: headingText, description: bodyText3 || headingText };
7150
+ const expandedBlock = isTemplateBlock(sourceBlock) ? {
7151
+ id: sourceBlock.id,
7152
+ startTime: 0,
7153
+ duration: sourceBlock.duration,
7154
+ audioSegment: sourceBlock.audioSegment,
7155
+ ...materialized.layers.length > 0 ? { layers: materialized.layers } : {},
7156
+ transition: sourceBlock.transition,
7157
+ template: sourceBlock.template
7158
+ } : {
7159
+ ...cloneData(sourceBlock),
7160
+ ...materialized.layers.length > 0 ? { layers: materialized.layers } : {}
7161
+ };
7162
+ const transition = resolveBlockTransition(expandedBlock, theme, blockIndex);
7163
+ if (transition !== expandedBlock.transition) {
7164
+ expandedBlock.transition = transition;
6411
7165
  }
6412
- if (paragraph) {
6413
- const paragraphText = extractPlainText(paragraph.node).trim();
6414
- const bodyStat = matchNumberHighlight(paragraphText);
6415
- if (bodyStat) {
6416
- const description = bodyWithoutParagraphStat(
6417
- contents,
6418
- paragraph.index,
6419
- paragraphText,
6420
- bodyStat.index,
6421
- bodyStat.end
7166
+ return expandedBlock;
7167
+ }
7168
+ function expandDocBlocks(blocks, options = {}) {
7169
+ const opts = options;
7170
+ const theme = opts.theme ?? DEFAULT_THEME;
7171
+ const viewport = opts.viewport ?? VIEWPORT_PRESETS.landscape;
7172
+ const {
7173
+ persistentLayers,
7174
+ audioSegments,
7175
+ customTemplates,
7176
+ failureMode = "fallback",
7177
+ onDiagnostic
7178
+ } = opts;
7179
+ const totalBlocks = blocks.length;
7180
+ const registry = customTemplates && customTemplates.length > 0 ? buildRegistry(customTemplates) : templateRegistry;
7181
+ const effectivePersistentLayers = persistentLayers === false ? void 0 : persistentLayers ?? theme.persistentLayers;
7182
+ const expandedPersistentLayers = effectivePersistentLayers ? {
7183
+ bottomLayers: expandPersistentLayers(effectivePersistentLayers.bottomLayers, theme),
7184
+ topLayers: expandPersistentLayers(effectivePersistentLayers.topLayers, theme)
7185
+ } : void 0;
7186
+ if (!audioSegments || audioSegments.length === 0) {
7187
+ let currentTime = 0;
7188
+ return blocks.map((block, index) => {
7189
+ const expandedBlock = materializeScheduledBlock(
7190
+ block,
7191
+ index,
7192
+ totalBlocks,
7193
+ theme,
7194
+ viewport,
7195
+ effectivePersistentLayers,
7196
+ expandedPersistentLayers,
7197
+ registry,
7198
+ failureMode,
7199
+ onDiagnostic
6422
7200
  );
6423
- return {
6424
- stat: bodyStat.value,
6425
- description: description || headingText || bodyStat.value
6426
- };
6427
- }
7201
+ expandedBlock.startTime = currentTime;
7202
+ currentTime += expandedBlock.duration;
7203
+ return expandedBlock;
7204
+ });
6428
7205
  }
6429
- return { stat: headingText, description: bodyText3 || headingText };
6430
- }
6431
- function deriveTemplateInputs(templateName, headingText, contents, options = {}) {
6432
- const placeholders = options.placeholders === true;
6433
- const bodyText3 = extractBodyPlainText(contents);
6434
- switch (templateName) {
6435
- case "statHighlight":
6436
- return deriveStatHighlightInputs(headingText, contents, bodyText3);
6437
- case "quote": {
6438
- const quote = extractBlockquoteText(contents) || bodyText3 || headingText;
6439
- return { quote };
6440
- }
6441
- case "fullBleedQuote":
6442
- case "pullQuote": {
6443
- const text = extractBlockquoteText(contents) || bodyText3 || headingText;
6444
- return { text };
7206
+ const blocksBySegment = /* @__PURE__ */ new Map();
7207
+ blocks.forEach((block, index) => {
7208
+ const segmentIndex = block.audioSegment ?? 0;
7209
+ if (!blocksBySegment.has(segmentIndex)) {
7210
+ blocksBySegment.set(segmentIndex, []);
6445
7211
  }
6446
- case "factCard":
6447
- return { fact: headingText, explanation: bodyText3 || headingText };
6448
- case "comparisonBar":
6449
- return placeholders ? { leftLabel: "A", leftValue: 60, rightLabel: "B", rightValue: 40 } : null;
6450
- case "list": {
6451
- const items = extractListItems(contents);
6452
- if (items.length > 0) return { items };
6453
- return placeholders ? { items: ["Item 1", "Item 2", "Item 3"] } : null;
7212
+ blocksBySegment.get(segmentIndex).push({ block, originalIndex: index });
7213
+ });
7214
+ const expandedBlocks = new Array(blocks.length);
7215
+ for (const [segmentIndex, segmentBlocks] of blocksBySegment) {
7216
+ const audioSegment = audioSegments[segmentIndex];
7217
+ if (!audioSegment) {
7218
+ let offsetTime = 0;
7219
+ for (const { block, originalIndex } of segmentBlocks) {
7220
+ const expandedBlock = materializeScheduledBlock(
7221
+ block,
7222
+ originalIndex,
7223
+ totalBlocks,
7224
+ theme,
7225
+ viewport,
7226
+ effectivePersistentLayers,
7227
+ expandedPersistentLayers,
7228
+ registry,
7229
+ failureMode,
7230
+ onDiagnostic
7231
+ );
7232
+ expandedBlock.startTime = offsetTime;
7233
+ offsetTime += expandedBlock.duration;
7234
+ expandedBlocks[originalIndex] = expandedBlock;
7235
+ }
7236
+ continue;
6454
7237
  }
6455
- case "definitionCard":
6456
- return { term: headingText, definition: bodyText3 || headingText };
6457
- case "dateEvent":
6458
- return { date: headingText, description: bodyText3 || headingText };
6459
- case "dataTable": {
6460
- const tableData = extractTableFromContents(contents);
6461
- if (tableData) return tableData;
6462
- return placeholders ? { headers: ["Column"], rows: [["Data"]] } : null;
7238
+ const sectionHeaderBlocks = [];
7239
+ const contentBlocks = [];
7240
+ for (const item of segmentBlocks) {
7241
+ const templateBlock = item.block;
7242
+ if (templateBlock.template === "sectionHeader") {
7243
+ sectionHeaderBlocks.push(item);
7244
+ } else {
7245
+ contentBlocks.push(item);
7246
+ }
6463
7247
  }
6464
- case "imageWithCaption": {
6465
- const img = extractFirstImage(contents);
6466
- if (!img) return placeholders ? { caption: headingText } : null;
6467
- return { imageSrc: img.src, imageAlt: img.alt || headingText, caption: headingText };
7248
+ const fixedDuration = sectionHeaderBlocks.reduce((sum, { block }) => {
7249
+ return sum + (block.duration ?? 0);
7250
+ }, 0);
7251
+ const contentBlockDuration = contentBlocks.reduce((sum, { block }) => {
7252
+ return sum + (block.duration ?? 0);
7253
+ }, 0);
7254
+ const remainingDuration = audioSegment.duration - fixedDuration;
7255
+ const scaleFactor = contentBlockDuration > 0 && remainingDuration > 0 ? remainingDuration / contentBlockDuration : 1;
7256
+ const expandedInfos = [];
7257
+ for (const { block, originalIndex } of segmentBlocks) {
7258
+ const expandedBlock = materializeScheduledBlock(
7259
+ block,
7260
+ originalIndex,
7261
+ totalBlocks,
7262
+ theme,
7263
+ viewport,
7264
+ effectivePersistentLayers,
7265
+ expandedPersistentLayers,
7266
+ registry,
7267
+ failureMode,
7268
+ onDiagnostic
7269
+ );
7270
+ const templateBlock = block;
7271
+ expandedInfos.push({
7272
+ block: expandedBlock,
7273
+ originalIndex,
7274
+ templateBlock,
7275
+ hasSourceTiming: typeof templateBlock.sourceStartTime === "number"
7276
+ });
6468
7277
  }
6469
- case "photoGrid": {
6470
- const images = extractImages(contents, 4);
6471
- if (images.length < 2) return placeholders ? {} : null;
6472
- return {
6473
- images: images.map((i) => ({ src: i.src, alt: i.alt })),
6474
- caption: headingText
6475
- };
7278
+ const hasAnySourceTiming = expandedInfos.some((info) => info.hasSourceTiming);
7279
+ expandedInfos.sort((a, b) => {
7280
+ if (hasAnySourceTiming) {
7281
+ const aIsHeader = a.templateBlock.template === "sectionHeader";
7282
+ const bIsHeader = b.templateBlock.template === "sectionHeader";
7283
+ if (aIsHeader && !bIsHeader) return -1;
7284
+ if (!aIsHeader && bIsHeader) return 1;
7285
+ if (a.hasSourceTiming && b.hasSourceTiming) {
7286
+ return a.templateBlock.sourceStartTime - b.templateBlock.sourceStartTime;
7287
+ }
7288
+ if (a.hasSourceTiming && !b.hasSourceTiming) return -1;
7289
+ if (!a.hasSourceTiming && b.hasSourceTiming) return 1;
7290
+ }
7291
+ return a.originalIndex - b.originalIndex;
7292
+ });
7293
+ let offsetWithinSegment = 0;
7294
+ for (let i = 0; i < expandedInfos.length; i++) {
7295
+ const info = expandedInfos[i];
7296
+ const { block: expandedBlock, templateBlock } = info;
7297
+ if (info.hasSourceTiming) {
7298
+ const sourceStart = templateBlock.sourceStartTime;
7299
+ expandedBlock.startTime = audioSegment.startTime + sourceStart;
7300
+ if (typeof templateBlock.sourceDuration === "number") {
7301
+ expandedBlock.duration = Math.max(5, templateBlock.sourceDuration);
7302
+ }
7303
+ offsetWithinSegment = sourceStart + expandedBlock.duration;
7304
+ } else if (templateBlock.template === "sectionHeader") {
7305
+ expandedBlock.startTime = audioSegment.startTime + offsetWithinSegment;
7306
+ offsetWithinSegment += expandedBlock.duration;
7307
+ } else {
7308
+ expandedBlock.startTime = audioSegment.startTime + offsetWithinSegment;
7309
+ const scaledDuration = expandedBlock.duration * scaleFactor;
7310
+ expandedBlock.duration = scaledDuration;
7311
+ offsetWithinSegment += scaledDuration;
7312
+ }
7313
+ expandedBlocks[info.originalIndex] = expandedBlock;
6476
7314
  }
6477
- case "leftFeature":
6478
- case "rightFeature": {
6479
- const img = extractFirstImage(contents);
6480
- if (!img && !placeholders) return null;
6481
- return {
6482
- imageSrc: img?.src ?? "",
6483
- imageAlt: img?.alt || headingText,
6484
- imageWidth: img?.width,
6485
- imageHeight: img?.height,
6486
- title: headingText,
6487
- body: bodyText3
6488
- };
7315
+ const segmentExpandedBlocks = expandedInfos.map((info) => expandedBlocks[info.originalIndex]).sort((a, b) => a.startTime - b.startTime);
7316
+ for (let i = 0; i < segmentExpandedBlocks.length - 1; i++) {
7317
+ const current = segmentExpandedBlocks[i];
7318
+ const next = segmentExpandedBlocks[i + 1];
7319
+ const currentEnd = current.startTime + current.duration;
7320
+ if (currentEnd > next.startTime) {
7321
+ current.duration = next.startTime - current.startTime;
7322
+ } else if (currentEnd < next.startTime - 0.5) {
7323
+ current.duration = Math.max(0.1, next.startTime - current.startTime);
7324
+ }
6489
7325
  }
6490
- case "diagram": {
6491
- const fences = (contents ?? []).filter((n) => n.type === "code");
6492
- const fence = fences.length === 1 && isEligibleAsciiFenceLang(fences[0].lang) ? fences[0] : void 0;
6493
- const detection = fence ? detectAsciiDiagram(fence.value, { explicit: isExplicitDiagramLang(fence.lang) }) : void 0;
6494
- if (!detection?.isDiagram || !detection.diagram) return placeholders ? {} : null;
6495
- const { nodes, edges } = asciiDiagramToTemplateData(detection.diagram);
6496
- return { nodes, edges, ...headingText ? { title: headingText } : {} };
7326
+ const MIN_TRANSITION_GAP = 5;
7327
+ const segmentEnd = audioSegment.startTime + audioSegment.duration;
7328
+ while (segmentExpandedBlocks.length > 1) {
7329
+ const lastBlock = segmentExpandedBlocks[segmentExpandedBlocks.length - 1];
7330
+ const timeFromLastToEnd = segmentEnd - lastBlock.startTime;
7331
+ if (timeFromLastToEnd < MIN_TRANSITION_GAP && lastBlock.template !== "sectionHeader") {
7332
+ const prevBlock = segmentExpandedBlocks[segmentExpandedBlocks.length - 2];
7333
+ prevBlock.duration = segmentEnd - prevBlock.startTime;
7334
+ lastBlock.duration = 0;
7335
+ lastBlock.startTime = segmentEnd;
7336
+ segmentExpandedBlocks.pop();
7337
+ } else {
7338
+ if (lastBlock.startTime + lastBlock.duration < segmentEnd) {
7339
+ lastBlock.duration = segmentEnd - lastBlock.startTime;
7340
+ }
7341
+ break;
7342
+ }
6497
7343
  }
6498
- case "timeline": {
6499
- const fences = (contents ?? []).filter((n) => n.type === "code");
6500
- const fence = fences.length === 1 && isEligibleAsciiTimelineFenceLang(fences[0].lang) ? fences[0] : void 0;
6501
- const detection = fence ? detectAsciiTimeline(fence.value, { explicit: true }) : void 0;
6502
- if (!detection?.isTimeline || !detection.timeline) return placeholders ? {} : null;
6503
- const { tracks, links } = asciiTimelineToTemplateData(detection.timeline);
6504
- return { tracks, links, ...headingText ? { title: headingText } : {} };
7344
+ if (segmentExpandedBlocks.length === 1) {
7345
+ const onlyBlock = segmentExpandedBlocks[0];
7346
+ if (onlyBlock.startTime + onlyBlock.duration < segmentEnd) {
7347
+ onlyBlock.duration = segmentEnd - onlyBlock.startTime;
7348
+ }
6505
7349
  }
6506
- case "tree": {
6507
- const fences = (contents ?? []).filter((n) => n.type === "code");
6508
- const fence = fences.length === 1 && isEligibleTreeFenceLang(fences[0].lang) ? fences[0] : void 0;
6509
- if (fence) {
6510
- const detection = detectTree(fence.value, { explicit: isExplicitTreeLang(fence.lang) });
6511
- if (detection.isTree && detection.tree) {
6512
- const { items } = treeToTemplateData(detection.tree);
6513
- return { items, ...headingText ? { title: headingText } : {} };
7350
+ let changed = true;
7351
+ while (changed) {
7352
+ changed = false;
7353
+ for (let i = segmentExpandedBlocks.length - 1; i >= 1; i--) {
7354
+ const block = segmentExpandedBlocks[i];
7355
+ if (block.duration > 0 && block.duration < MIN_TRANSITION_GAP) {
7356
+ const prev = segmentExpandedBlocks[i - 1];
7357
+ const blockEnd = block.startTime + block.duration;
7358
+ prev.duration = blockEnd - prev.startTime;
7359
+ block.duration = 0;
7360
+ block.startTime = segmentEnd;
7361
+ segmentExpandedBlocks.splice(i, 1);
7362
+ changed = true;
7363
+ break;
6514
7364
  }
6515
7365
  }
6516
- const list = findFirstList(contents);
6517
- if (list) {
6518
- const { items } = treeToTemplateData(treeFromMarkdownList(list));
6519
- if (items.length > 0) return { items, ...headingText ? { title: headingText } : {} };
7366
+ }
7367
+ const MAX_BLOCK_DURATION = 20;
7368
+ for (let i = 0; i < segmentExpandedBlocks.length; i++) {
7369
+ const block = segmentExpandedBlocks[i];
7370
+ if (block.duration > MAX_BLOCK_DURATION) {
7371
+ const numParts = Math.ceil(block.duration / MAX_BLOCK_DURATION);
7372
+ const partDuration = block.duration / numParts;
7373
+ if (partDuration >= MIN_TRANSITION_GAP) {
7374
+ block.duration = partDuration;
7375
+ for (let p = 1; p < numParts; p++) {
7376
+ const splitBlock = {
7377
+ id: `${block.id}-split-${p}`,
7378
+ startTime: block.startTime + p * partDuration,
7379
+ duration: partDuration,
7380
+ audioSegment: block.audioSegment,
7381
+ layers: (block.layers ?? []).map((layer) => ({
7382
+ ...cloneData(layer),
7383
+ id: `${layer.id}-split-${p}`
7384
+ })),
7385
+ transition: { type: "dissolve", duration: 1 },
7386
+ template: block.template
7387
+ };
7388
+ expandedBlocks.push(splitBlock);
7389
+ }
7390
+ }
6520
7391
  }
6521
- return placeholders ? {} : null;
6522
7392
  }
6523
- default:
6524
- return placeholders ? {} : null;
6525
7393
  }
7394
+ return expandedBlocks.filter((block) => block && block.duration > 0);
7395
+ }
7396
+ function getAvailableTemplates() {
7397
+ return Object.keys(templateRegistry);
7398
+ }
7399
+ function hasTemplate(name) {
7400
+ return resolveTemplateName(name) in templateRegistry;
6526
7401
  }
6527
7402
 
6528
7403
  // src/doc/customTemplatesFrontmatter.ts
@@ -6602,15 +7477,6 @@ function renameKeys(value, map) {
6602
7477
  function isDefaultViewport(v) {
6603
7478
  return !v || v.width === DEFAULT_VIEWPORT.width && v.height === DEFAULT_VIEWPORT.height;
6604
7479
  }
6605
- function base64ToUtf8(b64) {
6606
- if (typeof globalThis.atob === "function") {
6607
- const binary = globalThis.atob(b64);
6608
- const bytes = new Uint8Array(binary.length);
6609
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
6610
- return new TextDecoder().decode(bytes);
6611
- }
6612
- return Buffer.from(b64, "base64").toString("utf-8");
6613
- }
6614
7480
  function readCustomTemplatesFromFrontmatter(frontmatter) {
6615
7481
  if (!frontmatter) return void 0;
6616
7482
  const candidates = normalizeCandidates(frontmatter[FRONTMATTER_CUSTOM_TEMPLATES_KEY]);
@@ -7090,10 +7956,18 @@ function expandStandaloneAnnotations(blocks, makeId, defaultDuration) {
7090
7956
  makeId,
7091
7957
  defaultDuration
7092
7958
  });
7093
- if (produced.length > 0) {
7094
- block.contents = leading;
7959
+ if (produced.length === 0) {
7960
+ out.push(block);
7961
+ continue;
7962
+ }
7963
+ block.contents = leading;
7964
+ const nestable = (block.children?.length ?? 0) > 0 && !isContainerTemplate(block.template);
7965
+ if (nestable) {
7966
+ block.children = [...produced, ...block.children];
7967
+ out.push(block);
7968
+ } else {
7969
+ out.push(block, ...produced);
7095
7970
  }
7096
- out.push(block, ...produced);
7097
7971
  }
7098
7972
  return out;
7099
7973
  }
@@ -7465,8 +8339,10 @@ function applyAutoTemplates(blocks, resolvedDefault, state) {
7465
8339
  if (block.sourceHeading && !annotated && block.template === resolvedDefault) {
7466
8340
  const profile = profileBlockContents(block.contents ?? []);
7467
8341
  const picked = pickAutoTemplate(profile, state.featureIndex);
7468
- if (picked) {
7469
- const inputs = deriveTemplateInputs(picked, block.title ?? "", block.contents);
8342
+ if (picked && autoTemplatePreservesContent(picked, block.contents)) {
8343
+ const inputs = deriveTemplateInputs(picked, block.title ?? "", block.contents, {
8344
+ preserveSourceHeading: true
8345
+ });
7470
8346
  if (inputs) {
7471
8347
  if (picked === "leftFeature" || picked === "rightFeature") state.featureIndex += 1;
7472
8348
  block.template = picked;
@@ -7814,7 +8690,11 @@ function buildNarrationTimingJson(script, alignment, durationSec, options) {
7814
8690
  duration: Number.isFinite(durationSec) && durationSec >= 0 ? durationSec : 0,
7815
8691
  bookmarks,
7816
8692
  blocks,
7817
- ...options?.cameraOffsetSec !== void 0 ? { cameraOffsetSec: options.cameraOffsetSec } : {},
8693
+ // Write only what `parseNarrationTimingJson` will read back, so the codec
8694
+ // is a round-trip pair. A non-finite value would serialize to `null`
8695
+ // (JSON has no NaN) and be dropped on parse anyway — omitting it here
8696
+ // keeps the emitted sidecar honest rather than carrying a dead field.
8697
+ ...finiteNumber(options?.cameraOffsetSec) ? { cameraOffsetSec: options.cameraOffsetSec } : {},
7818
8698
  generator: {
7819
8699
  name: "squisq-teleprompter",
7820
8700
  method: "dsp-align",
@@ -7825,6 +8705,9 @@ function buildNarrationTimingJson(script, alignment, durationSec, options) {
7825
8705
  function finiteNonNegative(value) {
7826
8706
  return typeof value === "number" && Number.isFinite(value) && value >= 0;
7827
8707
  }
8708
+ function finiteNumber(value) {
8709
+ return typeof value === "number" && Number.isFinite(value);
8710
+ }
7828
8711
  function parseNarrationTimingJson(data) {
7829
8712
  try {
7830
8713
  const text = typeof data === "string" ? data : new TextDecoder().decode(data instanceof Uint8Array ? data : new Uint8Array(data));
@@ -7878,7 +8761,7 @@ function parseNarrationTimingJson(data) {
7878
8761
  duration: raw.duration,
7879
8762
  bookmarks,
7880
8763
  blocks,
7881
- ...finiteNonNegative(raw.cameraOffsetSec) ? { cameraOffsetSec: raw.cameraOffsetSec } : {},
8764
+ ...finiteNumber(raw.cameraOffsetSec) ? { cameraOffsetSec: raw.cameraOffsetSec } : {},
7882
8765
  ...typeof raw.generator === "object" && raw.generator !== null ? { generator: raw.generator } : {}
7883
8766
  };
7884
8767
  } catch {
@@ -7908,11 +8791,11 @@ export {
7908
8791
  themedScrim,
7909
8792
  getTemplateHint,
7910
8793
  titleBlock,
7911
- sectionHeader,
7912
8794
  DEFAULT_LAYOUT,
7913
8795
  getAccentLayout,
7914
8796
  createAccentLayers,
7915
8797
  adjustY,
8798
+ sectionHeader,
7916
8799
  statHighlight,
7917
8800
  quoteBlock,
7918
8801
  factCard,
@@ -7957,13 +8840,6 @@ export {
7957
8840
  coerceTemplateParams,
7958
8841
  lintTemplateParams,
7959
8842
  fallbackBlockLayers,
7960
- materializeBlockLayers,
7961
- TEMPLATE_METADATA,
7962
- coverBlock,
7963
- expandCoverBlock,
7964
- expandDocBlocks,
7965
- getAvailableTemplates,
7966
- hasTemplate,
7967
8843
  ASCII_CHAR_W,
7968
8844
  ASCII_CHAR_H,
7969
8845
  asciiCellToCanvas,
@@ -7980,9 +8856,21 @@ export {
7980
8856
  extractListItems,
7981
8857
  extractImages,
7982
8858
  extractFirstImage,
8859
+ extractEmbeddedVideos,
8860
+ extractFirstEmbeddedVideo,
7983
8861
  extractTableFromContents,
7984
8862
  extractBlockquoteText,
8863
+ autoTemplatePreservesContent,
7985
8864
  deriveTemplateInputs,
8865
+ BLOCK_MEDIA_LAYOUT_POLICIES,
8866
+ getBlockMediaLayoutPolicy,
8867
+ materializeBlockLayers,
8868
+ TEMPLATE_METADATA,
8869
+ coverBlock,
8870
+ expandCoverBlock,
8871
+ expandDocBlocks,
8872
+ getAvailableTemplates,
8873
+ hasTemplate,
7986
8874
  readCustomTemplatesFromFrontmatter,
7987
8875
  writeCustomTemplatesToFrontmatter,
7988
8876
  readCustomThemesFromFrontmatter,
@@ -8010,4 +8898,3 @@ export {
8010
8898
  buildNarrationTimingJson,
8011
8899
  parseNarrationTimingJson
8012
8900
  };
8013
- //# sourceMappingURL=chunk-JI77MGAK.js.map