@bendyline/squisq 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/dist/ContentContainer-DNx460y8.d.ts +88 -0
  2. package/dist/{Doc-1hmT1LZl.d.ts → Doc-Dwn5aHFC.d.ts} +115 -6
  3. package/dist/__tests__/dataTable.test.d.ts +2 -0
  4. package/dist/__tests__/dataTable.test.d.ts.map +1 -0
  5. package/dist/__tests__/dataTable.test.js +110 -0
  6. package/dist/__tests__/dataTable.test.js.map +1 -0
  7. package/dist/__tests__/markdownToDoc.test.js +75 -0
  8. package/dist/__tests__/markdownToDoc.test.js.map +1 -1
  9. package/dist/{chunk-6YJPI6VI.js → chunk-4X3JQXNM.js} +1 -1
  10. package/dist/chunk-DNJKBAI7.js +1 -0
  11. package/dist/chunk-DNJKBAI7.js.map +1 -0
  12. package/dist/{chunk-S4F2RY37.js → chunk-KEFWM2VC.js} +1 -1
  13. package/dist/chunk-KEFWM2VC.js.map +1 -0
  14. package/dist/{chunk-VP5SZTZ2.js → chunk-MIBRIRGJ.js} +2 -139
  15. package/dist/chunk-MIBRIRGJ.js.map +1 -0
  16. package/dist/{chunk-URU6QMLY.js → chunk-NJLL5XYC.js} +15 -8
  17. package/dist/chunk-NJLL5XYC.js.map +1 -0
  18. package/dist/chunk-QM5PKNPW.js +165 -0
  19. package/dist/chunk-QM5PKNPW.js.map +1 -0
  20. package/dist/{chunk-PJ7AODIM.js → chunk-T6WBC3ND.js} +459 -53
  21. package/dist/chunk-T6WBC3ND.js.map +1 -0
  22. package/dist/chunk-TSG2OSE6.js +145 -0
  23. package/dist/chunk-TSG2OSE6.js.map +1 -0
  24. package/dist/{chunk-O5HCIUAD.js → chunk-TVPKD76V.js} +81 -187
  25. package/dist/chunk-TVPKD76V.js.map +1 -0
  26. package/dist/contentExtractor-BNfVJV2U.d.ts +113 -0
  27. package/dist/doc/audioMapping.d.ts +36 -0
  28. package/dist/doc/audioMapping.d.ts.map +1 -0
  29. package/dist/doc/audioMapping.js +279 -0
  30. package/dist/doc/audioMapping.js.map +1 -0
  31. package/dist/doc/index.d.ts +1 -0
  32. package/dist/doc/index.d.ts.map +1 -1
  33. package/dist/doc/index.js +1 -0
  34. package/dist/doc/index.js.map +1 -1
  35. package/dist/doc/markdownToDoc.d.ts.map +1 -1
  36. package/dist/doc/markdownToDoc.js +126 -1
  37. package/dist/doc/markdownToDoc.js.map +1 -1
  38. package/dist/doc/templates/comparisonBar.d.ts.map +1 -1
  39. package/dist/doc/templates/comparisonBar.js +23 -5
  40. package/dist/doc/templates/comparisonBar.js.map +1 -1
  41. package/dist/doc/templates/coverBlock.d.ts.map +1 -1
  42. package/dist/doc/templates/coverBlock.js +3 -0
  43. package/dist/doc/templates/coverBlock.js.map +1 -1
  44. package/dist/doc/templates/dataTable.d.ts +14 -0
  45. package/dist/doc/templates/dataTable.d.ts.map +1 -0
  46. package/dist/doc/templates/dataTable.js +73 -0
  47. package/dist/doc/templates/dataTable.js.map +1 -0
  48. package/dist/doc/templates/dateEvent.d.ts.map +1 -1
  49. package/dist/doc/templates/dateEvent.js +4 -0
  50. package/dist/doc/templates/dateEvent.js.map +1 -1
  51. package/dist/doc/templates/definitionCard.d.ts.map +1 -1
  52. package/dist/doc/templates/definitionCard.js +4 -1
  53. package/dist/doc/templates/definitionCard.js.map +1 -1
  54. package/dist/doc/templates/factCard.d.ts.map +1 -1
  55. package/dist/doc/templates/factCard.js +4 -0
  56. package/dist/doc/templates/factCard.js.map +1 -1
  57. package/dist/doc/templates/fullBleedQuote.d.ts.map +1 -1
  58. package/dist/doc/templates/fullBleedQuote.js +2 -1
  59. package/dist/doc/templates/fullBleedQuote.js.map +1 -1
  60. package/dist/doc/templates/imageWithCaption.d.ts.map +1 -1
  61. package/dist/doc/templates/imageWithCaption.js +4 -0
  62. package/dist/doc/templates/imageWithCaption.js.map +1 -1
  63. package/dist/doc/templates/index.d.ts +1 -0
  64. package/dist/doc/templates/index.d.ts.map +1 -1
  65. package/dist/doc/templates/index.js +3 -0
  66. package/dist/doc/templates/index.js.map +1 -1
  67. package/dist/doc/templates/listBlock.d.ts.map +1 -1
  68. package/dist/doc/templates/listBlock.js +3 -0
  69. package/dist/doc/templates/listBlock.js.map +1 -1
  70. package/dist/doc/templates/mapBlock.d.ts.map +1 -1
  71. package/dist/doc/templates/mapBlock.js +3 -0
  72. package/dist/doc/templates/mapBlock.js.map +1 -1
  73. package/dist/doc/templates/photoGrid.d.ts.map +1 -1
  74. package/dist/doc/templates/photoGrid.js +3 -0
  75. package/dist/doc/templates/photoGrid.js.map +1 -1
  76. package/dist/doc/templates/pullQuote.d.ts.map +1 -1
  77. package/dist/doc/templates/pullQuote.js +4 -0
  78. package/dist/doc/templates/pullQuote.js.map +1 -1
  79. package/dist/doc/templates/quoteBlock.d.ts.map +1 -1
  80. package/dist/doc/templates/quoteBlock.js +4 -0
  81. package/dist/doc/templates/quoteBlock.js.map +1 -1
  82. package/dist/doc/templates/sectionHeader.d.ts.map +1 -1
  83. package/dist/doc/templates/sectionHeader.js +3 -2
  84. package/dist/doc/templates/sectionHeader.js.map +1 -1
  85. package/dist/doc/templates/statHighlight.d.ts.map +1 -1
  86. package/dist/doc/templates/statHighlight.js +4 -1
  87. package/dist/doc/templates/statHighlight.js.map +1 -1
  88. package/dist/doc/templates/titleBlock.d.ts.map +1 -1
  89. package/dist/doc/templates/titleBlock.js +3 -0
  90. package/dist/doc/templates/titleBlock.js.map +1 -1
  91. package/dist/doc/templates/twoColumn.d.ts.map +1 -1
  92. package/dist/doc/templates/twoColumn.js +7 -1
  93. package/dist/doc/templates/twoColumn.js.map +1 -1
  94. package/dist/doc/templates/videoPullQuote.d.ts.map +1 -1
  95. package/dist/doc/templates/videoPullQuote.js +4 -0
  96. package/dist/doc/templates/videoPullQuote.js.map +1 -1
  97. package/dist/doc/templates/videoWithCaption.d.ts.map +1 -1
  98. package/dist/doc/templates/videoWithCaption.js +3 -0
  99. package/dist/doc/templates/videoWithCaption.js.map +1 -1
  100. package/dist/doc/utils/themeUtils.d.ts +9 -0
  101. package/dist/doc/utils/themeUtils.d.ts.map +1 -1
  102. package/dist/doc/utils/themeUtils.js +26 -0
  103. package/dist/doc/utils/themeUtils.js.map +1 -1
  104. package/dist/markdown/convert.d.ts.map +1 -1
  105. package/dist/markdown/convert.js +16 -7
  106. package/dist/markdown/convert.js.map +1 -1
  107. package/dist/markdown/types.d.ts +4 -2
  108. package/dist/markdown/types.d.ts.map +1 -1
  109. package/dist/schemas/BlockTemplates.d.ts +20 -3
  110. package/dist/schemas/BlockTemplates.d.ts.map +1 -1
  111. package/dist/schemas/BlockTemplates.js +2 -2
  112. package/dist/schemas/BlockTemplates.js.map +1 -1
  113. package/dist/schemas/Doc.d.ts +93 -1
  114. package/dist/schemas/Doc.d.ts.map +1 -1
  115. package/dist/schemas/Doc.js.map +1 -1
  116. package/dist/schemas/themeLibrary.d.ts +2 -2
  117. package/dist/schemas/themeLibrary.d.ts.map +1 -1
  118. package/dist/schemas/themeLibrary.js +91 -26
  119. package/dist/schemas/themeLibrary.js.map +1 -1
  120. package/dist/story/index.d.ts +63 -5
  121. package/dist/story/index.js +17 -5
  122. package/dist/{themeLibrary-DR1VRo-8.d.ts → themeLibrary-DLJtGh8-.d.ts} +3 -3
  123. package/dist/transform/applyTransform.d.ts +22 -0
  124. package/dist/transform/applyTransform.d.ts.map +1 -0
  125. package/dist/transform/applyTransform.js +68 -0
  126. package/dist/transform/applyTransform.js.map +1 -0
  127. package/dist/transform/blockAnalyzer.d.ts +48 -0
  128. package/dist/transform/blockAnalyzer.d.ts.map +1 -0
  129. package/dist/transform/blockAnalyzer.js +135 -0
  130. package/dist/transform/blockAnalyzer.js.map +1 -0
  131. package/dist/transform/index.d.ts +6 -0
  132. package/dist/transform/index.d.ts.map +1 -0
  133. package/dist/transform/index.js +4 -0
  134. package/dist/transform/index.js.map +1 -0
  135. package/dist/transform/registry.d.ts +19 -0
  136. package/dist/transform/registry.d.ts.map +1 -0
  137. package/dist/transform/registry.js +41 -0
  138. package/dist/transform/registry.js.map +1 -0
  139. package/dist/transform/styles/dataDriven.d.ts +4 -0
  140. package/dist/transform/styles/dataDriven.d.ts.map +1 -0
  141. package/dist/transform/styles/dataDriven.js +15 -0
  142. package/dist/transform/styles/dataDriven.js.map +1 -0
  143. package/dist/transform/styles/documentary.d.ts +4 -0
  144. package/dist/transform/styles/documentary.d.ts.map +1 -0
  145. package/dist/transform/styles/documentary.js +15 -0
  146. package/dist/transform/styles/documentary.js.map +1 -0
  147. package/dist/transform/styles/index.d.ts +6 -0
  148. package/dist/transform/styles/index.d.ts.map +1 -0
  149. package/dist/transform/styles/index.js +6 -0
  150. package/dist/transform/styles/index.js.map +1 -0
  151. package/dist/transform/styles/magazine.d.ts +4 -0
  152. package/dist/transform/styles/magazine.d.ts.map +1 -0
  153. package/dist/transform/styles/magazine.js +15 -0
  154. package/dist/transform/styles/magazine.js.map +1 -0
  155. package/dist/transform/styles/minimal.d.ts +4 -0
  156. package/dist/transform/styles/minimal.d.ts.map +1 -0
  157. package/dist/transform/styles/minimal.js +15 -0
  158. package/dist/transform/styles/minimal.js.map +1 -0
  159. package/dist/transform/styles/narrative.d.ts +4 -0
  160. package/dist/transform/styles/narrative.d.ts.map +1 -0
  161. package/dist/transform/styles/narrative.js +15 -0
  162. package/dist/transform/styles/narrative.js.map +1 -0
  163. package/dist/transform/templateSelector.d.ts +30 -0
  164. package/dist/transform/templateSelector.d.ts.map +1 -0
  165. package/dist/transform/templateSelector.js +218 -0
  166. package/dist/transform/templateSelector.js.map +1 -0
  167. package/dist/transform/timingAllocator.d.ts +21 -0
  168. package/dist/transform/timingAllocator.d.ts.map +1 -0
  169. package/dist/transform/timingAllocator.js +111 -0
  170. package/dist/transform/timingAllocator.js.map +1 -0
  171. package/dist/transform/types.d.ts +86 -0
  172. package/dist/transform/types.d.ts.map +1 -0
  173. package/dist/transform/types.js +9 -0
  174. package/dist/transform/types.js.map +1 -0
  175. package/dist/{types-DHiv_Pnm.d.ts → types-DYAzisMG.d.ts} +4 -2
  176. package/package.json +6 -1
  177. package/src/__tests__/dataTable.test.ts +135 -0
  178. package/src/__tests__/markdownToDoc.test.ts +96 -0
  179. package/src/doc/audioMapping.ts +346 -0
  180. package/src/doc/index.ts +1 -0
  181. package/src/doc/markdownToDoc.ts +136 -2
  182. package/src/doc/templates/comparisonBar.ts +23 -5
  183. package/src/doc/templates/coverBlock.ts +3 -0
  184. package/src/doc/templates/dataTable.ts +84 -0
  185. package/src/doc/templates/dateEvent.ts +4 -0
  186. package/src/doc/templates/definitionCard.ts +4 -1
  187. package/src/doc/templates/factCard.ts +4 -0
  188. package/src/doc/templates/fullBleedQuote.ts +2 -1
  189. package/src/doc/templates/imageWithCaption.ts +4 -0
  190. package/src/doc/templates/index.ts +3 -0
  191. package/src/doc/templates/listBlock.ts +3 -0
  192. package/src/doc/templates/mapBlock.ts +3 -0
  193. package/src/doc/templates/photoGrid.ts +3 -0
  194. package/src/doc/templates/pullQuote.ts +4 -0
  195. package/src/doc/templates/quoteBlock.ts +4 -0
  196. package/src/doc/templates/sectionHeader.ts +3 -2
  197. package/src/doc/templates/statHighlight.ts +4 -1
  198. package/src/doc/templates/titleBlock.ts +3 -0
  199. package/src/doc/templates/twoColumn.ts +7 -1
  200. package/src/doc/templates/videoPullQuote.ts +4 -0
  201. package/src/doc/templates/videoWithCaption.ts +3 -0
  202. package/src/doc/utils/themeUtils.ts +29 -0
  203. package/src/markdown/convert.ts +17 -7
  204. package/src/markdown/types.ts +4 -2
  205. package/src/schemas/BlockTemplates.ts +22 -3
  206. package/src/schemas/Doc.ts +103 -1
  207. package/src/schemas/themeLibrary.ts +95 -26
  208. package/src/transform/applyTransform.ts +83 -0
  209. package/src/transform/blockAnalyzer.ts +181 -0
  210. package/src/transform/index.ts +20 -0
  211. package/src/transform/registry.ts +47 -0
  212. package/src/transform/styles/dataDriven.ts +16 -0
  213. package/src/transform/styles/documentary.ts +16 -0
  214. package/src/transform/styles/index.ts +5 -0
  215. package/src/transform/styles/magazine.ts +16 -0
  216. package/src/transform/styles/minimal.ts +16 -0
  217. package/src/transform/styles/narrative.ts +16 -0
  218. package/src/transform/templateSelector.ts +294 -0
  219. package/src/transform/timingAllocator.ts +126 -0
  220. package/src/transform/types.ts +105 -0
  221. package/dist/chunk-O5HCIUAD.js.map +0 -1
  222. package/dist/chunk-PJ7AODIM.js.map +0 -1
  223. package/dist/chunk-S4F2RY37.js.map +0 -1
  224. package/dist/chunk-URU6QMLY.js.map +0 -1
  225. package/dist/chunk-VP5SZTZ2.js.map +0 -1
  226. /package/dist/{chunk-6YJPI6VI.js.map → chunk-4X3JQXNM.js.map} +0 -0
