@bendyline/squisq 1.0.1 → 1.1.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 (236) hide show
  1. package/dist/ContentContainer-DNx460y8.d.ts +88 -0
  2. package/dist/{Doc-1hmT1LZl.d.ts → Doc-Cvt2lyzn.d.ts} +56 -4
  3. package/dist/MediaProvider-wpSe21B3.d.ts +66 -0
  4. package/dist/__tests__/contentContainer.test.d.ts +2 -0
  5. package/dist/__tests__/contentContainer.test.d.ts.map +1 -0
  6. package/dist/__tests__/contentContainer.test.js +234 -0
  7. package/dist/__tests__/contentContainer.test.js.map +1 -0
  8. package/dist/__tests__/markdownToDoc.test.js +75 -0
  9. package/dist/__tests__/markdownToDoc.test.js.map +1 -1
  10. package/dist/{chunk-PJ7AODIM.js → chunk-3WBJAZZ5.js} +394 -52
  11. package/dist/chunk-3WBJAZZ5.js.map +1 -0
  12. package/dist/chunk-4CRBS35L.js +292 -0
  13. package/dist/chunk-4CRBS35L.js.map +1 -0
  14. package/dist/{chunk-6YJPI6VI.js → chunk-4X3JQXNM.js} +1 -1
  15. package/dist/{chunk-S4F2RY37.js → chunk-CORGFIFU.js} +1 -1
  16. package/dist/chunk-CORGFIFU.js.map +1 -0
  17. package/dist/chunk-DNJKBAI7.js +1 -0
  18. package/dist/chunk-DNJKBAI7.js.map +1 -0
  19. package/dist/{chunk-VP5SZTZ2.js → chunk-MIBRIRGJ.js} +2 -139
  20. package/dist/chunk-MIBRIRGJ.js.map +1 -0
  21. package/dist/{chunk-URU6QMLY.js → chunk-NJLL5XYC.js} +15 -8
  22. package/dist/chunk-NJLL5XYC.js.map +1 -0
  23. package/dist/chunk-OFU53HI7.js +165 -0
  24. package/dist/chunk-OFU53HI7.js.map +1 -0
  25. package/dist/chunk-TSG2OSE6.js +145 -0
  26. package/dist/chunk-TSG2OSE6.js.map +1 -0
  27. package/dist/{chunk-O5HCIUAD.js → chunk-TVPKD76V.js} +81 -187
  28. package/dist/chunk-TVPKD76V.js.map +1 -0
  29. package/dist/contentExtractor-BNfVJV2U.d.ts +113 -0
  30. package/dist/doc/audioMapping.d.ts +36 -0
  31. package/dist/doc/audioMapping.d.ts.map +1 -0
  32. package/dist/doc/audioMapping.js +279 -0
  33. package/dist/doc/audioMapping.js.map +1 -0
  34. package/dist/doc/index.d.ts +1 -0
  35. package/dist/doc/index.d.ts.map +1 -1
  36. package/dist/doc/index.js +1 -0
  37. package/dist/doc/index.js.map +1 -1
  38. package/dist/doc/markdownToDoc.d.ts.map +1 -1
  39. package/dist/doc/markdownToDoc.js +126 -1
  40. package/dist/doc/markdownToDoc.js.map +1 -1
  41. package/dist/doc/templates/comparisonBar.d.ts.map +1 -1
  42. package/dist/doc/templates/comparisonBar.js +23 -5
  43. package/dist/doc/templates/comparisonBar.js.map +1 -1
  44. package/dist/doc/templates/coverBlock.d.ts.map +1 -1
  45. package/dist/doc/templates/coverBlock.js +3 -0
  46. package/dist/doc/templates/coverBlock.js.map +1 -1
  47. package/dist/doc/templates/dateEvent.d.ts.map +1 -1
  48. package/dist/doc/templates/dateEvent.js +4 -0
  49. package/dist/doc/templates/dateEvent.js.map +1 -1
  50. package/dist/doc/templates/definitionCard.d.ts.map +1 -1
  51. package/dist/doc/templates/definitionCard.js +4 -1
  52. package/dist/doc/templates/definitionCard.js.map +1 -1
  53. package/dist/doc/templates/factCard.d.ts.map +1 -1
  54. package/dist/doc/templates/factCard.js +4 -0
  55. package/dist/doc/templates/factCard.js.map +1 -1
  56. package/dist/doc/templates/fullBleedQuote.d.ts.map +1 -1
  57. package/dist/doc/templates/fullBleedQuote.js +2 -1
  58. package/dist/doc/templates/fullBleedQuote.js.map +1 -1
  59. package/dist/doc/templates/imageWithCaption.d.ts.map +1 -1
  60. package/dist/doc/templates/imageWithCaption.js +4 -0
  61. package/dist/doc/templates/imageWithCaption.js.map +1 -1
  62. package/dist/doc/templates/listBlock.d.ts.map +1 -1
  63. package/dist/doc/templates/listBlock.js +3 -0
  64. package/dist/doc/templates/listBlock.js.map +1 -1
  65. package/dist/doc/templates/mapBlock.d.ts.map +1 -1
  66. package/dist/doc/templates/mapBlock.js +3 -0
  67. package/dist/doc/templates/mapBlock.js.map +1 -1
  68. package/dist/doc/templates/photoGrid.d.ts.map +1 -1
  69. package/dist/doc/templates/photoGrid.js +3 -0
  70. package/dist/doc/templates/photoGrid.js.map +1 -1
  71. package/dist/doc/templates/pullQuote.d.ts.map +1 -1
  72. package/dist/doc/templates/pullQuote.js +4 -0
  73. package/dist/doc/templates/pullQuote.js.map +1 -1
  74. package/dist/doc/templates/quoteBlock.d.ts.map +1 -1
  75. package/dist/doc/templates/quoteBlock.js +4 -0
  76. package/dist/doc/templates/quoteBlock.js.map +1 -1
  77. package/dist/doc/templates/sectionHeader.d.ts.map +1 -1
  78. package/dist/doc/templates/sectionHeader.js +3 -2
  79. package/dist/doc/templates/sectionHeader.js.map +1 -1
  80. package/dist/doc/templates/statHighlight.d.ts.map +1 -1
  81. package/dist/doc/templates/statHighlight.js +4 -1
  82. package/dist/doc/templates/statHighlight.js.map +1 -1
  83. package/dist/doc/templates/titleBlock.d.ts.map +1 -1
  84. package/dist/doc/templates/titleBlock.js +3 -0
  85. package/dist/doc/templates/titleBlock.js.map +1 -1
  86. package/dist/doc/templates/twoColumn.d.ts.map +1 -1
  87. package/dist/doc/templates/twoColumn.js +7 -1
  88. package/dist/doc/templates/twoColumn.js.map +1 -1
  89. package/dist/doc/templates/videoPullQuote.d.ts.map +1 -1
  90. package/dist/doc/templates/videoPullQuote.js +4 -0
  91. package/dist/doc/templates/videoPullQuote.js.map +1 -1
  92. package/dist/doc/templates/videoWithCaption.d.ts.map +1 -1
  93. package/dist/doc/templates/videoWithCaption.js +3 -0
  94. package/dist/doc/templates/videoWithCaption.js.map +1 -1
  95. package/dist/doc/utils/themeUtils.d.ts +9 -0
  96. package/dist/doc/utils/themeUtils.d.ts.map +1 -1
  97. package/dist/doc/utils/themeUtils.js +26 -0
  98. package/dist/doc/utils/themeUtils.js.map +1 -1
  99. package/dist/markdown/convert.d.ts.map +1 -1
  100. package/dist/markdown/convert.js +16 -7
  101. package/dist/markdown/convert.js.map +1 -1
  102. package/dist/markdown/types.d.ts +4 -2
  103. package/dist/markdown/types.d.ts.map +1 -1
  104. package/dist/schemas/BlockTemplates.d.ts +2 -2
  105. package/dist/schemas/BlockTemplates.d.ts.map +1 -1
  106. package/dist/schemas/BlockTemplates.js +2 -2
  107. package/dist/schemas/BlockTemplates.js.map +1 -1
  108. package/dist/schemas/Doc.d.ts +52 -0
  109. package/dist/schemas/Doc.d.ts.map +1 -1
  110. package/dist/schemas/Doc.js.map +1 -1
  111. package/dist/schemas/themeLibrary.d.ts +2 -2
  112. package/dist/schemas/themeLibrary.d.ts.map +1 -1
  113. package/dist/schemas/themeLibrary.js +91 -26
  114. package/dist/schemas/themeLibrary.js.map +1 -1
  115. package/dist/storage/ContentContainer.d.ts +87 -0
  116. package/dist/storage/ContentContainer.d.ts.map +1 -0
  117. package/dist/storage/ContentContainer.js +122 -0
  118. package/dist/storage/ContentContainer.js.map +1 -0
  119. package/dist/storage/MediaProviderFromContainer.d.ts +21 -0
  120. package/dist/storage/MediaProviderFromContainer.d.ts.map +1 -0
  121. package/dist/storage/MediaProviderFromContainer.js +79 -0
  122. package/dist/storage/MediaProviderFromContainer.js.map +1 -0
  123. package/dist/storage/index.d.ts +3 -0
  124. package/dist/storage/index.d.ts.map +1 -1
  125. package/dist/storage/index.js +2 -0
  126. package/dist/storage/index.js.map +1 -1
  127. package/dist/story/index.d.ts +50 -5
  128. package/dist/story/index.js +15 -5
  129. package/dist/{themeLibrary-DR1VRo-8.d.ts → themeLibrary-VOHfPzRB.d.ts} +3 -3
  130. package/dist/transform/applyTransform.d.ts +22 -0
  131. package/dist/transform/applyTransform.d.ts.map +1 -0
  132. package/dist/transform/applyTransform.js +68 -0
  133. package/dist/transform/applyTransform.js.map +1 -0
  134. package/dist/transform/blockAnalyzer.d.ts +48 -0
  135. package/dist/transform/blockAnalyzer.d.ts.map +1 -0
  136. package/dist/transform/blockAnalyzer.js +135 -0
  137. package/dist/transform/blockAnalyzer.js.map +1 -0
  138. package/dist/transform/index.d.ts +6 -0
  139. package/dist/transform/index.d.ts.map +1 -0
  140. package/dist/transform/index.js +4 -0
  141. package/dist/transform/index.js.map +1 -0
  142. package/dist/transform/registry.d.ts +19 -0
  143. package/dist/transform/registry.d.ts.map +1 -0
  144. package/dist/transform/registry.js +41 -0
  145. package/dist/transform/registry.js.map +1 -0
  146. package/dist/transform/styles/dataDriven.d.ts +4 -0
  147. package/dist/transform/styles/dataDriven.d.ts.map +1 -0
  148. package/dist/transform/styles/dataDriven.js +15 -0
  149. package/dist/transform/styles/dataDriven.js.map +1 -0
  150. package/dist/transform/styles/documentary.d.ts +4 -0
  151. package/dist/transform/styles/documentary.d.ts.map +1 -0
  152. package/dist/transform/styles/documentary.js +15 -0
  153. package/dist/transform/styles/documentary.js.map +1 -0
  154. package/dist/transform/styles/index.d.ts +6 -0
  155. package/dist/transform/styles/index.d.ts.map +1 -0
  156. package/dist/transform/styles/index.js +6 -0
  157. package/dist/transform/styles/index.js.map +1 -0
  158. package/dist/transform/styles/magazine.d.ts +4 -0
  159. package/dist/transform/styles/magazine.d.ts.map +1 -0
  160. package/dist/transform/styles/magazine.js +15 -0
  161. package/dist/transform/styles/magazine.js.map +1 -0
  162. package/dist/transform/styles/minimal.d.ts +4 -0
  163. package/dist/transform/styles/minimal.d.ts.map +1 -0
  164. package/dist/transform/styles/minimal.js +15 -0
  165. package/dist/transform/styles/minimal.js.map +1 -0
  166. package/dist/transform/styles/narrative.d.ts +4 -0
  167. package/dist/transform/styles/narrative.d.ts.map +1 -0
  168. package/dist/transform/styles/narrative.js +15 -0
  169. package/dist/transform/styles/narrative.js.map +1 -0
  170. package/dist/transform/templateSelector.d.ts +30 -0
  171. package/dist/transform/templateSelector.d.ts.map +1 -0
  172. package/dist/transform/templateSelector.js +218 -0
  173. package/dist/transform/templateSelector.js.map +1 -0
  174. package/dist/transform/timingAllocator.d.ts +21 -0
  175. package/dist/transform/timingAllocator.d.ts.map +1 -0
  176. package/dist/transform/timingAllocator.js +111 -0
  177. package/dist/transform/timingAllocator.js.map +1 -0
  178. package/dist/transform/types.d.ts +86 -0
  179. package/dist/transform/types.d.ts.map +1 -0
  180. package/dist/transform/types.js +9 -0
  181. package/dist/transform/types.js.map +1 -0
  182. package/dist/{types-DHiv_Pnm.d.ts → types-DYAzisMG.d.ts} +4 -2
  183. package/package.json +6 -1
  184. package/src/__tests__/contentContainer.test.ts +282 -0
  185. package/src/__tests__/markdownToDoc.test.ts +96 -0
  186. package/src/doc/audioMapping.ts +346 -0
  187. package/src/doc/index.ts +1 -0
  188. package/src/doc/markdownToDoc.ts +136 -2
  189. package/src/doc/templates/comparisonBar.ts +23 -5
  190. package/src/doc/templates/coverBlock.ts +3 -0
  191. package/src/doc/templates/dateEvent.ts +4 -0
  192. package/src/doc/templates/definitionCard.ts +4 -1
  193. package/src/doc/templates/factCard.ts +4 -0
  194. package/src/doc/templates/fullBleedQuote.ts +2 -1
  195. package/src/doc/templates/imageWithCaption.ts +4 -0
  196. package/src/doc/templates/listBlock.ts +3 -0
  197. package/src/doc/templates/mapBlock.ts +3 -0
  198. package/src/doc/templates/photoGrid.ts +3 -0
  199. package/src/doc/templates/pullQuote.ts +4 -0
  200. package/src/doc/templates/quoteBlock.ts +4 -0
  201. package/src/doc/templates/sectionHeader.ts +3 -2
  202. package/src/doc/templates/statHighlight.ts +4 -1
  203. package/src/doc/templates/titleBlock.ts +3 -0
  204. package/src/doc/templates/twoColumn.ts +7 -1
  205. package/src/doc/templates/videoPullQuote.ts +4 -0
  206. package/src/doc/templates/videoWithCaption.ts +3 -0
  207. package/src/doc/utils/themeUtils.ts +29 -0
  208. package/src/markdown/convert.ts +17 -7
  209. package/src/markdown/types.ts +4 -2
  210. package/src/schemas/BlockTemplates.ts +2 -2
  211. package/src/schemas/Doc.ts +60 -0
  212. package/src/schemas/themeLibrary.ts +95 -26
  213. package/src/storage/ContentContainer.ts +200 -0
  214. package/src/storage/MediaProviderFromContainer.ts +92 -0
  215. package/src/storage/index.ts +3 -0
  216. package/src/transform/applyTransform.ts +83 -0
  217. package/src/transform/blockAnalyzer.ts +181 -0
  218. package/src/transform/index.ts +20 -0
  219. package/src/transform/registry.ts +47 -0
  220. package/src/transform/styles/dataDriven.ts +16 -0
  221. package/src/transform/styles/documentary.ts +16 -0
  222. package/src/transform/styles/index.ts +5 -0
  223. package/src/transform/styles/magazine.ts +16 -0
  224. package/src/transform/styles/minimal.ts +16 -0
  225. package/src/transform/styles/narrative.ts +16 -0
  226. package/src/transform/templateSelector.ts +294 -0
  227. package/src/transform/timingAllocator.ts +126 -0
  228. package/src/transform/types.ts +105 -0
  229. package/dist/chunk-O5HCIUAD.js.map +0 -1
  230. package/dist/chunk-PJ7AODIM.js.map +0 -1
  231. package/dist/chunk-S4F2RY37.js.map +0 -1
  232. package/dist/chunk-URU6QMLY.js.map +0 -1
  233. package/dist/chunk-VJN7UB2Z.js +0 -145
  234. package/dist/chunk-VJN7UB2Z.js.map +0 -1
  235. package/dist/chunk-VP5SZTZ2.js.map +0 -1
  236. /package/dist/{chunk-6YJPI6VI.js.map → chunk-4X3JQXNM.js.map} +0 -0
