@bendyline/squisq 2.4.3 → 2.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.
@@ -34,6 +34,7 @@ function isContainerTemplate(name) {
34
34
  var TEMPLATE_TOKEN_NAMES = /* @__PURE__ */ new Set([
35
35
  "title",
36
36
  "sectionHeader",
37
+ "bigText",
37
38
  "content",
38
39
  "statHighlight",
39
40
  "quote",
@@ -70,6 +71,180 @@ function isReservedAnnotationToken(token) {
70
71
  return TEMPLATE_TOKEN_NAMES.has(resolveTemplateName(token));
71
72
  }
72
73
 
74
+ // src/markdown/annotationCoercion.ts
75
+ var KNOWN_BLOCK_META_KEYS = {
76
+ x: "number",
77
+ y: "number",
78
+ startTime: "time",
79
+ duration: "time",
80
+ connectsTo: "connectionList",
81
+ transition: "transition",
82
+ transitionDuration: "time",
83
+ transitionDirection: "transitionDirection"
84
+ };
85
+ var BLOCK_META_KEY_DESCRIPTORS = [
86
+ {
87
+ key: "transition",
88
+ description: "Block-to-block transition effect",
89
+ values: TRANSITION_TYPES
90
+ },
91
+ {
92
+ key: "transitionDuration",
93
+ description: "How long the transition lasts",
94
+ valueHint: "seconds \u2014 e.g. 0.7, 700ms"
95
+ },
96
+ {
97
+ key: "transitionDirection",
98
+ description: "Directional variant for the transition",
99
+ values: TRANSITION_DIRECTIONS
100
+ },
101
+ {
102
+ key: "startTime",
103
+ description: "Timeline start time of this block",
104
+ valueHint: "mm:ss or seconds \u2014 e.g. 01:30, 5, 1500ms"
105
+ },
106
+ {
107
+ key: "duration",
108
+ description: "How long this block lasts",
109
+ valueHint: "mm:ss or seconds \u2014 e.g. 45, 1500ms"
110
+ },
111
+ {
112
+ key: "x",
113
+ description: "Horizontal position on the diagram canvas",
114
+ valueHint: "number \u2014 e.g. 600"
115
+ },
116
+ {
117
+ key: "y",
118
+ description: "Vertical position on the diagram canvas",
119
+ valueHint: "number \u2014 e.g. 300"
120
+ },
121
+ {
122
+ key: "connectsTo",
123
+ description: "Diagram connections to other blocks",
124
+ valueHint: "comma-separated target or target:type \u2014 e.g. foo,bar:flow"
125
+ }
126
+ ];
127
+ function coerceAnnotationValues(params) {
128
+ const blockMeta = {};
129
+ const metadata = {};
130
+ const warnings = [];
131
+ let transitionType = null;
132
+ let transitionDuration;
133
+ let transitionDirection;
134
+ for (const [key, raw] of Object.entries(params)) {
135
+ const kind = KNOWN_BLOCK_META_KEYS[key];
136
+ if (!kind) {
137
+ metadata[key] = raw;
138
+ continue;
139
+ }
140
+ if (kind === "number") {
141
+ const n = parseNumber(raw);
142
+ if (n == null) {
143
+ warnings.push(`Invalid number for "${key}": ${JSON.stringify(raw)}`);
144
+ } else {
145
+ blockMeta[key] = n;
146
+ }
147
+ } else if (kind === "time") {
148
+ const s = parseTimeSeconds(raw);
149
+ if (s == null) {
150
+ warnings.push(`Invalid time for "${key}": ${JSON.stringify(raw)}`);
151
+ } else if (key === "transitionDuration") {
152
+ transitionDuration = s;
153
+ } else {
154
+ blockMeta[key] = s;
155
+ }
156
+ } else if (kind === "connectionList") {
157
+ const { list, warning } = parseConnectionList(raw);
158
+ if (warning) warnings.push(`"${key}": ${warning}`);
159
+ blockMeta.connectsTo = list;
160
+ } else if (kind === "transition") {
161
+ const transition = normalizeTransitionType(raw);
162
+ if (transition == null) {
163
+ warnings.push(`Invalid transition for "${key}": ${JSON.stringify(raw)}`);
164
+ } else {
165
+ transitionType = transition;
166
+ }
167
+ } else if (kind === "transitionDirection") {
168
+ const direction = normalizeTransitionDirection(raw);
169
+ if (direction == null) {
170
+ warnings.push(`Invalid transition direction for "${key}": ${JSON.stringify(raw)}`);
171
+ } else {
172
+ transitionDirection = direction;
173
+ }
174
+ }
175
+ }
176
+ if (transitionType) {
177
+ blockMeta.transition = {
178
+ type: transitionType,
179
+ ...transitionDuration !== void 0 ? { duration: transitionDuration } : {},
180
+ ...transitionDirection !== void 0 ? { direction: transitionDirection } : {}
181
+ };
182
+ }
183
+ return { blockMeta, metadata, warnings };
184
+ }
185
+ function parseNumber(raw) {
186
+ if (raw.trim() === "") return null;
187
+ const n = Number(raw);
188
+ return Number.isFinite(n) ? n : null;
189
+ }
190
+ var TIME_BARE_RE = /^\d+(?:\.\d+)?$/;
191
+ var TIME_MS_RE = /^(\d+(?:\.\d+)?)ms$/;
192
+ var TIME_MMSS_RE = /^(\d+):(\d{1,2})(?:\.(\d+))?$/;
193
+ function parseTimeSeconds(raw) {
194
+ const trimmed = raw.trim();
195
+ if (!trimmed) return null;
196
+ if (TIME_BARE_RE.test(trimmed)) {
197
+ const n = Number(trimmed);
198
+ return Number.isFinite(n) ? n : null;
199
+ }
200
+ const msMatch = trimmed.match(TIME_MS_RE);
201
+ if (msMatch) {
202
+ const n = Number(msMatch[1]);
203
+ return Number.isFinite(n) ? n / 1e3 : null;
204
+ }
205
+ const mmssMatch = trimmed.match(TIME_MMSS_RE);
206
+ if (mmssMatch) {
207
+ const mins = Number(mmssMatch[1]);
208
+ const secs = Number(mmssMatch[2]);
209
+ const frac = mmssMatch[3] ? Number(`0.${mmssMatch[3]}`) : 0;
210
+ if (!Number.isFinite(mins) || !Number.isFinite(secs) || !Number.isFinite(frac)) {
211
+ return null;
212
+ }
213
+ if (secs >= 60) return null;
214
+ return mins * 60 + secs + frac;
215
+ }
216
+ return null;
217
+ }
218
+ function parseConnectionList(raw) {
219
+ const trimmed = raw.trim();
220
+ if (!trimmed) return { list: [], warning: null };
221
+ const list = [];
222
+ let droppedEmpty = false;
223
+ for (const part of trimmed.split(",")) {
224
+ const entry = part.trim();
225
+ if (!entry) {
226
+ droppedEmpty = true;
227
+ continue;
228
+ }
229
+ const colonIdx = entry.indexOf(":");
230
+ if (colonIdx < 0) {
231
+ list.push({ target: entry });
232
+ } else {
233
+ const target = entry.slice(0, colonIdx).trim();
234
+ const type = entry.slice(colonIdx + 1).trim();
235
+ if (!target) {
236
+ droppedEmpty = true;
237
+ continue;
238
+ }
239
+ list.push(type ? { target, type } : { target });
240
+ }
241
+ }
242
+ return {
243
+ list,
244
+ warning: droppedEmpty ? "dropped empty connection entries" : null
245
+ };
246
+ }
247
+
73
248
  // src/markdown/sanitize.ts
74
249
  var SAFE_LINK_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
75
250
  var SAFE_MEDIA_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "blob"]);
@@ -313,180 +488,6 @@ function stripUrlSchemeNoise(value) {
313
488
  return out;
314
489
  }
315
490
 
316
- // src/markdown/annotationCoercion.ts
317
- var KNOWN_BLOCK_META_KEYS = {
318
- x: "number",
319
- y: "number",
320
- startTime: "time",
321
- duration: "time",
322
- connectsTo: "connectionList",
323
- transition: "transition",
324
- transitionDuration: "time",
325
- transitionDirection: "transitionDirection"
326
- };
327
- var BLOCK_META_KEY_DESCRIPTORS = [
328
- {
329
- key: "transition",
330
- description: "Block-to-block transition effect",
331
- values: TRANSITION_TYPES
332
- },
333
- {
334
- key: "transitionDuration",
335
- description: "How long the transition lasts",
336
- valueHint: "seconds \u2014 e.g. 0.7, 700ms"
337
- },
338
- {
339
- key: "transitionDirection",
340
- description: "Directional variant for the transition",
341
- values: TRANSITION_DIRECTIONS
342
- },
343
- {
344
- key: "startTime",
345
- description: "Timeline start time of this block",
346
- valueHint: "mm:ss or seconds \u2014 e.g. 01:30, 5, 1500ms"
347
- },
348
- {
349
- key: "duration",
350
- description: "How long this block lasts",
351
- valueHint: "mm:ss or seconds \u2014 e.g. 45, 1500ms"
352
- },
353
- {
354
- key: "x",
355
- description: "Horizontal position on the diagram canvas",
356
- valueHint: "number \u2014 e.g. 600"
357
- },
358
- {
359
- key: "y",
360
- description: "Vertical position on the diagram canvas",
361
- valueHint: "number \u2014 e.g. 300"
362
- },
363
- {
364
- key: "connectsTo",
365
- description: "Diagram connections to other blocks",
366
- valueHint: "comma-separated target or target:type \u2014 e.g. foo,bar:flow"
367
- }
368
- ];
369
- function coerceAnnotationValues(params) {
370
- const blockMeta = {};
371
- const metadata = {};
372
- const warnings = [];
373
- let transitionType = null;
374
- let transitionDuration;
375
- let transitionDirection;
376
- for (const [key, raw] of Object.entries(params)) {
377
- const kind = KNOWN_BLOCK_META_KEYS[key];
378
- if (!kind) {
379
- metadata[key] = raw;
380
- continue;
381
- }
382
- if (kind === "number") {
383
- const n = parseNumber(raw);
384
- if (n == null) {
385
- warnings.push(`Invalid number for "${key}": ${JSON.stringify(raw)}`);
386
- } else {
387
- blockMeta[key] = n;
388
- }
389
- } else if (kind === "time") {
390
- const s = parseTimeSeconds(raw);
391
- if (s == null) {
392
- warnings.push(`Invalid time for "${key}": ${JSON.stringify(raw)}`);
393
- } else if (key === "transitionDuration") {
394
- transitionDuration = s;
395
- } else {
396
- blockMeta[key] = s;
397
- }
398
- } else if (kind === "connectionList") {
399
- const { list, warning } = parseConnectionList(raw);
400
- if (warning) warnings.push(`"${key}": ${warning}`);
401
- blockMeta.connectsTo = list;
402
- } else if (kind === "transition") {
403
- const transition = normalizeTransitionType(raw);
404
- if (transition == null) {
405
- warnings.push(`Invalid transition for "${key}": ${JSON.stringify(raw)}`);
406
- } else {
407
- transitionType = transition;
408
- }
409
- } else if (kind === "transitionDirection") {
410
- const direction = normalizeTransitionDirection(raw);
411
- if (direction == null) {
412
- warnings.push(`Invalid transition direction for "${key}": ${JSON.stringify(raw)}`);
413
- } else {
414
- transitionDirection = direction;
415
- }
416
- }
417
- }
418
- if (transitionType) {
419
- blockMeta.transition = {
420
- type: transitionType,
421
- ...transitionDuration !== void 0 ? { duration: transitionDuration } : {},
422
- ...transitionDirection !== void 0 ? { direction: transitionDirection } : {}
423
- };
424
- }
425
- return { blockMeta, metadata, warnings };
426
- }
427
- function parseNumber(raw) {
428
- if (raw.trim() === "") return null;
429
- const n = Number(raw);
430
- return Number.isFinite(n) ? n : null;
431
- }
432
- var TIME_BARE_RE = /^\d+(?:\.\d+)?$/;
433
- var TIME_MS_RE = /^(\d+(?:\.\d+)?)ms$/;
434
- var TIME_MMSS_RE = /^(\d+):(\d{1,2})(?:\.(\d+))?$/;
435
- function parseTimeSeconds(raw) {
436
- const trimmed = raw.trim();
437
- if (!trimmed) return null;
438
- if (TIME_BARE_RE.test(trimmed)) {
439
- const n = Number(trimmed);
440
- return Number.isFinite(n) ? n : null;
441
- }
442
- const msMatch = trimmed.match(TIME_MS_RE);
443
- if (msMatch) {
444
- const n = Number(msMatch[1]);
445
- return Number.isFinite(n) ? n / 1e3 : null;
446
- }
447
- const mmssMatch = trimmed.match(TIME_MMSS_RE);
448
- if (mmssMatch) {
449
- const mins = Number(mmssMatch[1]);
450
- const secs = Number(mmssMatch[2]);
451
- const frac = mmssMatch[3] ? Number(`0.${mmssMatch[3]}`) : 0;
452
- if (!Number.isFinite(mins) || !Number.isFinite(secs) || !Number.isFinite(frac)) {
453
- return null;
454
- }
455
- if (secs >= 60) return null;
456
- return mins * 60 + secs + frac;
457
- }
458
- return null;
459
- }
460
- function parseConnectionList(raw) {
461
- const trimmed = raw.trim();
462
- if (!trimmed) return { list: [], warning: null };
463
- const list = [];
464
- let droppedEmpty = false;
465
- for (const part of trimmed.split(",")) {
466
- const entry = part.trim();
467
- if (!entry) {
468
- droppedEmpty = true;
469
- continue;
470
- }
471
- const colonIdx = entry.indexOf(":");
472
- if (colonIdx < 0) {
473
- list.push({ target: entry });
474
- } else {
475
- const target = entry.slice(0, colonIdx).trim();
476
- const type = entry.slice(colonIdx + 1).trim();
477
- if (!target) {
478
- droppedEmpty = true;
479
- continue;
480
- }
481
- list.push(type ? { target, type } : { target });
482
- }
483
- }
484
- return {
485
- list,
486
- warning: droppedEmpty ? "dropped empty connection entries" : null
487
- };
488
- }
489
-
490
491
  // src/markdown/attrTokens.ts
491
492
  var DQ_RUN = `"(?:[^"\\\\]|\\\\.)*"`;
492
493
  var SQ_RUN = `'(?:[^'\\\\]|\\\\.)*'`;
@@ -589,13 +590,13 @@ export {
589
590
  CONTAINER_TEMPLATES,
590
591
  isContainerTemplate,
591
592
  isReservedAnnotationToken,
592
- sanitizeUrl,
593
- sanitizeHtmlNodes,
594
593
  KNOWN_BLOCK_META_KEYS,
595
594
  BLOCK_META_KEY_DESCRIPTORS,
596
595
  coerceAnnotationValues,
597
596
  parseNumber,
598
597
  parseTimeSeconds,
598
+ sanitizeUrl,
599
+ sanitizeHtmlNodes,
599
600
  matchTrailingTemplateAnnotation,
600
601
  matchTrailingPandocAttr,
601
602
  tokenizeAttrTokens,
@@ -23,7 +23,7 @@ import {
23
23
  templateRegistry,
24
24
  writeCustomTemplatesToFrontmatter,
25
25
  writeCustomThemesToFrontmatter
26
- } from "./chunk-2HY2ZA7U.js";
26
+ } from "./chunk-GSJEGMKF.js";
27
27
  import {
28
28
  ASCII_TREE_VOCAB,
29
29
  ASCII_VOCAB,
@@ -58,7 +58,7 @@ import {
58
58
  } from "./chunk-BAOV476U.js";
59
59
  import {
60
60
  parseMarkdown
61
- } from "./chunk-7TJJA2RI.js";
61
+ } from "./chunk-6MVWWZHL.js";
62
62
  import {
63
63
  KNOWN_BLOCK_META_KEYS,
64
64
  TEMPLATE_ALIASES,
@@ -66,7 +66,7 @@ import {
66
66
  isContainerTemplate,
67
67
  resolveTemplateName,
68
68
  serializeAnnotation
69
- } from "./chunk-D6YTLQCL.js";
69
+ } from "./chunk-NBKAXPSX.js";
70
70
  import {
71
71
  extractPlainText,
72
72
  getChildren,
@@ -334,6 +334,112 @@ function cssFilterForTreatment(treatment, blur) {
334
334
  return parts.length > 0 ? parts.join(" ") : void 0;
335
335
  }
336
336
 
337
+ // src/doc/coverSlideSettings.ts
338
+ var COVER_SLIDE_TEMPLATE_OPTIONS = Object.freeze([
339
+ {
340
+ id: "cover",
341
+ label: "Hero cover",
342
+ description: "The classic Squisq cover with title, subtitle, and optional hero image."
343
+ },
344
+ {
345
+ id: "title",
346
+ label: "Title",
347
+ description: "A theme-led title card without a full-bleed image."
348
+ },
349
+ {
350
+ id: "sectionHeader",
351
+ label: "Section header",
352
+ description: "A bold section divider using the hero image when one is available."
353
+ },
354
+ {
355
+ id: "imageWithCaption",
356
+ label: "Image with title",
357
+ description: "A full-bleed image with the title and subtitle overlaid.",
358
+ requiresHeroImage: true
359
+ },
360
+ {
361
+ id: "bigText",
362
+ label: "Big text",
363
+ description: "The title in gigantic uppercase type on a clean theme surface \u2014 thumbnail-ready."
364
+ },
365
+ {
366
+ id: "bigTextImage",
367
+ label: "Big text on image",
368
+ description: "The title in gigantic uppercase type over the hero image, with a contrast bloom behind the text.",
369
+ requiresHeroImage: true
370
+ }
371
+ ]);
372
+ var COVER_SLIDE_FRONTMATTER_KEYS = Object.freeze({
373
+ enabled: { canonical: "squisq-cover-slide", legacy: "cover-slide" },
374
+ template: { canonical: "squisq-cover-template", legacy: "cover-template" },
375
+ duration: { canonical: "squisq-cover-duration", legacy: "cover-duration" },
376
+ playback: { canonical: "squisq-cover-playback", legacy: "cover-playback" }
377
+ });
378
+ var DEFAULT_COVER_SLIDE_SETTINGS = Object.freeze({
379
+ enabled: true,
380
+ template: "cover",
381
+ duration: 2,
382
+ playback: "preroll"
383
+ });
384
+ var MAX_COVER_SLIDE_DURATION_SECONDS = 60;
385
+ function readSetting(frontmatter, keys) {
386
+ if (!frontmatter) return void 0;
387
+ return Object.prototype.hasOwnProperty.call(frontmatter, keys.canonical) ? frontmatter[keys.canonical] : frontmatter[keys.legacy];
388
+ }
389
+ function resolveBoolean(value) {
390
+ if (typeof value === "boolean") return value;
391
+ if (typeof value !== "string") return void 0;
392
+ const normalized = value.trim().toLowerCase();
393
+ if (["true", "yes", "on", "show", "visible"].includes(normalized)) return true;
394
+ if (["false", "no", "off", "hide", "hidden"].includes(normalized)) return false;
395
+ return void 0;
396
+ }
397
+ function resolveTemplate(value) {
398
+ if (typeof value !== "string") return void 0;
399
+ const normalized = value.trim().toLowerCase().replace(/[_\s-]+/g, "");
400
+ if (normalized === "cover" || normalized === "hero" || normalized === "managedcover") {
401
+ return "cover";
402
+ }
403
+ if (normalized === "title" || normalized === "titleblock") return "title";
404
+ if (normalized === "sectionheader" || normalized === "section") return "sectionHeader";
405
+ if (normalized === "imagewithcaption" || normalized === "imagetitle" || normalized === "fullbleedimage") {
406
+ return "imageWithCaption";
407
+ }
408
+ if (normalized === "bigtextimage" || normalized === "largetextimage" || normalized === "bigtextonimage") {
409
+ return "bigTextImage";
410
+ }
411
+ if (normalized === "bigtext" || normalized === "largetext") return "bigText";
412
+ return void 0;
413
+ }
414
+ function resolveDuration(value) {
415
+ const duration = typeof value === "number" ? value : typeof value === "string" && value.trim().length > 0 ? Number(value) : Number.NaN;
416
+ if (!Number.isFinite(duration) || duration < 0 || duration > MAX_COVER_SLIDE_DURATION_SECONDS) {
417
+ return void 0;
418
+ }
419
+ return duration;
420
+ }
421
+ function resolvePlayback(value) {
422
+ if (typeof value !== "string") return void 0;
423
+ const normalized = value.trim().toLowerCase().replace(/[_\s]+/g, "-");
424
+ if (["overlay", "over", "concurrent", "play-over"].includes(normalized)) return "overlay";
425
+ if (["preroll", "pre-roll", "delay", "push", "shift"].includes(normalized)) return "preroll";
426
+ return void 0;
427
+ }
428
+ function resolveCoverSlideSettings(frontmatter, overrides = {}) {
429
+ const resolved = {
430
+ enabled: resolveBoolean(readSetting(frontmatter, COVER_SLIDE_FRONTMATTER_KEYS.enabled)) ?? DEFAULT_COVER_SLIDE_SETTINGS.enabled,
431
+ template: resolveTemplate(readSetting(frontmatter, COVER_SLIDE_FRONTMATTER_KEYS.template)) ?? DEFAULT_COVER_SLIDE_SETTINGS.template,
432
+ duration: resolveDuration(readSetting(frontmatter, COVER_SLIDE_FRONTMATTER_KEYS.duration)) ?? DEFAULT_COVER_SLIDE_SETTINGS.duration,
433
+ playback: resolvePlayback(readSetting(frontmatter, COVER_SLIDE_FRONTMATTER_KEYS.playback)) ?? DEFAULT_COVER_SLIDE_SETTINGS.playback
434
+ };
435
+ return {
436
+ enabled: overrides.enabled ?? resolved.enabled,
437
+ template: overrides.template ?? resolved.template,
438
+ duration: resolveDuration(overrides.duration) ?? resolved.duration,
439
+ playback: overrides.playback ?? resolved.playback
440
+ };
441
+ }
442
+
337
443
  // src/doc/docToMarkdown.ts
338
444
  var TRANSITION_PARAM_KEYS = ["transition", "transitionDuration", "transitionDirection"];
339
445
  var DEFAULT_TEMPLATE = "sectionHeader";
@@ -681,6 +787,17 @@ var sectionHeader = (input) => {
681
787
  mediaBackground: !!s.imageSrc
682
788
  };
683
789
  };
790
+ var bigText = (input) => {
791
+ const b = input;
792
+ const media = b.imageSrc ? { type: "image", src: b.imageSrc, alt: b.imageAlt ?? "" } : void 0;
793
+ return {
794
+ kind: "hero",
795
+ variant: media ? "media" : "title",
796
+ slots: { title: b.title, media },
797
+ emphasis: "lead",
798
+ mediaBackground: !!media
799
+ };
800
+ };
684
801
  var content = (input) => {
685
802
  const c = input;
686
803
  return {
@@ -965,6 +1082,7 @@ var layout = (input, ctx) => canvasDraft("layout", ctx, input);
965
1082
  var sectionExtractors = {
966
1083
  title,
967
1084
  sectionHeader,
1085
+ bigText,
968
1086
  content,
969
1087
  statHighlight,
970
1088
  quote,
@@ -2025,24 +2143,24 @@ function retimeBlocks(blocks, ctx) {
2025
2143
  const range = ctx.ranges.get(block.id);
2026
2144
  const pinned = getPinnedBlockMeta(block);
2027
2145
  const next = { ...block };
2028
- if (range && pinned.duration == null && pinned.startTime == null) {
2029
- next.startTime = ctx.clipStart + range.startSec;
2030
- next.duration = Math.max(0, range.endSec - range.startSec);
2031
- ctx.cursor = Math.max(ctx.cursor, next.startTime + next.duration);
2032
- } else if (range) {
2033
- const pinnedStart = pinned.startTime ?? block.startTime;
2034
- const pinnedDuration = pinned.duration ?? block.duration;
2035
- const narrStart = ctx.clipStart + range.startSec;
2146
+ if (range) {
2147
+ const narrStart = ctx.clipStart + range.startSec + ctx.shift;
2036
2148
  const narrDuration = Math.max(0, range.endSec - range.startSec);
2037
- if (Math.abs(pinnedStart - narrStart) > PIN_CONFLICT_TOLERANCE_SEC || Math.abs(pinnedDuration - narrDuration) > PIN_CONFLICT_TOLERANCE_SEC) {
2149
+ next.startTime = pinned.startTime ?? narrStart;
2150
+ next.duration = pinned.duration ?? narrDuration;
2151
+ const end = next.startTime + next.duration;
2152
+ ctx.shift = end - (ctx.clipStart + range.endSec);
2153
+ const durationConflict = pinned.duration != null && Math.abs(pinned.duration - narrDuration) > PIN_CONFLICT_TOLERANCE_SEC;
2154
+ const startConflict = pinned.startTime != null && Math.abs(pinned.startTime - narrStart) > PIN_CONFLICT_TOLERANCE_SEC;
2155
+ if (durationConflict || startConflict) {
2038
2156
  ctx.diagnostics.push({
2039
2157
  severity: "info",
2040
2158
  code: "narration-pin-conflict",
2041
- message: `Block timing is pinned (duration=/startTime=) but the recorded narration says ~${narrDuration.toFixed(1)}s starting at ~${narrStart.toFixed(1)}s. The pin wins; remove it to follow the narration.`,
2159
+ message: `Block timing is pinned (duration=/startTime=) but the recorded narration says ~${narrDuration.toFixed(1)}s starting at ~${narrStart.toFixed(1)}s. The pin wins and later blocks follow it, while the recorded voice keeps its own schedule \u2014 playback drifts from the take past this block. Remove the pin to follow the narration.`,
2042
2160
  blockId: block.id
2043
2161
  });
2044
2162
  }
2045
- ctx.cursor = Math.max(ctx.cursor, pinnedStart + pinnedDuration);
2163
+ ctx.cursor = Math.max(ctx.cursor, end);
2046
2164
  } else {
2047
2165
  next.startTime = ctx.cursor;
2048
2166
  ctx.cursor += next.duration;
@@ -2068,7 +2186,8 @@ async function applyNarrationTiming(doc, container) {
2068
2186
  clipStart: clip.startAt,
2069
2187
  ranges,
2070
2188
  diagnostics: [],
2071
- cursor: 0
2189
+ cursor: 0,
2190
+ shift: 0
2072
2191
  };
2073
2192
  const blocks = retimeBlocks(doc.blocks, ctx);
2074
2193
  const duration = Math.max(clip.startAt + timing.duration, ctx.cursor);
@@ -3865,6 +3984,11 @@ export {
3865
3984
  getTransitionClass,
3866
3985
  getAnimationProgress,
3867
3986
  cssFilterForTreatment,
3987
+ COVER_SLIDE_TEMPLATE_OPTIONS,
3988
+ COVER_SLIDE_FRONTMATTER_KEYS,
3989
+ DEFAULT_COVER_SLIDE_SETTINGS,
3990
+ MAX_COVER_SLIDE_DURATION_SECONDS,
3991
+ resolveCoverSlideSettings,
3868
3992
  docToMarkdown,
3869
3993
  resolveThemeForDoc,
3870
3994
  isTemplatedPageBlock,