@bendyline/squisq 2.13.14 → 2.13.16

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 (43) hide show
  1. package/dist/{Doc-BhvotZx_.d.ts → Doc-DLfTam0R.d.ts} +82 -6
  2. package/dist/{ImageEditDoc-D8hYio6b.d.ts → ImageEditDoc-DdSHb6sj.d.ts} +1 -1
  3. package/dist/{chunk-YQQWE4HG.js → chunk-33CAD575.js} +200 -262
  4. package/dist/{chunk-L7QZORLB.js → chunk-37RLFDIT.js} +11 -4
  5. package/dist/chunk-4R3YZTPX.js +42 -0
  6. package/dist/{chunk-DBVCSVNV.js → chunk-A4ZFYGU6.js} +13 -38
  7. package/dist/{chunk-CTT4XAGO.js → chunk-BG2OYBGC.js} +2534 -737
  8. package/dist/{chunk-2SRF5III.js → chunk-BVL3F64S.js} +1 -1
  9. package/dist/chunk-C7DKMO4H.js +88 -0
  10. package/dist/{chunk-SGNWO7OF.js → chunk-MA7XUCSQ.js} +517 -30
  11. package/dist/{chunk-JSGIWW35.js → chunk-MTIR5PWF.js} +1 -1
  12. package/dist/{chunk-C4SB3NN4.js → chunk-NBDF6SQ5.js} +22 -2
  13. package/dist/chunk-TOPEP5B7.js +515 -0
  14. package/dist/{chunk-OIQ2GO2O.js → chunk-TXN23SG2.js} +1 -1
  15. package/dist/{chunk-7MS26OXD.js → chunk-UDPPWYYM.js} +6 -0
  16. package/dist/{chunk-J2G6EOEJ.js → chunk-XY5Z23C4.js} +75 -16
  17. package/dist/{chunk-5DFFVW75.js → chunk-Z73DG6IL.js} +1 -1
  18. package/dist/doc/index.d.ts +412 -26
  19. package/dist/doc/index.js +66 -11
  20. package/dist/fence/index.d.ts +1 -1
  21. package/dist/generate/index.d.ts +1 -1
  22. package/dist/imageEdit/index.d.ts +3 -3
  23. package/dist/index.d.ts +9 -9
  24. package/dist/index.js +147 -29
  25. package/dist/jsonForm/index.d.ts +1 -1
  26. package/dist/jsonForm/index.js +5 -4
  27. package/dist/markdown/index.js +3 -3
  28. package/dist/{materializePageSection-CWxou4mX.d.ts → materializePageSection-HIoRYiZu.d.ts} +121 -8
  29. package/dist/mediaEdit/index.d.ts +154 -3
  30. package/dist/mediaEdit/index.js +241 -2
  31. package/dist/narration/index.d.ts +1 -1
  32. package/dist/narration/index.js +7 -5
  33. package/dist/recommend/index.d.ts +129 -2
  34. package/dist/recommend/index.js +13 -4
  35. package/dist/schemas/index.d.ts +26 -7
  36. package/dist/schemas/index.js +14 -8
  37. package/dist/{themeLibrary-BIYE6aLw.d.ts → themeLibrary-B2K-aAOy.d.ts} +1 -1
  38. package/dist/timing/index.d.ts +66 -2
  39. package/dist/timing/index.js +12 -0
  40. package/dist/transform/index.d.ts +173 -16
  41. package/dist/transform/index.js +58 -17
  42. package/package.json +1 -1
  43. 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-BG2OYBGC.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-MA7XUCSQ.js";
