@bendyline/squisq 2.0.1 → 2.1.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 (79) hide show
  1. package/dist/{Doc-ylqWKDc_.d.ts → Doc-DynJleo-.d.ts} +159 -2
  2. package/dist/{ImageEditDoc-D828IXJ1.d.ts → ImageEditDoc-Ca4Tr9uN.d.ts} +1 -1
  3. package/dist/{chunk-PXLVXTLW.js → chunk-ILWDXLUK.js} +24 -6
  4. package/dist/chunk-ILWDXLUK.js.map +1 -0
  5. package/dist/{chunk-W4O2TX5I.js → chunk-JHZWYRSW.js} +569 -1
  6. package/dist/chunk-JHZWYRSW.js.map +1 -0
  7. package/dist/{chunk-X6DUONGT.js → chunk-JI77MGAK.js} +123 -10
  8. package/dist/chunk-JI77MGAK.js.map +1 -0
  9. package/dist/{chunk-TFJVXUHH.js → chunk-OTKVFVL6.js} +1403 -41
  10. package/dist/chunk-OTKVFVL6.js.map +1 -0
  11. package/dist/{chunk-MMPBOUK7.js → chunk-QUUPYOSP.js} +8 -2
  12. package/dist/chunk-QUUPYOSP.js.map +1 -0
  13. package/dist/chunk-VWCJ2WXC.js +215 -0
  14. package/dist/chunk-VWCJ2WXC.js.map +1 -0
  15. package/dist/{chunk-2PWOO4ZE.js → chunk-VYFRI7R6.js} +2 -2
  16. package/dist/{chunk-CY2R2VPR.js → chunk-XNLB57UD.js} +2 -2
  17. package/dist/doc/index.d.ts +144 -5
  18. package/dist/doc/index.js +24 -4
  19. package/dist/generate/index.d.ts +1 -1
  20. package/dist/imageEdit/index.d.ts +3 -3
  21. package/dist/index.d.ts +6 -5
  22. package/dist/index.js +66 -8
  23. package/dist/jsonForm/index.d.ts +1 -1
  24. package/dist/jsonForm/index.js +4 -4
  25. package/dist/materializePageSection-DDzubQXo.d.ts +223 -0
  26. package/dist/narration/index.d.ts +1 -1
  27. package/dist/narration/index.js +3 -3
  28. package/dist/schemas/index.d.ts +30 -5
  29. package/dist/schemas/index.js +41 -3
  30. package/dist/{themeLibrary-RjVgLpUi.d.ts → themeLibrary-B72m0W1y.d.ts} +1 -1
  31. package/dist/transform/index.d.ts +8 -9
  32. package/dist/transform/index.js +1 -1
  33. package/package.json +1 -1
  34. package/src/__tests__/materializeBlockLayers.test.ts +32 -0
  35. package/src/__tests__/materializePageSections.test.ts +404 -0
  36. package/src/__tests__/pageStyle.test.ts +258 -0
  37. package/src/__tests__/templateAnnotationParse.test.ts +13 -0
  38. package/src/doc/index.ts +34 -0
  39. package/src/doc/materializeBlockLayers.ts +170 -9
  40. package/src/doc/page/PageSection.ts +174 -0
  41. package/src/doc/page/materializePageSection.ts +616 -0
  42. package/src/doc/page/resolvePageBlock.ts +108 -0
  43. package/src/doc/page/sectionExtractors.ts +466 -0
  44. package/src/doc/pageCss.ts +628 -0
  45. package/src/doc/templateInputs.ts +1 -0
  46. package/src/schemas/Doc.ts +22 -1
  47. package/src/schemas/PageStyle.ts +213 -0
  48. package/src/schemas/Theme.ts +7 -0
  49. package/src/schemas/index.ts +2 -0
  50. package/src/schemas/pageStyleDefaults.ts +204 -0
  51. package/src/schemas/themeCompile.ts +7 -0
  52. package/src/schemas/themeValidator.ts +183 -0
  53. package/src/schemas/themes/bold.json +29 -0
  54. package/src/schemas/themes/cinematic.json +32 -0
  55. package/src/schemas/themes/documentary.json +29 -0
  56. package/src/schemas/themes/gezellig.json +30 -0
  57. package/src/schemas/themes/magazine.json +37 -0
  58. package/src/schemas/themes/minimalist.json +29 -0
  59. package/src/schemas/themes/morning-light.json +32 -0
  60. package/src/schemas/themes/standard-dark.json +24 -0
  61. package/src/schemas/themes/standard.json +24 -0
  62. package/src/schemas/themes/tech-dark.json +42 -0
  63. package/src/schemas/themes/warm-earth.json +24 -0
  64. package/src/transform/registry.ts +19 -0
  65. package/src/transform/styles/dataDriven.ts +1 -0
  66. package/src/transform/styles/documentary.ts +1 -0
  67. package/src/transform/styles/magazine.ts +1 -0
  68. package/src/transform/styles/minimal.ts +1 -0
  69. package/src/transform/styles/narrative.ts +1 -0
  70. package/src/transform/types.ts +7 -0
  71. package/dist/chunk-FVJUUTEM.js +0 -74
  72. package/dist/chunk-FVJUUTEM.js.map +0 -1
  73. package/dist/chunk-MMPBOUK7.js.map +0 -1
  74. package/dist/chunk-PXLVXTLW.js.map +0 -1
  75. package/dist/chunk-TFJVXUHH.js.map +0 -1
  76. package/dist/chunk-W4O2TX5I.js.map +0 -1
  77. package/dist/chunk-X6DUONGT.js.map +0 -1
  78. /package/dist/{chunk-2PWOO4ZE.js.map → chunk-VYFRI7R6.js.map} +0 -0
  79. /package/dist/{chunk-CY2R2VPR.js.map → chunk-XNLB57UD.js.map} +0 -0