@@ -18,6 +18,7 @@ import type { Layer } from '../../schemas/Doc.js';
18
18
  import type { TemplateContext } from '../../schemas/BlockTemplates.js';
19
19
  import { scaledFontSize } from '../../schemas/BlockTemplates.js';
20
20
  import type { StartBlockConfig } from '../../schemas/Doc.js';
21
+ import { getThemeFont } from '../utils/themeUtils.js';
21
22
  import { mapAmbientMotion } from './accentImage.js';
22
23
 
23
24
  /**
@@ -87,6 +88,7 @@ export function coverBlock(input: CoverBlockInput, context: TemplateContext): La
87
88
  text: title,
88
89
  style: {
89
90
  fontSize: titleFontSize,
91
+ fontFamily: getThemeFont(context, 'title'),
90
92
  fontWeight: 'bold',
91
93
  color: theme.colors.text,
92
94
  textAlign: 'center',
@@ -113,6 +115,7 @@ export function coverBlock(input: CoverBlockInput, context: TemplateContext): La
113
115
  text: subtitle,
114
116
  style: {
115
117
  fontSize: subtitleFontSize,
118
+ fontFamily: getThemeFont(context, 'body'),
116
119
  color: theme.colors.textMuted,
117
120
  textAlign: 'center',
118
121
  lineHeight: 1.5,
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Data Table Template
3
+ *
4
+ * Renders a themed table with header row and data rows.
5
+ * Uses a TableLayer (foreignObject-based HTML table inside SVG)
6
+ * for proper table layout within the viewport.
7
+ *
8
+ * Adapts font sizes for different viewports and uses theme colors
9
+ * for header background, text, borders, and body cells.
10
+ */
11
+
12
+ import type { Layer } from '../../schemas/Doc.js';
13
+ import type { DataTableInput, TemplateContext } from '../../schemas/BlockTemplates.js';
14
+ import { scaledFontSize } from '../../schemas/BlockTemplates.js';
15
+ import { getThemeFont, resolveColorScheme } from '../utils/themeUtils.js';
16
+ import { createBackgroundLayer } from './captionUtils.js';
17
+
18
+ export function dataTable(input: DataTableInput, context: TemplateContext): Layer[] {
19
+ const { title, headers, rows, align, colorScheme } = input;
20
+ const { theme } = context;
21
+
22
+ const colors = resolveColorScheme(context, colorScheme);
23
+ const titleFontSize = scaledFontSize(48, context, true);
24
+ const tableFontSize = scaledFontSize(28, context, false);
25
+
26
+ const layers: Layer[] = [
27
+ createBackgroundLayer(
28
+ 'bg',
29
+ `linear-gradient(170deg, ${theme.colors.background} 0%, #0f1520 100%)`,
30
+ ),
31
+ ];
32
+
33
+ // Optional title above the table
34
+ if (title) {
35
+ layers.push({
36
+ type: 'text',
37
+ id: 'title',
38
+ content: {
39
+ text: title,
40
+ style: {
41
+ fontSize: titleFontSize,
42
+ fontFamily: getThemeFont(context, 'title'),
43
+ fontWeight: 'bold',
44
+ color: theme.colors.text,
45
+ textAlign: 'center',
46
+ shadow: true,
47
+ },
48
+ },
49
+ position: { x: '50%', y: '10%', width: '80%', anchor: 'center' },
50
+ animation: { type: 'fadeIn', duration: 0.8 },
51
+ });
52
+ }
53
+
54
+ // Table layer
55
+ layers.push({
56
+ type: 'table',
57
+ id: 'table',
58
+ content: {
59
+ headers,
60
+ rows,
61
+ align,
62
+ style: {
63
+ headerBackground: colors.accent,
64
+ headerColor: colors.text,
65
+ cellBackground: 'rgba(255,255,255,0.05)',
66
+ cellColor: theme.colors.text,
67
+ borderColor: 'rgba(255,255,255,0.12)',
68
+ fontSize: tableFontSize,
69
+ fontFamily: getThemeFont(context, 'body'),
70
+ headerFontFamily: getThemeFont(context, 'title'),
71
+ borderRadius: 8,
72
+ },
73
+ },
74
+ position: {
75
+ x: '10%',
76
+ y: title ? '18%' : '8%',
77
+ width: '80%',
78
+ height: title ? '74%' : '84%',
79
+ },
80
+ animation: { type: 'fadeIn', duration: 1, delay: title ? 0.4 : 0 },
81
+ });
82
+
83
+ return layers;
84
+ }
@@ -13,6 +13,7 @@
13
13
  import type { Layer } from '../../schemas/Doc.js';
