@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
@@ -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
@@ -429,6 +429,24 @@ export interface VideoPullQuoteInput extends BaseTemplateBlock {
429
429
  };
430
430
  }
431
431
 
432
+ /**
433
+ * Data table - renders a themed table with headers and rows.
434
+ * Ideal for structured data, comparisons, and reference information.
435
+ */
436
+ export interface DataTableInput extends BaseTemplateBlock {
437
+ template: 'dataTable';
438
+ /** Optional title displayed above the table */
439
+ title?: string;
440
+ /** Header cell values */
441
+ headers: string[];
442
+ /** Data rows (array of cell value arrays) */
443
+ rows: string[][];
444
+ /** Per-column alignment */
445
+ align?: (('left' | 'right' | 'center') | null)[];
446
+ /** Color scheme for the table header */
447
+ colorScheme?: ColorScheme;
448
+ }
449
+
432
450
  /**
433
451
  * Union of all template block types.
434
452
  */
@@ -449,7 +467,8 @@ export type TemplateBlock =
449
467
  | ComparisonBarInput
450
468
  | PullQuoteInput
451
469
  | VideoWithCaptionInput
452
- | VideoPullQuoteInput;
470
+ | VideoPullQuoteInput
471
+ | DataTableInput;
453
472
 
454
473
  /**
455
474
  * A block can be either a raw Block or a TemplateBlock.
@@ -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
  /**
@@ -170,7 +177,7 @@ export interface Block {
170
177
  * A visual element within a block.
171
178
  * Layers are composited back-to-front (first layer is background).
172
179
  */
173
- export type Layer = ImageLayer | TextLayer | ShapeLayer | MapLayer | VideoLayer;
180
+ export type Layer = ImageLayer | TextLayer | ShapeLayer | MapLayer | VideoLayer | TableLayer;
174
181
 
175
182
  interface BaseLayer {
176
183
  /** Unique identifier for this layer */
@@ -293,6 +300,48 @@ export interface VideoLayer extends BaseLayer {
293
300
  };
294
301
  }
295
302
 
303
+ /**
304
+ * Table layer - renders a data table with themed styling.
305
+ * Uses foreignObject inside SVG for HTML-based table layout.
306
+ */
307
+ export interface TableLayer extends BaseLayer {
308
+ type: 'table';
309
+ content: {
310
+ /** Header cell values */
311
+ headers: string[];
312
+ /** Data rows (array of cell value arrays) */
313
+ rows: string[][];
314
+ /** Per-column alignment */
315
+ align?: (('left' | 'right' | 'center') | null)[];
316
+ /** Visual styling */
317
+ style: TableLayerStyle;
318
+ };
319
+ }
320
+
321
+ /**
322
+ * Styling options for a TableLayer.
323
+ */
324
+ export interface TableLayerStyle {
325
+ /** Header row background color */
326
+ headerBackground: string;
327
+ /** Header row text color */
328
+ headerColor: string;
329
+ /** Body cell background color */
330
+ cellBackground: string;
331
+ /** Body cell text color */
332
+ cellColor: string;
333
+ /** Border/divider color */
334
+ borderColor: string;
335
+ /** Font size in pixels */
336
+ fontSize: number;
337
+ /** Font family */
338
+ fontFamily?: string;
339
+ /** Header font family (falls back to fontFamily) */
340
+ headerFontFamily?: string;
341
+ /** Corner radius for the table container */
342
+ borderRadius?: number;
343
+ }
344
+
296
345
  /**
297
346
  * Available map tile styles from free/open-source providers.
298
347
  */