@@ -44,5 +44,34 @@
44
44
  "red": { "bg": "#1a202c", "text": "#fc8181", "accent": "#feb2b2" },
45
45
  "orange": { "bg": "#1a202c", "text": "#f6ad55", "accent": "#fbd38d" },
46
46
  "teal": { "bg": "#1a202c", "text": "#4fd1c5", "accent": "#81e6d9" }
47
+ },
48
+ "pageStyle": {
49
+ "family": "brutalist",
50
+ "tokens": {
51
+ "contentMaxWidth": 760,
52
+ "wideMaxWidth": 1200,
53
+ "sectionSpacing": "compact",
54
+ "cornerRadius": 0,
55
+ "divider": "thick-rule",
56
+ "backgroundRhythm": "accent-bands",
57
+ "heroStyle": "oversized-type",
58
+ "headingTreatment": {
59
+ "eyebrow": "kicker",
60
+ "scale": "oversized",
61
+ "case": "uppercase"
62
+ },
63
+ "imageFraming": "bordered",
64
+ "shadow": "heavy",
65
+ "quoteMark": "none",
66
+ "numeralStyle": "oversized"
67
+ },
68
+ "sections": {
69
+ "quote-band": {
70
+ "background": "accent"
71
+ }
72
+ },
73
+ "accentRotation": {
74
+ "strategy": "cycle"
75
+ }
47
76
  }
48
77
  }
@@ -107,5 +107,37 @@
107
107
  }
108
108
  }
109
109
  ]
110
+ },
111
+ "pageStyle": {
112
+ "family": "cinematic",
113
+ "tokens": {
114
+ "contentMaxWidth": 760,
115
+ "wideMaxWidth": 1100,
116
+ "sectionSpacing": "generous",
117
+ "cornerRadius": 0,
118
+ "divider": "gap-only",
119
+ "backgroundRhythm": "flat",
120
+ "heroStyle": "full-bleed",
121
+ "headingTreatment": {
122
+ "eyebrow": "kicker",
123
+ "scale": "display",
124
+ "case": "uppercase"
125
+ },
126
+ "imageFraming": "letterboxed",
127
+ "shadow": "none",
128
+ "quoteMark": "none",
129
+ "numeralStyle": "oversized",
130
+ "pattern": "noise"
131
+ },
132
+ "sections": {
133
+ "quote-band": {
134
+ "hints": {
135
+ "vignette": true
136
+ }
137
+ }
138
+ },
139
+ "accentRotation": {
140
+ "strategy": "primary-only"
141
+ }
110
142
  }
111
143
  }
@@ -92,5 +92,34 @@
92
92
  }
93
93
  }
94
94
  ]
