@bendyline/squisq 1.4.1 → 1.5.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 (216) hide show
  1. package/dist/{Doc-BeSeH2iX.d.ts → Doc-BsMkmzH8.d.ts} +838 -268
  2. package/dist/{ImageEditDoc-BTIvw0oq.d.ts → ImageEditDoc-DvoeHMP9.d.ts} +11 -4
  3. package/dist/{chunk-ZTX4PFFT.js → chunk-2OIBZYKP.js} +75 -2
  4. package/dist/chunk-2OIBZYKP.js.map +1 -0
  5. package/dist/chunk-2UQOHYE2.js +45 -0
  6. package/dist/chunk-2UQOHYE2.js.map +1 -0
  7. package/dist/{chunk-B4EEON3N.js → chunk-4AKUQPEE.js} +536 -103
  8. package/dist/chunk-4AKUQPEE.js.map +1 -0
  9. package/dist/chunk-4ZFRZBU2.js +6582 -0
  10. package/dist/chunk-4ZFRZBU2.js.map +1 -0
  11. package/dist/{chunk-57CVQCFX.js → chunk-7MQGNR76.js} +79 -3
  12. package/dist/chunk-7MQGNR76.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-MYIH7FWD.js → chunk-FBKP5CAO.js} +1 -1
  16. package/dist/chunk-FBKP5CAO.js.map +1 -0
  17. package/dist/chunk-KZNXZ5SM.js +333 -0
  18. package/dist/chunk-KZNXZ5SM.js.map +1 -0
  19. package/dist/{chunk-WMOI7FN3.js → chunk-LRCV7N7F.js} +12 -2
  20. package/dist/{chunk-WMOI7FN3.js.map → chunk-LRCV7N7F.js.map} +1 -1
  21. package/dist/{chunk-3K5OG6KI.js → chunk-OH43RPTP.js} +403 -187
  22. package/dist/chunk-OH43RPTP.js.map +1 -0
  23. package/dist/{chunk-H3AXU4MJ.js → chunk-OKJOQXK4.js} +149 -2
  24. package/dist/chunk-OKJOQXK4.js.map +1 -0
  25. package/dist/chunk-PGGGKYWG.js +1 -0
  26. package/dist/chunk-PGGGKYWG.js.map +1 -0
  27. package/dist/{chunk-7UDSRZKG.js → chunk-Q3ROPT5H.js} +35 -14
  28. package/dist/chunk-Q3ROPT5H.js.map +1 -0
  29. package/dist/chunk-QE7TWGVT.js +178 -0
  30. package/dist/chunk-QE7TWGVT.js.map +1 -0
  31. package/dist/chunk-RUDYOTA6.js +345 -0
  32. package/dist/chunk-RUDYOTA6.js.map +1 -0
  33. package/dist/chunk-TVSGLUID.js +596 -0
  34. package/dist/chunk-TVSGLUID.js.map +1 -0
  35. package/dist/chunk-ZLQKNOZQ.js +1 -0
  36. package/dist/chunk-ZLQKNOZQ.js.map +1 -0
  37. package/dist/doc/index.d.ts +4 -4
  38. package/dist/doc/index.js +124 -6
  39. package/dist/generate/index.d.ts +9 -2
  40. package/dist/generate/index.js +1 -1
  41. package/dist/icons/index.d.ts +3 -26
  42. package/dist/icons/index.js +13 -0
  43. package/dist/icons/inlineIconMarker.d.ts +1 -0
  44. package/dist/icons/inlineIconMarker.js +15 -0
  45. package/dist/icons/inlineIconMarker.js.map +1 -0
  46. package/dist/imageEdit/index.d.ts +11 -5
  47. package/dist/imageEdit/index.js +4 -1
  48. package/dist/index.d.ts +11 -8
  49. package/dist/index.js +243 -24
  50. package/dist/inlineIconMarker-CiE8Hlvz.d.ts +59 -0
  51. package/dist/markdown/index.d.ts +212 -3
  52. package/dist/markdown/index.js +40 -7
  53. package/dist/recommend/index.d.ts +1 -1
  54. package/dist/recommend/index.js +3 -2
  55. package/dist/schemas/index.d.ts +28 -8
  56. package/dist/schemas/index.js +35 -5
  57. package/dist/spatial/index.d.ts +9 -4
  58. package/dist/spatial/index.js +1 -1
  59. package/dist/story/index.d.ts +906 -40
  60. package/dist/story/index.js +124 -6
  61. package/dist/{themeLibrary-BHjJ0Qx6.d.ts → themeLibrary-DQQKuRMx.d.ts} +1 -1
  62. package/dist/transform/index.d.ts +48 -10
  63. package/dist/transform/index.js +17 -481
  64. package/dist/transform/index.js.map +1 -1
  65. package/dist/{types-BdZkdJ3z.d.ts → types-DlAZ7MW4.d.ts} +115 -1
  66. package/package.json +6 -1
  67. package/src/__tests__/animationUtils.test.ts +40 -0
  68. package/src/__tests__/annotationCoercion.test.ts +243 -0
  69. package/src/__tests__/applyRenderStyle.test.ts +146 -0
  70. package/src/__tests__/atmosphereLayers.test.ts +108 -0
  71. package/src/__tests__/attrTokens.test.ts +162 -0
  72. package/src/__tests__/customTemplateSample.test.ts +83 -0
  73. package/src/__tests__/customTemplatesFrontmatter.test.ts +205 -0
  74. package/src/__tests__/customThemesFrontmatter.test.ts +118 -0
  75. package/src/__tests__/diagramBlock.test.ts +122 -0
  76. package/src/__tests__/diagramLayout.test.ts +134 -0
  77. package/src/__tests__/drawing.test.ts +109 -0
  78. package/src/__tests__/drawingBlock.test.ts +124 -0
  79. package/src/__tests__/drawingLayout.test.ts +171 -0
  80. package/src/__tests__/geohash.test.ts +6 -5
  81. package/src/__tests__/getLayers.test.ts +6 -2
  82. package/src/__tests__/getLayersFallback.test.ts +103 -0
  83. package/src/__tests__/imageEditExportSvg.test.ts +70 -0
  84. package/src/__tests__/imageEditVersions.test.ts +72 -0
  85. package/src/__tests__/inlineIconMarker.test.ts +36 -0
  86. package/src/__tests__/layoutBlock.test.ts +83 -0
  87. package/src/__tests__/layoutLayout.test.ts +169 -0
  88. package/src/__tests__/markdown.test.ts +23 -0
  89. package/src/__tests__/markdownSanitize.test.ts +64 -0
  90. package/src/__tests__/markdownToDoc.test.ts +223 -2
  91. package/src/__tests__/mediaAnnotations.test.ts +64 -0
  92. package/src/__tests__/mediaSchedule.test.ts +118 -0
  93. package/src/__tests__/pandocAttrParse.test.ts +187 -0
  94. package/src/__tests__/resolveDocTheme.test.ts +55 -0
  95. package/src/__tests__/rootExports.test.ts +43 -0
  96. package/src/__tests__/shapeGeometry.test.ts +89 -0
  97. package/src/__tests__/shapeVocabulary.test.ts +133 -0
  98. package/src/__tests__/structuredData.test.ts +179 -0
  99. package/src/__tests__/templateAnnotationParse.test.ts +108 -0
  100. package/src/__tests__/templateMetadata.test.ts +21 -0
  101. package/src/__tests__/templates.test.ts +145 -4
  102. package/src/__tests__/themeCompile.test.ts +42 -0
  103. package/src/__tests__/themeValidator.test.ts +6 -0
  104. package/src/__tests__/transformV2.test.ts +137 -0
  105. package/src/__tests__/transitionNormalize.test.ts +61 -0
  106. package/src/__tests__/validateDoc.test.ts +100 -0
  107. package/src/doc/customTemplatesFrontmatter.ts +308 -0
  108. package/src/doc/customThemesFrontmatter.ts +90 -0
  109. package/src/doc/docToMarkdown.ts +73 -4
  110. package/src/doc/getLayers.ts +40 -17
  111. package/src/doc/index.ts +35 -1
  112. package/src/doc/markdownToDoc.ts +333 -12
  113. package/src/doc/mediaAnnotations.ts +118 -0
  114. package/src/doc/resolveDocTheme.ts +55 -0
  115. package/src/doc/structuredData.ts +230 -0
  116. package/src/doc/templateInputs.ts +240 -0
  117. package/src/doc/templates/__tests__/customTemplate.test.ts +151 -0
  118. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +110 -0
  119. package/src/doc/templates/accentImage.ts +30 -6
  120. package/src/doc/templates/captionUtils.ts +23 -0
  121. package/src/doc/templates/comparisonBar.ts +26 -18
  122. package/src/doc/templates/coverBlock.ts +7 -4
  123. package/src/doc/templates/customTemplate.ts +45 -0
  124. package/src/doc/templates/dataTable.ts +37 -19
  125. package/src/doc/templates/dateEvent.ts +41 -20
  126. package/src/doc/templates/definitionCard.ts +26 -18
  127. package/src/doc/templates/diagramBlock.ts +204 -0
  128. package/src/doc/templates/diagramLayout.ts +159 -0
  129. package/src/doc/templates/drawingBlock.ts +325 -0
  130. package/src/doc/templates/drawingLayout.ts +539 -0
  131. package/src/doc/templates/factCard.ts +51 -25
  132. package/src/doc/templates/fallbackBlock.ts +123 -0
  133. package/src/doc/templates/featureBlock.ts +20 -13
  134. package/src/doc/templates/fullBleedQuote.ts +18 -8
  135. package/src/doc/templates/imageWithCaption.ts +22 -9
  136. package/src/doc/templates/index.ts +215 -46
  137. package/src/doc/templates/layoutBlock.ts +43 -0
  138. package/src/doc/templates/layoutLayout.ts +243 -0
  139. package/src/doc/templates/listBlock.ts +56 -30
  140. package/src/doc/templates/mapBlock.ts +15 -12
  141. package/src/doc/templates/metadata.ts +120 -0
  142. package/src/doc/templates/persistentLayers.ts +129 -11
  143. package/src/doc/templates/photoGrid.ts +23 -12
  144. package/src/doc/templates/pullQuote.ts +30 -12
  145. package/src/doc/templates/quoteBlock.ts +54 -29
  146. package/src/doc/templates/sectionHeader.ts +25 -11
  147. package/src/doc/templates/statHighlight.ts +39 -22
  148. package/src/doc/templates/titleBlock.ts +59 -21
  149. package/src/doc/templates/tokens/__tests__/resolveTokens.test.ts +221 -0
  150. package/src/doc/templates/tokens/resolveTokens.ts +0 -0
  151. package/src/doc/templates/twoColumn.ts +48 -30
  152. package/src/doc/templates/videoPullQuote.ts +22 -12
  153. package/src/doc/templates/videoWithCaption.ts +13 -8
  154. package/src/doc/utils/animationUtils.ts +267 -3
  155. package/src/doc/utils/applyRenderStyle.ts +113 -0
  156. package/src/doc/utils/imageTreatment.ts +70 -0
  157. package/src/doc/utils/shapeGeometry.ts +464 -0
  158. package/src/doc/utils/themeUtils.ts +75 -1
  159. package/src/doc/validate.ts +476 -0
  160. package/src/generate/slideshowGenerator.ts +7 -0
  161. package/src/icons/index.ts +8 -0
  162. package/src/icons/inlineIconMarker.ts +73 -0
  163. package/src/imageEdit/export.ts +85 -2
  164. package/src/imageEdit/index.ts +1 -0
  165. package/src/imageEdit/persistence.ts +51 -0
  166. package/src/index.ts +2 -0
  167. package/src/markdown/annotationCoercion.ts +295 -0
  168. package/src/markdown/attrTokens.ts +222 -0
  169. package/src/markdown/convert.ts +229 -26
  170. package/src/markdown/index.ts +36 -0
  171. package/src/markdown/sanitize.ts +298 -0
  172. package/src/markdown/stringify.ts +84 -3
  173. package/src/markdown/types.ts +66 -0
  174. package/src/markdown/utils.ts +49 -1
  175. package/src/recommend/templates.ts +24 -0
  176. package/src/schemas/BlockTemplates.ts +133 -4
  177. package/src/schemas/CustomTemplates.ts +74 -0
  178. package/src/schemas/Doc.ts +353 -29
  179. package/src/schemas/ImageEditDoc.ts +11 -4
  180. package/src/schemas/Media.ts +169 -0
  181. package/src/schemas/Theme.ts +23 -1
  182. package/src/schemas/Transitions.ts +224 -0
  183. package/src/schemas/colorUtils.ts +33 -0
  184. package/src/schemas/fontStacks.ts +5 -2
  185. package/src/schemas/index.ts +3 -0
  186. package/src/schemas/themeCompile.ts +23 -4
  187. package/src/schemas/themeValidator.ts +49 -11
  188. package/src/schemas/themes/cinematic.json +69 -12
  189. package/src/schemas/themes/documentary.json +58 -11
  190. package/src/schemas/themes/gezellig.json +60 -11
  191. package/src/schemas/themes/magazine.json +54 -9
  192. package/src/schemas/themes/morning-light.json +52 -9
  193. package/src/schemas/themes/standard-dark.json +48 -13
  194. package/src/schemas/themes/standard.json +44 -12
  195. package/src/schemas/themes/tech-dark.json +65 -12
  196. package/src/spatial/Geohash.ts +52 -21
  197. package/src/transform/applyTransform.ts +43 -4
  198. package/src/transform/index.ts +1 -0
  199. package/src/transform/registry.ts +44 -13
  200. package/src/transform/styles/dataDriven.ts +2 -1
  201. package/src/transform/styles/documentary.ts +2 -1
  202. package/src/transform/styles/magazine.ts +3 -1
  203. package/src/transform/styles/minimal.ts +2 -1
  204. package/src/transform/styles/narrative.ts +3 -1
  205. package/src/transform/templateSelector.ts +87 -11
  206. package/src/transform/types.ts +35 -2
  207. package/dist/chunk-3K5OG6KI.js.map +0 -1
  208. package/dist/chunk-57CVQCFX.js.map +0 -1
  209. package/dist/chunk-5WFRKURX.js.map +0 -1
  210. package/dist/chunk-7UDSRZKG.js.map +0 -1
  211. package/dist/chunk-B4EEON3N.js.map +0 -1
  212. package/dist/chunk-H3AXU4MJ.js.map +0 -1
  213. package/dist/chunk-KSWJR66U.js +0 -3706
  214. package/dist/chunk-KSWJR66U.js.map +0 -1
  215. package/dist/chunk-MYIH7FWD.js.map +0 -1
  216. package/dist/chunk-ZTX4PFFT.js.map +0 -1