@@ -1,15 +1,20 @@
1
1
  import {
2
- DEFAULT_THEME,
2
+ DEFAULT_THEME
3
+ } from "./chunk-TVPKD76V.js";
4
+ import {
3
5
  VIEWPORT_PRESETS,
4
6
  createTemplateContext,
5
7
  getTwoColumnPositions,
6
8
  isPersistentLayerTemplate,
7
9
  isTemplateBlock,
8
10
  scaledFontSize2 as scaledFontSize
9
- } from "./chunk-O5HCIUAD.js";
11
+ } from "./chunk-OFU53HI7.js";
10
12
  import {
11
13
  extractPlainText
12
14
  } from "./chunk-QWVRN6I4.js";
15
+ import {
16
+ estimateReadingTime
17
+ } from "./chunk-4X3JQXNM.js";
13
18
 
14
19
  // src/doc/templates/persistentLayers.ts
15
20
  var GRADIENT_PRESETS = {
@@ -363,6 +368,61 @@ function getDocStyleConfig(preset, articleTitle, heroSrc, subtitle) {
363
368
  }
364
369
  }
365
370
 
371
+ // src/doc/utils/themeUtils.ts
372
+ var FALLBACK_COLOR_SCHEME = {
373
+ bg: "#1a365d",
374
+ text: "#63b3ed",
375
+ accent: "#90cdf4"
376
+ };
377
+ function resolveColorScheme(context, name) {
378
+ const schemes = context.theme.colorSchemes;
379
+ if (name && name in schemes) return schemes[name];
380
+ const keys = Object.keys(schemes);
381
+ if (keys.length > 0) return schemes[keys[0]];
382
+ return FALLBACK_COLOR_SCHEME;
383
+ }
384
+ var FALLBACK_BODY = 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif';
385
+ var FALLBACK_TITLE = 'Georgia, "Times New Roman", serif';
386
+ var FALLBACK_MONO = 'Consolas, "Courier New", monospace';
387
+ function getThemeFont(context, role) {
388
+ const { typography } = context.theme;
389
+ switch (role) {
390
+ case "title":
391
+ return typography.titleFontFamily || FALLBACK_TITLE;
392
+ case "body":
393
+ return typography.bodyFontFamily || FALLBACK_BODY;
394
+ case "mono":
395
+ return typography.monoFontFamily || FALLBACK_MONO;
396
+ }
397
+ }
398
+ function themedFontSize(basePx, context, isTitle = false) {
399
+ const { fontScale, layout, theme } = context;
400
+ const layoutScale = isTitle ? layout.titleScale : layout.bodyScale;
401
+ const themeScale = isTitle ? theme.typography.titleScale ?? 1 : theme.typography.bodyScale ?? 1;
402
+ return Math.round(basePx * fontScale * layoutScale * themeScale);
403
+ }
404
+ function scaleAnimationDuration(baseDuration, context) {
405
+ const speed = context.theme.style.animationSpeed ?? 1;
406
+ return baseDuration * speed;
407
+ }
408
+ function getDefaultAnimation(context, layerType) {
409
+ const rs = context.theme.renderStyle;
410
+ return layerType === "text" ? rs.defaultTextAnimation : rs.defaultImageAnimation;
411
+ }
412
+ function shouldUseShadow(context) {
413
+ return context.theme.style.textShadow ?? true;
414
+ }
415
+ function getOverlayOpacity(context) {
416
+ return context.theme.style.overlayOpacity ?? 0.5;
417
+ }
418
+ function getTemplateHint(context, templateName, key, fallback) {
419
+ const hints = context.theme.renderStyle.templateHints;
420
+ if (!hints) return fallback;
421
+ const tplHints = hints[templateName];
422
+ if (!tplHints || !(key in tplHints)) return fallback;
423
+ return tplHints[key];
424
+ }
425
+
366
426
  // src/doc/templates/titleBlock.ts
