@bendyline/squisq 1.2.2 → 1.4.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 (427) hide show
  1. package/dist/{Doc-BfBhiCNr.d.ts → Doc-BeSeH2iX.d.ts} +141 -33
  2. package/dist/ImageEditDoc-BTIvw0oq.d.ts +84 -0
  3. package/dist/__tests__/colorUtils.test.d.ts +2 -0
  4. package/dist/__tests__/colorUtils.test.d.ts.map +1 -0
  5. package/dist/__tests__/colorUtils.test.js +94 -0
  6. package/dist/__tests__/colorUtils.test.js.map +1 -0
  7. package/dist/__tests__/getLayers.test.js +31 -7
  8. package/dist/__tests__/getLayers.test.js.map +1 -1
  9. package/dist/__tests__/iconResolve.test.d.ts +2 -0
  10. package/dist/__tests__/iconResolve.test.d.ts.map +1 -0
  11. package/dist/__tests__/iconResolve.test.js +99 -0
  12. package/dist/__tests__/iconResolve.test.js.map +1 -0
  13. package/dist/__tests__/imageEditState.test.d.ts +2 -0
  14. package/dist/__tests__/imageEditState.test.d.ts.map +1 -0
  15. package/dist/__tests__/imageEditState.test.js +61 -0
  16. package/dist/__tests__/imageEditState.test.js.map +1 -0
  17. package/dist/__tests__/imageEditVersions.test.d.ts +2 -0
  18. package/dist/__tests__/imageEditVersions.test.d.ts.map +1 -0
  19. package/dist/__tests__/imageEditVersions.test.js +141 -0
  20. package/dist/__tests__/imageEditVersions.test.js.map +1 -0
  21. package/dist/__tests__/inferDocumentTitle.test.d.ts +2 -0
  22. package/dist/__tests__/inferDocumentTitle.test.d.ts.map +1 -0
  23. package/dist/__tests__/inferDocumentTitle.test.js +42 -0
  24. package/dist/__tests__/inferDocumentTitle.test.js.map +1 -0
  25. package/dist/__tests__/jsonForm.chooseControl.test.d.ts +2 -0
  26. package/dist/__tests__/jsonForm.chooseControl.test.d.ts.map +1 -0
  27. package/dist/__tests__/jsonForm.chooseControl.test.js +74 -0
  28. package/dist/__tests__/jsonForm.chooseControl.test.js.map +1 -0
  29. package/dist/__tests__/jsonForm.evaluateWhen.test.d.ts +2 -0
  30. package/dist/__tests__/jsonForm.evaluateWhen.test.d.ts.map +1 -0
  31. package/dist/__tests__/jsonForm.evaluateWhen.test.js +51 -0
  32. package/dist/__tests__/jsonForm.evaluateWhen.test.js.map +1 -0
  33. package/dist/__tests__/jsonForm.inferSchema.test.d.ts +2 -0
  34. package/dist/__tests__/jsonForm.inferSchema.test.d.ts.map +1 -0
  35. package/dist/__tests__/jsonForm.inferSchema.test.js +23 -0
  36. package/dist/__tests__/jsonForm.inferSchema.test.js.map +1 -0
  37. package/dist/__tests__/jsonForm.pointer.test.d.ts +2 -0
  38. package/dist/__tests__/jsonForm.pointer.test.d.ts.map +1 -0
  39. package/dist/__tests__/jsonForm.pointer.test.js +94 -0
  40. package/dist/__tests__/jsonForm.pointer.test.js.map +1 -0
  41. package/dist/__tests__/markdown.test.js +40 -0
  42. package/dist/__tests__/markdown.test.js.map +1 -1
  43. package/dist/__tests__/markdownIcons.test.d.ts +2 -0
  44. package/dist/__tests__/markdownIcons.test.d.ts.map +1 -0
  45. package/dist/__tests__/markdownIcons.test.js +93 -0
  46. package/dist/__tests__/markdownIcons.test.js.map +1 -0
  47. package/dist/__tests__/markdownToDoc.test.js +15 -4
  48. package/dist/__tests__/markdownToDoc.test.js.map +1 -1
  49. package/dist/__tests__/recommendTemplates.test.d.ts +2 -0
  50. package/dist/__tests__/recommendTemplates.test.d.ts.map +1 -0
  51. package/dist/__tests__/recommendTemplates.test.js +158 -0
  52. package/dist/__tests__/recommendTemplates.test.js.map +1 -0
  53. package/dist/__tests__/scopedContentContainer.test.d.ts +2 -0
  54. package/dist/__tests__/scopedContentContainer.test.d.ts.map +1 -0
  55. package/dist/__tests__/scopedContentContainer.test.js +74 -0
  56. package/dist/__tests__/scopedContentContainer.test.js.map +1 -0
  57. package/dist/__tests__/slideshowGenerator.test.js +1 -1
  58. package/dist/__tests__/slideshowGenerator.test.js.map +1 -1
  59. package/dist/__tests__/surface.test.d.ts +2 -0
  60. package/dist/__tests__/surface.test.d.ts.map +1 -0
  61. package/dist/__tests__/surface.test.js +57 -0
  62. package/dist/__tests__/surface.test.js.map +1 -0
  63. package/dist/__tests__/templateAnnotationParse.test.d.ts +2 -0
  64. package/dist/__tests__/templateAnnotationParse.test.d.ts.map +1 -0
  65. package/dist/__tests__/templateAnnotationParse.test.js +42 -0
  66. package/dist/__tests__/templateAnnotationParse.test.js.map +1 -0
  67. package/dist/__tests__/templates.test.js +90 -9
  68. package/dist/__tests__/templates.test.js.map +1 -1
  69. package/dist/__tests__/themeCompile.test.d.ts +2 -0
  70. package/dist/__tests__/themeCompile.test.d.ts.map +1 -0
  71. package/dist/__tests__/themeCompile.test.js +134 -0
  72. package/dist/__tests__/themeCompile.test.js.map +1 -0
  73. package/dist/__tests__/themeValidator.test.d.ts +2 -0
  74. package/dist/__tests__/themeValidator.test.d.ts.map +1 -0
  75. package/dist/__tests__/themeValidator.test.js +107 -0
  76. package/dist/__tests__/themeValidator.test.js.map +1 -0
  77. package/dist/__tests__/versions.test.d.ts +2 -0
  78. package/dist/__tests__/versions.test.d.ts.map +1 -0
  79. package/dist/__tests__/versions.test.js +339 -0
  80. package/dist/__tests__/versions.test.js.map +1 -0
  81. package/dist/{chunk-CYQHG27J.js → chunk-2TMAWB4Y.js} +74 -1
  82. package/dist/chunk-2TMAWB4Y.js.map +1 -0
  83. package/dist/{chunk-LAZELBON.js → chunk-3K5OG6KI.js} +165 -17
  84. package/dist/chunk-3K5OG6KI.js.map +1 -0
  85. package/dist/chunk-57CVQCFX.js +193 -0
  86. package/dist/chunk-57CVQCFX.js.map +1 -0
  87. package/dist/{chunk-QM5PKNPW.js → chunk-5WFRKURX.js} +1 -1
  88. package/dist/chunk-5WFRKURX.js.map +1 -0
  89. package/dist/chunk-B4EEON3N.js +1317 -0
  90. package/dist/chunk-B4EEON3N.js.map +1 -0
  91. package/dist/chunk-DBS6RIFL.js +81 -0
  92. package/dist/chunk-DBS6RIFL.js.map +1 -0
  93. package/dist/{chunk-MIBRIRGJ.js → chunk-FR2RBTKO.js} +3 -3
  94. package/dist/{chunk-MIBRIRGJ.js.map → chunk-FR2RBTKO.js.map} +1 -1
  95. package/dist/chunk-H3AXU4MJ.js +179 -0
  96. package/dist/chunk-H3AXU4MJ.js.map +1 -0
  97. package/dist/chunk-JELIRVDP.js +24 -0
  98. package/dist/chunk-JELIRVDP.js.map +1 -0
  99. package/dist/chunk-K32VJONL.js +193 -0
  100. package/dist/chunk-K32VJONL.js.map +1 -0
  101. package/dist/{chunk-GAQLMORN.js → chunk-KSWJR66U.js} +248 -44
  102. package/dist/chunk-KSWJR66U.js.map +1 -0
  103. package/dist/{chunk-TSG2OSE6.js → chunk-MYIH7FWD.js} +3 -3
  104. package/dist/chunk-MYIH7FWD.js.map +1 -0
  105. package/dist/chunk-VLJJHDAC.js +218 -0
  106. package/dist/chunk-VLJJHDAC.js.map +1 -0
  107. package/dist/chunk-WMOI7FN3.js +182 -0
  108. package/dist/chunk-WMOI7FN3.js.map +1 -0
  109. package/dist/chunk-ZTX4PFFT.js +520 -0
  110. package/dist/chunk-ZTX4PFFT.js.map +1 -0
  111. package/dist/doc/audioMapping.d.ts.map +1 -1
  112. package/dist/doc/audioMapping.js +80 -9
  113. package/dist/doc/audioMapping.js.map +1 -1
  114. package/dist/doc/getLayers.d.ts.map +1 -1
  115. package/dist/doc/getLayers.js +29 -18
  116. package/dist/doc/getLayers.js.map +1 -1
  117. package/dist/doc/markdownToDoc.d.ts.map +1 -1
  118. package/dist/doc/markdownToDoc.js +40 -6
  119. package/dist/doc/markdownToDoc.js.map +1 -1
  120. package/dist/doc/templates/featureBlock.d.ts +18 -0
  121. package/dist/doc/templates/featureBlock.d.ts.map +1 -0
  122. package/dist/doc/templates/featureBlock.js +180 -0
  123. package/dist/doc/templates/featureBlock.js.map +1 -0
  124. package/dist/doc/templates/fullBleedQuote.d.ts +12 -0
  125. package/dist/doc/templates/fullBleedQuote.d.ts.map +1 -1
  126. package/dist/doc/templates/fullBleedQuote.js +12 -0
  127. package/dist/doc/templates/fullBleedQuote.js.map +1 -1
  128. package/dist/doc/templates/index.d.ts +28 -4
  129. package/dist/doc/templates/index.d.ts.map +1 -1
  130. package/dist/doc/templates/index.js +45 -12
  131. package/dist/doc/templates/index.js.map +1 -1
  132. package/dist/doc/templates/listBlock.d.ts.map +1 -1
  133. package/dist/doc/templates/listBlock.js +22 -5
  134. package/dist/doc/templates/listBlock.js.map +1 -1
  135. package/dist/doc/templates/statHighlight.d.ts +12 -0
  136. package/dist/doc/templates/statHighlight.d.ts.map +1 -1
  137. package/dist/doc/templates/statHighlight.js +12 -0
  138. package/dist/doc/templates/statHighlight.js.map +1 -1
  139. package/dist/doc/templates/titleBlock.d.ts +11 -0
  140. package/dist/doc/templates/titleBlock.d.ts.map +1 -1
  141. package/dist/doc/templates/titleBlock.js +8 -0
  142. package/dist/doc/templates/titleBlock.js.map +1 -1
  143. package/dist/doc/utils/themeUtils.d.ts +4 -4
  144. package/dist/doc/utils/themeUtils.d.ts.map +1 -1
  145. package/dist/doc/utils/themeUtils.js +8 -7
  146. package/dist/doc/utils/themeUtils.js.map +1 -1
  147. package/dist/generate/slideshowGenerator.js +1 -1
  148. package/dist/generate/slideshowGenerator.js.map +1 -1
  149. package/dist/generate/templateMapper.js +2 -2
  150. package/dist/generate/templateMapper.js.map +1 -1
  151. package/dist/icons/iconData.d.ts +26 -0
  152. package/dist/icons/iconData.d.ts.map +1 -0
  153. package/dist/icons/iconData.js +12 -0
  154. package/dist/icons/iconData.js.map +1 -0
  155. package/dist/icons/index.d.ts +12 -0
  156. package/dist/icons/index.d.ts.map +1 -0
  157. package/dist/icons/index.js +10 -0
  158. package/dist/icons/index.js.map +1 -0
  159. package/dist/icons/resolve.d.ts +61 -0
  160. package/dist/icons/resolve.d.ts.map +1 -0
  161. package/dist/icons/resolve.js +130 -0
  162. package/dist/icons/resolve.js.map +1 -0
  163. package/dist/imageEdit/ImageEditVersionManager.d.ts +31 -0
  164. package/dist/imageEdit/ImageEditVersionManager.d.ts.map +1 -0
  165. package/dist/imageEdit/ImageEditVersionManager.js +39 -0
  166. package/dist/imageEdit/ImageEditVersionManager.js.map +1 -0
  167. package/dist/imageEdit/export.d.ts +33 -0
  168. package/dist/imageEdit/export.d.ts.map +1 -0
  169. package/dist/imageEdit/export.js +253 -0
  170. package/dist/imageEdit/export.js.map +1 -0
  171. package/dist/imageEdit/index.d.ts +14 -0
  172. package/dist/imageEdit/index.d.ts.map +1 -0
  173. package/dist/imageEdit/index.js +22 -0
  174. package/dist/imageEdit/index.js.map +1 -0
  175. package/dist/imageEdit/persistence.d.ts +15 -0
  176. package/dist/imageEdit/persistence.d.ts.map +1 -0
  177. package/dist/imageEdit/persistence.js +49 -0
  178. package/dist/imageEdit/persistence.js.map +1 -0
  179. package/dist/imageEdit/state.d.ts +42 -0
  180. package/dist/imageEdit/state.d.ts.map +1 -0
  181. package/dist/imageEdit/state.js +98 -0
  182. package/dist/imageEdit/state.js.map +1 -0
  183. package/dist/imageEdit/versionPaths.d.ts +27 -0
  184. package/dist/imageEdit/versionPaths.d.ts.map +1 -0
  185. package/dist/imageEdit/versionPaths.js +57 -0
  186. package/dist/imageEdit/versionPaths.js.map +1 -0
  187. package/dist/imageEdit/versions.d.ts +67 -0
  188. package/dist/imageEdit/versions.d.ts.map +1 -0
  189. package/dist/imageEdit/versions.js +161 -0
  190. package/dist/imageEdit/versions.js.map +1 -0
  191. package/dist/index.d.ts +4 -0
  192. package/dist/index.d.ts.map +1 -1
  193. package/dist/index.js +4 -0
  194. package/dist/index.js.map +1 -1
  195. package/dist/jsonForm/chooseControl.d.ts +20 -0
  196. package/dist/jsonForm/chooseControl.d.ts.map +1 -0
  197. package/dist/jsonForm/chooseControl.js +82 -0
  198. package/dist/jsonForm/chooseControl.js.map +1 -0
  199. package/dist/jsonForm/evaluateWhen.d.ts +12 -0
  200. package/dist/jsonForm/evaluateWhen.d.ts.map +1 -0
  201. package/dist/jsonForm/evaluateWhen.js +65 -0
  202. package/dist/jsonForm/evaluateWhen.js.map +1 -0
  203. package/dist/jsonForm/index.d.ts +14 -0
  204. package/dist/jsonForm/index.d.ts.map +1 -0
  205. package/dist/jsonForm/index.js +12 -0
  206. package/dist/jsonForm/index.js.map +1 -0
  207. package/dist/jsonForm/inferSchema.d.ts +12 -0
  208. package/dist/jsonForm/inferSchema.d.ts.map +1 -0
  209. package/dist/jsonForm/inferSchema.js +15 -0
  210. package/dist/jsonForm/inferSchema.js.map +1 -0
  211. package/dist/jsonForm/pointer.d.ts +26 -0
  212. package/dist/jsonForm/pointer.d.ts.map +1 -0
  213. package/dist/jsonForm/pointer.js +102 -0
  214. package/dist/jsonForm/pointer.js.map +1 -0
  215. package/dist/jsonForm/types.d.ts +120 -0
  216. package/dist/jsonForm/types.d.ts.map +1 -0
  217. package/dist/jsonForm/types.js +10 -0
  218. package/dist/jsonForm/types.js.map +1 -0
  219. package/dist/markdown/convert.d.ts +11 -1
  220. package/dist/markdown/convert.d.ts.map +1 -1
  221. package/dist/markdown/convert.js +235 -15
  222. package/dist/markdown/convert.js.map +1 -1
  223. package/dist/markdown/index.d.ts +2 -2
  224. package/dist/markdown/index.d.ts.map +1 -1
  225. package/dist/markdown/index.js +1 -1
  226. package/dist/markdown/index.js.map +1 -1
  227. package/dist/markdown/types.d.ts +34 -1
  228. package/dist/markdown/types.d.ts.map +1 -1
  229. package/dist/markdown/utils.d.ts +40 -0
  230. package/dist/markdown/utils.d.ts.map +1 -1
  231. package/dist/markdown/utils.js +155 -0
  232. package/dist/markdown/utils.js.map +1 -1
  233. package/dist/recommend/index.d.ts +16 -0
  234. package/dist/recommend/index.d.ts.map +1 -0
  235. package/dist/recommend/index.js +15 -0
  236. package/dist/recommend/index.js.map +1 -0
  237. package/dist/recommend/templates.d.ts +39 -0
  238. package/dist/recommend/templates.d.ts.map +1 -0
  239. package/dist/recommend/templates.js +235 -0
  240. package/dist/recommend/templates.js.map +1 -0
  241. package/dist/schemas/BlockTemplates.d.ts +46 -8
  242. package/dist/schemas/BlockTemplates.d.ts.map +1 -1
  243. package/dist/schemas/BlockTemplates.js +3 -3
  244. package/dist/schemas/BlockTemplates.js.map +1 -1
  245. package/dist/schemas/ImageEditDoc.d.ts +81 -0
  246. package/dist/schemas/ImageEditDoc.d.ts.map +1 -0
  247. package/dist/schemas/ImageEditDoc.js +20 -0
  248. package/dist/schemas/ImageEditDoc.js.map +1 -0
  249. package/dist/schemas/Theme.d.ts +93 -23
  250. package/dist/schemas/Theme.d.ts.map +1 -1
  251. package/dist/schemas/Theme.js +80 -13
  252. package/dist/schemas/Theme.js.map +1 -1
  253. package/dist/schemas/colorUtils.d.ts +51 -0
  254. package/dist/schemas/colorUtils.d.ts.map +1 -0
  255. package/dist/schemas/colorUtils.js +154 -0
  256. package/dist/schemas/colorUtils.js.map +1 -0
  257. package/dist/schemas/fontStacks.d.ts +57 -0
  258. package/dist/schemas/fontStacks.d.ts.map +1 -0
  259. package/dist/schemas/fontStacks.js +227 -0
  260. package/dist/schemas/fontStacks.js.map +1 -0
  261. package/dist/schemas/index.d.ts +5 -0
  262. package/dist/schemas/index.d.ts.map +1 -1
  263. package/dist/schemas/index.js +4 -0
  264. package/dist/schemas/index.js.map +1 -1
  265. package/dist/schemas/themeCompile.d.ts +45 -0
  266. package/dist/schemas/themeCompile.d.ts.map +1 -0
  267. package/dist/schemas/themeCompile.js +160 -0
  268. package/dist/schemas/themeCompile.js.map +1 -0
  269. package/dist/schemas/themeLibrary.d.ts +11 -15
  270. package/dist/schemas/themeLibrary.d.ts.map +1 -1
  271. package/dist/schemas/themeLibrary.js +18 -499
  272. package/dist/schemas/themeLibrary.js.map +1 -1
  273. package/dist/schemas/themeValidator.d.ts +26 -0
  274. package/dist/schemas/themeValidator.d.ts.map +1 -0
  275. package/dist/schemas/themeValidator.js +352 -0
  276. package/dist/schemas/themeValidator.js.map +1 -0
  277. package/dist/schemas/themes/bold.json +48 -0
  278. package/dist/schemas/themes/cinematic.json +54 -0
  279. package/dist/schemas/themes/documentary.json +49 -0
  280. package/dist/schemas/themes/gezellig.json +44 -0
  281. package/dist/schemas/themes/index.d.ts +26 -0
  282. package/dist/schemas/themes/index.d.ts.map +1 -0
  283. package/dist/schemas/themes/index.js +49 -0
  284. package/dist/schemas/themes/index.js.map +1 -0
  285. package/dist/schemas/themes/magazine.json +44 -0
  286. package/dist/schemas/themes/minimalist.json +45 -0
  287. package/dist/schemas/themes/morning-light.json +42 -0
  288. package/dist/schemas/themes/standard-dark.json +50 -0
  289. package/dist/schemas/themes/standard.json +42 -0
  290. package/dist/schemas/themes/tech-dark.json +48 -0
  291. package/dist/schemas/themes/warm-earth.json +50 -0
  292. package/dist/storage/ScopedContentContainer.d.ts +44 -0
  293. package/dist/storage/ScopedContentContainer.d.ts.map +1 -0
  294. package/dist/storage/ScopedContentContainer.js +94 -0
  295. package/dist/storage/ScopedContentContainer.js.map +1 -0
  296. package/dist/storage/index.d.ts +1 -0
  297. package/dist/storage/index.d.ts.map +1 -1
  298. package/dist/storage/index.js +1 -0
  299. package/dist/storage/index.js.map +1 -1
  300. package/dist/story/index.d.ts +53 -13
  301. package/dist/story/index.js +12 -4
  302. package/dist/themeLibrary-BHjJ0Qx6.d.ts +40 -0
  303. package/dist/{types-DYAzisMG.d.ts → types-BdZkdJ3z.d.ts} +35 -2
  304. package/dist/types-D98li1gV.d.ts +93 -0
  305. package/dist/versions/DocumentVersionManager.d.ts +31 -0
  306. package/dist/versions/DocumentVersionManager.d.ts.map +1 -0
  307. package/dist/versions/DocumentVersionManager.js +33 -0
  308. package/dist/versions/DocumentVersionManager.js.map +1 -0
  309. package/dist/versions/index.d.ts +29 -0
  310. package/dist/versions/index.d.ts.map +1 -0
  311. package/dist/versions/index.js +28 -0
  312. package/dist/versions/index.js.map +1 -0
  313. package/dist/versions/operations.d.ts +44 -0
  314. package/dist/versions/operations.d.ts.map +1 -0
  315. package/dist/versions/operations.js +190 -0
  316. package/dist/versions/operations.js.map +1 -0
  317. package/dist/versions/paths.d.ts +29 -0
  318. package/dist/versions/paths.d.ts.map +1 -0
  319. package/dist/versions/paths.js +66 -0
  320. package/dist/versions/paths.js.map +1 -0
  321. package/dist/versions/timestamp.d.ts +22 -0
  322. package/dist/versions/timestamp.d.ts.map +1 -0
  323. package/dist/versions/timestamp.js +44 -0
  324. package/dist/versions/timestamp.js.map +1 -0
  325. package/dist/versions/types.d.ts +92 -0
  326. package/dist/versions/types.d.ts.map +1 -0
  327. package/dist/versions/types.js +11 -0
  328. package/dist/versions/types.js.map +1 -0
  329. package/package.json +28 -1
  330. package/src/__tests__/colorUtils.test.ts +112 -0
  331. package/src/__tests__/getLayers.test.ts +32 -7
  332. package/src/__tests__/iconResolve.test.ts +122 -0
  333. package/src/__tests__/imageEditState.test.ts +74 -0
  334. package/src/__tests__/imageEditVersions.test.ts +169 -0
  335. package/src/__tests__/inferDocumentTitle.test.ts +50 -0
  336. package/src/__tests__/jsonForm.chooseControl.test.ts +86 -0
  337. package/src/__tests__/jsonForm.evaluateWhen.test.ts +59 -0
  338. package/src/__tests__/jsonForm.inferSchema.test.ts +25 -0
  339. package/src/__tests__/jsonForm.pointer.test.ts +103 -0
  340. package/src/__tests__/markdown.test.ts +44 -0
  341. package/src/__tests__/markdownIcons.test.ts +104 -0
  342. package/src/__tests__/markdownToDoc.test.ts +19 -4
  343. package/src/__tests__/recommendTemplates.test.ts +183 -0
  344. package/src/__tests__/scopedContentContainer.test.ts +89 -0
  345. package/src/__tests__/slideshowGenerator.test.ts +1 -1
  346. package/src/__tests__/surface.test.ts +68 -0
  347. package/src/__tests__/templateAnnotationParse.test.ts +42 -0
  348. package/src/__tests__/templates.test.ts +93 -9
  349. package/src/__tests__/themeCompile.test.ts +156 -0
  350. package/src/__tests__/themeValidator.test.ts +125 -0
  351. package/src/__tests__/versions.test.ts +415 -0
  352. package/src/doc/audioMapping.ts +95 -13
  353. package/src/doc/getLayers.ts +32 -21
  354. package/src/doc/markdownToDoc.ts +45 -7
  355. package/src/doc/templates/featureBlock.ts +202 -0
  356. package/src/doc/templates/fullBleedQuote.ts +13 -0
  357. package/src/doc/templates/index.ts +50 -16
  358. package/src/doc/templates/listBlock.ts +24 -5
  359. package/src/doc/templates/statHighlight.ts +13 -0
  360. package/src/doc/templates/titleBlock.ts +9 -0
  361. package/src/doc/utils/themeUtils.ts +8 -7
  362. package/src/generate/slideshowGenerator.ts +1 -1
  363. package/src/generate/templateMapper.ts +2 -2
  364. package/src/icons/iconData.ts +31 -0
  365. package/src/icons/index.ts +18 -0
  366. package/src/icons/resolve.ts +139 -0
  367. package/src/imageEdit/ImageEditVersionManager.ts +73 -0
  368. package/src/imageEdit/export.ts +326 -0
  369. package/src/imageEdit/index.ts +71 -0
  370. package/src/imageEdit/persistence.ts +62 -0
  371. package/src/imageEdit/state.ts +112 -0
  372. package/src/imageEdit/versionPaths.ts +57 -0
  373. package/src/imageEdit/versions.ts +231 -0
  374. package/src/index.ts +4 -0
  375. package/src/jsonForm/chooseControl.ts +88 -0
  376. package/src/jsonForm/evaluateWhen.ts +59 -0
  377. package/src/jsonForm/index.ts +22 -0
  378. package/src/jsonForm/inferSchema.ts +25 -0
  379. package/src/jsonForm/pointer.ts +109 -0
  380. package/src/jsonForm/types.ts +167 -0
  381. package/src/markdown/convert.ts +248 -15
  382. package/src/markdown/index.ts +4 -0
  383. package/src/markdown/types.ts +38 -1
  384. package/src/markdown/utils.ts +159 -0
  385. package/src/recommend/index.ts +16 -0
  386. package/src/recommend/templates.ts +273 -0
  387. package/src/schemas/BlockTemplates.ts +50 -7
  388. package/src/schemas/ImageEditDoc.ts +95 -0
  389. package/src/schemas/Theme.ts +152 -23
  390. package/src/schemas/colorUtils.ts +186 -0
  391. package/src/schemas/fontStacks.ts +257 -0
  392. package/src/schemas/index.ts +12 -0
  393. package/src/schemas/themeCompile.ts +203 -0
  394. package/src/schemas/themeLibrary.ts +18 -524
  395. package/src/schemas/themeValidator.ts +388 -0
  396. package/src/schemas/themes/bold.json +48 -0
  397. package/src/schemas/themes/cinematic.json +54 -0
  398. package/src/schemas/themes/documentary.json +49 -0
  399. package/src/schemas/themes/gezellig.json +44 -0
  400. package/src/schemas/themes/index.ts +53 -0
  401. package/src/schemas/themes/magazine.json +44 -0
  402. package/src/schemas/themes/minimalist.json +45 -0
  403. package/src/schemas/themes/morning-light.json +42 -0
  404. package/src/schemas/themes/standard-dark.json +50 -0
  405. package/src/schemas/themes/standard.json +42 -0
  406. package/src/schemas/themes/tech-dark.json +48 -0
  407. package/src/schemas/themes/warm-earth.json +50 -0
  408. package/src/storage/ScopedContentContainer.ts +113 -0
  409. package/src/storage/index.ts +1 -0
  410. package/src/versions/DocumentVersionManager.ts +70 -0
  411. package/src/versions/index.ts +54 -0
  412. package/src/versions/operations.ts +230 -0
  413. package/src/versions/paths.ts +66 -0
  414. package/src/versions/timestamp.ts +48 -0
  415. package/src/versions/types.ts +91 -0
  416. package/dist/chunk-53TZ2UTL.js +0 -470
  417. package/dist/chunk-53TZ2UTL.js.map +0 -1
  418. package/dist/chunk-7GF6RDED.js +0 -82
  419. package/dist/chunk-7GF6RDED.js.map +0 -1
  420. package/dist/chunk-CYQHG27J.js.map +0 -1
  421. package/dist/chunk-GAQLMORN.js.map +0 -1
  422. package/dist/chunk-LAZELBON.js.map +0 -1
  423. package/dist/chunk-QM5PKNPW.js.map +0 -1
  424. package/dist/chunk-TSG2OSE6.js.map +0 -1
  425. package/dist/chunk-XM4WSOHB.js +0 -92
  426. package/dist/chunk-XM4WSOHB.js.map +0 -1
  427. package/dist/themeLibrary-IP7EdyRq.d.ts +0 -44
