@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,8 +1,11 @@
1
+ import {
2
+ taskMarker
3
+ } from "./chunk-4R3YZTPX.js";
1
4
  import {
2
5
  mediaEditValuesFromAttributes,
3
6
  mediaEditValuesFromParams,
4
7
  parseMediaEdits
5
- } from "./chunk-L7QZORLB.js";
8
+ } from "./chunk-37RLFDIT.js";
6
9
  import {
7
10
  extractPlainText,
8
11
  findBlockVideoEmbed,
@@ -209,6 +212,109 @@ function extractMediaFromContents(contents, makeId, blockId) {
209
212
  return { media, documentMedia, remaining };
210
213
  }
211
214
 
215
+ // src/doc/templates/conceptItems.ts
216
+ var LABEL_WORDS = 7;
217
+ function splitConceptItem(text) {
218
+ const clean = text.replace(/\s+/gu, " ").trim();
219
+ const match = clean.match(/^(.{1,64}?)(:\s|\s[—–-]\s)(.+)$/u);
220
+ const range = !!match && !match[2].startsWith(":") && /\d$/u.test(match[1]);
221
+ if (match && !range && !/[.!?]\s/u.test(match[1]) && match[1].split(" ").length <= LABEL_WORDS)
222
+ return { label: match[1].trim(), detail: match[3].trim() };
223
+ return { label: "", detail: clean };
224
+ }
225
+ var FIGURE = /^[~≈<>±+−-]?\s?[$€£¥]?\s?\d[\d.,]*(?:\s?(?:%|‰|°[CF]?|[x×](?![a-z])|[KMB](?![a-z])|(?:thousand|million|billion|trillion|percent)\b)|\s(?:in|of|out of)\s\d[\d.,]*)?/iu;
226
+ function isDottedIdentifier(figure) {
227
+ const digits = figure.match(/\d[\d.,]*/u)?.[0] ?? "";
228
+ return /\d\.\d+\.\d/u.test(digits) && !/^\d{1,3}(?:\.\d{3})+(?:,\d+)?$/u.test(digits);
229
+ }
230
+ function isFigureText(text) {
231
+ const clean = text.trim();
232
+ if (!clean || clean.length > 18) return false;
233
+ const match = clean.match(FIGURE);
234
+ if (!match || !match[0].trim() || isDottedIdentifier(match[0])) return false;
235
+ const rest = clean.slice(match[0].length).trim();
236
+ return rest === "" || /^[\p{L}°/%²³.]{1,10}(?:\s[\p{L}/²³.]{1,8})?$/u.test(rest);
237
+ }
238
+ function splitStatItem(text) {
239
+ const { label, detail } = splitConceptItem(text);
240
+ if (label && isFigureText(label)) return { figure: label, caption: detail };
241
+ if (label && isFigureText(detail)) return { figure: detail, caption: label };
242
+ const clean = label ? `${label}: ${detail}` : detail;
243
+ const lead = clean.match(FIGURE)?.[0] ?? "";
244
+ const after = clean.slice(lead.length);
245
+ const figure = /^(?:\s|,\s|$)/u.test(after) && !isDottedIdentifier(lead) ? lead.trim() : "";
246
+ const rest = figure ? after.replace(/^[\s,;:—–-]+/u, "") : "";
247
+ if (figure && rest) return { figure: figure.replace(/[.,]+$/u, ""), caption: rest };
248
+ return { figure: "", caption: clean };
249
+ }
250
+ var OVERLAP_LABEL = /^(?:both|shared?|overlap|in common|common|common ground|together|all|all three|each|either)$/iu;
251
+ function vennOverlapIndex(items) {
252
+ return items.findIndex((item) => OVERLAP_LABEL.test(item.label.trim()));
253
+ }
254
+ var MONTH = "(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\\.?\\s+";
255
+ var LEADING_DATE = new RegExp(
256
+ `^((?:${MONTH}(?:\\d{1,2},?\\s+)?)?(?:\\d{4}s?|\\d{1,4}\\s?(?:BCE?|AD|CE))(?![.,]?\\d))[\\s,:;\u2014\u2013-]*(.*)$`,
257
+ "u"
258
+ );
259
+ function timelineItems(items) {
260
+ if (items.length < 2) return void 0;
261
+ let last = -Infinity;
262
+ const dated = [];
263
+ for (const item of items) {
264
+ const match = (item.label || item.detail).match(LEADING_DATE);
265
+ if (!match) return void 0;
266
+ const when = match[1];
267
+ const year = Number(when.match(/\d{1,4}(?=s?\s?(?:BCE?|AD|CE)?$)/u)?.[0] ?? NaN);
268
+ const signed = /BCE?$/u.test(when) ? -year : year;
269
+ if (!Number.isFinite(signed) || signed < last) return void 0;
270
+ last = signed;
271
+ const rest = match[2].trim();
272
+ dated.push(
273
+ item.label ? { when, label: rest, detail: item.detail } : { when, label: "", detail: rest }
274
+ );
275
+ }
276
+ return dated;
277
+ }
278
+ var LEVEL = /\b(low|little|small|less|minimal|few|high|a lot of|lots of|much|more|big|large|great|major|significant)\s+([\p{L}][\p{L}-]*)/giu;
279
+ var LOW_LEVELS = /* @__PURE__ */ new Set([
280
+ "low",
281
+ "little",
282
+ "small",
283
+ "less",
284
+ "minimal",
285
+ "few"
286
+ ]);
287
+ function readMatrixItems(items) {
288
+ if (items.length !== 4) return void 0;
289
+ const read = items.map((item) => {
290
+ const levels = /* @__PURE__ */ new Map();
291
+ for (const match of item.matchAll(LEVEL))
292
+ levels.set(match[2].toLowerCase(), !LOW_LEVELS.has(match[1].toLowerCase()));
293
+ return levels;
294
+ });
295
+ const measures = [...read[0].keys()].filter(
296
+ (measure) => read.every((levels) => levels.has(measure))
297
+ );
298
+ if (measures.length !== 2) return void 0;
299
+ const name = (measure) => `${measure[0].toUpperCase()}${measure.slice(1)}`;
300
+ const corner = (levels, x, y) => (levels.get(y) ? 0 : 2) + (levels.get(x) ? 1 : 0);
301
+ const [first, second] = measures;
302
+ for (const [x, y] of [
303
+ [first, second],
304
+ [second, first]
305
+ ]) {
306
+ const corners = read.map((levels) => corner(levels, x, y));
307
+ if (new Set(corners).size !== 4) return void 0;
308
+ if (corners.every((at, index) => at === index))
309
+ return { xAxis: name(x), yAxis: name(y), items: [...items], reordered: false };
310
+ }
311
+ const placed = [];
312
+ read.forEach((levels, index) => {
313
+ placed[corner(levels, first, second)] = items[index];
314
+ });
315
+ return { xAxis: name(first), yAxis: name(second), items: placed, reordered: true };
316
+ }
317
+
212
318
  // src/doc/asciiDiagram/chars.ts
213
319
  function toCells(s) {
214
320
  return Array.from(s);
@@ -1561,7 +1667,7 @@ function layeredPlacement(children, unpositionedIdx, opts) {
1561
1667
  }
1562
1668
  if (edges.length === 0) return null;
1563
1669
  const across = opts.direction === "right";
1564
- const result = layoutGraph(
1670
+ const result2 = layoutGraph(
1565
1671
  {
1566
1672
  nodes: unpositionedIdx.map((i) => ({
1567
1673
  id: children[i].id,
@@ -1582,7 +1688,7 @@ function layeredPlacement(children, unpositionedIdx, opts) {
1582
1688
  );
1583
1689
  const out = /* @__PURE__ */ new Map();
1584
1690
  unpositionedIdx.forEach((entryIdx, k) => {
1585
- const n = result.nodes[k];
1691
+ const n = result2.nodes[k];
1586
1692
  out.set(entryIdx, { x: Math.round(n.x), y: Math.round(n.y) });
1587
1693
  });
1588
1694
  return out;
@@ -3145,8 +3251,19 @@ function isPictureFence(node) {
3145
3251
  if (node.type !== "code") return false;
3146
3252
  return node.lang?.trim().toLowerCase() === "mermaid" || isSvgIslandLang(node.lang);
3147
3253
  }
3254
+ function isSpatialPictureFence(node) {
3255
+ if (node.type !== "code") return false;
3256
+ if (isExplicitDiagramLang(node.lang)) {
3257
+ return detectAsciiDiagram(node.value, { explicit: true }).isDiagram;
3258
+ }
3259
+ if (isExplicitTreeLang(node.lang)) return detectTree(node.value, { explicit: true }).isTree;
3260
+ if (isExplicitTimelineLang(node.lang)) {
3261
+ return detectAsciiTimeline(node.value, { explicit: true }).isTimeline;
3262
+ }
3263
+ return false;
3264
+ }
3148
3265
  function isMaterializedMediaNode(node) {
3149
- if (node.type === "code") return isPictureFence(node);
3266
+ if (node.type === "code") return isPictureFence(node) || isSpatialPictureFence(node);
3150
3267
  return findBlockVideoEmbed(node) !== null || isStandaloneHtmlVideo(node);
3151
3268
  }
3152
3269
  function isPictureParagraph(node) {
@@ -3175,6 +3292,37 @@ function extractBodyPlainText(contents) {
3175
3292
  function extractListItems(contents) {
3176
3293
  return extractRichListItems(contents).map((item) => item.text);
3177
3294
  }
3295
+ function extractConceptItems(contents) {
3296
+ const flat = (node) => extractPlainText(node).replace(/\s+/g, " ").trim();
3297
+ const items = [];
3298
+ for (const node of contents ?? []) {
3299
+ if (node.type !== "list") continue;
3300
+ for (const item of node.children) {
3301
+ const own = item.children.filter((child) => child.type !== "list").map(flat).filter(Boolean).join(" ");
3302
+ const nested = item.children.filter((child) => child.type === "list").flatMap((list) => list.children.map(flat)).filter(Boolean);
3303
+ if (!own && !nested.length) continue;
3304
+ if (!nested.length) items.push(own);
3305
+ else if (!own) items.push(nested.join("; "));
3306
+ else
3307
+ items.push(
3308
+ `${own.replace(/[:;,.]$/, "")}${/:\s/.test(own) ? ";" : ":"} ${nested.join("; ")}`
3309
+ );
3310
+ }
3311
+ }
3312
+ return items;
3313
+ }
3314
+ var COLUMN_LABEL_CHARACTERS = 40;
3315
+ function twoColumnsFromList(contents) {
3316
+ const items = extractConceptItems(contents);
3317
+ if (items.length !== 2) return null;
3318
+ const sides = items.map((item) => {
3319
+ const { label, detail } = splitConceptItem(item);
3320
+ if (label) return { label, sublabel: detail };
3321
+ return detail.length <= COLUMN_LABEL_CHARACTERS ? { label: detail } : null;
3322
+ });
3323
+ const [left, right] = sides;
3324
+ return left && right ? { left, right } : null;
3325
+ }
3178
3326
  function escapeInlineHtml(value) {
3179
3327
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3180
3328
  }
@@ -3240,10 +3388,12 @@ function renderMarkdownBlocksHtml(nodes, builds) {
3240
3388
  const tag = node.ordered ? "ol" : "ul";
3241
3389
  const start = node.ordered && node.start != null ? ` start="${node.start}"` : "";
3242
3390
  const items = node.children.map((item, index) => {
3243
- const taskMarker = item.checked == null ? "" : `<span>${item.checked ? "[x]" : "[ ]"} </span>`;
3391
+ const task = item.checked == null ? null : taskMarker(item.checked);
3244
3392
  const step = builds?.targets.get(item);
3245
- if (step === void 0) return `<li>${taskMarker}${renderBlocks(item.children)}</li>`;
3246
- const marker = item.checked == null ? node.ordered ? `${(node.start ?? 1) + index}.` : "\u2022" : item.checked ? "[x]" : "[ ]";
3393
+ if (step === void 0) {
3394
+ return task === null ? `<li>${renderBlocks(item.children)}</li>` : `<li data-squisq-task><span aria-hidden="true">${task} </span>${renderBlocks(item.children)}</li>`;
3395
+ }
3396
+ const marker = task ?? (node.ordered ? `${(node.start ?? 1) + index}.` : "\u2022");
3247
3397
  let first = true;
3248
3398
  let own = [];
3249
3399
  const fragments = [];
@@ -3428,13 +3578,17 @@ function extractEmbeddedVideos(contents, limit = Infinity) {
3428
3578
  const startAt = attrs?.["data-squisq-video-start-at"] ? parseTimeSeconds(attrs["data-squisq-video-start-at"]) : null;
3429
3579
  const clipStart = attrs?.["data-squisq-video-clip-start"] ? parseTimeSeconds(attrs["data-squisq-video-clip-start"]) : null;
3430
3580
  const clipEnd = attrs?.["data-squisq-video-clip-end"] ? parseTimeSeconds(attrs["data-squisq-video-clip-end"]) : null;
3581
+ const edits = parseMediaEdits(mediaEditValuesFromAttributes("video", attrs ?? {}));
3582
+ const cuts = edits?.cuts;
3431
3583
  add({
3432
3584
  src,
3433
3585
  ...attrs?.poster ? { posterSrc: attrs.poster } : {},
3434
3586
  alt: attrs?.["aria-label"] || attrs?.title || attrs?.alt || "",
3435
3587
  ...startAt != null ? { startAt } : {},
3436
3588
  ...clipStart != null ? { clipStart } : {},
3437
- ...clipEnd != null ? { clipEnd } : {}
3589
+ ...clipEnd != null ? { clipEnd } : {},
3590
+ ...cuts?.length ? { cuts } : {},
3591
+ ...edits ? { edits } : {}
3438
3592
  });
3439
3593
  }
3440
3594
  } else if ((n.type === "link" || n.type === "image") && typeof n.url === "string" && VIDEO_FILE_RE.test(n.url)) {
@@ -3466,12 +3620,26 @@ function extractTableFromContents(contents) {
3466
3620
  }
3467
3621
  return null;
3468
3622
  }
3623
+ function blockquoteLines(contents) {
3624
+ const quote = (contents ?? []).find((node) => node.type === "blockquote");
3625
+ if (!quote) return [];
3626
+ const children = quote.children ?? [];
3627
+ return children.flatMap((child) => extractPlainText(child).split(/\r?\n/)).map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean);
3628
+ }
3469
3629
  function extractBlockquoteText(contents) {
3470
- if (!contents) return "";
3471
- for (const node of contents) {
3472
- if (node.type === "blockquote") return extractPlainText(node).trim();
3630
+ return blockquoteLines(contents).join(" ");
3631
+ }
3632
+ var ATTRIBUTION_LINE = /^(?:[—–―]|-{2,})\s*(\S.*)$/u;
3633
+ var ATTRIBUTION_TAIL = /^(.*[.!?…]["”’')\]]?)\s+(?:[—–―]|-{2,})\s*([^.!?…]{2,90})$/su;
3634
+ function extractBlockquote(contents) {
3635
+ const lines = blockquoteLines(contents);
3636
+ if (lines.length > 1) {
3637
+ const last = lines[lines.length - 1].match(ATTRIBUTION_LINE);
3638
+ if (last) return { text: lines.slice(0, -1).join(" "), attribution: last[1].trim() };
3473
3639
  }
3474
- return "";
3640
+ const text = lines.join(" ");
3641
+ const tail = text.match(ATTRIBUTION_TAIL);
3642
+ return tail ? { text: tail[1].trim(), attribution: tail[2].trim() } : { text };
3475
3643
  }
3476
3644
  function firstParagraph(contents) {
3477
3645
  if (!contents) return null;
@@ -3552,6 +3720,61 @@ function deriveStatHighlightInputs(headingText, contents, bodyText) {
3552
3720
  function normalizeCoverageText(value) {
3553
3721
  return value.replace(/\s+/g, " ").trim();
3554
3722
  }
3723
+ var RICH_INLINE_TYPES = /* @__PURE__ */ new Set([
3724
+ "link",
3725
+ "linkReference",
3726
+ "image",
3727
+ "imageReference",
3728
+ "inlineCode",
3729
+ "inlineMath",
3730
+ "htmlInline",
3731
+ "icon",
3732
+ "footnoteReference",
3733
+ "mention"
3734
+ ]);
3735
+ function conceptListIsPlain(list) {
3736
+ const items = list.children;
3737
+ if (items.length < 2 || items.length > 6) return false;
3738
+ let plain = true;
3739
+ const visit = (node, depth) => {
3740
+ if (!plain) return;
3741
+ if (RICH_INLINE_TYPES.has(node.type) || node.type === "code" || node.type === "table") {
3742
+ plain = false;
3743
+ return;
3744
+ }
3745
+ const task = node.type === "listItem" && typeof node.checked === "boolean";
3746
+ if (task || node.type === "list" && depth >= 2) {
3747
+ plain = false;
3748
+ return;
3749
+ }
3750
+ const children = node.children ?? [];
3751
+ for (const child of children) visit(child, node.type === "list" ? depth + 1 : depth);
3752
+ };
3753
+ visit(list, 0);
3754
+ return plain;
3755
+ }
3756
+ var CONCEPT_PLACEHOLDERS = {
3757
+ stats: ["3\xD7: faster to make", "92%: finish a first draft", "14 days: saved each quarter"],
3758
+ pyramid: [
3759
+ "Vision: where it is going",
3760
+ "Strategy: how it gets there",
3761
+ "Daily work: what makes it happen"
3762
+ ],
3763
+ funnel: ["Everyone: who hears of it", "Interested: who looks closer", "Committed: who stays"],
3764
+ hub: [
3765
+ "People: who does the work",
3766
+ "Process: how it gets done",
3767
+ "Tools: what they use",
3768
+ "Goals: what it is for"
3769
+ ],
3770
+ venn: ["This: what only it has", "That: what only it has", "Both: what they share"],
3771
+ matrix: [
3772
+ "Do first: urgent and important",
3773
+ "Plan: important, not urgent",
3774
+ "Delegate: urgent, not important",
3775
+ "Drop: neither"
3776
+ ]
3777
+ };
3555
3778
  function autoTemplatePreservesContent(templateName, contents) {
3556
3779
  const nodes = contents ?? [];
3557
3780
  switch (templateName) {
@@ -3561,6 +3784,17 @@ function autoTemplatePreservesContent(templateName, contents) {
3561
3784
  return nodes.length === 1 && nodes[0]?.type === "table";
3562
3785
  case "list":
3563
3786
  return nodes.length === 1 && nodes[0]?.type === "list";
3787
+ case "cards":
3788
+ case "process":
3789
+ case "cycle":
3790
+ case "layers":
3791
+ case "stats":
3792
+ case "pyramid":
3793
+ case "funnel":
3794
+ case "hub":
3795
+ case "venn":
3796
+ case "matrix":
3797
+ return nodes.length === 1 && nodes[0]?.type === "list" && conceptListIsPlain(nodes[0]);
3564
3798
  case "diagram":
3565
3799
  case "timeline":
3566
3800
  return nodes.length === 1 && nodes[0]?.type === "code";
@@ -3615,8 +3849,14 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
3615
3849
  };
3616
3850
  }
3617
3851
  case "quote": {
3618
- const quote = extractBlockquoteText(contents) || bodyText;
3619
- if (quote) return { quote, ...headingText ? { title: headingText } : {} };
3852
+ const quoted = extractBlockquote(contents);
3853
+ const quote = quoted.text || bodyText;
3854
+ if (quote)
3855
+ return {
3856
+ quote,
3857
+ ...quoted.attribution ? { attribution: quoted.attribution } : {},
3858
+ ...headingText ? { title: headingText } : {}
3859
+ };
3620
3860
  return { quote: headingText };
3621
3861
  }
3622
3862
  case "fullBleedQuote": {
@@ -3624,18 +3864,22 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
3624
3864
  return { text };
3625
3865
  }
3626
3866
  case "pullQuote": {
3627
- const text = extractBlockquoteText(contents) || bodyText || headingText;
3867
+ const quoted = extractBlockquote(contents);
3868
+ const text = quoted.text || bodyText || headingText;
3628
3869
  const img = extractFirstImage(contents);
3629
3870
  return {
3630
3871
  text,
3872
+ ...quoted.attribution ? { attribution: quoted.attribution } : {},
3631
3873
  ...img ? { backgroundImage: { src: img.src, alt: img.alt || headingText } } : {}
3632
3874
  };
3633
3875
  }
3634
3876
  case "videoPullQuote": {
3635
- const text = extractBlockquoteText(contents) || bodyText || headingText;
3877
+ const quoted = extractBlockquote(contents);
3878
+ const text = quoted.text || bodyText || headingText;
3636
3879
  const video = extractFirstEmbeddedVideo(contents);
3637
3880
  return {
3638
3881
  text,
3882
+ ...quoted.attribution ? { attribution: quoted.attribution } : {},
3639
3883
  ...video ? {
3640
3884
  backgroundVideo: {
3641
3885
  src: video.src,
@@ -3649,10 +3893,13 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
3649
3893
  }
3650
3894
  case "factCard":
3651
3895
  return { fact: headingText, explanation: bodyText };
3652
- case "twoColumn":
3653
- return headingText ? { header: headingText } : placeholders ? {} : null;
3896
+ case "twoColumn": {
3897
+ const columns = twoColumnsFromList(contents);
3898
+ if (!headingText && !columns) return placeholders ? {} : null;
3899
+ return { ...headingText ? { header: headingText } : {}, ...columns };
3900
+ }
3654
3901
  case "comparisonBar":
3655
- return placeholders ? { leftLabel: "A", leftValue: 60, rightLabel: "B", rightValue: 40 } : null;
3902
+ return placeholders && !contents?.length ? { leftLabel: "A", leftValue: 60, rightLabel: "B", rightValue: 40 } : null;
3656
3903
  case "list": {
3657
3904
  const items = extractListItems(contents);
3658
3905
  const firstList = (contents ?? []).find((n) => n.type === "list");
@@ -3664,17 +3911,30 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
3664
3911
  ...headingText ? { title: headingText } : {}
3665
3912
  };
3666
3913
  }
3667
- return placeholders ? { items: ["Item 1", "Item 2", "Item 3"] } : null;
3914
+ return placeholders && !contents?.length ? { items: ["Item 1", "Item 2", "Item 3"] } : null;
3668
3915
  }
3669
3916
  case "cards":
3670
3917
  case "process":
3671
3918
  case "cycle":
3672
- case "layers": {
3673
- const listed = extractListItems(contents);
3919
+ case "layers":
3920
+ case "stats":
3921
+ case "pyramid":
3922
+ case "funnel":
3923
+ case "hub":
3924
+ case "venn":
3925
+ case "matrix": {
3926
+ const listed = extractConceptItems(contents);
3674
3927
  const items = listed.length ? listed : (contents ?? []).filter((node) => node.type === "paragraph" && !isPictureParagraph(node)).map((node) => extractPlainText(node).trim()).filter(Boolean);
3675
- if (items.length > 0) return { items, ...headingText ? { title: headingText } : {} };
3676
- return placeholders ? {
3677
- items: templateName === "cards" ? [
3928
+ if (items.length > 0) {
3929
+ const matrix = templateName === "matrix" ? readMatrixItems(items) : void 0;
3930
+ return {
3931
+ items,
3932
+ ...matrix && !matrix.reordered ? { xAxis: matrix.xAxis, yAxis: matrix.yAxis } : {},
3933
+ ...headingText ? { title: headingText } : {}
3934
+ };
3935
+ }
3936
+ return placeholders && !contents?.length ? {
3937
+ items: CONCEPT_PLACEHOLDERS[templateName] ?? (templateName === "cards" ? [
3678
3938
  "First idea: what makes it matter",
3679
3939
  "Second idea: how it differs",
3680
3940
  "Third idea: why it helps"
@@ -3682,7 +3942,11 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
3682
3942
  "Core: the innermost part",
3683
3943
  "Middle: what surrounds it",
3684
3944
  "Outer: the outermost layer"
3685
- ] : ["Begin: the first stage", "Grow: what happens next", "Finish: where it leads"]
3945
+ ] : [
3946
+ "Begin: the first stage",
3947
+ "Grow: what happens next",
3948
+ "Finish: where it leads"
3949
+ ])
3686
3950
  } : null;
3687
3951
  }
3688
3952
  case "definitionCard":
@@ -3811,6 +4075,203 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
3811
4075
  }
3812
4076
  }
3813
4077
 
4078
+ // src/recommend/listShape.ts
4079
+ var MAX_IDEAS = 6;
4080
+ var MAX_IDEA_CHARACTERS = 170;
4081
+ var CYCLE_CUE = /\b(?:cycles?(?!\s+(?:tracks?|lanes?|paths?|routes?|hire|shops?|parking|helmets?))|cyclical|loops?|recurring|rinse and repeat|virtuous circle|vicious circle)\b/iu;
4082
+ var CYCLE_INTRO = /\b(?:repeats? itself|(?:cycle|process|sequence|pattern|it|this|which) (?:then |all )?repeats?|over and over|again and again|feedback loop|comes? full circle|round and round)\b/iu;
4083
+ var CYCLE_CLOSE = /\b(?:repeats?|again|back to|returns? to|starts? over|begins? again|the cycle|full circle)\b/iu;
4084
+ var NESTED_CUE = /\b(?:layers?|anatomy|cross[- ]section|strata|shells?|nested|concentric|zones?|inside (?:the|an?)\b)/iu;
4085
+ var INNERMOST = /\b(?:inner(?:most)?|core|cent(?:er|re)|nucleus|heart|kernel)\b/iu;
4086
+ var OUTERMOST = /\b(?:outer(?:most)?|crust|surface|skin|shell|exosphere|atmosphere|bark|rind|periphery|edge)\b/iu;
4087
+ var HIERARCHY_CUE = /\b(?:hierarch(?:y|ies)|pyramids?|tiers?|levels? of|ranks?|chain of command|food chain|pecking order|priorit(?:y|ies)|maslow)\b/iu;
4088
+ var TOP_FIRST_CUE = /\b(?:chain of command|pecking order|priorit(?:y|ies)|ranks?|food chain)\b/iu;
4089
+ var APEX = /\b(?:top|highest|peak|apex|summit|chief|executive|president|king|queen|ceo)\b/iu;
4090
+ var BASE = /\b(?:base|bottom|foundation(?:al)?|basic|lowest|ground|bedrock|physiological)\b/iu;
4091
+ var FUNNEL_CUE = /\b(?:funnels?|conversion|drop[- ]?off|narrow(?:s|ing|ed)? down|winnow(?:s|ing|ed)?|whittl(?:e|es|ing|ed) down|filter(?:s|ing|ed)?(?: down)?|sieve)\b/iu;
4092
+ var SEQUENCE_CUE = /\b(?:steps?|how to|process(?:es)?|stages?|phases?|procedure|workflow|instructions?|journey|roadmap|pathway|sequence|walkthrough)\b/iu;
4093
+ var ORDINAL_OPENER = /^(?:(?:first|second|third|fourth|fifth)(?:ly)?|then|next|after(?:wards| that)?|finally|lastly|step \d+)\b/iu;
4094
+ var CONTRAST_CUE = /\b(?:vs\.?|versus|compared (?:to|with)|comparison|differences? between|contrast)\b|\bor\b[^?]*\?/iu;
4095
+ var OPPOSITES = [
4096
+ [/^before$/iu, /^after$/iu],
4097
+ [/^then$/iu, /^now$/iu],
4098
+ [/^pros?$/iu, /^cons?$/iu],
4099
+ [/^advantages?$/iu, /^disadvantages?$/iu],
4100
+ [/^strengths?$/iu, /^weakness(?:es)?$/iu],
4101
+ [/^benefits?$/iu, /^(?:risks?|costs?|drawbacks?)$/iu],
4102
+ [/^(?:risks?|costs?)$/iu, /^(?:benefits?|rewards?)$/iu],
4103
+ [/^old$/iu, /^new$/iu],
4104
+ [/^myth$/iu, /^(?:fact|reality|truth)$/iu],
4105
+ [/^expectations?$/iu, /^reality$/iu],
4106
+ [/^problem$/iu, /^solution$/iu],
4107
+ [/^causes?$/iu, /^effects?$/iu],
4108
+ [/^do$/iu, /^don[’']?t$/iu],
4109
+ [/^with$/iu, /^without$/iu],
4110
+ [/^for$/iu, /^against$/iu]
4111
+ ];
4112
+ var OVERLAP_CUE = /\b(?:overlap(?:s|ping)?|in common|venn|intersection|common ground)\b/iu;
4113
+ var MATRIX_CUE = /\b(?:matrix|quadrants?|2\s?[x×]\s?2|two[- ]by[- ]two|four[- ]box|eisenhower|swot)\b/iu;
4114
+ var HUB_CUE = /\b(?:factors?|causes?|drivers?|pillars?|elements?|components?|ingredients?|principles?|dimensions?|aspects?|forces?|contributors?|building blocks|parts of|sources of|reasons?)\b/iu;
4115
+ var YEAR_LIKE = /^(?:1\d{3}|20\d{2})s?$/u;
4116
+ function figureValue(figure) {
4117
+ const match = figure.replace(/,/gu, "").match(/\d+(?:\.\d+)?/u);
4118
+ if (!match) return NaN;
4119
+ const scale = /\b(?:trillion)\b/iu.test(figure) ? 1e12 : /\b(?:billion)\b|\dB\b/u.test(figure) ? 1e9 : /\b(?:million)\b|\dM\b/u.test(figure) ? 1e6 : /\b(?:thousand)\b|\d[Kk]\b/u.test(figure) ? 1e3 : 1;
4120
+ return Number(match[0]) * scale;
4121
+ }
4122
+ function result(shape, template, confidence, reason, inputs) {
4123
+ return { shape, template, confidence, reason, ...inputs ? { inputs } : {} };
4124
+ }
4125
+ var PLAIN = (reason) => result("plain", "list", "strong", reason);
4126
+ function opposites(a, b) {
4127
+ const [x, y] = [a.trim(), b.trim()];
4128
+ return OPPOSITES.some(
4129
+ ([left, right]) => left.test(x) && right.test(y) || left.test(y) && right.test(x)
4130
+ );
4131
+ }
4132
+ function classifyListShape(input) {
4133
+ const texts = input.items.map((item) => item.replace(/\s+/gu, " ").trim()).filter(Boolean);
4134
+ if (texts.length < 2) return PLAIN("fewer than two items");
4135
+ if (texts.length > MAX_IDEAS) return PLAIN(`more than ${MAX_IDEAS} items`);
4136
+ if (texts.some((text) => text.length > MAX_IDEA_CHARACTERS)) return PLAIN("items run long");
4137
+ const items = texts.map(splitConceptItem);
4138
+ const n = items.length;
4139
+ const heading = (input.heading ?? "").replace(/\s+/gu, " ").trim();
4140
+ const context = `${heading} ${input.intro ?? ""}`;
4141
+ const labelled = items.filter((item) => item.label).length;
4142
+ const first = `${items[0].label} ${items[0].detail}`;
4143
+ const last = `${items[n - 1].label} ${items[n - 1].detail}`;
4144
+ if (timelineItems(items))
4145
+ return result("timeline", "process", "strong", "items open with dates in order");
4146
+ const stats = texts.map(splitStatItem);
4147
+ const figures = stats.map((stat) => stat.figure);
4148
+ const allFigures = figures.every(Boolean) && !figures.every((figure) => YEAR_LIKE.test(figure));
4149
+ if (allFigures) {
4150
+ const values = figures.map(figureValue);
4151
+ const narrowing = n >= 3 && values.every((value, index) => index === 0 || value < values[index - 1]);
4152
+ if (narrowing && (FUNNEL_CUE.test(context) || SEQUENCE_CUE.test(context) || input.ordered))
4153
+ return result("funnel", "funnel", "strong", "figures that shrink stage by stage");
4154
+ const explicit = texts.every((text) => {
4155
+ const { label, detail } = splitConceptItem(text);
4156
+ return !!label && (isFigureText(label) || isFigureText(detail));
4157
+ });
4158
+ const scaled = figures.every(
4159
+ (figure) => /[%‰$€£¥×,]|\d\s?(?:[xKMB]|million|billion|trillion|thousand|percent)\b|\s(?:in|of)\s\d/iu.test(
4160
+ figure
4161
+ )
4162
+ );
4163
+ if (n <= 6 && (explicit || scaled))
4164
+ return result("figures", "stats", "strong", "every item is a figure and what it measures");
4165
+ }
4166
+ if (vennOverlapIndex(items) >= 0 && n === 3)
4167
+ return result("overlap", "venn", "strong", "two sets and an item naming what they share");
4168
+ if (n === 2 && items.every((item) => item.label) && opposites(items[0].label, items[1].label))
4169
+ return result("contrast", "cards", "strong", "two items labelled as opposites", {
4170
+ versus: true
4171
+ });
4172
+ if (n >= 3 && (CYCLE_CUE.test(heading) || CYCLE_INTRO.test(input.intro ?? "") || CYCLE_CLOSE.test(last) && (input.ordered || SEQUENCE_CUE.test(context))))
4173
+ return result(
4174
+ "cycle",
4175
+ "cycle",
4176
+ "strong",
4177
+ "the heading or the last item says it comes round again"
4178
+ );
4179
+ if (n === 4 && MATRIX_CUE.test(context))
4180
+ return result("matrix", "matrix", "strong", "four items under a heading that names a matrix");
4181
+ if (n >= 3 && FUNNEL_CUE.test(context))
4182
+ return result("funnel", "funnel", "strong", "the heading says the stages narrow");
4183
+ if (n >= 3 && NESTED_CUE.test(context)) {
4184
+ if (INNERMOST.test(first) && !INNERMOST.test(last))
4185
+ return result("nested", "layers", "strong", "nested parts, listed from the inside out");
4186
+ if (INNERMOST.test(last) && (OUTERMOST.test(first) || !INNERMOST.test(first)))
4187
+ return result("nested", "layers", "strong", "nested parts, listed from the outside in", {
4188
+ items: [...texts].reverse()
4189
+ });
4190
+ return result("nested", "layers", "weak", "the heading suggests nested parts");
4191
+ }
4192
+ if (n >= 3 && HIERARCHY_CUE.test(context)) {
4193
+ if (BASE.test(first) && !BASE.test(last))
4194
+ return result("hierarchy", "pyramid", "strong", "a hierarchy, listed from the base up", {
4195
+ items: [...texts].reverse()
4196
+ });
4197
+ if (APEX.test(first) || BASE.test(last) || TOP_FIRST_CUE.test(context))
4198
+ return result("hierarchy", "pyramid", "strong", "a hierarchy, listed from the top down");
4199
+ return result("hierarchy", "pyramid", "weak", "the heading suggests a hierarchy");
4200
+ }
4201
+ if (n === 2 && CONTRAST_CUE.test(heading))
4202
+ return result("contrast", "cards", "strong", "two items under a heading that compares them", {
4203
+ versus: true
4204
+ });
4205
+ if (n === 2 && OVERLAP_CUE.test(context))
4206
+ return result("overlap", "venn", "weak", "the heading suggests two sets that overlap");
4207
+ const openers = texts.filter((text) => ORDINAL_OPENER.test(text)).length;
4208
+ if (input.ordered || openers >= 2 || SEQUENCE_CUE.test(heading))
4209
+ return result(
4210
+ "sequence",
4211
+ "process",
4212
+ "strong",
4213
+ input.ordered ? "a numbered list" : openers >= 2 ? "items open with ordering words" : "the heading names steps"
4214
+ );
4215
+ const brief = texts.every((text) => text.length <= 110);
4216
+ if (n >= 3 && brief && HUB_CUE.test(heading) && labelled * 2 >= n)
4217
+ return result(
4218
+ "hub",
4219
+ "hub",
4220
+ "strong",
4221
+ "labelled items under a heading that names what they share"
4222
+ );
4223
+ if (labelled * 3 >= n * 2)
4224
+ return result("parallel", "cards", "strong", "items written as a label and its detail");
4225
+ if (n >= 3 && brief && HUB_CUE.test(heading))
4226
+ return result("hub", "hub", "weak", "the heading names what the items share");
4227
+ if (n <= 4 && texts.every((text) => text.length <= 90))
4228
+ return result("parallel", "cards", "weak", "a few short items");
4229
+ return PLAIN("no relationship the items make clear");
4230
+ }
4231
+ var SHARED_CUE = /\b(?:both|shared?|shares|sharing|alike|similar(?:ly|ity|ities)?|in common)\b/iu;
4232
+ function shapeIsStated(template, evidence) {
4233
+ const heading = (evidence.heading ?? "").replace(/\s+/gu, " ").trim();
4234
+ const passage = (evidence.passage ?? "").replace(/\s+/gu, " ").trim();
4235
+ const texts = evidence.items.map((item) => item.replace(/\s+/gu, " ").trim()).filter(Boolean);
4236
+ const context = `${heading} ${passage}`;
4237
+ const everything = `${context} ${texts.join(" ")}`;
4238
+ switch (template) {
4239
+ case "cycle":
4240
+ return CYCLE_CUE.test(context) || CYCLE_INTRO.test(passage) || texts.length > 0 && CYCLE_CLOSE.test(texts[texts.length - 1]);
4241
+ case "layers":
4242
+ return NESTED_CUE.test(context) || INNERMOST.test(everything) && OUTERMOST.test(everything);
4243
+ case "pyramid":
4244
+ return HIERARCHY_CUE.test(context) || APEX.test(everything) && BASE.test(everything);
4245
+ case "funnel": {
4246
+ if (FUNNEL_CUE.test(context)) return true;
4247
+ const values = texts.map((text) => figureValue(splitStatItem(text).figure));
4248
+ return values.length >= 3 && values.every(
4249
+ (value, index) => Number.isFinite(value) && (!index || value < values[index - 1])
4250
+ );
4251
+ }
4252
+ case "venn":
4253
+ return OVERLAP_CUE.test(context) || SHARED_CUE.test(passage);
4254
+ case "matrix":
4255
+ return MATRIX_CUE.test(context) || !!evidence.axes?.[0]?.trim() && !!evidence.axes[1]?.trim();
4256
+ default:
4257
+ return true;
4258
+ }
4259
+ }
4260
+ function unstatedShapeFallback(template) {
4261
+ switch (template) {
4262
+ case "cycle":
4263
+ case "funnel":
4264
+ return "process";
4265
+ case "layers":
4266
+ case "pyramid":
4267
+ case "venn":
4268
+ case "matrix":
4269
+ return "cards";
4270
+ default:
4271
+ return void 0;
4272
+ }
4273
+ }
4274
+
3814
4275
  // src/recommend/templates.ts
3815
4276
  var EMPTY_PROFILE = Object.freeze({
3816
4277
  hasImage: false,
@@ -3850,7 +4311,7 @@ function htmlElementsByTag(root, tagNames) {
3850
4311
  }
3851
4312
  return out;
3852
4313
  }
3853
- function profileBlockContents(nodes) {
4314
+ function profileBlockContents(nodes, options = {}) {
3854
4315
  if (!nodes || nodes.length === 0) return { ...EMPTY_PROFILE };
3855
4316
  let imageCount = 0;
3856
4317
  let hasBlockquote = false;
@@ -3904,6 +4365,7 @@ function profileBlockContents(nodes) {
3904
4365
  let hasNumberHighlight = false;
3905
4366
  walkMarkdownTree(root, (n) => {
3906
4367
  if (hasNumberHighlight) return true;
4368
+ if (n.type === "list") return true;
3907
4369
  if (n.type !== "paragraph") return;
3908
4370
  const text = extractPlainText(n).trim();
3909
4371
  if (!text) return true;
@@ -3939,6 +4401,12 @@ function profileBlockContents(nodes) {
3939
4401
  hasTree = true;
3940
4402
  }
3941
4403
  }
4404
+ const onlyList = nodes.length === 1 && nodes[0].type === "list" ? nodes[0] : void 0;
4405
+ const listShape = onlyList ? classifyListShape({
4406
+ heading: options.heading,
4407
+ items: extractConceptItems([onlyList]),
4408
+ ordered: onlyList.ordered === true
4409
+ }) : void 0;
3942
4410
  return {
3943
4411
  hasImage: imageCount > 0,
3944
4412
  imageCount,
@@ -3952,7 +4420,8 @@ function profileBlockContents(nodes) {
3952
4420
  hasCompoundBody: nodes.length > 1 && nodes.some((node) => node.type !== "paragraph"),
3953
4421
  hasAsciiDiagram,
3954
4422
  hasTimeline,
3955
- hasTree
4423
+ hasTree,
4424
+ ...listShape ? { listShape } : {}
3956
4425
  };
3957
4426
  }
3958
4427
  var UNIVERSAL_DEFAULTS = ["title", "sectionHeader", "content", "factCard", "twoColumn"];
@@ -4001,6 +4470,11 @@ function recommendedNamesForProfile(profile) {
4001
4470
  if (profile.hasList) {
4002
4471
  anyContentSignal = true;
4003
4472
  names.add("list");
4473
+ if (profile.listShape && profile.listShape.shape !== "plain") {
4474
+ names.add(profile.listShape.template);
4475
+ names.add("cards");
4476
+ names.add("process");
4477
+ }
4004
4478
  }
4005
4479
  if (profile.hasTable) {
4006
4480
  anyContentSignal = true;
@@ -4028,7 +4502,7 @@ function recommendedNamesForProfile(profile) {
4028
4502
  }
4029
4503
  return Array.from(names);
4030
4504
  }
4031
- function pickAutoTemplate(profile, blockIndex = 0) {
4505
+ function pickAutoTemplate(profile, blockIndex = 0, options = {}) {
4032
4506
  if (profile.hasCompoundBody) return void 0;
4033
4507
  if (profile.hasAsciiDiagram) return "diagram";
4034
4508
  if (profile.hasTimeline) return "timeline";
@@ -4039,7 +4513,8 @@ function pickAutoTemplate(profile, blockIndex = 0) {
4039
4513
  if (profile.hasImage) return blockIndex % 2 === 0 ? "leftFeature" : "rightFeature";
4040
4514
  if (profile.hasBlockquote) return "quote";
4041
4515
  if (profile.hasNumberHighlight) return "statHighlight";
4042
- if (profile.hasList) return "list";
4516
+ if (profile.hasList)
4517
+ return options.concepts !== false && profile.listShape?.confidence === "strong" ? profile.listShape.template : "list";
4043
4518
  return void 0;
4044
4519
  }
4045
4520
  function recommendTemplatesForBlock(profile, allNames) {
@@ -4060,6 +4535,12 @@ export {
4060
4535
  isMediaAnnotationName,
4061
4536
  isStandaloneHtmlVideo,
4062
4537
  extractMediaFromContents,
4538
+ splitConceptItem,
4539
+ isFigureText,
4540
+ splitStatItem,
4541
+ vennOverlapIndex,
4542
+ timelineItems,
4543
+ readMatrixItems,
4063
4544
  toCells,
4064
4545
  cellWidth,
4065
4546
  isHoriz,
@@ -4110,10 +4591,12 @@ export {
4110
4591
  findFirstList,
4111
4592
  extractHostedVideos,
4112
4593
  isPictureFence,
4594
+ isSpatialPictureFence,
4113
4595
  isMaterializedMediaNode,
4114
4596
  isPictureParagraph,
4115
4597
  extractBodyPlainText,
4116
4598
  extractListItems,
4599
+ extractConceptItems,
4117
4600
  renderMarkdownBlocksHtml,
4118
4601
  markdownBlockSeparatorLines,
4119
4602
  extractRichListItems,
@@ -4123,8 +4606,12 @@ export {
4123
4606
  extractFirstEmbeddedVideo,
4124
4607
  extractTableFromContents,
4125
4608
  extractBlockquoteText,
4609
+ extractBlockquote,
4126
4610
  autoTemplatePreservesContent,
4127
4611
  deriveTemplateInputs,
4612
+ classifyListShape,
4613
+ shapeIsStated,
4614
+ unstatedShapeFallback,
4128
4615
  profileBlockContents,
4129
4616
  pickAutoTemplate,
4130
4617
  recommendTemplatesForBlock