25
36
  import {
26
37
  extractContent,
27
38
  mapElementToBlock,
@@ -34,10 +45,11 @@ import {
34
45
  } from "./chunk-JNUNNQ74.js";
35
46
  import {
36
47
  parseMarkdown
37
- } from "./chunk-C4SB3NN4.js";
48
+ } from "./chunk-NBDF6SQ5.js";
38
49
  import {
39
- isContainerTemplate
40
- } from "./chunk-7MS26OXD.js";
50
+ isContainerTemplate,
51
+ resolveTemplateName
52
+ } from "./chunk-UDPPWYYM.js";
41
53
  import {
42
54
  extractPlainText,
43
55
  findBlockVideoEmbed,
@@ -59,142 +71,6 @@ import {
59
71
  hashString
60
72
  } from "./chunk-XRW6P7IF.js";
61
73
 
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
74
  // src/transform/presentationHints.ts
199
75
  var PRESENTATION_HINTS_KEY = "squisq-presentation-hints";
200
76
  var DYNAMIC_PRESENTATION_STYLE = "dynamic-slides";
@@ -204,13 +80,13 @@ function presentationPassageKey(passage) {
204
80
  function defaultPresentationHints() {
205
81
  return { version: 1, density: "balanced", inbetweens: true, blocks: [] };
206
82
  }
207
- function record2(value) {
83
+ function record(value) {
208
84
  return !!value && typeof value === "object" && !Array.isArray(value);
209
85
  }
210
86
  function exact(value, required, optional = []) {
211
87
  return required.every((key) => Object.prototype.hasOwnProperty.call(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
212
88
  }
213
- function text2(value, max) {
89
+ function text(value, max) {
214
90
  return typeof value === "string" && !!value.trim() && value.length <= max && [...value].every((char) => char.charCodeAt(0) >= 32 || ["\n", "\r", " "].includes(char));
215
91
  }
216
92
  function parsePresentationHints(input) {
@@ -223,12 +99,12 @@ function parsePresentationHints(input) {
223
99
  return null;
224
100
  }
225
101
  }
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)
102
+ 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
103
  return null;
228
104
  const ids = /* @__PURE__ */ new Set();
229
105
  const blocks = [];
230
106
  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(
107
+ 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
108
  (key) => typeof key === "string" && /^[0-9a-z]{1,7}-[0-9a-z]{1,4}$/u.test(key)
233
109
  )))
234
110
  return null;
@@ -245,7 +121,10 @@ function parsePresentationHints(input) {
245
121
  if (!Array.isArray(entry.ai) || entry.ai.length > 80) return null;
246
122
  hint.ai = [];
247
123
  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(
124
+ 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.
125
+ ["center", "xAxis", "yAxis"].some(
126
+ (key) => key in item && item[key] !== presentationBeatExtras(String(item.layout), item)[key]
127
+ ) || !presentationLayoutFits(
249
128
  item.layout,
250
129
  item.headline,
251
130
  item.points,
@@ -256,13 +135,14 @@ function parsePresentationHints(input) {
256
135
  source: item.source,
257
136
  layout: item.layout,
258
137
  headline: item.headline,
259
- points: [...item.points]
138
+ points: [...item.points],
139
+ ...presentationBeatExtras(String(item.layout), item)
260
140
  });
261
141
  }
262
142
  }
263
143
  if (entry.wording !== void 0) {
264
144
  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))))
145
+ 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
146
  return null;