95
+ },
96
+ "pageStyle": {
97
+ "family": "documentary",
98
+ "tokens": {
99
+ "contentMaxWidth": 680,
100
+ "wideMaxWidth": 1000,
101
+ "sectionSpacing": "generous",
102
+ "cornerRadius": 2,
103
+ "divider": "thick-rule",
104
+ "backgroundRhythm": "flat",
105
+ "heroStyle": "letterbox",
106
+ "headingTreatment": {
107
+ "eyebrow": "numbered",
108
+ "scale": "display",
109
+ "underline": "none"
110
+ },
111
+ "imageFraming": "letterboxed",
112
+ "shadow": "none",
113
+ "quoteMark": "accent-bar",
114
+ "numeralStyle": "boxed"
115
+ },
116
+ "sections": {
117
+ "quote-band": {
118
+ "variant": "editorial"
119
+ }
120
+ },
121
+ "accentRotation": {
122
+ "strategy": "primary-only"
123
+ }
95
124
  }
96
125
  }
@@ -89,5 +89,35 @@
89
89
  }
90
90
  }
91
91
  ]
92
+ },
93
+ "pageStyle": {
94
+ "family": "soft",
95
+ "tokens": {
96
+ "contentMaxWidth": 740,
97
+ "wideMaxWidth": 1060,
98
+ "sectionSpacing": "comfortable",
99
+ "cornerRadius": 16,
100
+ "divider": "dotted",
101
+ "backgroundRhythm": "tinted-panels",
102
+ "heroStyle": "stacked",
103
+ "headingTreatment": {
104
+ "eyebrow": "kicker",
105
+ "scale": "regular",
106
+ "case": "none"
107
+ },
108
+ "imageFraming": "circle-accent",
109
+ "shadow": "soft",
110
+ "quoteMark": "oversized-glyph",
111
+ "numeralStyle": "plain",
112
+ "pattern": "noise"
113
+ },
114
+ "sections": {
115
+ "quote-band": {
116
+ "background": "alternate"
117
+ }
118
+ },
119
+ "accentRotation": {
120
+ "strategy": "alternate-two"
121
+ }
92
122
  }
93
123
  }
@@ -85,5 +85,42 @@
85
85
  }
86
86
  }
87
87
  ]
88
+ },
89
+ "pageStyle": {
90
+ "family": "editorial",
91
+ "tokens": {
92
+ "contentMaxWidth": 720,
93
+ "wideMaxWidth": 1240,
94
+ "sectionSpacing": "comfortable",
95
+ "cornerRadius": 6,
96
+ "divider": "double-rule",
97
+ "backgroundRhythm": "flat",
98
+ "heroStyle": "split",
99
+ "headingTreatment": {
100
+ "eyebrow": "kicker",
101
+ "scale": "display",
102
+ "underline": "none"
103
+ },
104
+ "imageFraming": "flush",
105
+ "shadow": "none",
106
+ "quoteMark": "oversized-glyph",
107
+ "numeralStyle": "oversized",
108
+ "pattern": "diagonal"
109
+ },
110
+ "sections": {
111
+ "prose": {
112
+ "hints": {
113
+ "dropCap": true
114
+ }
115
+ },
116
+ "feature-split": {
117
+ "hints": {
118
+ "alternate": true
119
+ }
120
+ }
121
+ },
122
+ "accentRotation": {
123
+ "strategy": "alternate-two"
124
+ }
88
125
  }
89
126
  }
@@ -41,5 +41,34 @@
41
41
  "red": { "bg": "#fff5f5", "text": "#c53030", "accent": "#e53e3e" },
42
42
  "orange": { "bg": "#fffaf0", "text": "#c05621", "accent": "#dd6b20" },
43
43
  "teal": { "bg": "#e6fffa", "text": "#285e61", "accent": "#319795" }
44
+ },
45
+ "pageStyle": {
46
+ "family": "clean",
47
+ "tokens": {
48
+ "contentMaxWidth": 720,
49
+ "wideMaxWidth": 1000,
50
+ "sectionSpacing": "generous",
51
+ "cornerRadius": 8,
52
+ "divider": "gap-only",
53
+ "backgroundRhythm": "flat",
54
+ "heroStyle": "stacked",
55
+ "headingTreatment": {
56
+ "eyebrow": "none",
57
+ "scale": "display",
58
+ "underline": "none"
59
+ },
60
+ "imageFraming": "flush",
61
+ "shadow": "none",
62
+ "quoteMark": "none",
63
+ "numeralStyle": "plain"
64
+ },
65
+ "sections": {
66
+ "quote-band": {
67
+ "emphasis": "quiet"
68
+ }
69
+ },
70
+ "accentRotation": {
71
+ "strategy": "primary-only"
72
+ }
44
73
  }
45
74
  }
@@ -81,5 +81,37 @@
81
81
  }
82
82
  }
83
83
  ]
