@bendyline/squisq 1.5.2 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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-T5UK6YOB.js → chunk-2PWOO4ZE.js} +60 -17
  6. package/dist/chunk-2PWOO4ZE.js.map +1 -0
  7. package/dist/{chunk-VWUFZ6ZG.js → chunk-3E5F2XMR.js} +2 -29
  8. package/dist/chunk-3E5F2XMR.js.map +1 -0
  9. package/dist/chunk-4X3JQXNM.js +35 -0
  10. package/dist/chunk-4X3JQXNM.js.map +1 -0
  11. package/dist/{chunk-P3K7NLHF.js → chunk-5DKBFXPY.js} +31 -121
  12. package/dist/chunk-5DKBFXPY.js.map +1 -0
  13. package/dist/{chunk-DLXZMS5K.js → chunk-5J4HKRJF.js} +51 -3
  14. package/dist/chunk-5J4HKRJF.js.map +1 -0
  15. package/dist/chunk-5UYI3RJB.js +2503 -0
  16. package/dist/chunk-5UYI3RJB.js.map +1 -0
  17. package/dist/{chunk-BEQODJWV.js → chunk-66QO7XFB.js} +11 -291
  18. package/dist/chunk-66QO7XFB.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-CY2R2VPR.js +884 -0
  22. package/dist/chunk-CY2R2VPR.js.map +1 -0
  23. package/dist/chunk-FVJUUTEM.js +74 -0
  24. package/dist/chunk-FVJUUTEM.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-MMPBOUK7.js} +10 -73
  30. package/dist/chunk-MMPBOUK7.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-GPLTCSXO.js → chunk-R2GC6RLD.js} +7 -7
  36. package/dist/chunk-R2GC6RLD.js.map +1 -0
  37. package/dist/chunk-R2YZTCLO.js +292 -0
  38. package/dist/chunk-R2YZTCLO.js.map +1 -0
  39. package/dist/chunk-S7A5D7ZJ.js +174 -0
  40. package/dist/chunk-S7A5D7ZJ.js.map +1 -0
  41. package/dist/chunk-TFJVXUHH.js +2358 -0
  42. package/dist/chunk-TFJVXUHH.js.map +1 -0
  43. package/dist/{chunk-LTN6I7QW.js → chunk-TUWMK62B.js} +55 -4
  44. package/dist/chunk-TUWMK62B.js.map +1 -0
  45. package/dist/{chunk-IMSCRLLV.js → chunk-VR7UPJ7C.js} +35 -25
  46. package/dist/chunk-VR7UPJ7C.js.map +1 -0
  47. package/dist/{chunk-KKNUBQ6Y.js → chunk-W4O2TX5I.js} +157 -92
  48. package/dist/chunk-W4O2TX5I.js.map +1 -0
  49. package/dist/{chunk-5TYS5V3G.js → chunk-X6DUONGT.js} +4204 -3560
  50. package/dist/chunk-X6DUONGT.js.map +1 -0
  51. package/dist/doc/index.d.ts +2334 -5
  52. package/dist/doc/index.js +127 -33
  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 +246 -80
  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__/customTemplatesFrontmatter.test.ts +0 -15
  97. package/src/__tests__/customThemesFrontmatter.test.ts +1 -0
  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 +54 -4
  114. package/src/__tests__/markdownSanitize.test.ts +7 -5
  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 +12 -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 +70 -2
  164. package/src/doc/index.ts +76 -4
  165. package/src/doc/markdownToDoc.ts +138 -2
  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/__tests__/listBlock.test.ts +64 -0
  172. package/src/doc/templates/diagramBlock.ts +237 -38
  173. package/src/doc/templates/fallbackBlock.ts +1 -1
  174. package/src/doc/templates/index.ts +134 -279
  175. package/src/doc/templates/inputDescriptors.ts +20 -1
  176. package/src/doc/templates/listBlock.ts +88 -30
  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 +67 -5
  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/index.ts +1 -0
  204. package/src/markdown/stringify.ts +4 -9
  205. package/src/markdown/utils.ts +36 -14
  206. package/src/narration/align.ts +514 -0
  207. package/src/narration/features.ts +231 -0
  208. package/src/narration/index.ts +86 -0
  209. package/src/narration/nuclei.ts +118 -0
  210. package/src/narration/pacing.ts +211 -0
  211. package/src/narration/script.ts +239 -0
  212. package/src/narration/session.ts +100 -0
  213. package/src/narration/sidecar.ts +187 -0
  214. package/src/narration/syllables.ts +85 -0
  215. package/src/narration/trace.ts +50 -0
  216. package/src/narration/types.ts +299 -0
  217. package/src/narration/vad.ts +131 -0
  218. package/src/recommend/numberHighlight.ts +25 -0
  219. package/src/recommend/templates.ts +108 -12
  220. package/src/schemas/BlockTemplates.ts +156 -7
  221. package/src/schemas/Doc.ts +72 -9
  222. package/src/schemas/Media.ts +12 -2
  223. package/src/schemas/Theme.ts +45 -25
  224. package/src/schemas/Viewport.ts +2 -2
  225. package/src/schemas/fontStacks.ts +79 -0
  226. package/src/schemas/themeCompile.ts +15 -0
  227. package/src/schemas/themeConstants.ts +2 -0
  228. package/src/schemas/themeLibrary.ts +25 -19
  229. package/src/schemas/themeValidator.ts +1 -1
  230. package/src/schemas/themes/gezellig.json +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-5TYS5V3G.js.map +0 -1
  247. package/dist/chunk-A4O7GIWE.js.map +0 -1
  248. package/dist/chunk-BEQODJWV.js.map +0 -1
  249. package/dist/chunk-DLXZMS5K.js.map +0 -1
  250. package/dist/chunk-GPLTCSXO.js.map +0 -1
  251. package/dist/chunk-HWVFJAAH.js.map +0 -1
  252. package/dist/chunk-IMSCRLLV.js.map +0 -1
  253. package/dist/chunk-JELIRVDP.js +0 -24
  254. package/dist/chunk-JELIRVDP.js.map +0 -1
  255. package/dist/chunk-KKNUBQ6Y.js.map +0 -1
  256. package/dist/chunk-LDQ2HJIX.js.map +0 -1
  257. package/dist/chunk-LTN6I7QW.js.map +0 -1
  258. package/dist/chunk-P3K7NLHF.js.map +0 -1
  259. package/dist/chunk-T5UK6YOB.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 -1801
  266. package/dist/story/index.js +0 -279
  267. package/src/doc/getLayers.ts +0 -195
  268. /package/dist/{story → narration}/index.js.map +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/doc/utils/shapeGeometry.ts"],"sourcesContent":["/**\n * shapeGeometry — pure SVG geometry for the drawing/diagram shape vocabulary.\n *\n * Three concerns, all deterministic and dependency-free:\n * - `shapePath(kind, x, y, w, h)` → an SVG path `d` for a named shape inscribed\n * in a bounding box. Returns null for the natively-rendered primitives\n * (rect / circle / line / text / path) so the caller can keep using a\n * `ShapeLayer` for those; everything else becomes a computed `PathLayer`.\n * - `markerPath(style, dir)` → the `<marker>` path for a line end-style, in the\n * standard 0–10 marker viewBox (shared by the SSR `PathLayer` renderer and the\n * editor's edge renderer).\n * - `connectorPath(routing, start, end)` + `snapEndpoints(a, b)` → the line/edge\n * geometry between two shapes (straight / orthogonal / curved), lifted from the\n * diagram template so drawings and diagrams draw identical connectors. The older\n * `clipEndpoints(a, b)` helper remains for free-angle clipping, but rendered\n * connectors use snapped ports so authored lines land on stable attachment points.\n */\n\nimport type { MarkerStyle } from '../../schemas/Doc.js';\n\n// ============================================\n// Shapes\n// ============================================\n\n/** Connector routing styles. */\nexport type ConnectorRouting = 'straight' | 'orthogonal' | 'curved';\n\n/**\n * Shape kinds rendered as computed paths (everything except the primitives the\n * renderer draws natively). Used by templates to decide ShapeLayer vs PathLayer.\n */\nexport const PATH_SHAPE_KINDS = new Set([\n 'triangle',\n 'right-triangle',\n 'diamond',\n 'pentagon',\n 'hexagon',\n 'octagon',\n 'star',\n 'star4',\n 'star6',\n 'parallelogram',\n 'trapezoid',\n 'plus',\n 'chevron',\n 'arrow-right',\n 'arrow-left',\n 'arrow-up',\n 'arrow-down',\n 'double-arrow',\n 'callout',\n 'cylinder',\n 'cloud',\n 'heart',\n 'lightning',\n]);\n\n/** Round to 1 decimal for compact, stable path output. */\nfunction r(n: number): number {\n return Math.round(n * 10) / 10;\n}\n\n/** Close a list of points into an `M … L … Z` polygon path. */\nfunction poly(points: ReadonlyArray<readonly [number, number]>): string {\n if (points.length === 0) return '';\n const [first, ...rest] = points;\n const head = `M ${r(first[0])} ${r(first[1])}`;\n const tail = rest.map(([x, y]) => `L ${r(x)} ${r(y)}`).join(' ');\n return `${head} ${tail} Z`;\n}\n\n/** Regular polygon points inscribed in the box ellipse, `rotation` in radians. */\nfunction regular(\n cx: number,\n cy: number,\n rx: number,\n ry: number,\n sides: number,\n rotation: number,\n): Array<[number, number]> {\n const pts: Array<[number, number]> = [];\n for (let i = 0; i < sides; i++) {\n const a = rotation + (i * 2 * Math.PI) / sides;\n pts.push([cx + rx * Math.cos(a), cy + ry * Math.sin(a)]);\n }\n return pts;\n}\n\n/** Star points alternating outer/inner radius. */\nfunction star(\n cx: number,\n cy: number,\n rx: number,\n ry: number,\n spikes: number,\n innerRatio: number,\n): Array<[number, number]> {\n const pts: Array<[number, number]> = [];\n const rot = -Math.PI / 2; // first point up\n for (let i = 0; i < spikes * 2; i++) {\n const outer = i % 2 === 0;\n const a = rot + (i * Math.PI) / spikes;\n const fx = outer ? rx : rx * innerRatio;\n const fy = outer ? ry : ry * innerRatio;\n pts.push([cx + fx * Math.cos(a), cy + fy * Math.sin(a)]);\n }\n return pts;\n}\n\n/**\n * SVG path `d` for a named shape inscribed in `[x, y, w, h]`, or null when the\n * kind is rendered natively (rect / circle / line / text / path / arrow).\n */\nexport function shapePath(kind: string, x: number, y: number, w: number, h: number): string | null {\n const cx = x + w / 2;\n const cy = y + h / 2;\n const rx = w / 2;\n const ry = h / 2;\n const right = x + w;\n const bottom = y + h;\n\n switch (kind) {\n case 'triangle':\n return poly([\n [cx, y],\n [right, bottom],\n [x, bottom],\n ]);\n case 'right-triangle':\n return poly([\n [x, y],\n [x, bottom],\n [right, bottom],\n ]);\n case 'diamond':\n return poly([\n [cx, y],\n [right, cy],\n [cx, bottom],\n [x, cy],\n ]);\n case 'pentagon':\n return poly(regular(cx, cy, rx, ry, 5, -Math.PI / 2));\n case 'hexagon': {\n // Flat top/bottom hexagon: inset the left/right vertices.\n const inset = w * 0.25;\n return poly([\n [x + inset, y],\n [right - inset, y],\n [right, cy],\n [right - inset, bottom],\n [x + inset, bottom],\n [x, cy],\n ]);\n }\n case 'octagon': {\n const cut = Math.min(w, h) * 0.29;\n return poly([\n [x + cut, y],\n [right - cut, y],\n [right, y + cut],\n [right, bottom - cut],\n [right - cut, bottom],\n [x + cut, bottom],\n [x, bottom - cut],\n [x, y + cut],\n ]);\n }\n case 'star':\n return poly(star(cx, cy, rx, ry, 5, 0.4));\n case 'star4':\n return poly(star(cx, cy, rx, ry, 4, 0.4));\n case 'star6':\n return poly(star(cx, cy, rx, ry, 6, 0.5));\n case 'parallelogram': {\n const off = w * 0.25;\n return poly([\n [x + off, y],\n [right, y],\n [right - off, bottom],\n [x, bottom],\n ]);\n }\n case 'trapezoid': {\n const off = w * 0.25;\n return poly([\n [x + off, y],\n [right - off, y],\n [right, bottom],\n [x, bottom],\n ]);\n }\n case 'plus': {\n const t = Math.min(w, h) / 3; // arm thickness\n const lx = x + (w - t) / 2;\n const rxp = lx + t;\n const ty = y + (h - t) / 2;\n const byp = ty + t;\n return poly([\n [lx, y],\n [rxp, y],\n [rxp, ty],\n [right, ty],\n [right, byp],\n [rxp, byp],\n [rxp, bottom],\n [lx, bottom],\n [lx, byp],\n [x, byp],\n [x, ty],\n [lx, ty],\n ]);\n }\n case 'chevron': {\n const notch = w * 0.25;\n return poly([\n [x, y],\n [right - notch, y],\n [right, cy],\n [right - notch, bottom],\n [x, bottom],\n [x + notch, cy],\n ]);\n }\n case 'arrow-right':\n return blockArrow(x, y, w, h, 'right');\n case 'arrow-left':\n return blockArrow(x, y, w, h, 'left');\n case 'arrow-up':\n return blockArrow(x, y, w, h, 'up');\n case 'arrow-down':\n return blockArrow(x, y, w, h, 'down');\n case 'double-arrow': {\n const head = w * 0.3;\n const bodyT = h * 0.4;\n const ty = cy - bodyT / 2;\n const by = cy + bodyT / 2;\n return poly([\n [x, cy],\n [x + head, y],\n [x + head, ty],\n [right - head, ty],\n [right - head, y],\n [right, cy],\n [right - head, bottom],\n [right - head, by],\n [x + head, by],\n [x + head, bottom],\n ]);\n }\n case 'callout': {\n // Speech bubble: rounded-ish body (square corners for path simplicity)\n // with a tail off the lower-left.\n const bodyH = h * 0.78;\n const bb = y + bodyH;\n const tailX = x + w * 0.25;\n return `M ${r(x)} ${r(y)} L ${r(right)} ${r(y)} L ${r(right)} ${r(bb)} L ${r(tailX + w * 0.12)} ${r(bb)} L ${r(tailX)} ${r(bottom)} L ${r(tailX - w * 0.02)} ${r(bb)} L ${r(x)} ${r(bb)} Z`;\n }\n case 'cylinder': {\n const ell = h * 0.16; // ellipse half-height\n return (\n `M ${r(x)} ${r(y + ell)} ` +\n `A ${r(rx)} ${r(ell)} 0 0 1 ${r(right)} ${r(y + ell)} ` +\n `L ${r(right)} ${r(bottom - ell)} ` +\n `A ${r(rx)} ${r(ell)} 0 0 1 ${r(x)} ${r(bottom - ell)} ` +\n `Z ` +\n // top ellipse outline\n `M ${r(x)} ${r(y + ell)} ` +\n `A ${r(rx)} ${r(ell)} 0 0 0 ${r(right)} ${r(y + ell)}`\n );\n }\n case 'cloud': {\n // Four bumps along the top + a flat-ish bottom, via arcs.\n const b = h * 0.5;\n return (\n `M ${r(x + w * 0.2)} ${r(bottom)} ` +\n `A ${r(w * 0.2)} ${r(b)} 0 0 1 ${r(x + w * 0.15)} ${r(cy)} ` +\n `A ${r(w * 0.2)} ${r(b)} 0 0 1 ${r(x + w * 0.45)} ${r(y + h * 0.2)} ` +\n `A ${r(w * 0.2)} ${r(b)} 0 0 1 ${r(x + w * 0.8)} ${r(y + h * 0.25)} ` +\n `A ${r(w * 0.18)} ${r(b)} 0 0 1 ${r(x + w * 0.85)} ${r(bottom)} ` +\n `Z`\n );\n }\n case 'heart': {\n const topY = y + h * 0.3;\n return (\n `M ${r(cx)} ${r(bottom)} ` +\n `C ${r(x - w * 0.1)} ${r(cy)} ${r(x)} ${r(y)} ${r(cx)} ${r(topY)} ` +\n `C ${r(right)} ${r(y)} ${r(right + w * 0.1)} ${r(cy)} ${r(cx)} ${r(bottom)} ` +\n `Z`\n );\n }\n case 'lightning':\n return poly([\n [x + w * 0.55, y],\n [x + w * 0.2, y + h * 0.55],\n [x + w * 0.45, y + h * 0.55],\n [x + w * 0.3, bottom],\n [right, y + h * 0.4],\n [x + w * 0.55, y + h * 0.4],\n ]);\n default:\n return null;\n }\n}\n\n/** Block arrow (body rectangle + triangular head) pointing in `dir`. */\nfunction blockArrow(\n x: number,\n y: number,\n w: number,\n h: number,\n dir: 'right' | 'left' | 'up' | 'down',\n): string {\n const right = x + w;\n const bottom = y + h;\n const cx = x + w / 2;\n const cy = y + h / 2;\n if (dir === 'right' || dir === 'left') {\n const head = w * 0.4;\n const bodyT = h * 0.5;\n const ty = cy - bodyT / 2;\n const by = cy + bodyT / 2;\n if (dir === 'right') {\n return poly([\n [x, ty],\n [right - head, ty],\n [right - head, y],\n [right, cy],\n [right - head, bottom],\n [right - head, by],\n [x, by],\n ]);\n }\n return poly([\n [right, ty],\n [x + head, ty],\n [x + head, y],\n [x, cy],\n [x + head, bottom],\n [x + head, by],\n [right, by],\n ]);\n }\n const head = h * 0.4;\n const bodyT = w * 0.5;\n const lx = cx - bodyT / 2;\n const rxp = cx + bodyT / 2;\n if (dir === 'down') {\n return poly([\n [lx, y],\n [rxp, y],\n [rxp, bottom - head],\n [right, bottom - head],\n [cx, bottom],\n [x, bottom - head],\n [lx, bottom - head],\n ]);\n }\n return poly([\n [lx, bottom],\n [rxp, bottom],\n [rxp, y + head],\n [right, y + head],\n [cx, y],\n [x, y + head],\n [lx, y + head],\n ]);\n}\n\n// ============================================\n// End markers\n// ============================================\n\n/**\n * The `<marker>` path for a line end-style, in the 0–10 marker viewBox.\n * `dir` selects orientation (`end` points along +x; `start` is mirrored).\n * Returns null for `'none'`. `filled` shapes paint with the stroke color;\n * non-filled (`open`) draw as a stroked outline.\n */\nexport function markerPath(\n style: MarkerStyle,\n dir: 'start' | 'end',\n): { d: string; filled: boolean } | null {\n const mirror = dir === 'start';\n const mx = (n: number) => (mirror ? 10 - n : n);\n const tri = (closed: boolean) => `M ${mx(0)} 0 L ${mx(10)} 5 L ${mx(0)} 10${closed ? ' z' : ''}`;\n switch (style) {\n case 'arrow':\n return { d: tri(true), filled: true };\n case 'open':\n return { d: tri(false), filled: false };\n case 'diamond':\n return { d: 'M 0 5 L 5 0 L 10 5 L 5 10 z', filled: true };\n case 'square':\n return { d: 'M 1 1 L 9 1 L 9 9 L 1 9 z', filled: true };\n case 'circle':\n return { d: 'M 0 5 A 5 5 0 1 0 10 5 A 5 5 0 1 0 0 5 z', filled: true };\n case 'none':\n default:\n return null;\n }\n}\n\n// ============================================\n// Connector routing\n// ============================================\n\nexport interface ClipBox {\n cx: number;\n cy: number;\n rx: number;\n ry: number;\n}\n\nexport type ConnectorPort =\n | 'top'\n | 'right'\n | 'bottom'\n | 'left'\n | 'top-left'\n | 'top-right'\n | 'bottom-right'\n | 'bottom-left';\n\nexport interface ConnectorSnapPoint {\n port: ConnectorPort;\n x: number;\n y: number;\n}\n\nexport interface ConnectorAnchor {\n side: 'top' | 'right' | 'bottom' | 'left';\n offset: number;\n}\n\n/** Intersection of the center-to-center line with `from`'s bounding box. */\nexport function clipPoint(from: ClipBox, to: ClipBox): { x: number; y: number } {\n const dx = to.cx - from.cx;\n const dy = to.cy - from.cy;\n if (dx === 0 && dy === 0) return { x: from.cx, y: from.cy };\n const sx = Math.abs(dx) > 0 ? from.rx / Math.abs(dx) : Infinity;\n const sy = Math.abs(dy) > 0 ? from.ry / Math.abs(dy) : Infinity;\n const s = Math.min(sx, sy);\n return { x: from.cx + dx * s, y: from.cy + dy * s };\n}\n\n/** Clip both endpoints of an edge to the two shapes it joins. */\nexport function clipEndpoints(\n a: ClipBox,\n b: ClipBox,\n): { start: { x: number; y: number }; end: { x: number; y: number } } {\n return { start: clipPoint(a, b), end: clipPoint(b, a) };\n}\n\n/** The stable connector ports exposed around a shape/card box. */\nexport function snapPoints(box: ClipBox): ConnectorSnapPoint[] {\n const left = box.cx - box.rx;\n const right = box.cx + box.rx;\n const top = box.cy - box.ry;\n const bottom = box.cy + box.ry;\n return [\n { port: 'top', x: box.cx, y: top },\n { port: 'right', x: right, y: box.cy },\n { port: 'bottom', x: box.cx, y: bottom },\n { port: 'left', x: left, y: box.cy },\n { port: 'top-left', x: left, y: top },\n { port: 'top-right', x: right, y: top },\n { port: 'bottom-right', x: right, y: bottom },\n { port: 'bottom-left', x: left, y: bottom },\n ];\n}\n\n/** Resolve a normalized authored anchor to a concrete point on a box. */\nexport function anchorPoint(box: ClipBox, anchor: ConnectorAnchor): ConnectorSnapPoint {\n const offset = Math.max(0, Math.min(1, anchor.offset));\n const left = box.cx - box.rx;\n const right = box.cx + box.rx;\n const top = box.cy - box.ry;\n const bottom = box.cy + box.ry;\n switch (anchor.side) {\n case 'top':\n return { port: 'top', x: left + (right - left) * offset, y: top };\n case 'right':\n return { port: 'right', x: right, y: top + (bottom - top) * offset };\n case 'bottom':\n return { port: 'bottom', x: left + (right - left) * offset, y: bottom };\n case 'left':\n return { port: 'left', x: left, y: top + (bottom - top) * offset };\n }\n}\n\n/** Nearest stable connector port on `box` to an arbitrary point. */\nexport function nearestSnapPoint(\n box: ClipBox,\n point: { x: number; y: number },\n): ConnectorSnapPoint {\n let best = snapPoints(box)[0];\n let bestScore = Number.POSITIVE_INFINITY;\n for (const candidate of snapPoints(box)) {\n const score = distanceSq(candidate, point) + portTieBreak(candidate.port);\n if (score < bestScore) {\n best = candidate;\n bestScore = score;\n }\n }\n return best;\n}\n\n/**\n * Pick the pair of connector ports, one on each box, with the shortest\n * distance between them. This gives stable attachments to side/corner handles\n * instead of arbitrary center-ray intersections.\n */\nexport function snapEndpoints(\n a: ClipBox,\n b: ClipBox,\n): { start: ConnectorSnapPoint; end: ConnectorSnapPoint } {\n let bestStart = snapPoints(a)[0];\n let bestEnd = snapPoints(b)[0];\n let bestScore = Number.POSITIVE_INFINITY;\n for (const start of snapPoints(a)) {\n for (const end of snapPoints(b)) {\n const score = distanceSq(start, end) + portTieBreak(start.port) + portTieBreak(end.port);\n if (score < bestScore) {\n bestStart = start;\n bestEnd = end;\n bestScore = score;\n }\n }\n }\n return { start: bestStart, end: bestEnd };\n}\n\nfunction distanceSq(a: { x: number; y: number }, b: { x: number; y: number }): number {\n const dx = a.x - b.x;\n const dy = a.y - b.y;\n return dx * dx + dy * dy;\n}\n\nfunction portTieBreak(port: ConnectorPort): number {\n return port.includes('-') ? 0.02 : 0;\n}\n\n/** Build the SVG `d` for a connector between two clipped endpoints. */\nexport function connectorPath(\n routing: ConnectorRouting,\n start: { x: number; y: number; port?: ConnectorPort },\n end: { x: number; y: number; port?: ConnectorPort },\n): string {\n if (routing === 'straight') {\n return `M ${r(start.x)} ${r(start.y)} L ${r(end.x)} ${r(end.y)}`;\n }\n if (routing === 'orthogonal') {\n const startAxis = portAxis(start.port);\n const endAxis = portAxis(end.port);\n if (startAxis === 'vertical' && endAxis === 'vertical') {\n const midY = (start.y + end.y) / 2;\n return `M ${r(start.x)} ${r(start.y)} L ${r(start.x)} ${r(midY)} L ${r(end.x)} ${r(midY)} L ${r(end.x)} ${r(end.y)}`;\n }\n if (startAxis === 'vertical' && endAxis === 'horizontal') {\n return `M ${r(start.x)} ${r(start.y)} L ${r(start.x)} ${r(end.y)} L ${r(end.x)} ${r(end.y)}`;\n }\n if (startAxis === 'horizontal' && endAxis === 'vertical') {\n return `M ${r(start.x)} ${r(start.y)} L ${r(end.x)} ${r(start.y)} L ${r(end.x)} ${r(end.y)}`;\n }\n const midX = (start.x + end.x) / 2;\n return `M ${r(start.x)} ${r(start.y)} L ${r(midX)} ${r(start.y)} L ${r(midX)} ${r(end.y)} L ${r(end.x)} ${r(end.y)}`;\n }\n // curved\n const distance = Math.hypot(end.x - start.x, end.y - start.y);\n const cp = Math.max(40, distance / 2);\n const startVector = portVector(start.port, { x: 1, y: 0 });\n const endVector = portVector(end.port, { x: -1, y: 0 });\n return `M ${r(start.x)} ${r(start.y)} C ${r(start.x + startVector.x * cp)} ${r(start.y + startVector.y * cp)}, ${r(end.x + endVector.x * cp)} ${r(end.y + endVector.y * cp)}, ${r(end.x)} ${r(end.y)}`;\n}\n\nfunction portAxis(port: ConnectorPort | undefined): 'horizontal' | 'vertical' | null {\n if (port === 'top' || port === 'bottom') return 'vertical';\n if (port === 'left' || port === 'right') return 'horizontal';\n return null;\n}\n\nfunction portVector(\n port: ConnectorPort | undefined,\n fallback: { x: number; y: number },\n): { x: number; y: number } {\n switch (port) {\n case 'top':\n return { x: 0, y: -1 };\n case 'right':\n return { x: 1, y: 0 };\n case 'bottom':\n return { x: 0, y: 1 };\n case 'left':\n return { x: -1, y: 0 };\n default:\n return fallback;\n }\n}\n\n/** Map a `lineStyle` (solid|dashed|dotted) to an SVG `stroke-dasharray`. */\nexport function lineStyleDasharray(style: string | undefined): string | undefined {\n switch (style?.toLowerCase()) {\n case 'dashed':\n case 'dash':\n return '8 6';\n case 'dotted':\n case 'dot':\n return '2 6';\n default:\n return undefined;\n }\n}\n"],"mappings":"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","names":["head","bodyT"]}
