@bendyline/squisq 1.1.0 → 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 (211) 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/__tests__/markdownToDoc.test.js +75 -0
  4. package/dist/__tests__/markdownToDoc.test.js.map +1 -1
  5. package/dist/{chunk-PJ7AODIM.js → chunk-3WBJAZZ5.js} +394 -52
  6. package/dist/chunk-3WBJAZZ5.js.map +1 -0
  7. package/dist/{chunk-6YJPI6VI.js → chunk-4X3JQXNM.js} +1 -1
  8. package/dist/{chunk-S4F2RY37.js → chunk-CORGFIFU.js} +1 -1
  9. package/dist/chunk-CORGFIFU.js.map +1 -0
  10. package/dist/chunk-DNJKBAI7.js +1 -0
  11. package/dist/chunk-DNJKBAI7.js.map +1 -0
  12. package/dist/{chunk-VP5SZTZ2.js → chunk-MIBRIRGJ.js} +2 -139
  13. package/dist/chunk-MIBRIRGJ.js.map +1 -0
  14. package/dist/{chunk-URU6QMLY.js → chunk-NJLL5XYC.js} +15 -8
  15. package/dist/chunk-NJLL5XYC.js.map +1 -0
  16. package/dist/chunk-OFU53HI7.js +165 -0
  17. package/dist/chunk-OFU53HI7.js.map +1 -0
  18. package/dist/chunk-TSG2OSE6.js +145 -0
  19. package/dist/chunk-TSG2OSE6.js.map +1 -0
  20. package/dist/{chunk-O5HCIUAD.js → chunk-TVPKD76V.js} +81 -187
  21. package/dist/chunk-TVPKD76V.js.map +1 -0
  22. package/dist/contentExtractor-BNfVJV2U.d.ts +113 -0
  23. package/dist/doc/audioMapping.d.ts +36 -0
  24. package/dist/doc/audioMapping.d.ts.map +1 -0
  25. package/dist/doc/audioMapping.js +279 -0
  26. package/dist/doc/audioMapping.js.map +1 -0
  27. package/dist/doc/index.d.ts +1 -0
  28. package/dist/doc/index.d.ts.map +1 -1
  29. package/dist/doc/index.js +1 -0
  30. package/dist/doc/index.js.map +1 -1
  31. package/dist/doc/markdownToDoc.d.ts.map +1 -1
  32. package/dist/doc/markdownToDoc.js +126 -1
  33. package/dist/doc/markdownToDoc.js.map +1 -1
  34. package/dist/doc/templates/comparisonBar.d.ts.map +1 -1
  35. package/dist/doc/templates/comparisonBar.js +23 -5
  36. package/dist/doc/templates/comparisonBar.js.map +1 -1
  37. package/dist/doc/templates/coverBlock.d.ts.map +1 -1
  38. package/dist/doc/templates/coverBlock.js +3 -0
  39. package/dist/doc/templates/coverBlock.js.map +1 -1
  40. package/dist/doc/templates/dateEvent.d.ts.map +1 -1
  41. package/dist/doc/templates/dateEvent.js +4 -0
  42. package/dist/doc/templates/dateEvent.js.map +1 -1
  43. package/dist/doc/templates/definitionCard.d.ts.map +1 -1
  44. package/dist/doc/templates/definitionCard.js +4 -1
  45. package/dist/doc/templates/definitionCard.js.map +1 -1
  46. package/dist/doc/templates/factCard.d.ts.map +1 -1
  47. package/dist/doc/templates/factCard.js +4 -0
  48. package/dist/doc/templates/factCard.js.map +1 -1
  49. package/dist/doc/templates/fullBleedQuote.d.ts.map +1 -1
  50. package/dist/doc/templates/fullBleedQuote.js +2 -1
  51. package/dist/doc/templates/fullBleedQuote.js.map +1 -1
  52. package/dist/doc/templates/imageWithCaption.d.ts.map +1 -1
  53. package/dist/doc/templates/imageWithCaption.js +4 -0
  54. package/dist/doc/templates/imageWithCaption.js.map +1 -1
  55. package/dist/doc/templates/listBlock.d.ts.map +1 -1
  56. package/dist/doc/templates/listBlock.js +3 -0
  57. package/dist/doc/templates/listBlock.js.map +1 -1
  58. package/dist/doc/templates/mapBlock.d.ts.map +1 -1
  59. package/dist/doc/templates/mapBlock.js +3 -0
  60. package/dist/doc/templates/mapBlock.js.map +1 -1
  61. package/dist/doc/templates/photoGrid.d.ts.map +1 -1
  62. package/dist/doc/templates/photoGrid.js +3 -0
  63. package/dist/doc/templates/photoGrid.js.map +1 -1
  64. package/dist/doc/templates/pullQuote.d.ts.map +1 -1
  65. package/dist/doc/templates/pullQuote.js +4 -0
  66. package/dist/doc/templates/pullQuote.js.map +1 -1
  67. package/dist/doc/templates/quoteBlock.d.ts.map +1 -1
  68. package/dist/doc/templates/quoteBlock.js +4 -0
  69. package/dist/doc/templates/quoteBlock.js.map +1 -1
  70. package/dist/doc/templates/sectionHeader.d.ts.map +1 -1
  71. package/dist/doc/templates/sectionHeader.js +3 -2
  72. package/dist/doc/templates/sectionHeader.js.map +1 -1
  73. package/dist/doc/templates/statHighlight.d.ts.map +1 -1
  74. package/dist/doc/templates/statHighlight.js +4 -1
  75. package/dist/doc/templates/statHighlight.js.map +1 -1
  76. package/dist/doc/templates/titleBlock.d.ts.map +1 -1
  77. package/dist/doc/templates/titleBlock.js +3 -0
  78. package/dist/doc/templates/titleBlock.js.map +1 -1
  79. package/dist/doc/templates/twoColumn.d.ts.map +1 -1
  80. package/dist/doc/templates/twoColumn.js +7 -1
  81. package/dist/doc/templates/twoColumn.js.map +1 -1
  82. package/dist/doc/templates/videoPullQuote.d.ts.map +1 -1
  83. package/dist/doc/templates/videoPullQuote.js +4 -0
  84. package/dist/doc/templates/videoPullQuote.js.map +1 -1
  85. package/dist/doc/templates/videoWithCaption.d.ts.map +1 -1
  86. package/dist/doc/templates/videoWithCaption.js +3 -0
  87. package/dist/doc/templates/videoWithCaption.js.map +1 -1
  88. package/dist/doc/utils/themeUtils.d.ts +9 -0
  89. package/dist/doc/utils/themeUtils.d.ts.map +1 -1
  90. package/dist/doc/utils/themeUtils.js +26 -0
  91. package/dist/doc/utils/themeUtils.js.map +1 -1
  92. package/dist/markdown/convert.d.ts.map +1 -1
  93. package/dist/markdown/convert.js +16 -7
  94. package/dist/markdown/convert.js.map +1 -1
  95. package/dist/markdown/types.d.ts +4 -2
  96. package/dist/markdown/types.d.ts.map +1 -1
  97. package/dist/schemas/BlockTemplates.d.ts +2 -2
  98. package/dist/schemas/BlockTemplates.d.ts.map +1 -1
  99. package/dist/schemas/BlockTemplates.js +2 -2
  100. package/dist/schemas/BlockTemplates.js.map +1 -1
  101. package/dist/schemas/Doc.d.ts +52 -0
  102. package/dist/schemas/Doc.d.ts.map +1 -1
  103. package/dist/schemas/Doc.js.map +1 -1
  104. package/dist/schemas/themeLibrary.d.ts +2 -2
  105. package/dist/schemas/themeLibrary.d.ts.map +1 -1
  106. package/dist/schemas/themeLibrary.js +91 -26
  107. package/dist/schemas/themeLibrary.js.map +1 -1
  108. package/dist/story/index.d.ts +50 -5
  109. package/dist/story/index.js +15 -5
  110. package/dist/{themeLibrary-DR1VRo-8.d.ts → themeLibrary-VOHfPzRB.d.ts} +3 -3
  111. package/dist/transform/applyTransform.d.ts +22 -0
  112. package/dist/transform/applyTransform.d.ts.map +1 -0
  113. package/dist/transform/applyTransform.js +68 -0
  114. package/dist/transform/applyTransform.js.map +1 -0
  115. package/dist/transform/blockAnalyzer.d.ts +48 -0
  116. package/dist/transform/blockAnalyzer.d.ts.map +1 -0
  117. package/dist/transform/blockAnalyzer.js +135 -0
  118. package/dist/transform/blockAnalyzer.js.map +1 -0
  119. package/dist/transform/index.d.ts +6 -0
  120. package/dist/transform/index.d.ts.map +1 -0
  121. package/dist/transform/index.js +4 -0
  122. package/dist/transform/index.js.map +1 -0
  123. package/dist/transform/registry.d.ts +19 -0
  124. package/dist/transform/registry.d.ts.map +1 -0
  125. package/dist/transform/registry.js +41 -0
  126. package/dist/transform/registry.js.map +1 -0
  127. package/dist/transform/styles/dataDriven.d.ts +4 -0
  128. package/dist/transform/styles/dataDriven.d.ts.map +1 -0
  129. package/dist/transform/styles/dataDriven.js +15 -0
  130. package/dist/transform/styles/dataDriven.js.map +1 -0
  131. package/dist/transform/styles/documentary.d.ts +4 -0
  132. package/dist/transform/styles/documentary.d.ts.map +1 -0
  133. package/dist/transform/styles/documentary.js +15 -0
  134. package/dist/transform/styles/documentary.js.map +1 -0
  135. package/dist/transform/styles/index.d.ts +6 -0
  136. package/dist/transform/styles/index.d.ts.map +1 -0
  137. package/dist/transform/styles/index.js +6 -0
  138. package/dist/transform/styles/index.js.map +1 -0
  139. package/dist/transform/styles/magazine.d.ts +4 -0
  140. package/dist/transform/styles/magazine.d.ts.map +1 -0
  141. package/dist/transform/styles/magazine.js +15 -0
  142. package/dist/transform/styles/magazine.js.map +1 -0
  143. package/dist/transform/styles/minimal.d.ts +4 -0
  144. package/dist/transform/styles/minimal.d.ts.map +1 -0
  145. package/dist/transform/styles/minimal.js +15 -0
  146. package/dist/transform/styles/minimal.js.map +1 -0
  147. package/dist/transform/styles/narrative.d.ts +4 -0
  148. package/dist/transform/styles/narrative.d.ts.map +1 -0
  149. package/dist/transform/styles/narrative.js +15 -0
  150. package/dist/transform/styles/narrative.js.map +1 -0
  151. package/dist/transform/templateSelector.d.ts +30 -0
  152. package/dist/transform/templateSelector.d.ts.map +1 -0
  153. package/dist/transform/templateSelector.js +218 -0
  154. package/dist/transform/templateSelector.js.map +1 -0
  155. package/dist/transform/timingAllocator.d.ts +21 -0
  156. package/dist/transform/timingAllocator.d.ts.map +1 -0
  157. package/dist/transform/timingAllocator.js +111 -0
  158. package/dist/transform/timingAllocator.js.map +1 -0
  159. package/dist/transform/types.d.ts +86 -0
  160. package/dist/transform/types.d.ts.map +1 -0
  161. package/dist/transform/types.js +9 -0
  162. package/dist/transform/types.js.map +1 -0
  163. package/dist/{types-DHiv_Pnm.d.ts → types-DYAzisMG.d.ts} +4 -2
  164. package/package.json +6 -1
  165. package/src/__tests__/markdownToDoc.test.ts +96 -0
  166. package/src/doc/audioMapping.ts +346 -0
  167. package/src/doc/index.ts +1 -0
  168. package/src/doc/markdownToDoc.ts +136 -2
  169. package/src/doc/templates/comparisonBar.ts +23 -5
  170. package/src/doc/templates/coverBlock.ts +3 -0
  171. package/src/doc/templates/dateEvent.ts +4 -0
  172. package/src/doc/templates/definitionCard.ts +4 -1
  173. package/src/doc/templates/factCard.ts +4 -0
  174. package/src/doc/templates/fullBleedQuote.ts +2 -1
  175. package/src/doc/templates/imageWithCaption.ts +4 -0
  176. package/src/doc/templates/listBlock.ts +3 -0
  177. package/src/doc/templates/mapBlock.ts +3 -0
  178. package/src/doc/templates/photoGrid.ts +3 -0
  179. package/src/doc/templates/pullQuote.ts +4 -0
  180. package/src/doc/templates/quoteBlock.ts +4 -0
  181. package/src/doc/templates/sectionHeader.ts +3 -2
  182. package/src/doc/templates/statHighlight.ts +4 -1
  183. package/src/doc/templates/titleBlock.ts +3 -0
  184. package/src/doc/templates/twoColumn.ts +7 -1
  185. package/src/doc/templates/videoPullQuote.ts +4 -0
  186. package/src/doc/templates/videoWithCaption.ts +3 -0
  187. package/src/doc/utils/themeUtils.ts +29 -0
  188. package/src/markdown/convert.ts +17 -7
  189. package/src/markdown/types.ts +4 -2
  190. package/src/schemas/BlockTemplates.ts +2 -2
  191. package/src/schemas/Doc.ts +60 -0
  192. package/src/schemas/themeLibrary.ts +95 -26
  193. package/src/transform/applyTransform.ts +83 -0
  194. package/src/transform/blockAnalyzer.ts +181 -0
  195. package/src/transform/index.ts +20 -0
  196. package/src/transform/registry.ts +47 -0
  197. package/src/transform/styles/dataDriven.ts +16 -0
  198. package/src/transform/styles/documentary.ts +16 -0
  199. package/src/transform/styles/index.ts +5 -0
  200. package/src/transform/styles/magazine.ts +16 -0
  201. package/src/transform/styles/minimal.ts +16 -0
  202. package/src/transform/styles/narrative.ts +16 -0
  203. package/src/transform/templateSelector.ts +294 -0
  204. package/src/transform/timingAllocator.ts +126 -0
  205. package/src/transform/types.ts +105 -0
  206. package/dist/chunk-O5HCIUAD.js.map +0 -1
  207. package/dist/chunk-PJ7AODIM.js.map +0 -1
  208. package/dist/chunk-S4F2RY37.js.map +0 -1
  209. package/dist/chunk-URU6QMLY.js.map +0 -1
  210. package/dist/chunk-VP5SZTZ2.js.map +0 -1
  211. /package/dist/{chunk-6YJPI6VI.js.map → chunk-4X3JQXNM.js.map} +0 -0
