@bendyline/squisq 1.5.1 → 2.0.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 (268) hide show
  1. package/README.md +63 -10
  2. package/dist/{Doc-BBVGq1_9.d.ts → Doc-ylqWKDc_.d.ts} +248 -37
  3. package/dist/{ImageEditDoc-FuyGtt6o.d.ts → ImageEditDoc-D828IXJ1.d.ts} +1 -1
  4. package/dist/annotationCoercion-Dr_DY9Hx.d.ts +86 -0
  5. package/dist/{chunk-VWUFZ6ZG.js → chunk-3E5F2XMR.js} +2 -29
  6. package/dist/chunk-3E5F2XMR.js.map +1 -0
  7. package/dist/chunk-4X3JQXNM.js +35 -0
  8. package/dist/chunk-4X3JQXNM.js.map +1 -0
  9. package/dist/{chunk-2OIBZYKP.js → chunk-5DKBFXPY.js} +31 -121
  10. package/dist/chunk-5DKBFXPY.js.map +1 -0
  11. package/dist/{chunk-DLXZMS5K.js → chunk-5J4HKRJF.js} +51 -3
  12. package/dist/chunk-5J4HKRJF.js.map +1 -0
  13. package/dist/chunk-5UYI3RJB.js +2503 -0
  14. package/dist/chunk-5UYI3RJB.js.map +1 -0
  15. package/dist/{chunk-TEEEILMP.js → chunk-66QO7XFB.js} +19 -292
  16. package/dist/chunk-66QO7XFB.js.map +1 -0
  17. package/dist/{chunk-KKNUBQ6Y.js → chunk-6PJRB2CF.js} +156 -91
  18. package/dist/chunk-6PJRB2CF.js.map +1 -0
  19. package/dist/{chunk-HWVFJAAH.js → chunk-AMIR72JP.js} +28 -10
  20. package/dist/chunk-AMIR72JP.js.map +1 -0
  21. package/dist/chunk-FVJUUTEM.js +74 -0
  22. package/dist/chunk-FVJUUTEM.js.map +1 -0
  23. package/dist/{chunk-LH7I6SH7.js → chunk-GF3BVNLU.js} +6360 -5723
  24. package/dist/chunk-GF3BVNLU.js.map +1 -0
  25. package/dist/{chunk-Q3ROPT5H.js → chunk-GZY66SDN.js} +1 -1
  26. package/dist/{chunk-Q3ROPT5H.js.map → chunk-GZY66SDN.js.map} +1 -1
  27. package/dist/chunk-JOQYPRDZ.js +127 -0
  28. package/dist/chunk-JOQYPRDZ.js.map +1 -0
  29. package/dist/{chunk-A4O7GIWE.js → chunk-LYNJEOAC.js} +10 -73
  30. package/dist/chunk-LYNJEOAC.js.map +1 -0
  31. package/dist/{chunk-LDQ2HJIX.js → chunk-PXLVXTLW.js} +252 -54
  32. package/dist/chunk-PXLVXTLW.js.map +1 -0
  33. package/dist/{chunk-TQWLI6S2.js → chunk-Q7LROV2H.js} +4 -2
  34. package/dist/{chunk-TQWLI6S2.js.map → chunk-Q7LROV2H.js.map} +1 -1
  35. package/dist/{chunk-T5UK6YOB.js → chunk-QTSJOY54.js} +60 -17
  36. package/dist/chunk-QTSJOY54.js.map +1 -0
  37. package/dist/chunk-QVANWDPH.js +2358 -0
  38. package/dist/chunk-QVANWDPH.js.map +1 -0
  39. package/dist/{chunk-6ATE2PSM.js → chunk-R2GC6RLD.js} +7 -7
  40. package/dist/chunk-R2GC6RLD.js.map +1 -0
  41. package/dist/chunk-R2YZTCLO.js +292 -0
  42. package/dist/chunk-R2YZTCLO.js.map +1 -0
  43. package/dist/chunk-S7A5D7ZJ.js +174 -0
  44. package/dist/chunk-S7A5D7ZJ.js.map +1 -0
  45. package/dist/chunk-T3TI7GSZ.js +884 -0
  46. package/dist/chunk-T3TI7GSZ.js.map +1 -0
  47. package/dist/{chunk-RUDYOTA6.js → chunk-TUWMK62B.js} +110 -4
  48. package/dist/chunk-TUWMK62B.js.map +1 -0
  49. package/dist/{chunk-IMSCRLLV.js → chunk-VR7UPJ7C.js} +35 -25
  50. package/dist/chunk-VR7UPJ7C.js.map +1 -0
  51. package/dist/doc/index.d.ts +2334 -5
  52. package/dist/doc/index.js +134 -34
  53. package/dist/generate/index.d.ts +7 -2
  54. package/dist/generate/index.js +1 -1
  55. package/dist/imageEdit/index.d.ts +9 -6
  56. package/dist/imageEdit/index.js +3 -3
  57. package/dist/index.d.ts +12 -10
  58. package/dist/index.js +253 -81
  59. package/dist/jsonForm/index.d.ts +7 -3
  60. package/dist/jsonForm/index.js +9 -4
  61. package/dist/markdown/index.d.ts +6 -70
  62. package/dist/markdown/index.js +13 -9
  63. package/dist/narration/index.d.ts +618 -0
  64. package/dist/narration/index.js +82 -0
  65. package/dist/recommend/index.d.ts +15 -1
  66. package/dist/recommend/index.js +2 -2
  67. package/dist/schemas/index.d.ts +28 -6
  68. package/dist/schemas/index.js +15 -13
  69. package/dist/spatial/index.d.ts +0 -1
  70. package/dist/spatial/index.js +1 -1
  71. package/dist/storage/index.js +1 -1
  72. package/dist/{themeLibrary-CehcJhzz.d.ts → themeLibrary-RjVgLpUi.d.ts} +10 -11
  73. package/dist/timing/index.js +5 -3
  74. package/dist/transform/index.d.ts +35 -24
  75. package/dist/transform/index.js +9 -10
  76. package/dist/{types-DlAZ7MW4.d.ts → types-BwMPl2CB.d.ts} +1 -1
  77. package/dist/{types-D98li1gV.d.ts → types-C4kiRn_k.d.ts} +3 -1
  78. package/dist/versions/index.d.ts +2 -1
  79. package/dist/versions/index.js +2 -2
  80. package/package.json +6 -6
  81. package/src/__tests__/applyNarrationTiming.test.ts +190 -0
  82. package/src/__tests__/applyRenderStyle.test.ts +3 -3
  83. package/src/__tests__/asciiDiagramBattle.test.ts +246 -0
  84. package/src/__tests__/asciiDiagramDetect.test.ts +162 -0
  85. package/src/__tests__/asciiDiagramMapping.test.ts +38 -0
  86. package/src/__tests__/asciiDiagramParse.test.ts +233 -0
  87. package/src/__tests__/asciiDiagramPipeline.test.ts +273 -0
  88. package/src/__tests__/asciiDiagramRender.test.ts +162 -0
  89. package/src/__tests__/asciiDiagramRepair.test.ts +84 -0
  90. package/src/__tests__/asciiTimelineDetect.test.ts +157 -0
  91. package/src/__tests__/asciiTimelineParse.test.ts +205 -0
  92. package/src/__tests__/asciiTimelinePipeline.test.ts +349 -0
  93. package/src/__tests__/asciiTimelineRender.test.ts +376 -0
  94. package/src/__tests__/attrTokens.test.ts +5 -0
  95. package/src/__tests__/contentContainer.test.ts +43 -0
  96. package/src/__tests__/coverBlock.test.ts +33 -2
  97. package/src/__tests__/customTemplatesFrontmatter.test.ts +0 -15
  98. package/src/__tests__/dataTable.test.ts +25 -15
  99. package/src/__tests__/diagramBlock.test.ts +3 -3
  100. package/src/__tests__/diagramText.test.ts +26 -0
  101. package/src/__tests__/docToMarkdownMedia.test.ts +83 -0
  102. package/src/__tests__/drawingBlock.test.ts +3 -3
  103. package/src/__tests__/fixtures/asciiDiagramBattle.ts +512 -0
  104. package/src/__tests__/fixtures/asciiDiagrams.ts +287 -0
  105. package/src/__tests__/fixtures/asciiTimelines.ts +35 -0
  106. package/src/__tests__/fixtures/treeviewFixtures.ts +243 -0
  107. package/src/__tests__/getLayers.test.ts +32 -18
  108. package/src/__tests__/getLayersFallback.test.ts +8 -8
  109. package/src/__tests__/imageEditVersions.test.ts +2 -0
  110. package/src/__tests__/jsonForm.pointer.test.ts +47 -1
  111. package/src/__tests__/layoutBlock.test.ts +3 -3
  112. package/src/__tests__/majorApiTypes.test.ts +18 -0
  113. package/src/__tests__/markdown.test.ts +41 -0
  114. package/src/__tests__/markdownToDoc.test.ts +5 -2
  115. package/src/__tests__/matchFontFamily.test.ts +114 -0
  116. package/src/__tests__/materializeBlockLayers.test.ts +135 -0
  117. package/src/__tests__/materializeTestUtils.ts +17 -0
  118. package/src/__tests__/narrationAlign.test.ts +150 -0
  119. package/src/__tests__/narrationFeatures.test.ts +71 -0
  120. package/src/__tests__/narrationNuclei.test.ts +45 -0
  121. package/src/__tests__/narrationPacing.test.ts +189 -0
  122. package/src/__tests__/narrationScript.test.ts +149 -0
  123. package/src/__tests__/narrationSessionRepro.test.ts +62 -0
  124. package/src/__tests__/narrationSidecar.test.ts +88 -0
  125. package/src/__tests__/narrationSyllables.test.ts +65 -0
  126. package/src/__tests__/narrationTestSignals.ts +167 -0
  127. package/src/__tests__/narrationVad.test.ts +66 -0
  128. package/src/__tests__/resolveDocTheme.test.ts +16 -2
  129. package/src/__tests__/rootExports.test.ts +18 -0
  130. package/src/__tests__/scopedContentContainer.test.ts +20 -0
  131. package/src/__tests__/shapeGeometry.test.ts +49 -0
  132. package/src/__tests__/templateAnnotationParse.test.ts +10 -0
  133. package/src/__tests__/templateInputs.test.ts +69 -0
  134. package/src/__tests__/templateMetadata.test.ts +2 -1
  135. package/src/__tests__/templates.test.ts +143 -17
  136. package/src/__tests__/themeCompile.test.ts +49 -22
  137. package/src/__tests__/transformProvenance.test.ts +160 -0
  138. package/src/__tests__/transformV2.test.ts +40 -14
  139. package/src/__tests__/treeviewBattle.test.ts +76 -0
  140. package/src/__tests__/treeviewDetect.test.ts +168 -0
  141. package/src/__tests__/treeviewParse.test.ts +118 -0
  142. package/src/__tests__/treeviewPipeline.test.ts +183 -0
  143. package/src/__tests__/treeviewRender.test.ts +91 -0
  144. package/src/__tests__/versions.test.ts +44 -0
  145. package/src/doc/applyNarrationTiming.ts +244 -0
  146. package/src/doc/asciiDiagram/chars.ts +210 -0
  147. package/src/doc/asciiDiagram/detect.ts +175 -0
  148. package/src/doc/asciiDiagram/index.ts +38 -0
  149. package/src/doc/asciiDiagram/mapping.ts +225 -0
  150. package/src/doc/asciiDiagram/parse.ts +1095 -0
  151. package/src/doc/asciiDiagram/render.ts +963 -0
  152. package/src/doc/asciiDiagram/repair.ts +400 -0
  153. package/src/doc/asciiDiagram/types.ts +65 -0
  154. package/src/doc/asciiTimeline/detect.ts +138 -0
  155. package/src/doc/asciiTimeline/index.ts +26 -0
  156. package/src/doc/asciiTimeline/mapping.ts +43 -0
  157. package/src/doc/asciiTimeline/parse.ts +870 -0
  158. package/src/doc/asciiTimeline/render.ts +427 -0
  159. package/src/doc/asciiTimeline/types.ts +73 -0
  160. package/src/doc/asciiTimeline/wrappedFlow.ts +427 -0
  161. package/src/doc/audioMapping.ts +60 -46
  162. package/src/doc/customTemplatesFrontmatter.ts +0 -41
  163. package/src/doc/docToMarkdown.ts +126 -39
  164. package/src/doc/index.ts +76 -4
  165. package/src/doc/markdownToDoc.ts +135 -1
  166. package/src/doc/materializeBlockLayers.ts +304 -0
  167. package/src/doc/mediaAnnotations.ts +20 -0
  168. package/src/doc/resolveDocTheme.ts +8 -7
  169. package/src/doc/templateInputs.ts +185 -2
  170. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +22 -8
  171. package/src/doc/templates/coverBlock.ts +49 -13
  172. package/src/doc/templates/diagramBlock.ts +237 -38
  173. package/src/doc/templates/drawingBlock.ts +2 -2
  174. package/src/doc/templates/fallbackBlock.ts +1 -1
  175. package/src/doc/templates/index.ts +134 -279
  176. package/src/doc/templates/inputDescriptors.ts +20 -1
  177. package/src/doc/templates/metadata.ts +10 -0
  178. package/src/doc/templates/persistentLayers.ts +12 -126
  179. package/src/doc/templates/registry.ts +88 -0
  180. package/src/doc/templates/templateNames.ts +22 -0
  181. package/src/doc/templates/timelineBlock.ts +613 -0
  182. package/src/doc/templates/treeBlock.ts +131 -0
  183. package/src/doc/treeview/chars.ts +72 -0
  184. package/src/doc/treeview/detect.ts +145 -0
  185. package/src/doc/treeview/index.ts +30 -0
  186. package/src/doc/treeview/mapping.ts +95 -0
  187. package/src/doc/treeview/parse.ts +201 -0
  188. package/src/doc/treeview/render.ts +41 -0
  189. package/src/doc/treeview/types.ts +43 -0
  190. package/src/doc/utils/applyRenderStyle.ts +2 -9
  191. package/src/doc/utils/diagramText.ts +78 -0
  192. package/src/doc/utils/shapeGeometry.ts +157 -7
  193. package/src/doc/validate.ts +1 -1
  194. package/src/generate/templateMapper.ts +7 -0
  195. package/src/imageEdit/export.ts +6 -5
  196. package/src/imageEdit/versionPaths.ts +13 -26
  197. package/src/imageEdit/versions.ts +21 -100
  198. package/src/index.ts +1 -0
  199. package/src/internal/immutable.ts +58 -0
  200. package/src/jsonForm/index.ts +9 -1
  201. package/src/jsonForm/pointer.ts +68 -23
  202. package/src/markdown/attrTokens.ts +7 -5
  203. package/src/markdown/convert.ts +11 -1
  204. package/src/markdown/index.ts +1 -0
  205. package/src/markdown/stringify.ts +4 -9
  206. package/src/markdown/utils.ts +36 -14
  207. package/src/narration/align.ts +514 -0
  208. package/src/narration/features.ts +231 -0
  209. package/src/narration/index.ts +86 -0
  210. package/src/narration/nuclei.ts +118 -0
  211. package/src/narration/pacing.ts +211 -0
  212. package/src/narration/script.ts +239 -0
  213. package/src/narration/session.ts +100 -0
  214. package/src/narration/sidecar.ts +187 -0
  215. package/src/narration/syllables.ts +85 -0
  216. package/src/narration/trace.ts +50 -0
  217. package/src/narration/types.ts +299 -0
  218. package/src/narration/vad.ts +131 -0
  219. package/src/recommend/numberHighlight.ts +25 -0
  220. package/src/recommend/templates.ts +108 -12
  221. package/src/schemas/BlockTemplates.ts +156 -7
  222. package/src/schemas/Doc.ts +72 -9
  223. package/src/schemas/Media.ts +12 -2
  224. package/src/schemas/Theme.ts +45 -25
  225. package/src/schemas/Viewport.ts +2 -2
  226. package/src/schemas/fontStacks.ts +79 -0
  227. package/src/schemas/themeCompile.ts +15 -0
  228. package/src/schemas/themeConstants.ts +2 -0
  229. package/src/schemas/themeLibrary.ts +25 -19
  230. package/src/schemas/themeValidator.ts +1 -1
  231. package/src/spatial/Geohash.ts +0 -3
  232. package/src/storage/ContentContainer.ts +17 -9
  233. package/src/storage/ScopedContentContainer.ts +45 -15
  234. package/src/transform/applyTransform.ts +5 -5
  235. package/src/transform/index.ts +3 -1
  236. package/src/transform/registry.ts +169 -49
  237. package/src/transform/templateSelector.ts +21 -2
  238. package/src/transform/timingAllocator.ts +152 -21
  239. package/src/transform/types.ts +14 -1
  240. package/src/versions/gitignore.ts +34 -0
  241. package/src/versions/index.ts +2 -0
  242. package/src/versions/operations.ts +18 -104
  243. package/src/versions/paths.ts +13 -28
  244. package/src/versions/snapshotStore.ts +202 -0
  245. package/src/versions/types.ts +3 -1
  246. package/dist/chunk-2OIBZYKP.js.map +0 -1
  247. package/dist/chunk-6ATE2PSM.js.map +0 -1
  248. package/dist/chunk-A4O7GIWE.js.map +0 -1
  249. package/dist/chunk-DLXZMS5K.js.map +0 -1
  250. package/dist/chunk-HWVFJAAH.js.map +0 -1
  251. package/dist/chunk-IMSCRLLV.js.map +0 -1
  252. package/dist/chunk-JELIRVDP.js +0 -24
  253. package/dist/chunk-JELIRVDP.js.map +0 -1
  254. package/dist/chunk-KKNUBQ6Y.js.map +0 -1
  255. package/dist/chunk-LDQ2HJIX.js.map +0 -1
  256. package/dist/chunk-LH7I6SH7.js.map +0 -1
  257. package/dist/chunk-RUDYOTA6.js.map +0 -1
  258. package/dist/chunk-T5UK6YOB.js.map +0 -1
  259. package/dist/chunk-TEEEILMP.js.map +0 -1
  260. package/dist/chunk-UY7KGQ5R.js +0 -192
  261. package/dist/chunk-UY7KGQ5R.js.map +0 -1
  262. package/dist/chunk-VLJJHDAC.js +0 -218
  263. package/dist/chunk-VLJJHDAC.js.map +0 -1
  264. package/dist/chunk-VWUFZ6ZG.js.map +0 -1
  265. package/dist/story/index.d.ts +0 -1777
  266. package/dist/story/index.js +0 -273
  267. package/src/doc/getLayers.ts +0 -195
  268. /package/dist/{story → narration}/index.js.map +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/schemas/Viewport.ts","../src/schemas/LayoutStrategy.ts","../src/schemas/BlockTemplates.ts"],"sourcesContent":["/**\n * Viewport Configuration\n *\n * Defines viewport dimensions for doc rendering across different aspect ratios.\n * Templates use this configuration to adapt their layouts appropriately.\n *\n * Supported aspect ratios:\n * - landscape (16:9) - Default, standard video/presentation format\n * - portrait (9:16) - Vertical video, mobile stories\n * - square (1:1) - Social media posts\n * - standard (4:3) - Legacy presentation format\n */\n\n/**\n * Viewport configuration for doc rendering.\n */\nexport interface ViewportConfig {\n /** Canonical width in virtual pixels */\n width: number;\n /** Canonical height in virtual pixels */\n height: number;\n /** Human-readable name for debugging/display */\n name: string;\n}\n\n/**\n * Standard viewport presets for common aspect ratios.\n */\nexport const VIEWPORT_PRESETS = {\n /** 16:9 landscape (default, 1080p) */\n landscape: { width: 1920, height: 1080, name: '16:9 Landscape' },\n /** 9:16 portrait (vertical video, stories) */\n portrait: { width: 1080, height: 1920, name: '9:16 Portrait' },\n /** 1:1 square (social media) */\n square: { width: 1080, height: 1080, name: '1:1 Square' },\n /** 4:3 standard (legacy) */\n standard: { width: 1440, height: 1080, name: '4:3 Standard' },\n} as const;\n\n/**\n * Viewport preset name.\n */\nexport type ViewportPreset = keyof typeof VIEWPORT_PRESETS;\n\n/**\n * Viewport orientation derived from aspect ratio.\n */\nexport type ViewportOrientation = 'landscape' | 'portrait' | 'square';\n\n/**\n * Get a viewport configuration from a preset name or return the config if already a ViewportConfig.\n */\nexport function getViewport(viewport: ViewportPreset | ViewportConfig): ViewportConfig {\n if (typeof viewport === 'string') {\n return VIEWPORT_PRESETS[viewport];\n }\n return viewport;\n}\n\n/**\n * Calculate font scale factor for a viewport.\n * Based on diagonal pixels relative to 1080p landscape reference.\n * This ensures text remains readable across different viewport sizes.\n */\nconst REFERENCE_DIAGONAL = Math.sqrt(1920 * 1920 + 1080 * 1080); // ~2203\n\nexport function calculateFontScale(viewport: ViewportConfig): number {\n const currentDiagonal = Math.sqrt(\n viewport.width * viewport.width + viewport.height * viewport.height,\n );\n return currentDiagonal / REFERENCE_DIAGONAL;\n}\n\n/**\n * Determine if viewport is landscape, portrait, or square.\n */\nexport function getViewportOrientation(viewport: ViewportConfig): ViewportOrientation {\n const ratio = viewport.width / viewport.height;\n // Allow small tolerance for \"square\" classification\n if (Math.abs(ratio - 1) < 0.05) return 'square';\n return ratio > 1 ? 'landscape' : 'portrait';\n}\n\n/**\n * Get aspect ratio as a string (e.g., \"16:9\", \"9:16\", \"1:1\").\n */\nexport function getAspectRatioString(viewport: ViewportConfig): string {\n const gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b));\n const divisor = gcd(viewport.width, viewport.height);\n return `${viewport.width / divisor}:${viewport.height / divisor}`;\n}\n","/**\n * Layout Strategy\n *\n * Provides orientation-specific layout hints for slide templates.\n * Templates use these hints to position content appropriately for\n * different aspect ratios (landscape, portrait, square).\n *\n * Key adaptations by orientation:\n * - Portrait: Larger relative text, stacked layouts instead of side-by-side\n * - Square: Balanced layout with moderate adjustments\n * - Landscape: Default/reference layout\n */\n\nimport type { ViewportOrientation, ViewportConfig } from './Viewport.js';\nimport { calculateFontScale } from './Viewport.js';\n\n/**\n * Layout hints for template positioning based on orientation.\n */\nexport interface LayoutHints {\n /** Primary content Y position (percentage string, e.g., \"35%\") */\n primaryY: string;\n /** Secondary content Y position (percentage string) */\n secondaryY: string;\n /** Tertiary/detail Y position (percentage string) */\n tertiaryY: string;\n /** Title font size multiplier (relative to base) */\n titleScale: number;\n /** Body text font size multiplier (relative to base) */\n bodyScale: number;\n /** Two-column: left X position (percentage string) */\n columnLeftX: string;\n /** Two-column: right X position (percentage string) */\n columnRightX: string;\n /** Two-column in portrait: top Y position for stacked layout */\n columnTopY: string;\n /** Two-column in portrait: bottom Y position for stacked layout */\n columnBottomY: string;\n /** Whether two-column should stack vertically */\n stackColumns: boolean;\n /** Max text width (percentage string) for wrapping */\n maxTextWidth: string;\n /** Horizontal padding from edges (percentage string) */\n horizontalPadding: string;\n /** Caption position Y for imageWithCaption (percentage string) */\n captionY: string;\n /** Caption font size multiplier */\n captionScale: number;\n}\n\n/**\n * Layout configurations by orientation.\n */\nconst LAYOUT_CONFIGS: Record<ViewportOrientation, LayoutHints> = {\n landscape: {\n primaryY: '35%',\n secondaryY: '55%',\n tertiaryY: '75%',\n titleScale: 1.0,\n bodyScale: 1.0,\n columnLeftX: '25%',\n columnRightX: '75%',\n columnTopY: '35%',\n columnBottomY: '65%',\n stackColumns: false,\n maxTextWidth: '80%',\n horizontalPadding: '10%',\n captionY: '85%',\n captionScale: 1.0,\n },\n portrait: {\n // Content positioned higher for thumb-reach zone on mobile\n primaryY: '25%',\n secondaryY: '42%',\n tertiaryY: '58%',\n // Titles scale down for narrower width; body text scales up to fill vertical space\n titleScale: 0.7,\n bodyScale: 1.5,\n // Stacked columns instead of side-by-side\n columnLeftX: '50%',\n columnRightX: '50%',\n columnTopY: '30%',\n columnBottomY: '55%',\n stackColumns: true,\n // Wider relative to viewport to use available space\n maxTextWidth: '90%',\n horizontalPadding: '5%',\n captionY: '75%',\n captionScale: 0.85,\n },\n square: {\n primaryY: '30%',\n secondaryY: '50%',\n tertiaryY: '70%',\n titleScale: 0.85,\n bodyScale: 0.9,\n columnLeftX: '25%',\n columnRightX: '75%',\n columnTopY: '35%',\n columnBottomY: '65%',\n stackColumns: false,\n maxTextWidth: '85%',\n horizontalPadding: '7.5%',\n captionY: '82%',\n captionScale: 0.9,\n },\n};\n\n/**\n * Get layout hints for a given orientation.\n */\nexport function getLayoutHints(orientation: ViewportOrientation): LayoutHints {\n return LAYOUT_CONFIGS[orientation];\n}\n\n/**\n * Calculate a scaled font size based on viewport and layout hints.\n *\n * @param basePx - Base font size in pixels (designed for 1920x1080)\n * @param viewport - Target viewport configuration\n * @param orientation - Viewport orientation\n * @param isTitle - Whether this is title text (uses titleScale) or body (uses bodyScale)\n */\nexport function scaledFontSize(\n basePx: number,\n viewport: ViewportConfig,\n orientation: ViewportOrientation,\n isTitle: boolean = false,\n): number {\n const fontScale = calculateFontScale(viewport);\n const layout = getLayoutHints(orientation);\n const typeScale = isTitle ? layout.titleScale : layout.bodyScale;\n return Math.round(basePx * fontScale * typeScale);\n}\n\n/**\n * Get position for two-column layout based on orientation.\n * Returns { left: {x, y}, right: {x, y} } positions.\n */\nexport function getTwoColumnPositions(orientation: ViewportOrientation): {\n left: { x: string; y: string };\n right: { x: string; y: string };\n} {\n const layout = getLayoutHints(orientation);\n\n if (layout.stackColumns) {\n // Portrait: stack vertically\n return {\n left: { x: '50%', y: layout.columnTopY },\n right: { x: '50%', y: layout.columnBottomY },\n };\n }\n\n // Landscape/square: side by side\n return {\n left: { x: layout.columnLeftX, y: '50%' },\n right: { x: layout.columnRightX, y: '50%' },\n };\n}\n\n/**\n * Get safe text bounds for a given orientation.\n * Returns margins to keep text away from edges.\n */\nexport function getSafeTextBounds(orientation: ViewportOrientation): {\n left: string;\n right: string;\n top: string;\n bottom: string;\n} {\n const layout = getLayoutHints(orientation);\n return {\n left: layout.horizontalPadding,\n right: layout.horizontalPadding,\n top: '10%',\n bottom: '10%',\n };\n}\n","/**\n * Block Templates Schema\n *\n * Defines a template system for AI-generated doc blocks. Instead of\n * specifying low-level layers, positions, and animations, AI can use\n * high-level templates like \"title\" or \"statHighlight\" with simple\n * content parameters.\n *\n * Templates are expanded into full Block structures at render time,\n * ensuring consistent styling and reducing AI generation errors.\n *\n * Template Types:\n * - title: Doc intro with title + subtitle\n * - sectionHeader: Colored section divider\n * - statHighlight: Big number/stat with description\n * - quote: Large centered quote\n * - factCard: Key fact with explanation\n * - twoColumn: Side-by-side comparison\n * - dateEvent: Timeline-style date + description\n * - imageWithCaption: Image with text overlay\n */\n\nimport type { Layer, Block, Transition, MapTileStyle, MapMarker, MarkerStyle } from './Doc.js';\nimport type { ViewportConfig, ViewportOrientation } from './Viewport.js';\nimport type { LayoutHints } from './LayoutStrategy.js';\nimport type { Theme } from './Theme.js';\nimport { VIEWPORT_PRESETS, getViewportOrientation, calculateFontScale } from './Viewport.js';\nimport { getLayoutHints, scaledFontSize as scaleFontSize } from './LayoutStrategy.js';\n\n/**\n * Name of a color scheme defined in the active theme's `colorSchemes` map.\n * Templates reference schemes by name (e.g. 'blue', 'green') and the theme\n * resolves them to actual colors at render time via `resolveColorScheme()`.\n */\nexport type ColorScheme = string;\n\n/**\n * Default font family for doc text.\n * PT Serif provides a classic, readable appearance for documentary-style content.\n * Used as a fallback in TextLayer when no font is specified.\n */\nexport const DEFAULT_DOC_FONT = 'system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif';\n\n/**\n * Default font family for titles.\n */\nexport const DEFAULT_TITLE_FONT = 'Georgia, \"Times New Roman\", serif';\n\n// ============================================\n// Accent Image System\n// ============================================\n\n/**\n * Position options for accent images on text-based blocks.\n * Accents are tasteful image additions that complement rather than overwhelm text.\n */\nexport type AccentPosition =\n | 'left-strip' // Vertical strip on left (25-30% width), text shifts right\n | 'right-strip' // Vertical strip on right (25-30% width), text shifts left\n | 'bottom-strip' // Horizontal strip at bottom (25-30% height), text shifts up\n | 'corner-inset'; // Small inset image in corner with vignette edge\n\n/**\n * Accent image configuration for text-based blocks.\n * When present, the template adjusts layout to accommodate the image tastefully.\n */\nexport interface AccentImage {\n /** Path to image file (relative to article media dir) */\n src: string;\n /** Alt text for accessibility */\n alt: string;\n /** Where to position the accent image */\n position: AccentPosition;\n /** Ambient motion effect (subtle Ken Burns) */\n ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';\n /** Photo credit / artist name */\n credit?: string;\n /** License identifier (e.g., 'CC BY-SA 4.0') */\n license?: string;\n}\n\n// ============================================\n// Template Input Types\n// ============================================\n\n/**\n * Base properties shared by all template blocks.\n */\ninterface BaseTemplateBlock {\n /** Unique block ID */\n id: string;\n /** Block duration in seconds */\n duration: number;\n /** Which audio segment this block belongs to */\n audioSegment: number;\n /** Entry transition */\n transition?: Transition;\n /** Show doc's bottom layers (default: true) */\n useBottomLayer?: boolean;\n /** Show doc's top layers (default: true) */\n useTopLayer?: boolean;\n /**\n * Start time within the audio segment when this content is spoken.\n * Used to sync the block appearance with the narration.\n * If not provided, blocks are distributed evenly across the segment.\n */\n sourceStartTime?: number;\n /**\n * Duration of the spoken content this block represents.\n * Used with sourceStartTime to precisely position the block.\n */\n sourceDuration?: number;\n /**\n * Per-block override for the theme's photographic image grade\n * (`theme.style.imageTreatment`). `'none'` opts this block's imagery out;\n * a treatment type forces that grade regardless of the theme.\n */\n imageTreatment?: 'none' | 'mono' | 'duotone' | 'warm' | 'cool';\n}\n\n/**\n * Title block - doc intro with large title and subtitle.\n */\nexport interface TitleBlockInput extends BaseTemplateBlock {\n template: 'title';\n /** Main title text */\n title: string;\n /** Subtitle or tagline (supports \\n for line breaks) */\n subtitle?: string;\n /** Background color (defaults to theme primary) */\n backgroundColor?: string;\n}\n\n/**\n * Section header - section title card with optional background image.\n * Displays the section title prominently, optionally over an image.\n */\nexport interface SectionHeaderInput extends BaseTemplateBlock {\n template: 'sectionHeader';\n /** Section title */\n title: string;\n /** Color scheme for the section (used for fallback background) */\n colorScheme?: ColorScheme;\n /** Optional background image path */\n imageSrc?: string;\n /** Alt text for background image */\n imageAlt?: string;\n /** Ambient motion for background image */\n ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';\n}\n\n/**\n * Stat highlight - big number with description and optional detail.\n */\nexport interface StatHighlightInput extends BaseTemplateBlock {\n template: 'statHighlight';\n /** The statistic (e.g., \"89%\", \"2x\", \"7 miles\") */\n stat: string;\n /** Description of what the stat means */\n description: string;\n /** Additional detail or context */\n detail?: string;\n /** Color scheme for the stat */\n colorScheme?: ColorScheme;\n /** Optional accent image to complement the text */\n accentImage?: AccentImage;\n}\n\n/**\n * Quote block - large centered quote text.\n */\nexport interface QuoteBlockInput extends BaseTemplateBlock {\n template: 'quote';\n /** The quote text (supports \\n for line breaks) */\n quote: string;\n /** Attribution (author, source) */\n attribution?: string;\n /** Optional accent image to complement the text */\n accentImage?: AccentImage;\n}\n\n/**\n * Fact card - key fact with explanation.\n */\nexport interface FactCardInput extends BaseTemplateBlock {\n template: 'factCard';\n /** The main fact or statement */\n fact: string;\n /** Explanation or context */\n explanation: string;\n /** Optional source citation */\n source?: string;\n /** Optional accent image to complement the text */\n accentImage?: AccentImage;\n}\n\n/**\n * Two column - side-by-side comparison.\n */\nexport interface TwoColumnInput extends BaseTemplateBlock {\n template: 'twoColumn';\n /** Left column content */\n left: {\n label: string;\n sublabel?: string;\n };\n /** Right column content */\n right: {\n label: string;\n sublabel?: string;\n };\n /** Optional header above columns */\n header?: string;\n /** Color schemes for left and right */\n leftColor?: ColorScheme;\n rightColor?: ColorScheme;\n}\n\n/**\n * Date event - timeline-style date with description.\n */\nexport interface DateEventInput extends BaseTemplateBlock {\n template: 'dateEvent';\n /** The date (e.g., \"July 14, 1974\") */\n date: string;\n /** Description of what happened (supports \\n) */\n description: string;\n /** Optional footer text */\n footer?: string;\n /** Mood: 'neutral', 'somber', 'celebratory' */\n mood?: 'neutral' | 'somber' | 'celebratory';\n /** Optional accent image to complement the text */\n accentImage?: AccentImage;\n}\n\n/**\n * Image with caption - background image with text overlay.\n */\nexport interface ImageWithCaptionInput extends BaseTemplateBlock {\n template: 'imageWithCaption';\n /** Path to image file */\n imageSrc: string;\n /** Alt text for accessibility */\n imageAlt: string;\n /** Caption text */\n caption?: string;\n /** Caption position */\n captionPosition?: 'bottom' | 'top' | 'center';\n /** Ambient motion effect: slow zoom or pan for visual interest */\n ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';\n /** When true, style caption as a title (larger, centered, prominent) */\n isTitle?: boolean;\n /** Optional subtitle shown below the title (only when isTitle is true) */\n subtitle?: string;\n /** Photo credit / artist name */\n imageCredit?: string;\n /** License identifier (e.g., 'CC BY-SA 4.0') */\n imageLicense?: string;\n}\n\n/**\n * Shared fields for left/right feature blocks. A \"feature\" pairs an\n * image on one side of the block with a title and body paragraph on\n * the other — a common editorial layout for product highlights,\n * profile callouts, or section intros.\n */\ninterface FeatureInputBase extends BaseTemplateBlock {\n /** Path to the feature image. */\n imageSrc: string;\n /** Alt text for accessibility. */\n imageAlt?: string;\n /**\n * Explicit display width of the image, in pixels (typically pulled\n * from a `<img width>` attribute when the WYSIWYG editor resized it).\n * When set, the renderer treats the image as a sized asset and\n * centers it inside its half with padding instead of stretching it\n * to fill the cell.\n */\n imageWidth?: number;\n /** Explicit display height of the image, in pixels. */\n imageHeight?: number;\n /** Heading / title text shown next to the image. */\n title?: string;\n /** Body text shown below the title. */\n body?: string;\n}\n\n/**\n * Left feature — image on the left, title + body stacked on the right.\n */\nexport interface LeftFeatureInput extends FeatureInputBase {\n template: 'leftFeature';\n}\n\n/**\n * Right feature — image on the right, title + body stacked on the left.\n */\nexport interface RightFeatureInput extends FeatureInputBase {\n template: 'rightFeature';\n}\n\n/**\n * Map block - geographic map showing article location.\n *\n * Displays a map centered on given coordinates with optional title,\n * caption, and markers. Great for establishing geographic context.\n * See docs/MAP_TILES.md for available tile styles and attribution.\n */\nexport interface MapBlockInput extends BaseTemplateBlock {\n template: 'map';\n /** Map center coordinates */\n center: {\n lat: number;\n lng: number;\n };\n /** Zoom level (4-16, typically 8-12 for regional context) */\n zoom: number;\n /** Map tile style (default: 'terrain') */\n mapStyle?: MapTileStyle;\n /** Optional title overlay at top */\n title?: string;\n /** Optional caption at bottom */\n caption?: string;\n /** Optional markers to show on map */\n markers?: MapMarker[];\n /** Ambient motion effect: slow zoom or pan for visual interest */\n ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';\n /** Pre-rendered static image path (set by prerender-maps command) */\n staticSrc?: string;\n}\n\n/**\n * Full-bleed quote - dramatic short text filling the viewport.\n * Like a movie title card. For short, punchy sentences < 60 chars.\n */\nexport interface FullBleedQuoteInput extends BaseTemplateBlock {\n template: 'fullBleedQuote';\n /** Short dramatic text (< 60 chars recommended) */\n text: string;\n /** Color scheme for text tint */\n colorScheme?: ColorScheme;\n}\n\n/**\n * List block - 3-5 items stacked vertically with subtle numbering.\n * Good for enumerations like \"things to see\" or \"key features\".\n */\nexport interface ListBlockInput extends BaseTemplateBlock {\n template: 'list';\n /** List items (3-5 recommended) */\n items: string[];\n /** Optional header above the list */\n title?: string;\n /** Color scheme for numbering accents */\n colorScheme?: ColorScheme;\n /** Optional accent image */\n accentImage?: AccentImage;\n}\n\n/**\n * Photo grid - 2-4 images in a tiled layout.\n * Used when multiple images are available for visual variety.\n */\nexport interface PhotoGridInput extends BaseTemplateBlock {\n template: 'photoGrid';\n /** Images to display (2-4) */\n images: { src: string; alt: string; credit?: string; license?: string }[];\n /** Optional caption below the grid */\n caption?: string;\n /** Ambient motion effect for the largest image */\n ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';\n}\n\n/**\n * Definition card - dictionary-style term with definition.\n * Large term at top, definition below, optional origin note.\n */\nexport interface DefinitionCardInput extends BaseTemplateBlock {\n template: 'definitionCard';\n /** The term being defined */\n term: string;\n /** The definition text */\n definition: string;\n /** Optional etymology or origin note */\n origin?: string;\n /** Color scheme for term accent */\n colorScheme?: ColorScheme;\n /** Optional accent image */\n accentImage?: AccentImage;\n}\n\n/**\n * Comparison bar - two horizontal bars showing relative numeric values.\n * Good for side-by-side numeric comparisons with visual weight.\n */\nexport interface ComparisonBarInput extends BaseTemplateBlock {\n template: 'comparisonBar';\n /** Left bar label */\n leftLabel: string;\n /** Left bar numeric value */\n leftValue: number;\n /** Right bar label */\n rightLabel: string;\n /** Right bar numeric value */\n rightValue: number;\n /** Unit label (e.g., \"km\", \"people\") */\n unit?: string;\n /** Color scheme */\n colorScheme?: ColorScheme;\n}\n\n/**\n * Pull quote - quote text over a full-bleed background image with dark overlay.\n * Cinematic alternative to quoteBlock when a high-quality image is available.\n */\nexport interface PullQuoteInput extends BaseTemplateBlock {\n template: 'pullQuote';\n /** Quote text */\n text: string;\n /** Optional attribution */\n attribution?: string;\n /** Background image (fills entire viewport) */\n backgroundImage: { src: string; alt: string; credit?: string; license?: string };\n /** Ambient motion for background image */\n ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';\n}\n\n/**\n * Video with caption - background video clip with text overlay.\n * Plays a short clip from a source video, always muted (narration is the audio track).\n * Mirrors ImageWithCaptionInput but with video-specific fields.\n */\nexport interface VideoWithCaptionInput extends BaseTemplateBlock {\n template: 'videoWithCaption';\n /** Path to video file */\n videoSrc: string;\n /** Path to poster frame (shown before video loads) */\n posterSrc?: string;\n /** Alt text for accessibility */\n videoAlt: string;\n /** Start time within source video (seconds) */\n clipStart: number;\n /** End time within source video (seconds) */\n clipEnd: number;\n /** Source video total duration (for validation) */\n sourceDuration?: number;\n /** Caption text overlay */\n caption?: string;\n /** Caption position */\n captionPosition?: 'bottom' | 'top' | 'center';\n /** Video credit / artist name */\n videoCredit?: string;\n /** License identifier */\n videoLicense?: string;\n}\n\n/**\n * Video pull quote - quote text over a video clip background with dark overlay.\n * Cinematic alternative to pullQuote when a video clip is available.\n */\nexport interface VideoPullQuoteInput extends BaseTemplateBlock {\n template: 'videoPullQuote';\n /** Quote text */\n text: string;\n /** Optional attribution */\n attribution?: string;\n /** Background video clip */\n backgroundVideo: {\n src: string;\n posterSrc?: string;\n alt: string;\n clipStart: number;\n clipEnd: number;\n credit?: string;\n license?: string;\n };\n}\n\n/**\n * Diagram block - renders child headings as a node-and-edge diagram.\n *\n * Unlike other templates, the diagram template reads its content from\n * the parent block's `children` (passed via `context.children`) — each\n * child heading becomes a node, positioned by its `x`/`y` and connected\n * by its `connectsTo`. Per-diagram options on this input control overall\n * appearance; per-node data lives on the child blocks themselves.\n */\nexport interface DiagramBlockInput extends BaseTemplateBlock {\n template: 'diagram';\n /** Optional diagram title displayed above the canvas. */\n title?: string;\n /** Color scheme for nodes and edges. */\n colorScheme?: ColorScheme;\n /** Node shape style (default: 'rounded'). */\n nodeShape?: 'rounded' | 'rect' | 'pill';\n /** Edge routing (default: 'curved'). */\n edgeStyle?: 'curved' | 'straight' | 'orthogonal';\n /** Marker at each edge's start (default: 'none'). */\n startStyle?: MarkerStyle;\n /** Marker at each edge's end (default: 'arrow'). */\n endStyle?: MarkerStyle;\n /** Edge line style (default: 'solid'). */\n lineStyle?: 'solid' | 'dashed' | 'dotted';\n}\n\n/**\n * Free-form 2D canvas block (`layout`). Like `drawing`/`diagram`, the\n * `layout` template is a children-driven container: each child heading is\n * one absolutely-positioned layer (`{[text …]}` / `{[rectangle …]}` /\n * `{[image …]}`), read from `context.children`. A text box's content is the\n * child's body markdown. Backed by the `layoutBlock` template.\n *\n * (Legacy documents stored layers as a base64-JSON `layers=` param on the\n * heading; the editor auto-migrates those to child sub-blocks on first edit.)\n */\nexport interface RawLayersInput extends BaseTemplateBlock {\n template: 'layout';\n}\n\n/**\n * Drawing block — renders child headings as free-form shapes on a canvas.\n *\n * Like `diagram`, the drawing template reads its content from the parent\n * block's `children` (passed via `context.children`). Each child heading\n * is a shape: its `{[shape …]}` annotation names the primitive\n * (`rectangle`/`rect`, `circle`, `line`, `arrow`, `path`, `text`) and\n * carries geometry/style as params; its `{#id}` makes it referenceable;\n * its heading text is the shape label and its body text an optional\n * sublabel. Connectors (`line`/`arrow` with `from`/`to`) join shapes by id.\n *\n * Coordinates are author-defined units; the template fits the bounding box\n * of all shapes to the viewport (same scaling as `diagram`), so authors\n * don't have to think in absolute pixels.\n *\n * Legacy drawings authored visually persist their `Layer[]` as a base64\n * `layers=\"…\"` Pandoc param instead of children; the template decodes and\n * returns those directly when no children are present (see `drawingBlock`).\n */\nexport interface DrawingBlockInput extends BaseTemplateBlock {\n template: 'drawing';\n /** Optional title displayed above the canvas. */\n title?: string;\n /** Color scheme for shapes that don't pin their own fill/stroke. */\n colorScheme?: ColorScheme;\n /** Default fill for shapes that don't specify one (default: 'none'). */\n fill?: string;\n /** Default stroke for shapes that don't specify one (default: theme text). */\n stroke?: string;\n}\n\n/**\n * Data table - renders a themed table with headers and rows.\n * Ideal for structured data, comparisons, and reference information.\n */\nexport interface DataTableInput extends BaseTemplateBlock {\n template: 'dataTable';\n /** Optional title displayed above the table */\n title?: string;\n /** Header cell values */\n headers: string[];\n /** Data rows (array of cell value arrays) */\n rows: string[][];\n /** Per-column alignment */\n align?: (('left' | 'right' | 'center') | null)[];\n /** Color scheme for the table header */\n colorScheme?: ColorScheme;\n}\n\n/**\n * Union of all template block types.\n */\nexport type TemplateBlock =\n | TitleBlockInput\n | SectionHeaderInput\n | StatHighlightInput\n | QuoteBlockInput\n | FactCardInput\n | TwoColumnInput\n | DateEventInput\n | ImageWithCaptionInput\n | LeftFeatureInput\n | RightFeatureInput\n | MapBlockInput\n | FullBleedQuoteInput\n | ListBlockInput\n | PhotoGridInput\n | DefinitionCardInput\n | ComparisonBarInput\n | PullQuoteInput\n | VideoWithCaptionInput\n | VideoPullQuoteInput\n | DataTableInput\n | DiagramBlockInput\n | DrawingBlockInput\n | RawLayersInput;\n\n/**\n * A block can be either a raw Block or a TemplateBlock.\n */\nexport type DocBlock = Block | TemplateBlock;\n\n/**\n * Check if a block is a template block.\n */\nexport function isTemplateBlock(block: DocBlock): block is TemplateBlock {\n return 'template' in block && typeof block.template === 'string';\n}\n\n// ============================================\n// Template Function Type\n// ============================================\n\n/**\n * Template context passed to template functions.\n * Includes viewport information for multi-aspect ratio support.\n */\nexport interface TemplateContext {\n /** Full theme (colors, typography, style, renderStyle, colorSchemes) */\n theme: Theme;\n /** Block index in the doc */\n blockIndex: number;\n /** Total number of blocks */\n totalBlocks: number;\n /** Target viewport configuration */\n viewport: ViewportConfig;\n /** Computed font scale factor for this viewport */\n fontScale: number;\n /** Viewport orientation (landscape, portrait, square) */\n orientation: ViewportOrientation;\n /** Layout hints for this orientation */\n layout: LayoutHints;\n /**\n * The block's direct children (set by `getLayers` when the block has\n * `children`). Most templates ignore this; aggregate templates like\n * `diagram` consume it to render each child as part of their output.\n */\n children?: Block[];\n /**\n * The source block being expanded. Used by user-defined custom\n * templates so the token resolver can substitute `{title}`,\n * `{content}`, `{children}`, and `{image:N}` against the block's\n * data at render time. Built-in templates ignore this and rely on\n * their typed `input` parameters instead.\n */\n block?: Block;\n}\n\n/**\n * Create a template context with viewport-derived values.\n */\nexport function createTemplateContext(\n theme: Theme,\n blockIndex: number,\n totalBlocks: number,\n viewport: ViewportConfig = VIEWPORT_PRESETS.landscape,\n): TemplateContext {\n const orientation = getViewportOrientation(viewport);\n // Theme renderStyle may override individual layout hints (e.g. a theme\n // that wants a lower primaryY across every template).\n const overrides = theme.renderStyle.layoutOverrides;\n const baseLayout = getLayoutHints(orientation);\n return {\n theme,\n blockIndex,\n totalBlocks,\n viewport,\n fontScale: calculateFontScale(viewport),\n orientation,\n layout: overrides ? { ...baseLayout, ...overrides } : baseLayout,\n };\n}\n\n/**\n * Calculate a scaled font size for the given context.\n * Use this in templates to ensure text scales appropriately.\n *\n * @param basePx - Base font size in pixels (designed for 1920x1080)\n * @param context - Template context with viewport info\n * @param isTitle - Whether this is title text (larger scale) or body text\n */\nexport function scaledFontSize(\n basePx: number,\n context: TemplateContext,\n isTitle: boolean = false,\n): number {\n return scaleFontSize(basePx, context.viewport, context.orientation, isTitle);\n}\n\n/**\n * Template function signature.\n * Takes template input and returns layers array.\n */\nexport type TemplateFunction<T extends TemplateBlock> = (\n input: T,\n context: TemplateContext,\n) => Layer[];\n\n/**\n * Registry of all template functions.\n */\nexport type TemplateRegistry = {\n [K in TemplateBlock['template']]: TemplateFunction<Extract<TemplateBlock, { template: K }>>;\n};\n\n// ============================================\n// Persistent Layer System\n// ============================================\n\n/**\n * Configuration for persistent doc-wide layers.\n * Bottom layers render behind all block content, top layers on top.\n */\nexport interface PersistentLayerConfig {\n /** Layers rendered behind all block content */\n bottomLayers?: PersistentLayer[];\n /** Layers rendered on top of all block content */\n topLayers?: PersistentLayer[];\n}\n\n/**\n * A persistent layer - either a template or raw Layer.\n */\nexport type PersistentLayer = PersistentLayerTemplate | Layer;\n\n/**\n * Check if a persistent layer is a template.\n */\nexport function isPersistentLayerTemplate(\n layer: PersistentLayer,\n): layer is PersistentLayerTemplate {\n return 'template' in layer && typeof layer.template === 'string';\n}\n\n/**\n * Template-based persistent layer for easy configuration.\n */\nexport interface PersistentLayerTemplate {\n template: PersistentLayerTemplateType;\n config: PersistentLayerTemplateConfig;\n}\n\n/**\n * Available persistent layer template types.\n */\nexport type PersistentLayerTemplateType =\n | 'solidBackground' // Solid color fill\n | 'gradientBackground' // CSS gradient or preset\n | 'imageBackground' // Blurred/faded hero image\n | 'patternBackground' // Subtle pattern (dots, grid, diagonal, noise/grain)\n | 'vignette' // Soft radial edge darkening (top layer)\n | 'ambientGradient' // Slowly drifting surface gradient (bottom layer)\n | 'titleCaption' // Article title in corner\n | 'cornerBranding' // Logo or text badge\n | 'progressIndicator'; // Bar/dots showing position\n\n/**\n * Union of all persistent layer template configs.\n */\nexport type PersistentLayerTemplateConfig =\n | SolidBackgroundConfig\n | GradientBackgroundConfig\n | ImageBackgroundConfig\n | PatternBackgroundConfig\n | VignetteConfig\n | AmbientGradientConfig\n | TitleCaptionConfig\n | CornerBrandingConfig\n | ProgressIndicatorConfig;\n\n// ============================================\n// Background Layer Configs\n// ============================================\n\n/**\n * Solid color background.\n */\nexport interface SolidBackgroundConfig {\n type: 'solidBackground';\n /** CSS color value */\n color: string;\n}\n\n/**\n * Gradient background (CSS gradient or preset).\n */\nexport interface GradientBackgroundConfig {\n type: 'gradientBackground';\n /** Custom CSS gradient string */\n gradient?: string;\n /** Preset gradient name */\n preset?: 'dark-vignette' | 'radial-dark' | 'warm-sunset' | 'cool-blue' | 'earth-tones';\n}\n\n/**\n * Image background (blurred/faded hero image).\n */\nexport interface ImageBackgroundConfig {\n type: 'imageBackground';\n /** Path to image file */\n src: string;\n /** Blur radius in pixels (0-20) */\n blur?: number;\n /** Opacity (0-1) */\n opacity?: number;\n /** Ambient motion effect */\n ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';\n}\n\n/**\n * Pattern background (subtle repeating pattern).\n */\nexport interface PatternBackgroundConfig {\n type: 'patternBackground';\n /** Pattern type */\n pattern: 'dots' | 'grid' | 'diagonal' | 'noise';\n /** Pattern color */\n color?: string;\n /** Pattern opacity (0-1) */\n opacity?: number;\n /** Pattern scale multiplier */\n scale?: number;\n}\n\n/**\n * Soft radial vignette (top layer) — transparent center darkening toward\n * the frame edges. The classic film-look framing device.\n */\nexport interface VignetteConfig {\n type: 'vignette';\n /** Edge darkness 0-1 (default 0.3). */\n strength?: number;\n /** Vignette color (default black). */\n color?: string;\n}\n\n/**\n * Slowly drifting surface gradient (bottom layer). Colors default to the\n * theme's surface family at expansion time.\n */\nexport interface AmbientGradientConfig {\n type: 'ambientGradient';\n /** Gradient start color. */\n from?: string;\n /** Gradient end color. */\n to?: string;\n /** Drift loop duration in seconds (default 40). */\n duration?: number;\n}\n\n// ============================================\n// Overlay Layer Configs\n// ============================================\n\n/**\n * Title caption overlay (article title in corner).\n */\nexport interface TitleCaptionConfig {\n type: 'titleCaption';\n /** Title text */\n title: string;\n /** Subtitle text (e.g., short URL like \"qual..la/slug\") */\n subtitle?: string;\n /** Position on screen */\n position: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';\n /** Font size in pixels (default: 18) */\n fontSize?: number;\n /** Show thumbnail alongside title */\n showThumbnail?: boolean;\n /** Thumbnail image path */\n thumbnailSrc?: string;\n}\n\n/**\n * Corner branding overlay (logo or text badge).\n */\nexport interface CornerBrandingConfig {\n type: 'cornerBranding';\n /** Text content or image path */\n content: string;\n /** Position on screen */\n position: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';\n /** Whether content is an image path (vs text) */\n isImage?: boolean;\n}\n\n/**\n * Progress indicator overlay (bar or dots showing position).\n */\nexport interface ProgressIndicatorConfig {\n type: 'progressIndicator';\n /** Indicator style */\n style: 'bar' | 'dots' | 'fraction';\n /** Position on screen */\n position: 'bottom' | 'top';\n /** Indicator color */\n color?: string;\n}\n"],"mappings":"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","names":["scaledFontSize"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/markdown/htmlParse.ts","../src/markdown/utils.ts"],"sourcesContent":["/**\n * HTML ↔ HtmlNode Conversion\n *\n * Parses raw HTML strings into the squisq HtmlNode tree representation\n * using hast-util-from-html (parse5-backed), and serializes HtmlNode trees\n * back to HTML strings with a lightweight custom serializer.\n */\n\nimport { fromHtml } from 'hast-util-from-html';\nimport type { HtmlNode } from './types.js';\n\n// ============================================\n// Hast → HtmlNode conversion\n// ============================================\n\n/**\n * Mapping from hast DOM-style property names to HTML attribute names.\n * Only includes names where the property and attribute differ.\n */\nconst HAST_PROP_TO_ATTR: Record<string, string> = {\n className: 'class',\n htmlFor: 'for',\n httpEquiv: 'http-equiv',\n acceptCharset: 'accept-charset',\n};\n\n/** HTML void elements that are self-closing. */\nconst VOID_ELEMENTS = new Set([\n 'area',\n 'base',\n 'br',\n 'col',\n 'embed',\n 'hr',\n 'img',\n 'input',\n 'link',\n 'meta',\n 'param',\n 'source',\n 'track',\n 'wbr',\n]);\n\n/**\n * Convert a hast property name to an HTML attribute name.\n */\nfunction propertyToAttribute(prop: string): string {\n if (HAST_PROP_TO_ATTR[prop]) return HAST_PROP_TO_ATTR[prop];\n\n // data-* attributes: dataFooBar → data-foo-bar\n if (prop.length > 4 && prop.startsWith('data') && prop[4] >= 'A' && prop[4] <= 'Z') {\n return (\n 'data-' +\n prop\n .slice(4)\n .replace(/[A-Z]/g, (m) => '-' + m.toLowerCase())\n .replace(/^-/, '')\n );\n }\n\n // aria-* attributes: ariaLabel → aria-label\n if (prop.length > 4 && prop.startsWith('aria') && prop[4] >= 'A' && prop[4] <= 'Z') {\n return (\n 'aria-' +\n prop\n .slice(4)\n .replace(/[A-Z]/g, (m) => '-' + m.toLowerCase())\n .replace(/^-/, '')\n );\n }\n\n // Most HTML attributes are lowercase (tabIndex → tabindex, readOnly → readonly, etc.)\n return prop.toLowerCase();\n}\n\n/**\n * Convert hast element properties to a flat HTML attribute map.\n */\nfunction convertHastProperties(properties: Record<string, unknown>): Record<string, string> {\n const attrs: Record<string, string> = {};\n for (const [key, value] of Object.entries(properties)) {\n if (value === false || value === undefined || value === null) continue;\n\n const attrName = propertyToAttribute(key);\n\n if (value === true) {\n attrs[attrName] = '';\n } else if (Array.isArray(value)) {\n attrs[attrName] = value.join(' ');\n } else {\n attrs[attrName] = String(value);\n }\n }\n return attrs;\n}\n\n/**\n * Recursively convert hast child nodes to HtmlNode[].\n */\nfunction hastChildrenToNodes(children: unknown[]): HtmlNode[] {\n const result: HtmlNode[] = [];\n for (const child of children) {\n const node = hastNodeToHtmlNode(child as Record<string, unknown>);\n if (node) result.push(node);\n }\n return result;\n}\n\n/**\n * Convert a single hast node to an HtmlNode.\n */\nfunction hastNodeToHtmlNode(node: Record<string, unknown>): HtmlNode | null {\n switch (node.type) {\n case 'element': {\n const tagName = node.tagName as string;\n const properties = (node.properties ?? {}) as Record<string, unknown>;\n const children = (node.children ?? []) as unknown[];\n return {\n type: 'htmlElement',\n tagName,\n attributes: convertHastProperties(properties),\n children: hastChildrenToNodes(children),\n selfClosing: VOID_ELEMENTS.has(tagName) && children.length === 0,\n };\n }\n case 'text':\n return {\n type: 'htmlText',\n value: node.value as string,\n };\n case 'comment':\n return {\n type: 'htmlComment',\n value: node.value as string,\n };\n case 'doctype':\n // Doctype nodes are rare in markdown HTML; skip them\n return null;\n default:\n return null;\n }\n}\n\n// ============================================\n// Public API\n// ============================================\n\n/**\n * Parse a raw HTML string into an HtmlNode[] tree.\n *\n * Uses the HTML5 parsing algorithm (via parse5) for correct handling of\n * self-closing tags, optional end tags, entity decoding, etc.\n *\n * @param html - Raw HTML string to parse\n * @returns Array of top-level HtmlNode elements\n */\nexport function parseHtmlToNodes(html: string): HtmlNode[] {\n if (!html || !html.trim()) return [];\n\n try {\n const tree = fromHtml(html, { fragment: true });\n return hastChildrenToNodes(tree.children as unknown[]);\n } catch {\n // If parsing fails, return the raw text as a single text node\n return [{ type: 'htmlText', value: html }];\n }\n}\n\n/**\n * Escape special characters in HTML attribute values.\n */\nfunction escapeHtmlAttr(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n}\n\n/**\n * Serialize an HtmlNode back to an HTML string.\n */\nfunction stringifyNode(node: HtmlNode): string {\n switch (node.type) {\n case 'htmlElement': {\n const attrs = Object.entries(node.attributes)\n .map(([k, v]) => (v === '' ? k : `${k}=\"${escapeHtmlAttr(v)}\"`))\n .join(' ');\n const open = attrs ? `<${node.tagName} ${attrs}>` : `<${node.tagName}>`;\n\n if (node.selfClosing) return open;\n\n const inner = node.children.map(stringifyNode).join('');\n return `${open}${inner}</${node.tagName}>`;\n }\n case 'htmlText':\n return node.value;\n case 'htmlComment':\n return `<!--${node.value}-->`;\n }\n}\n\n/**\n * Serialize an HtmlNode[] tree back to an HTML string.\n *\n * @param nodes - Array of HtmlNode elements to serialize\n * @returns HTML string\n */\nexport function stringifyHtmlNodes(nodes: HtmlNode[]): string {\n return nodes.map(stringifyNode).join('');\n}\n","/**\n * Markdown Tree Utilities\n *\n * Helper functions for traversing and querying the MarkdownDocument tree.\n * All functions are pure and operate on the JSON node interfaces.\n */\n\nimport type { MarkdownNode, MarkdownDocument, HtmlNode } from './types.js';\nimport { parseHtmlToNodes } from './htmlParse.js';\n\n/**\n * Get the children of any markdown node, if it has children.\n * Returns an empty array for leaf nodes (text, code, break, etc.).\n *\n * This is useful for generic tree walking where you don't want to\n * check the specific node type.\n */\nexport function getChildren(node: MarkdownNode): MarkdownNode[] {\n if ('children' in node && Array.isArray(node.children)) {\n return node.children as MarkdownNode[];\n }\n return [];\n}\n\n/**\n * Walk the markdown tree depth-first, calling the visitor for each node.\n *\n * The visitor receives the current node and its parent. Return `true`\n * from the visitor to skip the node's children (prune).\n *\n * @param node - The root node to start walking from\n * @param visitor - Called for each node; return true to skip children\n * @param parent - (internal) Parent node\n */\nexport function walkMarkdownTree(\n node: MarkdownNode,\n visitor: (node: MarkdownNode, parent?: MarkdownNode) => void | boolean,\n parent?: MarkdownNode,\n): void {\n const skip = visitor(node, parent);\n if (skip === true) return;\n\n const children = getChildren(node);\n for (const child of children) {\n walkMarkdownTree(child, visitor, node);\n }\n}\n\n/**\n * Find all nodes of a specific type in the tree.\n *\n * @param root - The document or node to search within\n * @param type - The node type to find (e.g., 'heading', 'link', 'text')\n * @returns Array of matching nodes\n *\n * @example\n * ```ts\n * const headings = findNodesByType(doc, 'heading');\n * const links = findNodesByType(doc, 'link');\n * ```\n */\nexport function findNodesByType<T extends MarkdownNode>(root: MarkdownNode, type: T['type']): T[] {\n const results: T[] = [];\n walkMarkdownTree(root, (node) => {\n if (node.type === type) {\n results.push(node as T);\n }\n });\n return results;\n}\n\n/**\n * Extract all plain text content from a node and its descendants.\n * Concatenates text values, ignoring formatting, links, etc.\n *\n * @param node - The node to extract text from\n * @returns Plain text content\n *\n * @example\n * ```ts\n * const heading = doc.children[0]; // { type: 'heading', children: [{ type: 'text', value: 'Hello' }] }\n * extractPlainText(heading); // 'Hello'\n * ```\n */\nexport function extractPlainText(node: MarkdownNode): string {\n if ('value' in node && typeof node.value === 'string') {\n return node.value;\n }\n\n const children = getChildren(node);\n // Preserve boundaries between block-level elements (list items, paragraphs\n // inside list items, blockquotes) so downstream consumers like caption\n // splitting can treat each item as a separate phrase.\n const separator = node.type === 'list' || node.type === 'listItem' ? '\\n' : '';\n return children.map(extractPlainText).join(separator);\n}\n\n/**\n * Count the total number of nodes in the tree.\n *\n * @param node - The root node\n * @returns Total node count (including the root)\n */\nexport function countNodes(node: MarkdownNode): number {\n let count = 1;\n const children = getChildren(node);\n for (const child of children) {\n count += countNodes(child);\n }\n return count;\n}\n\n/**\n * Parse a YAML frontmatter string into a key-value record.\n *\n * Handles simple `key: value` pairs common in markdown frontmatter.\n * Values are trimmed; quoted strings have their quotes removed.\n * Returns `null` if parsing fails or the input is empty.\n *\n * @param yaml - The raw YAML string (without the `---` delimiters)\n * @returns A record of string keys to parsed values, or null\n */\nexport function parseFrontmatter(yaml: string): Record<string, unknown> | null {\n if (!yaml || !yaml.trim()) return null;\n\n const result: Record<string, unknown> = {};\n const lines = yaml.split('\\n');\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n\n const colonIdx = trimmed.indexOf(':');\n if (colonIdx < 1) continue;\n\n const key = trimmed.slice(0, colonIdx).trim();\n let value: string | boolean | number = trimmed.slice(colonIdx + 1).trim();\n\n // YAML literal block scalar: `key: |` or `key: |-`. Subsequent lines\n // that are more-indented than the key belong to the scalar; they are\n // dedented by their common leading indent and joined with `\\n`. A line\n // at the key's indent (or less) ends the scalar. Both `|` and `|-`\n // yield the joined string here (`|-` without a trailing newline); we do\n // not model `|`'s trailing-newline / `+`/`-` chomping nuances beyond\n // stripping trailing blank lines.\n if (value === '|' || value === '|-') {\n const keyIndent = line.length - line.trimStart().length;\n const blockLines: string[] = [];\n let j = i + 1;\n for (; j < lines.length; j++) {\n const bl = lines[j];\n if (bl.trim() === '') {\n blockLines.push('');\n continue;\n }\n const indent = bl.length - bl.trimStart().length;\n if (indent <= keyIndent) break; // dedent ends the scalar\n blockLines.push(bl);\n }\n i = j - 1; // resume the outer walk at the terminating line\n\n // Drop trailing blank lines captured inside the scalar.\n while (blockLines.length > 0 && blockLines[blockLines.length - 1] === '') {\n blockLines.pop();\n }\n\n // Dedent by the common leading whitespace of the non-blank lines.\n let commonIndent = Infinity;\n for (const bl of blockLines) {\n if (bl === '') continue;\n commonIndent = Math.min(commonIndent, bl.length - bl.trimStart().length);\n }\n if (!isFinite(commonIndent)) commonIndent = 0;\n result[key] = blockLines.map((bl) => (bl === '' ? '' : bl.slice(commonIndent))).join('\\n');\n continue;\n }\n\n // Remove surrounding quotes\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n\n // Parse booleans and numbers\n if (value === 'true') {\n result[key] = true;\n } else if (value === 'false') {\n result[key] = false;\n } else if (value !== '' && !isNaN(Number(value))) {\n result[key] = Number(value);\n } else {\n result[key] = value;\n }\n }\n\n return Object.keys(result).length > 0 ? result : null;\n}\n\n/**\n * Format a multi-line string as a YAML literal block scalar body, i.e.\n * `|-` followed by each source line indented two spaces. Paired with\n * {@link parseFrontmatter}'s block-scalar reader so multi-line frontmatter\n * values (e.g. pretty-printed JSON payloads) round-trip. Blank lines stay\n * blank (no trailing whitespace) so the output diffs cleanly.\n */\nexport function formatBlockScalar(value: string): string {\n const body = value\n .split('\\n')\n .map((line) => (line === '' ? '' : ` ${line}`))\n .join('\\n');\n return `|-\\n${body}`;\n}\n\nconst FRONTMATTER_BLOCK_RE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---(\\r?\\n)?/;\n\n/** Quote a frontmatter scalar so it round-trips cleanly through `parseFrontmatter`. */\nfunction formatFrontmatterValue(value: string | number | boolean): string {\n if (typeof value === 'boolean' || typeof value === 'number') return String(value);\n // Multi-line strings can't live on one line — emit a YAML literal block\n // scalar (`|-`) that `parseFrontmatter` reads back. This carries pretty-\n // printed JSON payloads (opt-in custom templates/themes). Single-line\n // values keep their exact historical output below.\n if (/[\\r\\n]/.test(value)) return formatBlockScalar(value.replace(/\\r\\n?/g, '\\n'));\n // A single-line JSON object/array literal round-trips verbatim through\n // the line-based `parseFrontmatter` (no leading quote to strip, no\n // newline to split on), so write it unquoted to keep it human-readable\n // — matching how `stringifyMarkdown` emits string values. Used by the\n // compact custom-layouts payload.\n if (/^[{[]/.test(value) && !/[\\r\\n]/.test(value)) return value;\n // Quote when needed: leading/trailing whitespace, leading punctuation that\n // could trigger YAML modes, or values that look like reserved literals.\n const needsQuote =\n /^\\s|\\s$/.test(value) ||\n /^[!&*?|>%@`]/.test(value) ||\n /[:#]/.test(value) ||\n value === '' ||\n value === 'true' ||\n value === 'false' ||\n /^-?\\d+(\\.\\d+)?$/.test(value);\n if (!needsQuote) return value;\n // Prefer double quotes; escape any embedded double-quote / backslash.\n const escaped = value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n return `\"${escaped}\"`;\n}\n\n/**\n * Update a markdown source string's YAML frontmatter block, applying the\n * given key/value updates. A `null` or `undefined` value removes the key.\n * If no frontmatter block exists and any non-null update is supplied, a\n * new block is prepended; if every update is a removal and no block\n * exists, the source is returned unchanged.\n *\n * Existing key order is preserved; new keys are appended in the order\n * they appear in `updates`. The simple parser used here handles plain\n * `key: value` pairs only — comments, multi-line scalars, and nested\n * structures are left untouched (we only rewrite lines whose key matches\n * one in `updates`).\n *\n * @param source - The markdown source string.\n * @param updates - Map of key → new value (or null/undefined to remove).\n * @returns The updated markdown source string.\n */\nexport function setFrontmatterValues(\n source: string,\n updates: Record<string, string | number | boolean | null | undefined>,\n): string {\n const updateKeys = Object.keys(updates);\n if (updateKeys.length === 0) return source;\n\n const match = source.match(FRONTMATTER_BLOCK_RE);\n const handled = new Set<string>();\n\n // Existing frontmatter block — rewrite line-by-line, preserving order.\n if (match) {\n const inner = match[1];\n const lines = inner.split(/\\r?\\n/);\n const newLines: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) {\n newLines.push(line);\n continue;\n }\n const colonIdx = trimmed.indexOf(':');\n if (colonIdx < 1) {\n newLines.push(line);\n continue;\n }\n const key = trimmed.slice(0, colonIdx).trim();\n if (!Object.prototype.hasOwnProperty.call(updates, key)) {\n newLines.push(line);\n continue;\n }\n handled.add(key);\n const next = updates[key];\n if (next === null || next === undefined) continue; // remove\n newLines.push(`${key}: ${formatFrontmatterValue(next)}`);\n }\n\n // Append any keys that weren't already present.\n for (const key of updateKeys) {\n if (handled.has(key)) continue;\n const next = updates[key];\n if (next === null || next === undefined) continue;\n newLines.push(`${key}: ${formatFrontmatterValue(next)}`);\n }\n\n // If every line got removed, drop the block entirely.\n const nonEmpty = newLines.filter((l) => l.trim().length > 0);\n const rest = source.slice(match[0].length);\n if (nonEmpty.length === 0) return rest;\n return `---\\n${newLines.join('\\n')}\\n---\\n${rest}`;\n }\n\n // No existing block — only create one if there's at least one non-null\n // value to write.\n const fresh: string[] = [];\n for (const key of updateKeys) {\n const next = updates[key];\n if (next === null || next === undefined) continue;\n fresh.push(`${key}: ${formatFrontmatterValue(next)}`);\n }\n if (fresh.length === 0) return source;\n return `---\\n${fresh.join('\\n')}\\n---\\n${source}`;\n}\n\n/**\n * Create a minimal MarkdownDocument from a list of block nodes.\n * Convenience function for programmatic document construction.\n */\nexport function createDocument(...children: MarkdownDocument['children']): MarkdownDocument {\n return {\n type: 'document',\n children,\n };\n}\n\n/**\n * Infer a human-readable title for a markdown document.\n *\n * Resolution order:\n * 1. `title:` value in YAML frontmatter (when present and non-empty)\n * 2. Text of the first heading found, walking top-level depth (H1, then\n * H2, etc.) — the shallowest heading wins regardless of source order\n *\n * Returns `undefined` when no usable title is found.\n */\nexport function inferDocumentTitle(doc: MarkdownDocument): string | undefined {\n const fmTitle = doc.frontmatter?.title;\n if (typeof fmTitle === 'string') {\n const trimmed = fmTitle.trim();\n if (trimmed) return trimmed;\n }\n\n let best: { depth: number; text: string } | null = null;\n for (const node of doc.children) {\n if (node.type !== 'heading') continue;\n const text = extractPlainText(node).trim();\n if (!text) continue;\n if (best === null || node.depth < best.depth) {\n best = { depth: node.depth, text };\n if (best.depth === 1) break;\n }\n }\n\n return best?.text;\n}\n\n/**\n * Read a theme id from a markdown document's frontmatter.\n *\n * Checks the editor's canonical `squisq-theme` key first, then the\n * shorter legacy aliases (`themeId`, `theme`). Returns `undefined`\n * when none of them carry a non-empty string. Centralizing the lookup\n * keeps the export pipelines (DOCX, PPTX, HTML, plain-HTML bundle) in\n * sync with whatever the editor writes — adding a new frontmatter\n * spelling later is a one-file change.\n */\nexport function readFrontmatterThemeId(\n frontmatter: Record<string, unknown> | undefined,\n): string | undefined {\n if (!frontmatter) return undefined;\n const keys = ['squisq-theme', 'themeId', 'theme'] as const;\n for (const key of keys) {\n const raw = frontmatter[key];\n if (typeof raw === 'string') {\n const trimmed = raw.trim();\n if (trimmed) return trimmed;\n }\n }\n return undefined;\n}\n\n/**\n * Project an inline-HTML string (as stored on `TextLayer.content.html`) down\n * to plain text — the value mirrored into `content.text` for plain consumers\n * (PDF/markdown export, search, accessibility) and the SVG `<text>` fallback.\n *\n * `<br>` and block-level tags (`p`, `div`, `h1`–`h6`, `li`, `blockquote`,\n * `pre`) become line breaks so multi-line rich content reads sensibly. Entity\n * decoding is handled by {@link parseHtmlToNodes} (parse5-backed), so this is\n * more correct than a regex tag-stripper.\n */\nexport function plainTextFromInlineHtml(html: string): string {\n const BLOCK = new Set([\n 'p',\n 'div',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'li',\n 'blockquote',\n 'pre',\n ]);\n const walk = (nodes: HtmlNode[]): string =>\n nodes\n .map((n) => {\n if (n.type === 'htmlText') return n.value;\n if (n.type !== 'htmlElement') return '';\n const tag = n.tagName.toLowerCase();\n if (tag === 'br') return '\\n';\n const inner = walk(n.children);\n return BLOCK.has(tag) ? `${inner}\\n` : inner;\n })\n .join('');\n return walk(parseHtmlToNodes(html))\n .replace(/\\u00A0/g, ' ')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/storage/MemoryStorageAdapter.ts","../src/storage/LocalStorageAdapter.ts","../src/storage/LocalForageAdapter.ts","../src/storage/ContentContainer.ts","../src/storage/ScopedContentContainer.ts","../src/storage/MediaProviderFromContainer.ts"],"sourcesContent":["/**\n * In-memory storage adapter for testing and SSR environments.\n * Data does not persist across page reloads.\n */\n\nimport type { StorageAdapter } from './Storage.js';\n\nexport class MemoryStorageAdapter implements StorageAdapter {\n readonly supportsEnumeration = true;\n private store = new Map<string, string>();\n\n async get<T>(key: string): Promise<T | null> {\n const val = this.store.get(key);\n if (val === undefined) return null;\n try {\n return JSON.parse(val) as T;\n } catch {\n return null;\n }\n }\n\n async set<T>(key: string, value: T): Promise<void> {\n this.store.set(key, JSON.stringify(value));\n }\n\n async remove(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async clear(): Promise<void> {\n this.store.clear();\n }\n\n async keys(): Promise<string[]> {\n return Array.from(this.store.keys());\n }\n}\n","/**\n * localStorage adapter for web browsers.\n * Supports a configurable key prefix to namespace stored data.\n */\n\nimport type { StorageAdapter } from './Storage.js';\n\nexport class LocalStorageAdapter implements StorageAdapter {\n readonly supportsEnumeration = true;\n private prefix: string;\n\n constructor(prefix = '') {\n this.prefix = prefix;\n }\n\n async get<T>(key: string): Promise<T | null> {\n try {\n const raw = localStorage.getItem(this.prefix + key);\n if (raw === null) return null;\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n }\n\n async set<T>(key: string, value: T): Promise<void> {\n localStorage.setItem(this.prefix + key, JSON.stringify(value));\n }\n\n async remove(key: string): Promise<void> {\n localStorage.removeItem(this.prefix + key);\n }\n\n async clear(): Promise<void> {\n const keysToRemove: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const k = localStorage.key(i);\n if (k && k.startsWith(this.prefix)) {\n keysToRemove.push(k);\n }\n }\n for (const k of keysToRemove) {\n localStorage.removeItem(k);\n }\n }\n\n async keys(): Promise<string[]> {\n const result: string[] = [];\n for (let i = 0; i < localStorage.length; i++) {\n const k = localStorage.key(i);\n if (k && k.startsWith(this.prefix)) {\n result.push(k.slice(this.prefix.length));\n }\n }\n return result;\n }\n}\n","/**\n * LocalForage adapter - IndexedDB storage with automatic fallbacks.\n *\n * Uses localforage which provides IndexedDB storage with automatic\n * fallbacks to WebSQL and localStorage. This allows storing much more\n * data than raw localStorage (which is limited to ~5MB).\n *\n * Unlike LocalStorageAdapter, this adapter can store binary data\n * (ArrayBuffer, Blob, Uint8Array) natively without JSON serialization.\n *\n * Ported from qualla-internal/shared/services/StorageAdapters.ts with\n * configurable store name and key prefix.\n */\n\nimport localforage from 'localforage';\nimport type { StorageAdapter } from './Storage.js';\n\nexport interface LocalForageAdapterOptions {\n /** IndexedDB database name (default: 'squisq') */\n name?: string;\n /** IndexedDB object store name (default: 'data') */\n storeName?: string;\n /** Key prefix for namespacing (default: '') */\n prefix?: string;\n /** Human-readable description (default: 'Squisq storage') */\n description?: string;\n}\n\nexport class LocalForageAdapter implements StorageAdapter {\n readonly supportsEnumeration = true;\n private store: LocalForage;\n private prefix: string;\n\n constructor(options: LocalForageAdapterOptions = {}) {\n const {\n name = 'squisq',\n storeName = 'data',\n prefix = '',\n description = 'Squisq storage',\n } = options;\n\n this.prefix = prefix;\n this.store = localforage.createInstance({ name, storeName, description });\n }\n\n async get<T>(key: string): Promise<T | null> {\n try {\n const value = await this.store.getItem<T>(this.prefix + key);\n return value;\n } catch (e: unknown) {\n console.warn('[Storage] get (IndexedDB) failed:', key, e);\n return null;\n }\n }\n\n async set<T>(key: string, value: T): Promise<void> {\n try {\n await this.store.setItem(this.prefix + key, value);\n } catch (e: unknown) {\n console.error('[Storage] set (IndexedDB) failed:', key, e);\n throw e;\n }\n }\n\n async remove(key: string): Promise<void> {\n try {\n await this.store.removeItem(this.prefix + key);\n } catch (e: unknown) {\n console.warn('[Storage] remove (IndexedDB) failed:', key, e);\n }\n }\n\n async clear(): Promise<void> {\n try {\n if (this.prefix) {\n // Only remove keys with our prefix\n const keys = await this.store.keys();\n const keysToRemove = keys.filter((k) => k.startsWith(this.prefix));\n await Promise.all(keysToRemove.map((k) => this.store.removeItem(k)));\n } else {\n // No prefix — clear the entire store\n await this.store.clear();\n }\n } catch (e: unknown) {\n console.error('[Storage] clear (IndexedDB) failed:', e);\n }\n }\n\n async keys(): Promise<string[]> {\n try {\n const allKeys = await this.store.keys();\n if (this.prefix) {\n return allKeys\n .filter((k) => k.startsWith(this.prefix))\n .map((k) => k.slice(this.prefix.length));\n }\n return allKeys;\n } catch (e: unknown) {\n console.warn('[Storage] keys (IndexedDB) failed:', e);\n return [];\n }\n }\n}\n","/**\n * ContentContainer — virtual file system for document containers.\n *\n * A Squisq document is more than just markdown: it's a container of files\n * including the primary markdown document, images, audio, timing data, and\n * other media. ContentContainer provides an abstract async file system\n * interface for reading, writing, and listing these files.\n *\n * Paths are forward-slash separated strings relative to the container root\n * (e.g., 'images/hero.jpg', 'index.md'). No leading slash.\n *\n * Implementations (in core):\n * - MemoryContentContainer — in-memory Map (for zip import, tests, transient use)\n * - ScopedContentContainer — a prefix-scoped view onto a parent container\n */\n\n/**\n * Metadata about a file in a ContentContainer.\n */\nexport interface ContentEntry {\n /** Relative path within the container (e.g., 'images/hero.jpg') */\n path: string;\n /** MIME type (e.g., 'image/jpeg') */\n mimeType: string;\n /** File size in bytes */\n size: number;\n}\n\n/**\n * Abstract async file system for document containers.\n *\n * All paths are forward-slash separated, relative to the container root,\n * with no leading slash. Example: 'images/hero.jpg', 'index.md'.\n */\nexport interface ContentContainer {\n /** Read a file's binary content. Returns null if the file does not exist. */\n readFile(path: string): Promise<ArrayBuffer | null>;\n\n /** Write a file. Creates or overwrites. */\n writeFile(path: string, data: ArrayBuffer | Uint8Array, mimeType?: string): Promise<void>;\n\n /** Remove a file. No-op if the file does not exist. */\n removeFile(path: string): Promise<void>;\n\n /**\n * List files in the container.\n *\n * Returns file **entries** (`ContentEntry[]` with `.path`/`.mimeType`/`.size`),\n * NOT bare path strings — despite the name. Read `entry.path` for the path;\n * calling string methods (e.g. `.startsWith`) directly on an entry will throw.\n * @param prefix — Optional path prefix to filter by (e.g., 'images/')\n */\n listFiles(prefix?: string): Promise<ContentEntry[]>;\n\n /** Check whether a file exists. */\n exists(path: string): Promise<boolean>;\n\n /**\n * Find the primary markdown document path.\n *\n * Discovery order: index.md → doc.md → document.md → first *.md at root.\n * Returns null if no markdown file is found at the root level.\n */\n getDocumentPath(): Promise<string | null>;\n\n /**\n * Convenience: read the primary markdown document as a UTF-8 string.\n * Returns null if no markdown file is found.\n */\n readDocument(): Promise<string | null>;\n\n /**\n * Convenience: write a markdown document.\n * @param markdown — The markdown content\n * @param filename — Filename to use (defaults to 'index.md')\n */\n writeDocument(markdown: string, filename?: string): Promise<void>;\n}\n\n// ============================================\n// Well-known markdown filenames in priority order\n// ============================================\n\nconst MARKDOWN_PRIORITY = ['index.md', 'doc.md', 'document.md'];\n\n/**\n * Find the primary markdown path from a list of file entries.\n * Exported for reuse by other ContentContainer implementations.\n */\nexport function findDocumentPath(entries: ContentEntry[]): string | null {\n // Only consider root-level files (no '/' in path)\n const rootFiles = entries.filter((e) => !e.path.includes('/'));\n\n for (const name of MARKDOWN_PRIORITY) {\n if (rootFiles.some((e) => e.path.toLowerCase() === name)) {\n return name;\n }\n }\n\n // Fallback: first .md file at root\n const firstMd = rootFiles.find((e) => e.path.toLowerCase().endsWith('.md'));\n return firstMd?.path ?? null;\n}\n\n// ============================================\n// MemoryContentContainer\n// ============================================\n\ninterface MemoryFile {\n data: ArrayBuffer;\n mimeType: string;\n}\n\n/**\n * In-memory ContentContainer backed by a Map.\n *\n * Used for zip import (deserialize into memory), tests, and transient operations.\n */\nexport class MemoryContentContainer implements ContentContainer {\n private files = new Map<string, MemoryFile>();\n\n async readFile(path: string): Promise<ArrayBuffer | null> {\n return this.files.get(path)?.data ?? null;\n }\n\n async writeFile(path: string, data: ArrayBuffer | Uint8Array, mimeType?: string): Promise<void> {\n // Copy to a standalone ArrayBuffer. Uint8Array/Buffer may share a larger\n // backing buffer (e.g., Node's buffer pool), so we must slice to the\n // exact byte range to avoid storing stale pool data.\n let buffer: ArrayBuffer;\n if (data instanceof ArrayBuffer) {\n buffer = data;\n } else {\n buffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;\n }\n this.files.set(path, {\n data: buffer,\n mimeType: mimeType ?? guessMimeType(path),\n });\n }\n\n async removeFile(path: string): Promise<void> {\n this.files.delete(path);\n }\n\n async listFiles(prefix?: string): Promise<ContentEntry[]> {\n const entries: ContentEntry[] = [];\n for (const [path, file] of this.files) {\n if (prefix && !path.startsWith(prefix)) continue;\n entries.push({\n path,\n mimeType: file.mimeType,\n size: file.data.byteLength,\n });\n }\n return entries;\n }\n\n async exists(path: string): Promise<boolean> {\n return this.files.has(path);\n }\n\n async getDocumentPath(): Promise<string | null> {\n return findDocumentPath(await this.listFiles());\n }\n\n async readDocument(): Promise<string | null> {\n const docPath = await this.getDocumentPath();\n if (!docPath) return null;\n const data = await this.readFile(docPath);\n if (!data) return null;\n return new TextDecoder().decode(data);\n }\n\n async writeDocument(markdown: string, filename?: string): Promise<void> {\n const name = filename ?? 'index.md';\n const data = new TextEncoder().encode(markdown);\n await this.writeFile(name, data, 'text/markdown');\n }\n}\n\n// ============================================\n// MIME type guessing\n// ============================================\n\nconst EXTENSION_MIME_MAP: Record<string, string> = {\n '.md': 'text/markdown',\n '.txt': 'text/plain',\n '.json': 'application/json',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.png': 'image/png',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.mp4': 'video/mp4',\n '.webm': 'video/webm',\n '.mp3': 'audio/mpeg',\n '.wav': 'audio/wav',\n '.ogg': 'audio/ogg',\n '.css': 'text/css',\n '.html': 'text/html',\n '.js': 'application/javascript',\n};\n\nfunction guessMimeType(path: string): string {\n const dot = path.lastIndexOf('.');\n if (dot === -1) return 'application/octet-stream';\n const ext = path.slice(dot).toLowerCase();\n return EXTENSION_MIME_MAP[ext] ?? 'application/octet-stream';\n}\n","/**\n * ScopedContentContainer\n *\n * Wraps a parent {@link ContentContainer} and prefixes every path with a\n * fixed subfolder, presenting that subfolder as the root. Useful for\n * sidecar folders (e.g. `<imagebasename>_files/`) where a component\n * should be able to read/write/list files relative to its own root\n * without knowing where it lives in the parent tree.\n *\n * Scopes nest: `scopeContainer(scopeContainer(parent, 'a'), 'b')` is\n * equivalent to `scopeContainer(parent, 'a/b')`.\n *\n * `getDocumentPath()` always returns `null` for scoped sidecars — they\n * are not document roots.\n */\n\nimport type { ContentContainer, ContentEntry } from './ContentContainer.js';\n\n/** Normalize a prefix: strip leading/trailing slashes, ensure no `..` segments. */\nfunction normalizePrefix(prefix: string): string {\n let p = prefix.trim();\n while (p.startsWith('/')) p = p.slice(1);\n while (p.endsWith('/')) p = p.slice(0, -1);\n if (p.length === 0) {\n throw new Error('ScopedContentContainer: prefix must not be empty');\n }\n if (p.split('/').some((seg) => seg === '..' || seg === '.')) {\n throw new Error(\n `ScopedContentContainer: prefix must not contain '.' or '..' segments: ${prefix}`,\n );\n }\n return p;\n}\n\n/**\n * A ContentContainer view rooted at a subfolder of a parent container.\n * All paths passed to / returned from this container are relative to\n * the subfolder; the parent never sees an unprefixed path.\n */\nexport class ScopedContentContainer implements ContentContainer {\n private readonly parent: ContentContainer;\n /** Prefix without trailing slash. */\n readonly prefix: string;\n /** Prefix with trailing slash. */\n private readonly prefixSlash: string;\n\n constructor(parent: ContentContainer, prefix: string) {\n this.parent = parent;\n this.prefix = normalizePrefix(prefix);\n this.prefixSlash = `${this.prefix}/`;\n }\n\n private toParent(path: string): string {\n let p = path;\n while (p.startsWith('/')) p = p.slice(1);\n return this.prefixSlash + p;\n }\n\n /** Strip the prefix from a parent-space path. Returns null if the path is outside the scope. */\n private fromParent(parentPath: string): string | null {\n if (!parentPath.startsWith(this.prefixSlash)) return null;\n return parentPath.slice(this.prefixSlash.length);\n }\n\n readFile(path: string): Promise<ArrayBuffer | null> {\n return this.parent.readFile(this.toParent(path));\n }\n\n writeFile(path: string, data: ArrayBuffer | Uint8Array, mimeType?: string): Promise<void> {\n return this.parent.writeFile(this.toParent(path), data, mimeType);\n }\n\n removeFile(path: string): Promise<void> {\n return this.parent.removeFile(this.toParent(path));\n }\n\n async listFiles(prefix?: string): Promise<ContentEntry[]> {\n const parentPrefix = prefix ? this.toParent(prefix) : this.prefixSlash;\n const entries = await this.parent.listFiles(parentPrefix);\n const out: ContentEntry[] = [];\n for (const e of entries) {\n const local = this.fromParent(e.path);\n if (local === null) continue;\n out.push({ path: local, mimeType: e.mimeType, size: e.size });\n }\n return out;\n }\n\n exists(path: string): Promise<boolean> {\n return this.parent.exists(this.toParent(path));\n }\n\n /** Sidecars are not document roots. */\n async getDocumentPath(): Promise<string | null> {\n return null;\n }\n\n async readDocument(): Promise<string | null> {\n return null;\n }\n\n async writeDocument(): Promise<void> {\n throw new Error(\n 'ScopedContentContainer is a sidecar view and does not support writeDocument(). ' +\n 'Write to a specific path with writeFile() instead.',\n );\n }\n}\n\n/** Factory: convenience wrapper for `new ScopedContentContainer(parent, prefix)`. */\nexport function scopeContainer(parent: ContentContainer, prefix: string): ScopedContentContainer {\n return new ScopedContentContainer(parent, prefix);\n}\n","/**\n * MediaProviderFromContainer — bridges ContentContainer to MediaProvider.\n *\n * Creates a MediaProvider that resolves relative paths by reading binary data\n * from a ContentContainer and generating blob URLs. Blob URLs are cached and\n * revoked on dispose().\n *\n * This allows any ContentContainer (memory, slot-backed, zip-loaded) to be\n * used with existing rendering components (DocPlayer, ImageLayer, VideoLayer)\n * that consume MediaProvider.\n */\n\nimport type { MediaProvider, MediaEntry } from '../schemas/MediaProvider.js';\nimport type { ContentContainer } from './ContentContainer.js';\n\n/**\n * Create a MediaProvider backed by a ContentContainer.\n *\n * @param container — The ContentContainer to read/write media from\n * @returns A MediaProvider that resolves paths to blob URLs\n */\nexport function createMediaProviderFromContainer(container: ContentContainer): MediaProvider {\n const blobUrlCache = new Map<string, string>();\n\n return {\n async resolveUrl(relativePath: string): Promise<string> {\n const cached = blobUrlCache.get(relativePath);\n if (cached) return cached;\n\n const data = await container.readFile(relativePath);\n if (!data) return relativePath;\n\n const entries = await container.listFiles();\n const entry = entries.find((e) => e.path === relativePath);\n const mimeType = entry?.mimeType ?? 'application/octet-stream';\n\n const blob = new Blob([data], { type: mimeType });\n const url = URL.createObjectURL(blob);\n blobUrlCache.set(relativePath, url);\n return url;\n },\n\n async listMedia(): Promise<MediaEntry[]> {\n const entries = await container.listFiles();\n return entries\n .filter((e) => !e.path.toLowerCase().endsWith('.md'))\n .map((e) => ({\n name: e.path,\n mimeType: e.mimeType,\n size: e.size,\n }));\n },\n\n async addMedia(\n name: string,\n data: ArrayBuffer | Blob | Uint8Array,\n mimeType: string,\n ): Promise<string> {\n // Invalidate any cached blob URL for this path before overwriting\n const cached = blobUrlCache.get(name);\n if (cached) {\n URL.revokeObjectURL(cached);\n blobUrlCache.delete(name);\n }\n\n let buffer: ArrayBuffer | Uint8Array;\n if (data instanceof Blob) {\n buffer = await data.arrayBuffer();\n } else {\n buffer = data;\n }\n await container.writeFile(name, buffer, mimeType);\n return name;\n },\n\n async removeMedia(relativePath: string): Promise<void> {\n const cached = blobUrlCache.get(relativePath);\n if (cached) {\n URL.revokeObjectURL(cached);\n blobUrlCache.delete(relativePath);\n }\n await container.removeFile(relativePath);\n },\n\n dispose(): void {\n for (const url of blobUrlCache.values()) {\n URL.revokeObjectURL(url);\n }\n blobUrlCache.clear();\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -1,24 +0,0 @@