@@ -8,7 +8,12 @@
8
8
  * This is shared code used by both site and efb-app doc renderers.
9
9
  */
10
10
 
11
- import type { Animation, AnimationType, TransitionType } from '../../schemas/Doc.js';
11
+ import type {
12
+ Animation,
13
+ AnimationType,
14
+ TransitionDirection,
15
+ TransitionType,
16
+ } from '../../schemas/Doc.js';
12
17
 
13
18
  interface AnimationResult {
14
19
  /** CSS class name to apply */
@@ -112,9 +117,268 @@ export function getDefaultAnimationDuration(type: AnimationType): number {
112
117
  /**
113
118
  * Get transition class for slide entry/exit.
114
119
  */
115
- export function getTransitionClass(type: TransitionType, entering: boolean): string {
120
+ export function getTransitionClass(
121
+ type: TransitionType,
122
+ entering: boolean,
123
+ direction?: TransitionDirection,
124
+ ): string {
125
+ if (type === 'cut') return '';
116
126
  const mode = entering ? 'enter' : 'exit';
117
- return `transition-${type}-${mode}`;
127
+ return `transition-${getTransitionVisualClass(type, direction)}-${mode}`;
128
+ }
129
+
130
+ type TransitionVisualClass =
131
+ | 'cut'
132
+ | 'fade'
133
+ | 'dissolve'
134
+ | 'pushLeft'
135
+ | 'pushRight'
136
+ | 'pushUp'
137
+ | 'pushDown'
138
+ | 'wipeLeft'
139
+ | 'wipeRight'
140
+ | 'wipeUp'
141
+ | 'wipeDown'
142
+ | 'splitHorizontal'
143
+ | 'splitVertical'
144
+ | 'revealLeft'
145
+ | 'revealRight'
146
+ | 'revealUp'
147
+ | 'revealDown'
148
+ | 'random'
149
+ | 'randomBarsHorizontal'
150
+ | 'randomBarsVertical'
151
+ | 'shapeCircle'
152
+ | 'shapeDiamond'
153
+ | 'shapePlus'
154
+ | 'uncoverLeft'
155
+ | 'uncoverRight'
156
+ | 'uncoverUp'
157
+ | 'uncoverDown'
158
+ | 'coverLeft'
159
+ | 'coverRight'
160
+ | 'coverUp'
161
+ | 'coverDown'
162
+ | 'zoom'
163
+ | 'panLeft'
164
+ | 'panRight'
165
+ | 'panUp'
166
+ | 'panDown'
167
+ | 'flash'
168
+ | 'newsflash'
169
+ | 'fallOver'
170
+ | 'drape'
171
+ | 'curtains'
172
+ | 'wind'
173
+ | 'prestige'
174
+ | 'fracture'
175
+ | 'crush'
176
+ | 'peelOff'
177
+ | 'pageCurl'
178
+ | 'pageCurlDouble'
179
+ | 'pageCurlSingle'
180
+ | 'airplane'
181
+ | 'origami'
182
+ | 'checkerboard'
183
+ | 'blindsHorizontal'
184
+ | 'blindsVertical'
185
+ | 'clock'
186
+ | 'ripple'
187
+ | 'honeycomb'
188
+ | 'glitter'
189
+ | 'vortex'
190
+ | 'shred'
191
+ | 'switch'
192
+ | 'flip'
193
+ | 'gallery'
194
+ | 'cube'
195
+ | 'doors'
196
+ | 'box'
197
+ | 'ferrisWheel'
198
+ | 'conveyor'
199
+ | 'rotate'
200
+ | 'window'
201
+ | 'orbit'
202
+ | 'flyThrough'
203
+ | 'morph'
204
+ | 'combHorizontal'
205
+ | 'combVertical'
206
+ | 'pullLeft'
207
+ | 'pullRight'
208
+ | 'pullUp'
209
+ | 'pullDown'
210
+ | 'stripsLeft'
211
+ | 'stripsRight'
212
+ | 'stripsUp'
213
+ | 'stripsDown'
214
+ | 'wedge'
215
+ | 'wheel'
216
+ | 'wheelReverse'
217
+ | 'prism'
218
+ | 'warp'
219
+ | 'slideLeft'
220
+ | 'slideRight'
221
+ | 'slideUp'
222
+ | 'slideDown';
223
+
224
+ const TRANSITION_VISUAL_CLASS_BY_TYPE: Record<TransitionType, TransitionVisualClass> = {
225
+ airplane: 'airplane',
226
+ blinds: 'blindsHorizontal',
227
+ box: 'box',
228
+ checker: 'checkerboard',
229
+ checkerboard: 'checkerboard',
230
+ circle: 'shapeCircle',
231
+ clock: 'clock',
232
+ comb: 'combHorizontal',
233
+ conveyor: 'conveyor',
234
+ cover: 'coverLeft',
235
+ crush: 'crush',
236
+ cube: 'cube',
237
+ curtains: 'curtains',
238
+ cut: 'cut',
239
+ diamond: 'shapeDiamond',
240
+ dissolve: 'dissolve',
241
+ doors: 'doors',
242
+ drape: 'drape',
243
+ fade: 'fade',
244
+ fallOver: 'fallOver',
245
+ ferris: 'ferrisWheel',
246
+ ferrisWheel: 'ferrisWheel',
247
+ flash: 'flash',
248
+ flip: 'flip',
249
+ flyThrough: 'flyThrough',
250
+ flythrough: 'flyThrough',
251
+ fracture: 'fracture',
252
+ gallery: 'gallery',
253
+ glitter: 'glitter',
254
+ honeycomb: 'honeycomb',
255
+ morph: 'morph',
256
+ newsflash: 'newsflash',
257
+ orbit: 'orbit',
258
+ origami: 'origami',
259
+ pageCurl: 'pageCurl',
260
+ pageCurlDouble: 'pageCurlDouble',
261
+ pageCurlSingle: 'pageCurlSingle',
262
+ pan: 'panLeft',
263
+ peelOff: 'peelOff',
264
+ plus: 'shapePlus',
265
+ prestige: 'prestige',
266
+ prism: 'prism',
267
+ pull: 'pullLeft',
268
+ push: 'pushLeft',
269
+ random: 'random',
270
+ randomBar: 'randomBarsHorizontal',
271
+ randomBars: 'randomBarsVertical',
272
+ reveal: 'revealLeft',
273
+ ripple: 'ripple',
274
+ rotate: 'rotate',
275
+ shape: 'shapeCircle',
276
+ shred: 'shred',
277
+ slideDown: 'slideDown',
278
+ slideLeft: 'slideLeft',
279
+ slideRight: 'slideRight',
280
+ slideUp: 'slideUp',
281
+ split: 'splitHorizontal',
282
+ strips: 'stripsLeft',
283
+ switch: 'switch',
284
+ uncover: 'uncoverLeft',
285
+ vortex: 'vortex',
286
+ warp: 'warp',
287
+ wedge: 'wedge',
288
+ wheel: 'wheel',
289
+ wheelReverse: 'wheelReverse',
290
+ wind: 'wind',
291
+ window: 'window',
292
+ wipe: 'wipeLeft',
293
+ zoom: 'zoom',
294
+ };
295
+
296
+ function getTransitionVisualClass(
297
+ type: TransitionType,
298
+ direction?: TransitionDirection,
299
+ ): TransitionVisualClass {
300
+ switch (type) {
301
+ case 'randomBars':
302
+ case 'randomBar':
303
+ return axisClass('randomBars', direction, type === 'randomBar' ? 'Horizontal' : 'Vertical');
304
+ case 'comb':
305
+ return axisClass('comb', direction, 'Horizontal');
306
+ case 'blinds':
307
+ return axisClass('blinds', direction, 'Horizontal');
308
+ case 'split':
309
+ return axisClass('split', direction, 'Horizontal');
310
+ case 'push':
311
+ return directionalClass('push', direction, 'Left');
312
+ case 'wipe':
313
+ return directionalClass('wipe', direction, 'Left');
314
+ case 'cover':
315
+ return directionalClass('cover', direction, 'Left');
316
+ case 'uncover':
317
+ return directionalClass('uncover', direction, 'Left');
318
+ case 'pull':
319
+ return directionalClass('pull', direction, 'Left');
320
+ case 'reveal':
321
+ return directionalClass('reveal', direction, 'Left');
322
+ case 'strips':
323
+ return directionalClass('strips', direction, 'Left');
324
+ case 'pan':
325
+ return directionalClass('pan', direction, 'Left');
326
+ default:
327
+ return TRANSITION_VISUAL_CLASS_BY_TYPE[type];
328
+ }
329
+ }
330
+
331
+ function directionalClass(
332
+ prefix: 'cover' | 'pan' | 'pull' | 'push' | 'reveal' | 'strips' | 'uncover' | 'wipe',
333
+ direction: TransitionDirection | undefined,
334
+ fallback: 'Left' | 'Right' | 'Up' | 'Down',
335
+ ): TransitionVisualClass {
336
+ const suffix = directionSuffix(direction, fallback);
337
+ return `${prefix}${suffix}` as TransitionVisualClass;
338
+ }
339
+
340
+ function axisClass(
341
+ prefix: 'blinds' | 'comb' | 'randomBars' | 'split',
342
+ direction: TransitionDirection | undefined,
343
+ fallback: 'Horizontal' | 'Vertical',
344
+ ): TransitionVisualClass {
345
+ const suffix = axisSuffix(direction, fallback);
346
+ return `${prefix}${suffix}` as TransitionVisualClass;
347
+ }
348
+
349
+ function directionSuffix(
350
+ direction: TransitionDirection | undefined,
351
+ fallback: 'Left' | 'Right' | 'Up' | 'Down',
352
+ ): 'Left' | 'Right' | 'Up' | 'Down' {
353
+ switch (direction) {
354
+ case 'right':
355
+ return 'Right';
356
+ case 'up':
357
+ return 'Up';
358
+ case 'down':
359
+ return 'Down';
360
+ case 'left':
361
+ default:
362
+ return fallback;
363
+ }
364
+ }
365
+
366
+ function axisSuffix(
367
+ direction: TransitionDirection | undefined,
368
+ fallback: 'Horizontal' | 'Vertical',
369
+ ): 'Horizontal' | 'Vertical' {
370
+ switch (direction) {
371
+ case 'vertical':
372
+ case 'up':
373
+ case 'down':
374
+ return 'Vertical';
375
+ case 'horizontal':
376
+ case 'left':
377
+ case 'right':
378
+ return 'Horizontal';
379
+ default:
380
+ return fallback;
381
+ }
118
382
  }
119
383
 
120
384
  /**
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Render-style post-pass — applies a theme's mechanical motion defaults to
3
+ * template-generated layers.
4
+ *
5
+ * This is the intent-free half of theme motion (see `themedEntrance` in
6
+ * [themeUtils.ts](./themeUtils.ts) for the intent-bearing half): it never
7
+ * decides *what kind* of entrance a layer gets, only
8
+ *
9
+ * 1. scales existing animation durations/delays by the theme's
10
+ * `style.animationSpeed` multiplier (1.4 = calmer/slower, 0.7 = snappier,
11
+ * 1.0 = no-op), and
12
+ * 2. gives full-bleed cover imagery with no authored animation a gentle,
13
+ * deterministic Ken Burns when the theme opts into ambient motion.
14
+ *
15
+ * Applied ONLY to template-generated layers — raw authored `block.layers`
16
+ * are never touched, and an explicit `animation` (including
17
+ * `{ type: 'none' }`) always wins.
18
+ */
19
+
20
+ import type { Layer, Animation } from '../../schemas/Doc.js';
21
+ import type { Theme } from '../../schemas/Theme.js';
22
+ import { SeededRandom, hashString } from '../../random/index.js';
23
+
24
+ /** The gentle Ken Burns variants ambient motion rotates between. */
25
+ const AMBIENT_VARIANTS: readonly Animation[] = [
26
+ { type: 'slowZoom', direction: 'in' },
27
+ { type: 'slowZoom', direction: 'out' },
28
+ { type: 'slowZoom', panDirection: 'left' },
29
+ { type: 'slowZoom', panDirection: 'right' },
30
+ ];
31
+
32
+ /** Minimum ambient Ken Burns duration — short blocks shouldn't rush the pan. */
33
+ const MIN_AMBIENT_DURATION_SECONDS = 8;
34
+
35
+ function isZeroish(v: number | string | undefined): boolean {
36
+ return v === 0 || v === '0' || v === '0%';
37
+ }
38
+
39
+ /**
40
+ * Full-bleed cover imagery is the only shape we can safely blanket-animate:
41
+ * anything smaller (strips, insets, grid cells) was placed deliberately and
42
+ * a zoom would fight the composition.
43
+ */
44
+ function isFullBleedCoverMedia(layer: Layer): boolean {
45
+ if (layer.type !== 'image' && layer.type !== 'video') return false;
46
+ const { position } = layer;
47
+ if (!isZeroish(position.x) || !isZeroish(position.y)) return false;
48
+ if (position.width !== '100%' || position.height !== '100%') return false;
49
+ const fit = (layer.content as { fit?: string }).fit;
50
+ return fit === undefined || fit === 'cover';
51
+ }
52
+
53
+ /**
54
+ * Whether the theme asks for ambient Ken Burns on otherwise-static imagery.
55
+ * `defaultImageAnimation: 'slowZoom'` is treated as an alias for the boolean
56
+ * (that's what themes authoring it were reaching for).
57
+ */
58
+ export function themeWantsAmbientMotion(theme: Theme): boolean {
59
+ return (
60
+ theme.renderStyle.ambientMotion === true ||
61
+ theme.renderStyle.defaultImageAnimation === 'slowZoom'
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Apply the theme's mechanical motion defaults to a block's
67
+ * template-generated layers. Pure: returns new layer objects where a change
68
+ * applies and the original objects where none does.
69
+ *
70
+ * @param layers Template-generated layers (never raw authored layers)
71
+ * @param block The source block — supplies the ambient-motion seed and duration
72
+ * @param theme Resolved theme
73
+ */
74
+ export function applyRenderStyleToLayers(
75
+ layers: Layer[],
76
+ block: { id: string; duration?: number },
77
+ theme: Theme,
78
+ ): Layer[] {
79
+ const speed = theme.style.animationSpeed ?? 1.0;
80
+ const ambient = themeWantsAmbientMotion(theme);
81
+ if (speed === 1.0 && !ambient) return layers;
82
+
83
+ return layers.map((layer) => {
84
+ let animation = layer.animation;
85
+
86
+ // Ambient Ken Burns for full-bleed cover imagery with no authored
87
+ // animation. Deterministic per block+layer so re-renders, export
88
+ // frames, and screenshots always agree.
89
+ if (!animation && ambient && isFullBleedCoverMedia(layer)) {
90
+ const rng = new SeededRandom(hashString(`${block.id}:${layer.id}`));
91
+ const variant = rng.pickRequired(AMBIENT_VARIANTS as Animation[]);
92
+ animation = {
93
+ ...variant,
94
+ duration: Math.max(MIN_AMBIENT_DURATION_SECONDS, block.duration ?? 0),
95
+ };
96
+ }
97
+
98
+ if (!animation || animation.type === 'none') {
99
+ return animation === layer.animation ? layer : { ...layer, animation };
100
+ }
101
+
102
+ // Speed scaling — single site so templates never double-scale.
103
+ if (speed !== 1.0) {
104
+ animation = {
105
+ ...animation,
106
+ ...(animation.duration != null ? { duration: animation.duration * speed } : {}),
107
+ ...(animation.delay != null ? { delay: animation.delay * speed } : {}),
108
+ };
109
+ }
110
+
111
+ return animation === layer.animation ? layer : { ...layer, animation };
112
+ });
113
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Image treatment → CSS filter derivation.
3
+ *
4
+ * Treatments are theme-level photographic grades (see `ImageTreatment` in
5
+ * [Doc.ts](../../schemas/Doc.ts)). They compile to plain CSS `filter`
6
+ * functions so the same string works on a foreignObject `<img>`, an SVG
7
+ * `<image>` element, and in headless frame capture — no SVG filter defs to
8
+ * coordinate, no renderer-specific code paths.
9
+ */
10
+
11
+ import type { ImageTreatment } from '../../schemas/Doc.js';
12
+ import { hexHueDegrees } from '../../schemas/colorUtils.js';
13
+
14
+ /** Sepia's intrinsic tint hue (deg) — duotone rotates from here to the target. */
15
+ const SEPIA_HUE_DEGREES = 40;
16
+
17
+ const clamp01 = (v: number) => Math.max(0, Math.min(1, v));
18
+ const r2 = (v: number) => Math.round(v * 100) / 100;
19
+
20
+ /**
21
+ * Build the CSS `filter` value for a treatment and/or blur.
22
+ * Returns `undefined` when there is nothing to apply.
23
+ */
24
+ export function cssFilterForTreatment(
25
+ treatment?: ImageTreatment,
26
+ blur?: number,
27
+ ): string | undefined {
28
+ const parts: string[] = [];
29
+
30
+ if (blur != null && blur > 0) {
31
+ parts.push(`blur(${Math.round(blur)}px)`);
32
+ }
33
+
34
+ if (treatment && treatment.type !== 'none') {
35
+ const s = clamp01(treatment.strength ?? 0.6);
36
+ switch (treatment.type) {
37
+ case 'mono':
38
+ // Archival black & white with a whisper of contrast to keep depth.
39
+ parts.push(`grayscale(${r2(s)})`, `contrast(${r2(1 + 0.05 * s)})`);
40
+ break;
41
+ case 'duotone': {
42
+ // Classic CSS duotone approximation: flatten to gray, tint through
43
+ // sepia (intrinsic hue ~40°), rotate to the target hue, re-saturate.
44
+ const hue = hexHueDegrees(treatment.color ?? '#3d5a80');
45
+ parts.push(
46
+ 'grayscale(1)',
47
+ `sepia(${r2(Math.max(0.35, s))})`,
48
+ `hue-rotate(${Math.round(hue - SEPIA_HUE_DEGREES)}deg)`,
49
+ `saturate(${r2(1 + s)})`,
50
+ );
51
+ break;
52
+ }
53
+ case 'warm':
54
+ // Partial sepia is a well-behaved warmer; slight saturation lift.
55
+ parts.push(`sepia(${r2(0.4 * s)})`, `saturate(${r2(1 + 0.15 * s)})`);
56
+ break;
57
+ case 'cool':
58
+ // Desaturate a touch and nudge warm tones toward magenta/blue —
59
+ // small rotations read as a grade, large ones wreck hues.
60
+ parts.push(
61
+ `saturate(${r2(1 - 0.25 * s)})`,
62
+ `hue-rotate(${Math.round(-12 * s)}deg)`,
63
+ `contrast(${r2(1 + 0.06 * s)})`,
64
+ );
65
+ break;
66
+ }
67
+ }
68
+
69
+ return parts.length > 0 ? parts.join(' ') : undefined;
70
+ }