14
14
  import type { DateEventInput, 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
 
18
19
  const MOOD_COLORS = {
@@ -60,6 +61,7 @@ export function dateEvent(input: DateEventInput, context: TemplateContext): Laye
60
61
  text: date,
61
62
  style: {
62
63
  fontSize: dateFontSize,
64
+ fontFamily: getThemeFont(context, 'title'),
63
65
  fontWeight: 'bold',
64
66
  color: colors.date,
65
67
  shadow: !!accentImage,
@@ -81,6 +83,7 @@ export function dateEvent(input: DateEventInput, context: TemplateContext): Laye
81
83
  text: description,
82
84
  style: {
83
85
  fontSize: descFontSize,
86
+ fontFamily: getThemeFont(context, 'body'),
84
87
  color: colors.text,
85
88
  textAlign: 'center',
86
89
  lineHeight: 1.8,
@@ -105,6 +108,7 @@ export function dateEvent(input: DateEventInput, context: TemplateContext): Laye
105
108
  text: footer,
106
109
  style: {
107
110
  fontSize: footerFontSize,
111
+ fontFamily: getThemeFont(context, 'body'),
108
112
  color: theme.colors.textMuted,
109
113
  shadow: !!accentImage,
110
114
  },
@@ -11,7 +11,7 @@
11
11
  import type { Layer } from '../../schemas/Doc.js';
12
12
  import type { DefinitionCardInput, 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
  import { createAccentLayers, getAccentLayout, adjustY, DEFAULT_LAYOUT } from './accentImage.js';
16
16
  import { createBackgroundLayer } from './captionUtils.js';
17
17
 
@@ -47,6 +47,7 @@ export function definitionCard(input: DefinitionCardInput, context: TemplateCont
47
47
  text: term,
48
48
  style: {
49
49
  fontSize: termFontSize,
50
+ fontFamily: getThemeFont(context, 'title'),
50
51
  fontWeight: 'bold',
51
52
  color: colors.text,
52
53
  textAlign: 'center',
@@ -86,6 +87,7 @@ export function definitionCard(input: DefinitionCardInput, context: TemplateCont
86
87
  text: definition,
87
88
  style: {
88
89
  fontSize: defFontSize,
90
+ fontFamily: getThemeFont(context, 'body'),
89
91
  color: theme.colors.text,
90
92
  textAlign: 'center',
91
93
  lineHeight: 1.6,
@@ -111,6 +113,7 @@ export function definitionCard(input: DefinitionCardInput, context: TemplateCont
111
113
  text: origin,
112
114
  style: {
113
115
  fontSize: originFontSize,
116
+ fontFamily: getThemeFont(context, 'body'),
114
117
  color: theme.colors.textMuted,
115
118
  textAlign: 'center',
116
119
  shadow: !!accentImage,
@@ -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,
@@ -44,6 +44,7 @@ import { comparisonBar } from './comparisonBar.js';
44
44
  import { pullQuote } from './pullQuote.js';
45
45
  import { videoWithCaption } from './videoWithCaption.js';
46
46
  import { videoPullQuote } from './videoPullQuote.js';
47
+ import { dataTable } from './dataTable.js';
47
48
 
48
49
  /**
49
50
  * Registry mapping template names to their implementation functions.
@@ -68,6 +69,7 @@ export const templateRegistry: TemplateRegistry = {
68
69
  pullQuote,
69
70
  videoWithCaption,
70
71
  videoPullQuote,
72
+ dataTable,
71
73
  };
72
74
 
73
75
  /**
@@ -565,6 +567,7 @@ export { comparisonBar } from './comparisonBar.js';
565
567
  export { pullQuote } from './pullQuote.js';
566
568
  export { videoWithCaption } from './videoWithCaption.js';
567
569
  export { videoPullQuote } from './videoPullQuote.js';
570
+ export { dataTable } from './dataTable.js';
568
571
 
569
572
  // Re-export accent image utilities
570
573
  export { getAccentLayout, createAccentLayers, adjustY, DEFAULT_LAYOUT } from './accentImage.js';
@@ -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
  // ============================================