@bendyline/squisq 2.13.13 → 2.13.15

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 (35) hide show
  1. package/dist/{Doc-BhvotZx_.d.ts → Doc-C_Sh49ot.d.ts} +59 -6
  2. package/dist/{ImageEditDoc-D8hYio6b.d.ts → ImageEditDoc-Sqw3Tw-R.d.ts} +1 -1
  3. package/dist/{chunk-OIQ2GO2O.js → chunk-2CB37BOU.js} +1 -1
  4. package/dist/{chunk-CTT4XAGO.js → chunk-4HQ7QZKB.js} +2289 -630
  5. package/dist/chunk-C7DKMO4H.js +88 -0
  6. package/dist/{chunk-C4SB3NN4.js → chunk-DDJHIZED.js} +1 -1
  7. package/dist/{chunk-TCGGJUKC.js → chunk-FYP7GNER.js} +93 -15
  8. package/dist/chunk-N3FQSUVT.js +515 -0
  9. package/dist/{chunk-SGNWO7OF.js → chunk-OFE5RZWW.js} +490 -24
  10. package/dist/{chunk-YQQWE4HG.js → chunk-OJCAKUHW.js} +192 -260
  11. package/dist/{chunk-7MS26OXD.js → chunk-UDPPWYYM.js} +6 -0
  12. package/dist/{chunk-5DFFVW75.js → chunk-Y34KYMAS.js} +1 -1
  13. package/dist/doc/index.d.ts +337 -22
  14. package/dist/doc/index.js +56 -8
  15. package/dist/fence/index.d.ts +1 -1
  16. package/dist/generate/index.d.ts +1 -1
  17. package/dist/imageEdit/index.d.ts +3 -3
  18. package/dist/index.d.ts +8 -8
  19. package/dist/index.js +127 -21
  20. package/dist/jsonForm/index.d.ts +1 -1
  21. package/dist/markdown/index.js +3 -3
  22. package/dist/{materializePageSection-CWxou4mX.d.ts → materializePageSection-Dmf-rf1b.d.ts} +121 -8
  23. package/dist/mediaEdit/index.d.ts +2 -2
  24. package/dist/narration/index.d.ts +1 -1
  25. package/dist/narration/index.js +4 -4
  26. package/dist/recommend/index.d.ts +129 -2
  27. package/dist/recommend/index.js +11 -3
  28. package/dist/schemas/index.d.ts +4 -4
  29. package/dist/{themeLibrary-BIYE6aLw.d.ts → themeLibrary-B4VLovhW.d.ts} +1 -1
  30. package/dist/timing/index.d.ts +66 -2
  31. package/dist/timing/index.js +12 -0
  32. package/dist/transform/index.d.ts +173 -16
  33. package/dist/transform/index.js +55 -16
  34. package/package.json +1 -1
  35. package/dist/chunk-FCB6JLWB.js +0 -125