@@ -13,6 +13,7 @@
13
13
  import type { Layer } from '../../schemas/Doc.js';
14
14
  import type { FactCardInput, TemplateContext } from '../../schemas/BlockTemplates.js';
15
15
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
16
+ import { getThemeFont } from '../utils/themeUtils.js';
16
17
  import { createAccentLayers, getAccentLayout, adjustY, DEFAULT_LAYOUT } from './accentImage.js';
17
18
  import { createBackgroundLayer } from './captionUtils.js';
18
19
 
@@ -48,6 +49,7 @@ export function factCard(input: FactCardInput, context: TemplateContext): Layer[
48
49
  text: fact,
49
50
  style: {
50
51
  fontSize: factFontSize,
52
+ fontFamily: getThemeFont(context, 'title'),
51
53
  fontWeight: 'bold',
52
54
  color: theme.colors.text,
53
55
  textAlign: 'center',
@@ -72,6 +74,7 @@ export function factCard(input: FactCardInput, context: TemplateContext): Layer[
72
74
  text: explanation,
73
75
  style: {
74
76
  fontSize: explainFontSize,
77
+ fontFamily: getThemeFont(context, 'body'),
75
78
  color: theme.colors.textMuted,
76
79
  textAlign: 'center',
77
80
  lineHeight: 1.5,
@@ -96,6 +99,7 @@ export function factCard(input: FactCardInput, context: TemplateContext): Layer[
96
99
  text: source,
97
100
  style: {
98
101
  fontSize: sourceFontSize,
102
+ fontFamily: getThemeFont(context, 'body'),
99
103
  color: theme.colors.textMuted,
100
104
  textAlign: 'center',
101
105
  shadow: !!accentImage,
@@ -11,7 +11,7 @@
11
11
  import type { Layer } from '../../schemas/Doc.js';
12
12
  import type { FullBleedQuoteInput, TemplateContext } from '../../schemas/BlockTemplates.js';
13
13
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
14
- import { resolveColorScheme } from '../utils/themeUtils.js';
14
+ import { resolveColorScheme, getThemeFont } from '../utils/themeUtils.js';
15
15
 
16
16
  export function fullBleedQuote(input: FullBleedQuoteInput, context: TemplateContext): Layer[] {
17
17
  const { text, colorScheme = 'blue' } = input;
@@ -40,6 +40,7 @@ export function fullBleedQuote(input: FullBleedQuoteInput, context: TemplateCont
40
40
  text,
41
41
  style: {
42
42
  fontSize: textFontSize,
43
+ fontFamily: getThemeFont(context, 'title'),
43
44
  fontWeight: 'bold',
44
45
  color: colors.text,
45
46
  textAlign: 'center',
@@ -11,6 +11,7 @@
11
11
  import type { Layer } from '../../schemas/Doc.js';
12
12
  import type { ImageWithCaptionInput, TemplateContext } from '../../schemas/BlockTemplates.js';
13
13
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
14
+ import { getThemeFont } from '../utils/themeUtils.js';
14
15
  import { cleanCaption } from './captionUtils.js';
15
16
  import { mapAmbientMotion } from './accentImage.js';
16
17
 
@@ -80,6 +81,7 @@ export function imageWithCaption(input: ImageWithCaptionInput, context: Template
80
81
  text: caption,
81
82
  style: {
82
83
  fontSize: titleFontSize,
84
+ fontFamily: getThemeFont(context, 'title'),
83
85
  fontWeight: 'bold',
84
86
  color: theme.colors.text,
85
87
  textAlign: 'center',
@@ -104,6 +106,7 @@ export function imageWithCaption(input: ImageWithCaptionInput, context: Template
104
106
  text: subtitle,
105
107
  style: {
106
108
  fontSize: subtitleFontSize,
109
+ fontFamily: getThemeFont(context, 'body'),
107
110
  color: theme.colors.textMuted,
108
111
  textAlign: 'center',
109
112
  lineHeight: 1.5,
@@ -148,6 +151,7 @@ export function imageWithCaption(input: ImageWithCaptionInput, context: Template
148
151
  text: caption,
149
152
  style: {
150
153
  fontSize: captionFontSize,
154
+ fontFamily: getThemeFont(context, 'body'),
151
155
  color: theme.colors.text,
152
156
  textAlign: 'center',
153
157
  shadow: true,
@@ -11,6 +11,7 @@
11
11
  import type { Layer } from '../../schemas/Doc.js';
12
12
  import type { ListBlockInput, TemplateContext } from '../../schemas/BlockTemplates.js';
13
13
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
14
+ import { getThemeFont } from '../utils/themeUtils.js';
14
15
  import { createAccentLayers, getAccentLayout, adjustY, DEFAULT_LAYOUT } from './accentImage.js';
15
16
 
16
17
  export function listBlock(input: ListBlockInput, context: TemplateContext): Layer[] {
@@ -51,6 +52,7 @@ export function listBlock(input: ListBlockInput, context: TemplateContext): Laye
51
52
  text: title,
52
53
  style: {
53
54
  fontSize: titleFontSize,
55
+ fontFamily: getThemeFont(context, 'title'),
54
56
  fontWeight: 'bold',
55
57
  color: theme.colors.text,
56
58
  textAlign: 'center',
@@ -82,6 +84,7 @@ export function listBlock(input: ListBlockInput, context: TemplateContext): Laye
82
84
  text: itemText,
83
85
  style: {
84
86
  fontSize: itemFontSize,
87
+ fontFamily: getThemeFont(context, 'body'),
85
88
  color: theme.colors.text,
86
89
  lineHeight: 1.4,
87
90
  shadow: !!accentImage,
@@ -15,6 +15,7 @@
15
15
  import type { Layer } from '../../schemas/Doc.js';
16
16
  import type { MapBlockInput, TemplateContext } from '../../schemas/BlockTemplates.js';
17
17
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
18
+ import { getThemeFont } from '../utils/themeUtils.js';
18
19
  import { mapAmbientMotion } from './accentImage.js';
19
20
 
20
21
  export function mapBlock(input: MapBlockInput, context: TemplateContext): Layer[] {
@@ -76,6 +77,7 @@ export function mapBlock(input: MapBlockInput, context: TemplateContext): Layer[
76
77
  text: title,
77
78
  style: {
78
79
  fontSize: titleFontSize,
80
+ fontFamily: getThemeFont(context, 'title'),
79
81
  fontWeight: 'bold',
80
82
  color: theme.colors.text,
81
83
  textAlign: 'center',
@@ -113,6 +115,7 @@ export function mapBlock(input: MapBlockInput, context: TemplateContext): Layer[
113
115
  text: caption,
114
116
  style: {
115
117
  fontSize: captionFontSize,
118
+ fontFamily: getThemeFont(context, 'body'),
116
119
  color: theme.colors.text,
117
120
  textAlign: 'center',
118
121
  shadow: true,
@@ -18,6 +18,7 @@
18
18
  import type { Layer } from '../../schemas/Doc.js';
19
19
  import type { PhotoGridInput, TemplateContext } from '../../schemas/BlockTemplates.js';
20
20
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
21
+ import { getThemeFont } from '../utils/themeUtils.js';
21
22
  import { cleanCaption } from './captionUtils.js';
22
23
 
23
24
  /** Gap between images as percentage */
@@ -72,6 +73,7 @@ export function photoGrid(input: PhotoGridInput, context: TemplateContext): Laye
72
73
  text: img.alt,
73
74
  style: {
74
75
  fontSize: altFontSize,
76
+ fontFamily: getThemeFont(context, 'body'),
75
77
  color: theme.colors.textMuted,
76
78
  textAlign: 'center' as const,
77
79
  },
@@ -126,6 +128,7 @@ export function photoGrid(input: PhotoGridInput, context: TemplateContext): Laye
126
128
  text: caption,
127
129
  style: {
128
130
  fontSize: captionFontSize,
131
+ fontFamily: getThemeFont(context, 'body'),
129
132
  color: theme.colors.text,
130
133
  textAlign: 'center',
131
134
  shadow: true,
@@ -11,6 +11,7 @@
11
11
  import type { Layer } from '../../schemas/Doc.js';
12
12
  import type { PullQuoteInput, TemplateContext } from '../../schemas/BlockTemplates.js';
13
13
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
14
+ import { getThemeFont } from '../utils/themeUtils.js';
14
15
 
15
16
  export function pullQuote(input: PullQuoteInput, context: TemplateContext): Layer[] {
16
17
  const { text, attribution, backgroundImage, ambientMotion } = input;
@@ -57,6 +58,7 @@ export function pullQuote(input: PullQuoteInput, context: TemplateContext): Laye
57
58
  text: '\u201C',
58
59
  style: {
59
60
  fontSize: decoFontSize,
61
+ fontFamily: getThemeFont(context, 'title'),
60
62
  color: 'rgba(255, 255, 255, 0.08)',
61
63
  textAlign: 'center',
62
64
  },
@@ -76,6 +78,7 @@ export function pullQuote(input: PullQuoteInput, context: TemplateContext): Laye
76
78
  text,
77
79
  style: {
78
80
  fontSize: quoteFontSize,
81
+ fontFamily: getThemeFont(context, 'title'),
79
82
  color: '#ffffff',
80
83
  textAlign: 'center',
81
84
  lineHeight: 1.5,
@@ -101,6 +104,7 @@ export function pullQuote(input: PullQuoteInput, context: TemplateContext): Laye
101
104
  text: `\u2014 ${attribution}`,
102
105
  style: {
103
106
  fontSize: attrFontSize,
107
+ fontFamily: getThemeFont(context, 'body'),
104
108
  color: 'rgba(255, 255, 255, 0.7)',
105
109
  textAlign: 'center',
106
110
  shadow: true,
@@ -13,6 +13,7 @@
13
13
  import type { Layer } from '../../schemas/Doc.js';
14
14
  import type { QuoteBlockInput, TemplateContext } from '../../schemas/BlockTemplates.js';
15
15
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
16
+ import { getThemeFont } from '../utils/themeUtils.js';
16
17
  import { createAccentLayers, getAccentLayout, adjustY, DEFAULT_LAYOUT } from './accentImage.js';
17
18
  import { createBackgroundLayer } from './captionUtils.js';
18
19
 
@@ -50,6 +51,7 @@ export function quoteBlock(input: QuoteBlockInput, context: TemplateContext): La
50
51
  text: '\u201C',
51
52
  style: {
52
53
  fontSize: decorativeQuoteFontSize,
54
+ fontFamily: getThemeFont(context, 'title'),
53
55
  color: 'rgba(255, 255, 255, 0.06)',
54
56
  textAlign: 'center',
55
57
  },
@@ -70,6 +72,7 @@ export function quoteBlock(input: QuoteBlockInput, context: TemplateContext): La
70
72
  text: quote,
71
73
  style: {
72
74
  fontSize: quoteFontSize,
75
+ fontFamily: getThemeFont(context, 'title'),
73
76
  color: theme.colors.text,
74
77
  textAlign: 'center',
75
78
  lineHeight: 1.6,
@@ -94,6 +97,7 @@ export function quoteBlock(input: QuoteBlockInput, context: TemplateContext): La
94
97
  text: `— ${attribution}`,
95
98
  style: {
96
99
  fontSize: attrFontSize,
100
+ fontFamily: getThemeFont(context, 'body'),
97
101
  color: theme.colors.textMuted,
98
102
  textAlign: 'center',
99
103
  shadow: !!accentImage,
@@ -12,10 +12,10 @@
12
12
  import type { Layer, AnimationType } from '../../schemas/Doc.js';
13
13
  import type { SectionHeaderInput, TemplateContext } from '../../schemas/BlockTemplates.js';
14
14
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
15
- import { resolveColorScheme } from '../utils/themeUtils.js';
15
+ import { resolveColorScheme, getThemeFont } from '../utils/themeUtils.js';
16
16
 
17
17
  export function sectionHeader(input: SectionHeaderInput, context: TemplateContext): Layer[] {
18
- const { title, colorScheme = 'blue', imageSrc, imageAlt, ambientMotion } = input;
18
+ const { title = '', colorScheme = 'blue', imageSrc, imageAlt, ambientMotion } = input;
19
19
  const { layout } = context;
20
20
  const colors = resolveColorScheme(context, colorScheme);
21
21
 
@@ -86,6 +86,7 @@ export function sectionHeader(input: SectionHeaderInput, context: TemplateContex
86
86
  text: title,
87
87
  style: {
88
88
  fontSize: titleFontSize,
89
+ fontFamily: getThemeFont(context, 'title'),
89
90
  fontWeight: 'bold',
90
91
  color: imageSrc ? '#ffffff' : colors.text,
91
92
  textAlign: 'center',
@@ -13,7 +13,7 @@
13
13
  import type { Layer } from '../../schemas/Doc.js';
14
14
  import type { StatHighlightInput, TemplateContext } from '../../schemas/BlockTemplates.js';
15
15
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
16
- import { resolveColorScheme } from '../utils/themeUtils.js';
16
+ import { resolveColorScheme, getThemeFont } from '../utils/themeUtils.js';
17
17
  import { createAccentLayers, getAccentLayout, adjustY, DEFAULT_LAYOUT } from './accentImage.js';
18
18
  import { createBackgroundLayer } from './captionUtils.js';
19
19
 
@@ -50,6 +50,7 @@ export function statHighlight(input: StatHighlightInput, context: TemplateContex
50
50
  text: stat,
51
51
  style: {
52
52
  fontSize: statFontSize,
53
+ fontFamily: getThemeFont(context, 'title'),
53
54
  fontWeight: 'bold',
54
55
  color: colors.text,
55
56
  shadow: !!accentImage,
@@ -71,6 +72,7 @@ export function statHighlight(input: StatHighlightInput, context: TemplateContex
71
72
  text: description,
72
73
  style: {
73
74
  fontSize: descFontSize,
75
+ fontFamily: getThemeFont(context, 'body'),
74
76
  color: theme.colors.textMuted,
75
77
  textAlign: 'center',
76
78
  lineHeight: 1.6,
@@ -95,6 +97,7 @@ export function statHighlight(input: StatHighlightInput, context: TemplateContex
95
97
  text: detail,
96
98
  style: {
97
99
  fontSize: detailFontSize,
100
+ fontFamily: getThemeFont(context, 'body'),
98
101
  color: colors.accent,
99
102
  textAlign: 'center',
100
103
  shadow: !!accentImage,
@@ -11,6 +11,7 @@
11
11
  import type { Layer } from '../../schemas/Doc.js';
12
12
  import type { TitleBlockInput, TemplateContext } from '../../schemas/BlockTemplates.js';
13
13
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
14
+ import { getThemeFont } from '../utils/themeUtils.js';
14
15
 
15
16
  export function titleBlock(input: TitleBlockInput, context: TemplateContext): Layer[] {
16
17
  const { title, subtitle, backgroundColor } = input;
@@ -56,6 +57,7 @@ export function titleBlock(input: TitleBlockInput, context: TemplateContext): La
56
57
  text: title,
57
58
  style: {
58
59
  fontSize: titleFontSize,
60
+ fontFamily: getThemeFont(context, 'title'),
59
61
  fontWeight: 'bold',
60
62
  color: theme.colors.text,
61
63
  textAlign: 'center',
@@ -81,6 +83,7 @@ export function titleBlock(input: TitleBlockInput, context: TemplateContext): La
81
83
  text: subtitle,
82
84
  style: {
83
85
  fontSize: subtitleFontSize,
86
+ fontFamily: getThemeFont(context, 'body'),
84
87
  color: theme.colors.textMuted,
85
88
  textAlign: 'center',
86
89
  lineHeight: 1.5,
@@ -13,7 +13,7 @@
13
13
  import type { Layer } from '../../schemas/Doc.js';
14
14
  import type { TwoColumnInput, TemplateContext } from '../../schemas/BlockTemplates.js';
15
15
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
16
- import { resolveColorScheme } from '../utils/themeUtils.js';
16
+ import { resolveColorScheme, getThemeFont } from '../utils/themeUtils.js';
17
17
  import { getTwoColumnPositions } from '../../schemas/LayoutStrategy.js';
18
18
 
19
19
  export function twoColumn(input: TwoColumnInput, context: TemplateContext): Layer[] {
@@ -87,6 +87,7 @@ export function twoColumn(input: TwoColumnInput, context: TemplateContext): Laye
87
87
  text: header,
88
88
  style: {
89
89
  fontSize: headerFontSize,
90
+ fontFamily: getThemeFont(context, 'title'),
90
91
  color: theme.colors.textMuted,
91
92
  textAlign: 'center',
92
93
  },
@@ -126,6 +127,7 @@ export function twoColumn(input: TwoColumnInput, context: TemplateContext): Laye
126
127
  text: left.label,
127
128
  style: {
128
129
  fontSize: labelFontSize,
130
+ fontFamily: getThemeFont(context, 'body'),
129
131
  fontWeight: 'bold',
130
132
  color: leftColors.text,
131
133
  textAlign: 'center',
@@ -149,6 +151,7 @@ export function twoColumn(input: TwoColumnInput, context: TemplateContext): Laye
149
151
  text: left.sublabel,
150
152
  style: {
151
153
  fontSize: sublabelFontSize,
154
+ fontFamily: getThemeFont(context, 'body'),
152
155
  color: theme.colors.textMuted,
153
156
  textAlign: 'center',
154
157
  lineHeight: 1.4,
@@ -176,6 +179,7 @@ export function twoColumn(input: TwoColumnInput, context: TemplateContext): Laye
176
179
  text: connectorSymbol,
177
180
  style: {
178
181
  fontSize: connectorFontSize,
182
+ fontFamily: getThemeFont(context, 'title'),
179
183
  fontWeight: 'bold',
180
184
  color: 'rgba(255, 255, 255, 0.6)',
181
185
  textAlign: 'center',
@@ -197,6 +201,7 @@ export function twoColumn(input: TwoColumnInput, context: TemplateContext): Laye
197
201
  text: right.label,
198
202
  style: {
199
203
  fontSize: labelFontSize,
204
+ fontFamily: getThemeFont(context, 'body'),
200
205
  fontWeight: 'bold',
201
206
  color: rightColors.text,
202
207
  textAlign: 'center',
@@ -220,6 +225,7 @@ export function twoColumn(input: TwoColumnInput, context: TemplateContext): Laye
220
225
  text: right.sublabel,
221
226
  style: {
222
227
  fontSize: sublabelFontSize,
228
+ fontFamily: getThemeFont(context, 'body'),
223
229
  color: theme.colors.textMuted,
224
230
  textAlign: 'center',
225
231
  lineHeight: 1.4,
@@ -18,6 +18,7 @@
18
18
  import type { Layer } from '../../schemas/Doc.js';
19
19
  import type { VideoPullQuoteInput, TemplateContext } from '../../schemas/BlockTemplates.js';
20
20
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
21
+ import { getThemeFont } from '../utils/themeUtils.js';
21
22
 
22
23
  export function videoPullQuote(input: VideoPullQuoteInput, context: TemplateContext): Layer[] {
23
24
  const { text, attribution, backgroundVideo } = input;
@@ -66,6 +67,7 @@ export function videoPullQuote(input: VideoPullQuoteInput, context: TemplateCont
66
67
  text: '\u201C',
67
68
  style: {
68
69
  fontSize: decoFontSize,
70
+ fontFamily: getThemeFont(context, 'title'),
69
71
  color: 'rgba(255, 255, 255, 0.08)',
70
72
  textAlign: 'center',
71
73
  },
@@ -85,6 +87,7 @@ export function videoPullQuote(input: VideoPullQuoteInput, context: TemplateCont
85
87
  text,
86
88
  style: {
87
89
  fontSize: quoteFontSize,
90
+ fontFamily: getThemeFont(context, 'title'),
88
91
  color: '#ffffff',
89
92
  textAlign: 'center',
90
93
  lineHeight: 1.5,
@@ -110,6 +113,7 @@ export function videoPullQuote(input: VideoPullQuoteInput, context: TemplateCont
110
113
  text: `\u2014 ${attribution}`,
111
114
  style: {
112
115
  fontSize: attrFontSize,
116
+ fontFamily: getThemeFont(context, 'body'),
113
117
  color: 'rgba(255, 255, 255, 0.7)',
114
118
  textAlign: 'center',
115
119
  shadow: true,
@@ -18,6 +18,7 @@
18
18
  import type { Layer } from '../../schemas/Doc.js';
19
19
  import type { VideoWithCaptionInput, TemplateContext } from '../../schemas/BlockTemplates.js';
20
20
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
21
+ import { getThemeFont } from '../utils/themeUtils.js';
21
22
  import { cleanCaption } from './captionUtils.js';
22
23
 
23
24
  export function videoWithCaption(input: VideoWithCaptionInput, context: TemplateContext): Layer[] {
@@ -86,6 +87,7 @@ export function videoWithCaption(input: VideoWithCaptionInput, context: Template
86
87
  text: caption,
87
88
  style: {
88
89
  fontSize: captionFontSize,
90
+ fontFamily: getThemeFont(context, 'body'),
89
91
  color: theme.colors.text,
90
92
  textAlign: 'center',
91
93
  shadow: true,
@@ -110,6 +112,7 @@ export function videoWithCaption(input: VideoWithCaptionInput, context: Template
110
112
  text: videoCredit,
111
113
  style: {
112
114
  fontSize: creditFontSize,
115
+ fontFamily: getThemeFont(context, 'body'),
113
116
  color: 'rgba(255, 255, 255, 0.5)',
114
117
  textAlign: 'right',
115
118
  },
@@ -37,6 +37,35 @@ export function resolveColorScheme(
37
37
  return FALLBACK_COLOR_SCHEME;
38
38
  }
39
39
 
40
+ // ============================================
41
+ // Font Family Resolution
42
+ // ============================================
43
+
44
+ /** Safe system font fallbacks. */
45
+ const FALLBACK_BODY = 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif';
46
+ const FALLBACK_TITLE = 'Georgia, "Times New Roman", serif';
47
+ const FALLBACK_MONO = 'Consolas, "Courier New", monospace';
48
+
49
+ /**
50
+ * Get the theme font family for a given role.
51
+ * Templates call this to set `fontFamily` on text layers.
52
+ *
53
+ * @param context Template context (includes theme with typography settings)
54
+ * @param role 'title' for headings, 'body' for descriptions, 'mono' for code
55
+ * @returns A CSS font-family string with the theme's font and safe fallbacks
56
+ */
57
+ export function getThemeFont(context: TemplateContext, role: 'title' | 'body' | 'mono'): string {
58
+ const { typography } = context.theme;
59
+ switch (role) {
60
+ case 'title':
61
+ return typography.titleFontFamily || FALLBACK_TITLE;
62
+ case 'body':
63
+ return typography.bodyFontFamily || FALLBACK_BODY;
64
+ case 'mono':
65
+ return typography.monoFontFamily || FALLBACK_MONO;
66
+ }
67
+ }
68
+
40
69
  // ============================================
41
70
  // Font Sizing
42
71
  // ============================================
@@ -181,17 +181,25 @@ function extractTemplateAnnotation(
181
181
  */
182
182
  function parseAnnotationTokens(inner: string): HeadingTemplateAnnotation {
183
183
  const tokens = inner.split(/\s+/);
184
- const template = tokens[0];
185
184
  const params: Record<string, string> = {};
186
185
 
187
- for (let i = 1; i < tokens.length; i++) {
186
+ // If the first token contains '=', there's no template name
187
+ // the annotation is purely key-value (e.g., `{[audio=intro.mp3]}`).
188
+ const firstIsParam = tokens[0].indexOf('=') > 0;
189
+ const template = firstIsParam ? undefined : tokens[0];
190
+ const startIdx = firstIsParam ? 0 : 1;
191
+
192
+ for (let i = startIdx; i < tokens.length; i++) {
188
193
  const eqIdx = tokens[i].indexOf('=');
189
194
  if (eqIdx > 0) {
190
195
  params[tokens[i].slice(0, eqIdx)] = tokens[i].slice(eqIdx + 1);
191
196
  }
192
197
  }
193
198
 
194
- const result: HeadingTemplateAnnotation = { template };
199
+ const result: HeadingTemplateAnnotation = {};
200
+ if (template) {
201
+ result.template = template;
202
+ }
195
203
  if (Object.keys(params).length > 0) {
196
204
  result.params = params;
197
205
  }
@@ -202,14 +210,16 @@ function parseAnnotationTokens(inner: string): HeadingTemplateAnnotation {
202
210
  * Serialize a HeadingTemplateAnnotation back to `{[templateName key=value …]}` text.
203
211
  */
204
212
  function serializeTemplateAnnotation(annotation: HeadingTemplateAnnotation): string {
205
- let result = `{[${annotation.template}`;
213
+ const parts: string[] = [];
214
+ if (annotation.template) {
215
+ parts.push(annotation.template);
216
+ }
206
217
  if (annotation.params) {
207
218
  for (const [key, value] of Object.entries(annotation.params)) {
208
- result += ` ${key}=${value}`;
219
+ parts.push(`${key}=${value}`);
209
220
  }
210
221
  }
211
- result += ']}';
212
- return result;
222
+ return `{[${parts.join(' ')}]}`;
213
223
  }
214
224
 
215
225
  /**
@@ -118,8 +118,10 @@ interface MarkdownNodeBase {
118
118
  * → `{ template: 'chart', params: { colorScheme: 'blue' } }`
119
119
  */
120
120
  export interface HeadingTemplateAnnotation {
121
- /** Template name (first token inside `{[…]}`) */
122
- template: string;
121
+ /** Template name (first token inside `{[…]}`).
122
+ * Optional when the annotation contains only key=value pairs
123
+ * (e.g., `{[audio=intro.mp3]}`). */
124
+ template?: string;
123
125
  /** Optional key-value parameters (remaining `key=value` tokens) */
124
126
  params?: Record<string, string>;
125
127
  }
@@ -39,12 +39,12 @@ export type ColorScheme = string;
39
39
  * PT Serif provides a classic, readable appearance for documentary-style content.
40
40
  * Used as a fallback in TextLayer when no font is specified.
41
41
  */
42
- export const DEFAULT_DOC_FONT = '"PT Serif", Georgia, "Times New Roman", serif';
42
+ export const DEFAULT_DOC_FONT = 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif';
43
43
 
44
44
  /**
45
45
  * Default font family for titles.
46
46
  */
47
- export const DEFAULT_TITLE_FONT = '"PT Serif", Georgia, "Times New Roman", serif';
47
+ export const DEFAULT_TITLE_FONT = 'Georgia, "Times New Roman", serif';
48
48
 
49
49
  // ============================================
50
50
  // Accent Image System
@@ -127,6 +127,13 @@ export interface Block {
127
127
  /** Template name that generated this block (for debugging) */
128
128
  template?: string;
129
129
 
130
+ /**
131
+ * Display title for template rendering.
132
+ * Extracted from the sourceHeading when the block is created by markdownToDoc().
133
+ * Templates like sectionHeader and titleBlock read this for their title layer.
134
+ */
135
+ title?: string;
136
+
130
137
  // ── Markdown-driven hierarchy (optional) ──
131
138
 
132
139
  /**
@@ -436,6 +443,19 @@ export interface AudioTrack {
436
443
  // Captions
437
444
  // ============================================
438
445
 
446
+ /**
447
+ * A single word with precise timing, used for word-level highlighting
448
+ * in social-style captions. Populated from TTS timing data when available.
449
+ */
450
+ export interface CaptionWord {
451
+ /** The word text */
452
+ text: string;
453
+ /** Start time in seconds (relative to doc start) */
454
+ startTime: number;
455
+ /** End time in seconds */
456
+ endTime: number;
457
+ }
458
+
439
459
  /**
440
460
  * A single caption phrase to display during playback.
441
461
  */
@@ -448,6 +468,13 @@ export interface CaptionPhrase {
448
468
  endTime: number;
449
469
  /** Which audio segment this caption belongs to (0-indexed) */
450
470
  audioSegment: number;
471
+ /**
472
+ * Optional per-word timing from TTS timing data.
473
+ * When present, enables precise word-level highlighting in social
474
+ * caption style. When absent, word timing is interpolated evenly
475
+ * across the phrase duration.
476
+ */
477
+ words?: CaptionWord[];
451
478
  }
452
479
 
453
480
  /**
@@ -476,6 +503,39 @@ export interface AudioSegment {
476
503
  startTime: number;
477
504
  }
478
505
 
506
+ // ============================================
507
+ // Audio Timing Data (from TTS)
508
+ // ============================================
509
+
510
+ /**
511
+ * A single word-level timing bookmark from TTS synthesis.
512
+ * Used for precise audio-to-content mapping and word-level caption sync.
513
+ */
514
+ export interface AudioBookmark {
515
+ /** Unique identifier (e.g., "word-0", "word-1") */
516
+ id: string;
517
+ /** Timestamp in seconds from audio start */
518
+ time: number;
519
+ /** Character offset in the source text */
520
+ charOffset: number;
521
+ /** The word text at this position */
522
+ textFragment?: string;
523
+ }
524
+
525
+ /**
526
+ * Timing data for an audio segment, typically from a `.timing.json` file
527
+ * generated alongside TTS audio. Contains the source text and per-word
528
+ * timing bookmarks for precise content matching and caption sync.
529
+ */
530
+ export interface AudioTimingData {
531
+ /** The normalized plain text that was synthesized */
532
+ sourceText: string;
533
+ /** Word-level timing bookmarks */
534
+ bookmarks: AudioBookmark[];
535
+ /** Total audio duration in seconds */
536
+ duration: number;
537
+ }
538
+
479
539
  // ============================================
480
540
  // Helper Functions
481
541
  // ============================================