@bendyline/squisq 1.4.1 → 1.5.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 (254) hide show
  1. package/README.md +14 -1
  2. package/dist/{ContentContainer-DNx460y8.d.ts → ContentContainer-BXUlIf18.d.ts} +6 -2
  3. package/dist/{Doc-BeSeH2iX.d.ts → Doc-BBVGq1_9.d.ts} +922 -273
  4. package/dist/{ImageEditDoc-BTIvw0oq.d.ts → ImageEditDoc-FuyGtt6o.d.ts} +11 -4
  5. package/dist/{chunk-ZTX4PFFT.js → chunk-2OIBZYKP.js} +75 -2
  6. package/dist/chunk-2OIBZYKP.js.map +1 -0
  7. package/dist/chunk-2UQOHYE2.js +45 -0
  8. package/dist/chunk-2UQOHYE2.js.map +1 -0
  9. package/dist/chunk-6ATE2PSM.js +344 -0
  10. package/dist/chunk-6ATE2PSM.js.map +1 -0
  11. package/dist/{chunk-57CVQCFX.js → chunk-A4O7GIWE.js} +74 -23
  12. package/dist/chunk-A4O7GIWE.js.map +1 -0
  13. package/dist/{chunk-5WFRKURX.js → chunk-DLXZMS5K.js} +4 -2
  14. package/dist/chunk-DLXZMS5K.js.map +1 -0
  15. package/dist/chunk-ET53IIEP.js +1 -0
  16. package/dist/chunk-ET53IIEP.js.map +1 -0
  17. package/dist/chunk-HWVFJAAH.js +362 -0
  18. package/dist/chunk-HWVFJAAH.js.map +1 -0
  19. package/dist/{chunk-2TMAWB4Y.js → chunk-IMSCRLLV.js} +1 -1
  20. package/dist/chunk-IMSCRLLV.js.map +1 -0
  21. package/dist/{chunk-B4EEON3N.js → chunk-KKNUBQ6Y.js} +536 -103
  22. package/dist/chunk-KKNUBQ6Y.js.map +1 -0
  23. package/dist/chunk-LDQ2HJIX.js +596 -0
  24. package/dist/chunk-LDQ2HJIX.js.map +1 -0
  25. package/dist/chunk-LH7I6SH7.js +7208 -0
  26. package/dist/chunk-LH7I6SH7.js.map +1 -0
  27. package/dist/chunk-PGGGKYWG.js +1 -0
  28. package/dist/chunk-PGGGKYWG.js.map +1 -0
  29. package/dist/{chunk-7UDSRZKG.js → chunk-Q3ROPT5H.js} +35 -14
  30. package/dist/chunk-Q3ROPT5H.js.map +1 -0
  31. package/dist/chunk-QE7TWGVT.js +178 -0
  32. package/dist/chunk-QE7TWGVT.js.map +1 -0
  33. package/dist/chunk-RUDYOTA6.js +345 -0
  34. package/dist/chunk-RUDYOTA6.js.map +1 -0
  35. package/dist/{chunk-K32VJONL.js → chunk-T5UK6YOB.js} +41 -2
  36. package/dist/chunk-T5UK6YOB.js.map +1 -0
  37. package/dist/{chunk-3K5OG6KI.js → chunk-TEEEILMP.js} +412 -186
  38. package/dist/chunk-TEEEILMP.js.map +1 -0
  39. package/dist/{chunk-FR2RBTKO.js → chunk-TQWLI6S2.js} +93 -27
  40. package/dist/chunk-TQWLI6S2.js.map +1 -0
  41. package/dist/{chunk-WMOI7FN3.js → chunk-UY7KGQ5R.js} +12 -2
  42. package/dist/{chunk-WMOI7FN3.js.map → chunk-UY7KGQ5R.js.map} +1 -1
  43. package/dist/{chunk-3E5F2XMR.js → chunk-VWUFZ6ZG.js} +29 -2
  44. package/dist/chunk-VWUFZ6ZG.js.map +1 -0
  45. package/dist/chunk-ZLQKNOZQ.js +1 -0
  46. package/dist/chunk-ZLQKNOZQ.js.map +1 -0
  47. package/dist/doc/index.d.ts +5 -5
  48. package/dist/doc/index.js +135 -8
  49. package/dist/generate/index.d.ts +3 -74
  50. package/dist/generate/index.js +2 -7
  51. package/dist/icons/index.d.ts +3 -26
  52. package/dist/icons/index.js +13 -0
  53. package/dist/icons/inlineIconMarker.d.ts +1 -0
  54. package/dist/icons/inlineIconMarker.js +15 -0
  55. package/dist/icons/inlineIconMarker.js.map +1 -0
  56. package/dist/imageEdit/index.d.ts +12 -6
  57. package/dist/imageEdit/index.js +4 -1
  58. package/dist/index.d.ts +14 -11
  59. package/dist/index.js +265 -37
  60. package/dist/inlineIconMarker-CiE8Hlvz.d.ts +59 -0
  61. package/dist/jsonForm/index.d.ts +42 -1
  62. package/dist/jsonForm/index.js +9 -1
  63. package/dist/markdown/index.d.ts +228 -3
  64. package/dist/markdown/index.js +42 -7
  65. package/dist/recommend/index.d.ts +1 -1
  66. package/dist/recommend/index.js +3 -2
  67. package/dist/schemas/index.d.ts +28 -8
  68. package/dist/schemas/index.js +36 -8
  69. package/dist/spatial/index.d.ts +9 -4
  70. package/dist/spatial/index.js +1 -1
  71. package/dist/storage/index.d.ts +2 -2
  72. package/dist/storage/index.js +1 -1
  73. package/dist/story/index.d.ts +1021 -41
  74. package/dist/story/index.js +135 -8
  75. package/dist/{themeLibrary-BHjJ0Qx6.d.ts → themeLibrary-CehcJhzz.d.ts} +1 -1
  76. package/dist/timing/index.js +3 -5
  77. package/dist/transform/index.d.ts +48 -10
  78. package/dist/transform/index.js +17 -481
  79. package/dist/transform/index.js.map +1 -1
  80. package/dist/{types-BdZkdJ3z.d.ts → types-DlAZ7MW4.d.ts} +115 -1
  81. package/dist/versions/index.d.ts +1 -1
  82. package/package.json +6 -1
  83. package/src/__tests__/animationUtils.test.ts +40 -0
  84. package/src/__tests__/annotationCoercion.test.ts +243 -0
  85. package/src/__tests__/applyRenderStyle.test.ts +146 -0
  86. package/src/__tests__/atmosphereLayers.test.ts +108 -0
  87. package/src/__tests__/attrTokens.test.ts +162 -0
  88. package/src/__tests__/contentExtractor.test.ts +60 -0
  89. package/src/__tests__/coverBlock.test.ts +46 -1
  90. package/src/__tests__/customTemplateSample.test.ts +144 -0
  91. package/src/__tests__/customTemplatesFrontmatter.test.ts +236 -0
  92. package/src/__tests__/customThemesFrontmatter.test.ts +145 -0
  93. package/src/__tests__/diagramBlock.test.ts +122 -0
  94. package/src/__tests__/diagramLayout.test.ts +134 -0
  95. package/src/__tests__/drawing.test.ts +109 -0
  96. package/src/__tests__/drawingBlock.test.ts +124 -0
  97. package/src/__tests__/drawingLayout.test.ts +171 -0
  98. package/src/__tests__/geohash.test.ts +6 -5
  99. package/src/__tests__/getLayers.test.ts +6 -2
  100. package/src/__tests__/getLayersFallback.test.ts +103 -0
  101. package/src/__tests__/imageEditExportSvg.test.ts +70 -0
  102. package/src/__tests__/imageEditVersions.test.ts +72 -0
  103. package/src/__tests__/inlineIconMarker.test.ts +36 -0
  104. package/src/__tests__/inputDescriptors.test.ts +162 -0
  105. package/src/__tests__/jsonForm.tokens.test.ts +84 -0
  106. package/src/__tests__/layoutBlock.test.ts +83 -0
  107. package/src/__tests__/layoutLayout.test.ts +169 -0
  108. package/src/__tests__/markdown.test.ts +87 -0
  109. package/src/__tests__/markdownSanitize.test.ts +79 -0
  110. package/src/__tests__/markdownToDoc.test.ts +391 -2
  111. package/src/__tests__/mediaAnnotations.test.ts +91 -0
  112. package/src/__tests__/mediaSchedule.test.ts +118 -0
  113. package/src/__tests__/pandocAttrParse.test.ts +187 -0
  114. package/src/__tests__/resolveDocTheme.test.ts +55 -0
  115. package/src/__tests__/rootExports.test.ts +43 -0
  116. package/src/__tests__/shapeGeometry.test.ts +89 -0
  117. package/src/__tests__/shapeVocabulary.test.ts +133 -0
  118. package/src/__tests__/structuredData.test.ts +249 -0
  119. package/src/__tests__/templateAnnotationParse.test.ts +108 -0
  120. package/src/__tests__/templateMetadata.test.ts +31 -0
  121. package/src/__tests__/templates.test.ts +257 -4
  122. package/src/__tests__/themeCompile.test.ts +42 -0
  123. package/src/__tests__/themeValidator.test.ts +6 -0
  124. package/src/__tests__/transformV2.test.ts +137 -0
  125. package/src/__tests__/transitionNormalize.test.ts +61 -0
  126. package/src/__tests__/validateDoc.test.ts +260 -0
  127. package/src/doc/annotationBlocks.ts +117 -0
  128. package/src/doc/customTemplatesFrontmatter.ts +314 -0
  129. package/src/doc/customThemesFrontmatter.ts +95 -0
  130. package/src/doc/docToMarkdown.ts +104 -5
  131. package/src/doc/getLayers.ts +49 -17
  132. package/src/doc/index.ts +47 -1
  133. package/src/doc/markdownToDoc.ts +411 -13
  134. package/src/doc/mediaAnnotations.ts +100 -0
  135. package/src/doc/resolveDocTheme.ts +55 -0
  136. package/src/doc/standaloneAnnotation.ts +58 -0
  137. package/src/doc/structuredData.ts +301 -0
  138. package/src/doc/templateInputs.ts +240 -0
  139. package/src/doc/templates/__tests__/customTemplate.test.ts +170 -0
  140. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +110 -0
  141. package/src/doc/templates/accentImage.ts +30 -6
  142. package/src/doc/templates/captionUtils.ts +23 -0
  143. package/src/doc/templates/comparisonBar.ts +33 -18
  144. package/src/doc/templates/coverBlock.ts +35 -5
  145. package/src/doc/templates/customTemplate.ts +51 -0
  146. package/src/doc/templates/dataTable.ts +37 -19
  147. package/src/doc/templates/dateEvent.ts +41 -20
  148. package/src/doc/templates/definitionCard.ts +26 -18
  149. package/src/doc/templates/diagramBlock.ts +204 -0
  150. package/src/doc/templates/diagramLayout.ts +159 -0
  151. package/src/doc/templates/drawingBlock.ts +325 -0
  152. package/src/doc/templates/drawingLayout.ts +539 -0
  153. package/src/doc/templates/factCard.ts +54 -26
  154. package/src/doc/templates/fallbackBlock.ts +123 -0
  155. package/src/doc/templates/featureBlock.ts +20 -13
  156. package/src/doc/templates/fullBleedQuote.ts +18 -8
  157. package/src/doc/templates/imageWithCaption.ts +30 -15
  158. package/src/doc/templates/index.ts +225 -46
  159. package/src/doc/templates/inputDescriptors.ts +432 -0
  160. package/src/doc/templates/layoutBlock.ts +43 -0
  161. package/src/doc/templates/layoutLayout.ts +243 -0
  162. package/src/doc/templates/listBlock.ts +56 -30
  163. package/src/doc/templates/mapBlock.ts +15 -12
  164. package/src/doc/templates/metadata.ts +120 -0
  165. package/src/doc/templates/persistentLayers.ts +129 -11
  166. package/src/doc/templates/photoGrid.ts +22 -34
  167. package/src/doc/templates/pullQuote.ts +30 -12
  168. package/src/doc/templates/quoteBlock.ts +54 -29
  169. package/src/doc/templates/sectionHeader.ts +25 -11
  170. package/src/doc/templates/statHighlight.ts +39 -22
  171. package/src/doc/templates/titleBlock.ts +59 -21
  172. package/src/doc/templates/tokens/__tests__/resolveTokens.test.ts +487 -0
  173. package/src/doc/templates/tokens/resolveTokens.ts +453 -0
  174. package/src/doc/templates/twoColumn.ts +48 -30
  175. package/src/doc/templates/videoPullQuote.ts +22 -12
  176. package/src/doc/templates/videoWithCaption.ts +21 -15
  177. package/src/doc/utils/animationUtils.ts +267 -3
  178. package/src/doc/utils/applyRenderStyle.ts +113 -0
  179. package/src/doc/utils/imageTreatment.ts +70 -0
  180. package/src/doc/utils/nearest.ts +59 -0
  181. package/src/doc/utils/shapeGeometry.ts +464 -0
  182. package/src/doc/utils/themeUtils.ts +75 -1
  183. package/src/doc/validate.ts +575 -0
  184. package/src/generate/contentExtractor.ts +148 -35
  185. package/src/generate/index.ts +0 -3
  186. package/src/icons/index.ts +8 -0
  187. package/src/icons/inlineIconMarker.ts +73 -0
  188. package/src/imageEdit/export.ts +85 -2
  189. package/src/imageEdit/index.ts +1 -0
  190. package/src/imageEdit/persistence.ts +51 -0
  191. package/src/index.ts +2 -0
  192. package/src/jsonForm/index.ts +2 -0
  193. package/src/jsonForm/tokens.ts +78 -0
  194. package/src/markdown/annotationCoercion.ts +295 -0
  195. package/src/markdown/attrTokens.ts +241 -0
  196. package/src/markdown/convert.ts +229 -26
  197. package/src/markdown/index.ts +37 -0
  198. package/src/markdown/sanitize.ts +318 -0
  199. package/src/markdown/stringify.ts +96 -6
  200. package/src/markdown/types.ts +66 -0
  201. package/src/markdown/utils.ts +111 -2
  202. package/src/recommend/templates.ts +24 -0
  203. package/src/schemas/BlockTemplates.ts +133 -4
  204. package/src/schemas/CustomTemplates.ts +126 -0
  205. package/src/schemas/Doc.ts +387 -65
  206. package/src/schemas/ImageEditDoc.ts +11 -4
  207. package/src/schemas/Media.ts +169 -0
  208. package/src/schemas/Theme.ts +23 -1
  209. package/src/schemas/Transitions.ts +224 -0
  210. package/src/schemas/colorUtils.ts +33 -0
  211. package/src/schemas/fontStacks.ts +5 -2
  212. package/src/schemas/index.ts +3 -0
  213. package/src/schemas/themeCompile.ts +24 -5
  214. package/src/schemas/themeValidator.ts +49 -11
  215. package/src/schemas/themes/cinematic.json +69 -12
  216. package/src/schemas/themes/documentary.json +58 -11
  217. package/src/schemas/themes/gezellig.json +60 -11
  218. package/src/schemas/themes/magazine.json +54 -9
  219. package/src/schemas/themes/morning-light.json +52 -9
  220. package/src/schemas/themes/standard-dark.json +48 -13
  221. package/src/schemas/themes/standard.json +44 -12
  222. package/src/schemas/themes/tech-dark.json +65 -12
  223. package/src/spatial/Geohash.ts +52 -21
  224. package/src/storage/ContentContainer.ts +6 -2
  225. package/src/transform/applyTransform.ts +43 -4
  226. package/src/transform/index.ts +1 -0
  227. package/src/transform/registry.ts +44 -13
  228. package/src/transform/styles/dataDriven.ts +2 -1
  229. package/src/transform/styles/documentary.ts +2 -1
  230. package/src/transform/styles/magazine.ts +3 -1
  231. package/src/transform/styles/minimal.ts +2 -1
  232. package/src/transform/styles/narrative.ts +3 -1
  233. package/src/transform/templateSelector.ts +87 -11
  234. package/src/transform/types.ts +35 -2
  235. package/dist/chunk-2TMAWB4Y.js.map +0 -1
  236. package/dist/chunk-3E5F2XMR.js.map +0 -1
  237. package/dist/chunk-3K5OG6KI.js.map +0 -1
  238. package/dist/chunk-4X3JQXNM.js +0 -35
  239. package/dist/chunk-4X3JQXNM.js.map +0 -1
  240. package/dist/chunk-57CVQCFX.js.map +0 -1
  241. package/dist/chunk-5WFRKURX.js.map +0 -1
  242. package/dist/chunk-7UDSRZKG.js.map +0 -1
  243. package/dist/chunk-B4EEON3N.js.map +0 -1
  244. package/dist/chunk-FR2RBTKO.js.map +0 -1
  245. package/dist/chunk-H3AXU4MJ.js +0 -179
  246. package/dist/chunk-H3AXU4MJ.js.map +0 -1
  247. package/dist/chunk-K32VJONL.js.map +0 -1
  248. package/dist/chunk-KSWJR66U.js +0 -3706
  249. package/dist/chunk-KSWJR66U.js.map +0 -1
  250. package/dist/chunk-MYIH7FWD.js +0 -145
  251. package/dist/chunk-MYIH7FWD.js.map +0 -1
  252. package/dist/chunk-ZTX4PFFT.js.map +0 -1
  253. package/src/__tests__/slideshowGenerator.test.ts +0 -128
  254. package/src/generate/slideshowGenerator.ts +0 -273