367
427
  function titleBlock(input, context) {
368
428
  const { title, subtitle, backgroundColor } = input;
@@ -404,6 +464,7 @@ function titleBlock(input, context) {
404
464
  text: title,
405
465
  style: {
406
466
  fontSize: titleFontSize,
467
+ fontFamily: getThemeFont(context, "title"),
407
468
  fontWeight: "bold",
408
469
  color: theme.colors.text,
409
470
  textAlign: "center",
@@ -427,6 +488,7 @@ function titleBlock(input, context) {
427
488
  text: subtitle,
428
489
  style: {
429
490
  fontSize: subtitleFontSize,
491
+ fontFamily: getThemeFont(context, "body"),
430
492
  color: theme.colors.textMuted,
431
493
  textAlign: "center",
432
494
  lineHeight: 1.5
@@ -444,50 +506,9 @@ function titleBlock(input, context) {
444
506
  return layers;
445
507
  }
446
508
 
447
- // src/doc/utils/themeUtils.ts
448
- var FALLBACK_COLOR_SCHEME = {
449
- bg: "#1a365d",
450
- text: "#63b3ed",
451
- accent: "#90cdf4"
452
- };
453
- function resolveColorScheme(context, name) {
454
- const schemes = context.theme.colorSchemes;
455
- if (name && name in schemes) return schemes[name];
456
- const keys = Object.keys(schemes);
457
- if (keys.length > 0) return schemes[keys[0]];
458
- return FALLBACK_COLOR_SCHEME;
459
- }
460
- function themedFontSize(basePx, context, isTitle = false) {
461
- const { fontScale, layout, theme } = context;
462
- const layoutScale = isTitle ? layout.titleScale : layout.bodyScale;
463
- const themeScale = isTitle ? theme.typography.titleScale ?? 1 : theme.typography.bodyScale ?? 1;
464
- return Math.round(basePx * fontScale * layoutScale * themeScale);
465
- }
466
- function scaleAnimationDuration(baseDuration, context) {
467
- const speed = context.theme.style.animationSpeed ?? 1;
468
- return baseDuration * speed;
469
- }
470
- function getDefaultAnimation(context, layerType) {
471
- const rs = context.theme.renderStyle;
472
- return layerType === "text" ? rs.defaultTextAnimation : rs.defaultImageAnimation;
473
- }
474
- function shouldUseShadow(context) {
475
- return context.theme.style.textShadow ?? true;
476
- }
477
- function getOverlayOpacity(context) {
478
- return context.theme.style.overlayOpacity ?? 0.5;
479
- }
480
- function getTemplateHint(context, templateName, key, fallback) {
481
- const hints = context.theme.renderStyle.templateHints;
482
- if (!hints) return fallback;
483
- const tplHints = hints[templateName];
484
- if (!tplHints || !(key in tplHints)) return fallback;
485
- return tplHints[key];
486
- }
487
-
488
509
  // src/doc/templates/sectionHeader.ts
489
510
  function sectionHeader(input, context) {
490
- const { title, colorScheme = "blue", imageSrc, imageAlt, ambientMotion } = input;
511
+ const { title = "", colorScheme = "blue", imageSrc, imageAlt, ambientMotion } = input;
491
512
  const { layout } = context;
492
513
  const colors = resolveColorScheme(context, colorScheme);
493
514
  const titleFontSize = scaledFontSize(72, context, true);
@@ -543,6 +564,7 @@ function sectionHeader(input, context) {
543
564
  text: title,
544
565
  style: {
545
566
  fontSize: titleFontSize,
567
+ fontFamily: getThemeFont(context, "title"),
546
568
  fontWeight: "bold",
547
569
  color: imageSrc ? "#ffffff" : colors.text,
548
570
  textAlign: "center",
@@ -818,6 +840,7 @@ function statHighlight(input, context) {
818
840
  text: stat,
819
841
  style: {
820
842
  fontSize: statFontSize,
843
+ fontFamily: getThemeFont(context, "title"),
821
844
  fontWeight: "bold",
822
845
  color: colors.text,
823
846
  shadow: !!accentImage
@@ -837,6 +860,7 @@ function statHighlight(input, context) {
837
860
  text: description,
838
861
  style: {
839
862
  fontSize: descFontSize,
863
+ fontFamily: getThemeFont(context, "body"),
840
864
  color: theme.colors.textMuted,
841
865
  textAlign: "center",
842
866
  lineHeight: 1.6,
@@ -859,6 +883,7 @@ function statHighlight(input, context) {
859
883
  text: detail,
860
884
  style: {
861
885
  fontSize: detailFontSize,
886
+ fontFamily: getThemeFont(context, "body"),
862
887
  color: colors.accent,
863
888
  textAlign: "center",
864
889
  shadow: !!accentImage
@@ -899,6 +924,7 @@ function quoteBlock(input, context) {
899
924
  text: "\u201C",
900
925
  style: {
901
926
  fontSize: decorativeQuoteFontSize,
927
+ fontFamily: getThemeFont(context, "title"),
902
928
  color: "rgba(255, 255, 255, 0.06)",
903
929
  textAlign: "center"
904
930
  }
@@ -917,6 +943,7 @@ function quoteBlock(input, context) {
917
943
  text: quote,
918
944
  style: {
919
945
  fontSize: quoteFontSize,
946
+ fontFamily: getThemeFont(context, "title"),
920
947
  color: theme.colors.text,
921
948
  textAlign: "center",
922
949
  lineHeight: 1.6,
@@ -939,6 +966,7 @@ function quoteBlock(input, context) {
939
966
  text: `\u2014 ${attribution}`,
940
967
  style: {
941
968
  fontSize: attrFontSize,
969
+ fontFamily: getThemeFont(context, "body"),
942
970
  color: theme.colors.textMuted,
943
971
  textAlign: "center",
944
972
  shadow: !!accentImage
@@ -979,6 +1007,7 @@ function factCard(input, context) {
979
1007
  text: fact,
980
1008
  style: {
981
1009
  fontSize: factFontSize,
1010
+ fontFamily: getThemeFont(context, "title"),
982
1011
  fontWeight: "bold",
983
1012
  color: theme.colors.text,
984
1013
  textAlign: "center",
@@ -1001,6 +1030,7 @@ function factCard(input, context) {
1001
1030
  text: explanation,
1002
1031
  style: {
1003
1032
  fontSize: explainFontSize,
1033
+ fontFamily: getThemeFont(context, "body"),
1004
1034
  color: theme.colors.textMuted,
1005
1035
  textAlign: "center",
1006
1036
  lineHeight: 1.5,
@@ -1023,6 +1053,7 @@ function factCard(input, context) {
1023
1053
  text: source,
1024
1054
  style: {
1025
1055
  fontSize: sourceFontSize,
1056
+ fontFamily: getThemeFont(context, "body"),
1026
1057
  color: theme.colors.textMuted,
1027
1058
  textAlign: "center",
1028
1059
  shadow: !!accentImage
@@ -1093,6 +1124,7 @@ function twoColumn(input, context) {
1093
1124
  text: header,
1094
1125
  style: {
1095
1126
  fontSize: headerFontSize,
1127
+ fontFamily: getThemeFont(context, "title"),
1096
1128
  color: theme.colors.textMuted,
1097
1129
  textAlign: "center"
1098
1130
  }
@@ -1125,6 +1157,7 @@ function twoColumn(input, context) {
1125
1157
  text: left.label,
1126
1158
  style: {
1127
1159
  fontSize: labelFontSize,
1160
+ fontFamily: getThemeFont(context, "body"),
1128
1161
  fontWeight: "bold",
1129
1162
  color: leftColors.text,
1130
1163
  textAlign: "center",
@@ -1147,6 +1180,7 @@ function twoColumn(input, context) {
1147
1180
  text: left.sublabel,
1148
1181
  style: {
1149
1182
  fontSize: sublabelFontSize,
1183
+ fontFamily: getThemeFont(context, "body"),
1150
1184
  color: theme.colors.textMuted,
1151
1185
  textAlign: "center",
1152
1186
  lineHeight: 1.4
@@ -1171,6 +1205,7 @@ function twoColumn(input, context) {
1171
1205
  text: connectorSymbol,
1172
1206
  style: {
1173
1207
  fontSize: connectorFontSize,
1208
+ fontFamily: getThemeFont(context, "title"),
1174
1209
  fontWeight: "bold",
1175
1210
  color: "rgba(255, 255, 255, 0.6)",
1176
1211
  textAlign: "center"
@@ -1190,6 +1225,7 @@ function twoColumn(input, context) {
1190
1225
  text: right.label,
1191
1226
  style: {
1192
1227
  fontSize: labelFontSize,
1228
+ fontFamily: getThemeFont(context, "body"),
1193
1229
  fontWeight: "bold",
1194
1230
  color: rightColors.text,
1195
1231
  textAlign: "center",
@@ -1212,6 +1248,7 @@ function twoColumn(input, context) {
1212
1248
  text: right.sublabel,
1213
1249
  style: {
1214
1250
  fontSize: sublabelFontSize,
1251
+ fontFamily: getThemeFont(context, "body"),
1215
1252
  color: theme.colors.textMuted,
1216
1253
  textAlign: "center",
1217
1254
  lineHeight: 1.4
@@ -1265,6 +1302,7 @@ function dateEvent(input, context) {
1265
1302
  text: date,
1266
1303
  style: {
1267
1304
  fontSize: dateFontSize,
1305
+ fontFamily: getThemeFont(context, "title"),
1268
1306
  fontWeight: "bold",
1269
1307
  color: colors.date,
1270
1308
  shadow: !!accentImage
@@ -1284,6 +1322,7 @@ function dateEvent(input, context) {
1284
1322
  text: description,
1285
1323
  style: {
1286
1324
  fontSize: descFontSize,
1325
+ fontFamily: getThemeFont(context, "body"),
1287
1326
  color: colors.text,
1288
1327
  textAlign: "center",
1289
1328
  lineHeight: 1.8,
@@ -1306,6 +1345,7 @@ function dateEvent(input, context) {
1306
1345
  text: footer,
1307
1346
  style: {
1308
1347
  fontSize: footerFontSize,
1348
+ fontFamily: getThemeFont(context, "body"),
1309
1349
  color: theme.colors.textMuted,
1310
1350
  shadow: !!accentImage
1311
1351
  }
@@ -1375,6 +1415,7 @@ function imageWithCaption(input, context) {
1375
1415
  text: caption,
1376
1416
  style: {
1377
1417
  fontSize: titleFontSize,
1418
+ fontFamily: getThemeFont(context, "title"),
1378
1419
  fontWeight: "bold",
1379
1420
  color: theme.colors.text,
1380
1421
  textAlign: "center",
@@ -1397,6 +1438,7 @@ function imageWithCaption(input, context) {
1397
1438
  text: subtitle,
1398
1439
  style: {
1399
1440
  fontSize: subtitleFontSize,
1441
+ fontFamily: getThemeFont(context, "body"),
1400
1442
  color: theme.colors.textMuted,
1401
1443
  textAlign: "center",
1402
1444
  lineHeight: 1.5
@@ -1435,6 +1477,7 @@ function imageWithCaption(input, context) {
1435
1477
  text: caption,
1436
1478
  style: {
1437
1479
  fontSize: captionFontSize,
1480
+ fontFamily: getThemeFont(context, "body"),
1438
1481
  color: theme.colors.text,
1439
1482
  textAlign: "center",
1440
1483
  shadow: true
@@ -1503,6 +1546,7 @@ function mapBlock(input, context) {
1503
1546
  text: title,
1504
1547
  style: {
1505
1548
  fontSize: titleFontSize,
1549
+ fontFamily: getThemeFont(context, "title"),
1506
1550
  fontWeight: "bold",
1507
1551
  color: theme.colors.text,
1508
1552
  textAlign: "center",
@@ -1535,6 +1579,7 @@ function mapBlock(input, context) {
1535
1579
  text: caption,
1536
1580
  style: {
1537
1581
  fontSize: captionFontSize,
1582
+ fontFamily: getThemeFont(context, "body"),
1538
1583
  color: theme.colors.text,
1539
1584
  textAlign: "center",
1540
1585
  shadow: true
@@ -1577,6 +1622,7 @@ function fullBleedQuote(input, context) {
1577
1622
  text,
1578
1623
  style: {
1579
1624
  fontSize: textFontSize,
1625
+ fontFamily: getThemeFont(context, "title"),
1580
1626
  fontWeight: "bold",
1581
1627
  color: colors.text,
1582
1628
  textAlign: "center",
@@ -1626,6 +1672,7 @@ function listBlock(input, context) {
1626
1672
  text: title,
1627
1673
  style: {
1628
1674
  fontSize: titleFontSize,
1675
+ fontFamily: getThemeFont(context, "title"),
1629
1676
  fontWeight: "bold",
1630
1677
  color: theme.colors.text,
1631
1678
  textAlign: "center",
@@ -1652,6 +1699,7 @@ function listBlock(input, context) {
1652
1699
  text: itemText,
1653
1700
  style: {
1654
1701
  fontSize: itemFontSize,
1702
+ fontFamily: getThemeFont(context, "body"),
1655
1703
  color: theme.colors.text,
1656
1704
  lineHeight: 1.4,
1657
1705
  shadow: !!accentImage
@@ -1706,6 +1754,7 @@ function photoGrid(input, context) {
1706
1754
  text: img.alt,
1707
1755
  style: {
1708
1756
  fontSize: altFontSize,
1757
+ fontFamily: getThemeFont(context, "body"),
1709
1758
  color: theme.colors.textMuted,
1710
1759
  textAlign: "center"
1711
1760
  }
@@ -1751,6 +1800,7 @@ function photoGrid(input, context) {
1751
1800
  text: caption,
1752
1801
  style: {
1753
1802
  fontSize: captionFontSize,
1803
+ fontFamily: getThemeFont(context, "body"),
1754
1804
  color: theme.colors.text,
1755
1805
  textAlign: "center",
1756
1806
  shadow: true
@@ -1835,6 +1885,7 @@ function definitionCard(input, context) {
1835
1885
  text: term,
1836
1886
  style: {
1837
1887
  fontSize: termFontSize,
1888
+ fontFamily: getThemeFont(context, "title"),
1838
1889
  fontWeight: "bold",
1839
1890
  color: colors.text,
1840
1891
  textAlign: "center",
@@ -1871,6 +1922,7 @@ function definitionCard(input, context) {
1871
1922
  text: definition,
1872
1923
  style: {
1873
1924
  fontSize: defFontSize,
1925
+ fontFamily: getThemeFont(context, "body"),
1874
1926
  color: theme.colors.text,
1875
1927
  textAlign: "center",
1876
1928
  lineHeight: 1.6,
@@ -1894,6 +1946,7 @@ function definitionCard(input, context) {
1894
1946
  text: origin,
1895
1947
  style: {
1896
1948
  fontSize: originFontSize,
1949
+ fontFamily: getThemeFont(context, "body"),
1897
1950
  color: theme.colors.textMuted,
1898
1951
  textAlign: "center",
1899
1952
  shadow: !!accentImage
@@ -1949,7 +2002,11 @@ function comparisonBar(input, context) {
1949
2002
  id: "left-label",
1950
2003
  content: {
1951
2004
  text: leftLabel,
1952
- style: { fontSize: labelFontSize, color: theme.colors.textMuted }
2005
+ style: {
2006
+ fontSize: labelFontSize,
2007
+ fontFamily: getThemeFont(context, "body"),
2008
+ color: theme.colors.textMuted
2009
+ }
1953
2010
  },
1954
2011
  position: { x: `${barStartX}%`, y: `${topBarY - 8}%` }
1955
2012
  },
@@ -1959,7 +2016,12 @@ function comparisonBar(input, context) {
1959
2016
  id: "left-value",
1960
2017
  content: {
1961
2018
  text: leftDisplay,
1962
- style: { fontSize: valueFontSize, fontWeight: "bold", color: colors.text }
2019
+ style: {
2020
+ fontSize: valueFontSize,
2021
+ fontFamily: getThemeFont(context, "body"),
2022
+ fontWeight: "bold",
2023
+ color: colors.text
2024
+ }
1963
2025
  },
1964
2026
  position: { x: `${barStartX + leftBarWidth + 2}%`, y: `${topBarY - 1}%` },
1965
2027
  animation: { type: "fadeIn", duration: 0.8, delay: 0.3 }
@@ -1987,7 +2049,11 @@ function comparisonBar(input, context) {
1987
2049
  id: "right-label",
1988
2050
  content: {
1989
2051
  text: rightLabel,
1990
- style: { fontSize: labelFontSize, color: theme.colors.textMuted }
2052
+ style: {
2053
+ fontSize: labelFontSize,
2054
+ fontFamily: getThemeFont(context, "body"),
2055
+ color: theme.colors.textMuted
2056
+ }
1991
2057
  },
1992
2058
  position: { x: `${barStartX}%`, y: `${bottomBarY - 8}%` }
1993
2059
  },
@@ -1997,7 +2063,12 @@ function comparisonBar(input, context) {
1997
2063
  id: "right-value",
1998
2064
  content: {
1999
2065
  text: rightDisplay,
2000
- style: { fontSize: valueFontSize, fontWeight: "bold", color: colors.accent }
2066
+ style: {
2067
+ fontSize: valueFontSize,
2068
+ fontFamily: getThemeFont(context, "body"),
2069
+ fontWeight: "bold",
2070
+ color: colors.accent
2071
+ }
2001
2072
  },
2002
2073
  position: { x: `${barStartX + rightBarWidth + 2}%`, y: `${bottomBarY - 1}%` },
2003
2074
  animation: { type: "fadeIn", duration: 0.8, delay: 0.6 }
@@ -2062,6 +2133,7 @@ function pullQuote(input, context) {
2062
2133
  text: "\u201C",
2063
2134
  style: {
2064
2135
  fontSize: decoFontSize,
2136
+ fontFamily: getThemeFont(context, "title"),
2065
2137
  color: "rgba(255, 255, 255, 0.08)",
2066
2138
  textAlign: "center"
2067
2139
  }
@@ -2080,6 +2152,7 @@ function pullQuote(input, context) {
2080
2152
  text,
2081
2153
  style: {
2082
2154
  fontSize: quoteFontSize,
2155
+ fontFamily: getThemeFont(context, "title"),
2083
2156
  color: "#ffffff",
2084
2157
  textAlign: "center",
2085
2158
  lineHeight: 1.5,
@@ -2103,6 +2176,7 @@ function pullQuote(input, context) {
2103
2176
  text: `\u2014 ${attribution}`,
2104
2177
  style: {
2105
2178
  fontSize: attrFontSize,
2179
+ fontFamily: getThemeFont(context, "body"),
2106
2180
  color: "rgba(255, 255, 255, 0.7)",
2107
2181
  textAlign: "center",
2108
2182
  shadow: true
@@ -2179,6 +2253,7 @@ function videoWithCaption(input, context) {
2179
2253
  text: caption,
2180
2254
  style: {
2181
2255
  fontSize: captionFontSize,
2256
+ fontFamily: getThemeFont(context, "body"),
2182
2257
  color: theme.colors.text,
2183
2258
  textAlign: "center",
2184
2259
  shadow: true
@@ -2201,6 +2276,7 @@ function videoWithCaption(input, context) {
2201
2276
  text: videoCredit,
2202
2277
  style: {
2203
2278
  fontSize: creditFontSize,
2279
+ fontFamily: getThemeFont(context, "body"),
2204
2280
  color: "rgba(255, 255, 255, 0.5)",
2205
2281
  textAlign: "right"
2206
2282
  }
@@ -2257,6 +2333,7 @@ function videoPullQuote(input, context) {
2257
2333
  text: "\u201C",
2258
2334
  style: {
2259
2335
  fontSize: decoFontSize,
2336
+ fontFamily: getThemeFont(context, "title"),
2260
2337
  color: "rgba(255, 255, 255, 0.08)",
2261
2338
  textAlign: "center"
2262
2339
  }
@@ -2275,6 +2352,7 @@ function videoPullQuote(input, context) {
2275
2352
  text,
2276
2353
  style: {
2277
2354
  fontSize: quoteFontSize,
2355
+ fontFamily: getThemeFont(context, "title"),
2278
2356
  color: "#ffffff",
2279
2357
  textAlign: "center",
2280
2358
  lineHeight: 1.5,
@@ -2298,6 +2376,7 @@ function videoPullQuote(input, context) {
2298
2376
  text: `\u2014 ${attribution}`,
2299
2377
  style: {
2300
2378
  fontSize: attrFontSize,
2379
+ fontFamily: getThemeFont(context, "body"),
2301
2380
  color: "rgba(255, 255, 255, 0.7)",
2302
2381
  textAlign: "center",
2303
2382
  shadow: true
@@ -2354,6 +2433,7 @@ function coverBlock(input, context) {
2354
2433
  text: title,
2355
2434
  style: {
2356
2435
  fontSize: titleFontSize,
2436
+ fontFamily: getThemeFont(context, "title"),
2357
2437
  fontWeight: "bold",
2358
2438
  color: theme.colors.text,
2359
2439
  textAlign: "center",
@@ -2378,6 +2458,7 @@ function coverBlock(input, context) {
2378
2458
  text: subtitle,
2379
2459
  style: {
2380
2460
  fontSize: subtitleFontSize,
2461
+ fontFamily: getThemeFont(context, "body"),
2381
2462
  color: theme.colors.textMuted,
2382
2463
  textAlign: "center",
2383
2464
  lineHeight: 1.5
@@ -2816,13 +2897,15 @@ function markdownToDoc(markdownDoc, options) {
2816
2897
  const id = heading ? generateId(heading, headingIndex++) : "preamble";
2817
2898
  const annotation = heading?.templateAnnotation;
2818
2899
  const template = annotation?.template ?? (heading ? defaultTemplate : void 0);
2900
+ const title = heading ? extractPlainText(heading) : void 0;
2819
2901
  const block = {
2820
2902
  id,
2821
2903
  startTime: 0,
2822
2904
  duration: defaultDuration,
2823
2905
  audioSegment: 0,
2824
2906
  template,
2825
- ...heading ? { sourceHeading: heading } : {}
2907
+ ...heading ? { sourceHeading: heading } : {},
2908
+ ...title ? { title } : {}
2826
2909
  };
2827
2910
  if (annotation?.params) {
2828
2911
  block.templateOverrides = annotation.params;
@@ -2868,11 +2951,38 @@ function markdownToDoc(markdownDoc, options) {
2868
2951
  rootBlocks.push(currentBlock);
2869
2952
  }
2870
2953
  const allBlocks = flattenBlocks(rootBlocks);
2954
+ const minDuration = 3;
2955
+ const phrases = [];
2956
+ for (const block of allBlocks) {
2957
+ const bodyText = getBlockBodyText(block);
2958
+ if (bodyText.length > 0) {
2959
+ const estimate = estimateReadingTime(bodyText);
2960
+ block.duration = Math.max(minDuration, estimate.seconds);
2961
+ } else {
2962
+ block.duration = defaultDuration;
2963
+ }
2964
+ }
2871
2965
  let currentTime = 0;
2872
2966
  for (const block of allBlocks) {
2873
2967
  block.startTime = currentTime;
2968
+ const bodyText = getBlockBodyText(block);
2969
+ if (bodyText.length > 0) {
2970
+ const sentences = splitIntoSentences(bodyText);
2971
+ if (sentences.length > 0) {
2972
+ const timePerSentence = block.duration / sentences.length;
2973
+ for (let i = 0; i < sentences.length; i++) {
2974
+ phrases.push({
2975
+ text: sentences[i],
2976
+ startTime: currentTime + i * timePerSentence,
2977
+ endTime: currentTime + (i + 1) * timePerSentence,
2978
+ audioSegment: 0
2979
+ });
2980
+ }
2981
+ }
2982
+ }
2874
2983
  currentTime += block.duration;
2875
2984
  }
2985
+ const captions = phrases.length > 0 ? { phrases, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), version: 1 } : void 0;
2876
2986
  return {
2877
2987
  articleId,
2878
2988
  duration: currentTime,
@@ -2880,6 +2990,7 @@ function markdownToDoc(markdownDoc, options) {
2880
2990
  audio: {
2881
2991
  segments: []
2882
2992
  },
2993
+ ...captions ? { captions } : {},
2883
2994
  ...markdownDoc.frontmatter ? { frontmatter: markdownDoc.frontmatter } : {}
2884
2995
  };
2885
2996
  }
@@ -2906,6 +3017,56 @@ function countBlocks(blocks) {
2906
3017
  function getBlockDepth(block) {
2907
3018
  return block.sourceHeading?.depth ?? 0;
2908
3019
  }
3020
+ function getBlockBodyText(block) {
3021
+ if (!block.contents || block.contents.length === 0) return "";
3022
+ return block.contents.map((node) => extractPlainText(node)).join("\n").trim();
3023
+ }
3024
+ var MAX_PHRASE_WORDS = 12;
3025
+ function splitIntoSentences(text) {
3026
+ const lines = text.split(/\n+/).map((s) => s.trim()).filter((s) => s.length > 0);
3027
+ const sentenceRe = /(?<=[.!?])\s+/;
3028
+ const fragments = [];
3029
+ for (const line of lines) {
3030
+ const sentences = line.split(sentenceRe).map((s) => s.trim()).filter((s) => s.length > 0);
3031
+ fragments.push(...sentences);
3032
+ }
3033
+ if (fragments.length === 0) return [];
3034
+ const split = [];
3035
+ for (const frag of fragments) {
3036
+ const words = frag.split(/\s+/);
3037
+ if (words.length > MAX_PHRASE_WORDS) {
3038
+ const mid = Math.floor(frag.length / 2);
3039
+ const clauseRe = /[,;]\s+|\s+—\s+|\s+-\s+/g;
3040
+ let bestSplit = -1;
3041
+ let bestDist = Infinity;
3042
+ let match;
3043
+ while ((match = clauseRe.exec(frag)) !== null) {
3044
+ const dist = Math.abs(match.index - mid);
3045
+ if (dist < bestDist) {
3046
+ bestDist = dist;
3047
+ bestSplit = match.index + match[0].length;
3048
+ }
3049
+ }
3050
+ if (bestSplit > 0 && bestSplit < frag.length - 5) {
3051
+ split.push(frag.slice(0, bestSplit).trim());
3052
+ split.push(frag.slice(bestSplit).trim());
3053
+ } else {
3054
+ split.push(frag);
3055
+ }
3056
+ } else {
3057
+ split.push(frag);
3058
+ }
3059
+ }
3060
+ const merged = [split[0]];
3061
+ for (let i = 1; i < split.length; i++) {
3062
+ if (split[i].length < 15 && merged.length > 0) {
3063
+ merged[merged.length - 1] += " " + split[i];
3064
+ } else {
3065
+ merged.push(split[i]);
3066
+ }
3067
+ }
3068
+ return merged;
3069
+ }
2909
3070
 
2910
3071
  // src/doc/docToMarkdown.ts
2911
3072
  function docToMarkdown(doc) {
@@ -2988,17 +3149,196 @@ function injectPersistentLayers(layers, block, context) {
2988
3149
  return [...useBottom ? bottomLayers : [], ...layers, ...useTop ? topLayers : []];
2989
3150
  }
2990
3151
 
3152
+ // src/doc/audioMapping.ts
3153
+ function parseTimingJson(data) {
3154
+ try {
3155
+ const text = new TextDecoder().decode(data);
3156
+ const parsed = JSON.parse(text);
3157
+ if (parsed && typeof parsed.sourceText === "string" && typeof parsed.duration === "number") {
3158
+ return {
3159
+ sourceText: parsed.sourceText,
3160
+ bookmarks: Array.isArray(parsed.bookmarks) ? parsed.bookmarks : [],
3161
+ duration: parsed.duration
3162
+ };
3163
+ }
3164
+ return null;
3165
+ } catch {
3166
+ return null;
3167
+ }
3168
+ }
3169
+ function normalizeToWords(text) {
3170
+ const words = text.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 2);
3171
+ return new Set(words);
3172
+ }
3173
+ function scoreTextSimilarity(a, b) {
3174
+ const wordsA = normalizeToWords(a);
3175
+ const wordsB = normalizeToWords(b);
3176
+ if (wordsA.size === 0 || wordsB.size === 0) return 0;
3177
+ let intersection = 0;
3178
+ for (const w of wordsA) {
3179
+ if (wordsB.has(w)) intersection++;
3180
+ }
3181
+ const union = (/* @__PURE__ */ new Set([...wordsA, ...wordsB])).size;
3182
+ return union > 0 ? intersection / union : 0;
3183
+ }
3184
+ function slugify2(text) {
3185
+ return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
3186
+ }
3187
+ function blockToPlainText(block) {
3188
+ const parts = [];
3189
+ if (block.title) parts.push(block.title);
3190
+ if (block.contents) {
3191
+ for (const node of block.contents) {
3192
+ parts.push(extractPlainText(node));
3193
+ }
3194
+ }
3195
+ return parts.join("\n");
3196
+ }
3197
+ function flattenForAudio(blocks) {
3198
+ const result = [];
3199
+ for (const block of blocks) {
3200
+ if (block.children && block.children.length > 0) {
3201
+ result.push(...flattenForAudio(block.children));
3202
+ } else {
3203
+ result.push(block);
3204
+ }
3205
+ }
3206
+ return result;
3207
+ }
3208
+ async function discoverMp3s(container) {
3209
+ const files = await container.listFiles();
3210
+ const mp3Files = files.filter((f) => f.path.endsWith(".mp3"));
3211
+ const results = [];
3212
+ for (const file of mp3Files) {
3213
+ const filename = file.path.split("/").pop() ?? file.path;
3214
+ let timing = null;
3215
+ const timingPath = `${file.path}.timing.json`;
3216
+ const timingData = await container.readFile(timingPath);
3217
+ if (timingData) {
3218
+ timing = parseTimingJson(timingData);
3219
+ }
3220
+ results.push({ path: file.path, filename, timing });
3221
+ }
3222
+ return results;
3223
+ }
3224
+ function resolveFromAnnotations(blocks, mp3s) {
3225
+ const matches = [];
3226
+ const flat = flattenForAudio(blocks);
3227
+ for (const block of flat) {
3228
+ const audioRef = block.templateOverrides?.audio;
3229
+ if (!audioRef) continue;
3230
+ const mp3 = mp3s.find(
3231
+ (m) => m.filename === audioRef || m.path === audioRef || m.path.endsWith(`/${audioRef}`)
3232
+ );
3233
+ if (mp3) {
3234
+ matches.push({ block, mp3 });
3235
+ }
3236
+ }
3237
+ return matches;
3238
+ }
3239
+ var MIN_CONTENT_SIMILARITY = 0.35;
3240
+ var MIN_FILENAME_SIMILARITY = 0.5;
3241
+ function autoMatchBlocks(blocks, mp3s) {
3242
+ const flat = flattenForAudio(blocks);
3243
+ if (flat.length === 0 || mp3s.length === 0) return [];
3244
+ const scoredPairs = [];
3245
+ for (let mi = 0; mi < mp3s.length; mi++) {
3246
+ const mp3 = mp3s[mi];
3247
+ for (let bi = 0; bi < flat.length; bi++) {
3248
+ const block = flat[bi];
3249
+ const blockText = blockToPlainText(block);
3250
+ if (!blockText.trim()) continue;
3251
+ let score = 0;
3252
+ if (mp3.timing?.sourceText) {
3253
+ score = scoreTextSimilarity(mp3.timing.sourceText, blockText);
3254
+ }
3255
+ if (score < MIN_CONTENT_SIMILARITY && block.title) {
3256
+ const titleSlug = slugify2(block.title);
3257
+ const fileSlug = slugify2(mp3.filename.replace(/\.mp3$/, ""));
3258
+ if (fileSlug.includes(titleSlug) && titleSlug.length >= 3) {
3259
+ score = Math.max(score, MIN_FILENAME_SIMILARITY + 0.1);
3260
+ }
3261
+ }
3262
+ if (score >= MIN_CONTENT_SIMILARITY) {
3263
+ scoredPairs.push({ block, blockIndex: bi, mp3, mp3Index: mi, score });
3264
+ }
3265
+ }
3266
+ }
3267
+ scoredPairs.sort((a, b) => b.score - a.score);
3268
+ const usedBlocks = /* @__PURE__ */ new Set();
3269
+ const usedMp3s = /* @__PURE__ */ new Set();
3270
+ const matches = [];
3271
+ for (const pair of scoredPairs) {
3272
+ if (usedBlocks.has(pair.blockIndex) || usedMp3s.has(pair.mp3Index)) continue;
3273
+ usedBlocks.add(pair.blockIndex);
3274
+ usedMp3s.add(pair.mp3Index);
3275
+ matches.push({ block: pair.block, mp3: pair.mp3, score: pair.score });
3276
+ }
3277
+ return matches;
3278
+ }
3279
+ async function resolveAudioMapping(doc, container) {
3280
+ const mp3s = await discoverMp3s(container);
3281
+ if (mp3s.length === 0) return doc;
3282
+ let matches = resolveFromAnnotations(doc.blocks, mp3s);
3283
+ if (matches.length === 0) {
3284
+ matches = autoMatchBlocks(doc.blocks, mp3s).map((m) => ({
3285
+ block: m.block,
3286
+ mp3: m.mp3
3287
+ }));
3288
+ }
3289
+ if (matches.length === 0) return doc;
3290
+ const flat = flattenForAudio(doc.blocks);
3291
+ const blockOrder = new Map(flat.map((b, i) => [b.id, i]));
3292
+ matches.sort((a, b) => (blockOrder.get(a.block.id) ?? 0) - (blockOrder.get(b.block.id) ?? 0));
3293
+ const segments = [];
3294
+ const blockToSegment = /* @__PURE__ */ new Map();
3295
+ let startTime = 0;
3296
+ for (let i = 0; i < matches.length; i++) {
3297
+ const { block, mp3 } = matches[i];
3298
+ const duration = mp3.timing?.duration ?? 30;
3299
+ segments.push({
3300
+ src: mp3.path,
3301
+ name: block.title ?? block.id,
3302
+ duration,
3303
+ startTime
3304
+ });
3305
+ blockToSegment.set(block.id, i);
3306
+ startTime += duration;
3307
+ }
3308
+ const newBlocks = assignSegmentIndices(doc.blocks, blockToSegment);
3309
+ return {
3310
+ ...doc,
3311
+ blocks: newBlocks,
3312
+ duration: startTime,
3313
+ audio: { segments }
3314
+ };
3315
+ }
3316
+ function assignSegmentIndices(blocks, blockToSegment, inheritedSegment) {
3317
+ return blocks.map((block) => {
3318
+ const segment = blockToSegment.get(block.id) ?? inheritedSegment ?? block.audioSegment;
3319
+ const newBlock = {
3320
+ ...block,
3321
+ audioSegment: segment
3322
+ };
3323
+ if (block.children) {
3324
+ newBlock.children = assignSegmentIndices(block.children, blockToSegment, segment);
3325
+ }
3326
+ return newBlock;
3327
+ });
3328
+ }
3329
+
2991
3330
  export {
2992
3331
  expandPersistentLayers,
2993
3332
  getDocStyleConfig,
2994
- titleBlock,
2995
3333
  resolveColorScheme,
3334
+ getThemeFont,
2996
3335
  themedFontSize,
2997
3336
  scaleAnimationDuration,
2998
3337
  getDefaultAnimation,
2999
3338
  shouldUseShadow,
3000
3339
  getOverlayOpacity,
3001
3340
  getTemplateHint,
3341
+ titleBlock,
3002
3342
  sectionHeader,
3003
3343
  DEFAULT_LAYOUT,
3004
3344
  getAccentLayout,
@@ -3035,6 +3375,8 @@ export {
3035
3375
  countBlocks,
3036
3376
  getBlockDepth,
3037
3377
  docToMarkdown,
3038
- getLayers
3378
+ getLayers,
3379
+ scoreTextSimilarity,
3380
+ resolveAudioMapping
3039
3381
  };
3040
- //# sourceMappingURL=chunk-PJ7AODIM.js.map
3382
+ //# sourceMappingURL=chunk-3WBJAZZ5.js.map