1
- // src/versions/timestamp.ts
2
- var TIMESTAMP_RE = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/;
3
- function pad2(n) {
4
- return n < 10 ? `0${n}` : String(n);
5
- }
6
- function formatVersionTimestamp(date) {
7
- return String(date.getUTCFullYear()).padStart(4, "0") + pad2(date.getUTCMonth() + 1) + pad2(date.getUTCDate()) + "T" + pad2(date.getUTCHours()) + pad2(date.getUTCMinutes()) + pad2(date.getUTCSeconds()) + "Z";
8
- }
9
- function parseVersionTimestamp(stamp) {
10
- const m = TIMESTAMP_RE.exec(stamp);
11
- if (!m) return null;
12
- const [, y, mo, d, h, mi, s] = m;
13
- const ms = Date.UTC(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi), Number(s));
14
- if (Number.isNaN(ms)) return null;
15
- return new Date(ms);
16
- }
17
- var VERSION_TIMESTAMP_PATTERN = TIMESTAMP_RE;
18
-
19
- export {
20
- formatVersionTimestamp,
21
- parseVersionTimestamp,
22
- VERSION_TIMESTAMP_PATTERN
23
- };
24
- //# sourceMappingURL=chunk-JELIRVDP.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/versions/timestamp.ts"],"sourcesContent":["/**\n * Sortable, human-readable timestamps for version filenames.\n *\n * Format: ISO 8601 basic, UTC, second precision, `Z` suffix —\n * e.g. `20260430T152030Z`. Lexicographic order matches chronological order,\n * so `listFiles` returns versions in time order without parsing.\n */\n\nconst TIMESTAMP_RE = /^(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})Z$/;\n\nfunction pad2(n: number): string {\n return n < 10 ? `0${n}` : String(n);\n}\n\n/**\n * Format a Date as a sortable, filename-safe UTC timestamp string.\n */\nexport function formatVersionTimestamp(date: Date): string {\n return (\n String(date.getUTCFullYear()).padStart(4, '0') +\n pad2(date.getUTCMonth() + 1) +\n pad2(date.getUTCDate()) +\n 'T' +\n pad2(date.getUTCHours()) +\n pad2(date.getUTCMinutes()) +\n pad2(date.getUTCSeconds()) +\n 'Z'\n );\n}\n\n/**\n * Parse a version timestamp string back to a Date. Returns null if the input\n * doesn't match the expected format.\n */\nexport function parseVersionTimestamp(stamp: string): Date | null {\n const m = TIMESTAMP_RE.exec(stamp);\n if (!m) return null;\n const [, y, mo, d, h, mi, s] = m;\n const ms = Date.UTC(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi), Number(s));\n if (Number.isNaN(ms)) return null;\n return new Date(ms);\n}\n\n/**\n * The pattern a valid version timestamp matches. Exported so other modules\n * can validate filenames without re-deriving the regex.\n */\nexport const VERSION_TIMESTAMP_PATTERN = TIMESTAMP_RE;\n"],"mappings":";AAQA,IAAM,eAAe;AAErB,SAAS,KAAK,GAAmB;AAC/B,SAAO,IAAI,KAAK,IAAI,CAAC,KAAK,OAAO,CAAC;AACpC;AAKO,SAAS,uBAAuB,MAAoB;AACzD,SACE,OAAO,KAAK,eAAe,CAAC,EAAE,SAAS,GAAG,GAAG,IAC7C,KAAK,KAAK,YAAY,IAAI,CAAC,IAC3B,KAAK,KAAK,WAAW,CAAC,IACtB,MACA,KAAK,KAAK,YAAY,CAAC,IACvB,KAAK,KAAK,cAAc,CAAC,IACzB,KAAK,KAAK,cAAc,CAAC,IACzB;AAEJ;AAMO,SAAS,sBAAsB,OAA4B;AAChE,QAAM,IAAI,aAAa,KAAK,KAAK;AACjC,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,CAAC,EAAE,GAAG,IAAI,GAAG,GAAG,IAAI,CAAC,IAAI;AAC/B,QAAM,KAAK,KAAK,IAAI,OAAO,CAAC,GAAG,OAAO,EAAE,IAAI,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC;AAC1F,MAAI,OAAO,MAAM,EAAE,EAAG,QAAO;AAC7B,SAAO,IAAI,KAAK,EAAE;AACpB;AAMO,IAAM,4BAA4B;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/schemas/CustomTemplates.ts","../src/schemas/Theme.ts","../src/schemas/colorUtils.ts","../src/schemas/themeValidator.ts","../src/schemas/themes/standard.json","../src/schemas/themes/standard-dark.json","../src/schemas/themes/documentary.json","../src/schemas/themes/minimalist.json","../src/schemas/themes/bold.json","../src/schemas/themes/morning-light.json","../src/schemas/themes/tech-dark.json","../src/schemas/themes/magazine.json","../src/schemas/themes/cinematic.json","../src/schemas/themes/warm-earth.json","../src/schemas/themes/gezellig.json","../src/schemas/themes/index.ts","../src/schemas/themeLibrary.ts","../src/schemas/fontStacks.ts"],"sourcesContent":["/**\n * Custom Templates Schema\n *\n * User-defined block layout templates. The author designs a visual\n * layout once (in the editor's TemplateDesigner) and saves it as a\n * `CustomTemplateDefinition`. The definition lives either in the\n * document's frontmatter (per-doc, portable, ships with the markdown)\n * or in the user's local library (cross-doc reuse via localStorage).\n *\n * A custom template's content is just a `Layer[]` — the same shape that\n * built-in templates emit at expansion time. The novel bit is\n * *placeholder tokens*: TextLayer / ImageLayer content fields may carry\n * strings like `{title}`, `{content}`, `{children}`, or `{image:N}`\n * that the resolver (see `doc/templates/tokens/resolveTokens.ts`)\n * substitutes against the source Block at render time.\n *\n * Responsiveness model: every numeric position field in `layers` is\n * stored as a `%`-string relative to `viewport`, so the same template\n * renders correctly at any of Squisq's viewport presets (landscape,\n * portrait, square). The `viewport` field itself is purely an\n * authoring-time reference — it tells the designer canvas which aspect\n * ratio to mount with, and the thumbnail renderer which viewBox to use.\n */\n\nimport type { Layer } from './Doc.js';\n\n/**\n * Iteration directive for a custom-template layer.\n *\n * A layer that carries a `repeat` is cloned once per item of a source\n * collection at render time (see `resolveTokens`). The clones are laid\n * out in a row or column, offset from the base position by the layer's\n * own width/height plus `gap`, and each clone resolves per-item tokens\n * (`{item}`, `{item:src}`, `{item:label}`, `{index}`) against its item.\n */\nexport interface LayerRepeat {\n /**\n * Which of the source Block's collections to iterate:\n * - `images` — every image in the block body (alt + url)\n * - `children` — child block titles\n * - `listItems` — top-level list-item text in the block body\n */\n source: 'images' | 'children' | 'listItems';\n\n /**\n * Layout axis for the clones. `column` (default) stacks them\n * vertically (offsets `position.y`); `row` lays them out horizontally\n * (offsets `position.x`).\n */\n direction?: 'row' | 'column';\n\n /**\n * Extra spacing added between consecutive clones, in the same unit as\n * the layer's `width`/`height`. Defaults to 24.\n */\n gap?: number;\n\n /** Cap on the number of clones rendered (omit for \"all items\"). */\n max?: number;\n}\n\n/**\n * A custom-template layer: a normal render `Layer` that may additionally\n * carry a `repeat` directive. This is a strict superset of the core\n * `Layer` type used only inside `CustomTemplateDefinition.layers`; the\n * resolver strips `repeat` before emitting the final `Layer[]`, so the\n * core `Layer` type is never widened.\n */\nexport type CustomTemplateLayer = Layer & { repeat?: LayerRepeat };\n\n/**\n * A single user-defined template. Stored in `Doc.customTemplates`\n * (frontmatter-backed) or in the user's local library.\n */\nexport interface CustomTemplateDefinition {\n /**\n * Stable id used in heading annotations (`### Foo {[name]}`). Must be\n * a slug — lowercase alphanumerics, hyphens. The picker enforces\n * uniqueness within a (doc + library) merged namespace.\n */\n name: string;\n\n /** Human-readable label shown in the template picker. */\n label: string;\n\n /** Optional one-sentence description for the picker card. */\n description?: string;\n\n /**\n * Authoring viewport — the canvas size the designer drew against.\n * Stored so the designer and thumbnail renderer can reproduce the\n * same canvas. Layer positions are stored as `%`-strings, so the\n * actual render-time viewport can differ from this without breaking\n * the layout. Defaults to 1920×1080 landscape.\n */\n viewport: {\n width: number;\n height: number;\n };\n\n /**\n * The visual content. Every `position.x` / `position.y` /\n * `position.width` / `position.height` should be a `%`-string at\n * save time (the designer's normalizePositions step enforces this).\n *\n * Tokens supported (grammar v2):\n * - TextLayer.content.text — `{title}`, `{content}`, `{children}`,\n * `{image:N}` (Nth image's alt text), `{attr:key}` (block\n * attribute), plus a pipe default on any token\n * (`{attr:subtitle|Untitled}`).\n * - ImageLayer.content.src — `{image:N}` (the URL) and `{attr:key}`,\n * with pipe defaults (`{image:1|fallback.jpg}`).\n * - Inside a layer with `repeat`, per-item tokens: `{item}`,\n * `{item:src}`, `{item:label}`, `{index}` (1-based).\n *\n * A layer may carry a `repeat` directive (see {@link LayerRepeat}) to\n * be cloned once per item of a source collection.\n */\n layers: CustomTemplateLayer[];\n}\n\n/**\n * Tag value for the YAML frontmatter key that carries inlined custom\n * template definitions. Exported so the markdown bridge stays in sync.\n */\nexport const FRONTMATTER_CUSTOM_TEMPLATES_KEY = 'squisq-custom-templates';\n","/**\n * Theme System\n *\n * A Theme bundles color palette, typography, visual style, render-style\n * algorithm, and per-block color schemes into one JSON-serializable object.\n *\n * Built-in themes and customizer-authored themes share this exact schema.\n * Customizer-authored themes additionally specify `seedColors` so the few\n * fields the user picked can be re-edited later; built-ins typically omit\n * `seedColors` and ship with explicit `colors` already filled in.\n *\n * Design principles:\n * - Fully JSON-serializable. No functions; no raw CSS font strings.\n * - Font families are structured `FontFamily` references resolved by\n * `resolveFontFamily()` at render time.\n * - Doc carries an optional `themeId`; resolution happens at render time\n * via `resolveTheme(id)`.\n * - `createTheme` deep-merges a base theme with partial overrides;\n * `compileTheme` fills in defaults and derives missing color slots from\n * `seedColors`; both produce a complete `Theme`.\n */\n\nimport type { LayoutHints } from './LayoutStrategy.js';\nimport type { AnimationType, ImageTreatment, TransitionType } from './Doc.js';\nimport type { PersistentLayerConfig } from './BlockTemplates.js';\n\n// ============================================\n// Schema version\n// ============================================\n\n/** Current Theme schema version. Bump on breaking changes; loader migrates. */\nexport const THEME_SCHEMA_VERSION = '1' as const;\nexport type ThemeSchemaVersion = typeof THEME_SCHEMA_VERSION;\n\n/**\n * Frontmatter key under which a document inlines its custom Theme\n * definitions. The theme analog of `FRONTMATTER_CUSTOM_TEMPLATES_KEY`\n * (custom block templates). See `doc/customThemesFrontmatter.ts` for the\n * codec and `doc/resolveDocTheme.ts` for doc-scoped resolution.\n */\nexport const FRONTMATTER_CUSTOM_THEMES_KEY = 'squisq-custom-themes';\n\n// ============================================\n// Color Palette\n// ============================================\n\n/**\n * Core color palette for a theme. Every color is a `#rrggbb` hex string.\n */\nexport interface ThemeColorPalette {\n /** Primary accent color */\n primary: string;\n /** Secondary accent color */\n secondary: string;\n /** Background color (typically dark) */\n background: string;\n /** Lighter background for contrast panels */\n backgroundLight: string;\n /** Main text color */\n text: string;\n /** Muted/secondary text color */\n textMuted: string;\n /** Highlight/emphasis color */\n highlight: string;\n /** Warning/alert color */\n warning: string;\n}\n\n/**\n * A named color scheme used by templates for per-block color variation\n * (e.g., statHighlight or sectionHeader).\n */\nexport interface ThemeColorScheme {\n /** Background fill */\n bg: string;\n /** Primary text tint */\n text: string;\n /** Accent / secondary tint */\n accent: string;\n}\n\n/**\n * Optional authoring metadata. When present and `colors` is partial,\n * `compileTheme` derives missing color slots from these seeds via OKLCh\n * lightening / darkening. Round-trips a customizer-authored theme back\n * into editable form.\n */\nexport interface ThemeSeedColors {\n primary: string;\n secondary?: string;\n accent?: string;\n background?: string;\n text?: string;\n}\n\n// ============================================\n// Typography\n// ============================================\n\n/** Categorical bucket for a font family — drives default fallback. */\nexport type FontFamilyKind = 'sans' | 'serif' | 'mono' | 'display';\n\n/**\n * Structured reference to a font family. Either a curated stack id (looked\n * up in `AVAILABLE_FONT_STACKS`) or a custom user-supplied name with a\n * structured fallback bucket. Themes never carry raw CSS family strings.\n */\nexport type FontFamily =\n | { stackId: string }\n | {\n custom: {\n name: string;\n fallback: 'serif' | 'sans-serif' | 'monospace' | 'system-ui';\n };\n };\n\n/**\n * Typography settings for a theme.\n */\nexport interface ThemeTypography {\n /** Font family for body / description text */\n bodyFont: FontFamily;\n /** Font family for titles and headings */\n titleFont: FontFamily;\n /** Font family for code / monospaced text (optional) */\n monoFont?: FontFamily;\n /** Multiplier applied to LayoutHints.titleScale (default 1.0) */\n titleScale?: number;\n /** Multiplier applied to LayoutHints.bodyScale (default 1.0) */\n bodyScale?: number;\n /** Default body line height (default 1.4) */\n lineHeight?: number;\n /** Default title line height */\n titleLineHeight?: number;\n /** Default title font weight */\n titleWeight?: 'normal' | 'bold';\n}\n\n// ============================================\n// Visual Style\n// ============================================\n\n/**\n * Global visual-style knobs that templates consult.\n */\nexport interface ThemeStyle {\n /** Default border radius for cards / shapes (px) */\n borderRadius?: number;\n /** Whether templates should default to text shadows */\n textShadow?: boolean;\n /** Darkness of overlay on image-backed blocks (0–1) */\n overlayOpacity?: number;\n /** Multiplier on all animation durations (1.0 = normal, <1 faster, >1 slower) */\n animationSpeed?: number;\n /** Default horizontal padding for text (percentage string, e.g. \"5%\") */\n blockPadding?: string;\n /**\n * Photographic grade applied to template imagery (full-bleed photos,\n * feature halves, accent strips) so photos look native to the theme.\n * Duotone tints default to the theme primary. Blocks opt out per\n * template via `imageTreatment: 'none'`.\n */\n imageTreatment?: ImageTreatment;\n}\n\n// ============================================\n// Render Style\n// ============================================\n\n/**\n * Render-style algorithm preset. Controls layout tweaks, default animations,\n * transitions, and per-template behavioral hints.\n */\nexport interface RenderStyle {\n /** Identifier for this algorithmic approach (e.g. \"documentary\", \"magazine\") */\n name: string;\n /** Partial overrides merged onto the orientation-based LayoutHints */\n layoutOverrides?: Partial<LayoutHints>;\n /** Default entrance animation for text layers */\n defaultTextAnimation?: AnimationType;\n /** Default animation for background / image layers */\n defaultImageAnimation?: AnimationType;\n /** Whether to apply Ken Burns ambient motion to images by default */\n ambientMotion?: boolean;\n /** Default block-to-block transition */\n defaultTransition?: { type: TransitionType; duration?: number };\n /**\n * Per-template behavioral hints. Keys are template names, values are\n * string/number/boolean maps that templates can read to vary their output.\n * Templates that consume hints publish a `hintSchema` export documenting\n * the keys they recognise.\n */\n templateHints?: Record<string, Record<string, string | number | boolean>>;\n}\n\n// ============================================\n// Theme\n// ============================================\n\n/**\n * A complete, JSON-serializable theme definition. Built-in and customizer-\n * authored themes share this exact shape.\n */\nexport interface Theme {\n /** Schema version. Always '1' for now. */\n schemaVersion: ThemeSchemaVersion;\n /** Unique identifier (e.g. \"documentary\") */\n id: string;\n /** Human-readable display name */\n name: string;\n /** Short description for theme pickers */\n description?: string;\n /**\n * Id of the theme this custom theme was derived from, if any. Recorded\n * by `compileTheme({ base })` so a customizer can re-open the theme and\n * re-inherit the base's render style / color schemes / typography.\n * Absent on built-ins.\n */\n basedOn?: string;\n /** Optional authoring seeds (customizer fills these; built-ins typically omit) */\n seedColors?: ThemeSeedColors;\n /** Color palette */\n colors: ThemeColorPalette;\n /** Typography settings */\n typography: ThemeTypography;\n /** Global visual-style knobs */\n style: ThemeStyle;\n /** Algorithmic render-style preset */\n renderStyle: RenderStyle;\n /**\n * Named color schemes for per-block color variation.\n * Templates reference these by name (e.g. \"blue\", \"warm\").\n */\n colorSchemes: Record<string, ThemeColorScheme>;\n /** Optional persistent layers baked into the theme */\n persistentLayers?: PersistentLayerConfig;\n}\n\n// ============================================\n// Deep-Partial helper type\n// ============================================\n\n/**\n * Recursively makes every property optional.\n */\nexport type DeepPartial<T> = {\n [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];\n};\n\n// ============================================\n// Helpers\n// ============================================\n\n/**\n * Deep-merge `source` into `target`, returning a new object.\n * Arrays are replaced wholesale (not concatenated).\n */\nfunction deepMerge<T extends Record<string, unknown>>(target: T, source: DeepPartial<T>): T {\n const result = { ...target } as Record<string, unknown>;\n for (const key of Object.keys(source)) {\n const srcVal = (source as Record<string, unknown>)[key];\n const tgtVal = (target as Record<string, unknown>)[key];\n if (\n srcVal !== null &&\n srcVal !== undefined &&\n typeof srcVal === 'object' &&\n !Array.isArray(srcVal) &&\n tgtVal !== null &&\n tgtVal !== undefined &&\n typeof tgtVal === 'object' &&\n !Array.isArray(tgtVal)\n ) {\n result[key] = deepMerge(\n tgtVal as Record<string, unknown>,\n srcVal as DeepPartial<Record<string, unknown>>,\n );\n } else if (srcVal !== undefined) {\n result[key] = srcVal;\n }\n }\n return result as T;\n}\n\n/**\n * Create a new theme by deep-merging overrides onto a base theme.\n * The returned theme gets its own `id` if one is provided in overrides,\n * otherwise keeps the base theme's `id` suffixed with \"-custom\".\n */\nexport function createTheme(base: Theme, overrides: DeepPartial<Theme>): Theme {\n const merged = deepMerge(\n base as unknown as Record<string, unknown>,\n overrides as DeepPartial<Record<string, unknown>>,\n ) as unknown as Theme;\n if (!overrides.id && merged.id === base.id) {\n merged.id = `${base.id}-custom`;\n }\n if (!merged.schemaVersion) merged.schemaVersion = THEME_SCHEMA_VERSION;\n return merged;\n}\n\n// ============================================\n// Surface Schemes — orthogonal to Theme\n// ============================================\n\n/**\n * A Surface Scheme is the light/dark axis, orthogonal to the editorial\n * identity of a Theme. A Theme chooses voice (serif vs sans, muted vs\n * bold, documentary vs magazine); a SurfaceScheme chooses what the paper\n * looks like. Any theme can render on either surface.\n */\nexport interface SurfaceScheme {\n /** Identifier — 'light', 'dark', or a custom id. */\n id: string;\n background: string;\n backgroundLight: string;\n text: string;\n textMuted: string;\n}\n\n/** Near-white paper with dark text — standard light mode. */\nexport const LIGHT_SURFACE: SurfaceScheme = {\n id: 'light',\n background: '#ffffff',\n backgroundLight: '#f5f5f5',\n text: '#1a1a1a',\n textMuted: '#666666',\n};\n\n/** Dark charcoal paper with light text — standard dark mode. */\nexport const DARK_SURFACE: SurfaceScheme = {\n id: 'dark',\n background: '#1a202c',\n backgroundLight: '#2d3748',\n text: '#f0f0f0',\n textMuted: '#a0aec0',\n};\n\n/**\n * Overlay a SurfaceScheme's surface colors onto a Theme's palette,\n * leaving everything else (primary, highlight, warning, typography,\n * style, renderStyle, colorSchemes) untouched.\n */\nexport function applySurface(theme: Theme, surface: SurfaceScheme): Theme {\n return {\n ...theme,\n colors: {\n ...theme.colors,\n background: surface.background,\n backgroundLight: surface.backgroundLight,\n text: surface.text,\n textMuted: surface.textMuted,\n },\n };\n}\n\n// ============================================\n// Runtime registry — for custom themes\n// ============================================\n\nconst CUSTOM_THEME_REGISTRY = new Map<string, Theme>();\n\n/**\n * Register a Theme so it can be looked up by id via `resolveTheme(id)`.\n * Lets `Doc.themeId` round-trip through Doc serialization for custom themes.\n *\n * Registered themes take precedence over built-ins with the same id.\n */\nexport function registerTheme(theme: Theme): void {\n CUSTOM_THEME_REGISTRY.set(theme.id, theme);\n}\n\n/** Remove a previously registered theme. */\nexport function unregisterTheme(id: string): void {\n CUSTOM_THEME_REGISTRY.delete(id);\n}\n\n/** Snapshot of all currently registered (non-built-in) themes. */\nexport function getRegisteredThemes(): Theme[] {\n return Array.from(CUSTOM_THEME_REGISTRY.values());\n}\n\n/** @internal — used by themeLibrary's `resolveTheme` to check the registry first. */\nexport function lookupRegisteredTheme(id: string | undefined): Theme | undefined {\n if (!id) return undefined;\n return CUSTOM_THEME_REGISTRY.get(id);\n}\n","/**\n * Color Utilities\n *\n * Pure-JS color math used by theme compilation and contrast checks.\n * No dependencies. Operates on `#rrggbb` hex strings.\n *\n * Internally uses OKLCh — perceptually uniform, so lightening by 0.1\n * looks like the same change at any starting point. Out-of-gamut\n * results are clamped to sRGB.\n */\n\nconst HEX_RE = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;\n\n/** Test whether a string is a valid `#rgb` or `#rrggbb` hex color. */\nexport function isHex(value: string): boolean {\n return HEX_RE.test(value);\n}\n\n/** Parse `#rgb` or `#rrggbb` into 0..1 sRGB triplet. Returns null on failure. */\nfunction parseHex(hex: string): [number, number, number] | null {\n if (!HEX_RE.test(hex)) return null;\n let body = hex.slice(1);\n if (body.length === 3)\n body = body\n .split('')\n .map((c) => c + c)\n .join('');\n return [\n parseInt(body.slice(0, 2), 16) / 255,\n parseInt(body.slice(2, 4), 16) / 255,\n parseInt(body.slice(4, 6), 16) / 255,\n ];\n}\n\nfunction toHex(rgb: [number, number, number]): string {\n const ch = (v: number) => {\n const c = Math.max(0, Math.min(255, Math.round(v * 255)));\n return c.toString(16).padStart(2, '0');\n };\n return '#' + ch(rgb[0]) + ch(rgb[1]) + ch(rgb[2]);\n}\n\nfunction srgbToLinear(c: number): number {\n return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n}\n\nfunction linearToSrgb(c: number): number {\n return c <= 0.0031308 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;\n}\n\ninterface OKLCh {\n L: number;\n C: number;\n h: number;\n}\n\nfunction rgbToOklch(rgb: [number, number, number]): OKLCh {\n const [sr, sg, sb] = rgb;\n const r = srgbToLinear(sr);\n const g = srgbToLinear(sg);\n const b = srgbToLinear(sb);\n\n const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;\n const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;\n const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;\n\n const l_ = Math.cbrt(l);\n const m_ = Math.cbrt(m);\n const s_ = Math.cbrt(s);\n\n const L = 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_;\n const a = 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_;\n const bb = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_;\n\n const C = Math.sqrt(a * a + bb * bb);\n let h = (Math.atan2(bb, a) * 180) / Math.PI;\n if (h < 0) h += 360;\n return { L, C, h };\n}\n\nfunction oklchToRgb({ L, C, h }: OKLCh): [number, number, number] {\n const hr = (h * Math.PI) / 180;\n const a = C * Math.cos(hr);\n const bb = C * Math.sin(hr);\n\n const l_ = L + 0.3963377774 * a + 0.2158037573 * bb;\n const m_ = L - 0.1055613458 * a - 0.0638541728 * bb;\n const s_ = L - 0.0894841775 * a - 1.291485548 * bb;\n\n const l = l_ * l_ * l_;\n const m = m_ * m_ * m_;\n const s = s_ * s_ * s_;\n\n const r = +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;\n const g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;\n const b = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;\n\n return [\n Math.max(0, Math.min(1, linearToSrgb(r))),\n Math.max(0, Math.min(1, linearToSrgb(g))),\n Math.max(0, Math.min(1, linearToSrgb(b))),\n ];\n}\n\n/**\n * Lighten a color by `amount` (0..1) in OKLCh L space.\n * Falls back to the input on parse failure.\n */\nexport function oklchLighten(hex: string, amount: number): string {\n const rgb = parseHex(hex);\n if (!rgb) return hex;\n const lch = rgbToOklch(rgb);\n lch.L = Math.max(0, Math.min(1, lch.L + amount));\n return toHex(oklchToRgb(lch));\n}\n\n/**\n * Darken a color by `amount` (0..1) in OKLCh L space.\n * Falls back to the input on parse failure.\n */\nexport function oklchDarken(hex: string, amount: number): string {\n return oklchLighten(hex, -amount);\n}\n\n/**\n * Adjust the OKLCh chroma of a color by a multiplier (e.g. 0.7 to mute).\n */\nexport function oklchSetChroma(hex: string, multiplier: number): string {\n const rgb = parseHex(hex);\n if (!rgb) return hex;\n const lch = rgbToOklch(rgb);\n lch.C = Math.max(0, lch.C * multiplier);\n return toHex(oklchToRgb(lch));\n}\n\n/**\n * WCAG relative luminance of a hex color, returned as 0..1.\n */\nexport function relativeLuminance(hex: string): number {\n const rgb = parseHex(hex);\n if (!rgb) return 0;\n const [r, g, b] = rgb.map(srgbToLinear) as [number, number, number];\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\n/**\n * Hue of a hex color in HSL degrees (0..360). Returns 0 for pure grays\n * and on parse failure. Used by the duotone image treatment to aim its\n * `hue-rotate()` at the theme's tint color.\n */\nexport function hexHueDegrees(hex: string): number {\n const rgb = parseHex(hex);\n if (!rgb) return 0;\n const [r, g, b] = rgb;\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n if (max === min) return 0;\n const d = max - min;\n let h: number;\n if (max === r) h = ((g - b) / d) % 6;\n else if (max === g) h = (b - r) / d + 2;\n else h = (r - g) / d + 4;\n return Math.round((h * 60 + 360) % 360);\n}\n\n/**\n * Convert a hex color + alpha (0..1) to an `rgba()` string.\n * Falls back to the input on parse failure (e.g. a value that is\n * already an rgba()/gradient string).\n */\nexport function withAlpha(hex: string, alpha: number): string {\n const rgb = parseHex(hex);\n if (!rgb) return hex;\n const a = Math.max(0, Math.min(1, alpha));\n const ch = (v: number) => Math.round(v * 255);\n return `rgba(${ch(rgb[0])}, ${ch(rgb[1])}, ${ch(rgb[2])}, ${a})`;\n}\n\n/**\n * WCAG contrast ratio between two hex colors. Returns 1..21.\n */\nexport function contrastRatio(a: string, b: string): number {\n const la = relativeLuminance(a);\n const lb = relativeLuminance(b);\n const lighter = Math.max(la, lb);\n const darker = Math.min(la, lb);\n return (lighter + 0.05) / (darker + 0.05);\n}\n\n/**\n * Pick a near-white or near-black text color that contrasts well against `bg`.\n */\nexport function pickContrastingText(bg: string, light = '#f7fafc', dark = '#1a202c'): string {\n return contrastRatio(bg, dark) >= contrastRatio(bg, light) ? dark : light;\n}\n\n/**\n * Derive a 5-step lighter→darker scale from a seed color.\n * Stops are evenly spaced in OKLCh L space around the seed.\n * `spread` controls amplitude (0.05 = subtle, 0.15 = balanced, 0.22 = high).\n */\nexport function deriveScale(\n seedHex: string,\n spread = 0.15,\n): {\n lighter2: string;\n lighter1: string;\n base: string;\n darker1: string;\n darker2: string;\n} {\n return {\n lighter2: oklchLighten(seedHex, spread),\n lighter1: oklchLighten(seedHex, spread / 2),\n base: seedHex,\n darker1: oklchDarken(seedHex, spread / 2),\n darker2: oklchDarken(seedHex, spread),\n };\n}\n","/**\n * Theme Validator\n *\n * Hand-rolled validator for `Theme` JSON. No dependencies. Produces a\n * flat list of typed errors with JSON paths, suitable for surfacing in\n * the customizer UI or throwing at module init for built-in themes.\n *\n * Design: walk the candidate object, push errors for each violation.\n * Coverage is exhaustive for required fields and shapes; permissive on\n * forward-compatible fields (unknown keys are ignored, not rejected).\n */\n\nimport type { Theme, ThemeColorPalette } from './Theme.js';\nimport { THEME_SCHEMA_VERSION } from './Theme.js';\nimport { isHex } from './colorUtils.js';\nimport { isTransitionType } from './Transitions.js';\n\nexport interface ValidationError {\n path: string;\n message: string;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n errors: ValidationError[];\n /** Present only when `valid === true`. The same input typed as `Theme`. */\n theme?: Theme;\n}\n\nconst VALID_FALLBACK = new Set(['serif', 'sans-serif', 'monospace', 'system-ui']);\nconst VALID_TITLE_WEIGHT = new Set(['normal', 'bold']);\nconst VALID_GRADIENT_PRESETS = new Set([\n 'dark-vignette',\n 'radial-dark',\n 'warm-sunset',\n 'cool-blue',\n 'earth-tones',\n]);\n\nclass V {\n errors: ValidationError[] = [];\n err(path: string, message: string): void {\n this.errors.push({ path, message });\n }\n isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n }\n isString(v: unknown): v is string {\n return typeof v === 'string';\n }\n isNumber(v: unknown): v is number {\n return typeof v === 'number' && Number.isFinite(v);\n }\n isBoolean(v: unknown): v is boolean {\n return typeof v === 'boolean';\n }\n hex(path: string, v: unknown): void {\n if (!this.isString(v)) {\n this.err(path, `expected hex color string, got ${typeof v}`);\n } else if (!isHex(v)) {\n this.err(path, `expected #rgb or #rrggbb hex color, got \"${v}\"`);\n }\n }\n\n palette(path: string, v: unknown): void {\n if (!this.isObject(v)) {\n this.err(path, 'expected object');\n return;\n }\n const required: (keyof ThemeColorPalette)[] = [\n 'primary',\n 'secondary',\n 'background',\n 'backgroundLight',\n 'text',\n 'textMuted',\n 'highlight',\n 'warning',\n ];\n for (const k of required) this.hex(`${path}.${k}`, v[k]);\n }\n\n colorScheme(path: string, v: unknown): void {\n if (!this.isObject(v)) {\n this.err(path, 'expected object');\n return;\n }\n this.hex(`${path}.bg`, v.bg);\n this.hex(`${path}.text`, v.text);\n this.hex(`${path}.accent`, v.accent);\n }\n\n colorSchemes(path: string, v: unknown): void {\n if (!this.isObject(v)) {\n this.err(path, 'expected object map');\n return;\n }\n for (const key of Object.keys(v)) {\n this.colorScheme(`${path}.${key}`, v[key]);\n }\n }\n\n fontFamily(path: string, v: unknown): void {\n if (!this.isObject(v)) {\n this.err(path, 'expected FontFamily object ({ stackId } or { custom })');\n return;\n }\n if ('stackId' in v) {\n if (!this.isString(v.stackId) || v.stackId.length === 0) {\n this.err(`${path}.stackId`, 'expected non-empty string');\n }\n return;\n }\n if ('custom' in v) {\n const c = v.custom;\n if (!this.isObject(c)) {\n this.err(`${path}.custom`, 'expected { name, fallback } object');\n return;\n }\n if (!this.isString(c.name) || c.name.length === 0) {\n this.err(`${path}.custom.name`, 'expected non-empty string');\n }\n if (!this.isString(c.fallback) || !VALID_FALLBACK.has(c.fallback)) {\n this.err(\n `${path}.custom.fallback`,\n `expected one of: serif, sans-serif, monospace, system-ui`,\n );\n }\n return;\n }\n this.err(path, 'expected { stackId } or { custom: { name, fallback } }');\n }\n\n typography(path: string, v: unknown): void {\n if (!this.isObject(v)) {\n this.err(path, 'expected object');\n return;\n }\n this.fontFamily(`${path}.bodyFont`, v.bodyFont);\n this.fontFamily(`${path}.titleFont`, v.titleFont);\n if (v.monoFont !== undefined) this.fontFamily(`${path}.monoFont`, v.monoFont);\n for (const k of ['titleScale', 'bodyScale', 'lineHeight', 'titleLineHeight']) {\n if (v[k] !== undefined && !this.isNumber(v[k])) {\n this.err(`${path}.${k}`, `expected number, got ${typeof v[k]}`);\n }\n }\n if (v.titleWeight !== undefined) {\n if (!this.isString(v.titleWeight) || !VALID_TITLE_WEIGHT.has(v.titleWeight)) {\n this.err(`${path}.titleWeight`, 'expected \"normal\" or \"bold\"');\n }\n }\n }\n\n style(path: string, v: unknown): void {\n if (!this.isObject(v)) {\n this.err(path, 'expected object');\n return;\n }\n if (v.borderRadius !== undefined && !this.isNumber(v.borderRadius)) {\n this.err(`${path}.borderRadius`, 'expected number');\n }\n if (v.textShadow !== undefined && !this.isBoolean(v.textShadow)) {\n this.err(`${path}.textShadow`, 'expected boolean');\n }\n for (const k of ['overlayOpacity', 'animationSpeed']) {\n if (v[k] !== undefined && !this.isNumber(v[k])) {\n this.err(`${path}.${k}`, 'expected number');\n }\n }\n if (v.blockPadding !== undefined && !this.isString(v.blockPadding)) {\n this.err(`${path}.blockPadding`, 'expected string (percentage e.g. \"5%\")');\n }\n if (v.imageTreatment !== undefined) {\n this.imageTreatment(`${path}.imageTreatment`, v.imageTreatment);\n }\n }\n\n imageTreatment(path: string, v: unknown): void {\n if (!this.isObject(v)) {\n this.err(path, 'expected object');\n return;\n }\n const kinds = ['none', 'mono', 'duotone', 'warm', 'cool'];\n if (!this.isString(v.type) || !kinds.includes(v.type)) {\n this.err(`${path}.type`, `expected one of ${kinds.join('|')}`);\n }\n if (v.strength !== undefined && !this.isNumber(v.strength)) {\n this.err(`${path}.strength`, 'expected number (0..1)');\n }\n if (v.color !== undefined) this.hex(`${path}.color`, v.color);\n }\n\n renderStyle(path: string, v: unknown): void {\n if (!this.isObject(v)) {\n this.err(path, 'expected object');\n return;\n }\n if (!this.isString(v.name) || v.name.length === 0) {\n this.err(`${path}.name`, 'expected non-empty string');\n }\n if (v.ambientMotion !== undefined && !this.isBoolean(v.ambientMotion)) {\n this.err(`${path}.ambientMotion`, 'expected boolean');\n }\n for (const k of ['defaultTextAnimation', 'defaultImageAnimation']) {\n if (v[k] !== undefined && !this.isString(v[k])) {\n this.err(`${path}.${k}`, 'expected animation type string');\n }\n }\n if (v.defaultTransition !== undefined) {\n const t = v.defaultTransition;\n if (!this.isObject(t)) {\n this.err(`${path}.defaultTransition`, 'expected object');\n } else {\n if (!this.isString(t.type) || !isTransitionType(t.type)) {\n this.err(`${path}.defaultTransition.type`, 'expected valid transition type');\n }\n if (t.duration !== undefined && !this.isNumber(t.duration)) {\n this.err(`${path}.defaultTransition.duration`, 'expected number');\n }\n }\n }\n if (v.layoutOverrides !== undefined && !this.isObject(v.layoutOverrides)) {\n this.err(`${path}.layoutOverrides`, 'expected object');\n }\n if (v.templateHints !== undefined) {\n if (!this.isObject(v.templateHints)) {\n this.err(`${path}.templateHints`, 'expected object map');\n } else {\n for (const tplName of Object.keys(v.templateHints)) {\n const hints = v.templateHints[tplName];\n if (!this.isObject(hints)) {\n this.err(`${path}.templateHints.${tplName}`, 'expected object');\n continue;\n }\n for (const key of Object.keys(hints)) {\n const val = hints[key];\n const t = typeof val;\n if (t !== 'string' && t !== 'number' && t !== 'boolean') {\n this.err(\n `${path}.templateHints.${tplName}.${key}`,\n `expected string|number|boolean, got ${t}`,\n );\n }\n }\n }\n }\n }\n }\n\n seedColors(path: string, v: unknown): void {\n if (!this.isObject(v)) {\n this.err(path, 'expected object');\n return;\n }\n if (v.primary === undefined) {\n this.err(`${path}.primary`, 'required when seedColors is present');\n } else {\n this.hex(`${path}.primary`, v.primary);\n }\n for (const k of ['secondary', 'accent', 'background', 'text']) {\n if (v[k] !== undefined) this.hex(`${path}.${k}`, v[k]);\n }\n }\n\n persistentLayer(path: string, v: unknown): void {\n if (!this.isObject(v)) {\n this.err(path, 'expected object');\n return;\n }\n // Either { template, config } or a raw Layer — only validate the template form here\n if ('template' in v) {\n if (!this.isString(v.template)) {\n this.err(`${path}.template`, 'expected string');\n return;\n }\n const cfg = v.config;\n if (!this.isObject(cfg)) {\n this.err(`${path}.config`, 'expected object');\n return;\n }\n if (cfg.type !== v.template) {\n this.err(`${path}.config.type`, `expected \"${v.template}\", got \"${String(cfg.type)}\"`);\n }\n if (v.template === 'gradientBackground' && cfg.preset !== undefined) {\n if (!this.isString(cfg.preset) || !VALID_GRADIENT_PRESETS.has(cfg.preset)) {\n this.err(\n `${path}.config.preset`,\n `expected one of: ${Array.from(VALID_GRADIENT_PRESETS).join(', ')}`,\n );\n }\n }\n if (v.template === 'gradientBackground' && 'gradient' in cfg) {\n this.err(\n `${path}.config.gradient`,\n 'raw CSS gradient strings are not allowed in Theme JSON; use a `preset`',\n );\n }\n if (v.template === 'patternBackground') {\n const patterns = ['dots', 'grid', 'diagonal', 'noise'];\n if (!this.isString(cfg.pattern) || !patterns.includes(cfg.pattern)) {\n this.err(`${path}.config.pattern`, `expected one of ${patterns.join('|')}`);\n }\n for (const k of ['opacity', 'scale']) {\n if (cfg[k] !== undefined && !this.isNumber(cfg[k])) {\n this.err(`${path}.config.${k}`, 'expected number');\n }\n }\n if (cfg.color !== undefined) this.hex(`${path}.config.color`, cfg.color);\n }\n if (v.template === 'vignette') {\n if (cfg.strength !== undefined && !this.isNumber(cfg.strength)) {\n this.err(`${path}.config.strength`, 'expected number (0..1)');\n }\n if (cfg.color !== undefined) this.hex(`${path}.config.color`, cfg.color);\n }\n if (v.template === 'ambientGradient') {\n if (cfg.duration !== undefined && !this.isNumber(cfg.duration)) {\n this.err(`${path}.config.duration`, 'expected number (seconds)');\n }\n for (const k of ['from', 'to']) {\n if (cfg[k] !== undefined) this.hex(`${path}.config.${k}`, cfg[k]);\n }\n }\n }\n // Raw Layer form is permissive — no schema check here\n }\n\n persistentLayers(path: string, v: unknown): void {\n if (!this.isObject(v)) {\n this.err(path, 'expected object');\n return;\n }\n for (const side of ['bottomLayers', 'topLayers']) {\n const arr = v[side];\n if (arr === undefined) continue;\n if (!Array.isArray(arr)) {\n this.err(`${path}.${side}`, 'expected array');\n continue;\n }\n arr.forEach((entry, i) => this.persistentLayer(`${path}.${side}[${i}]`, entry));\n }\n }\n}\n\nexport function validateTheme(input: unknown): ValidationResult {\n const v = new V();\n if (!v.isObject(input)) {\n return { valid: false, errors: [{ path: '', message: 'expected object' }] };\n }\n\n if (input.schemaVersion !== THEME_SCHEMA_VERSION) {\n v.err(\n 'schemaVersion',\n `expected \"${THEME_SCHEMA_VERSION}\", got \"${String(input.schemaVersion)}\"`,\n );\n }\n if (!v.isString(input.id) || input.id.length === 0) {\n v.err('id', 'expected non-empty string');\n }\n if (!v.isString(input.name) || input.name.length === 0) {\n v.err('name', 'expected non-empty string');\n }\n if (input.description !== undefined && !v.isString(input.description)) {\n v.err('description', 'expected string');\n }\n if (input.basedOn !== undefined && !v.isString(input.basedOn)) {\n v.err('basedOn', 'expected string');\n }\n\n if (input.seedColors !== undefined) v.seedColors('seedColors', input.seedColors);\n\n if (input.colors === undefined) {\n v.err('colors', 'required');\n } else {\n v.palette('colors', input.colors);\n }\n\n if (input.typography === undefined) {\n v.err('typography', 'required');\n } else {\n v.typography('typography', input.typography);\n }\n\n if (input.style === undefined) {\n v.err('style', 'required');\n } else {\n v.style('style', input.style);\n }\n\n if (input.renderStyle === undefined) {\n v.err('renderStyle', 'required');\n } else {\n v.renderStyle('renderStyle', input.renderStyle);\n }\n\n if (input.colorSchemes === undefined) {\n v.err('colorSchemes', 'required');\n } else {\n v.colorSchemes('colorSchemes', input.colorSchemes);\n }\n\n if (input.persistentLayers !== undefined) {\n v.persistentLayers('persistentLayers', input.persistentLayers);\n }\n\n if (v.errors.length === 0) {\n return { valid: true, errors: [], theme: input as unknown as Theme };\n }\n return { valid: false, errors: v.errors };\n}\n\n/** Throws on invalid input. Useful at module init for built-in themes. */\nexport function assertTheme(input: unknown, source = 'theme'): Theme {\n const result = validateTheme(input);\n if (!result.valid || !result.theme) {\n const summary = result.errors\n .slice(0, 5)\n .map((e) => ` ${e.path || '<root>'}: ${e.message}`)\n .join('\\n');\n const more = result.errors.length > 5 ? `\\n ... and ${result.errors.length - 5} more` : '';\n throw new Error(`Invalid ${source}:\\n${summary}${more}`);\n }\n return result.theme;\n}\n\n// (Theme types are re-exported from `./Theme.js` via the schemas barrel.)\n","{\n \"schemaVersion\": \"1\",\n \"id\": \"standard\",\n \"name\": \"Standard Light\",\n \"description\": \"Clean and safe — black-on-white, system fonts only, no external font dependencies. Default theme.\",\n \"colors\": {\n \"primary\": \"#2563eb\",\n \"secondary\": \"#475569\",\n \"background\": \"#ffffff\",\n \"backgroundLight\": \"#f1f5f9\",\n \"text\": \"#0f172a\",\n \"textMuted\": \"#475569\",\n \"highlight\": \"#1d4ed8\",\n \"warning\": \"#dc2626\"\n },\n \"typography\": {\n \"bodyFont\": {\n \"stackId\": \"system-sans\"\n },\n \"titleFont\": {\n \"stackId\": \"system-serif\"\n },\n \"monoFont\": {\n \"stackId\": \"system-mono\"\n },\n \"titleWeight\": \"bold\"\n },\n \"style\": {\n \"textShadow\": false,\n \"overlayOpacity\": 0.35,\n \"animationSpeed\": 1.0\n },\n \"renderStyle\": {\n \"name\": \"standard\",\n \"defaultTextAnimation\": \"fadeIn\",\n \"ambientMotion\": false,\n \"defaultTransition\": {\n \"type\": \"fade\",\n \"duration\": 0.7\n }\n },\n \"colorSchemes\": {\n \"blue\": {\n \"bg\": \"#dbeafe\",\n \"text\": \"#1d4ed8\",\n \"accent\": \"#2563eb\"\n },\n \"green\": {\n \"bg\": \"#dcfce7\",\n \"text\": \"#166534\",\n \"accent\": \"#16a34a\"\n },\n \"purple\": {\n \"bg\": \"#ede9fe\",\n \"text\": \"#5b21b6\",\n \"accent\": \"#7c3aed\"\n },\n \"red\": {\n \"bg\": \"#fee2e2\",\n \"text\": \"#991b1b\",\n \"accent\": \"#dc2626\"\n },\n \"orange\": {\n \"bg\": \"#ffedd5\",\n \"text\": \"#9a3412\",\n \"accent\": \"#ea580c\"\n },\n \"teal\": {\n \"bg\": \"#ccfbf1\",\n \"text\": \"#115e59\",\n \"accent\": \"#0d9488\"\n }\n }\n}\n","{\n \"schemaVersion\": \"1\",\n \"id\": \"standard-dark\",\n \"name\": \"Standard Dark\",\n \"description\": \"Standard, dark variant. System fonts, dark navy background, light text.\",\n \"colors\": {\n \"primary\": \"#3182ce\",\n \"secondary\": \"#4a5568\",\n \"background\": \"#1a202c\",\n \"backgroundLight\": \"#2d3748\",\n \"text\": \"#f7fafc\",\n \"textMuted\": \"#a0aec0\",\n \"highlight\": \"#4299e1\",\n \"warning\": \"#fc8181\"\n },\n \"typography\": {\n \"bodyFont\": {\n \"stackId\": \"system-sans\"\n },\n \"titleFont\": {\n \"stackId\": \"system-serif\"\n },\n \"monoFont\": {\n \"stackId\": \"system-mono\"\n },\n \"titleWeight\": \"bold\"\n },\n \"style\": {\n \"textShadow\": true,\n \"overlayOpacity\": 0.45,\n \"animationSpeed\": 1.0\n },\n \"renderStyle\": {\n \"name\": \"standard\",\n \"defaultTextAnimation\": \"fadeIn\",\n \"ambientMotion\": false,\n \"defaultTransition\": {\n \"type\": \"fade\",\n \"duration\": 0.7\n }\n },\n \"colorSchemes\": {\n \"blue\": {\n \"bg\": \"#1a365d\",\n \"text\": \"#63b3ed\",\n \"accent\": \"#90cdf4\"\n },\n \"green\": {\n \"bg\": \"#22543d\",\n \"text\": \"#9ae6b4\",\n \"accent\": \"#68d391\"\n },\n \"purple\": {\n \"bg\": \"#44337a\",\n \"text\": \"#d6bcfa\",\n \"accent\": \"#b794f4\"\n },\n \"red\": {\n \"bg\": \"#742a2a\",\n \"text\": \"#fc8181\",\n \"accent\": \"#feb2b2\"\n },\n \"orange\": {\n \"bg\": \"#744210\",\n \"text\": \"#fbd38d\",\n \"accent\": \"#f6ad55\"\n },\n \"teal\": {\n \"bg\": \"#234e52\",\n \"text\": \"#81e6d9\",\n \"accent\": \"#4fd1c5\"\n }\n },\n \"persistentLayers\": {\n \"bottomLayers\": [\n {\n \"template\": \"gradientBackground\",\n \"config\": {\n \"type\": \"gradientBackground\",\n \"preset\": \"dark-vignette\"\n }\n }\n ]\n }\n}\n","{\n \"schemaVersion\": \"1\",\n \"id\": \"documentary\",\n \"name\": \"Documentary\",\n \"description\": \"Classic dark cinematic look with elegant serif typography.\",\n \"colors\": {\n \"primary\": \"#3d5a80\",\n \"secondary\": \"#63b3ed\",\n \"background\": \"#1a202c\",\n \"backgroundLight\": \"#2d3748\",\n \"text\": \"#ffffff\",\n \"textMuted\": \"#a0aec0\",\n \"highlight\": \"#63b3ed\",\n \"warning\": \"#fc8181\"\n },\n \"typography\": {\n \"bodyFont\": {\n \"stackId\": \"source-serif\"\n },\n \"titleFont\": {\n \"stackId\": \"playfair\"\n },\n \"titleWeight\": \"bold\"\n },\n \"style\": {\n \"textShadow\": true,\n \"overlayOpacity\": 0.5,\n \"animationSpeed\": 1.0,\n \"imageTreatment\": {\n \"type\": \"mono\",\n \"strength\": 0.35\n }\n },\n \"renderStyle\": {\n \"name\": \"documentary\",\n \"defaultTextAnimation\": \"fadeIn\",\n \"defaultImageAnimation\": \"slowZoom\",\n \"ambientMotion\": true,\n \"defaultTransition\": {\n \"type\": \"fade\",\n \"duration\": 0.8\n }\n },\n \"colorSchemes\": {\n \"blue\": {\n \"bg\": \"#22354d\",\n \"text\": \"#a8c3e0\",\n \"accent\": \"#7fa3c9\"\n },\n \"green\": {\n \"bg\": \"#26382e\",\n \"text\": \"#a9c6ae\",\n \"accent\": \"#82a88a\"\n },\n \"purple\": {\n \"bg\": \"#35304a\",\n \"text\": \"#beb3d6\",\n \"accent\": \"#9d8fc2\"\n },\n \"red\": {\n \"bg\": \"#45262a\",\n \"text\": \"#d9a2a2\",\n \"accent\": \"#c37f7c\"\n },\n \"orange\": {\n \"bg\": \"#453522\",\n \"text\": \"#d9bb90\",\n \"accent\": \"#c29a64\"\n },\n \"teal\": {\n \"bg\": \"#1f3a3d\",\n \"text\": \"#9cc4c4\",\n \"accent\": \"#6fa5a5\"\n }\n },\n \"persistentLayers\": {\n \"bottomLayers\": [\n {\n \"template\": \"gradientBackground\",\n \"config\": {\n \"type\": \"gradientBackground\",\n \"preset\": \"dark-vignette\"\n }\n }\n ],\n \"topLayers\": [\n {\n \"template\": \"vignette\",\n \"config\": {\n \"type\": \"vignette\",\n \"strength\": 0.25\n }\n }\n ]\n }\n}\n","{\n \"schemaVersion\": \"1\",\n \"id\": \"minimalist\",\n \"name\": \"Minimalist\",\n \"description\": \"Clean near-white background with sans-serif type and minimal animation.\",\n \"colors\": {\n \"primary\": \"#2d3748\",\n \"secondary\": \"#4a5568\",\n \"background\": \"#f7fafc\",\n \"backgroundLight\": \"#edf2f7\",\n \"text\": \"#1a202c\",\n \"textMuted\": \"#718096\",\n \"highlight\": \"#3182ce\",\n \"warning\": \"#e53e3e\"\n },\n \"typography\": {\n \"bodyFont\": { \"stackId\": \"inter\" },\n \"titleFont\": { \"stackId\": \"inter\" },\n \"titleWeight\": \"bold\",\n \"lineHeight\": 1.6\n },\n \"style\": {\n \"textShadow\": false,\n \"overlayOpacity\": 0.3,\n \"animationSpeed\": 1.2,\n \"borderRadius\": 8\n },\n \"renderStyle\": {\n \"name\": \"minimalist\",\n \"defaultTextAnimation\": \"fadeIn\",\n \"ambientMotion\": false,\n \"defaultTransition\": { \"type\": \"fade\", \"duration\": 0.6 },\n \"templateHints\": {\n \"title\": { \"showAccentLine\": false }\n }\n },\n \"colorSchemes\": {\n \"blue\": { \"bg\": \"#ebf8ff\", \"text\": \"#2b6cb0\", \"accent\": \"#3182ce\" },\n \"green\": { \"bg\": \"#f0fff4\", \"text\": \"#276749\", \"accent\": \"#38a169\" },\n \"purple\": { \"bg\": \"#faf5ff\", \"text\": \"#553c9a\", \"accent\": \"#805ad5\" },\n \"red\": { \"bg\": \"#fff5f5\", \"text\": \"#c53030\", \"accent\": \"#e53e3e\" },\n \"orange\": { \"bg\": \"#fffaf0\", \"text\": \"#c05621\", \"accent\": \"#dd6b20\" },\n \"teal\": { \"bg\": \"#e6fffa\", \"text\": \"#285e61\", \"accent\": \"#319795\" }\n }\n}\n","{\n \"schemaVersion\": \"1\",\n \"id\": \"bold\",\n \"name\": \"Bold\",\n \"description\": \"High-contrast black with vivid accents and dramatic animations.\",\n \"colors\": {\n \"primary\": \"#e53e3e\",\n \"secondary\": \"#ed8936\",\n \"background\": \"#000000\",\n \"backgroundLight\": \"#1a1a1a\",\n \"text\": \"#ffffff\",\n \"textMuted\": \"#cbd5e0\",\n \"highlight\": \"#f6e05e\",\n \"warning\": \"#fc8181\"\n },\n \"typography\": {\n \"bodyFont\": { \"stackId\": \"roboto\" },\n \"titleFont\": { \"stackId\": \"oswald\" },\n \"titleWeight\": \"bold\",\n \"titleScale\": 1.15,\n \"bodyScale\": 1.0,\n \"titleLineHeight\": 1.1\n },\n \"style\": {\n \"textShadow\": true,\n \"overlayOpacity\": 0.6,\n \"animationSpeed\": 0.8\n },\n \"renderStyle\": {\n \"name\": \"bold\",\n \"defaultTextAnimation\": \"zoomIn\",\n \"defaultImageAnimation\": \"slowZoom\",\n \"ambientMotion\": true,\n \"defaultTransition\": { \"type\": \"dissolve\", \"duration\": 0.5 },\n \"templateHints\": {\n \"statHighlight\": { \"entrance\": \"dramatic\" },\n \"fullBleedQuote\": { \"entrance\": \"dramatic\" }\n }\n },\n \"colorSchemes\": {\n \"blue\": { \"bg\": \"#1a202c\", \"text\": \"#63b3ed\", \"accent\": \"#90cdf4\" },\n \"green\": { \"bg\": \"#1a202c\", \"text\": \"#48bb78\", \"accent\": \"#68d391\" },\n \"purple\": { \"bg\": \"#1a202c\", \"text\": \"#b794f4\", \"accent\": \"#d6bcfa\" },\n \"red\": { \"bg\": \"#1a202c\", \"text\": \"#fc8181\", \"accent\": \"#feb2b2\" },\n \"orange\": { \"bg\": \"#1a202c\", \"text\": \"#f6ad55\", \"accent\": \"#fbd38d\" },\n \"teal\": { \"bg\": \"#1a202c\", \"text\": \"#4fd1c5\", \"accent\": \"#81e6d9\" }\n }\n}\n","{\n \"schemaVersion\": \"1\",\n \"id\": \"morning-light\",\n \"name\": \"Morning Light\",\n \"description\": \"Warm light background with dark text and gentle serif typography.\",\n \"colors\": {\n \"primary\": \"#744210\",\n \"secondary\": \"#975a16\",\n \"background\": \"#faf8f5\",\n \"backgroundLight\": \"#f5f0eb\",\n \"text\": \"#2d3748\",\n \"textMuted\": \"#718096\",\n \"highlight\": \"#dd6b20\",\n \"warning\": \"#c53030\"\n },\n \"typography\": {\n \"bodyFont\": {\n \"stackId\": \"lora\"\n },\n \"titleFont\": {\n \"stackId\": \"merriweather\"\n },\n \"titleWeight\": \"bold\",\n \"lineHeight\": 1.7\n },\n \"style\": {\n \"textShadow\": false,\n \"overlayOpacity\": 0.35,\n \"animationSpeed\": 1.1,\n \"borderRadius\": 4\n },\n \"renderStyle\": {\n \"name\": \"morning-light\",\n \"defaultTextAnimation\": \"fadeIn\",\n \"ambientMotion\": false,\n \"defaultTransition\": {\n \"type\": \"fade\",\n \"duration\": 1.0\n }\n },\n \"colorSchemes\": {\n \"blue\": {\n \"bg\": \"#ebf8ff\",\n \"text\": \"#2c5282\",\n \"accent\": \"#4299e1\"\n },\n \"green\": {\n \"bg\": \"#f0fff4\",\n \"text\": \"#22543d\",\n \"accent\": \"#48bb78\"\n },\n \"purple\": {\n \"bg\": \"#faf5ff\",\n \"text\": \"#44337a\",\n \"accent\": \"#9f7aea\"\n },\n \"red\": {\n \"bg\": \"#fff5f5\",\n \"text\": \"#9b2c2c\",\n \"accent\": \"#fc8181\"\n },\n \"orange\": {\n \"bg\": \"#fffaf0\",\n \"text\": \"#744210\",\n \"accent\": \"#ed8936\"\n },\n \"teal\": {\n \"bg\": \"#e6fffa\",\n \"text\": \"#234e52\",\n \"accent\": \"#38b2ac\"\n }\n },\n \"persistentLayers\": {\n \"topLayers\": [\n {\n \"template\": \"patternBackground\",\n \"config\": {\n \"type\": \"patternBackground\",\n \"pattern\": \"noise\",\n \"opacity\": 0.03\n }\n }\n ]\n }\n}\n","{\n \"schemaVersion\": \"1\",\n \"id\": \"tech-dark\",\n \"name\": \"Tech Dark\",\n \"description\": \"True black with neon cyan/green accents and monospace titles.\",\n \"colors\": {\n \"primary\": \"#00e5ff\",\n \"secondary\": \"#00e676\",\n \"background\": \"#0a0a0a\",\n \"backgroundLight\": \"#1a1a2e\",\n \"text\": \"#e0e0e0\",\n \"textMuted\": \"#9e9e9e\",\n \"highlight\": \"#00e5ff\",\n \"warning\": \"#ff5252\"\n },\n \"typography\": {\n \"bodyFont\": {\n \"stackId\": \"ibm-plex-sans\"\n },\n \"titleFont\": {\n \"stackId\": \"jetbrains-mono\"\n },\n \"monoFont\": {\n \"stackId\": \"jetbrains-mono\"\n },\n \"titleWeight\": \"bold\",\n \"titleScale\": 0.9,\n \"lineHeight\": 1.5\n },\n \"style\": {\n \"textShadow\": true,\n \"overlayOpacity\": 0.55,\n \"animationSpeed\": 0.9,\n \"borderRadius\": 2,\n \"imageTreatment\": {\n \"type\": \"cool\",\n \"strength\": 0.45\n }\n },\n \"renderStyle\": {\n \"name\": \"tech-dark\",\n \"defaultTextAnimation\": \"typewriter\",\n \"defaultImageAnimation\": \"slowZoom\",\n \"ambientMotion\": true,\n \"defaultTransition\": {\n \"type\": \"dissolve\",\n \"duration\": 0.4\n },\n \"templateHints\": {\n \"statHighlight\": {\n \"entrance\": \"dramatic\"\n }\n }\n },\n \"colorSchemes\": {\n \"blue\": {\n \"bg\": \"#0d1b2a\",\n \"text\": \"#00b0ff\",\n \"accent\": \"#40c4ff\"\n },\n \"green\": {\n \"bg\": \"#0d1b0d\",\n \"text\": \"#00e676\",\n \"accent\": \"#69f0ae\"\n },\n \"purple\": {\n \"bg\": \"#1a0d2e\",\n \"text\": \"#d500f9\",\n \"accent\": \"#ea80fc\"\n },\n \"red\": {\n \"bg\": \"#2e0d0d\",\n \"text\": \"#ff5252\",\n \"accent\": \"#ff8a80\"\n },\n \"orange\": {\n \"bg\": \"#2e1a0d\",\n \"text\": \"#ff9100\",\n \"accent\": \"#ffab40\"\n },\n \"teal\": {\n \"bg\": \"#0d2e2e\",\n \"text\": \"#1de9b6\",\n \"accent\": \"#64ffda\"\n }\n },\n \"persistentLayers\": {\n \"topLayers\": [\n {\n \"template\": \"patternBackground\",\n \"config\": {\n \"type\": \"patternBackground\",\n \"pattern\": \"grid\",\n \"color\": \"#00e5ff\",\n \"opacity\": 0.04,\n \"scale\": 1.5\n }\n }\n ]\n }\n}\n","{\n \"schemaVersion\": \"1\",\n \"id\": \"magazine\",\n \"name\": \"Magazine\",\n \"description\": \"Rich editorial palette with mixed serif/sans typography and slide transitions.\",\n \"colors\": {\n \"primary\": \"#c53030\",\n \"secondary\": \"#2b6cb0\",\n \"background\": \"#1a202c\",\n \"backgroundLight\": \"#2d3748\",\n \"text\": \"#f7fafc\",\n \"textMuted\": \"#cbd5e0\",\n \"highlight\": \"#ed8936\",\n \"warning\": \"#fc8181\"\n },\n \"typography\": {\n \"bodyFont\": {\n \"stackId\": \"dm-sans\"\n },\n \"titleFont\": {\n \"stackId\": \"dm-serif\"\n },\n \"titleWeight\": \"bold\",\n \"titleScale\": 1.05,\n \"lineHeight\": 1.5\n },\n \"style\": {\n \"textShadow\": true,\n \"overlayOpacity\": 0.45,\n \"animationSpeed\": 1.0,\n \"borderRadius\": 6\n },\n \"renderStyle\": {\n \"name\": \"magazine\",\n \"defaultTextAnimation\": \"fadeIn\",\n \"defaultImageAnimation\": \"slowZoom\",\n \"ambientMotion\": true,\n \"defaultTransition\": {\n \"type\": \"slideLeft\",\n \"duration\": 0.7\n }\n },\n \"colorSchemes\": {\n \"blue\": {\n \"bg\": \"#1d3a6e\",\n \"text\": \"#a3c9f5\",\n \"accent\": \"#4d9de0\"\n },\n \"green\": {\n \"bg\": \"#14532d\",\n \"text\": \"#86efac\",\n \"accent\": \"#22c55e\"\n },\n \"purple\": {\n \"bg\": \"#3b2a70\",\n \"text\": \"#c4b5fd\",\n \"accent\": \"#a78bfa\"\n },\n \"red\": {\n \"bg\": \"#7f1d1d\",\n \"text\": \"#fca5a5\",\n \"accent\": \"#f87171\"\n },\n \"orange\": {\n \"bg\": \"#7c2d12\",\n \"text\": \"#fdba74\",\n \"accent\": \"#f97316\"\n },\n \"teal\": {\n \"bg\": \"#134e4a\",\n \"text\": \"#5eead4\",\n \"accent\": \"#14b8a6\"\n }\n },\n \"persistentLayers\": {\n \"topLayers\": [\n {\n \"template\": \"patternBackground\",\n \"config\": {\n \"type\": \"patternBackground\",\n \"pattern\": \"diagonal\",\n \"color\": \"#f7fafc\",\n \"opacity\": 0.03,\n \"scale\": 1.2\n }\n }\n ]\n }\n}\n","{\n \"schemaVersion\": \"1\",\n \"id\": \"cinematic\",\n \"name\": \"Cinematic\",\n \"description\": \"Ultra-dark with moody gradients, gold accents, and slow dissolve transitions.\",\n \"colors\": {\n \"primary\": \"#d69e2e\",\n \"secondary\": \"#b7791f\",\n \"background\": \"#0a0f1a\",\n \"backgroundLight\": \"#1a202c\",\n \"text\": \"#f7fafc\",\n \"textMuted\": \"#a0aec0\",\n \"highlight\": \"#ecc94b\",\n \"warning\": \"#fc8181\"\n },\n \"typography\": {\n \"bodyFont\": {\n \"stackId\": \"crimson\"\n },\n \"titleFont\": {\n \"stackId\": \"cormorant\"\n },\n \"titleWeight\": \"bold\",\n \"lineHeight\": 1.5,\n \"titleLineHeight\": 1.2\n },\n \"style\": {\n \"textShadow\": true,\n \"overlayOpacity\": 0.55,\n \"animationSpeed\": 1.2,\n \"imageTreatment\": {\n \"type\": \"warm\",\n \"strength\": 0.5\n }\n },\n \"renderStyle\": {\n \"name\": \"cinematic\",\n \"defaultTextAnimation\": \"fadeIn\",\n \"defaultImageAnimation\": \"slowZoom\",\n \"ambientMotion\": true,\n \"defaultTransition\": {\n \"type\": \"dissolve\",\n \"duration\": 1.2\n },\n \"templateHints\": {\n \"title\": {\n \"showAccentLine\": true\n }\n }\n },\n \"colorSchemes\": {\n \"blue\": {\n \"bg\": \"#0f1d33\",\n \"text\": \"#9db8dc\",\n \"accent\": \"#5d84b8\"\n },\n \"green\": {\n \"bg\": \"#10251c\",\n \"text\": \"#9ecfae\",\n \"accent\": \"#55a878\"\n },\n \"purple\": {\n \"bg\": \"#1f1433\",\n \"text\": \"#c3aede\",\n \"accent\": \"#8f74c0\"\n },\n \"red\": {\n \"bg\": \"#2e1114\",\n \"text\": \"#e0959a\",\n \"accent\": \"#c05f66\"\n },\n \"orange\": {\n \"bg\": \"#2e1d0b\",\n \"text\": \"#e3b978\",\n \"accent\": \"#d69e2e\"\n },\n \"teal\": {\n \"bg\": \"#0d2626\",\n \"text\": \"#93cfc7\",\n \"accent\": \"#4d9e94\"\n }\n },\n \"persistentLayers\": {\n \"bottomLayers\": [\n {\n \"template\": \"gradientBackground\",\n \"config\": {\n \"type\": \"gradientBackground\",\n \"preset\": \"radial-dark\"\n }\n }\n ],\n \"topLayers\": [\n {\n \"template\": \"vignette\",\n \"config\": {\n \"type\": \"vignette\",\n \"strength\": 0.35\n }\n },\n {\n \"template\": \"patternBackground\",\n \"config\": {\n \"type\": \"patternBackground\",\n \"pattern\": \"noise\",\n \"opacity\": 0.05\n }\n }\n ]\n }\n}\n","{\n \"schemaVersion\": \"1\",\n \"id\": \"warm-earth\",\n \"name\": \"Warm Earth\",\n \"description\": \"Natural warm tones with earthy browns and terracotta accents.\",\n \"colors\": {\n \"primary\": \"#9c4221\",\n \"secondary\": \"#c05621\",\n \"background\": \"#1c1410\",\n \"backgroundLight\": \"#2d241e\",\n \"text\": \"#faf5f0\",\n \"textMuted\": \"#c4a882\",\n \"highlight\": \"#dd6b20\",\n \"warning\": \"#e53e3e\"\n },\n \"typography\": {\n \"bodyFont\": { \"stackId\": \"pt-serif\" },\n \"titleFont\": { \"stackId\": \"pt-serif\" },\n \"titleWeight\": \"bold\",\n \"lineHeight\": 1.6\n },\n \"style\": {\n \"textShadow\": true,\n \"overlayOpacity\": 0.5,\n \"animationSpeed\": 1.1\n },\n \"renderStyle\": {\n \"name\": \"warm-earth\",\n \"defaultTextAnimation\": \"fadeIn\",\n \"defaultImageAnimation\": \"panLeft\",\n \"ambientMotion\": true,\n \"defaultTransition\": { \"type\": \"fade\", \"duration\": 0.9 }\n },\n \"colorSchemes\": {\n \"blue\": { \"bg\": \"#2a1f14\", \"text\": \"#90cdf4\", \"accent\": \"#63b3ed\" },\n \"green\": { \"bg\": \"#1e2a14\", \"text\": \"#9ae6b4\", \"accent\": \"#68d391\" },\n \"purple\": { \"bg\": \"#2a142a\", \"text\": \"#d6bcfa\", \"accent\": \"#b794f4\" },\n \"red\": { \"bg\": \"#2a1414\", \"text\": \"#fc8181\", \"accent\": \"#feb2b2\" },\n \"orange\": { \"bg\": \"#2a1e0a\", \"text\": \"#fbd38d\", \"accent\": \"#f6ad55\" },\n \"teal\": { \"bg\": \"#142a28\", \"text\": \"#81e6d9\", \"accent\": \"#4fd1c5\" }\n },\n \"persistentLayers\": {\n \"bottomLayers\": [\n {\n \"template\": \"gradientBackground\",\n \"config\": { \"type\": \"gradientBackground\", \"preset\": \"earth-tones\" }\n }\n ]\n }\n}\n","{\n \"schemaVersion\": \"1\",\n \"id\": \"gezellig\",\n \"name\": \"Gezellig\",\n \"description\": \"Warm sage + terracotta palette inspired by the Gezel desktop app. Subtle orange-tinted dark background, cream text, sage and terracotta accents.\",\n \"colors\": {\n \"primary\": \"#b0724c\",\n \"secondary\": \"#667f62\",\n \"background\": \"#1f1a17\",\n \"backgroundLight\": \"#2b2420\",\n \"text\": \"#f3ede0\",\n \"textMuted\": \"#a89b85\",\n \"highlight\": \"#c0875d\",\n \"warning\": \"#e07a6b\"\n },\n \"typography\": {\n \"bodyFont\": {\n \"stackId\": \"system-sans-emoji\"\n },\n \"titleFont\": {\n \"stackId\": \"system-serif-emoji\"\n },\n \"monoFont\": {\n \"stackId\": \"system-mono\"\n },\n \"titleWeight\": \"bold\",\n \"lineHeight\": 1.55\n },\n \"style\": {\n \"textShadow\": false,\n \"overlayOpacity\": 0.4,\n \"animationSpeed\": 1.0,\n \"borderRadius\": 6,\n \"imageTreatment\": {\n \"type\": \"warm\",\n \"strength\": 0.3\n }\n },\n \"renderStyle\": {\n \"name\": \"gezellig\",\n \"defaultTextAnimation\": \"fadeIn\",\n \"defaultImageAnimation\": \"slowZoom\",\n \"ambientMotion\": false,\n \"defaultTransition\": {\n \"type\": \"fade\",\n \"duration\": 0.6\n }\n },\n \"colorSchemes\": {\n \"terracotta\": {\n \"bg\": \"#3d2620\",\n \"text\": \"#f3ede0\",\n \"accent\": \"#c0875d\"\n },\n \"sage\": {\n \"bg\": \"#28332a\",\n \"text\": \"#e3ead9\",\n \"accent\": \"#8aa589\"\n },\n \"amber\": {\n \"bg\": \"#3d2e17\",\n \"text\": \"#fbe3a8\",\n \"accent\": \"#d4a149\"\n },\n \"plum\": {\n \"bg\": \"#2e1f2a\",\n \"text\": \"#e8d4e0\",\n \"accent\": \"#b088a5\"\n },\n \"ink\": {\n \"bg\": \"#1a1f26\",\n \"text\": \"#d5dde8\",\n \"accent\": \"#7b8fa8\"\n },\n \"cream\": {\n \"bg\": \"#f3ede0\",\n \"text\": \"#2b2420\",\n \"accent\": \"#b0724c\"\n }\n },\n \"persistentLayers\": {\n \"topLayers\": [\n {\n \"template\": \"patternBackground\",\n \"config\": {\n \"type\": \"patternBackground\",\n \"pattern\": \"noise\",\n \"opacity\": 0.04\n }\n }\n ]\n }\n}\n","/**\n * Built-in Theme Library\n *\n * Loads each built-in theme from its JSON sidecar, validates it, and\n * exports the typed `Theme` constants. The JSON files in this directory\n * are syntactically identical to customizer-authored themes — they just\n * use more of the schema.\n *\n * Validation runs at module init: if a built-in JSON drifts from the\n * schema, the import fails loudly during build / startup.\n */\n\nimport type { Theme } from '../Theme.js';\nimport { assertTheme } from '../themeValidator.js';\n\nimport standardJson from './standard.json' with { type: 'json' };\nimport standardDarkJson from './standard-dark.json' with { type: 'json' };\nimport documentaryJson from './documentary.json' with { type: 'json' };\nimport minimalistJson from './minimalist.json' with { type: 'json' };\nimport boldJson from './bold.json' with { type: 'json' };\nimport morningLightJson from './morning-light.json' with { type: 'json' };\nimport techDarkJson from './tech-dark.json' with { type: 'json' };\nimport magazineJson from './magazine.json' with { type: 'json' };\nimport cinematicJson from './cinematic.json' with { type: 'json' };\nimport warmEarthJson from './warm-earth.json' with { type: 'json' };\nimport gezelligJson from './gezellig.json' with { type: 'json' };\n\nexport const standard: Theme = assertTheme(standardJson, 'standard.json');\nexport const standardDark: Theme = assertTheme(standardDarkJson, 'standard-dark.json');\nexport const documentary: Theme = assertTheme(documentaryJson, 'documentary.json');\nexport const minimalist: Theme = assertTheme(minimalistJson, 'minimalist.json');\nexport const bold: Theme = assertTheme(boldJson, 'bold.json');\nexport const morningLight: Theme = assertTheme(morningLightJson, 'morning-light.json');\nexport const techDark: Theme = assertTheme(techDarkJson, 'tech-dark.json');\nexport const magazine: Theme = assertTheme(magazineJson, 'magazine.json');\nexport const cinematic: Theme = assertTheme(cinematicJson, 'cinematic.json');\nexport const warmEarth: Theme = assertTheme(warmEarthJson, 'warm-earth.json');\nexport const gezellig: Theme = assertTheme(gezelligJson, 'gezellig.json');\n\n/** All built-in themes, keyed by id. */\nexport const BUILTIN_THEMES: Record<string, Theme> = {\n standard,\n 'standard-dark': standardDark,\n documentary,\n minimalist,\n bold,\n 'morning-light': morningLight,\n 'tech-dark': techDark,\n magazine,\n cinematic,\n 'warm-earth': warmEarth,\n gezellig,\n};\n","/**\n * Built-in Theme Library\n *\n * Public entry point for theme lookup. Built-in themes live as JSON\n * sidecars in [./themes/](./themes/) — they share the exact same shape\n * as customizer-authored themes (no two-tier distinction).\n *\n * Usage:\n * ```ts\n * import { THEMES, resolveTheme } from '@bendyline/squisq/schemas';\n *\n * const theme = resolveTheme('minimalist'); // by id, with registry override\n * const theme = THEMES.documentary; // direct access to a built-in\n * ```\n */\n\nimport type { Theme } from './Theme.js';\nimport { lookupRegisteredTheme } from './Theme.js';\nimport { BUILTIN_THEMES } from './themes/index.js';\n\n/** All built-in themes, keyed by id. */\nexport const THEMES: Record<string, Theme> = BUILTIN_THEMES;\n\n/** The default theme id. */\nexport const DEFAULT_THEME_ID = 'standard';\n\n/** The default theme (standard — safe system fonts, no external dependencies). */\nexport const DEFAULT_THEME: Theme = THEMES[DEFAULT_THEME_ID];\n\n/**\n * Resolve a theme by id. Custom themes registered via `registerTheme()`\n * take precedence over built-ins; otherwise built-ins are searched and\n * the default theme is returned for unknown ids.\n */\nexport function resolveTheme(id: string | undefined): Theme {\n const registered = lookupRegisteredTheme(id);\n if (registered) return registered;\n if (id && id in THEMES) return THEMES[id];\n return DEFAULT_THEME;\n}\n\n/** Get all built-in theme ids. */\nexport function getAvailableThemes(): string[] {\n return Object.keys(THEMES);\n}\n\n/** Get a summary of all built-in themes (id + name + description) for theme pickers. */\nexport function getThemeSummaries(): Array<{ id: string; name: string; description?: string }> {\n return Object.values(THEMES).map((t) => ({\n id: t.id,\n name: t.name,\n description: t.description,\n }));\n}\n","/**\n * Font Stacks\n *\n * Curated registry of font families used by built-in themes and offered in\n * the theme customizer. Each stack has a stable `id` (referenced by JSON\n * theme files via `{ stackId }`) and a CSS `family` string that resolves\n * to the actual font when rendered.\n *\n * Themes never carry raw CSS font strings — they reference a `stackId` or\n * specify a `{ custom: { name, fallback } }` form, both of which resolve\n * here at render time.\n */\n\nimport type { FontFamily, FontFamilyKind } from './Theme.js';\n\nexport interface FontStack {\n /** Stable identifier referenced by Theme JSON. */\n id: string;\n /** Display label for pickers. */\n label: string;\n /** Full CSS font-family string with safe fallbacks. */\n family: string;\n /** Categorical bucket for the picker UI. */\n kind: FontFamilyKind;\n /**\n * Google Fonts family name, when this stack is served by Google Fonts.\n * Used by exports that bundle a `<link>` to fonts.googleapis.com so the\n * face renders correctly even when the host page doesn't preload it.\n * Omit (or set to undefined) for purely system stacks.\n */\n googleFontFamily?: string;\n}\n\n/** All curated stacks, in the order they should appear in pickers. */\nexport const AVAILABLE_FONT_STACKS: FontStack[] = [\n // System defaults\n {\n id: 'system-sans',\n label: 'System Sans',\n family: 'system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif',\n kind: 'sans',\n },\n {\n id: 'system-serif',\n label: 'System Serif',\n family: 'Georgia, \"Times New Roman\", serif',\n kind: 'serif',\n },\n {\n id: 'system-mono',\n label: 'System Mono',\n family: 'Consolas, \"Courier New\", monospace',\n kind: 'mono',\n },\n // Curated Google Fonts. `googleFontFamily` is the canonical family\n // name Google serves the face under (used by exports that emit a\n // `<link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/...\">`\n // so the face renders even when the host doesn't preload it).\n {\n id: 'inter',\n label: 'Inter',\n family: '\"Inter\", \"Segoe UI\", Roboto, sans-serif',\n kind: 'sans',\n googleFontFamily: 'Inter',\n },\n {\n id: 'ibm-plex-sans',\n label: 'IBM Plex Sans',\n family: '\"IBM Plex Sans\", \"Segoe UI\", Roboto, sans-serif',\n kind: 'sans',\n googleFontFamily: 'IBM Plex Sans',\n },\n {\n id: 'roboto',\n label: 'Roboto',\n family: '\"Roboto\", \"Segoe UI\", Arial, sans-serif',\n kind: 'sans',\n googleFontFamily: 'Roboto',\n },\n {\n id: 'dm-sans',\n label: 'DM Sans',\n family: '\"DM Sans\", \"Segoe UI\", Roboto, sans-serif',\n kind: 'sans',\n googleFontFamily: 'DM Sans',\n },\n {\n id: 'oswald',\n label: 'Oswald',\n // Condensed grotesques make a far better stand-in for Oswald than\n // Impact/Arial Black, whose extreme weight distorts every layout\n // tuned for the real face.\n family: '\"Oswald\", \"Arial Narrow\", \"Helvetica Neue Condensed\", \"Roboto Condensed\", sans-serif',\n kind: 'display',\n googleFontFamily: 'Oswald',\n },\n {\n id: 'playfair',\n label: 'Playfair Display',\n family: '\"Playfair Display\", Georgia, serif',\n kind: 'serif',\n googleFontFamily: 'Playfair Display',\n },\n {\n id: 'source-serif',\n label: 'Source Serif',\n family: '\"Source Serif 4\", \"PT Serif\", Georgia, serif',\n kind: 'serif',\n googleFontFamily: 'Source Serif 4',\n },\n {\n id: 'merriweather',\n label: 'Merriweather',\n family: '\"Merriweather\", Georgia, serif',\n kind: 'serif',\n googleFontFamily: 'Merriweather',\n },\n {\n id: 'lora',\n label: 'Lora',\n family: '\"Lora\", \"Merriweather\", Georgia, serif',\n kind: 'serif',\n googleFontFamily: 'Lora',\n },\n {\n id: 'pt-serif',\n label: 'PT Serif',\n family: '\"PT Serif\", Georgia, serif',\n kind: 'serif',\n googleFontFamily: 'PT Serif',\n },\n {\n id: 'crimson',\n label: 'Crimson Text',\n family: '\"Crimson Text\", Georgia, serif',\n kind: 'serif',\n googleFontFamily: 'Crimson Text',\n },\n {\n id: 'cormorant',\n label: 'Cormorant Garamond',\n family: '\"Cormorant Garamond\", Garamond, Georgia, serif',\n kind: 'serif',\n googleFontFamily: 'Cormorant Garamond',\n },\n {\n id: 'dm-serif',\n label: 'DM Serif Display',\n family: '\"DM Serif Display\", Georgia, serif',\n kind: 'display',\n googleFontFamily: 'DM Serif Display',\n },\n {\n id: 'jetbrains-mono',\n label: 'JetBrains Mono',\n family: '\"JetBrains Mono\", \"Fira Code\", Consolas, monospace',\n kind: 'mono',\n googleFontFamily: 'JetBrains Mono',\n },\n // Themed system stacks with emoji fallbacks (used by gezellig)\n {\n id: 'system-sans-emoji',\n label: 'System Sans + Emoji',\n family:\n 'system-ui, -apple-system, \"Segoe UI\", Roboto, \"OpenMoji Color\", \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Noto Color Emoji\", sans-serif',\n kind: 'sans',\n },\n {\n id: 'system-serif-emoji',\n label: 'System Serif + Emoji',\n family:\n 'Georgia, \"Times New Roman\", \"OpenMoji Color\", \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Noto Color Emoji\", serif',\n kind: 'serif',\n },\n];\n\nconst STACKS_BY_ID: Record<string, FontStack> = Object.fromEntries(\n AVAILABLE_FONT_STACKS.map((s) => [s.id, s]),\n);\n\n/** Look up a stack by id. Returns undefined if not registered. */\nexport function getFontStack(id: string): FontStack | undefined {\n return STACKS_BY_ID[id];\n}\n\n/** Default fallback CSS family per kind, used when a custom font has no fallback specified. */\nconst FALLBACK_BY_KIND: Record<FontFamilyKind, string> = {\n sans: 'system-ui, sans-serif',\n serif: 'Georgia, serif',\n mono: 'Consolas, monospace',\n display: '\"Arial Narrow\", \"Helvetica Neue\", Arial, sans-serif',\n};\n\n/**\n * Resolve a `FontFamily` reference (or legacy raw string) to a CSS family string.\n * Used by `getThemeFont` at render time. Never returns an empty string —\n * unrecognized stackIds fall back to the requested role's default.\n */\nexport function resolveFontFamily(\n family: FontFamily | string | undefined,\n roleFallback: string,\n): string {\n if (!family) return roleFallback;\n if (typeof family === 'string') return family;\n if ('stackId' in family) {\n const stack = STACKS_BY_ID[family.stackId];\n return stack ? stack.family : roleFallback;\n }\n if ('custom' in family) {\n const { name, fallback } = family.custom;\n const fb =\n fallback === 'system-ui'\n ? 'system-ui, sans-serif'\n : fallback === 'serif'\n ? 'Georgia, serif'\n : fallback === 'monospace'\n ? 'Consolas, monospace'\n : 'system-ui, sans-serif';\n const safeName = name.includes(' ') || name.includes(',') ? `\"${name}\"` : name;\n return `${safeName}, ${fb}`;\n }\n return roleFallback;\n}\n\n/** Convenience: quick `{ stackId }` factory for code that constructs themes. */\nexport function fontStack(id: string): FontFamily {\n return { stackId: id };\n}\n\n/**\n * Build a `https://fonts.googleapis.com/css2?...` URL that loads the\n * subset of supplied `FontFamily` references that are served by Google\n * Fonts. Returns `null` when nothing in the list needs Google hosting\n * (system stacks, custom fonts, or unknown stack ids) — callers can use\n * that to skip emitting a `<link>` entirely.\n *\n * The URL requests regular + bold (400/700) for each family, which is\n * what the templates use. `display=swap` keeps the fallback visible\n * while the web font streams in.\n */\nexport function buildGoogleFontsUrl(families: Array<FontFamily | undefined>): string | null {\n const names = new Set<string>();\n for (const f of families) {\n if (!f || typeof f === 'string') continue;\n if ('stackId' in f) {\n const stack = STACKS_BY_ID[f.stackId];\n if (stack?.googleFontFamily) names.add(stack.googleFontFamily);\n }\n }\n if (names.size === 0) return null;\n // css2 syntax: ?family=Name+With+Spaces:wght@400;700&family=Other:wght@400;700&display=swap\n const params = Array.from(names)\n .sort()\n .map((name) => `family=${encodeURIComponent(name).replace(/%20/g, '+')}:wght@400;700`)\n .join('&');\n return `https://fonts.googleapis.com/css2?${params}&display=swap`;\n}\n\n/** Default fallback families exposed for tools that need them. */\nexport const FONT_FALLBACKS = FALLBACK_BY_KIND;\n"],"mappings":"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","names":[]}