267
147
  hint.wording = {
268
148
  source: wording.source,
@@ -589,13 +469,13 @@ function maskNonProse(stripped, contents) {
589
469
  let masked = stripped;
590
470
  let cursor = 0;
591
471
  for (const node of contents) {
592
- const text3 = stripMarkdown(extractPlainText(node));
593
- if (!text3) continue;
594
- const at = stripped.indexOf(text3, cursor);
472
+ const text2 = stripMarkdown(extractPlainText(node));
473
+ if (!text2) continue;
474
+ const at = stripped.indexOf(text2, cursor);
595
475
  if (at < 0) continue;
596
- cursor = at + text3.length;
476
+ cursor = at + text2.length;
597
477
  if (PROSE_NODE_TYPES.has(node.type)) continue;
598
- masked = masked.slice(0, at) + text3.replace(/\S/g, " ") + masked.slice(cursor);
478
+ masked = masked.slice(0, at) + text2.replace(/\S/g, " ") + masked.slice(cursor);
599
479
  }
600
480
  return masked;
601
481
  }
@@ -897,8 +777,8 @@ var PROSE_KEYS = /* @__PURE__ */ new Set([
897
777
  "quote",
898
778
  "text"
899
779
  ]);
900
- function briefText(text3) {
901
- const normalized = text3.replace(/\s+/g, " ").trim();
780
+ function briefText(text2) {
781
+ const normalized = text2.replace(/\s+/g, " ").trim();
902
782
  const sentence = normalized.match(/^.*?[.!?](?:["'”’])?(?=\s|$)/)?.[0] ?? normalized;
903
783
  const words = sentence.split(/\s+/).filter(Boolean);
904
784
  let brief = words.slice(0, 24).join(" ");
@@ -959,11 +839,11 @@ function extractFeatureContents(contents, keepData = false) {
959
839
  for (const node of contents ?? []) visit(node);
960
840
  return features;
961
841
  }
962
- function withoutProse(record3) {
963
- return record3 && Object.fromEntries(Object.entries(record3).filter(([key]) => !PROSE_KEYS.has(key)));
842
+ function withoutProse(record2) {
843
+ return record2 && Object.fromEntries(Object.entries(record2).filter(([key]) => !PROSE_KEYS.has(key)));
964
844
  }
965
845
  function typedMediaContents(block, bodyMedia) {
966
- const record3 = {
846
+ const record2 = {
967
847
  ...block,
968
848
  ...block.templateData,
969
849
  ...coerceTemplateParams(block.template ?? "", block.templateOverrides ?? {}).input
@@ -979,23 +859,23 @@ function typedMediaContents(block, bodyMedia) {
979
859
  });
980
860
  }
981
861
  };
982
- addImage(record3.imageSrc, record3.imageAlt);
862
+ addImage(record2.imageSrc, record2.imageAlt);
983
863
  for (const key of ["accentImage", "backgroundImage"]) {
984
- const image = record3[key];
864
+ const image = record2[key];
985
865
  if (image && typeof image === "object") {
986
866
  const data = image;
987
867
  addImage(data.src, data.alt);
988
868
  }
989
869
  }
990
- if (Array.isArray(record3.images)) {
991
- for (const image of record3.images) {
870
+ if (Array.isArray(record2.images)) {
871
+ for (const image of record2.images) {
992
872
  if (image && typeof image === "object") {
993
873
  const data = image;
994
874
  addImage(data.src, data.alt);
995
875
  }
996
876
  }
997
877
  }
998
- if (typeof record3.videoSrc === "string" && record3.videoSrc) {
878
+ if (typeof record2.videoSrc === "string" && record2.videoSrc) {
999
879
  const htmlChildren = [
1000
880
  {
1001
881
  type: "htmlElement",
@@ -1003,8 +883,8 @@ function typedMediaContents(block, bodyMedia) {
1003
883
  selfClosing: false,
1004
884
  children: [],
1005
885
  attributes: {
1006
- src: record3.videoSrc,
1007
- ...typeof record3.posterSrc === "string" ? { poster: record3.posterSrc } : {}
886
+ src: record2.videoSrc,
887
+ ...typeof record2.posterSrc === "string" ? { poster: record2.posterSrc } : {}
1008
888
  }
1009
889
  }
1010
890
  ];
@@ -1636,21 +1516,21 @@ function standaloneImagePool(images, content) {
1636
1516
  ...available.filter((image) => accents.has(image.src))
1637
1517
  ];
1638
1518
  }
1639
- function chunkNarration(text3, count) {
1640
- if (count <= 1) return [text3];
1519
+ function chunkNarration(text2, count) {
1520
+ if (count <= 1) return [text2];
1641
1521
  const sentences = [];
1642
1522
  const boundary = /[.!?]["'’”)\]]*\s+/g;
1643
1523
  let start = 0;
1644
1524
  let match;
1645
- while ((match = boundary.exec(text3)) !== null) {
1525
+ while ((match = boundary.exec(text2)) !== null) {
1646
1526
  const end = match.index + match[0].length;
1647
- const sentence = text3.slice(start, end).trim();
1527
+ const sentence = text2.slice(start, end).trim();
1648
1528
  if (sentence) sentences.push(sentence);
1649
1529
  start = end;
1650
1530
  }
1651
- const tail = text3.slice(start).trim();
1531
+ const tail = text2.slice(start).trim();
1652
1532
  if (tail) sentences.push(tail);
1653
- if (sentences.length < 2) return [text3];
1533
+ if (sentences.length < 2) return [text2];
1654
1534
  const chunkCount = Math.min(count, sentences.length);
1655
1535
  const words = sentences.map((sentence) => sentence.split(/\s+/).length);
1656
1536
  const totalWords = words.reduce((sum, n) => sum + n, 0);
@@ -1721,6 +1601,51 @@ function videoBlock(video, index, segmentId, duration) {
1721
1601
  };
1722
1602
  }
1723
1603
 
1604
+ // src/transform/conceptReveal.ts
1605
+ function stems(text2) {
1606
+ return (text2.toLowerCase().match(/[\p{L}\p{N}][\p{L}\p{N}'-]*/gu) ?? []).filter((word) => word.length >= 4).map((word) => word.slice(0, 5));
1607
+ }
1608
+ function matchPointsToSentences(points, sourceText, start, end) {
1609
+ const passage = sourceText.slice(start, end);
1610
+ const sentences = [...passage.matchAll(/[^.!?\n]+[.!?]*/gu)].map((match) => ({
1611
+ offset: start + match.index,
1612
+ words: new Set(stems(match[0]))
1613
+ }));
1614
+ let last = -1;
1615
+ const offsets = [];
1616
+ for (const point of points) {
1617
+ const words = stems(point);
1618
+ let best;
1619
+ for (const sentence of sentences) {
1620
+ const shared = words.filter((word) => sentence.words.has(word)).length;
1621
+ if (shared > (best?.shared ?? 1)) best = { offset: sentence.offset, shared };
1622
+ }
1623
+ if (!best || best.offset < last) return void 0;
1624
+ offsets.push(last = best.offset);
1625
+ }
1626
+ return offsets;
1627
+ }
1628
+ function conceptBuildSpec() {
1629
+ return { mode: "items", effect: "fadeInUp", previous: "keep" };
1630
+ }
1631
+ function conceptRevealTiming(template, items, itemTimes, start, end) {
1632
+ const build = conceptBuildSpec();
1633
+ const steps = conceptBuildStepTexts(template, items);
1634
+ if (!itemTimes || itemTimes.length !== steps.length) return { build };
1635
+ const lead = Math.min(0.5, (end - start) / 8);
1636
+ const starts = itemTimes.map(
1637
+ (time) => Math.min(Math.max(0, end - start - 0.1), Math.max(lead, time - start))
1638
+ );
1639
+ starts[0] = Math.min(starts[0], Math.max(lead, Math.min(1, (end - start) / 6)));
1640
+ return {
1641
+ build,
1642
+ buildTiming: {
1643
+ sourceText: buildSourceText(steps.map((text2, index) => ({ id: `build-${index}`, text: text2 }))),
1644
+ starts
1645
+ }
1646
+ };
1647
+ }
1648
+
1724
1649
  // src/transform/presentationMedia.ts
1725
1650
  function presentationBlockImages(doc) {
1726
1651
  const own = /* @__PURE__ */ new Map();
@@ -1810,6 +1735,7 @@ function presentationMediaNode(item, elapsed) {
1810
1735
 
1811
1736
  // src/transform/presentationPlanner.ts
1812
1737
  var SENTENCE_END = /[.!?][”"')\]]?$/u;
1738
+ 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
1739
  var SENTENCE_START = /^[\p{Lu}\p{N}"“‘'([]/u;
1814
1740
  var SEQUENCE_CUE = /^(?:(?:first|second|third|fourth)(?:ly)?|then|next|after\b|afterwards|at that point|eventually|finally|lastly)\b|^\d+[.)]\s/iu;
1815
1741
  var ORDINAL_OPENER = /^(?:(?:first|second|third|fourth)(?:ly)?|then|next|after that|afterwards|finally|lastly),?\s+/iu;
@@ -1817,19 +1743,19 @@ function stagePoint(sentence) {
1817
1743
  const rest = sentence.replace(ORDINAL_OPENER, "");
1818
1744
  return pointText(rest === sentence ? sentence : `${rest[0].toUpperCase()}${rest.slice(1)}`);
1819
1745
  }
1820
- var CYCLE_CUE = /\b(?:cycles?|cyclical|repeats?|over and over|again and again|loop)\b/iu;
1821
1746
  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
1747
  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
1748
  function labelledPoint(sentence) {
1824
1749
  const match = sentence.replace(/\s+/gu, " ").trim().match(DEFINES);
1825
- if (match && !PRONOUN.test(match[1].split(" ")[0])) {
1750
+ const clauses = match && /[,;]\s+(?:and\s+)?[^,;]*?\s(?:is|are|was|were)\s/u.test(match[2]);
1751
+ if (match && !clauses && !PRONOUN.test(match[1].split(" ")[0])) {
1826
1752
  const label = match[1];
1827
1753
  return clip(`${label[0].toUpperCase()}${label.slice(1)}: ${match[2]}`, Infinity, 160);
1828
1754
  }
1829
1755
  return clip(sentence, Infinity, 160);
1830
1756
  }
1831
- function clip(text3, words, characters) {
1832
- const clean = text3.replace(/\s+/gu, " ").trim();
1757
+ function clip(text2, words, characters) {
1758
+ const clean = text2.replace(/\s+/gu, " ").trim();
1833
1759
  const parts = clean.split(" ");
1834
1760
  let result = parts.length > words ? parts.slice(0, words).join(" ") : clean;
1835
1761
  if (result.length > characters - 1) {
@@ -1839,13 +1765,14 @@ function clip(text3, words, characters) {
1839
1765
  }
1840
1766
  return result === clean ? clean : result.replace(/[\s,;:–—-]+$/u, "") + "\u2026";
1841
1767
  }
1842
- var headlineText = (text3) => clip(text3, 12, 110);
1843
- var pointText = (text3) => clip(text3, Infinity, 180);
1844
- var captionText = (text3) => clip(text3, 18, 110);
1768
+ var headlineText = (text2) => clip(text2, 12, 110);
1769
+ var pointText = (text2) => clip(text2, Infinity, 180);
1770
+ var captionText = (text2) => clip(text2, 18, 110);
1845
1771
  var FEATURE_POINTS = 2;
1846
1772
  var FEATURE_POINT_CHARACTERS = 100;
1847
- function featurePointText(text3) {
1848
- const clean = text3.replace(/\s+/gu, " ").trim();
1773
+ var HAND_DRAWN_TEMPLATES = /* @__PURE__ */ new Set(["drawing", "layout"]);
1774
+ function featurePointText(text2) {
1775
+ const clean = text2.replace(/\s+/gu, " ").trim();
1849
1776
  if (clean.length <= FEATURE_POINT_CHARACTERS) return clean;
1850
1777
  const head = clean.slice(0, FEATURE_POINT_CHARACTERS);
1851
1778
  const clause = Math.max(
@@ -1859,19 +1786,30 @@ function sentencesIn(sourceText, start, end) {
1859
1786
  let cursor = 0;
1860
1787
  const add = (to) => {
1861
1788
  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 });
1789
+ const text2 = raw.trim();
1790
+ if (!text2) return;
1791
+ const from = start + cursor + raw.indexOf(text2);
1792
+ sentences.push({ text: text2, start: from, end: from + text2.length });
1866
1793
  };
1867
1794
  for (const match of passage.matchAll(/(?<=[.!?][”"')\]]?)\s+(?=[\p{Lu}\p{N}"“‘'([])|\n+/gu)) {
1795
+ if (!match[0].includes("\n") && ABBREVIATION.test(passage.slice(cursor, match.index))) continue;
1868
1796
  add(match.index);
1869
1797
  cursor = match.index + match[0].length;
1870
1798
  }
1871
1799
  add(passage.length);
1872
1800
  return sentences;
1873
1801
  }
1874
- var comparable = (text3) => text3.toLowerCase().replace(/\s+/gu, " ").replace(/[\s….!?,;:]+$/u, "").trim();
1802
+ var STAT_SENTENCE_CHARACTERS = 120;
1803
+ var STAT_SPACING = 3;
1804
+ var YEAR = /^(?:1\d{3}|20\d{2})$/u;
1805
+ function strikingFigure(sentence) {
1806
+ if (sentence.length > STAT_SENTENCE_CHARACTERS) return void 0;
1807
+ const value = matchNumberHighlight(sentence)?.value.trim();
1808
+ if (!value || YEAR.test(value)) return void 0;
1809
+ return /[%‰$€£¥×,]|\d\s?(?:[xKMB]|million|billion|thousand|percent|times)\b/iu.test(value) || /\d{4,}/u.test(value) ? value : void 0;
1810
+ }
1811
+ var EMPHASIS_SENTENCE_CHARACTERS = 60;
1812
+ var comparable = (text2) => text2.toLowerCase().replace(/\s+/gu, " ").replace(/[\s….!?,;:]+$/u, "").trim();
1875
1813
  function repeats(sentence, headline) {
1876
1814
  const body = comparable(sentence);
1877
1815
  const head = comparable(headline);
@@ -1879,7 +1817,7 @@ function repeats(sentence, headline) {
1879
1817
  }
1880
1818
  function chunkWords(words, limit) {
1881
1819
  const chunks = [];
1882
- const ends = (count) => SENTENCE_END.test(words[count - 1][0]) && (count >= words.length || SENTENCE_START.test(words[count][0]));
1820
+ 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
1821
  let first = 0;
1884
1822
  while (first < words.length) {
1885
1823
  let last = Math.min(first + limit, words.length);
@@ -1889,6 +1827,8 @@ function chunkWords(words, limit) {
1889
1827
  const reach = Math.min(words.length, first + Math.ceil(limit * 1.5));
1890
1828
  for (let cursor = last + 1; cursor <= reach && !cut; cursor++) if (ends(cursor)) cut = cursor;
1891
1829
  if (cut) last = cut;
1830
+ if (words.length - last < limit / 2 && words.length - first <= Math.ceil(limit * 1.5))
1831
+ last = words.length;
1892
1832
  }
1893
1833
  chunks.push([first, last]);
1894
1834
  first = last;
@@ -1913,11 +1853,11 @@ function featureSpans(sourceText, from, to, items) {
1913
1853
  let cursor = from;
1914
1854
  for (const item of items) {
1915
1855
  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;
1856
+ const text2 = extractPlainText(item.node).trim();
1857
+ const at = text2 ? sourceText.indexOf(text2, cursor) : -1;
1858
+ if (at < 0 || at + text2.length > to) continue;
1859
+ spans.push([at, at + text2.length]);
1860
+ cursor = at + text2.length;
1921
1861
  }
1922
1862
  return spans;
1923
1863
  }
@@ -1938,7 +1878,14 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
1938
1878
  doc,
1939
1879
  script.blocks.map((block) => block.blockId)
1940
1880
  );
1881
+ const drawnLists = new Set(
1882
+ flattenRenderableBlocks(doc.blocks).filter(
1883
+ (block) => !!conceptTemplate(block.template ?? "") && block.contents?.length === 1 && block.contents[0].type === "list" || !block.autoTemplate && HAND_DRAWN_TEMPLATES.has(resolveTemplateName(block.template ?? "")) && !!block.children?.length
1884
+ ).map((block) => block.id)
1885
+ );
1941
1886
  const beats = [];
1887
+ let lastStat = -Infinity;
1888
+ let lastEmphasis = -Infinity;
1942
1889
  const supportingOpeners = /* @__PURE__ */ new Set();
1943
1890
  const quotes = /* @__PURE__ */ new Map();
1944
1891
  const perSection = hints.density === "section";
@@ -1951,7 +1898,7 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
1951
1898
  const inbetweens = !perSection && (hint?.inbetweens ?? hints.inbetweens);
1952
1899
  const heading = block.heading?.trim();
1953
1900
  const headingEnd = block.charStart + (heading?.length ?? 0);
1954
- if (hint?.layout === AUTHORED_PRESENTATION_LAYOUT) {
1901
+ if (hint?.layout === AUTHORED_PRESENTATION_LAYOUT || hint?.layout === void 0 && !wording && drawnLists.has(block.blockId)) {
1955
1902
  if (!words.length) continue;
1956
1903
  const start = block.charStart + words[0].index;
1957
1904
  const last = words[words.length - 1];
@@ -2034,13 +1981,23 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
2034
1981
  const cardPoints = shown.slice(0, 4).map((item) => labelledPoint(item.text));
2035
1982
  const custom = chunk === 0 ? wording : void 0;
2036
1983
  if (custom?.headline) headline = custom.headline;
2037
- const pointsFor = (layout2) => custom?.points ?? (["steps", "process", "cycle"].includes(layout2) ? stepPoints : layout2 === "cards" ? cardPoints : textPoints);
1984
+ const pointsFor = (layout2) => custom?.points ?? (["steps", "process", "cycle"].includes(layout2) ? stepPoints : layout2 === "cards" ? cardPoints : layout2 === "stat" && statPoints ? statPoints : layout2 === "emphasis" && takeaway ? [] : textPoints);
2038
1985
  const quote = custom?.headline ? void 0 : body.find((sentence) => sentence.text.length <= 110)?.text;
2039
- const headlineFor = (layout2) => layout2 === "quote" && quote ? quote : headline;
1986
+ const headlineFor = (layout2) => layout2 === "quote" && quote ? quote : layout2 === "emphasis" && takeaway ? headlineText(takeaway) : headline;
2040
1987
  let layout = index === 0 ? "title" : textPoints.length >= 2 ? "cards" : "statement";
2041
- if (sequence) layout = steps.length >= 3 && CYCLE_CUE.test(prose) ? "cycle" : "steps";
1988
+ if (sequence)
1989
+ layout = steps.length >= 3 && shapeIsStated("cycle", { heading, passage: prose, items: stepPoints }) ? "cycle" : "steps";
2042
1990
  else if (textPoints.length === 2 && /\b(versus|whereas|compared|instead)\b/iu.test(prose))
2043
1991
  layout = "comparison";
1992
+ const figureLine = !sequence && !custom && index > 0 && index - lastStat >= STAT_SPACING ? shown.find((sentence) => strikingFigure(sentence.text)) : void 0;
1993
+ const statPoints = figureLine ? [
1994
+ pointText(figureLine.text),
1995
+ ...shown.filter((sentence) => sentence !== figureLine && !strikingFigure(sentence.text)).slice(0, 1).map((item) => pointText(item.text))
1996
+ ] : void 0;
1997
+ if (statPoints && !matchNumberHighlight(headline) && presentationLayoutFits("stat", headline, statPoints, passage))
1998
+ layout = "stat";
1999
+ 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;
2000
+ if (takeaway) layout = "emphasis";
2044
2001
  if (image) layout = "image";
2045
2002
  if (preferred && (preferred === "image" ? !!image : presentationLayoutFits(
2046
2003
  preferred,
@@ -2059,6 +2016,8 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
2059
2016
  ...layout === "image" && image?.src ? { imageSrc: image.src } : {}
2060
2017
  };
2061
2018
  if (quote) quotes.set(beat.id, quote);
2019
+ if (beat.layout === "stat") lastStat = index;
2020
+ if (beat.layout === "emphasis") lastEmphasis = index;
2062
2021
  let split;
2063
2022
  if (inbetweens && !custom?.points && layout === "steps" && sequence) {
2064
2023
  if (shown.some((sentence) => sentence.end <= steps[0].start)) split = steps[0].start;
@@ -2132,6 +2091,7 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
2132
2091
  beat.headline = cached.headline;
2133
2092
  beat.points = [...cached.points];
2134
2093
  beat.layout = cached.layout;
2094
+ Object.assign(beat, presentationBeatExtras(cached.layout, cached));
2135
2095
  suggested.add(beat.id);
2136
2096
  origin = "ai";
2137
2097
  }
@@ -2173,9 +2133,13 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
2173
2133
  if (refs.length) beat.media = refs;
2174
2134
  });
2175
2135
  }
2136
+ const mediaKinds = new Map(
2137
+ [...sectionMedia.values()].flatMap((items) => items.map((item) => [item.ref, item.kind]))
2138
+ );
2176
2139
  for (const beat of beats) {
2177
2140
  if (!beat.media) continue;
2178
- if (!worded.has(beat.id)) {
2141
+ const drawn = !!conceptTemplate(beat.layout) && !beat.media.some((ref) => mediaKinds.get(ref) === "table");
2142
+ if (!worded.has(beat.id) && !drawn) {
2179
2143
  const points = beat.points.slice(0, FEATURE_POINTS);
2180
2144
  beat.points = suggested.has(beat.id) ? points : points.map(featurePointText);
2181
2145
  }
@@ -2189,56 +2153,14 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
2189
2153
  ))
2190
2154
  beat.layout = "statement";
2191
2155
  }
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);
2156
+ for (const beat of beats) {
2157
+ const extras = presentationBeatExtras(beat.layout, beat);
2158
+ delete beat.center;
2159
+ delete beat.xAxis;
2160
+ delete beat.yAxis;
2161
+ Object.assign(beat, extras);
2220
2162
  }
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
- };
2163
+ return { version: 1, sourceText: script.sourceText, origin, beats };
2242
2164
  }
2243
2165
 
2244
2166
  // src/transform/presentationCompiler.ts
@@ -2397,11 +2319,15 @@ function compilePresentationPlan(doc, plan) {
2397
2319
  if (beat.layout === AUTHORED_PRESENTATION_LAYOUT) {
2398
2320
  if (!authored) throw new Error("A section shown as authored is no longer in the document.");
2399
2321
  const { children, media: _media, ...own } = authored;
2322
+ const drawn = conceptTemplate(authored.template ?? "");
2323
+ const given = authored.templateData?.items;
2324
+ const items = drawn && !build ? Array.isArray(given) ? given.map((item) => String(item ?? "")) : extractConceptItems(authored.contents) : [];
2400
2325
  blocks.push({
2401
2326
  ...own,
2402
2327
  ...base,
2403
2328
  ...children && isContainerTemplate(authored.template) ? { children } : {},
2404
- transition: authored.transition ?? base.transition
2329
+ transition: authored.transition ?? base.transition,
2330
+ ...drawn && items.length ? conceptReveal(drawn, { ...beat, points: items }, start, end, script.sourceText, timeAt) : {}
2405
2331
  });
2406
2332
  continue;
2407
2333
  }
@@ -2437,9 +2363,9 @@ function conceptReveal(template, beat, start, end, sourceText, timeAt) {
2437
2363
  return conceptRevealTiming(template, beat.points, offsets?.map(timeAt), start, end);
2438
2364
  }
2439
2365
  function tableFeatureBlock(beat, layout, base, features) {
2440
- const paragraph = (text3) => ({
2366
+ const paragraph = (text2) => ({
2441
2367
  type: "paragraph",
2442
- children: [{ type: "text", value: text3 }]
2368
+ children: [{ type: "text", value: text2 }]
2443
2369
  });
2444
2370
  const words = ["list", "steps", "comparison"].includes(layout) && beat.points.length > 1 ? [
2445
2371
  {
@@ -2516,6 +2442,28 @@ function visualBlock(beat, layout, base, index) {
2516
2442
  return { ...base, template: "cycle", title: beat.headline, items: beat.points };
2517
2443
  case "layers":
2518
2444
  return { ...base, template: "layers", title: beat.headline, items: beat.points };
2445
+ case "stats":
2446
+ case "pyramid":
2447
+ case "funnel":
2448
+ case "venn":
2449
+ return { ...base, template: layout, title: beat.headline, items: beat.points };
2450
+ case "hub":
2451
+ return {
2452
+ ...base,
2453
+ template: "hub",
2454
+ title: beat.headline,
2455
+ items: beat.points,
2456
+ ...beat.center ? { center: beat.center } : {}
2457
+ };
2458
+ case "matrix":
2459
+ return {
2460
+ ...base,
2461
+ template: "matrix",
2462
+ title: beat.headline,
2463
+ items: beat.points,
2464
+ ...beat.xAxis ? { xAxis: beat.xAxis } : {},
2465
+ ...beat.yAxis ? { yAxis: beat.yAxis } : {}
2466
+ };
2519
2467
  }
2520
2468
  }
2521
2469
  function applySavedPresentation(doc) {
@@ -2547,14 +2495,6 @@ function applySavedPresentation(doc) {
2547
2495
  }
2548
2496
 
2549
2497
  export {
2550
- PRESENTATION_KEY,
2551
- PRESENTATION_LAYOUTS,
2552
- AUTHORED_PRESENTATION_LAYOUT,
2553
- presentationStat,
2554
- presentationLayoutFits,
2555
- PRESENTATION_LIMITS,
2556
- parsePresentationPlan,
2557
- serializePresentationPlan,
2558
2498
  PRESENTATION_HINTS_KEY,
2559
2499
  DYNAMIC_PRESENTATION_STYLE,
2560
2500
  presentationPassageKey,
@@ -2573,13 +2513,11 @@ export {
2573
2513
  allocateTiming,
2574
2514
  applyTransform,
2575
2515
  transformNarratedSegment,
2576
- collectPresentationMedia,
2577
- createPresentationPlan,
2578
- CONCEPT_TEMPLATES,
2579
- conceptTemplate,
2580
2516
  matchPointsToSentences,
2581
2517
  conceptBuildSpec,
2582
2518
  conceptRevealTiming,
2519
+ collectPresentationMedia,
2520
+ createPresentationPlan,
2583
2521
  compilePresentationPlan,
2584
2522
  applySavedPresentation
2585
2523
  };