84
+ },
85
+ "pageStyle": {
86
+ "family": "soft",
87
+ "tokens": {
88
+ "contentMaxWidth": 760,
89
+ "wideMaxWidth": 1100,
90
+ "sectionSpacing": "comfortable",
91
+ "cornerRadius": 14,
92
+ "divider": "dotted",
93
+ "backgroundRhythm": "tinted-panels",
94
+ "heroStyle": "split",
95
+ "headingTreatment": {
96
+ "eyebrow": "kicker",
97
+ "scale": "regular",
98
+ "underline": "none"
99
+ },
100
+ "imageFraming": "rounded",
101
+ "shadow": "soft",
102
+ "quoteMark": "oversized-glyph",
103
+ "numeralStyle": "plain",
104
+ "pattern": "noise"
105
+ },
106
+ "sections": {
107
+ "callout": {
108
+ "hints": {
109
+ "framing": "circle-accent"
110
+ }
111
+ }
112
+ },
113
+ "accentRotation": {
114
+ "strategy": "alternate-two"
115
+ }
84
116
  }
85
117
  }
@@ -81,5 +81,29 @@
81
81
  }
82
82
  }
83
83
  ]
84
+ },
85
+ "pageStyle": {
86
+ "family": "clean",
87
+ "tokens": {
88
+ "contentMaxWidth": 760,
89
+ "wideMaxWidth": 1100,
90
+ "sectionSpacing": "comfortable",
91
+ "cornerRadius": 8,
92
+ "divider": "hairline",
93
+ "backgroundRhythm": "tinted-panels",
94
+ "heroStyle": "stacked",
95
+ "headingTreatment": {
96
+ "eyebrow": "kicker",
97
+ "scale": "regular",
98
+ "underline": "none"
99
+ },
100
+ "imageFraming": "rounded",
101
+ "shadow": "none",
102
+ "quoteMark": "accent-bar",
103
+ "numeralStyle": "oversized"
104
+ },
105
+ "accentRotation": {
106
+ "strategy": "cycle"
107
+ }
84
108
  }
85
109
  }
@@ -70,5 +70,29 @@
70
70
  "text": "#115e59",
71
71
  "accent": "#0d9488"
72
72
  }
73
+ },
74
+ "pageStyle": {
75
+ "family": "clean",
76
+ "tokens": {
77
+ "contentMaxWidth": 760,
78
+ "wideMaxWidth": 1100,
79
+ "sectionSpacing": "comfortable",
80
+ "cornerRadius": 8,
81
+ "divider": "hairline",
82
+ "backgroundRhythm": "alternate",
83
+ "heroStyle": "stacked",
84
+ "headingTreatment": {
85
+ "eyebrow": "kicker",
86
+ "scale": "regular",
87
+ "underline": "none"
88
+ },
89
+ "imageFraming": "rounded",
90
+ "shadow": "soft",
91
+ "quoteMark": "accent-bar",
92
+ "numeralStyle": "plain"
93
+ },
94
+ "accentRotation": {
95
+ "strategy": "cycle"
96
+ }
73
97
  }
74
98
  }
@@ -97,5 +97,47 @@
97
97
  }
98
98
  }
99
99
  ]
100
+ },
101
+ "pageStyle": {
102
+ "family": "terminal",
103
+ "tokens": {
104
+ "contentMaxWidth": 820,
105
+ "wideMaxWidth": 1200,
106
+ "sectionSpacing": "compact",
107
+ "cornerRadius": 2,
108
+ "divider": "hairline",
109
+ "backgroundRhythm": "tinted-panels",
110
+ "heroStyle": "stacked",
111
+ "headingTreatment": {
112
+ "eyebrow": "mono-tag",
113
+ "scale": "regular",
114
+ "underline": "none"
115
+ },
116
+ "imageFraming": "bordered",
117
+ "shadow": "none",
118
+ "quoteMark": "none",
119
+ "numeralStyle": "mono",
120
+ "pattern": "grid"
121
+ },
122
+ "templates": {
123
+ "diagram": {
124
+ "hints": {
125
+ "frame": "terminal"
126
+ }
127
+ },
128
+ "tree": {
129
+ "hints": {
130
+ "frame": "terminal"
131
+ }
132
+ },
133
+ "timeline": {
134
+ "hints": {
135
+ "frame": "terminal"
136
+ }
137
+ }
138
+ },
139
+ "accentRotation": {
140
+ "strategy": "primary-only"
141
+ }
100
142
  }
101
143
  }
@@ -46,5 +46,29 @@
46
46
  "config": { "type": "gradientBackground", "preset": "earth-tones" }