@@ -28,10 +28,11 @@ import type {
28
28
  MarkdownBlockNode,
29
29
  MarkdownHeading,
30
30
  MarkdownNode,
31
- MarkdownImage,
31
+ HtmlNode,
32
32
  } from '../markdown/types.js';
33
33
  import { extractPlainText } from '../markdown/utils.js';
34
34
  import { estimateReadingTime } from '../timing/readingTime.js';
35
+ import { resolveTemplateName } from './templates/index.js';
35
36
 
36
37
  // ============================================
37
38
  // Options
@@ -152,9 +153,13 @@ export function markdownToDoc(markdownDoc: MarkdownDocument, options?: MarkdownT
152
153
  function makeBlock(heading: MarkdownHeading | null): Block {
153
154
  const id = heading ? generateId(heading, headingIndex++) : 'preamble';
154
155
 
155
- // Use template from annotation if present, otherwise fall back to default
156
+ // Use template from annotation if present, otherwise fall back to default.
157
+ // Legacy template ids (e.g. `titleBlock`) are normalized to their canonical
158
+ // short form (`title`) so downstream comparisons can rely on the new names
159
+ // without sprinkling alias resolution everywhere.
156
160
  const annotation = heading?.templateAnnotation;
157
- const template = annotation?.template ?? (heading ? defaultTemplate : undefined);
161
+ const rawTemplate = annotation?.template ?? (heading ? defaultTemplate : undefined);
162
+ const template = rawTemplate != null ? resolveTemplateName(rawTemplate) : undefined;
158
163
 
159
164
  // Extract heading text so templates (e.g. sectionHeader) that expect a
160
165
  // `title` property receive it without having to reach into sourceHeading.
@@ -434,12 +439,45 @@ function splitIntoSentences(text: string): string[] {
434
439
  return merged;
435
440
  }
436
441
 
442
+ interface ImageRef {
443
+ url: string;
444
+ alt?: string;
445
+ }
446
+
437
447
  /**
438
- * Walk a MarkdownNode tree depth-first to find the first image node.
439
- * Returns the MarkdownImage or undefined if none found.
448
+ * Walk a parsed HTML sub-tree depth-first for an `<img>` with a `src`.
449
+ * Used to surface images that the WYSIWYG editor serialized as raw HTML
450
+ * (e.g. when an image was resized — markdown's `![alt](url)` shorthand
451
+ * has no width syntax, so resized images round-trip as `<img src width>`).
440
452
  */
441
- function findFirstImage(node: MarkdownNode): MarkdownImage | undefined {
442
- if (node.type === 'image') return node as MarkdownImage;
453
+ function findFirstHtmlImage(nodes: HtmlNode[]): ImageRef | undefined {
454
+ for (const node of nodes) {
455
+ if (node.type !== 'htmlElement') continue;
456
+ if (node.tagName === 'img' && node.attributes.src) {
457
+ return { url: node.attributes.src, alt: node.attributes.alt };
458
+ }
459
+ const nested = findFirstHtmlImage(node.children);
460
+ if (nested) return nested;
461
+ }
462
+ return undefined;
463
+ }
464
+
465
+ /**
466
+ * Walk a MarkdownNode tree depth-first to find the first image reference,
467
+ * whether expressed as a markdown image node or a raw HTML `<img>` tag.
468
+ */
469
+ function findFirstImage(node: MarkdownNode): ImageRef | undefined {
470
+ if (node.type === 'image') {
471
+ const img = node as { url: string; alt?: string };
472
+ return { url: img.url, alt: img.alt };
473
+ }
474
+ if (node.type === 'htmlBlock' || node.type === 'htmlInline') {
475
+ const html = node as { htmlChildren?: HtmlNode[] };
476
+ if (html.htmlChildren) {
477
+ const found = findFirstHtmlImage(html.htmlChildren);
478
+ if (found) return found;
479
+ }
480
+ }
443
481
  if ('children' in node && Array.isArray((node as { children?: unknown[] }).children)) {
444
482
  for (const child of (node as { children: MarkdownNode[] }).children) {
445
483
  const found = findFirstImage(child);
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Feature Block Templates — `leftFeature` and `rightFeature`
3
+ *
4
+ * Pairs a "feature" image on one side of the block with a title + body
5
+ * paragraph on the other. The two templates are mirror images, so the
6
+ * actual layer-builder lives here once and the per-side template files
7
+ * just call it with a `side` parameter.
8
+ *
9
+ * Used for editorial layouts like product highlights, profile cards,
10
+ * and section intros where a single image deserves to sit next to a
11
+ * short text block rather than behind it (which is what
12
+ * `imageWithCaption` does).
13
+ */
14
+
15
+ import type { Layer } from '../../schemas/Doc.js';
16
+ import type {
17
+ LeftFeatureInput,
18
+ RightFeatureInput,
19
+ TemplateContext,
20
+ } from '../../schemas/BlockTemplates.js';
21
+ import { scaledFontSize } from '../../schemas/BlockTemplates.js';
22
+ import { getThemeFont } from '../utils/themeUtils.js';
23
+
24
+ type FeatureInput = LeftFeatureInput | RightFeatureInput;
25
+
26
+ /**
27
+ * Build the layer list for either feature template. Layers are in
28
+ * back-to-front order: background, image, then text.
29
+ */
30
+ function buildFeatureLayers(
31
+ input: FeatureInput,
32
+ context: TemplateContext,
33
+ side: 'left' | 'right',
34
+ ): Layer[] {
35
+ const { imageSrc, imageAlt, imageWidth, imageHeight, title, body } = input;
36
+ const { theme, layout } = context;
37
+ // Treat the image as "sized" when the host gave us an explicit width
38
+ // or height — that's our cue that the user resized the image in the
39
+ // editor and we should respect that as a sizing hint instead of
40
+ // stretching the image to fill the whole half.
41
+ const sized = (imageWidth ?? 0) > 0 || (imageHeight ?? 0) > 0;
42
+ // In portrait the image stacks above the text rather than splitting
43
+ // horizontally — same flag the twoColumn template uses, so the two
44
+ // behave consistently on narrow viewports.
45
+ const stack = layout.stackColumns;
46
+
47
+ const titleFontSize = scaledFontSize(48, context, true);
48
+ const bodyFontSize = scaledFontSize(24, context, false);
49
+
50
+ // Image takes the full left or right half. The text column gets the
51
+ // opposite half, with a comfortable inset so the text doesn't kiss
52
+ // the image edge or the card border. All values are percentages of
53
+ // the block viewport so they scale with the card size.
54
+ //
55
+ // When the image is "sized" we shrink it inside its half: the half
56
+ // still claims the space (so the text column stays the same width
57
+ // and the layout doesn't shift around when dimensions are toggled)
58
+ // but the image itself sits centered with padding around it. The
59
+ // sized box uses a square envelope sized to the smaller dimension of
60
+ // its half so the image breathes regardless of viewport aspect.
61
+ let imgX: string;
62
+ let imgY: string;
63
+ let imgW: string;
64
+ let imgH: string;
65
+ let imageFit: 'cover' | 'contain';
66
+ if (stack) {
67
+ imgX = '0';
68
+ imgY = '0';
69
+ imgW = '100%';
70
+ imgH = '50%';
71
+ imageFit = sized ? 'contain' : 'cover';
72
+ } else if (sized) {
73
+ // 90% of the half (with explicit aspect via fit='contain') so the
74
+ // image keeps its natural proportions and never touches the card
75
+ // edges. We don't try to honor the literal pixel value — block
76
+ // viewports are designed in their own coordinate space — but the
77
+ // *intent* (smaller image, padded, centered) is what comes through.
78
+ const halfStart = side === 'left' ? 5 : 55; // %
79
+ imgX = `${halfStart}%`;
80
+ imgY = '5%';
81
+ imgW = '40%';
82
+ imgH = '90%';
83
+ imageFit = 'contain';
84
+ } else {
85
+ imgX = side === 'left' ? '0' : '50%';
86
+ imgY = '0';
87
+ imgW = '50%';
88
+ imgH = '100%';
89
+ imageFit = 'cover';
90
+ }
91
+
92
+ // Text-column geometry. `textX` is the LEFT edge of the column when
93
+ // the side is "left" (image left, text right), and the RIGHT edge of
94
+ // the column when the side is "right" — we use top-left / top-right
95
+ // anchors below so text reads left-aligned in both cases without
96
+ // having to flip the column origin manually.
97
+ const COLUMN_INSET = 4; // % of block width — padding from card edge / divider
98
+ const textColumnWidth = stack ? 90 : 42;
99
+ const textX = stack
100
+ ? `${(100 - textColumnWidth) / 2}%`
101
+ : side === 'left'
102
+ ? `${50 + COLUMN_INSET}%` // text column starts just past the divider
103
+ : `${50 - COLUMN_INSET}%`; // right edge of left-half text column
104
+
105
+ // Stack the title above the body, with the pair vertically centered
106
+ // in the text column. The title sits 8% above center; body sits 2%
107
+ // below — these offsets keep both lines visible at the small card
108
+ // sizes used by the inline preview gutter without overflowing.
109
+ const titleY = stack ? '60%' : body ? '42%' : '50%';
110
+ const bodyY = stack ? '78%' : title ? '55%' : '50%';
111
+ const textAnchor = side === 'right' && !stack ? 'top-right' : 'top-left';
112
+ const textAlign: 'left' | 'right' = side === 'right' && !stack ? 'right' : 'left';
113
+
114
+ // Paint our own background so the text column has a theme-paired
115
+ // surface to sit on. The host wrapper's surface isn't always theme-
116
+ // aware (e.g. the InlinePreviewGutter card uses a fixed light SVG
117
+ // background), and `theme.colors.text` is only guaranteed legible
118
+ // against `theme.colors.background` — themes like Gezellig pair
119
+ // cream text with a dark warm-brown background, which would
120
+ // disappear on a light wrapper. Matching the convention every other
121
+ // text-bearing template uses (see twoColumn / listBlock).
122
+ const layers: Layer[] = [
123
+ {
124
+ type: 'shape',
125
+ id: 'feature-bg',
126
+ content: { shape: 'rect', fill: theme.colors.background },
127
+ position: { x: 0, y: 0, width: '100%', height: '100%' },
128
+ },
129
+ ];
130
+
131
+ if (imageSrc) {
132
+ layers.push({
133
+ type: 'image',
134
+ id: 'feature-image',
135
+ content: {
136
+ src: imageSrc,
137
+ alt: imageAlt ?? title ?? '',
138
+ fit: imageFit,
139
+ },
140
+ position: { x: imgX, y: imgY, width: imgW, height: imgH },
141
+ });
142
+ }
143
+
144
+ if (title) {
145
+ layers.push({
146
+ type: 'text',
147
+ id: 'feature-title',
148
+ content: {
149
+ text: title,
150
+ style: {
151
+ fontSize: titleFontSize,
152
+ fontFamily: getThemeFont(context, 'title'),
153
+ fontWeight: 'bold',
154
+ color: theme.colors.text,
155
+ textAlign,
156
+ lineHeight: 1.2,
157
+ },
158
+ },
159
+ position: {
160
+ x: textX,
161
+ y: titleY,
162
+ anchor: textAnchor,
163
+ width: `${textColumnWidth}%`,
164
+ },
165
+ animation: { type: 'fadeIn', duration: 0.8 },
166
+ });
167
+ }
168
+
169
+ if (body) {
170
+ layers.push({
171
+ type: 'text',
172
+ id: 'feature-body',
173
+ content: {
174
+ text: body,
175
+ style: {
176
+ fontSize: bodyFontSize,
177
+ fontFamily: getThemeFont(context, 'body'),
178
+ color: theme.colors.textMuted,
179
+ textAlign,
180
+ lineHeight: 1.5,
181
+ },
182
+ },
183
+ position: {
184
+ x: textX,
185
+ y: bodyY,
186
+ anchor: textAnchor,
187
+ width: `${textColumnWidth}%`,
188
+ },
189
+ animation: { type: 'fadeIn', duration: 0.8, delay: 0.2 },
190
+ });
191
+ }
192
+
193
+ return layers;
194
+ }
195
+
196
+ export function leftFeature(input: LeftFeatureInput, context: TemplateContext): Layer[] {
197
+ return buildFeatureLayers(input, context, 'left');
198
+ }
199
+
200
+ export function rightFeature(input: RightFeatureInput, context: TemplateContext): Layer[] {
201
+ return buildFeatureLayers(input, context, 'right');
202
+ }
@@ -13,6 +13,19 @@ import type { FullBleedQuoteInput, TemplateContext } from '../../schemas/BlockTe
13
13
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
14
14
  import { resolveColorScheme, getThemeFont } from '../utils/themeUtils.js';
15
15
 
16
+ /**
17
+ * Hint schema published for the theme validator + future customizer hint UI.
18
+ * Themes may set `templateHints.fullBleedQuote` entries matching these keys.
19
+ */
20
+ export const fullBleedQuoteHintSchema = {
21
+ /** Entrance style for the quote text. 'subtle' or 'dramatic'. */
22
+ entrance: {
23
+ type: 'string' as const,
24
+ options: ['subtle', 'dramatic'] as const,
25
+ default: 'subtle',
26
+ },
27
+ } as const;
28
+
16
29
  export function fullBleedQuote(input: FullBleedQuoteInput, context: TemplateContext): Layer[] {
17
30
  const { text, colorScheme = 'blue' } = input;
18
31
  const { theme } = context;
@@ -35,6 +35,7 @@ import { factCard } from './factCard.js';
35
35
  import { twoColumn } from './twoColumn.js';
36
36
  import { dateEvent } from './dateEvent.js';
37
37
  import { imageWithCaption } from './imageWithCaption.js';
38
+ import { leftFeature, rightFeature } from './featureBlock.js';
38
39
  import { mapBlock } from './mapBlock.js';
39
40
  import { fullBleedQuote } from './fullBleedQuote.js';
40
41
  import { listBlock } from './listBlock.js';
@@ -47,22 +48,33 @@ import { videoPullQuote } from './videoPullQuote.js';
47
48
  import { dataTable } from './dataTable.js';
48
49
 
49
50
  /**
50
- * Registry mapping template names to their implementation functions.
51
- * Note: coverBlock is not in the registry as it's used directly for start blocks,
52
- * not as a regular template in the block sequence.
51
+ * Registry mapping template ids (the strings that appear in
52
+ * `{[name]}` markdown annotations) to their implementation functions.
53
+ *
54
+ * Some implementation functions keep historical "Block" suffixes
55
+ * (`titleBlock`, `quoteBlock`, `mapBlock`, `listBlock`) for source
56
+ * readability — the registry surfaces them under the cleaner short
57
+ * ids (`title`, `quote`, `map`, `list`). {@link TEMPLATE_ALIASES} keeps
58
+ * the legacy long ids resolvable for documents written before the
59
+ * rename.
60
+ *
61
+ * Note: coverBlock is not in the registry as it's used directly for
62
+ * start blocks, not as a regular template in the block sequence.
53
63
  */
54
64
  export const templateRegistry: TemplateRegistry = {
55
- titleBlock,
65
+ title: titleBlock,
56
66
  sectionHeader,
57
67
  statHighlight,
58
- quoteBlock,
68
+ quote: quoteBlock,
59
69
  factCard,
60
70
  twoColumn,
61
71
  dateEvent,
62
72
  imageWithCaption,
63
- mapBlock,
73
+ leftFeature,
74
+ rightFeature,
75
+ map: mapBlock,
64
76
  fullBleedQuote,
65
- listBlock,
77
+ list: listBlock,
66
78
  photoGrid,
67
79
  definitionCard,
68
80
  comparisonBar,
@@ -72,11 +84,35 @@ export const templateRegistry: TemplateRegistry = {
72
84
  dataTable,
73
85
  };
74
86
 
87
+ /**
88
+ * Back-compat: maps legacy template ids (kept around for documents
89
+ * authored before the "Block" suffix was dropped) to the canonical
90
+ * short ids. Resolved by {@link resolveTemplateName} so both the
91
+ * registry lookup and `hasTemplate()` accept either form.
92
+ */
93
+ export const TEMPLATE_ALIASES: Readonly<Record<string, string>> = {
94
+ titleBlock: 'title',
95
+ quoteBlock: 'quote',
96
+ mapBlock: 'map',
97
+ listBlock: 'list',
98
+ };
99
+
100
+ /**
101
+ * Resolve a template id through {@link TEMPLATE_ALIASES}. Returns the
102
+ * input unchanged when no alias is registered.
103
+ */
104
+ export function resolveTemplateName(name: string): string {
105
+ return TEMPLATE_ALIASES[name] ?? name;
106
+ }
107
+
75
108
  /**
76
109
  * Expand a template block into a full Block with layers.
77
110
  */
78
111
  export function expandTemplateBlock(templateBlock: TemplateBlock, context: TemplateContext): Block {
79
- const templateFn = templateRegistry[templateBlock.template];
112
+ const resolved = resolveTemplateName(templateBlock.template);
113
+ const templateFn = (templateRegistry as Record<string, unknown>)[resolved] as
114
+ | ((input: TemplateBlock, ctx: TemplateContext) => Layer[])
115
+ | undefined;
80
116
 
81
117
  if (!templateFn) {
82
118
  console.warn(`Unknown template: ${templateBlock.template}`);
@@ -94,12 +130,7 @@ export function expandTemplateBlock(templateBlock: TemplateBlock, context: Templ
94
130
  // Generate layers from template with error handling
95
131
  let layers: Layer[];
96
132
  try {
97
- // Each registry entry accepts its specific TemplateBlock variant; the
98
- // discriminated union guarantees the shapes match at runtime.
99
- layers = (templateFn as (input: TemplateBlock, ctx: TemplateContext) => Layer[])(
100
- templateBlock,
101
- context,
102
- );
133
+ layers = templateFn(templateBlock, context);
103
134
  if (!Array.isArray(layers)) {
104
135
  console.error(
105
136
  `Template ${templateBlock.template} did not return an array, got:`,
@@ -503,10 +534,12 @@ export function getAvailableTemplates(): string[] {
503
534
  }
504
535
 
505
536
  /**
506
- * Check if a template exists.
537
+ * Check if a template exists. Accepts both the canonical short id
538
+ * (`title`, `quote`, `map`, `list`) and legacy aliases
539
+ * (`titleBlock`, `quoteBlock`, `mapBlock`, `listBlock`).
507
540
  */
508
541
  export function hasTemplate(name: string): boolean {
509
- return name in templateRegistry;
542
+ return resolveTemplateName(name) in templateRegistry;
510
543
  }
511
544
 
512
545
  // Re-export types and utilities from schemas
@@ -556,6 +589,7 @@ export { factCard } from './factCard.js';
556
589
  export { twoColumn } from './twoColumn.js';
557
590
  export { dateEvent } from './dateEvent.js';
558
591
  export { imageWithCaption } from './imageWithCaption.js';
592
+ export { leftFeature, rightFeature } from './featureBlock.js';
559
593
  export { mapBlock } from './mapBlock.js';
560
594
  export { coverBlock, expandCoverBlock } from './coverBlock.js';
561
595
  export type { CoverBlockInput } from './coverBlock.js';
@@ -15,7 +15,13 @@ import { getThemeFont } from '../utils/themeUtils.js';
15
15
  import { createAccentLayers, getAccentLayout, adjustY, DEFAULT_LAYOUT } from './accentImage.js';
16
16
 
17
17
  export function listBlock(input: ListBlockInput, context: TemplateContext): Layer[] {
18
- const { items, title, accentImage } = input;
18
+ const { title, accentImage } = input;
19
+ // `items` is required by the schema, but malformed / partially-authored
20
+ // blocks (e.g. someone wrote `template: list` with no items yet) reach
21
+ // this code path during live preview. Treat missing/non-array as empty
22
+ // so we render the title-and-background frame instead of blowing up
23
+ // every keystroke with a TypeError.
24
+ const items: string[] = Array.isArray(input.items) ? input.items : [];
19
25
  const { theme } = context;
20
26
 
21
27
  // Get layout adjustments if accent image is present
@@ -68,7 +74,20 @@ export function listBlock(input: ListBlockInput, context: TemplateContext): Laye
68
74
  });
69
75
  }
70
76
 
71
- // Calculate vertical spacing for items
77
+ // Left edge of the text column — derived from the center+width pair so
78
+ // items sit flush with the column's left margin instead of being
79
+ // centered. Falls back to a safe 8% if the layout strings ever turn
80
+ // out unparseable (they're authored, but defensive against future
81
+ // edits to the accent-layout types).
82
+ const centerX = parseFloat(accentLayout.textCenterX);
83
+ const widthPct = parseFloat(accentLayout.textWidth);
84
+ const leftX =
85
+ Number.isFinite(centerX) && Number.isFinite(widthPct) ? `${centerX - widthPct / 2}%` : '8%';
86
+
87
+ // Calculate vertical spacing for items. We keep the available band
88
+ // (startY → endY) but use a tighter line-height (1.2) so wrapped
89
+ // items don't bleed into the next entry as aggressively as the old
90
+ // 1.4 value did.
72
91
  const endY = 80;
73
92
  const spacing = items.length > 1 ? (endY - startY) / (items.length - 1) : 0;
74
93
 
@@ -86,14 +105,14 @@ export function listBlock(input: ListBlockInput, context: TemplateContext): Laye
86
105
  fontSize: itemFontSize,
87
106
  fontFamily: getThemeFont(context, 'body'),
88
107
  color: theme.colors.text,
89
- lineHeight: 1.4,
108
+ textAlign: 'left',
109
+ lineHeight: 1.2,
90
110
  shadow: !!accentImage,
91
111
  },
92
112
  },
93
113
  position: {
94
- x: accentLayout.textCenterX,
114
+ x: leftX,
95
115
  y: adjustY(`${y}%`, accentLayout),
96
- anchor: 'center',
97
116
  width: accentLayout.textWidth,
98
117
  },
99
118
  animation: { type: 'fadeIn', duration: 0.8, delay: 0.3 + 0.3 * i },
@@ -17,6 +17,19 @@ import { resolveColorScheme, getThemeFont } from '../utils/themeUtils.js';
17
17
  import { createAccentLayers, getAccentLayout, adjustY, DEFAULT_LAYOUT } from './accentImage.js';
18
18
  import { createBackgroundLayer } from './captionUtils.js';
19
19
 
20
+ /**
21
+ * Hint schema published for the theme validator + future customizer hint UI.
22
+ * Themes may set `templateHints.statHighlight` entries matching these keys.
23
+ */
24
+ export const statHighlightHintSchema = {
25
+ /** Entrance style for the stat number. 'subtle' or 'dramatic'. */
26
+ entrance: {
27
+ type: 'string' as const,
28
+ options: ['subtle', 'dramatic'] as const,
29
+ default: 'subtle',
30
+ },
31
+ } as const;
32
+
20
33
  export function statHighlight(input: StatHighlightInput, context: TemplateContext): Layer[] {
21
34
  const { stat, description, detail, colorScheme = 'blue', accentImage } = input;
22
35
  const { theme } = context;
@@ -13,6 +13,15 @@ import type { TitleBlockInput, TemplateContext } from '../../schemas/BlockTempla
13
13
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
14
14
  import { getThemeFont } from '../utils/themeUtils.js';
15
15
 
16
+ /**
17
+ * Hint schema published for the theme validator + future customizer hint UI.
18
+ * Themes may set `templateHints.title` entries matching these keys.
19
+ */
20
+ export const titleBlockHintSchema = {
21
+ /** Whether to render a thin accent line above the title. Default: theme-dependent. */
22
+ showAccentLine: { type: 'boolean' as const, default: true },
23
+ } as const;
24
+
16
25
  export function titleBlock(input: TitleBlockInput, context: TemplateContext): Layer[] {
17
26
  const { title, subtitle, backgroundColor } = input;
18
27
  const { theme, layout } = context;
@@ -9,6 +9,7 @@
9
9
  import type { TemplateContext } from '../../schemas/BlockTemplates.js';
10
10
  import type { ThemeColorScheme, RenderStyle } from '../../schemas/Theme.js';
11
11
  import type { AnimationType } from '../../schemas/Doc.js';
12
+ import { resolveFontFamily } from '../../schemas/fontStacks.js';
12
13
 
13
14
  // ============================================
14
15
  // Color Scheme Resolution
@@ -47,22 +48,22 @@ const FALLBACK_TITLE = 'Georgia, "Times New Roman", serif';
47
48
  const FALLBACK_MONO = 'Consolas, "Courier New", monospace';
48
49
 
49
50
  /**
50
- * Get the theme font family for a given role.
51
+ * Get the theme font family for a given role, resolved to a CSS string.
51
52
  * Templates call this to set `fontFamily` on text layers.
52
53
  *
53
- * @param context Template context (includes theme with typography settings)
54
- * @param role 'title' for headings, 'body' for descriptions, 'mono' for code
55
- * @returns A CSS font-family string with the theme's font and safe fallbacks
54
+ * Theme fields are structured `FontFamily` references (`{ stackId }` or
55
+ * `{ custom: { name, fallback } }`); this helper resolves them to a
56
+ * CSS font-family string with safe fallbacks.
56
57
  */
57
58
  export function getThemeFont(context: TemplateContext, role: 'title' | 'body' | 'mono'): string {
58
59
  const { typography } = context.theme;
59
60
  switch (role) {
60
61
  case 'title':
61
- return typography.titleFontFamily || FALLBACK_TITLE;
62
+ return resolveFontFamily(typography.titleFont, FALLBACK_TITLE);
62
63
  case 'body':
63
- return typography.bodyFontFamily || FALLBACK_BODY;
64
+ return resolveFontFamily(typography.bodyFont, FALLBACK_BODY);
64
65
  case 'mono':
65
- return typography.monoFontFamily || FALLBACK_MONO;
66
+ return resolveFontFamily(typography.monoFont, FALLBACK_MONO);
66
67
  }
67
68
  }
68
69
 
@@ -158,7 +158,7 @@ export function generateSlideshow(
158
158
  if (title) {
159
159
  const titleBlock: TitleBlockInput = {
160
160
  id: `slide-${slideIndex++}`,
161
- template: 'titleBlock',
161
+ template: 'title',
162
162
  title,
163
163
  subtitle,
164
164
  duration: Math.min(perSlide, 6),
@@ -109,7 +109,7 @@ function mapDate(data: DateData, base: Base, opts: MapOptions): DateEventInput {
109
109
  function mapQuote(data: QuoteData, base: Base, opts: MapOptions): QuoteBlockInput {
110
110
  return {
111
111
  ...base,
112
- template: 'quoteBlock',
112
+ template: 'quote',
113
113
  quote: data.quote,
114
114
  attribution: data.attribution,
115
115
  accentImage: opts.accentImage,
@@ -149,7 +149,7 @@ function mapImpactLine(data: ImpactLineData, base: Base, opts: MapOptions): Full
149
149
  function mapList(data: ListData, base: Base, opts: MapOptions): ListBlockInput {
150
150
  return {
151
151
  ...base,
152
- template: 'listBlock',
152
+ template: 'list',
153
153
  items: data.items,
154
154
  title: data.title,
155
155
  colorScheme: opts.colorScheme,