@@ -142,9 +142,8 @@ var MARKDOWN_PRIORITY = ["index.md", "doc.md", "document.md"];
142
142
  function findDocumentPath(entries) {
143
143
  const rootFiles = entries.filter((e) => !e.path.includes("/"));
144
144
  for (const name of MARKDOWN_PRIORITY) {
145
- if (rootFiles.some((e) => e.path.toLowerCase() === name)) {
146
- return name;
147
- }
145
+ const match = rootFiles.find((e) => e.path.toLowerCase() === name);
146
+ if (match) return match.path;
148
147
  }
149
148
  const firstMd = rootFiles.find((e) => e.path.toLowerCase().endsWith(".md"));
150
149
  return firstMd?.path ?? null;
@@ -154,17 +153,20 @@ var MemoryContentContainer = class {
154
153
  this.files = /* @__PURE__ */ new Map();
155
154
  }
156
155
  async readFile(path) {
157
- return this.files.get(path)?.data ?? null;
156
+ const stored = this.files.get(path)?.data;
157
+ return stored ? stored.slice(0) : null;
158
158
  }
159
159
  async writeFile(path, data, mimeType) {
160
- let buffer;
161
- if (data instanceof ArrayBuffer) {
162
- buffer = data;
160
+ let source;
161
+ if (ArrayBuffer.isView(data)) {
162
+ source = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
163
163
  } else {
164
- buffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
164
+ source = new Uint8Array(data);
165
165
  }
166
+ const owned = new Uint8Array(source.byteLength);
167
+ owned.set(source);
166
168
  this.files.set(path, {
167
- data: buffer,
169
+ data: owned.buffer,
168
170
  mimeType: mimeType ?? guessMimeType(path)
169
171
  });
170
172
  }
@@ -230,19 +232,28 @@ function guessMimeType(path) {
230
232
  }
231
233
 
232
234
  // src/storage/ScopedContentContainer.ts
233
- function normalizePrefix(prefix) {
234
- let p = prefix.trim();
235
- while (p.startsWith("/")) p = p.slice(1);
236
- while (p.endsWith("/")) p = p.slice(0, -1);
237
- if (p.length === 0) {
238
- throw new Error("ScopedContentContainer: prefix must not be empty");
239
- }
240
- if (p.split("/").some((seg) => seg === ".." || seg === ".")) {
235
+ function normalizeRelativePath(path, label, options = {}) {
236
+ const original = path;
237
+ let normalized = label === "prefix" ? path.trim() : path;
238
+ if (normalized.includes("\\")) {
239
+ throw new Error(`ScopedContentContainer: ${label} must use forward slashes: ${original}`);
240
+ }
241
+ const hadTrailingSlash = normalized.endsWith("/");
242
+ normalized = normalized.replace(/^\/+|\/+$/g, "");
243
+ const segments = normalized === "" ? [] : normalized.split("/").filter(Boolean);
244
+ if (segments.some((segment) => segment === "." || segment === "..")) {
241
245
  throw new Error(
242
- `ScopedContentContainer: prefix must not contain '.' or '..' segments: ${prefix}`
246
+ `ScopedContentContainer: ${label} must not contain '.' or '..' segments: ${original}`
243
247
  );
244
248
  }
245
- return p;
249
+ if (segments.length === 0 && !options.allowEmpty) {
250
+ throw new Error(`ScopedContentContainer: ${label} must not be empty`);
251
+ }
252
+ const result = segments.join("/");
253
+ return options.preserveTrailingSlash && hadTrailingSlash && result ? `${result}/` : result;
254
+ }
255
+ function normalizePrefix(prefix) {
256
+ return normalizeRelativePath(prefix, "prefix");
246
257
  }
247
258
  var ScopedContentContainer = class {
248
259
  constructor(parent, prefix) {
@@ -250,10 +261,9 @@ var ScopedContentContainer = class {
250
261
  this.prefix = normalizePrefix(prefix);
251
262
  this.prefixSlash = `${this.prefix}/`;
252
263
  }
253
- toParent(path) {
254
- let p = path;
255
- while (p.startsWith("/")) p = p.slice(1);
256
- return this.prefixSlash + p;
264
+ toParent(path, options = {}) {
265
+ const normalized = normalizeRelativePath(path, "path", options);
266
+ return this.prefixSlash + normalized;
257
267
  }
258
268
  /** Strip the prefix from a parent-space path. Returns null if the path is outside the scope. */
259
269
  fromParent(parentPath) {
@@ -270,7 +280,7 @@ var ScopedContentContainer = class {
270
280
  return this.parent.removeFile(this.toParent(path));
271
281
  }
272
282
  async listFiles(prefix) {
273
- const parentPrefix = prefix ? this.toParent(prefix) : this.prefixSlash;
283
+ const parentPrefix = prefix == null ? this.prefixSlash : this.toParent(prefix, { allowEmpty: true, preserveTrailingSlash: true });
274
284
  const entries = await this.parent.listFiles(parentPrefix);
275
285
  const out = [];
276
286
  for (const e of entries) {
@@ -367,4 +377,4 @@ export {
367
377
  scopeContainer,
368
378
  createMediaProviderFromContainer
369
379
  };
370
- //# sourceMappingURL=chunk-IMSCRLLV.js.map
380
+ //# sourceMappingURL=chunk-VR7UPJ7C.js.map
@@ -0,0 +1 @@
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 const match = rootFiles.find((e) => e.path.toLowerCase() === name);\n if (match) return match.path;\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 const stored = this.files.get(path)?.data;\n // A container owns its bytes. Returning the backing buffer would let a\n // caller mutate stored state without going through writeFile().\n return stored ? stored.slice(0) : 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 // ArrayBuffer.isView is cross-realm-safe (unlike `instanceof\n // ArrayBuffer`, which fails for values created in jsdom/iframes).\n let source: Uint8Array;\n if (ArrayBuffer.isView(data)) {\n source = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n } else {\n source = new Uint8Array(data);\n }\n // Always materialize in this realm, including for iframe ArrayBuffers and\n // views backed by SharedArrayBuffer.\n const owned = new Uint8Array(source.byteLength);\n owned.set(source);\n this.files.set(path, {\n data: owned.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\ninterface NormalizePathOptions {\n allowEmpty?: boolean;\n preserveTrailingSlash?: boolean;\n}\n\n/**\n * Normalize a container-relative path without ever resolving traversal.\n * Backslashes are rejected rather than translated because a filesystem-backed\n * parent may interpret them as separators even when an in-memory parent does\n * not. Dot segments are similarly rejected instead of collapsed so an invalid\n * caller path can never escape after a later platform-specific normalization.\n */\nfunction normalizeRelativePath(\n path: string,\n label: 'prefix' | 'path',\n options: NormalizePathOptions = {},\n): string {\n const original = path;\n let normalized = label === 'prefix' ? path.trim() : path;\n if (normalized.includes('\\\\')) {\n throw new Error(`ScopedContentContainer: ${label} must use forward slashes: ${original}`);\n }\n\n const hadTrailingSlash = normalized.endsWith('/');\n normalized = normalized.replace(/^\\/+|\\/+$/g, '');\n const segments = normalized === '' ? [] : normalized.split('/').filter(Boolean);\n if (segments.some((segment) => segment === '.' || segment === '..')) {\n throw new Error(\n `ScopedContentContainer: ${label} must not contain '.' or '..' segments: ${original}`,\n );\n }\n if (segments.length === 0 && !options.allowEmpty) {\n throw new Error(`ScopedContentContainer: ${label} must not be empty`);\n }\n\n const result = segments.join('/');\n return options.preserveTrailingSlash && hadTrailingSlash && result ? `${result}/` : result;\n}\n\n/** Normalize a prefix: strip leading/trailing slashes, ensure no `..` segments. */\nfunction normalizePrefix(prefix: string): string {\n return normalizeRelativePath(prefix, 'prefix');\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, options: NormalizePathOptions = {}): string {\n const normalized = normalizeRelativePath(path, 'path', options);\n return this.prefixSlash + normalized;\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 =\n prefix == null\n ? this.prefixSlash\n : this.toParent(prefix, { allowEmpty: true, preserveTrailingSlash: true });\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":";AAOO,IAAM,uBAAN,MAAqD;AAAA,EAArD;AACL,SAAS,sBAAsB;AAC/B,SAAQ,QAAQ,oBAAI,IAAoB;AAAA;AAAA,EAExC,MAAM,IAAO,KAAgC;AAC3C,UAAM,MAAM,KAAK,MAAM,IAAI,GAAG;AAC9B,QAAI,QAAQ,OAAW,QAAO;AAC9B,QAAI;AACF,aAAO,KAAK,MAAM,GAAG;AAAA,IACvB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,IAAO,KAAa,OAAyB;AACjD,SAAK,MAAM,IAAI,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,EAC3C;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA,EAEA,MAAM,OAA0B;AAC9B,WAAO,MAAM,KAAK,KAAK,MAAM,KAAK,CAAC;AAAA,EACrC;AACF;;;AC7BO,IAAM,sBAAN,MAAoD;AAAA,EAIzD,YAAY,SAAS,IAAI;AAHzB,SAAS,sBAAsB;AAI7B,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,IAAO,KAAgC;AAC3C,QAAI;AACF,YAAM,MAAM,aAAa,QAAQ,KAAK,SAAS,GAAG;AAClD,UAAI,QAAQ,KAAM,QAAO;AACzB,aAAO,KAAK,MAAM,GAAG;AAAA,IACvB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,IAAO,KAAa,OAAyB;AACjD,iBAAa,QAAQ,KAAK,SAAS,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,EAC/D;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,iBAAa,WAAW,KAAK,SAAS,GAAG;AAAA,EAC3C;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,eAAyB,CAAC;AAChC,aAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,YAAM,IAAI,aAAa,IAAI,CAAC;AAC5B,UAAI,KAAK,EAAE,WAAW,KAAK,MAAM,GAAG;AAClC,qBAAa,KAAK,CAAC;AAAA,MACrB;AAAA,IACF;AACA,eAAW,KAAK,cAAc;AAC5B,mBAAa,WAAW,CAAC;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,MAAM,OAA0B;AAC9B,UAAM,SAAmB,CAAC;AAC1B,aAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,YAAM,IAAI,aAAa,IAAI,CAAC;AAC5B,UAAI,KAAK,EAAE,WAAW,KAAK,MAAM,GAAG;AAClC,eAAO,KAAK,EAAE,MAAM,KAAK,OAAO,MAAM,CAAC;AAAA,MACzC;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;;;AC1CA,OAAO,iBAAiB;AAcjB,IAAM,qBAAN,MAAmD;AAAA,EAKxD,YAAY,UAAqC,CAAC,GAAG;AAJrD,SAAS,sBAAsB;AAK7B,UAAM;AAAA,MACJ,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,cAAc;AAAA,IAChB,IAAI;AAEJ,SAAK,SAAS;AACd,SAAK,QAAQ,YAAY,eAAe,EAAE,MAAM,WAAW,YAAY,CAAC;AAAA,EAC1E;AAAA,EAEA,MAAM,IAAO,KAAgC;AAC3C,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,MAAM,QAAW,KAAK,SAAS,GAAG;AAC3D,aAAO;AAAA,IACT,SAAS,GAAY;AACnB,cAAQ,KAAK,qCAAqC,KAAK,CAAC;AACxD,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,IAAO,KAAa,OAAyB;AACjD,QAAI;AACF,YAAM,KAAK,MAAM,QAAQ,KAAK,SAAS,KAAK,KAAK;AAAA,IACnD,SAAS,GAAY;AACnB,cAAQ,MAAM,qCAAqC,KAAK,CAAC;AACzD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,QAAI;AACF,YAAM,KAAK,MAAM,WAAW,KAAK,SAAS,GAAG;AAAA,IAC/C,SAAS,GAAY;AACnB,cAAQ,KAAK,wCAAwC,KAAK,CAAC;AAAA,IAC7D;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI;AACF,UAAI,KAAK,QAAQ;AAEf,cAAM,OAAO,MAAM,KAAK,MAAM,KAAK;AACnC,cAAM,eAAe,KAAK,OAAO,CAAC,MAAM,EAAE,WAAW,KAAK,MAAM,CAAC;AACjE,cAAM,QAAQ,IAAI,aAAa,IAAI,CAAC,MAAM,KAAK,MAAM,WAAW,CAAC,CAAC,CAAC;AAAA,MACrE,OAAO;AAEL,cAAM,KAAK,MAAM,MAAM;AAAA,MACzB;AAAA,IACF,SAAS,GAAY;AACnB,cAAQ,MAAM,uCAAuC,CAAC;AAAA,IACxD;AAAA,EACF;AAAA,EAEA,MAAM,OAA0B;AAC9B,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,MAAM,KAAK;AACtC,UAAI,KAAK,QAAQ;AACf,eAAO,QACJ,OAAO,CAAC,MAAM,EAAE,WAAW,KAAK,MAAM,CAAC,EACvC,IAAI,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,MAAM,CAAC;AAAA,MAC3C;AACA,aAAO;AAAA,IACT,SAAS,GAAY;AACnB,cAAQ,KAAK,sCAAsC,CAAC;AACpD,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AACF;;;ACnBA,IAAM,oBAAoB,CAAC,YAAY,UAAU,aAAa;AAMvD,SAAS,iBAAiB,SAAwC;AAEvE,QAAM,YAAY,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,SAAS,GAAG,CAAC;AAE7D,aAAW,QAAQ,mBAAmB;AACpC,UAAM,QAAQ,UAAU,KAAK,CAAC,MAAM,EAAE,KAAK,YAAY,MAAM,IAAI;AACjE,QAAI,MAAO,QAAO,MAAM;AAAA,EAC1B;AAGA,QAAM,UAAU,UAAU,KAAK,CAAC,MAAM,EAAE,KAAK,YAAY,EAAE,SAAS,KAAK,CAAC;AAC1E,SAAO,SAAS,QAAQ;AAC1B;AAgBO,IAAM,yBAAN,MAAyD;AAAA,EAAzD;AACL,SAAQ,QAAQ,oBAAI,IAAwB;AAAA;AAAA,EAE5C,MAAM,SAAS,MAA2C;AACxD,UAAM,SAAS,KAAK,MAAM,IAAI,IAAI,GAAG;AAGrC,WAAO,SAAS,OAAO,MAAM,CAAC,IAAI;AAAA,EACpC;AAAA,EAEA,MAAM,UAAU,MAAc,MAAgC,UAAkC;AAM9F,QAAI;AACJ,QAAI,YAAY,OAAO,IAAI,GAAG;AAC5B,eAAS,IAAI,WAAW,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU;AAAA,IACvE,OAAO;AACL,eAAS,IAAI,WAAW,IAAI;AAAA,IAC9B;AAGA,UAAM,QAAQ,IAAI,WAAW,OAAO,UAAU;AAC9C,UAAM,IAAI,MAAM;AAChB,SAAK,MAAM,IAAI,MAAM;AAAA,MACnB,MAAM,MAAM;AAAA,MACZ,UAAU,YAAY,cAAc,IAAI;AAAA,IAC1C,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,WAAW,MAA6B;AAC5C,SAAK,MAAM,OAAO,IAAI;AAAA,EACxB;AAAA,EAEA,MAAM,UAAU,QAA0C;AACxD,UAAM,UAA0B,CAAC;AACjC,eAAW,CAAC,MAAM,IAAI,KAAK,KAAK,OAAO;AACrC,UAAI,UAAU,CAAC,KAAK,WAAW,MAAM,EAAG;AACxC,cAAQ,KAAK;AAAA,QACX;AAAA,QACA,UAAU,KAAK;AAAA,QACf,MAAM,KAAK,KAAK;AAAA,MAClB,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,OAAO,MAAgC;AAC3C,WAAO,KAAK,MAAM,IAAI,IAAI;AAAA,EAC5B;AAAA,EAEA,MAAM,kBAA0C;AAC9C,WAAO,iBAAiB,MAAM,KAAK,UAAU,CAAC;AAAA,EAChD;AAAA,EAEA,MAAM,eAAuC;AAC3C,UAAM,UAAU,MAAM,KAAK,gBAAgB;AAC3C,QAAI,CAAC,QAAS,QAAO;AACrB,UAAM,OAAO,MAAM,KAAK,SAAS,OAAO;AACxC,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO,IAAI,YAAY,EAAE,OAAO,IAAI;AAAA,EACtC;AAAA,EAEA,MAAM,cAAc,UAAkB,UAAkC;AACtE,UAAM,OAAO,YAAY;AACzB,UAAM,OAAO,IAAI,YAAY,EAAE,OAAO,QAAQ;AAC9C,UAAM,KAAK,UAAU,MAAM,MAAM,eAAe;AAAA,EAClD;AACF;AAMA,IAAM,qBAA6C;AAAA,EACjD,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AACT;AAEA,SAAS,cAAc,MAAsB;AAC3C,QAAM,MAAM,KAAK,YAAY,GAAG;AAChC,MAAI,QAAQ,GAAI,QAAO;AACvB,QAAM,MAAM,KAAK,MAAM,GAAG,EAAE,YAAY;AACxC,SAAO,mBAAmB,GAAG,KAAK;AACpC;;;AC7LA,SAAS,sBACP,MACA,OACA,UAAgC,CAAC,GACzB;AACR,QAAM,WAAW;AACjB,MAAI,aAAa,UAAU,WAAW,KAAK,KAAK,IAAI;AACpD,MAAI,WAAW,SAAS,IAAI,GAAG;AAC7B,UAAM,IAAI,MAAM,2BAA2B,KAAK,8BAA8B,QAAQ,EAAE;AAAA,EAC1F;AAEA,QAAM,mBAAmB,WAAW,SAAS,GAAG;AAChD,eAAa,WAAW,QAAQ,cAAc,EAAE;AAChD,QAAM,WAAW,eAAe,KAAK,CAAC,IAAI,WAAW,MAAM,GAAG,EAAE,OAAO,OAAO;AAC9E,MAAI,SAAS,KAAK,CAAC,YAAY,YAAY,OAAO,YAAY,IAAI,GAAG;AACnE,UAAM,IAAI;AAAA,MACR,2BAA2B,KAAK,2CAA2C,QAAQ;AAAA,IACrF;AAAA,EACF;AACA,MAAI,SAAS,WAAW,KAAK,CAAC,QAAQ,YAAY;AAChD,UAAM,IAAI,MAAM,2BAA2B,KAAK,oBAAoB;AAAA,EACtE;AAEA,QAAM,SAAS,SAAS,KAAK,GAAG;AAChC,SAAO,QAAQ,yBAAyB,oBAAoB,SAAS,GAAG,MAAM,MAAM;AACtF;AAGA,SAAS,gBAAgB,QAAwB;AAC/C,SAAO,sBAAsB,QAAQ,QAAQ;AAC/C;AAOO,IAAM,yBAAN,MAAyD;AAAA,EAO9D,YAAY,QAA0B,QAAgB;AACpD,SAAK,SAAS;AACd,SAAK,SAAS,gBAAgB,MAAM;AACpC,SAAK,cAAc,GAAG,KAAK,MAAM;AAAA,EACnC;AAAA,EAEQ,SAAS,MAAc,UAAgC,CAAC,GAAW;AACzE,UAAM,aAAa,sBAAsB,MAAM,QAAQ,OAAO;AAC9D,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA;AAAA,EAGQ,WAAW,YAAmC;AACpD,QAAI,CAAC,WAAW,WAAW,KAAK,WAAW,EAAG,QAAO;AACrD,WAAO,WAAW,MAAM,KAAK,YAAY,MAAM;AAAA,EACjD;AAAA,EAEA,SAAS,MAA2C;AAClD,WAAO,KAAK,OAAO,SAAS,KAAK,SAAS,IAAI,CAAC;AAAA,EACjD;AAAA,EAEA,UAAU,MAAc,MAAgC,UAAkC;AACxF,WAAO,KAAK,OAAO,UAAU,KAAK,SAAS,IAAI,GAAG,MAAM,QAAQ;AAAA,EAClE;AAAA,EAEA,WAAW,MAA6B;AACtC,WAAO,KAAK,OAAO,WAAW,KAAK,SAAS,IAAI,CAAC;AAAA,EACnD;AAAA,EAEA,MAAM,UAAU,QAA0C;AACxD,UAAM,eACJ,UAAU,OACN,KAAK,cACL,KAAK,SAAS,QAAQ,EAAE,YAAY,MAAM,uBAAuB,KAAK,CAAC;AAC7E,UAAM,UAAU,MAAM,KAAK,OAAO,UAAU,YAAY;AACxD,UAAM,MAAsB,CAAC;AAC7B,eAAW,KAAK,SAAS;AACvB,YAAM,QAAQ,KAAK,WAAW,EAAE,IAAI;AACpC,UAAI,UAAU,KAAM;AACpB,UAAI,KAAK,EAAE,MAAM,OAAO,UAAU,EAAE,UAAU,MAAM,EAAE,KAAK,CAAC;AAAA,IAC9D;AACA,WAAO;AAAA,EACT;AAAA,EAEA,OAAO,MAAgC;AACrC,WAAO,KAAK,OAAO,OAAO,KAAK,SAAS,IAAI,CAAC;AAAA,EAC/C;AAAA;AAAA,EAGA,MAAM,kBAA0C;AAC9C,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,eAAuC;AAC3C,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,gBAA+B;AACnC,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACF;AAGO,SAAS,eAAe,QAA0B,QAAwC;AAC/F,SAAO,IAAI,uBAAuB,QAAQ,MAAM;AAClD;;;ACzHO,SAAS,iCAAiC,WAA4C;AAC3F,QAAM,eAAe,oBAAI,IAAoB;AAE7C,SAAO;AAAA,IACL,MAAM,WAAW,cAAuC;AACtD,YAAM,SAAS,aAAa,IAAI,YAAY;AAC5C,UAAI,OAAQ,QAAO;AAEnB,YAAM,OAAO,MAAM,UAAU,SAAS,YAAY;AAClD,UAAI,CAAC,KAAM,QAAO;AAElB,YAAM,UAAU,MAAM,UAAU,UAAU;AAC1C,YAAM,QAAQ,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,YAAY;AACzD,YAAM,WAAW,OAAO,YAAY;AAEpC,YAAM,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,MAAM,SAAS,CAAC;AAChD,YAAM,MAAM,IAAI,gBAAgB,IAAI;AACpC,mBAAa,IAAI,cAAc,GAAG;AAClC,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,YAAmC;AACvC,YAAM,UAAU,MAAM,UAAU,UAAU;AAC1C,aAAO,QACJ,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,YAAY,EAAE,SAAS,KAAK,CAAC,EACnD,IAAI,CAAC,OAAO;AAAA,QACX,MAAM,EAAE;AAAA,QACR,UAAU,EAAE;AAAA,QACZ,MAAM,EAAE;AAAA,MACV,EAAE;AAAA,IACN;AAAA,IAEA,MAAM,SACJ,MACA,MACA,UACiB;AAEjB,YAAM,SAAS,aAAa,IAAI,IAAI;AACpC,UAAI,QAAQ;AACV,YAAI,gBAAgB,MAAM;AAC1B,qBAAa,OAAO,IAAI;AAAA,MAC1B;AAEA,UAAI;AACJ,UAAI,gBAAgB,MAAM;AACxB,iBAAS,MAAM,KAAK,YAAY;AAAA,MAClC,OAAO;AACL,iBAAS;AAAA,MACX;AACA,YAAM,UAAU,UAAU,MAAM,QAAQ,QAAQ;AAChD,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,YAAY,cAAqC;AACrD,YAAM,SAAS,aAAa,IAAI,YAAY;AAC5C,UAAI,QAAQ;AACV,YAAI,gBAAgB,MAAM;AAC1B,qBAAa,OAAO,YAAY;AAAA,MAClC;AACA,YAAM,UAAU,WAAW,YAAY;AAAA,IACzC;AAAA,IAEA,UAAgB;AACd,iBAAW,OAAO,aAAa,OAAO,GAAG;AACvC,YAAI,gBAAgB,GAAG;AAAA,MACzB;AACA,mBAAa,MAAM;AAAA,IACrB;AAAA,EACF;AACF;","names":[]}
@@ -1,3 +1,6 @@
1
+ import {
2
+ cloneAndFreezeData
3
+ } from "./chunk-5J4HKRJF.js";
1
4
  import {
2
5
  isTransitionType
3
6
  } from "./chunk-QE7TWGVT.js";
@@ -5,76 +8,8 @@ import {
5
8
  // src/schemas/CustomTemplates.ts
6
9
  var FRONTMATTER_CUSTOM_TEMPLATES_KEY = "squisq-custom-templates";
7
10
 
8
- // src/schemas/Theme.ts
11
+ // src/schemas/themeConstants.ts
9
12
  var THEME_SCHEMA_VERSION = "1";
10
- var FRONTMATTER_CUSTOM_THEMES_KEY = "squisq-custom-themes";
11
- function deepMerge(target, source) {
12
- const result = { ...target };
13
- for (const key of Object.keys(source)) {
14
- const srcVal = source[key];
15
- const tgtVal = target[key];
16
- if (srcVal !== null && srcVal !== void 0 && typeof srcVal === "object" && !Array.isArray(srcVal) && tgtVal !== null && tgtVal !== void 0 && typeof tgtVal === "object" && !Array.isArray(tgtVal)) {
17
- result[key] = deepMerge(
18
- tgtVal,
19
- srcVal
20
- );
21
- } else if (srcVal !== void 0) {
22
- result[key] = srcVal;
23
- }
24
- }
25
- return result;
26
- }
27
- function createTheme(base, overrides) {
28
- const merged = deepMerge(
29
- base,
30
- overrides
31
- );
32
- if (!overrides.id && merged.id === base.id) {
33
- merged.id = `${base.id}-custom`;
34
- }
35
- if (!merged.schemaVersion) merged.schemaVersion = THEME_SCHEMA_VERSION;
36
- return merged;
37
- }
38
- var LIGHT_SURFACE = {
39
- id: "light",
40
- background: "#ffffff",
41
- backgroundLight: "#f5f5f5",
42
- text: "#1a1a1a",
43
- textMuted: "#666666"
44
- };
45
- var DARK_SURFACE = {
46
- id: "dark",
47
- background: "#1a202c",
48
- backgroundLight: "#2d3748",
49
- text: "#f0f0f0",
50
- textMuted: "#a0aec0"
51
- };
52
- function applySurface(theme, surface) {
53
- return {
54
- ...theme,
55
- colors: {
56
- ...theme.colors,
57
- background: surface.background,
58
- backgroundLight: surface.backgroundLight,
59
- text: surface.text,
60
- textMuted: surface.textMuted
61
- }
62
- };
63
- }
64
- var CUSTOM_THEME_REGISTRY = /* @__PURE__ */ new Map();
65
- function registerTheme(theme) {
66
- CUSTOM_THEME_REGISTRY.set(theme.id, theme);
67
- }
68
- function unregisterTheme(id) {
69
- CUSTOM_THEME_REGISTRY.delete(id);
70
- }
71
- function getRegisteredThemes() {
72
- return Array.from(CUSTOM_THEME_REGISTRY.values());
73
- }
74
- function lookupRegisteredTheme(id) {
75
- if (!id) return void 0;
76
- return CUSTOM_THEME_REGISTRY.get(id);
77
- }
78
13
 
79
14
  // src/schemas/colorUtils.ts
80
15
  var HEX_RE = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
@@ -579,6 +514,90 @@ ${summary}${more}`);
579
514
  return result.theme;
580
515
  }
581
516
 
517
+ // src/schemas/Theme.ts
518
+ var FRONTMATTER_CUSTOM_THEMES_KEY = "squisq-custom-themes";
519
+ function deepMerge(target, source) {
520
+ const result = { ...target };
521
+ for (const key of Object.keys(source)) {
522
+ const srcVal = source[key];
523
+ const tgtVal = target[key];
524
+ if (srcVal !== null && srcVal !== void 0 && typeof srcVal === "object" && !Array.isArray(srcVal) && tgtVal !== null && tgtVal !== void 0 && typeof tgtVal === "object" && !Array.isArray(tgtVal)) {
525
+ result[key] = deepMerge(
526
+ tgtVal,
527
+ srcVal
528
+ );
529
+ } else if (srcVal !== void 0) {
530
+ result[key] = srcVal;
531
+ }
532
+ }
533
+ return result;
534
+ }
535
+ function createTheme(base, overrides) {
536
+ const merged = deepMerge(
537
+ base,
538
+ overrides
539
+ );
540
+ if (!overrides.id && merged.id === base.id) {
541
+ merged.id = `${base.id}-custom`;
542
+ }
543
+ if (!merged.schemaVersion) merged.schemaVersion = THEME_SCHEMA_VERSION;
544
+ return merged;
545
+ }
546
+ var LIGHT_SURFACE = {
547
+ id: "light",
548
+ background: "#ffffff",
549
+ backgroundLight: "#f5f5f5",
550
+ text: "#1a1a1a",
551
+ textMuted: "#666666"
552
+ };
553
+ var DARK_SURFACE = {
554
+ id: "dark",
555
+ background: "#1a202c",
556
+ backgroundLight: "#2d3748",
557
+ text: "#f0f0f0",
558
+ textMuted: "#a0aec0"
559
+ };
560
+ function applySurface(theme, surface) {
561
+ return {
562
+ ...theme,
563
+ colors: {
564
+ ...theme.colors,
565
+ background: surface.background,
566
+ backgroundLight: surface.backgroundLight,
567
+ text: surface.text,
568
+ textMuted: surface.textMuted
569
+ }
570
+ };
571
+ }
572
+ function validatedThemeSnapshot(theme) {
573
+ const result = validateTheme(theme);
574
+ if (!result.valid || !result.theme) {
575
+ const details = result.errors.slice(0, 5).map((error) => `${error.path || "<root>"}: ${error.message}`).join("; ");
576
+ throw new TypeError(`Cannot register invalid theme: ${details}`);
577
+ }
578
+ return cloneAndFreezeData(result.theme);
579
+ }
580
+ function createThemeRegistry(initialThemes = []) {
581
+ const themes = /* @__PURE__ */ new Map();
582
+ const registry = {
583
+ register(theme) {
584
+ const snapshot = validatedThemeSnapshot(theme);
585
+ themes.set(snapshot.id, snapshot);
586
+ },
587
+ unregister(id) {
588
+ return themes.delete(id);
589
+ },
590
+ get(id) {
591
+ return themes.get(id);
592
+ },
593
+ list() {
594
+ return Array.from(themes.values());
595
+ }
596
+ };
597
+ for (const theme of initialThemes) registry.register(theme);
598
+ return Object.freeze(registry);
599
+ }
600
+
582
601
  // src/schemas/themes/standard.json
583
602
  var standard_default = {
584
603
  schemaVersion: "1",
@@ -1388,7 +1407,7 @@ var gezellig_default = {
1388
1407
  schemaVersion: "1",
1389
1408
  id: "gezellig",
1390
1409
  name: "Gezellig",
1391
- description: "Warm sage + terracotta palette inspired by the Gezel desktop app. Subtle orange-tinted dark background, cream text, sage and terracotta accents.",
1410
+ description: "Cozy, warm-hued palette with a subtle orange-tinted dark background, cream text, and sage and terracotta accents.",
1392
1411
  colors: {
1393
1412
  primary: "#b0724c",
1394
1413
  secondary: "#667f62",
@@ -1505,24 +1524,29 @@ var BUILTIN_THEMES = {
1505
1524
  };
1506
1525
 
1507
1526
  // src/schemas/themeLibrary.ts
1508
- var THEMES = BUILTIN_THEMES;
1527
+ var THEMES = Object.freeze(
1528
+ Object.fromEntries(
1529
+ Object.entries(BUILTIN_THEMES).map(([id, theme]) => [id, cloneAndFreezeData(theme)])
1530
+ )
1531
+ );
1509
1532
  var DEFAULT_THEME_ID = "standard";
1510
1533
  var DEFAULT_THEME = THEMES[DEFAULT_THEME_ID];
1511
- function resolveTheme(id) {
1512
- const registered = lookupRegisteredTheme(id);
1534
+ function resolveTheme(id, registry) {
1535
+ const registered = id ? registry?.get(id) : void 0;
1513
1536
  if (registered) return registered;
1514
1537
  if (id && id in THEMES) return THEMES[id];
1515
1538
  return DEFAULT_THEME;
1516
1539
  }
1517
- function getAvailableThemes() {
1518
- return Object.keys(THEMES);
1540
+ function getAvailableThemes(registry) {
1541
+ return Array.from(
1542
+ /* @__PURE__ */ new Set([...Object.keys(THEMES), ...registry?.list().map((theme) => theme.id) ?? []])
1543
+ );
1519
1544
  }
1520
- function getThemeSummaries() {
1521
- return Object.values(THEMES).map((t) => ({
1522
- id: t.id,
1523
- name: t.name,
1524
- description: t.description
1525
- }));
1545
+ function getThemeSummaries(registry) {
1546
+ return getAvailableThemes(registry).map((id) => {
1547
+ const theme = resolveTheme(id, registry);
1548
+ return { id, name: theme.name, description: theme.description };
1549
+ });
1526
1550
  }
1527
1551
 
1528
1552
  // src/schemas/fontStacks.ts
@@ -1709,19 +1733,52 @@ function buildGoogleFontsUrl(families) {
1709
1733
  return `https://fonts.googleapis.com/css2?${params}&display=swap`;
1710
1734
  }
1711
1735
  var FONT_FALLBACKS = FALLBACK_BY_KIND;
1736
+ function normalizeFontName(name) {
1737
+ return name.trim().replace(/^["']+|["']+$/g, "").replace(/\s+/g, " ").toLowerCase();
1738
+ }
1739
+ var STACK_NAME_ALIASES = (() => {
1740
+ const aliases = {};
1741
+ for (const stack of AVAILABLE_FONT_STACKS) {
1742
+ aliases[normalizeFontName(stack.label)] = stack.id;
1743
+ if (stack.googleFontFamily) aliases[normalizeFontName(stack.googleFontFamily)] = stack.id;
1744
+ }
1745
+ Object.assign(aliases, {
1746
+ playfair: "playfair",
1747
+ "source serif": "source-serif",
1748
+ "source serif pro": "source-serif",
1749
+ "crimson pro": "crimson",
1750
+ cormorant: "cormorant",
1751
+ georgia: "system-serif",
1752
+ "times new roman": "system-serif",
1753
+ times: "system-serif",
1754
+ consolas: "system-mono",
1755
+ "courier new": "system-mono",
1756
+ courier: "system-mono",
1757
+ "segoe ui": "system-sans",
1758
+ "system-ui": "system-sans"
1759
+ });
1760
+ return aliases;
1761
+ })();
1762
+ var MONO_NAME_RE = /\b(mono|monospace|consolas|courier|menlo|inconsolata|cascadia|fira code|source code|code)\b/;
1763
+ var SERIF_NAME_RE = /\b(georgia|times|garamond|cambria|caslon|baskerville|bodoni|didot|palatino|book antiqua|goudy|minion|charter|constantia|century schoolbook|antiqua|roman)\b/;
1764
+ function guessFontFallback(name) {
1765
+ const n = normalizeFontName(name);
1766
+ if (MONO_NAME_RE.test(n)) return "monospace";
1767
+ if (n.includes("serif") && !n.includes("sans") || SERIF_NAME_RE.test(n)) return "serif";
1768
+ return "sans-serif";
1769
+ }
1770
+ function matchFontFamily(name) {
1771
+ const normalized = normalizeFontName(name);
1772
+ if (!normalized) return { stackId: "system-sans" };
1773
+ const stackId = STACK_NAME_ALIASES[normalized];
1774
+ if (stackId) return { stackId };
1775
+ const displayName = name.trim().replace(/^["']+|["']+$/g, "");
1776
+ return { custom: { name: displayName, fallback: guessFontFallback(name) } };
1777
+ }
1712
1778
 
1713
1779
  export {
1714
1780
  FRONTMATTER_CUSTOM_TEMPLATES_KEY,
1715
1781
  THEME_SCHEMA_VERSION,
1716
- FRONTMATTER_CUSTOM_THEMES_KEY,
1717
- createTheme,
1718
- LIGHT_SURFACE,
1719
- DARK_SURFACE,
1720
- applySurface,
1721
- registerTheme,
1722
- unregisterTheme,
1723
- getRegisteredThemes,
1724
- lookupRegisteredTheme,
1725
1782
  isHex,
1726
1783
  oklchLighten,
1727
1784
  oklchDarken,
@@ -1734,6 +1791,12 @@ export {
1734
1791
  deriveScale,
1735
1792
  validateTheme,
1736
1793
  assertTheme,
1794
+ FRONTMATTER_CUSTOM_THEMES_KEY,
1795
+ createTheme,
1796
+ LIGHT_SURFACE,
1797
+ DARK_SURFACE,
1798
+ applySurface,
1799
+ createThemeRegistry,
1737
1800
  THEMES,
1738
1801
  DEFAULT_THEME_ID,
1739
1802
  DEFAULT_THEME,
@@ -1745,6 +1808,8 @@ export {
1745
1808
  resolveFontFamily,
1746
1809
  fontStack,
1747
1810
  buildGoogleFontsUrl,
1748
- FONT_FALLBACKS
1811
+ FONT_FALLBACKS,
1812
+ guessFontFallback,
1813
+ matchFontFamily
1749
1814
  };
1750
- //# sourceMappingURL=chunk-KKNUBQ6Y.js.map
1815
+ //# sourceMappingURL=chunk-W4O2TX5I.js.map