47
47
  }
48
48
  ]
49
+ },
50
+ "pageStyle": {
51
+ "family": "organic",
52
+ "tokens": {
53
+ "contentMaxWidth": 740,
54
+ "wideMaxWidth": 1080,
55
+ "sectionSpacing": "comfortable",
56
+ "cornerRadius": 12,
57
+ "divider": "thick-rule",
58
+ "backgroundRhythm": "tinted-panels",
59
+ "heroStyle": "stacked",
60
+ "headingTreatment": {
61
+ "eyebrow": "kicker",
62
+ "scale": "regular",
63
+ "underline": "accent-bar"
64
+ },
65
+ "imageFraming": "polaroid",
66
+ "shadow": "soft",
67
+ "quoteMark": "oversized-glyph",
68
+ "numeralStyle": "boxed"
69
+ },
70
+ "accentRotation": {
71
+ "strategy": "alternate-two"
72
+ }
49
73
  }
50
74
  }
@@ -121,6 +121,25 @@ function validateTransformStyle(style: TransformStyleConfig): void {
121
121
  fail('budget.slidesPerMinute must be a positive finite number');
122
122
  }
123
123
  }
124
+ const pageValue = candidate.page;
125
+ if (pageValue !== undefined) {
126
+ if (!pageValue || typeof pageValue !== 'object' || Array.isArray(pageValue)) {
127
+ fail('page must be an object');
128
+ }
129
+ const page = pageValue as { spacing?: unknown; emphasisCurve?: unknown };
130
+ if (
131
+ page.spacing !== undefined &&
132
+ !['compact', 'comfortable', 'generous'].includes(page.spacing as string)
133
+ ) {
134
+ fail('page.spacing must be compact, comfortable, or generous');
135
+ }
136
+ if (
137
+ page.emphasisCurve !== undefined &&
138
+ !['even', 'front-loaded'].includes(page.emphasisCurve as string)
139
+ ) {
140
+ fail('page.emphasisCurve must be even or front-loaded');
141
+ }
142
+ }
124
143
  }
125
144
 
126
145
  function styleSnapshot(style: TransformStyleConfig): TransformStyleConfig {
@@ -14,4 +14,5 @@ export const dataDrivenStyle: TransformStyleConfig = {
14
14
  blocksPerSection: { max: 6 },
15
15
  transitionStyle: 'cut',
16
16
  suggestedThemeId: 'tech-dark',
17
+ page: { spacing: 'compact', emphasisCurve: 'even' },
17
18
  };
@@ -14,4 +14,5 @@ export const documentaryStyle: TransformStyleConfig = {
14
14
  blocksPerSection: { max: 3 },
15
15
  transitionStyle: 'fade',
16
16
  suggestedThemeId: 'documentary',
17
+ page: { spacing: 'generous', emphasisCurve: 'front-loaded' },
17
18
  };
@@ -15,4 +15,5 @@ export const magazineStyle: TransformStyleConfig = {
15
15
  transitionStyle: 'dissolve',
16
16
  suggestedThemeId: 'magazine',
17
17
  templateMap: { quote: 'pullQuote', impactLine: 'fullBleedQuote' },
18
+ page: { spacing: 'comfortable', emphasisCurve: 'front-loaded' },
18
19
  };
@@ -14,4 +14,5 @@ export const minimalStyle: TransformStyleConfig = {
14
14
  blocksPerSection: { max: 2 },
15
15
  transitionStyle: 'fade',
16
16
  suggestedThemeId: 'minimalist',
17
+ page: { spacing: 'generous', emphasisCurve: 'even' },
17
18
  };
@@ -15,4 +15,5 @@ export const narrativeStyle: TransformStyleConfig = {
15
15
  transitionStyle: 'fade',
16
16
  suggestedThemeId: 'cinematic',
17
17
  pacing: { intro: true, outro: true },
18
+ page: { spacing: 'comfortable', emphasisCurve: 'even' },
18
19
  };
@@ -95,6 +95,13 @@ export interface TransformStyleConfig {
95
95
  /** Target promoted blocks per minute of content (e.g. 5). */
96
96
  slidesPerMinute?: number;
97
97
  };
98
+
99
+ /**
100
+ * Page (linear) mode hints for docs produced by this style. Consumers
101
+ * pass this as `MaterializePageSectionsOptions.transformPage` so the
102
+ * summarization strategy shapes the page rendition (spacing, emphasis).
103
+ */
104
+ page?: import('../doc/page/materializePageSection.js').PageTransformHints;
98
105
  }