@@ -1,14 +1,24 @@
1
1
  import {
2
+ AUTHORED_PRESENTATION_LAYOUT,
3
+ PRESENTATION_KEY,
4
+ PRESENTATION_LAYOUTS,
5
+ PRESENTATION_LIMITS,
2
6
  buildNarrationScript,
3
7
  buildSourceText,
4
8
  coerceTemplateParams,
5
9
  conceptBuildStepTexts,
10
+ conceptTemplate,
6
11
  flattenRenderableBlocks,
7
12
  getPinnedBlockMeta,
8
- markdownToDoc
9
- } from "./chunk-CTT4XAGO.js";
13
+ markdownToDoc,
14
+ parsePresentationPlan,
15
+ presentationBeatExtras,
16
+ presentationLayoutFits,
17
+ presentationStat
18
+ } from "./chunk-4HQ7QZKB.js";
10
19
  import {
11
20
  deriveTemplateInputs,
21
+ extractConceptItems,
12
22
  extractEmbeddedVideos,
13
23
  extractImages,
14
24
  isAsciiDiagramFence,
@@ -20,8 +30,9 @@ import {
20
30
  isPictureParagraph,
21
31
  isStandaloneHtmlVideo,
22
32
  isTreeFence,
23
- matchNumberHighlight
24
- } from "./chunk-SGNWO7OF.js";
33
+ matchNumberHighlight,
34
+ shapeIsStated
35
+ } from "./chunk-OFE5RZWW.js";
25
36
  import {
26
37
  extractContent,
27
38
  mapElementToBlock,
@@ -34,10 +45,10 @@ import {
34
45
  } from "./chunk-JNUNNQ74.js";
35
46
  import {
36
47
  parseMarkdown
37
- } from "./chunk-C4SB3NN4.js";
48
+ } from "./chunk-DDJHIZED.js";
38
49
  import {
39
50
  isContainerTemplate
40
- } from "./chunk-7MS26OXD.js";
51
+ } from "./chunk-UDPPWYYM.js";
41
52
  import {
42
53
  extractPlainText,
43
54
  findBlockVideoEmbed,
@@ -59,142 +70,6 @@ import {
59
70
  hashString
60
71
  } from "./chunk-XRW6P7IF.js";
61
72
 
62
- // src/transform/presentationPlan.ts
63
- var PRESENTATION_KEY = "squisq-presentation";
64
- var PRESENTATION_LAYOUTS = [
65
- "title",
66
- "divider",
67
- "statement",
68
- "emphasis",
69
- "quote",
70
- "stat",
71
- "definition",
72
- "list",
73
- "comparison",
74
- "steps",
75
- "image",
76
- "cards",
77
- "process",
78
- "cycle",
79
- "layers"
80
- ];
81
- var AUTHORED_PRESENTATION_LAYOUT = "authored";
82
- var EMPHASIS_CHARACTERS = 60;
83
- var spaced = (text3) => text3.replace(/\s+/gu, " ").trim();
84
- function presentationStat(headline, points) {
85
- for (const [index, line] of [headline, ...points].entries()) {
86
- const match = matchNumberHighlight(line);
87
- if (match) return { match, line, index: index - 1 };
88
- }
89
- return null;
90
- }
91
- function presentationLayoutFits(layout, headline, points, passage) {
92
- switch (layout) {
93
- case "list":
94
- case "steps":
95
- case "cards":
96
- case "process":
97
- return points.length >= 2;
98
- case "cycle":
99
- case "layers":
100
- return points.length >= 3;
101
- case "comparison":
102
- return points.length === 2;
103
- case "definition":
104
- return points.length >= 1;
105
- case "stat":
106
- return presentationStat(headline, points) !== null;
107
- case "emphasis":
108
- return headline.length <= EMPHASIS_CHARACTERS;
109
- case "quote":
110
- return spaced(passage).includes(spaced(headline));
111
- default:
112
- return true;
113
- }
114
- }
115
- var PRESENTATION_LIMITS = {
116
- sourceCharacters: 4e4,
117
- beats: 80,
118
- serializedCharacters: 16e4,
119
- mediaPerBeat: 4
120
- };
121
- function record(value) {
122
- return !!value && typeof value === "object" && !Array.isArray(value);
123
- }
124
- function keys(value, required, optional = []) {
125
- return required.every((key) => Object.prototype.hasOwnProperty.call(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
126
- }
127
- function text(value, max) {
128
- return typeof value === "string" && value.trim().length > 0 && value.length <= max && !Array.from(value).some((character) => {
129
- const code = character.charCodeAt(0);
130
- return code < 32 && code !== 9 && code !== 10 && code !== 13;
131
- });
132
- }
133
- function parsePresentationPlan(input) {
134
- let value = input;
135
- if (typeof value === "string") {
136
- if (value.length > PRESENTATION_LIMITS.serializedCharacters) return null;
137
- try {
138
- value = JSON.parse(value);
139
- } catch {
140
- return null;
141
- }
142
- }
143
- if (!record(value) || !keys(value, ["version", "sourceText", "origin", "beats"]) || value.version !== 1 || !text(value.sourceText, PRESENTATION_LIMITS.sourceCharacters) || typeof value.origin !== "string" || !["automatic", "ai"].includes(value.origin) || !Array.isArray(value.beats) || !value.beats.length || value.beats.length > PRESENTATION_LIMITS.beats)
144
- return null;
145
- const beats = [];
146
- const ids = /* @__PURE__ */ new Set();
147
- let end = 0;
148
- for (const beat of value.beats) {
149
- if (!record(beat) || !keys(
150
- beat,
151
- ["id", "sourceStart", "sourceEnd", "layout", "headline", "points"],
152
- ["imageSrc", "media", "hidden"]
153
- ) || "hidden" in beat && beat.hidden !== true || !text(beat.id, 80) || ids.has(beat.id) || !Number.isInteger(beat.sourceStart) || !Number.isInteger(beat.sourceEnd) || typeof beat.sourceStart !== "number" || typeof beat.sourceEnd !== "number" || beat.sourceStart < end || beat.sourceEnd <= beat.sourceStart || beat.sourceEnd > value.sourceText.length || value.sourceText.slice(end, beat.sourceStart).trim() || !text(beat.headline, 110) || !Array.isArray(beat.points) || beat.points.length > 4 || !beat.points.every((point) => text(point, 180)))
154
- return null;
155
- if (beat.layout === AUTHORED_PRESENTATION_LAYOUT) {
156
- if (beat.points.length || "media" in beat) return null;
157
- } else if (!PRESENTATION_LAYOUTS.includes(beat.layout) || !presentationLayoutFits(
158
- beat.layout,
159
- beat.headline,
160
- beat.points,
161
- value.sourceText.slice(beat.sourceStart, beat.sourceEnd)
162
- ))
163
- return null;
164
- if (beat.layout === "image" ? !text(beat.imageSrc, 2048) : "imageSrc" in beat) return null;
165
- if ("media" in beat && (!Array.isArray(beat.media) || !beat.media.length || beat.media.length > PRESENTATION_LIMITS.mediaPerBeat || new Set(beat.media).size !== beat.media.length || !beat.media.every((ref) => text(ref, 1100))))
166
- return null;
167
- ids.add(beat.id);
168
- end = beat.sourceEnd;
169
- beats.push({
170
- id: beat.id,
171
- sourceStart: beat.sourceStart,
172
- sourceEnd: beat.sourceEnd,
173
- layout: beat.layout,
174
- headline: beat.headline,
175
- points: [...beat.points],
176
- ...beat.layout === "image" ? { imageSrc: beat.imageSrc } : {},
177
- ..."media" in beat ? { media: [...beat.media] } : {},
178
- ...beat.hidden === true ? { hidden: true } : {}
179
- });
180
- }
181
- if (value.sourceText.slice(end).trim()) return null;
182
- return {
183
- version: 1,
184
- sourceText: value.sourceText,
185
- origin: value.origin,
186
- beats
187
- };
188
- }
189
- function serializePresentationPlan(plan) {
190
- const parsed = parsePresentationPlan(plan);
191
- if (!parsed) throw new Error("The presentation plan is invalid.");
192
- const result = JSON.stringify(parsed);
193
- if (result.length > PRESENTATION_LIMITS.serializedCharacters)
194
- throw new Error("The presentation plan is too large.");
195
- return result;
196
- }
197
-
198
73
  // src/transform/presentationHints.ts
199
74
  var PRESENTATION_HINTS_KEY = "squisq-presentation-hints";
200
75
  var DYNAMIC_PRESENTATION_STYLE = "dynamic-slides";
@@ -204,13 +79,13 @@ function presentationPassageKey(passage) {
204
79
  function defaultPresentationHints() {
205
80
  return { version: 1, density: "balanced", inbetweens: true, blocks: [] };
206
81
  }
207
- function record2(value) {
82
+ function record(value) {
208
83
  return !!value && typeof value === "object" && !Array.isArray(value);
209
84
  }
210
85
  function exact(value, required, optional = []) {
211
86
  return required.every((key) => Object.prototype.hasOwnProperty.call(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
212
87
  }
213
- function text2(value, max) {
88
+ function text(value, max) {
214
89
  return typeof value === "string" && !!value.trim() && value.length <= max && [...value].every((char) => char.charCodeAt(0) >= 32 || ["\n", "\r", " "].includes(char));
215
90
  }
216
91
  function parsePresentationHints(input) {
@@ -223,12 +98,12 @@ function parsePresentationHints(input) {
223
98
  return null;
224
99
  }
225
100
  }
226
- if (!record2(value) || !exact(value, ["version", "density", "inbetweens", "blocks"]) || value.version !== 1 || !["concise", "balanced", "detailed", "section"].includes(String(value.density)) || typeof value.inbetweens !== "boolean" || !Array.isArray(value.blocks) || value.blocks.length > 80)
101
+ if (!record(value) || !exact(value, ["version", "density", "inbetweens", "blocks"]) || value.version !== 1 || !["concise", "balanced", "detailed", "section"].includes(String(value.density)) || typeof value.inbetweens !== "boolean" || !Array.isArray(value.blocks) || value.blocks.length > 80)
227
102
  return null;
228
103
  const ids = /* @__PURE__ */ new Set();
229
104
  const blocks = [];
230
105
  for (const entry of value.blocks) {
231
- if (!record2(entry) || !exact(entry, ["blockId"], ["layout", "inbetweens", "wording", "ai", "build", "hidden"]) || !text2(entry.blockId, 1024) || ids.has(entry.blockId) || entry.layout !== void 0 && entry.layout !== AUTHORED_PRESENTATION_LAYOUT && !PRESENTATION_LAYOUTS.includes(entry.layout) || entry.inbetweens !== void 0 && typeof entry.inbetweens !== "boolean" || entry.hidden !== void 0 && (!Array.isArray(entry.hidden) || !entry.hidden.length || entry.hidden.length > 80 || new Set(entry.hidden).size !== entry.hidden.length || !entry.hidden.every(
106
+ if (!record(entry) || !exact(entry, ["blockId"], ["layout", "inbetweens", "wording", "ai", "build", "hidden"]) || !text(entry.blockId, 1024) || ids.has(entry.blockId) || entry.layout !== void 0 && entry.layout !== AUTHORED_PRESENTATION_LAYOUT && !PRESENTATION_LAYOUTS.includes(entry.layout) || entry.inbetweens !== void 0 && typeof entry.inbetweens !== "boolean" || entry.hidden !== void 0 && (!Array.isArray(entry.hidden) || !entry.hidden.length || entry.hidden.length > 80 || new Set(entry.hidden).size !== entry.hidden.length || !entry.hidden.every(
232
107
  (key) => typeof key === "string" && /^[0-9a-z]{1,7}-[0-9a-z]{1,4}$/u.test(key)
233
108
  )))
234
109
  return null;
@@ -245,7 +120,10 @@ function parsePresentationHints(input) {
245
120
  if (!Array.isArray(entry.ai) || entry.ai.length > 80) return null;
246
121
  hint.ai = [];
247
122
  for (const item of entry.ai) {
248
- if (!record2(item) || !exact(item, ["source", "layout", "headline", "points"]) || !text2(item.source, 4e3) || !PRESENTATION_LAYOUTS.includes(item.layout) || !text2(item.headline, 110) || !Array.isArray(item.points) || item.points.length > 4 || !item.points.every((point) => text2(point, 180)) || !presentationLayoutFits(
123
+ if (!record(item) || !exact(item, ["source", "layout", "headline", "points"], ["center", "xAxis", "yAxis"]) || !text(item.source, 4e3) || !PRESENTATION_LAYOUTS.includes(item.layout) || !text(item.headline, 110) || !Array.isArray(item.points) || item.points.length > PRESENTATION_LIMITS.points || !item.points.every((point) => text(point, 180)) || // An extra is saved only on the layout that draws it, as it will be set.
124
+ ["center", "xAxis", "yAxis"].some(
125
+ (key) => key in item && item[key] !== presentationBeatExtras(String(item.layout), item)[key]
126
+ ) || !presentationLayoutFits(
249
127
  item.layout,
250
128
  item.headline,
251
129
  item.points,
@@ -256,13 +134,14 @@ function parsePresentationHints(input) {
256
134
  source: item.source,
257
135
  layout: item.layout,
258
136
  headline: item.headline,
259
- points: [...item.points]
137
+ points: [...item.points],
138
+ ...presentationBeatExtras(String(item.layout), item)
260
139
  });
261
140
  }
262
141
  }
263
142
  if (entry.wording !== void 0) {
264
143
  const wording = entry.wording;
265
- if (!record2(wording) || !exact(wording, ["source"], ["headline", "points"]) || !text2(wording.source, 4e4) || wording.headline === void 0 && wording.points === void 0 || wording.headline !== void 0 && !text2(wording.headline, 110) || wording.points !== void 0 && (!Array.isArray(wording.points) || wording.points.length > 4 || !wording.points.every((point) => text2(point, 180))))
144
+ if (!record(wording) || !exact(wording, ["source"], ["headline", "points"]) || !text(wording.source, 4e4) || wording.headline === void 0 && wording.points === void 0 || wording.headline !== void 0 && !text(wording.headline, 110) || wording.points !== void 0 && (!Array.isArray(wording.points) || wording.points.length > PRESENTATION_LIMITS.points || !wording.points.every((point) => text(point, 180))))
266
145
  return null;
267
146
  hint.wording = {
268
147
  source: wording.source,
@@ -589,13 +468,13 @@ function maskNonProse(stripped, contents) {
589
468
  let masked = stripped;
590
469
  let cursor = 0;
591
470
  for (const node of contents) {
592
- const text3 = stripMarkdown(extractPlainText(node));
593
- if (!text3) continue;
594
- const at = stripped.indexOf(text3, cursor);
471
+ const text2 = stripMarkdown(extractPlainText(node));
472
+ if (!text2) continue;
473
+ const at = stripped.indexOf(text2, cursor);
595
474
  if (at < 0) continue;
596
- cursor = at + text3.length;
475
+ cursor = at + text2.length;
597
476
  if (PROSE_NODE_TYPES.has(node.type)) continue;
598
- masked = masked.slice(0, at) + text3.replace(/\S/g, " ") + masked.slice(cursor);
477
+ masked = masked.slice(0, at) + text2.replace(/\S/g, " ") + masked.slice(cursor);
599
478
  }
600
479
  return masked;
601
480
  }
@@ -897,8 +776,8 @@ var PROSE_KEYS = /* @__PURE__ */ new Set([
897
776
  "quote",
898
777
  "text"
899
778
  ]);
900
- function briefText(text3) {
901
- const normalized = text3.replace(/\s+/g, " ").trim();
779
+ function briefText(text2) {
780
+ const normalized = text2.replace(/\s+/g, " ").trim();
902
781
  const sentence = normalized.match(/^.*?[.!?](?:["'”’])?(?=\s|$)/)?.[0] ?? normalized;
903
782
  const words = sentence.split(/\s+/).filter(Boolean);
904
783
  let brief = words.slice(0, 24).join(" ");
@@ -959,11 +838,11 @@ function extractFeatureContents(contents, keepData = false) {
959
838
  for (const node of contents ?? []) visit(node);
960
839
  return features;
961
840
  }
962
- function withoutProse(record3) {
963
- return record3 && Object.fromEntries(Object.entries(record3).filter(([key]) => !PROSE_KEYS.has(key)));
841
+ function withoutProse(record2) {
842
+ return record2 && Object.fromEntries(Object.entries(record2).filter(([key]) => !PROSE_KEYS.has(key)));
964
843
  }
965
844
  function typedMediaContents(block, bodyMedia) {
966
- const record3 = {
845
+ const record2 = {
967
846
  ...block,
968
847
  ...block.templateData,
969
848
  ...coerceTemplateParams(block.template ?? "", block.templateOverrides ?? {}).input
@@ -979,23 +858,23 @@ function typedMediaContents(block, bodyMedia) {
979
858
  });
980
859
  }
981
860
  };
982
- addImage(record3.imageSrc, record3.imageAlt);
861
+ addImage(record2.imageSrc, record2.imageAlt);
983
862
  for (const key of ["accentImage", "backgroundImage"]) {
984
- const image = record3[key];
863
+ const image = record2[key];
985
864
  if (image && typeof image === "object") {
986
865
  const data = image;
987
866
  addImage(data.src, data.alt);
988
867
  }
989
868
  }
990
- if (Array.isArray(record3.images)) {
991
- for (const image of record3.images) {
869
+ if (Array.isArray(record2.images)) {
870
+ for (const image of record2.images) {
992
871
  if (image && typeof image === "object") {
993
872
  const data = image;
994
873
  addImage(data.src, data.alt);
995
874
  }
996
875
  }
997
876
  }
998
- if (typeof record3.videoSrc === "string" && record3.videoSrc) {
877
+ if (typeof record2.videoSrc === "string" && record2.videoSrc) {
999
878
  const htmlChildren = [
1000
879
  {
1001
880
  type: "htmlElement",
@@ -1003,8 +882,8 @@ function typedMediaContents(block, bodyMedia) {
1003
882
  selfClosing: false,
1004
883
  children: [],
1005
884
  attributes: {
1006
- src: record3.videoSrc,
1007
- ...typeof record3.posterSrc === "string" ? { poster: record3.posterSrc } : {}
885
+ src: record2.videoSrc,
886
+ ...typeof record2.posterSrc === "string" ? { poster: record2.posterSrc } : {}
1008
887
  }
1009
888
  }
1010
889
  ];
@@ -1636,21 +1515,21 @@ function standaloneImagePool(images, content) {
1636
1515
  ...available.filter((image) => accents.has(image.src))
1637
1516
  ];
1638
1517
  }
1639
- function chunkNarration(text3, count) {
1640
- if (count <= 1) return [text3];
1518
+ function chunkNarration(text2, count) {
1519
+ if (count <= 1) return [text2];
1641
1520
  const sentences = [];
1642
1521
  const boundary = /[.!?]["'’”)\]]*\s+/g;
1643
1522
  let start = 0;
1644
1523
  let match;
1645
- while ((match = boundary.exec(text3)) !== null) {
1524
+ while ((match = boundary.exec(text2)) !== null) {
1646
1525
  const end = match.index + match[0].length;
1647
- const sentence = text3.slice(start, end).trim();
1526
+ const sentence = text2.slice(start, end).trim();
1648
1527
  if (sentence) sentences.push(sentence);
1649
1528
  start = end;
1650
1529
  }
1651
- const tail = text3.slice(start).trim();
1530
+ const tail = text2.slice(start).trim();
1652
1531
  if (tail) sentences.push(tail);
1653
- if (sentences.length < 2) return [text3];
1532
+ if (sentences.length < 2) return [text2];
1654
1533
  const chunkCount = Math.min(count, sentences.length);
1655
1534
  const words = sentences.map((sentence) => sentence.split(/\s+/).length);
1656
1535
  const totalWords = words.reduce((sum, n) => sum + n, 0);
@@ -1721,6 +1600,51 @@ function videoBlock(video, index, segmentId, duration) {
1721
1600
  };
1722
1601
  }
1723
1602
 
1603
+ // src/transform/conceptReveal.ts
1604
+ function stems(text2) {
1605
+ return (text2.toLowerCase().match(/[\p{L}\p{N}][\p{L}\p{N}'-]*/gu) ?? []).filter((word) => word.length >= 4).map((word) => word.slice(0, 5));
1606
+ }
1607
+ function matchPointsToSentences(points, sourceText, start, end) {
1608
+ const passage = sourceText.slice(start, end);
1609
+ const sentences = [...passage.matchAll(/[^.!?\n]+[.!?]*/gu)].map((match) => ({
1610
+ offset: start + match.index,
1611
+ words: new Set(stems(match[0]))
1612
+ }));
1613
+ let last = -1;
1614
+ const offsets = [];
1615
+ for (const point of points) {
1616
+ const words = stems(point);
1617
+ let best;
1618
+ for (const sentence of sentences) {
1619
+ const shared = words.filter((word) => sentence.words.has(word)).length;
1620
+ if (shared > (best?.shared ?? 1)) best = { offset: sentence.offset, shared };
1621
+ }
1622
+ if (!best || best.offset < last) return void 0;
1623
+ offsets.push(last = best.offset);
1624
+ }
1625
+ return offsets;
1626
+ }
1627
+ function conceptBuildSpec() {
1628
+ return { mode: "items", effect: "fadeInUp", previous: "keep" };
1629
+ }
1630
+ function conceptRevealTiming(template, items, itemTimes, start, end) {
1631
+ const build = conceptBuildSpec();
1632
+ const steps = conceptBuildStepTexts(template, items);
1633
+ if (!itemTimes || itemTimes.length !== steps.length) return { build };
1634
+ const lead = Math.min(0.5, (end - start) / 8);
1635
+ const starts = itemTimes.map(
1636
+ (time) => Math.min(Math.max(0, end - start - 0.1), Math.max(lead, time - start))
1637
+ );
1638
+ starts[0] = Math.min(starts[0], Math.max(lead, Math.min(1, (end - start) / 6)));
1639
+ return {
1640
+ build,
1641
+ buildTiming: {
1642
+ sourceText: buildSourceText(steps.map((text2, index) => ({ id: `build-${index}`, text: text2 }))),
1643
+ starts
1644
+ }
1645
+ };
1646
+ }
1647
+
1724
1648
  // src/transform/presentationMedia.ts
1725
1649
  function presentationBlockImages(doc) {
1726
1650
  const own = /* @__PURE__ */ new Map();
@@ -1810,6 +1734,7 @@ function presentationMediaNode(item, elapsed) {
1810
1734
 
1811
1735
  // src/transform/presentationPlanner.ts
1812
1736
  var SENTENCE_END = /[.!?][”"')\]]?$/u;
1737
+ var ABBREVIATION = /(?:^|[\s(“"‘'])(?:\p{Lu}|(?:\p{Lu}\.)+\p{Lu}|Mr|Mrs|Ms|Mx|Dr|Prof|Sr|Jr|St|Mt|Ft|Gen|Col|Lt|Capt|Sgt|Cmdr|Adm|Gov|Sen|Rep|Rev|Hon|vs|etc|e\.g|i\.e|cf|ca|approx|est|No|Fig|Inc|Ltd|Co|Corp)\.$/u;
1813
1738
  var SENTENCE_START = /^[\p{Lu}\p{N}"“‘'([]/u;
1814
1739
  var SEQUENCE_CUE = /^(?:(?:first|second|third|fourth)(?:ly)?|then|next|after\b|afterwards|at that point|eventually|finally|lastly)\b|^\d+[.)]\s/iu;
1815
1740
  var ORDINAL_OPENER = /^(?:(?:first|second|third|fourth)(?:ly)?|then|next|after that|afterwards|finally|lastly),?\s+/iu;
@@ -1817,19 +1742,19 @@ function stagePoint(sentence) {
1817
1742
  const rest = sentence.replace(ORDINAL_OPENER, "");
1818
1743
  return pointText(rest === sentence ? sentence : `${rest[0].toUpperCase()}${rest.slice(1)}`);
1819
1744
  }
1820
- var CYCLE_CUE = /\b(?:cycles?|cyclical|repeats?|over and over|again and again|loop)\b/iu;
1821
1745
  var DEFINES = /^(?:the |an? )?((?:[\p{Lu}][\p{L}'’-]*|[\p{Ll}][\p{L}'’-]*)(?: [\p{L}'’()-]+){0,3}?) (?:is|are|was|were) (?:an? |the )?(.{12,})$/u;
1822
1746
  var PRONOUN = /^(?:it|its|this|that|these|those|there|they|their|he|his|she|her|we|our|you|your|i|my|which|one|each|some|to|so|but|and|or)$/iu;
1823
1747
  function labelledPoint(sentence) {
1824
1748
  const match = sentence.replace(/\s+/gu, " ").trim().match(DEFINES);
1825
- if (match && !PRONOUN.test(match[1].split(" ")[0])) {
1749
+ const clauses = match && /[,;]\s+(?:and\s+)?[^,;]*?\s(?:is|are|was|were)\s/u.test(match[2]);
1750
+ if (match && !clauses && !PRONOUN.test(match[1].split(" ")[0])) {
1826
1751
  const label = match[1];
1827
1752
  return clip(`${label[0].toUpperCase()}${label.slice(1)}: ${match[2]}`, Infinity, 160);
1828
1753
  }
1829
1754
  return clip(sentence, Infinity, 160);
1830
1755
  }
1831
- function clip(text3, words, characters) {
1832
- const clean = text3.replace(/\s+/gu, " ").trim();
1756
+ function clip(text2, words, characters) {
1757
+ const clean = text2.replace(/\s+/gu, " ").trim();
1833
1758
  const parts = clean.split(" ");
1834
1759
  let result = parts.length > words ? parts.slice(0, words).join(" ") : clean;
1835
1760
  if (result.length > characters - 1) {
@@ -1839,13 +1764,13 @@ function clip(text3, words, characters) {
1839
1764
  }
1840
1765
  return result === clean ? clean : result.replace(/[\s,;:–—-]+$/u, "") + "\u2026";
1841
1766
  }
1842
- var headlineText = (text3) => clip(text3, 12, 110);
1843
- var pointText = (text3) => clip(text3, Infinity, 180);
1844
- var captionText = (text3) => clip(text3, 18, 110);
1767
+ var headlineText = (text2) => clip(text2, 12, 110);
1768
+ var pointText = (text2) => clip(text2, Infinity, 180);
1769
+ var captionText = (text2) => clip(text2, 18, 110);
1845
1770
  var FEATURE_POINTS = 2;
1846
1771
  var FEATURE_POINT_CHARACTERS = 100;
1847
- function featurePointText(text3) {
1848
- const clean = text3.replace(/\s+/gu, " ").trim();
1772
+ function featurePointText(text2) {
1773
+ const clean = text2.replace(/\s+/gu, " ").trim();
1849
1774
  if (clean.length <= FEATURE_POINT_CHARACTERS) return clean;
1850
1775
  const head = clean.slice(0, FEATURE_POINT_CHARACTERS);
1851
1776
  const clause = Math.max(
@@ -1859,19 +1784,30 @@ function sentencesIn(sourceText, start, end) {
1859
1784
  let cursor = 0;
1860
1785
  const add = (to) => {
1861
1786
  const raw = passage.slice(cursor, to);
1862
- const text3 = raw.trim();
1863
- if (!text3) return;
1864
- const from = start + cursor + raw.indexOf(text3);
1865
- sentences.push({ text: text3, start: from, end: from + text3.length });
1787
+ const text2 = raw.trim();
1788
+ if (!text2) return;
1789
+ const from = start + cursor + raw.indexOf(text2);
1790
+ sentences.push({ text: text2, start: from, end: from + text2.length });
1866
1791
  };
1867
1792
  for (const match of passage.matchAll(/(?<=[.!?][”"')\]]?)\s+(?=[\p{Lu}\p{N}"“‘'([])|\n+/gu)) {
1793
+ if (!match[0].includes("\n") && ABBREVIATION.test(passage.slice(cursor, match.index))) continue;
1868
1794
  add(match.index);
1869
1795
  cursor = match.index + match[0].length;
1870
1796
  }
1871
1797
  add(passage.length);
1872
1798
  return sentences;
1873
1799
  }
1874
- var comparable = (text3) => text3.toLowerCase().replace(/\s+/gu, " ").replace(/[\s….!?,;:]+$/u, "").trim();
1800
+ var STAT_SENTENCE_CHARACTERS = 120;
1801
+ var STAT_SPACING = 3;
1802
+ var YEAR = /^(?:1\d{3}|20\d{2})$/u;
1803
+ function strikingFigure(sentence) {
1804
+ if (sentence.length > STAT_SENTENCE_CHARACTERS) return void 0;
1805
+ const value = matchNumberHighlight(sentence)?.value.trim();
1806
+ if (!value || YEAR.test(value)) return void 0;
1807
+ return /[%‰$€£¥×,]|\d\s?(?:[xKMB]|million|billion|thousand|percent|times)\b/iu.test(value) || /\d{4,}/u.test(value) ? value : void 0;
1808
+ }
1809
+ var EMPHASIS_SENTENCE_CHARACTERS = 60;
1810
+ var comparable = (text2) => text2.toLowerCase().replace(/\s+/gu, " ").replace(/[\s….!?,;:]+$/u, "").trim();
1875
1811
  function repeats(sentence, headline) {
1876
1812
  const body = comparable(sentence);
1877
1813
  const head = comparable(headline);
@@ -1879,7 +1815,7 @@ function repeats(sentence, headline) {
1879
1815
  }
1880
1816
  function chunkWords(words, limit) {
1881
1817
  const chunks = [];
1882
- const ends = (count) => SENTENCE_END.test(words[count - 1][0]) && (count >= words.length || SENTENCE_START.test(words[count][0]));
1818
+ const ends = (count) => SENTENCE_END.test(words[count - 1][0]) && (count >= words.length || SENTENCE_START.test(words[count][0]) && !ABBREVIATION.test(words[count - 1][0]));
1883
1819
  let first = 0;
1884
1820
  while (first < words.length) {
1885
1821
  let last = Math.min(first + limit, words.length);
@@ -1889,6 +1825,8 @@ function chunkWords(words, limit) {
1889
1825
  const reach = Math.min(words.length, first + Math.ceil(limit * 1.5));
1890
1826
  for (let cursor = last + 1; cursor <= reach && !cut; cursor++) if (ends(cursor)) cut = cursor;
1891
1827
  if (cut) last = cut;
1828
+ if (words.length - last < limit / 2 && words.length - first <= Math.ceil(limit * 1.5))
1829
+ last = words.length;
1892
1830
  }
1893
1831
  chunks.push([first, last]);
1894
1832
  first = last;
@@ -1913,11 +1851,11 @@ function featureSpans(sourceText, from, to, items) {
1913
1851
  let cursor = from;
1914
1852
  for (const item of items) {
1915
1853
  if (item.kind === "image") continue;
1916
- const text3 = extractPlainText(item.node).trim();
1917
- const at = text3 ? sourceText.indexOf(text3, cursor) : -1;
1918
- if (at < 0 || at + text3.length > to) continue;
1919
- spans.push([at, at + text3.length]);
1920
- cursor = at + text3.length;
1854
+ const text2 = extractPlainText(item.node).trim();
1855
+ const at = text2 ? sourceText.indexOf(text2, cursor) : -1;
1856
+ if (at < 0 || at + text2.length > to) continue;
1857
+ spans.push([at, at + text2.length]);
1858
+ cursor = at + text2.length;
1921
1859
  }
1922
1860
  return spans;
1923
1861
  }
@@ -1938,7 +1876,14 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
1938
1876
  doc,
1939
1877
  script.blocks.map((block) => block.blockId)
1940
1878
  );
1879
+ const drawnLists = new Set(
1880
+ flattenRenderableBlocks(doc.blocks).filter(
1881
+ (block) => !!conceptTemplate(block.template ?? "") && block.contents?.length === 1 && block.contents[0].type === "list"
1882
+ ).map((block) => block.id)
1883
+ );
1941
1884
  const beats = [];
1885
+ let lastStat = -Infinity;
1886
+ let lastEmphasis = -Infinity;
1942
1887
  const supportingOpeners = /* @__PURE__ */ new Set();
1943
1888
  const quotes = /* @__PURE__ */ new Map();
1944
1889
  const perSection = hints.density === "section";
@@ -1951,7 +1896,7 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
1951
1896
  const inbetweens = !perSection && (hint?.inbetweens ?? hints.inbetweens);
1952
1897
  const heading = block.heading?.trim();
1953
1898
  const headingEnd = block.charStart + (heading?.length ?? 0);
1954
- if (hint?.layout === AUTHORED_PRESENTATION_LAYOUT) {
1899
+ if (hint?.layout === AUTHORED_PRESENTATION_LAYOUT || hint?.layout === void 0 && !wording && drawnLists.has(block.blockId)) {
1955
1900
  if (!words.length) continue;
1956
1901
  const start = block.charStart + words[0].index;
1957
1902
  const last = words[words.length - 1];
@@ -2034,13 +1979,23 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
2034
1979
  const cardPoints = shown.slice(0, 4).map((item) => labelledPoint(item.text));
2035
1980
  const custom = chunk === 0 ? wording : void 0;
2036
1981
  if (custom?.headline) headline = custom.headline;
2037
- const pointsFor = (layout2) => custom?.points ?? (["steps", "process", "cycle"].includes(layout2) ? stepPoints : layout2 === "cards" ? cardPoints : textPoints);
1982
+ const pointsFor = (layout2) => custom?.points ?? (["steps", "process", "cycle"].includes(layout2) ? stepPoints : layout2 === "cards" ? cardPoints : layout2 === "stat" && statPoints ? statPoints : layout2 === "emphasis" && takeaway ? [] : textPoints);
2038
1983
  const quote = custom?.headline ? void 0 : body.find((sentence) => sentence.text.length <= 110)?.text;
2039
- const headlineFor = (layout2) => layout2 === "quote" && quote ? quote : headline;
1984
+ const headlineFor = (layout2) => layout2 === "quote" && quote ? quote : layout2 === "emphasis" && takeaway ? headlineText(takeaway) : headline;
2040
1985
  let layout = index === 0 ? "title" : textPoints.length >= 2 ? "cards" : "statement";
2041
- if (sequence) layout = steps.length >= 3 && CYCLE_CUE.test(prose) ? "cycle" : "steps";
1986
+ if (sequence)
1987
+ layout = steps.length >= 3 && shapeIsStated("cycle", { heading, passage: prose, items: stepPoints }) ? "cycle" : "steps";
2042
1988
  else if (textPoints.length === 2 && /\b(versus|whereas|compared|instead)\b/iu.test(prose))
2043
1989
  layout = "comparison";
1990
+ const figureLine = !sequence && !custom && index > 0 && index - lastStat >= STAT_SPACING ? shown.find((sentence) => strikingFigure(sentence.text)) : void 0;
1991
+ const statPoints = figureLine ? [
1992
+ pointText(figureLine.text),
1993
+ ...shown.filter((sentence) => sentence !== figureLine && !strikingFigure(sentence.text)).slice(0, 1).map((item) => pointText(item.text))
1994
+ ] : void 0;
1995
+ if (statPoints && !matchNumberHighlight(headline) && presentationLayoutFits("stat", headline, statPoints, passage))
1996
+ layout = "stat";
1997
+ const takeaway = layout === "statement" && !!heading && chunk > 0 && index - lastEmphasis >= 2 && shown.length === 1 && shown[0].text.length <= EMPHASIS_SENTENCE_CHARACTERS ? shown[0].text : void 0;
1998
+ if (takeaway) layout = "emphasis";
2044
1999
  if (image) layout = "image";
2045
2000
  if (preferred && (preferred === "image" ? !!image : presentationLayoutFits(
2046
2001
  preferred,
@@ -2059,6 +2014,8 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
2059
2014
  ...layout === "image" && image?.src ? { imageSrc: image.src } : {}
2060
2015
  };
2061
2016
  if (quote) quotes.set(beat.id, quote);
2017
+ if (beat.layout === "stat") lastStat = index;
2018
+ if (beat.layout === "emphasis") lastEmphasis = index;
2062
2019
  let split;
2063
2020
  if (inbetweens && !custom?.points && layout === "steps" && sequence) {
2064
2021
  if (shown.some((sentence) => sentence.end <= steps[0].start)) split = steps[0].start;
@@ -2132,6 +2089,7 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
2132
2089
  beat.headline = cached.headline;
2133
2090
  beat.points = [...cached.points];
2134
2091
  beat.layout = cached.layout;
2092
+ Object.assign(beat, presentationBeatExtras(cached.layout, cached));
2135
2093
  suggested.add(beat.id);
2136
2094
  origin = "ai";
2137
2095
  }
@@ -2189,56 +2147,14 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
2189
2147
  ))
2190
2148
  beat.layout = "statement";
2191
2149
  }
2192
- return { version: 1, sourceText: script.sourceText, origin, beats };
2193
- }
2194
-
2195
- // src/transform/conceptReveal.ts
2196
- var CONCEPT_TEMPLATES = ["cards", "process", "cycle", "layers"];
2197
- function conceptTemplate(template) {
2198
- return CONCEPT_TEMPLATES.find((name) => name === template);
2199
- }
2200
- function stems(text3) {
2201
- return (text3.toLowerCase().match(/[\p{L}\p{N}][\p{L}\p{N}'-]*/gu) ?? []).filter((word) => word.length >= 4).map((word) => word.slice(0, 5));
2202
- }
2203
- function matchPointsToSentences(points, sourceText, start, end) {
2204
- const passage = sourceText.slice(start, end);
2205
- const sentences = [...passage.matchAll(/[^.!?\n]+[.!?]*/gu)].map((match) => ({
2206
- offset: start + match.index,
2207
- words: new Set(stems(match[0]))
2208
- }));
2209
- let last = -1;
2210
- const offsets = [];
2211
- for (const point of points) {
2212
- const words = stems(point);
2213
- let best;
2214
- for (const sentence of sentences) {
2215
- const shared = words.filter((word) => sentence.words.has(word)).length;
2216
- if (shared > (best?.shared ?? 1)) best = { offset: sentence.offset, shared };
2217
- }
2218
- if (!best || best.offset < last) return void 0;
2219
- offsets.push(last = best.offset);
2150
+ for (const beat of beats) {
2151
+ const extras = presentationBeatExtras(beat.layout, beat);
2152
+ delete beat.center;
2153
+ delete beat.xAxis;
2154
+ delete beat.yAxis;
2155
+ Object.assign(beat, extras);
2220
2156
  }
2221
- return offsets;
2222
- }
2223
- function conceptBuildSpec() {
2224
- return { mode: "items", effect: "fadeInUp", previous: "keep" };
2225
- }
2226
- function conceptRevealTiming(template, items, itemTimes, start, end) {
2227
- const build = conceptBuildSpec();
2228
- const steps = conceptBuildStepTexts(template, items);
2229
- if (!itemTimes || itemTimes.length !== steps.length) return { build };
2230
- const lead = Math.min(0.5, (end - start) / 8);
2231
- const starts = itemTimes.map(
2232
- (time) => Math.min(Math.max(0, end - start - 0.1), Math.max(lead, time - start))
2233
- );
2234
- starts[0] = Math.min(starts[0], Math.max(lead, Math.min(1, (end - start) / 6)));
2235
- return {
2236
- build,
2237
- buildTiming: {
2238
- sourceText: buildSourceText(steps.map((text3, index) => ({ id: `build-${index}`, text: text3 }))),
2239
- starts
2240
- }
2241
- };
2157
+ return { version: 1, sourceText: script.sourceText, origin, beats };
2242
2158
  }
2243
2159
 
2244
2160
  // src/transform/presentationCompiler.ts
@@ -2397,11 +2313,15 @@ function compilePresentationPlan(doc, plan) {
2397
2313
  if (beat.layout === AUTHORED_PRESENTATION_LAYOUT) {
2398
2314
  if (!authored) throw new Error("A section shown as authored is no longer in the document.");
2399
2315
  const { children, media: _media, ...own } = authored;
2316
+ const drawn = conceptTemplate(authored.template ?? "");
2317
+ const given = authored.templateData?.items;
2318
+ const items = drawn && !build ? Array.isArray(given) ? given.map((item) => String(item ?? "")) : extractConceptItems(authored.contents) : [];
2400
2319
  blocks.push({
2401
2320
  ...own,
2402
2321
  ...base,
2403
2322
  ...children && isContainerTemplate(authored.template) ? { children } : {},
2404
- transition: authored.transition ?? base.transition
2323
+ transition: authored.transition ?? base.transition,
2324
+ ...drawn && items.length ? conceptReveal(drawn, { ...beat, points: items }, start, end, script.sourceText, timeAt) : {}
2405
2325
  });
2406
2326
  continue;
2407
2327
  }
@@ -2437,9 +2357,9 @@ function conceptReveal(template, beat, start, end, sourceText, timeAt) {
2437
2357
  return conceptRevealTiming(template, beat.points, offsets?.map(timeAt), start, end);
2438
2358
  }
2439
2359
  function tableFeatureBlock(beat, layout, base, features) {
2440
- const paragraph = (text3) => ({
2360
+ const paragraph = (text2) => ({
2441
2361
  type: "paragraph",
2442
- children: [{ type: "text", value: text3 }]
2362
+ children: [{ type: "text", value: text2 }]
2443
2363
  });
2444
2364
  const words = ["list", "steps", "comparison"].includes(layout) && beat.points.length > 1 ? [
2445
2365
  {
@@ -2516,6 +2436,28 @@ function visualBlock(beat, layout, base, index) {
2516
2436
  return { ...base, template: "cycle", title: beat.headline, items: beat.points };
2517
2437
  case "layers":
2518
2438
  return { ...base, template: "layers", title: beat.headline, items: beat.points };
2439
+ case "stats":
2440
+ case "pyramid":
2441
+ case "funnel":
2442
+ case "venn":
2443
+ return { ...base, template: layout, title: beat.headline, items: beat.points };
2444
+ case "hub":
2445
+ return {
2446
+ ...base,
2447
+ template: "hub",
2448
+ title: beat.headline,
2449
+ items: beat.points,
2450
+ ...beat.center ? { center: beat.center } : {}
2451
+ };
2452
+ case "matrix":
2453
+ return {
2454
+ ...base,
2455
+ template: "matrix",
2456
+ title: beat.headline,
2457
+ items: beat.points,
2458
+ ...beat.xAxis ? { xAxis: beat.xAxis } : {},
2459
+ ...beat.yAxis ? { yAxis: beat.yAxis } : {}
2460
+ };
2519
2461
  }
2520
2462
  }
2521
2463
  function applySavedPresentation(doc) {
@@ -2547,14 +2489,6 @@ function applySavedPresentation(doc) {
2547
2489
  }
2548
2490
 
2549
2491
  export {
2550
- PRESENTATION_KEY,
2551
- PRESENTATION_LAYOUTS,
2552
- AUTHORED_PRESENTATION_LAYOUT,
2553
- presentationStat,
2554
- presentationLayoutFits,
2555
- PRESENTATION_LIMITS,
2556
- parsePresentationPlan,
2557
- serializePresentationPlan,
2558
2492
  PRESENTATION_HINTS_KEY,
2559
2493
  DYNAMIC_PRESENTATION_STYLE,
2560
2494
  presentationPassageKey,
@@ -2573,13 +2507,11 @@ export {
2573
2507
  allocateTiming,
2574
2508
  applyTransform,
2575
2509
  transformNarratedSegment,
2576
- collectPresentationMedia,
2577
- createPresentationPlan,
2578
- CONCEPT_TEMPLATES,
2579
- conceptTemplate,
2580
2510
  matchPointsToSentences,
2581
2511
  conceptBuildSpec,
2582
2512
  conceptRevealTiming,
2513
+ collectPresentationMedia,
2514
+ createPresentationPlan,
2583
2515
  compilePresentationPlan,
2584
2516
  applySavedPresentation
2585
2517
  };