@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.
- package/README.md +14 -1
- package/dist/{ContentContainer-DNx460y8.d.ts → ContentContainer-BXUlIf18.d.ts} +6 -2
- package/dist/{Doc-BeSeH2iX.d.ts → Doc-BBVGq1_9.d.ts} +922 -273
- package/dist/{ImageEditDoc-BTIvw0oq.d.ts → ImageEditDoc-FuyGtt6o.d.ts} +11 -4
- package/dist/{chunk-ZTX4PFFT.js → chunk-2OIBZYKP.js} +75 -2
- package/dist/chunk-2OIBZYKP.js.map +1 -0
- package/dist/chunk-2UQOHYE2.js +45 -0
- package/dist/chunk-2UQOHYE2.js.map +1 -0
- package/dist/chunk-6ATE2PSM.js +344 -0
- package/dist/chunk-6ATE2PSM.js.map +1 -0
- package/dist/{chunk-57CVQCFX.js → chunk-A4O7GIWE.js} +74 -23
- package/dist/chunk-A4O7GIWE.js.map +1 -0
- package/dist/{chunk-5WFRKURX.js → chunk-DLXZMS5K.js} +4 -2
- package/dist/chunk-DLXZMS5K.js.map +1 -0
- package/dist/chunk-ET53IIEP.js +1 -0
- package/dist/chunk-ET53IIEP.js.map +1 -0
- package/dist/chunk-HWVFJAAH.js +362 -0
- package/dist/chunk-HWVFJAAH.js.map +1 -0
- package/dist/{chunk-2TMAWB4Y.js → chunk-IMSCRLLV.js} +1 -1
- package/dist/chunk-IMSCRLLV.js.map +1 -0
- package/dist/{chunk-B4EEON3N.js → chunk-KKNUBQ6Y.js} +536 -103
- package/dist/chunk-KKNUBQ6Y.js.map +1 -0
- package/dist/chunk-LDQ2HJIX.js +596 -0
- package/dist/chunk-LDQ2HJIX.js.map +1 -0
- package/dist/chunk-LH7I6SH7.js +7208 -0
- package/dist/chunk-LH7I6SH7.js.map +1 -0
- package/dist/chunk-PGGGKYWG.js +1 -0
- package/dist/chunk-PGGGKYWG.js.map +1 -0
- package/dist/{chunk-7UDSRZKG.js → chunk-Q3ROPT5H.js} +35 -14
- package/dist/chunk-Q3ROPT5H.js.map +1 -0
- package/dist/chunk-QE7TWGVT.js +178 -0
- package/dist/chunk-QE7TWGVT.js.map +1 -0
- package/dist/chunk-RUDYOTA6.js +345 -0
- package/dist/chunk-RUDYOTA6.js.map +1 -0
- package/dist/{chunk-K32VJONL.js → chunk-T5UK6YOB.js} +41 -2
- package/dist/chunk-T5UK6YOB.js.map +1 -0
- package/dist/{chunk-3K5OG6KI.js → chunk-TEEEILMP.js} +412 -186
- package/dist/chunk-TEEEILMP.js.map +1 -0
- package/dist/{chunk-FR2RBTKO.js → chunk-TQWLI6S2.js} +93 -27
- package/dist/chunk-TQWLI6S2.js.map +1 -0
- package/dist/{chunk-WMOI7FN3.js → chunk-UY7KGQ5R.js} +12 -2
- package/dist/{chunk-WMOI7FN3.js.map → chunk-UY7KGQ5R.js.map} +1 -1
- package/dist/{chunk-3E5F2XMR.js → chunk-VWUFZ6ZG.js} +29 -2
- package/dist/chunk-VWUFZ6ZG.js.map +1 -0
- package/dist/chunk-ZLQKNOZQ.js +1 -0
- package/dist/chunk-ZLQKNOZQ.js.map +1 -0
- package/dist/doc/index.d.ts +5 -5
- package/dist/doc/index.js +135 -8
- package/dist/generate/index.d.ts +3 -74
- package/dist/generate/index.js +2 -7
- package/dist/icons/index.d.ts +3 -26
- package/dist/icons/index.js +13 -0
- package/dist/icons/inlineIconMarker.d.ts +1 -0
- package/dist/icons/inlineIconMarker.js +15 -0
- package/dist/icons/inlineIconMarker.js.map +1 -0
- package/dist/imageEdit/index.d.ts +12 -6
- package/dist/imageEdit/index.js +4 -1
- package/dist/index.d.ts +14 -11
- package/dist/index.js +265 -37
- package/dist/inlineIconMarker-CiE8Hlvz.d.ts +59 -0
- package/dist/jsonForm/index.d.ts +42 -1
- package/dist/jsonForm/index.js +9 -1
- package/dist/markdown/index.d.ts +228 -3
- package/dist/markdown/index.js +42 -7
- package/dist/recommend/index.d.ts +1 -1
- package/dist/recommend/index.js +3 -2
- package/dist/schemas/index.d.ts +28 -8
- package/dist/schemas/index.js +36 -8
- package/dist/spatial/index.d.ts +9 -4
- package/dist/spatial/index.js +1 -1
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.js +1 -1
- package/dist/story/index.d.ts +1021 -41
- package/dist/story/index.js +135 -8
- package/dist/{themeLibrary-BHjJ0Qx6.d.ts → themeLibrary-CehcJhzz.d.ts} +1 -1
- package/dist/timing/index.js +3 -5
- package/dist/transform/index.d.ts +48 -10
- package/dist/transform/index.js +17 -481
- package/dist/transform/index.js.map +1 -1
- package/dist/{types-BdZkdJ3z.d.ts → types-DlAZ7MW4.d.ts} +115 -1
- package/dist/versions/index.d.ts +1 -1
- package/package.json +6 -1
- package/src/__tests__/animationUtils.test.ts +40 -0
- package/src/__tests__/annotationCoercion.test.ts +243 -0
- package/src/__tests__/applyRenderStyle.test.ts +146 -0
- package/src/__tests__/atmosphereLayers.test.ts +108 -0
- package/src/__tests__/attrTokens.test.ts +162 -0
- package/src/__tests__/contentExtractor.test.ts +60 -0
- package/src/__tests__/coverBlock.test.ts +46 -1
- package/src/__tests__/customTemplateSample.test.ts +144 -0
- package/src/__tests__/customTemplatesFrontmatter.test.ts +236 -0
- package/src/__tests__/customThemesFrontmatter.test.ts +145 -0
- package/src/__tests__/diagramBlock.test.ts +122 -0
- package/src/__tests__/diagramLayout.test.ts +134 -0
- package/src/__tests__/drawing.test.ts +109 -0
- package/src/__tests__/drawingBlock.test.ts +124 -0
- package/src/__tests__/drawingLayout.test.ts +171 -0
- package/src/__tests__/geohash.test.ts +6 -5
- package/src/__tests__/getLayers.test.ts +6 -2
- package/src/__tests__/getLayersFallback.test.ts +103 -0
- package/src/__tests__/imageEditExportSvg.test.ts +70 -0
- package/src/__tests__/imageEditVersions.test.ts +72 -0
- package/src/__tests__/inlineIconMarker.test.ts +36 -0
- package/src/__tests__/inputDescriptors.test.ts +162 -0
- package/src/__tests__/jsonForm.tokens.test.ts +84 -0
- package/src/__tests__/layoutBlock.test.ts +83 -0
- package/src/__tests__/layoutLayout.test.ts +169 -0
- package/src/__tests__/markdown.test.ts +87 -0
- package/src/__tests__/markdownSanitize.test.ts +79 -0
- package/src/__tests__/markdownToDoc.test.ts +391 -2
- package/src/__tests__/mediaAnnotations.test.ts +91 -0
- package/src/__tests__/mediaSchedule.test.ts +118 -0
- package/src/__tests__/pandocAttrParse.test.ts +187 -0
- package/src/__tests__/resolveDocTheme.test.ts +55 -0
- package/src/__tests__/rootExports.test.ts +43 -0
- package/src/__tests__/shapeGeometry.test.ts +89 -0
- package/src/__tests__/shapeVocabulary.test.ts +133 -0
- package/src/__tests__/structuredData.test.ts +249 -0
- package/src/__tests__/templateAnnotationParse.test.ts +108 -0
- package/src/__tests__/templateMetadata.test.ts +31 -0
- package/src/__tests__/templates.test.ts +257 -4
- package/src/__tests__/themeCompile.test.ts +42 -0
- package/src/__tests__/themeValidator.test.ts +6 -0
- package/src/__tests__/transformV2.test.ts +137 -0
- package/src/__tests__/transitionNormalize.test.ts +61 -0
- package/src/__tests__/validateDoc.test.ts +260 -0
- package/src/doc/annotationBlocks.ts +117 -0
- package/src/doc/customTemplatesFrontmatter.ts +314 -0
- package/src/doc/customThemesFrontmatter.ts +95 -0
- package/src/doc/docToMarkdown.ts +104 -5
- package/src/doc/getLayers.ts +49 -17
- package/src/doc/index.ts +47 -1
- package/src/doc/markdownToDoc.ts +411 -13
- package/src/doc/mediaAnnotations.ts +100 -0
- package/src/doc/resolveDocTheme.ts +55 -0
- package/src/doc/standaloneAnnotation.ts +58 -0
- package/src/doc/structuredData.ts +301 -0
- package/src/doc/templateInputs.ts +240 -0
- package/src/doc/templates/__tests__/customTemplate.test.ts +170 -0
- package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +110 -0
- package/src/doc/templates/accentImage.ts +30 -6
- package/src/doc/templates/captionUtils.ts +23 -0
- package/src/doc/templates/comparisonBar.ts +33 -18
- package/src/doc/templates/coverBlock.ts +35 -5
- package/src/doc/templates/customTemplate.ts +51 -0
- package/src/doc/templates/dataTable.ts +37 -19
- package/src/doc/templates/dateEvent.ts +41 -20
- package/src/doc/templates/definitionCard.ts +26 -18
- package/src/doc/templates/diagramBlock.ts +204 -0
- package/src/doc/templates/diagramLayout.ts +159 -0
- package/src/doc/templates/drawingBlock.ts +325 -0
- package/src/doc/templates/drawingLayout.ts +539 -0
- package/src/doc/templates/factCard.ts +54 -26
- package/src/doc/templates/fallbackBlock.ts +123 -0
- package/src/doc/templates/featureBlock.ts +20 -13
- package/src/doc/templates/fullBleedQuote.ts +18 -8
- package/src/doc/templates/imageWithCaption.ts +30 -15
- package/src/doc/templates/index.ts +225 -46
- package/src/doc/templates/inputDescriptors.ts +432 -0
- package/src/doc/templates/layoutBlock.ts +43 -0
- package/src/doc/templates/layoutLayout.ts +243 -0
- package/src/doc/templates/listBlock.ts +56 -30
- package/src/doc/templates/mapBlock.ts +15 -12
- package/src/doc/templates/metadata.ts +120 -0
- package/src/doc/templates/persistentLayers.ts +129 -11
- package/src/doc/templates/photoGrid.ts +22 -34
- package/src/doc/templates/pullQuote.ts +30 -12
- package/src/doc/templates/quoteBlock.ts +54 -29
- package/src/doc/templates/sectionHeader.ts +25 -11
- package/src/doc/templates/statHighlight.ts +39 -22
- package/src/doc/templates/titleBlock.ts +59 -21
- package/src/doc/templates/tokens/__tests__/resolveTokens.test.ts +487 -0
- package/src/doc/templates/tokens/resolveTokens.ts +453 -0
- package/src/doc/templates/twoColumn.ts +48 -30
- package/src/doc/templates/videoPullQuote.ts +22 -12
- package/src/doc/templates/videoWithCaption.ts +21 -15
- package/src/doc/utils/animationUtils.ts +267 -3
- package/src/doc/utils/applyRenderStyle.ts +113 -0
- package/src/doc/utils/imageTreatment.ts +70 -0
- package/src/doc/utils/nearest.ts +59 -0
- package/src/doc/utils/shapeGeometry.ts +464 -0
- package/src/doc/utils/themeUtils.ts +75 -1
- package/src/doc/validate.ts +575 -0
- package/src/generate/contentExtractor.ts +148 -35
- package/src/generate/index.ts +0 -3
- package/src/icons/index.ts +8 -0
- package/src/icons/inlineIconMarker.ts +73 -0
- package/src/imageEdit/export.ts +85 -2
- package/src/imageEdit/index.ts +1 -0
- package/src/imageEdit/persistence.ts +51 -0
- package/src/index.ts +2 -0
- package/src/jsonForm/index.ts +2 -0
- package/src/jsonForm/tokens.ts +78 -0
- package/src/markdown/annotationCoercion.ts +295 -0
- package/src/markdown/attrTokens.ts +241 -0
- package/src/markdown/convert.ts +229 -26
- package/src/markdown/index.ts +37 -0
- package/src/markdown/sanitize.ts +318 -0
- package/src/markdown/stringify.ts +96 -6
- package/src/markdown/types.ts +66 -0
- package/src/markdown/utils.ts +111 -2
- package/src/recommend/templates.ts +24 -0
- package/src/schemas/BlockTemplates.ts +133 -4
- package/src/schemas/CustomTemplates.ts +126 -0
- package/src/schemas/Doc.ts +387 -65
- package/src/schemas/ImageEditDoc.ts +11 -4
- package/src/schemas/Media.ts +169 -0
- package/src/schemas/Theme.ts +23 -1
- package/src/schemas/Transitions.ts +224 -0
- package/src/schemas/colorUtils.ts +33 -0
- package/src/schemas/fontStacks.ts +5 -2
- package/src/schemas/index.ts +3 -0
- package/src/schemas/themeCompile.ts +24 -5
- package/src/schemas/themeValidator.ts +49 -11
- package/src/schemas/themes/cinematic.json +69 -12
- package/src/schemas/themes/documentary.json +58 -11
- package/src/schemas/themes/gezellig.json +60 -11
- package/src/schemas/themes/magazine.json +54 -9
- package/src/schemas/themes/morning-light.json +52 -9
- package/src/schemas/themes/standard-dark.json +48 -13
- package/src/schemas/themes/standard.json +44 -12
- package/src/schemas/themes/tech-dark.json +65 -12
- package/src/spatial/Geohash.ts +52 -21
- package/src/storage/ContentContainer.ts +6 -2
- package/src/transform/applyTransform.ts +43 -4
- package/src/transform/index.ts +1 -0
- package/src/transform/registry.ts +44 -13
- package/src/transform/styles/dataDriven.ts +2 -1
- package/src/transform/styles/documentary.ts +2 -1
- package/src/transform/styles/magazine.ts +3 -1
- package/src/transform/styles/minimal.ts +2 -1
- package/src/transform/styles/narrative.ts +3 -1
- package/src/transform/templateSelector.ts +87 -11
- package/src/transform/types.ts +35 -2
- package/dist/chunk-2TMAWB4Y.js.map +0 -1
- package/dist/chunk-3E5F2XMR.js.map +0 -1
- package/dist/chunk-3K5OG6KI.js.map +0 -1
- package/dist/chunk-4X3JQXNM.js +0 -35
- package/dist/chunk-4X3JQXNM.js.map +0 -1
- package/dist/chunk-57CVQCFX.js.map +0 -1
- package/dist/chunk-5WFRKURX.js.map +0 -1
- package/dist/chunk-7UDSRZKG.js.map +0 -1
- package/dist/chunk-B4EEON3N.js.map +0 -1
- package/dist/chunk-FR2RBTKO.js.map +0 -1
- package/dist/chunk-H3AXU4MJ.js +0 -179
- package/dist/chunk-H3AXU4MJ.js.map +0 -1
- package/dist/chunk-K32VJONL.js.map +0 -1
- package/dist/chunk-KSWJR66U.js +0 -3706
- package/dist/chunk-KSWJR66U.js.map +0 -1
- package/dist/chunk-MYIH7FWD.js +0 -145
- package/dist/chunk-MYIH7FWD.js.map +0 -1
- package/dist/chunk-ZTX4PFFT.js.map +0 -1
- package/src/__tests__/slideshowGenerator.test.ts +0 -128
- package/src/generate/slideshowGenerator.ts +0 -273
|
@@ -14,9 +14,15 @@
|
|
|
14
14
|
"warning": "#ff5252"
|
|
15
15
|
},
|
|
16
16
|
"typography": {
|
|
17
|
-
"bodyFont": {
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
"bodyFont": {
|
|
18
|
+
"stackId": "ibm-plex-sans"
|
|
19
|
+
},
|
|
20
|
+
"titleFont": {
|
|
21
|
+
"stackId": "jetbrains-mono"
|
|
22
|
+
},
|
|
23
|
+
"monoFont": {
|
|
24
|
+
"stackId": "jetbrains-mono"
|
|
25
|
+
},
|
|
20
26
|
"titleWeight": "bold",
|
|
21
27
|
"titleScale": 0.9,
|
|
22
28
|
"lineHeight": 1.5
|
|
@@ -25,24 +31,71 @@
|
|
|
25
31
|
"textShadow": true,
|
|
26
32
|
"overlayOpacity": 0.55,
|
|
27
33
|
"animationSpeed": 0.9,
|
|
28
|
-
"borderRadius": 2
|
|
34
|
+
"borderRadius": 2,
|
|
35
|
+
"imageTreatment": {
|
|
36
|
+
"type": "cool",
|
|
37
|
+
"strength": 0.45
|
|
38
|
+
}
|
|
29
39
|
},
|
|
30
40
|
"renderStyle": {
|
|
31
41
|
"name": "tech-dark",
|
|
32
42
|
"defaultTextAnimation": "typewriter",
|
|
33
43
|
"defaultImageAnimation": "slowZoom",
|
|
34
44
|
"ambientMotion": true,
|
|
35
|
-
"defaultTransition": {
|
|
45
|
+
"defaultTransition": {
|
|
46
|
+
"type": "dissolve",
|
|
47
|
+
"duration": 0.4
|
|
48
|
+
},
|
|
36
49
|
"templateHints": {
|
|
37
|
-
"statHighlight": {
|
|
50
|
+
"statHighlight": {
|
|
51
|
+
"entrance": "dramatic"
|
|
52
|
+
}
|
|
38
53
|
}
|
|
39
54
|
},
|
|
40
55
|
"colorSchemes": {
|
|
41
|
-
"blue": {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"
|
|
56
|
+
"blue": {
|
|
57
|
+
"bg": "#0d1b2a",
|
|
58
|
+
"text": "#00b0ff",
|
|
59
|
+
"accent": "#40c4ff"
|
|
60
|
+
},
|
|
61
|
+
"green": {
|
|
62
|
+
"bg": "#0d1b0d",
|
|
63
|
+
"text": "#00e676",
|
|
64
|
+
"accent": "#69f0ae"
|
|
65
|
+
},
|
|
66
|
+
"purple": {
|
|
67
|
+
"bg": "#1a0d2e",
|
|
68
|
+
"text": "#d500f9",
|
|
69
|
+
"accent": "#ea80fc"
|
|
70
|
+
},
|
|
71
|
+
"red": {
|
|
72
|
+
"bg": "#2e0d0d",
|
|
73
|
+
"text": "#ff5252",
|
|
74
|
+
"accent": "#ff8a80"
|
|
75
|
+
},
|
|
76
|
+
"orange": {
|
|
77
|
+
"bg": "#2e1a0d",
|
|
78
|
+
"text": "#ff9100",
|
|
79
|
+
"accent": "#ffab40"
|
|
80
|
+
},
|
|
81
|
+
"teal": {
|
|
82
|
+
"bg": "#0d2e2e",
|
|
83
|
+
"text": "#1de9b6",
|
|
84
|
+
"accent": "#64ffda"
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"persistentLayers": {
|
|
88
|
+
"topLayers": [
|
|
89
|
+
{
|
|
90
|
+
"template": "patternBackground",
|
|
91
|
+
"config": {
|
|
92
|
+
"type": "patternBackground",
|
|
93
|
+
"pattern": "grid",
|
|
94
|
+
"color": "#00e5ff",
|
|
95
|
+
"opacity": 0.04,
|
|
96
|
+
"scale": 1.5
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
]
|
|
47
100
|
}
|
|
48
101
|
}
|
package/src/spatial/Geohash.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import ngeohash from 'ngeohash';
|
|
18
|
+
import { haversineDistance } from './Haversine.js';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Encode latitude/longitude to a geohash string.
|
|
@@ -64,9 +65,7 @@ export function getNeighbors(hash: string): string[] {
|
|
|
64
65
|
* // => ['c23n', 'c23p', 'c23q', 'c23j', 'c23m', 'c23k', 'c23h', 'c23e', 'c23s']
|
|
65
66
|
*/
|
|
66
67
|
export function getGeohash4Neighbors(geohash4: string): string[] {
|
|
67
|
-
if (geohash4.length !== 4)
|
|
68
|
-
throw new Error(`getGeohash4Neighbors requires 4-char geohash, got: ${geohash4}`);
|
|
69
|
-
}
|
|
68
|
+
if (geohash4.length !== 4) return [];
|
|
70
69
|
const neighbors = getNeighbors(geohash4);
|
|
71
70
|
return [geohash4, ...neighbors];
|
|
72
71
|
}
|
|
@@ -92,18 +91,20 @@ export function getGeohashPrefix(geohash: string, precision: number): string {
|
|
|
92
91
|
* performance issues.
|
|
93
92
|
*/
|
|
94
93
|
export function geohashToHierarchicalPath(geohash4: string): string {
|
|
95
|
-
if (geohash4.length !== 4)
|
|
96
|
-
throw new Error(`geohashToHierarchicalPath requires 4-char geohash, got: ${geohash4}`);
|
|
97
|
-
}
|
|
94
|
+
if (geohash4.length !== 4) return '';
|
|
98
95
|
const [c1, c2, c3, c4] = geohash4.split('');
|
|
99
96
|
return `${c1}/${c2}/${c3}/${c4}`;
|
|
100
97
|
}
|
|
101
98
|
|
|
102
99
|
/**
|
|
103
|
-
* Compute all geohash cells along the path between two geohash cells.
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
100
|
+
* Compute all geohash cells along the great-circle path between two geohash cells.
|
|
101
|
+
* Samples points along the spherical arc from `from` to `to`, encoding each to a
|
|
102
|
+
* geohash at the given precision, and returns the deduplicated list of intermediate
|
|
103
|
+
* cells (excluding `from` and `to`).
|
|
104
|
+
*
|
|
105
|
+
* Uses spherical linear interpolation (SLERP) via 3D unit vectors, which correctly
|
|
106
|
+
* handles antimeridian crossings and polar paths — both of which flat lat/lng
|
|
107
|
+
* interpolation gets wrong.
|
|
107
108
|
*/
|
|
108
109
|
export function getGeohashPath(from: string, to: string, precision = 4): string[] {
|
|
109
110
|
if (from === to) return [];
|
|
@@ -111,15 +112,34 @@ export function getGeohashPath(from: string, to: string, precision = 4): string[
|
|
|
111
112
|
const fromCenter = decodeGeohash(from);
|
|
112
113
|
const toCenter = decodeGeohash(to);
|
|
113
114
|
|
|
114
|
-
//
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const approxKm = Math.sqrt((dLat * 111) ** 2 + (dLng * 111 * Math.cos(midLatRad)) ** 2);
|
|
115
|
+
// Use haversine for accurate great-circle distance (step sizing)
|
|
116
|
+
const distKm = haversineDistance(
|
|
117
|
+
{ lat: fromCenter.lat, lng: fromCenter.lng },
|
|
118
|
+
{ lat: toCenter.lat, lng: toCenter.lng },
|
|
119
|
+
);
|
|
120
120
|
|
|
121
121
|
// Step every ~15km to avoid skipping any geohash4 cell (~39x19km)
|
|
122
|
-
const steps = Math.max(2, Math.ceil(
|
|
122
|
+
const steps = Math.max(2, Math.ceil(distKm / 15));
|
|
123
|
+
|
|
124
|
+
// Convert endpoints to unit Cartesian vectors for SLERP
|
|
125
|
+
const φ1 = (fromCenter.lat * Math.PI) / 180;
|
|
126
|
+
const λ1 = (fromCenter.lng * Math.PI) / 180;
|
|
127
|
+
const φ2 = (toCenter.lat * Math.PI) / 180;
|
|
128
|
+
const λ2 = (toCenter.lng * Math.PI) / 180;
|
|
129
|
+
|
|
130
|
+
const x1 = Math.cos(φ1) * Math.cos(λ1);
|
|
131
|
+
const y1 = Math.cos(φ1) * Math.sin(λ1);
|
|
132
|
+
const z1 = Math.sin(φ1);
|
|
133
|
+
const x2 = Math.cos(φ2) * Math.cos(λ2);
|
|
134
|
+
const y2 = Math.cos(φ2) * Math.sin(λ2);
|
|
135
|
+
const z2 = Math.sin(φ2);
|
|
136
|
+
|
|
137
|
+
// Central angle between the two points
|
|
138
|
+
const dot = Math.max(-1, Math.min(1, x1 * x2 + y1 * y2 + z1 * z2));
|
|
139
|
+
const d = Math.acos(dot);
|
|
140
|
+
// Near-antipodal fallback: sin(d)≈0 means the great circle is undefined.
|
|
141
|
+
// Extremely unlikely for geohash path use; fall back to linear lat/lng.
|
|
142
|
+
const useLinear = Math.abs(Math.sin(d)) < 1e-10;
|
|
123
143
|
|
|
124
144
|
const seen = new Set<string>();
|
|
125
145
|
seen.add(from);
|
|
@@ -128,8 +148,20 @@ export function getGeohashPath(from: string, to: string, precision = 4): string[
|
|
|
128
148
|
const path: string[] = [];
|
|
129
149
|
for (let i = 1; i < steps; i++) {
|
|
130
150
|
const t = i / steps;
|
|
131
|
-
|
|
132
|
-
|
|
151
|
+
let lat: number;
|
|
152
|
+
let lng: number;
|
|
153
|
+
if (useLinear) {
|
|
154
|
+
lat = fromCenter.lat + t * (toCenter.lat - fromCenter.lat);
|
|
155
|
+
lng = fromCenter.lng + t * (toCenter.lng - fromCenter.lng);
|
|
156
|
+
} else {
|
|
157
|
+
const A = Math.sin((1 - t) * d) / Math.sin(d);
|
|
158
|
+
const B = Math.sin(t * d) / Math.sin(d);
|
|
159
|
+
const x = A * x1 + B * x2;
|
|
160
|
+
const y = A * y1 + B * y2;
|
|
161
|
+
const z = A * z1 + B * z2;
|
|
162
|
+
lat = (Math.atan2(z, Math.sqrt(x * x + y * y)) * 180) / Math.PI;
|
|
163
|
+
lng = (Math.atan2(y, x) * 180) / Math.PI;
|
|
164
|
+
}
|
|
133
165
|
const hash = encodeGeohash(lat, lng, precision);
|
|
134
166
|
if (!seen.has(hash)) {
|
|
135
167
|
seen.add(hash);
|
|
@@ -163,6 +195,5 @@ export function geohashOverlapsBounds(
|
|
|
163
195
|
);
|
|
164
196
|
}
|
|
165
197
|
|
|
166
|
-
// haversineDistance is now in shared/spatial/Haversine.ts (canonical location).
|
|
167
198
|
// Re-export for backwards compatibility with existing imports.
|
|
168
|
-
export { haversineDistance }
|
|
199
|
+
export { haversineDistance };
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* Paths are forward-slash separated strings relative to the container root
|
|
10
10
|
* (e.g., 'images/hero.jpg', 'index.md'). No leading slash.
|
|
11
11
|
*
|
|
12
|
-
* Implementations:
|
|
12
|
+
* Implementations (in core):
|
|
13
13
|
* - MemoryContentContainer — in-memory Map (for zip import, tests, transient use)
|
|
14
|
-
* -
|
|
14
|
+
* - ScopedContentContainer — a prefix-scoped view onto a parent container
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -44,6 +44,10 @@ export interface ContentContainer {
|
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* List files in the container.
|
|
47
|
+
*
|
|
48
|
+
* Returns file **entries** (`ContentEntry[]` with `.path`/`.mimeType`/`.size`),
|
|
49
|
+
* NOT bare path strings — despite the name. Read `entry.path` for the path;
|
|
50
|
+
* calling string methods (e.g. `.startsWith`) directly on an entry will throw.
|
|
47
51
|
* @param prefix — Optional path prefix to filter by (e.g., 'images/')
|
|
48
52
|
*/
|
|
49
53
|
listFiles(prefix?: string): Promise<ContentEntry[]>;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { Doc } from '../schemas/Doc.js';
|
|
12
|
+
import type { DocBlock, TitleBlockInput, SectionHeaderInput } from '../schemas/BlockTemplates.js';
|
|
12
13
|
import type { TransformStyleId, TransformOptions, TransformResult } from './types.js';
|
|
13
14
|
import { resolveTransformStyle } from './registry.js';
|
|
14
15
|
import { analyzeBlocks, extractDocImages } from './blockAnalyzer.js';
|
|
@@ -62,14 +63,43 @@ export function applyTransform(
|
|
|
62
63
|
// 2. Select extractions and build template blocks
|
|
63
64
|
const selection = selectAndBuild(analyzed, config, images, seed);
|
|
64
65
|
|
|
66
|
+
// 2b. Pacing bookends: an opening title beat and/or a closing beat.
|
|
67
|
+
let paced: DocBlock[] = selection.blocks;
|
|
68
|
+
let pacingInserted = 0;
|
|
69
|
+
const docTitle = firstTitle(doc);
|
|
70
|
+
if (config.pacing?.intro && docTitle && !doc.startBlock) {
|
|
71
|
+
const intro: TitleBlockInput = {
|
|
72
|
+
template: 'title',
|
|
73
|
+
id: 'transform-intro',
|
|
74
|
+
duration: 4,
|
|
75
|
+
audioSegment: 0,
|
|
76
|
+
title: docTitle,
|
|
77
|
+
};
|
|
78
|
+
paced = [intro, ...paced];
|
|
79
|
+
pacingInserted++;
|
|
80
|
+
}
|
|
81
|
+
if (config.pacing?.outro && docTitle) {
|
|
82
|
+
const outro: SectionHeaderInput = {
|
|
83
|
+
template: 'sectionHeader',
|
|
84
|
+
id: 'transform-outro',
|
|
85
|
+
duration: 3,
|
|
86
|
+
audioSegment: 0,
|
|
87
|
+
title: docTitle,
|
|
88
|
+
colorScheme: config.colorSchemes[0],
|
|
89
|
+
};
|
|
90
|
+
paced = [...paced, outro];
|
|
91
|
+
pacingInserted++;
|
|
92
|
+
}
|
|
93
|
+
|
|
65
94
|
// 3. Allocate timing across the new block sequence
|
|
66
|
-
const timedBlocks = allocateTiming(
|
|
95
|
+
const timedBlocks = allocateTiming(paced, doc.duration);
|
|
67
96
|
|
|
68
|
-
// 4. Assemble the output Doc
|
|
97
|
+
// 4. Assemble the output Doc. A style's suggested theme applies only
|
|
98
|
+
// when neither the caller nor the doc declares one.
|
|
69
99
|
const transformedDoc: Doc = {
|
|
70
100
|
...doc,
|
|
71
101
|
blocks: timedBlocks,
|
|
72
|
-
themeId: options?.themeId ?? doc.themeId,
|
|
102
|
+
themeId: options?.themeId ?? doc.themeId ?? config.suggestedThemeId,
|
|
73
103
|
};
|
|
74
104
|
|
|
75
105
|
return {
|
|
@@ -77,7 +107,16 @@ export function applyTransform(
|
|
|
77
107
|
stats: {
|
|
78
108
|
totalInputBlocks: doc.blocks.length,
|
|
79
109
|
transformedBlocks: selection.transformedCount,
|
|
80
|
-
insertedBlocks: selection.insertedCount,
|
|
110
|
+
insertedBlocks: selection.insertedCount + pacingInserted,
|
|
81
111
|
},
|
|
82
112
|
};
|
|
83
113
|
}
|
|
114
|
+
|
|
115
|
+
/** First non-empty block title (the doc's working title). */
|
|
116
|
+
function firstTitle(doc: Doc): string | undefined {
|
|
117
|
+
if (doc.startBlock?.title) return doc.startBlock.title;
|
|
118
|
+
for (const block of doc.blocks) {
|
|
119
|
+
if (block.title) return block.title;
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
package/src/transform/index.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Transform Style Registry
|
|
3
3
|
*
|
|
4
|
-
* Central registry of built-in transform styles
|
|
5
|
-
*
|
|
4
|
+
* Central registry of built-in transform styles plus a dynamic
|
|
5
|
+
* registration API. Follows the same pattern as themeLibrary.ts /
|
|
6
|
+
* `registerTheme` — registered styles take precedence over built-ins,
|
|
7
|
+
* unknown ids fall back to the default.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import type { TransformStyleConfig, TransformStyleSummary } from './types.js';
|
|
@@ -12,7 +14,7 @@ import { dataDrivenStyle } from './styles/dataDriven.js';
|
|
|
12
14
|
import { narrativeStyle } from './styles/narrative.js';
|
|
13
15
|
import { minimalStyle } from './styles/minimal.js';
|
|
14
16
|
|
|
15
|
-
/** All
|
|
17
|
+
/** All built-in transform styles, keyed by id. */
|
|
16
18
|
const TRANSFORM_STYLES: Record<string, TransformStyleConfig> = {
|
|
17
19
|
[documentaryStyle.id]: documentaryStyle,
|
|
18
20
|
[magazineStyle.id]: magazineStyle,
|
|
@@ -21,27 +23,56 @@ const TRANSFORM_STYLES: Record<string, TransformStyleConfig> = {
|
|
|
21
23
|
[minimalStyle.id]: minimalStyle,
|
|
22
24
|
};
|
|
23
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Id aliases: the data-driven style's file/export are named `dataDriven`
|
|
28
|
+
* but its registered id is hyphenated — accept both.
|
|
29
|
+
*/
|
|
30
|
+
const STYLE_ALIASES: Record<string, string> = {
|
|
31
|
+
dataDriven: 'data-driven',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Host-registered custom styles (registered ids win over built-ins). */
|
|
35
|
+
const registeredStyles = new Map<string, TransformStyleConfig>();
|
|
36
|
+
|
|
24
37
|
/** Default style used when no id is provided. */
|
|
25
38
|
export const DEFAULT_TRANSFORM_STYLE_ID = 'documentary';
|
|
26
39
|
|
|
27
40
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
41
|
+
* Register (or replace) a custom transform style at runtime. Hosts use
|
|
42
|
+
* this the same way they use `registerTheme` — to add product-specific
|
|
43
|
+
* styles without forking the registry.
|
|
44
|
+
*/
|
|
45
|
+
export function registerTransformStyle(style: TransformStyleConfig): void {
|
|
46
|
+
registeredStyles.set(style.id, style);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Remove a previously registered custom style. */
|
|
50
|
+
export function unregisterTransformStyle(id: string): void {
|
|
51
|
+
registeredStyles.delete(id);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resolve a transform style by id. Registered styles win over built-ins;
|
|
56
|
+
* aliases are honored; unknown ids fall back to the default style.
|
|
30
57
|
*/
|
|
31
58
|
export function resolveTransformStyle(id: string): TransformStyleConfig {
|
|
32
|
-
|
|
59
|
+
const canonical = STYLE_ALIASES[id] ?? id;
|
|
60
|
+
return (
|
|
61
|
+
registeredStyles.get(canonical) ??
|
|
62
|
+
TRANSFORM_STYLES[canonical] ??
|
|
63
|
+
TRANSFORM_STYLES[DEFAULT_TRANSFORM_STYLE_ID]
|
|
64
|
+
);
|
|
33
65
|
}
|
|
34
66
|
|
|
35
|
-
/** Get all
|
|
67
|
+
/** Get all known style ids (built-ins plus registered). */
|
|
36
68
|
export function getTransformStyleIds(): string[] {
|
|
37
|
-
return Object.keys(TRANSFORM_STYLES);
|
|
69
|
+
return Array.from(new Set([...Object.keys(TRANSFORM_STYLES), ...registeredStyles.keys()]));
|
|
38
70
|
}
|
|
39
71
|
|
|
40
72
|
/** Get summary info for all styles (for UI dropdowns). */
|
|
41
73
|
export function getTransformStyleSummaries(): TransformStyleSummary[] {
|
|
42
|
-
return
|
|
43
|
-
id
|
|
44
|
-
name,
|
|
45
|
-
|
|
46
|
-
}));
|
|
74
|
+
return getTransformStyleIds().map((id) => {
|
|
75
|
+
const { name, description } = resolveTransformStyle(id);
|
|
76
|
+
return { id, name, description };
|
|
77
|
+
});
|
|
47
78
|
}
|
|
@@ -11,6 +11,7 @@ export const dataDrivenStyle: TransformStyleConfig = {
|
|
|
11
11
|
colorSchemes: ['blue', 'orange', 'green', 'purple', 'red'],
|
|
12
12
|
insertSectionHeaders: true,
|
|
13
13
|
interleaveImages: false,
|
|
14
|
-
blocksPerSection: {
|
|
14
|
+
blocksPerSection: { max: 6 },
|
|
15
15
|
transitionStyle: 'cut',
|
|
16
|
+
suggestedThemeId: 'tech-dark',
|
|
16
17
|
};
|
|
@@ -11,6 +11,7 @@ export const documentaryStyle: TransformStyleConfig = {
|
|
|
11
11
|
colorSchemes: ['blue', 'green', 'purple'],
|
|
12
12
|
insertSectionHeaders: false,
|
|
13
13
|
interleaveImages: true,
|
|
14
|
-
blocksPerSection: {
|
|
14
|
+
blocksPerSection: { max: 3 },
|
|
15
15
|
transitionStyle: 'fade',
|
|
16
|
+
suggestedThemeId: 'documentary',
|
|
16
17
|
};
|
|
@@ -11,6 +11,8 @@ export const magazineStyle: TransformStyleConfig = {
|
|
|
11
11
|
colorSchemes: ['blue', 'green', 'purple', 'orange', 'red'],
|
|
12
12
|
insertSectionHeaders: true,
|
|
13
13
|
interleaveImages: true,
|
|
14
|
-
blocksPerSection: {
|
|
14
|
+
blocksPerSection: { max: 5 },
|
|
15
15
|
transitionStyle: 'dissolve',
|
|
16
|
+
suggestedThemeId: 'magazine',
|
|
17
|
+
templateMap: { quote: 'pullQuote', impactLine: 'fullBleedQuote' },
|
|
16
18
|
};
|
|
@@ -11,6 +11,7 @@ export const minimalStyle: TransformStyleConfig = {
|
|
|
11
11
|
colorSchemes: ['blue', 'green'],
|
|
12
12
|
insertSectionHeaders: false,
|
|
13
13
|
interleaveImages: false,
|
|
14
|
-
blocksPerSection: {
|
|
14
|
+
blocksPerSection: { max: 2 },
|
|
15
15
|
transitionStyle: 'fade',
|
|
16
|
+
suggestedThemeId: 'minimalist',
|
|
16
17
|
};
|
|
@@ -11,6 +11,8 @@ export const narrativeStyle: TransformStyleConfig = {
|
|
|
11
11
|
colorSchemes: ['blue', 'purple', 'green'],
|
|
12
12
|
insertSectionHeaders: true,
|
|
13
13
|
interleaveImages: true,
|
|
14
|
-
blocksPerSection: {
|
|
14
|
+
blocksPerSection: { max: 4 },
|
|
15
15
|
transitionStyle: 'fade',
|
|
16
|
+
suggestedThemeId: 'cinematic',
|
|
17
|
+
pacing: { intro: true, outro: true },
|
|
16
18
|
};
|
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
* variety. Ported and generalized from qualla-internal's TemplateSelector.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { Block } from '../schemas/Doc.js';
|
|
9
|
+
import type { Block, TransitionType } from '../schemas/Doc.js';
|
|
10
10
|
import type {
|
|
11
11
|
TemplateBlock,
|
|
12
12
|
AccentImage,
|
|
13
13
|
AccentPosition,
|
|
14
14
|
ImageWithCaptionInput,
|
|
15
15
|
SectionHeaderInput,
|
|
16
|
+
QuoteBlockInput,
|
|
17
|
+
FullBleedQuoteInput,
|
|
18
|
+
StatHighlightInput,
|
|
16
19
|
} from '../schemas/BlockTemplates.js';
|
|
17
20
|
import type { ExtractedElement } from '../generate/contentExtractor.js';
|
|
18
21
|
import type { TransformStyleConfig, TransformImage } from './types.js';
|
|
@@ -73,8 +76,17 @@ export function selectAndBuild(
|
|
|
73
76
|
// 1. Collect all extractions across all blocks, filtered by config
|
|
74
77
|
const candidates = collectCandidates(analyzed, config);
|
|
75
78
|
|
|
76
|
-
// 2. Determine how many blocks to transform
|
|
77
|
-
|
|
79
|
+
// 2. Determine how many blocks to transform. `transformRatio` scales
|
|
80
|
+
// with block count; an optional duration-based budget (slides per
|
|
81
|
+
// minute) caps runaway promotion on long dense documents.
|
|
82
|
+
const ratioCap = Math.max(1, Math.floor(analyzed.length * config.transformRatio));
|
|
83
|
+
const slidesPerMinute = config.budget?.slidesPerMinute;
|
|
84
|
+
const totalDurationSeconds = analyzed.reduce((sum, ab) => sum + (ab.block.duration || 0), 0);
|
|
85
|
+
const budgetCap =
|
|
86
|
+
slidesPerMinute && totalDurationSeconds > 0
|
|
87
|
+
? Math.max(1, Math.round((totalDurationSeconds / 60) * slidesPerMinute))
|
|
88
|
+
: Infinity;
|
|
89
|
+
const maxTransforms = Math.min(ratioCap, budgetCap);
|
|
78
90
|
const selected = candidates.slice(0, maxTransforms);
|
|
79
91
|
|
|
80
92
|
// 3. Build the output block sequence
|
|
@@ -233,7 +245,7 @@ function buildBlockSequence(
|
|
|
233
245
|
accentPositionIndex++;
|
|
234
246
|
}
|
|
235
247
|
|
|
236
|
-
|
|
248
|
+
let templateBlock = mapElementToBlock(sel.element, {
|
|
237
249
|
id: `transform-${blockIdCounter++}`,
|
|
238
250
|
duration: ab.block.duration > 0 ? ab.block.duration / extractions.length : 6,
|
|
239
251
|
audioSegment: ab.block.audioSegment,
|
|
@@ -242,15 +254,24 @@ function buildBlockSequence(
|
|
|
242
254
|
sourceStartTime: sel.element.sourcePosition,
|
|
243
255
|
});
|
|
244
256
|
|
|
257
|
+
// Style-level extraction→template remap (v2 contract). Only known
|
|
258
|
+
// text-compatible pairings translate; image-backed targets consume
|
|
259
|
+
// the accent image as their background. Unknown pairings keep the
|
|
260
|
+
// default mapping.
|
|
261
|
+
const remapTarget = config.templateMap?.[sel.element.data.type];
|
|
262
|
+
if (remapTarget && remapTarget !== templateBlock.template) {
|
|
263
|
+
const translated = translateTemplateBlock(templateBlock, remapTarget);
|
|
264
|
+
if (translated) templateBlock = translated;
|
|
265
|
+
}
|
|
266
|
+
|
|
245
267
|
// Apply transition based on style
|
|
246
268
|
if (config.transitionStyle !== 'cut') {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
: config.transitionStyle,
|
|
269
|
+
const transitionType: TransitionType =
|
|
270
|
+
config.transitionStyle === 'mixed'
|
|
271
|
+
? (rng.pick<TransitionType>(['fade', 'dissolve']) ?? 'fade')
|
|
272
|
+
: config.transitionStyle;
|
|
273
|
+
templateBlock.transition = {
|
|
274
|
+
type: transitionType,
|
|
254
275
|
duration: 0.5,
|
|
255
276
|
};
|
|
256
277
|
}
|
|
@@ -292,3 +313,58 @@ function buildBlockSequence(
|
|
|
292
313
|
|
|
293
314
|
return { blocks, transformedCount, insertedCount };
|
|
294
315
|
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Translate a mapped template block into a style-requested target
|
|
319
|
+
* template. Only text-compatible pairings are supported; returns null
|
|
320
|
+
* (keep the default) for unknown pairs or when a required input —
|
|
321
|
+
* a background image for `pullQuote` — is unavailable.
|
|
322
|
+
*/
|
|
323
|
+
function translateTemplateBlock(block: TemplateBlock, target: string): TemplateBlock | null {
|
|
324
|
+
const base = {
|
|
325
|
+
id: block.id,
|
|
326
|
+
duration: block.duration,
|
|
327
|
+
audioSegment: block.audioSegment,
|
|
328
|
+
sourceStartTime: block.sourceStartTime,
|
|
329
|
+
sourceDuration: block.sourceDuration,
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
if (block.template === 'quote') {
|
|
333
|
+
const quote = block as QuoteBlockInput;
|
|
334
|
+
if (target === 'fullBleedQuote') {
|
|
335
|
+
return { ...base, template: 'fullBleedQuote', text: quote.quote };
|
|
336
|
+
}
|
|
337
|
+
if (target === 'pullQuote') {
|
|
338
|
+
// pullQuote requires a full-bleed background image — reuse the
|
|
339
|
+
// accent image the selector already assigned to this block.
|
|
340
|
+
const bg = quote.accentImage;
|
|
341
|
+
if (!bg) return null;
|
|
342
|
+
return {
|
|
343
|
+
...base,
|
|
344
|
+
template: 'pullQuote',
|
|
345
|
+
text: quote.quote,
|
|
346
|
+
attribution: quote.attribution,
|
|
347
|
+
backgroundImage: { src: bg.src, alt: bg.alt, credit: bg.credit, license: bg.license },
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (block.template === 'fullBleedQuote' && target === 'quote') {
|
|
354
|
+
const fbq = block as FullBleedQuoteInput;
|
|
355
|
+
return { ...base, template: 'quote', quote: fbq.text };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (block.template === 'statHighlight' && target === 'factCard') {
|
|
359
|
+
const stat = block as StatHighlightInput;
|
|
360
|
+
return {
|
|
361
|
+
...base,
|
|
362
|
+
template: 'factCard',
|
|
363
|
+
fact: stat.stat,
|
|
364
|
+
explanation: stat.description,
|
|
365
|
+
accentImage: stat.accentImage,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return null;
|
|
370
|
+
}
|
package/src/transform/types.ts
CHANGED
|
@@ -57,11 +57,44 @@ export interface TransformStyleConfig {
|
|
|
57
57
|
/** Whether to interleave imageWithCaption blocks when images are available. */
|
|
58
58
|
interleaveImages: boolean;
|
|
59
59
|
|
|
60
|
-
/**
|
|
61
|
-
blocksPerSection: {
|
|
60
|
+
/** Maximum template blocks promoted per source section. */
|
|
61
|
+
blocksPerSection: { max: number };
|
|
62
62
|
|
|
63
63
|
/** Transition style preference for transformed blocks. */
|
|
64
64
|
transitionStyle: 'cut' | 'fade' | 'dissolve' | 'mixed';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Per-style extraction→template remapping. Falls back to the fixed
|
|
68
|
+
* default map in `generate/templateMapper.ts` for unmapped types.
|
|
69
|
+
* Only text-compatible targets are honored (see
|
|
70
|
+
* `translateTemplateBlock`) — an unknown pairing keeps the default.
|
|
71
|
+
*/
|
|
72
|
+
templateMap?: Partial<Record<ExtractionType, string>>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Theme this style pairs best with. Consumers (CLI, editor preview)
|
|
76
|
+
* apply it only when the doc doesn't already declare a `themeId`.
|
|
77
|
+
*/
|
|
78
|
+
suggestedThemeId?: string;
|
|
79
|
+
|
|
80
|
+
/** Optional opening/closing beats added around the transformed doc. */
|
|
81
|
+
pacing?: {
|
|
82
|
+
/** Prepend a `title` block built from the doc's title. */
|
|
83
|
+
intro?: boolean;
|
|
84
|
+
/** Append a closing beat (best unused quote/impact line, else a sectionHeader). */
|
|
85
|
+
outro?: boolean;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Duration-based slide budget (folded in from the since-removed
|
|
90
|
+
* `generateSlideshow`): caps promoted template blocks at roughly
|
|
91
|
+
* `slidesPerMinute` of doc duration (default: uncapped — only
|
|
92
|
+
* `transformRatio` applies).
|
|
93
|
+
*/
|
|
94
|
+
budget?: {
|
|
95
|
+
/** Target promoted blocks per minute of content (e.g. 5). */
|
|
96
|
+
slidesPerMinute?: number;
|
|
97
|
+
};
|
|
65
98
|
}
|
|
66
99
|
|
|
67
100
|
// ── Transform options ────────────────────────────────────────────
|