@@ -1,273 +0,0 @@
1
- /**
2
- * Slideshow Generator
3
- *
4
- * Turns plain text + optional images into a complete squisq `Doc`.
5
- *
6
- * Pipeline:
7
- * 1. Strip markdown (if needed)
8
- * 2. Extract compelling content (stats, dates, quotes, …)
9
- * 3. Estimate narration timing
10
- * 4. Map extractions → template blocks
11
- * 5. Interleave image slides
12
- * 6. Return a ready-to-render `Doc`
13
- */
14
-
15
- import type { Doc } from '../schemas/Doc.js';
16
- import type {
17
- TemplateBlock,
18
- ImageWithCaptionInput,
19
- TitleBlockInput,
20
- ColorScheme,
21
- AccentImage,
22
- AccentPosition,
23
- } from '../schemas/BlockTemplates.js';
24
- import { extractContent, stripMarkdown } from './contentExtractor.js';
25
- import { mapElementToBlock } from './templateMapper.js';
26
- import { estimateNarrationDuration } from '../timing/narrationTiming.js';
27
- import { SeededRandom, hashString } from '../random/SeededRandom.js';
28
-
29
- // ── Public types ───────────────────────────────────────────────────
30
-
31
- /** A Doc variant whose blocks are TemplateBlocks (no startTime required). */
32
- export type SlideshowDoc = Omit<Doc, 'blocks'> & {
33
- blocks: TemplateBlock[];
34
- };
35
-
36
- /** Image metadata passed to the generator. */
37
- export interface SlideshowImage {
38
- /** Image source path or URL. */
39
- src: string;
40
- /** Alt text for accessibility. */
41
- alt?: string;
42
- /** Photo credit. */
43
- credit?: string;
44
- /** License identifier. */
45
- license?: string;
46
- }
47
-
48
- /** Options for `generateSlideshow`. */
49
- export interface SlideshowOptions {
50
- /** Deterministic seed (default: hash of the text). */
51
- seed?: number;
52
- /** Title slide text. Omit to skip the title slide. */
53
- title?: string;
54
- /** Subtitle for the title slide. */
55
- subtitle?: string;
56
- /** Explicit total duration in seconds. When omitted, estimated from text. */
57
- duration?: number;
58
- /** Target number of content slides (default: 5–8, auto-scaled). */
59
- targetSlides?: number;
60
- /** Color schemes to rotate (default: ['blue','green','purple','orange','red']). */
61
- colorSchemes?: ColorScheme[];
62
- /** Ambient motions to rotate on images. */
63
- ambientMotions?: Array<'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight'>;
64
- /** Accent positions to rotate on text slides. */
65
- accentPositions?: AccentPosition[];
66
- /** Minimum slide duration in seconds (default: 4). */
67
- minSlideDuration?: number;
68
- /** Maximum slide duration in seconds (default: 15). */
69
- maxSlideDuration?: number;
70
- /** Theme identifier to embed in the Doc. */
71
- themeId?: string;
72
- /** Whether the input text is markdown (default: false). */
73
- isMarkdown?: boolean;
74
- /** Minimum extraction confidence (default: 0.3). */
75
- minConfidence?: number;
76
- }
77
-
78
- const DEFAULT_COLOR_SCHEMES: ColorScheme[] = ['blue', 'green', 'purple', 'orange', 'red'];
79
- const DEFAULT_AMBIENT_MOTIONS: Array<'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight'> = [
80
- 'zoomIn',
81
- 'zoomOut',
82
- 'panLeft',
83
- 'panRight',
84
- ];
85
- const DEFAULT_ACCENT_POSITIONS: AccentPosition[] = ['left-strip', 'right-strip', 'bottom-strip'];
86
-
87
- // ── Main ───────────────────────────────────────────────────────────
88
-
89
- /**
90
- * Generate a slideshow `Doc` from text and optional images.
91
- *
92
- * ```ts
93
- * const doc = generateSlideshow(articleText, myImages, {
94
- * title: 'Mount Rainier',
95
- * themeId: 'documentary',
96
- * });
97
- * // doc is a complete Doc ready for DocPlayer / LinearDocView
98
- * ```
99
- */
100
- export function generateSlideshow(
101
- text: string,
102
- images: SlideshowImage[] = [],
103
- options: SlideshowOptions = {},
104
- ): SlideshowDoc {
105
- const {
106
- seed,
107
- title,
108
- subtitle,
109
- duration: explicitDuration,
110
- targetSlides,
111
- colorSchemes = DEFAULT_COLOR_SCHEMES,
112
- ambientMotions = DEFAULT_AMBIENT_MOTIONS,
113
- accentPositions = DEFAULT_ACCENT_POSITIONS,
114
- minSlideDuration = 4,
115
- maxSlideDuration = 15,
116
- themeId,
117
- isMarkdown = false,
118
- minConfidence = 0.3,
119
- } = options;
120
-
121
- const plainText = isMarkdown ? stripMarkdown(text) : text;
122
- const rng = new SeededRandom(seed ?? hashString(plainText));
123
-
124
- // 1. Estimate total duration
125
- const totalDuration = explicitDuration ?? estimateNarrationDuration(plainText);
126
-
127
- // 2. Extract compelling content
128
- const extraction = extractContent(plainText, { minConfidence });
129
- const allElements = extraction.elements;
130
-
131
- // 3. Decide slide budget
132
- const slideBudget = targetSlides ?? computeSlideBudget(totalDuration, allElements.length);
133
-
134
- // Allocate slots between content and images
135
- const imageSlotCount = images.length > 0 ? Math.max(1, Math.round(slideBudget * 0.4)) : 0;
136
- const contentSlotCount = Math.max(0, slideBudget - imageSlotCount - (title ? 1 : 0));
137
-
138
- // Select top elements by confidence, then restore source order
139
- const sorted = [...allElements].sort((a, b) => b.confidence - a.confidence);
140
- const selected = sorted.slice(0, contentSlotCount);
141
- selected.sort((a, b) => a.sourcePosition - b.sourcePosition);
142
-
143
- // 4. Build slides
144
- const blocks: TemplateBlock[] = [];
145
- let slideIndex = 0;
146
- let colorIdx = 0;
147
- let accentIdx = 0;
148
-
149
- // Determine per-slide duration
150
- const totalSlideCount =
151
- (title ? 1 : 0) + selected.length + Math.min(imageSlotCount, images.length);
152
- const perSlide =
153
- totalSlideCount > 0
154
- ? Math.min(maxSlideDuration, Math.max(minSlideDuration, totalDuration / totalSlideCount))
155
- : minSlideDuration;
156
-
157
- // Title slide
158
- if (title) {
159
- const titleBlock: TitleBlockInput = {
160
- id: `slide-${slideIndex++}`,
161
- template: 'title',
162
- title,
163
- subtitle,
164
- duration: Math.min(perSlide, 6),
165
- audioSegment: 0,
166
- };
167
- blocks.push(titleBlock);
168
- }
169
-
170
- // Prepare image pool for standalone + accent images
171
- const standaloneImages = images.slice(0, Math.min(imageSlotCount, images.length));
172
- const accentPool =
173
- images.length > standaloneImages.length ? images.slice(standaloneImages.length) : [...images]; // reuse if not enough
174
-
175
- // Interleave pattern: insert an image slide every N content slides
176
- const imageInterval =
177
- standaloneImages.length > 0
178
- ? Math.max(1, Math.floor(selected.length / (standaloneImages.length + 1)))
179
- : Infinity;
180
- let imgIdx = 0;
181
- let sinceImage = 0;
182
-
183
- for (let i = 0; i < selected.length; i++) {
184
- // Maybe insert a standalone image slide before this content slide
185
- if (sinceImage >= imageInterval && imgIdx < standaloneImages.length) {
186
- blocks.push(
187
- createImageSlide(standaloneImages[imgIdx], slideIndex++, perSlide, rng, ambientMotions),
188
- );
189
- imgIdx++;
190
- sinceImage = 0;
191
- }
192
-
193
- const element = selected[i];
194
-
195
- // Optional accent image
196
- let accentImage: AccentImage | undefined;
197
- if (accentPool.length > 0) {
198
- const img = accentPool[i % accentPool.length];
199
- accentImage = {
200
- src: img.src,
201
- alt: img.alt ?? '',
202
- position: accentPositions[accentIdx % accentPositions.length],
203
- ambientMotion: rng.pickRequired(ambientMotions),
204
- credit: img.credit,
205
- license: img.license,
206
- };
207
- accentIdx++;
208
- }
209
-
210
- const block = mapElementToBlock(element, {
211
- id: `slide-${slideIndex++}`,
212
- duration: perSlide,
213
- audioSegment: 0,
214
- colorScheme: colorSchemes[colorIdx % colorSchemes.length],
215
- accentImage,
216
- });
217
- blocks.push(block);
218
- colorIdx++;
219
- sinceImage++;
220
- }
221
-
222
- // Append any remaining standalone images
223
- while (imgIdx < standaloneImages.length) {
224
- blocks.push(
225
- createImageSlide(standaloneImages[imgIdx], slideIndex++, perSlide, rng, ambientMotions),
226
- );
227
- imgIdx++;
228
- }
229
-
230
- // 5. Assemble Doc
231
- const doc: SlideshowDoc = {
232
- articleId: title ?? 'slideshow',
233
- duration: totalDuration,
234
- blocks,
235
- audio: {
236
- segments: [{ src: '', name: 'main', duration: totalDuration, startTime: 0 }],
237
- },
238
- themeId,
239
- };
240
-
241
- return doc;
242
- }
243
-
244
- // ── Helpers ────────────────────────────────────────────────────────
245
-
246
- function computeSlideBudget(duration: number, elementCount: number): number {
247
- // Aim for ~5 slides per minute of content, clamped 3–12
248
- const byDuration = Math.round((duration / 60) * 5);
249
- // But don't exceed available elements + a few image slots
250
- const budget = Math.min(byDuration, elementCount + 4);
251
- return Math.max(3, Math.min(12, budget));
252
- }
253
-
254
- function createImageSlide(
255
- img: SlideshowImage,
256
- index: number,
257
- duration: number,
258
- rng: SeededRandom,
259
- motions: Array<'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight'>,
260
- ): ImageWithCaptionInput {
261
- return {
262
- id: `slide-${index}`,
263
- template: 'imageWithCaption',
264
- imageSrc: img.src,
265
- imageAlt: img.alt ?? '',
266
- caption: img.alt,
267
- duration,
268
- audioSegment: 0,
269
- ambientMotion: rng.pickRequired(motions),
270
- imageCredit: img.credit,
271
- imageLicense: img.license,
272
- };
273
- }