@@ -436,6 +485,19 @@ export interface AudioTrack {
436
485
  // Captions
437
486
  // ============================================
438
487
 
488
+ /**
489
+ * A single word with precise timing, used for word-level highlighting
490
+ * in social-style captions. Populated from TTS timing data when available.
491
+ */
492
+ export interface CaptionWord {
493
+ /** The word text */
494
+ text: string;
495
+ /** Start time in seconds (relative to doc start) */
496
+ startTime: number;
497
+ /** End time in seconds */
498
+ endTime: number;
499
+ }
500
+
439
501
  /**
440
502
  * A single caption phrase to display during playback.
441
503
  */
@@ -448,6 +510,13 @@ export interface CaptionPhrase {
448
510
  endTime: number;
449
511
  /** Which audio segment this caption belongs to (0-indexed) */
450
512
  audioSegment: number;
513
+ /**
514
+ * Optional per-word timing from TTS timing data.
515
+ * When present, enables precise word-level highlighting in social
516
+ * caption style. When absent, word timing is interpolated evenly
517
+ * across the phrase duration.
518
+ */
519
+ words?: CaptionWord[];
451
520
  }
452
521
 
453
522
  /**
@@ -476,6 +545,39 @@ export interface AudioSegment {
476
545
  startTime: number;
477
546
  }
478
547
 
548
+ // ============================================
549
+ // Audio Timing Data (from TTS)
550
+ // ============================================
551
+
552
+ /**
553
+ * A single word-level timing bookmark from TTS synthesis.
554
+ * Used for precise audio-to-content mapping and word-level caption sync.
555
+ */
556
+ export interface AudioBookmark {
557
+ /** Unique identifier (e.g., "word-0", "word-1") */
558
+ id: string;
559
+ /** Timestamp in seconds from audio start */
560
+ time: number;
561
+ /** Character offset in the source text */
562
+ charOffset: number;
563
+ /** The word text at this position */
564
+ textFragment?: string;
565
+ }
566
+
567
+ /**
568
+ * Timing data for an audio segment, typically from a `.timing.json` file
569
+ * generated alongside TTS audio. Contains the source text and per-word
570
+ * timing bookmarks for precise content matching and caption sync.
571
+ */
572
+ export interface AudioTimingData {
573
+ /** The normalized plain text that was synthesized */
574
+ sourceText: string;
575
+ /** Word-level timing bookmarks */
576
+ bookmarks: AudioBookmark[];
577
+ /** Total audio duration in seconds */
578
+ duration: number;
579
+ }
580
+
479
581
  // ============================================
480
582
  // Helper Functions
481
583
  // ============================================
@@ -19,11 +19,32 @@ import type { Theme, ThemeColorScheme } from './Theme.js';
19
19
  // Shared constants
20
20
  // ============================================
21
21
 
22
- const SERIF = '"PT Serif", Georgia, "Times New Roman", serif';
23
- const SANS = '"Inter", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
24
- const MONO = '"JetBrains Mono", "Fira Code", "Consolas", monospace';
25
- const MERRIWEATHER = '"Merriweather", Georgia, "Times New Roman", serif';
26
- const GEORGIA = 'Georgia, "Times New Roman", serif';
22
+ // ── Font stacks ──────────────────────────────────────────────────
23
+ // Each stack lists the recommended Google Font first (works when the
24
+ // wrapping site loads it), then degrades to safe system fonts.
25
+ // Squisq itself does NOT load Google Fonts.
26
+
27
+ /** System fonts only — no external dependencies. */
28
+ const SYSTEM_SANS = 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif';
29
+ const SYSTEM_SERIF = 'Georgia, "Times New Roman", serif';
30
+ const SYSTEM_MONO = 'Consolas, "Courier New", monospace';
31
+
32
+ /** Theme-specific stacks with Google Font leads. */
33
+ const PLAYFAIR = '"Playfair Display", Georgia, serif';
34
+ const SOURCE_SERIF = '"Source Serif 4", "PT Serif", Georgia, serif';
35
+ const INTER = '"Inter", "Segoe UI", Roboto, sans-serif';
36
+ const OSWALD = '"Oswald", Impact, "Arial Black", sans-serif';
37
+ const ROBOTO = '"Roboto", "Segoe UI", Arial, sans-serif';
38
+ const MERRIWEATHER = '"Merriweather", Georgia, serif';
39
+ const LORA = '"Lora", "Merriweather", Georgia, serif';
40
+ const JETBRAINS_MONO = '"JetBrains Mono", "Fira Code", Consolas, monospace';
41
+ const IBM_PLEX_SANS = '"IBM Plex Sans", "Segoe UI", Roboto, sans-serif';
42
+ const DM_SERIF = '"DM Serif Display", Georgia, serif';
43
+ const DM_SANS = '"DM Sans", "Segoe UI", Roboto, sans-serif';
44
+ const CORMORANT = '"Cormorant Garamond", Garamond, Georgia, serif';
45
+ const CRIMSON = '"Crimson Text", Georgia, serif';
46
+ const PT_SERIF = '"PT Serif", Georgia, serif';
47
+ const HANKEN_GROTESK = '"Hanken Grotesk", system-ui, sans-serif';
27
48
 
28
49
  /** Standard 6-scheme set used by the documentary theme (migrated from old COLOR_SCHEMES). */
29
50
  const CLASSIC_COLOR_SCHEMES: Record<string, ThemeColorScheme> = {
@@ -36,13 +57,60 @@ const CLASSIC_COLOR_SCHEMES: Record<string, ThemeColorScheme> = {
36
57
  };
37
58
 
38
59
  // ============================================
39
- // 1. Documentary (default — migrated from old DEFAULT_THEME)
60
+ // 1. Standard (default — safe system fonts, no external dependencies)
61
+ // ============================================
62
+
63
+ const standard: Theme = {
64
+ id: 'standard',
65
+ name: 'Standard',
66
+ description: 'Clean and safe. System fonts only — no external font dependencies.',
67
+ colors: {
68
+ primary: '#3182ce',
69
+ secondary: '#4a5568',
70
+ background: '#1a202c',
71
+ backgroundLight: '#2d3748',
72
+ text: '#f7fafc',
73
+ textMuted: '#a0aec0',
74
+ highlight: '#4299e1',
75
+ warning: '#fc8181',
76
+ },
77
+ typography: {
78
+ bodyFontFamily: SYSTEM_SANS,
79
+ titleFontFamily: SYSTEM_SERIF,
80
+ monoFontFamily: SYSTEM_MONO,
81
+ titleWeight: 'bold',
82
+ },
83
+ style: {
84
+ textShadow: true,
85
+ overlayOpacity: 0.45,
86
+ animationSpeed: 1.0,
87
+ },
88
+ renderStyle: {
89
+ name: 'standard',
90
+ defaultTextAnimation: 'fadeIn',
91
+ defaultImageAnimation: 'slowZoom',
92
+ ambientMotion: true,
93
+ defaultTransition: { type: 'fade', duration: 0.7 },
94
+ },
95
+ colorSchemes: CLASSIC_COLOR_SCHEMES,
96
+ persistentLayers: {
97
+ bottomLayers: [
98
+ {
99
+ template: 'gradientBackground',
100
+ config: { type: 'gradientBackground', preset: 'dark-vignette' },
101
+ },
102
+ ],
103
+ },
104
+ };
105
+
106
+ // ============================================
107
+ // 2. Documentary
40
108
  // ============================================
41
109
 
42
110
  const documentary: Theme = {
43
111
  id: 'documentary',
44
112
  name: 'Documentary',
45
- description: 'Classic dark cinematic look with serif typography. The original Squisq style.',
113
+ description: 'Classic dark cinematic look with elegant serif typography.',
46
114
  colors: {
47
115
  primary: '#3d5a80',
48
116
  secondary: '#63b3ed',
@@ -54,8 +122,8 @@ const documentary: Theme = {
54
122
  warning: '#fc8181',
55
123
  },
56
124
  typography: {
57
- bodyFontFamily: SERIF,
58
- titleFontFamily: SERIF,
125
+ bodyFontFamily: SOURCE_SERIF,
126
+ titleFontFamily: PLAYFAIR,
59
127
  titleWeight: 'bold',
60
128
  },
61
129
  style: {
@@ -100,8 +168,8 @@ const minimalist: Theme = {
100
168
  warning: '#e53e3e',
101
169
  },
102
170
  typography: {
103
- bodyFontFamily: SANS,
104
- titleFontFamily: SANS,
171
+ bodyFontFamily: INTER,
172
+ titleFontFamily: INTER,
105
173
  titleWeight: 'bold',
106
174
  lineHeight: 1.6,
107
175
  },
@@ -149,8 +217,8 @@ const bold: Theme = {
149
217
  warning: '#fc8181',
150
218
  },
151
219
  typography: {
152
- bodyFontFamily: SANS,
153
- titleFontFamily: SANS,
220
+ bodyFontFamily: ROBOTO,
221
+ titleFontFamily: OSWALD,
154
222
  titleWeight: 'bold',
155
223
  titleScale: 1.15,
156
224
  bodyScale: 1.0,
@@ -201,7 +269,7 @@ const morningLight: Theme = {
201
269
  warning: '#c53030',
202
270
  },
203
271
  typography: {
204
- bodyFontFamily: MERRIWEATHER,
272
+ bodyFontFamily: LORA,
205
273
  titleFontFamily: MERRIWEATHER,
206
274
  titleWeight: 'bold',
207
275
  lineHeight: 1.7,
@@ -247,9 +315,9 @@ const techDark: Theme = {
247
315
  warning: '#ff5252',
248
316
  },
249
317
  typography: {
250
- bodyFontFamily: SANS,
251
- titleFontFamily: MONO,
252
- monoFontFamily: MONO,
318
+ bodyFontFamily: IBM_PLEX_SANS,
319
+ titleFontFamily: JETBRAINS_MONO,
320
+ monoFontFamily: JETBRAINS_MONO,
253
321
  titleWeight: 'bold',
254
322
  titleScale: 0.9,
255
323
  lineHeight: 1.5,
@@ -299,8 +367,8 @@ const magazine: Theme = {
299
367
  warning: '#fc8181',
300
368
  },
301
369
  typography: {
302
- bodyFontFamily: SANS,
303
- titleFontFamily: GEORGIA,
370
+ bodyFontFamily: DM_SANS,
371
+ titleFontFamily: DM_SERIF,
304
372
  titleWeight: 'bold',
305
373
  titleScale: 1.05,
306
374
  lineHeight: 1.5,
@@ -347,8 +415,8 @@ const cinematic: Theme = {
347
415
  warning: '#fc8181',
348
416
  },
349
417
  typography: {
350
- bodyFontFamily: SERIF,
351
- titleFontFamily: SERIF,
418
+ bodyFontFamily: CRIMSON,
419
+ titleFontFamily: CORMORANT,
352
420
  titleWeight: 'bold',
353
421
  lineHeight: 1.5,
354
422
  titleLineHeight: 1.2,
@@ -405,8 +473,8 @@ const warmEarth: Theme = {
405
473
  warning: '#e53e3e',
406
474
  },
407
475
  typography: {
408
- bodyFontFamily: GEORGIA,
409
- titleFontFamily: GEORGIA,
476
+ bodyFontFamily: HANKEN_GROTESK,
477
+ titleFontFamily: PT_SERIF,
410
478
  titleWeight: 'bold',
411
479
  lineHeight: 1.6,
412
480
  },
@@ -448,6 +516,7 @@ const warmEarth: Theme = {
448
516
  * All built-in themes, keyed by id.
449
517
  */
450
518
  export const THEMES: Record<string, Theme> = {
519
+ standard,
451
520
  documentary,
452
521
  minimalist,
453
522
  bold,
@@ -459,10 +528,10 @@ export const THEMES: Record<string, Theme> = {
459
528
  };
460
529
 
461
530
  /** The default theme id. */
462
- export const DEFAULT_THEME_ID = 'documentary';
531
+ export const DEFAULT_THEME_ID = 'standard';
463
532
 
464
- /** The default theme (documentarymigrated from old DEFAULT_THEME). */
465
- export const DEFAULT_THEME: Theme = documentary;
533
+ /** The default theme (standardsafe system fonts, no external dependencies). */
534
+ export const DEFAULT_THEME: Theme = standard;
466
535
 
467
536
  /**
468
537
  * Resolve a theme by id. Returns the default theme when `id` is
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Apply Transform
3
+ *
4
+ * Main entry point for the transform module. Takes a Doc and a style,
5
+ * analyzes block content, selects extractions to promote, and returns
6
+ * a new Doc with blocks optimized for visual presentation.
7
+ *
8
+ * The input Doc is never mutated — a fresh Doc is always returned.
9
+ */
10
+
11
+ import type { Doc } from '../schemas/Doc.js';
12
+ import type { TransformStyleId, TransformOptions, TransformResult } from './types.js';
13
+ import { resolveTransformStyle } from './registry.js';
14
+ import { analyzeBlocks, extractDocImages } from './blockAnalyzer.js';
15
+ import { selectAndBuild } from './templateSelector.js';
16
+ import { allocateTiming } from './timingAllocator.js';
17
+ import { hashString } from '../random/SeededRandom.js';
18
+
19
+ /**
20
+ * Apply a transform style to a Doc, producing a new Doc with blocks
21
+ * optimized for visual presentation.
22
+ *
23
+ * @param doc - The source Doc (not mutated).
24
+ * @param styleId - Registered transform style id (e.g. 'documentary', 'magazine').
25
+ * @param options - Optional seed, images, theme override.
26
+ * @returns TransformResult with the new Doc and stats.
27
+ */
28
+ export function applyTransform(
29
+ doc: Doc,
30
+ styleId: TransformStyleId,
31
+ options?: TransformOptions,
32
+ ): TransformResult {
33
+ const baseConfig = resolveTransformStyle(styleId);
34
+ const config = options?.overrides
35
+ ? { ...baseConfig, ...options.overrides, id: baseConfig.id }
36
+ : baseConfig;
37
+
38
+ const seed = options?.seed ?? hashString(doc.articleId || 'transform');
39
+
40
+ // Use provided images or auto-extract from the doc's markdown content
41
+ const images = options?.images ?? extractDocImages(doc.blocks);
42
+
43
+ // 1. Analyze blocks
44
+ const analyzed = analyzeBlocks(doc.blocks, {
45
+ minConfidence: config.minConfidence,
46
+ types: config.preferredTypes,
47
+ });
48
+
49
+ // If no blocks have meaningful content, return the doc unchanged
50
+ const hasContent = analyzed.some((ab) => ab.bodyWordCount >= 5);
51
+ if (!hasContent) {
52
+ return {
53
+ doc: { ...doc },
54
+ stats: {
55
+ totalInputBlocks: doc.blocks.length,
56
+ transformedBlocks: 0,
57
+ insertedBlocks: 0,
58
+ },
59
+ };
60
+ }
61
+
62
+ // 2. Select extractions and build template blocks
63
+ const selection = selectAndBuild(analyzed, config, images, seed);
64
+
65
+ // 3. Allocate timing across the new block sequence
66
+ const timedBlocks = allocateTiming(selection.blocks, doc.duration);
67
+
68
+ // 4. Assemble the output Doc
69
+ const transformedDoc: Doc = {
70
+ ...doc,
71
+ blocks: timedBlocks,
72
+ themeId: options?.themeId ?? doc.themeId,
73
+ };
74
+
75
+ return {
76
+ doc: transformedDoc,
77
+ stats: {
78
+ totalInputBlocks: doc.blocks.length,
79
+ transformedBlocks: selection.transformedCount,
80
+ insertedBlocks: selection.insertedCount,
81
+ },
82
+ };
83
+ }