99
106
 
100
107
  /** Built-in id or a call-scoped declarative style definition. */
@@ -1,74 +0,0 @@
1
- // src/schemas/Media.ts
2
- function flatten(blocks, out = []) {
3
- for (const b of blocks) {
4
- out.push(b);
5
- if (b.children && b.children.length > 0) flatten(b.children, out);
6
- }
7
- return out;
8
- }
9
- function clipLength(clip) {
10
- if (clip.clipEnd == null) return null;
11
- return Math.max(0, clip.clipEnd - (clip.clipStart ?? 0));
12
- }
13
- function baseTimelineEnd(doc) {
14
- const blockEnd = flatten(doc.blocks).reduce(
15
- (max, b) => Math.max(max, b.startTime + b.duration),
16
- 0
17
- );
18
- return Math.max(doc.duration ?? 0, blockEnd);
19
- }
20
- function resolveMediaSchedule(doc) {
21
- const out = [];
22
- const docEnd = baseTimelineEnd(doc);
23
- for (const block of flatten(doc.blocks)) {
24
- const blockEnd = block.startTime + block.duration;
25
- for (const clip of block.media ?? []) {
26
- const start = block.startTime + clip.startAt;
27
- const len = clipLength(clip);
28
- let end;
29
- if (clip.spillover) {
30
- end = len != null ? start + len : blockEnd;
31
- } else {
32
- end = len != null ? Math.min(start + len, blockEnd) : blockEnd;
33
- }
34
- out.push({
35
- id: clip.id,
36
- src: clip.src,
37
- kind: clip.kind,
38
- absoluteStart: start,
39
- absoluteEnd: Math.max(start, end),
40
- sourceIn: clip.clipStart ?? 0,
41
- anchor: "block",
42
- blockId: block.id,
43
- ...clip.sourceLine != null ? { sourceLine: clip.sourceLine } : {}
44
- });
45
- }
46
- }
47
- for (const clip of doc.documentMedia ?? []) {
48
- const start = clip.startAt;
49
- const len = clipLength(clip);
50
- const end = len != null ? start + len : docEnd;
51
- out.push({
52
- id: clip.id,
53
- src: clip.src,
54
- kind: clip.kind,
55
- absoluteStart: start,
56
- absoluteEnd: Math.max(start, end),
57
- sourceIn: clip.clipStart ?? 0,
58
- anchor: "document",
59
- ...clip.sourceLine != null ? { sourceLine: clip.sourceLine } : {}
60
- });
61
- }
62
- return out;
63
- }
64
- function getDocPlaybackDuration(doc) {
65
- const base = baseTimelineEnd(doc);
66
- const mediaEnd = resolveMediaSchedule(doc).reduce((max, c) => Math.max(max, c.absoluteEnd), 0);
67
- return Math.max(base, mediaEnd);
68
- }
69
-
70
- export {
71
- resolveMediaSchedule,
72
- getDocPlaybackDuration
73
- };
74
- //# sourceMappingURL=chunk-FVJUUTEM.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/schemas/Media.ts"],"sourcesContent":["/**\n * Media-clip timing model.\n *\n * An additive layer on top of the simple sequential `Doc.audio.segments[]`\n * narration track, which remains first-class. A {@link MediaClip} is a piece\n * of audio or video whose\n * timing is expressed *relative to its parent block* (or the whole document),\n * with an optional `startAt` offset and an optional `spillover` past the\n * block's end. {@link resolveMediaSchedule} flattens these into absolute,\n * doc-timeline `ScheduledClip`s that playback and export consume.\n *\n * Authoring: a body-level `{[audio …]}` / `{[video …]}` annotation inside a\n * block becomes a `block.media` clip; the same annotation in the preamble\n * (before the first heading) with `anchor=document` becomes a\n * `doc.documentMedia` clip that spans the whole timeline.\n */\n\nimport type { Block, Doc } from './Doc.js';\n\n/**\n * A timed piece of media attached to a block (or the document).\n */\nexport interface MediaClip {\n /** Stable id (for React keys and timeline selection). */\n id: string;\n /** Source path (mp3/mp4/…), relative to the article media dir. */\n src: string;\n kind: 'audio' | 'video';\n /**\n * Seconds from the parent block's start when this clip begins. For an\n * `anchor='document'` clip, seconds from the document start. Default 0.\n */\n startAt: number;\n /** Source in-point within the file (seconds). Default 0. */\n clipStart?: number;\n /** Source out-point within the file (seconds). Default: file/block end. */\n clipEnd?: number;\n /**\n * When false (default) the clip stops at its block's end. When true it\n * keeps playing past the block boundary until the clip itself finishes.\n * Ignored for `anchor='document'` clips (they already span the timeline).\n */\n spillover?: boolean;\n /**\n * `'block'` (default) — timed relative to the parent block. `'document'` —\n * timed relative to the document start and able to span every block.\n */\n anchor: 'block' | 'document';\n /**\n * 1-based source line of the authoring annotation, when derived from\n * markdown. Enables round-tripping edits (e.g. the timeline editor rewriting\n * `startAt`) back to the exact line. Absent for programmatically built clips.\n */\n sourceLine?: number;\n /**\n * Round-trip home of the authoring annotation: the block whose contents\n * held the paragraph, its position in that block's ORIGINAL contents\n * array (before extraction), and the paragraph's exact source text.\n * `docToMarkdown` re-inserts the annotation from this — `raw` verbatim\n * when present, a re-serialized annotation otherwise. Absent for\n * programmatically built clips (those emit at the document top).\n */\n origin?: { blockId: string; index: number; raw?: string };\n}\n\n/**\n * A {@link MediaClip} resolved to absolute document-timeline coordinates.\n * Produced by {@link resolveMediaSchedule}; consumed by the playback\n * scheduler and the MP4 export.\n */\nexport interface ScheduledClip {\n id: string;\n src: string;\n kind: 'audio' | 'video';\n /** Absolute doc-timeline second the clip starts. */\n absoluteStart: number;\n /** Absolute doc-timeline second the clip ends (exclusive). */\n absoluteEnd: number;\n /** Source in-point to seek to at `absoluteStart`. */\n sourceIn: number;\n anchor: 'block' | 'document';\n /** Owning block id for block-anchored clips (for video re-homing/export). */\n blockId?: string;\n /** 1-based source line of the authoring annotation, when known. */\n sourceLine?: number;\n}\n\n/** Depth-first flatten of the block tree (local copy to avoid a doc-layer dep). */\nfunction flatten(blocks: Block[], out: Block[] = []): Block[] {\n for (const b of blocks) {\n out.push(b);\n if (b.children && b.children.length > 0) flatten(b.children, out);\n }\n return out;\n}\n\n/** Played length of a clip when known from its in/out points, else null. */\nfunction clipLength(clip: MediaClip): number | null {\n if (clip.clipEnd == null) return null;\n return Math.max(0, clip.clipEnd - (clip.clipStart ?? 0));\n}\n\n/**\n * Base playback span ignoring media — the larger of the audio-segment sum\n * and the last block's end. Document clips that don't pin a length run to\n * this point.\n */\nfunction baseTimelineEnd(doc: Doc): number {\n const blockEnd = flatten(doc.blocks).reduce(\n (max, b) => Math.max(max, b.startTime + b.duration),\n 0,\n );\n return Math.max(doc.duration ?? 0, blockEnd);\n}\n\n/**\n * Flatten every block clip and document clip into absolute-timed\n * `ScheduledClip`s. Pure — depends only on `doc`.\n */\nexport function resolveMediaSchedule(doc: Doc): ScheduledClip[] {\n const out: ScheduledClip[] = [];\n const docEnd = baseTimelineEnd(doc);\n\n for (const block of flatten(doc.blocks)) {\n const blockEnd = block.startTime + block.duration;\n for (const clip of block.media ?? []) {\n const start = block.startTime + clip.startAt;\n const len = clipLength(clip);\n let end: number;\n if (clip.spillover) {\n // Continue past the block; fall back to the block end when the\n // played length is unknown (no clipEnd authored).\n end = len != null ? start + len : blockEnd;\n } else {\n end = len != null ? Math.min(start + len, blockEnd) : blockEnd;\n }\n out.push({\n id: clip.id,\n src: clip.src,\n kind: clip.kind,\n absoluteStart: start,\n absoluteEnd: Math.max(start, end),\n sourceIn: clip.clipStart ?? 0,\n anchor: 'block',\n blockId: block.id,\n ...(clip.sourceLine != null ? { sourceLine: clip.sourceLine } : {}),\n });\n }\n }\n\n for (const clip of doc.documentMedia ?? []) {\n const start = clip.startAt;\n const len = clipLength(clip);\n const end = len != null ? start + len : docEnd;\n out.push({\n id: clip.id,\n src: clip.src,\n kind: clip.kind,\n absoluteStart: start,\n absoluteEnd: Math.max(start, end),\n sourceIn: clip.clipStart ?? 0,\n anchor: 'document',\n ...(clip.sourceLine != null ? { sourceLine: clip.sourceLine } : {}),\n });\n }\n\n return out;\n}\n\n/**\n * Total playback duration including any media that spills past the last\n * block (or audio segment). Export uses this for the frame count and the\n * player for its effective timeline length.\n */\nexport function getDocPlaybackDuration(doc: Doc): number {\n const base = baseTimelineEnd(doc);\n const mediaEnd = resolveMediaSchedule(doc).reduce((max, c) => Math.max(max, c.absoluteEnd), 0);\n return Math.max(base, mediaEnd);\n}\n"],"mappings":";AAwFA,SAAS,QAAQ,QAAiB,MAAe,CAAC,GAAY;AAC5D,aAAW,KAAK,QAAQ;AACtB,QAAI,KAAK,CAAC;AACV,QAAI,EAAE,YAAY,EAAE,SAAS,SAAS,EAAG,SAAQ,EAAE,UAAU,GAAG;AAAA,EAClE;AACA,SAAO;AACT;AAGA,SAAS,WAAW,MAAgC;AAClD,MAAI,KAAK,WAAW,KAAM,QAAO;AACjC,SAAO,KAAK,IAAI,GAAG,KAAK,WAAW,KAAK,aAAa,EAAE;AACzD;AAOA,SAAS,gBAAgB,KAAkB;AACzC,QAAM,WAAW,QAAQ,IAAI,MAAM,EAAE;AAAA,IACnC,CAAC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,YAAY,EAAE,QAAQ;AAAA,IAClD;AAAA,EACF;AACA,SAAO,KAAK,IAAI,IAAI,YAAY,GAAG,QAAQ;AAC7C;AAMO,SAAS,qBAAqB,KAA2B;AAC9D,QAAM,MAAuB,CAAC;AAC9B,QAAM,SAAS,gBAAgB,GAAG;AAElC,aAAW,SAAS,QAAQ,IAAI,MAAM,GAAG;AACvC,UAAM,WAAW,MAAM,YAAY,MAAM;AACzC,eAAW,QAAQ,MAAM,SAAS,CAAC,GAAG;AACpC,YAAM,QAAQ,MAAM,YAAY,KAAK;AACrC,YAAM,MAAM,WAAW,IAAI;AAC3B,UAAI;AACJ,UAAI,KAAK,WAAW;AAGlB,cAAM,OAAO,OAAO,QAAQ,MAAM;AAAA,MACpC,OAAO;AACL,cAAM,OAAO,OAAO,KAAK,IAAI,QAAQ,KAAK,QAAQ,IAAI;AAAA,MACxD;AACA,UAAI,KAAK;AAAA,QACP,IAAI,KAAK;AAAA,QACT,KAAK,KAAK;AAAA,QACV,MAAM,KAAK;AAAA,QACX,eAAe;AAAA,QACf,aAAa,KAAK,IAAI,OAAO,GAAG;AAAA,QAChC,UAAU,KAAK,aAAa;AAAA,QAC5B,QAAQ;AAAA,QACR,SAAS,MAAM;AAAA,QACf,GAAI,KAAK,cAAc,OAAO,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,MACnE,CAAC;AAAA,IACH;AAAA,EACF;AAEA,aAAW,QAAQ,IAAI,iBAAiB,CAAC,GAAG;AAC1C,UAAM,QAAQ,KAAK;AACnB,UAAM,MAAM,WAAW,IAAI;AAC3B,UAAM,MAAM,OAAO,OAAO,QAAQ,MAAM;AACxC,QAAI,KAAK;AAAA,MACP,IAAI,KAAK;AAAA,MACT,KAAK,KAAK;AAAA,MACV,MAAM,KAAK;AAAA,MACX,eAAe;AAAA,MACf,aAAa,KAAK,IAAI,OAAO,GAAG;AAAA,MAChC,UAAU,KAAK,aAAa;AAAA,MAC5B,QAAQ;AAAA,MACR,GAAI,KAAK,cAAc,OAAO,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,IACnE,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAOO,SAAS,uBAAuB,KAAkB;AACvD,QAAM,OAAO,gBAAgB,GAAG;AAChC,QAAM,WAAW,qBAAqB,GAAG,EAAE,OAAO,CAAC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,WAAW,GAAG,CAAC;AAC7F,SAAO,KAAK,IAAI,MAAM,QAAQ;AAChC;","names":[]}