@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
@@ -209,6 +209,109 @@ function extractMediaFromContents(contents, makeId, blockId) {
209
209
  return { media, documentMedia, remaining };
210
210
  }
211
211
 
212
+ // src/doc/templates/conceptItems.ts
213
+ var LABEL_WORDS = 7;
214
+ function splitConceptItem(text) {
215
+ const clean = text.replace(/\s+/gu, " ").trim();
216
+ const match = clean.match(/^(.{1,64}?)(:\s|\s[—–-]\s)(.+)$/u);
217
+ const range = !!match && !match[2].startsWith(":") && /\d$/u.test(match[1]);
218
+ if (match && !range && !/[.!?]\s/u.test(match[1]) && match[1].split(" ").length <= LABEL_WORDS)
219
+ return { label: match[1].trim(), detail: match[3].trim() };
220
+ return { label: "", detail: clean };
221
+ }
222
+ 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;
223
+ function isDottedIdentifier(figure) {
224
+ const digits = figure.match(/\d[\d.,]*/u)?.[0] ?? "";
225
+ return /\d\.\d+\.\d/u.test(digits) && !/^\d{1,3}(?:\.\d{3})+(?:,\d+)?$/u.test(digits);
226
+ }
227
+ function isFigureText(text) {
228
+ const clean = text.trim();
229
+ if (!clean || clean.length > 18) return false;
230
+ const match = clean.match(FIGURE);
231
+ if (!match || !match[0].trim() || isDottedIdentifier(match[0])) return false;
232
+ const rest = clean.slice(match[0].length).trim();
233
+ return rest === "" || /^[\p{L}°/%²³.]{1,10}(?:\s[\p{L}/²³.]{1,8})?$/u.test(rest);
234
+ }
235
+ function splitStatItem(text) {
236
+ const { label, detail } = splitConceptItem(text);
237
+ if (label && isFigureText(label)) return { figure: label, caption: detail };
238
+ if (label && isFigureText(detail)) return { figure: detail, caption: label };
239
+ const clean = label ? `${label}: ${detail}` : detail;
240
+ const lead = clean.match(FIGURE)?.[0] ?? "";
241
+ const after = clean.slice(lead.length);
242
+ const figure = /^(?:\s|,\s|$)/u.test(after) && !isDottedIdentifier(lead) ? lead.trim() : "";
243
+ const rest = figure ? after.replace(/^[\s,;:—–-]+/u, "") : "";
244
+ if (figure && rest) return { figure: figure.replace(/[.,]+$/u, ""), caption: rest };
245
+ return { figure: "", caption: clean };
246
+ }
247
+ var OVERLAP_LABEL = /^(?:both|shared?|overlap|in common|common|common ground|together|all|all three|each|either)$/iu;
248
+ function vennOverlapIndex(items) {
249
+ return items.findIndex((item) => OVERLAP_LABEL.test(item.label.trim()));
250
+ }
251
+ var MONTH = "(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\\.?\\s+";
252
+ var LEADING_DATE = new RegExp(
253
+ `^((?:${MONTH}(?:\\d{1,2},?\\s+)?)?(?:\\d{4}s?|\\d{1,4}\\s?(?:BCE?|AD|CE))(?![.,]?\\d))[\\s,:;\u2014\u2013-]*(.*)$`,
254
+ "u"
255
+ );
256
+ function timelineItems(items) {
257
+ if (items.length < 2) return void 0;
258
+ let last = -Infinity;
259
+ const dated = [];
260
+ for (const item of items) {
261
+ const match = (item.label || item.detail).match(LEADING_DATE);
262
+ if (!match) return void 0;
263
+ const when = match[1];
264
+ const year = Number(when.match(/\d{1,4}(?=s?\s?(?:BCE?|AD|CE)?$)/u)?.[0] ?? NaN);
265
+ const signed = /BCE?$/u.test(when) ? -year : year;
266
+ if (!Number.isFinite(signed) || signed < last) return void 0;
267
+ last = signed;
268
+ const rest = match[2].trim();
269
+ dated.push(
270
+ item.label ? { when, label: rest, detail: item.detail } : { when, label: "", detail: rest }
271
+ );
272
+ }
273
+ return dated;
274
+ }
275
+ 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;
276
+ var LOW_LEVELS = /* @__PURE__ */ new Set([
277
+ "low",
278
+ "little",
279
+ "small",
280
+ "less",
281
+ "minimal",
282
+ "few"
283
+ ]);
284
+ function readMatrixItems(items) {
285
+ if (items.length !== 4) return void 0;
286
+ const read = items.map((item) => {
287
+ const levels = /* @__PURE__ */ new Map();
288
+ for (const match of item.matchAll(LEVEL))
289
+ levels.set(match[2].toLowerCase(), !LOW_LEVELS.has(match[1].toLowerCase()));
290
+ return levels;
291
+ });
292
+ const measures = [...read[0].keys()].filter(
293
+ (measure) => read.every((levels) => levels.has(measure))
294
+ );
295
+ if (measures.length !== 2) return void 0;
296
+ const name = (measure) => `${measure[0].toUpperCase()}${measure.slice(1)}`;
297
+ const corner = (levels, x, y) => (levels.get(y) ? 0 : 2) + (levels.get(x) ? 1 : 0);
298
+ const [first, second] = measures;
299
+ for (const [x, y] of [
300
+ [first, second],
301
+ [second, first]
302
+ ]) {
303
+ const corners = read.map((levels) => corner(levels, x, y));
304
+ if (new Set(corners).size !== 4) return void 0;
305
+ if (corners.every((at, index) => at === index))
306
+ return { xAxis: name(x), yAxis: name(y), items: [...items], reordered: false };
307
+ }
308
+ const placed = [];
309
+ read.forEach((levels, index) => {
310
+ placed[corner(levels, first, second)] = items[index];
311
+ });
312
+ return { xAxis: name(first), yAxis: name(second), items: placed, reordered: true };
313
+ }
314
+
212
315
  // src/doc/asciiDiagram/chars.ts
213
316
  function toCells(s) {
214
317
  return Array.from(s);
@@ -1561,7 +1664,7 @@ function layeredPlacement(children, unpositionedIdx, opts) {
1561
1664
  }
1562
1665
  if (edges.length === 0) return null;
1563
1666
  const across = opts.direction === "right";
1564
- const result = layoutGraph(
1667
+ const result2 = layoutGraph(
1565
1668
  {
1566
1669
  nodes: unpositionedIdx.map((i) => ({
1567
1670
  id: children[i].id,
@@ -1582,7 +1685,7 @@ function layeredPlacement(children, unpositionedIdx, opts) {
1582
1685
  );
1583
1686
  const out = /* @__PURE__ */ new Map();
1584
1687
  unpositionedIdx.forEach((entryIdx, k) => {
1585
- const n = result.nodes[k];
1688
+ const n = result2.nodes[k];
1586
1689
  out.set(entryIdx, { x: Math.round(n.x), y: Math.round(n.y) });
1587
1690
  });
1588
1691
  return out;
@@ -3175,6 +3278,37 @@ function extractBodyPlainText(contents) {
3175
3278
  function extractListItems(contents) {
3176
3279
  return extractRichListItems(contents).map((item) => item.text);
3177
3280
  }
3281
+ function extractConceptItems(contents) {
3282
+ const flat = (node) => extractPlainText(node).replace(/\s+/g, " ").trim();
3283
+ const items = [];
3284
+ for (const node of contents ?? []) {
3285
+ if (node.type !== "list") continue;
3286
+ for (const item of node.children) {
3287
+ const own = item.children.filter((child) => child.type !== "list").map(flat).filter(Boolean).join(" ");
3288
+ const nested = item.children.filter((child) => child.type === "list").flatMap((list) => list.children.map(flat)).filter(Boolean);
3289
+ if (!own && !nested.length) continue;
3290
+ if (!nested.length) items.push(own);
3291
+ else if (!own) items.push(nested.join("; "));
3292
+ else
3293
+ items.push(
3294
+ `${own.replace(/[:;,.]$/, "")}${/:\s/.test(own) ? ";" : ":"} ${nested.join("; ")}`
3295
+ );
3296
+ }
3297
+ }
3298
+ return items;
3299
+ }
3300
+ var COLUMN_LABEL_CHARACTERS = 40;
3301
+ function twoColumnsFromList(contents) {
3302
+ const items = extractConceptItems(contents);
3303
+ if (items.length !== 2) return null;
3304
+ const sides = items.map((item) => {
3305
+ const { label, detail } = splitConceptItem(item);
3306
+ if (label) return { label, sublabel: detail };
3307
+ return detail.length <= COLUMN_LABEL_CHARACTERS ? { label: detail } : null;
3308
+ });
3309
+ const [left, right] = sides;
3310
+ return left && right ? { left, right } : null;
3311
+ }
3178
3312
  function escapeInlineHtml(value) {
3179
3313
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3180
3314
  }
@@ -3466,12 +3600,26 @@ function extractTableFromContents(contents) {
3466
3600
  }
3467
3601
  return null;
3468
3602
  }
3603
+ function blockquoteLines(contents) {
3604
+ const quote = (contents ?? []).find((node) => node.type === "blockquote");
3605
+ if (!quote) return [];
3606
+ const children = quote.children ?? [];
3607
+ return children.flatMap((child) => extractPlainText(child).split(/\r?\n/)).map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean);
3608
+ }
3469
3609
  function extractBlockquoteText(contents) {
3470
- if (!contents) return "";
3471
- for (const node of contents) {
3472
- if (node.type === "blockquote") return extractPlainText(node).trim();
3610
+ return blockquoteLines(contents).join(" ");
3611
+ }
3612
+ var ATTRIBUTION_LINE = /^(?:[—–―]|-{2,})\s*(\S.*)$/u;
3613
+ var ATTRIBUTION_TAIL = /^(.*[.!?…]["”’')\]]?)\s+(?:[—–―]|-{2,})\s*([^.!?…]{2,90})$/su;
3614
+ function extractBlockquote(contents) {
3615
+ const lines = blockquoteLines(contents);
3616
+ if (lines.length > 1) {
3617
+ const last = lines[lines.length - 1].match(ATTRIBUTION_LINE);
3618
+ if (last) return { text: lines.slice(0, -1).join(" "), attribution: last[1].trim() };
3473
3619
  }
3474
- return "";
3620
+ const text = lines.join(" ");
3621
+ const tail = text.match(ATTRIBUTION_TAIL);
3622
+ return tail ? { text: tail[1].trim(), attribution: tail[2].trim() } : { text };
3475
3623
  }
3476
3624
  function firstParagraph(contents) {
3477
3625
  if (!contents) return null;
@@ -3552,6 +3700,61 @@ function deriveStatHighlightInputs(headingText, contents, bodyText) {
3552
3700
  function normalizeCoverageText(value) {
3553
3701
  return value.replace(/\s+/g, " ").trim();
3554
3702
  }
3703
+ var RICH_INLINE_TYPES = /* @__PURE__ */ new Set([
3704
+ "link",
3705
+ "linkReference",
3706
+ "image",
3707
+ "imageReference",
3708
+ "inlineCode",
3709
+ "inlineMath",
3710
+ "htmlInline",
3711
+ "icon",
3712
+ "footnoteReference",
3713
+ "mention"
3714
+ ]);
3715
+ function conceptListIsPlain(list) {
3716
+ const items = list.children;
3717
+ if (items.length < 2 || items.length > 6) return false;
3718
+ let plain = true;
3719
+ const visit = (node, depth) => {
3720
+ if (!plain) return;
3721
+ if (RICH_INLINE_TYPES.has(node.type) || node.type === "code" || node.type === "table") {
3722
+ plain = false;
3723
+ return;
3724
+ }
3725
+ const task = node.type === "listItem" && typeof node.checked === "boolean";
3726
+ if (task || node.type === "list" && depth >= 2) {
3727
+ plain = false;
3728
+ return;
3729
+ }
3730
+ const children = node.children ?? [];
3731
+ for (const child of children) visit(child, node.type === "list" ? depth + 1 : depth);
3732
+ };
3733
+ visit(list, 0);
3734
+ return plain;
3735
+ }
3736
+ var CONCEPT_PLACEHOLDERS = {
3737
+ stats: ["3\xD7: faster to make", "92%: finish a first draft", "14 days: saved each quarter"],
3738
+ pyramid: [
3739
+ "Vision: where it is going",
3740
+ "Strategy: how it gets there",
3741
+ "Daily work: what makes it happen"
3742
+ ],
3743
+ funnel: ["Everyone: who hears of it", "Interested: who looks closer", "Committed: who stays"],
3744
+ hub: [
3745
+ "People: who does the work",
3746
+ "Process: how it gets done",
3747
+ "Tools: what they use",
3748
+ "Goals: what it is for"
3749
+ ],
3750
+ venn: ["This: what only it has", "That: what only it has", "Both: what they share"],
3751
+ matrix: [
3752
+ "Do first: urgent and important",
3753
+ "Plan: important, not urgent",
3754
+ "Delegate: urgent, not important",
3755
+ "Drop: neither"
3756
+ ]
3757
+ };
3555
3758
  function autoTemplatePreservesContent(templateName, contents) {
3556
3759
  const nodes = contents ?? [];
3557
3760
  switch (templateName) {
@@ -3561,6 +3764,17 @@ function autoTemplatePreservesContent(templateName, contents) {
3561
3764
  return nodes.length === 1 && nodes[0]?.type === "table";
3562
3765
  case "list":
3563
3766
  return nodes.length === 1 && nodes[0]?.type === "list";
3767
+ case "cards":
3768
+ case "process":
3769
+ case "cycle":
3770
+ case "layers":
3771
+ case "stats":
3772
+ case "pyramid":
3773
+ case "funnel":
3774
+ case "hub":
3775
+ case "venn":
3776
+ case "matrix":
3777
+ return nodes.length === 1 && nodes[0]?.type === "list" && conceptListIsPlain(nodes[0]);
3564
3778
  case "diagram":
3565
3779
  case "timeline":
3566
3780
  return nodes.length === 1 && nodes[0]?.type === "code";
@@ -3615,8 +3829,14 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
3615
3829
  };
3616
3830
  }
3617
3831
  case "quote": {
3618
- const quote = extractBlockquoteText(contents) || bodyText;
3619
- if (quote) return { quote, ...headingText ? { title: headingText } : {} };
3832
+ const quoted = extractBlockquote(contents);
3833
+ const quote = quoted.text || bodyText;
3834
+ if (quote)
3835
+ return {
3836
+ quote,
3837
+ ...quoted.attribution ? { attribution: quoted.attribution } : {},
3838
+ ...headingText ? { title: headingText } : {}
3839
+ };
3620
3840
  return { quote: headingText };
3621
3841
  }
3622
3842
  case "fullBleedQuote": {
@@ -3624,18 +3844,22 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
3624
3844
  return { text };
3625
3845
  }
3626
3846
  case "pullQuote": {
3627
- const text = extractBlockquoteText(contents) || bodyText || headingText;
3847
+ const quoted = extractBlockquote(contents);
3848
+ const text = quoted.text || bodyText || headingText;
3628
3849
  const img = extractFirstImage(contents);
3629
3850
  return {
3630
3851
  text,
3852
+ ...quoted.attribution ? { attribution: quoted.attribution } : {},
3631
3853
  ...img ? { backgroundImage: { src: img.src, alt: img.alt || headingText } } : {}
3632
3854
  };
3633
3855
  }
3634
3856
  case "videoPullQuote": {
3635
- const text = extractBlockquoteText(contents) || bodyText || headingText;
3857
+ const quoted = extractBlockquote(contents);
3858
+ const text = quoted.text || bodyText || headingText;
3636
3859
  const video = extractFirstEmbeddedVideo(contents);
3637
3860
  return {
3638
3861
  text,
3862
+ ...quoted.attribution ? { attribution: quoted.attribution } : {},
3639
3863
  ...video ? {
3640
3864
  backgroundVideo: {
3641
3865
  src: video.src,
@@ -3649,10 +3873,13 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
3649
3873
  }
3650
3874
  case "factCard":
3651
3875
  return { fact: headingText, explanation: bodyText };
3652
- case "twoColumn":
3653
- return headingText ? { header: headingText } : placeholders ? {} : null;
3876
+ case "twoColumn": {
3877
+ const columns = twoColumnsFromList(contents);
3878
+ if (!headingText && !columns) return placeholders ? {} : null;
3879
+ return { ...headingText ? { header: headingText } : {}, ...columns };
3880
+ }
3654
3881
  case "comparisonBar":
3655
- return placeholders ? { leftLabel: "A", leftValue: 60, rightLabel: "B", rightValue: 40 } : null;
3882
+ return placeholders && !contents?.length ? { leftLabel: "A", leftValue: 60, rightLabel: "B", rightValue: 40 } : null;
3656
3883
  case "list": {
3657
3884
  const items = extractListItems(contents);
3658
3885
  const firstList = (contents ?? []).find((n) => n.type === "list");
@@ -3664,17 +3891,30 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
3664
3891
  ...headingText ? { title: headingText } : {}
3665
3892
  };
3666
3893
  }
3667
- return placeholders ? { items: ["Item 1", "Item 2", "Item 3"] } : null;
3894
+ return placeholders && !contents?.length ? { items: ["Item 1", "Item 2", "Item 3"] } : null;
3668
3895
  }
3669
3896
  case "cards":
3670
3897
  case "process":
3671
3898
  case "cycle":
3672
- case "layers": {
3673
- const listed = extractListItems(contents);
3899
+ case "layers":
3900
+ case "stats":
3901
+ case "pyramid":
3902
+ case "funnel":
3903
+ case "hub":
3904
+ case "venn":
3905
+ case "matrix": {
3906
+ const listed = extractConceptItems(contents);
3674
3907
  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" ? [
3908
+ if (items.length > 0) {
3909
+ const matrix = templateName === "matrix" ? readMatrixItems(items) : void 0;
3910
+ return {
3911
+ items,
3912
+ ...matrix && !matrix.reordered ? { xAxis: matrix.xAxis, yAxis: matrix.yAxis } : {},
3913
+ ...headingText ? { title: headingText } : {}
3914
+ };
3915
+ }
3916
+ return placeholders && !contents?.length ? {
3917
+ items: CONCEPT_PLACEHOLDERS[templateName] ?? (templateName === "cards" ? [
3678
3918
  "First idea: what makes it matter",
3679
3919
  "Second idea: how it differs",
3680
3920
  "Third idea: why it helps"
@@ -3682,7 +3922,11 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
3682
3922
  "Core: the innermost part",
3683
3923
  "Middle: what surrounds it",
3684
3924
  "Outer: the outermost layer"
3685
- ] : ["Begin: the first stage", "Grow: what happens next", "Finish: where it leads"]
3925
+ ] : [
3926
+ "Begin: the first stage",
3927
+ "Grow: what happens next",
3928
+ "Finish: where it leads"
3929
+ ])
3686
3930
  } : null;
3687
3931
  }
3688
3932
  case "definitionCard":
@@ -3811,6 +4055,203 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
3811
4055
  }
3812
4056
  }
3813
4057
 
4058
+ // src/recommend/listShape.ts
4059
+ var MAX_IDEAS = 6;
4060
+ var MAX_IDEA_CHARACTERS = 170;
4061
+ 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;
4062
+ 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;
4063
+ var CYCLE_CLOSE = /\b(?:repeats?|again|back to|returns? to|starts? over|begins? again|the cycle|full circle)\b/iu;
4064
+ var NESTED_CUE = /\b(?:layers?|anatomy|cross[- ]section|strata|shells?|nested|concentric|zones?|inside (?:the|an?)\b)/iu;
4065
+ var INNERMOST = /\b(?:inner(?:most)?|core|cent(?:er|re)|nucleus|heart|kernel)\b/iu;
4066
+ var OUTERMOST = /\b(?:outer(?:most)?|crust|surface|skin|shell|exosphere|atmosphere|bark|rind|periphery|edge)\b/iu;
4067
+ 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;
4068
+ var TOP_FIRST_CUE = /\b(?:chain of command|pecking order|priorit(?:y|ies)|ranks?|food chain)\b/iu;
4069
+ var APEX = /\b(?:top|highest|peak|apex|summit|chief|executive|president|king|queen|ceo)\b/iu;
4070
+ var BASE = /\b(?:base|bottom|foundation(?:al)?|basic|lowest|ground|bedrock|physiological)\b/iu;
4071
+ 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;
4072
+ var SEQUENCE_CUE = /\b(?:steps?|how to|process(?:es)?|stages?|phases?|procedure|workflow|instructions?|journey|roadmap|pathway|sequence|walkthrough)\b/iu;
4073
+ var ORDINAL_OPENER = /^(?:(?:first|second|third|fourth|fifth)(?:ly)?|then|next|after(?:wards| that)?|finally|lastly|step \d+)\b/iu;
4074
+ var CONTRAST_CUE = /\b(?:vs\.?|versus|compared (?:to|with)|comparison|differences? between|contrast)\b|\bor\b[^?]*\?/iu;
4075
+ var OPPOSITES = [
4076
+ [/^before$/iu, /^after$/iu],
4077
+ [/^then$/iu, /^now$/iu],
4078
+ [/^pros?$/iu, /^cons?$/iu],
4079
+ [/^advantages?$/iu, /^disadvantages?$/iu],
4080
+ [/^strengths?$/iu, /^weakness(?:es)?$/iu],
4081
+ [/^benefits?$/iu, /^(?:risks?|costs?|drawbacks?)$/iu],
4082
+ [/^(?:risks?|costs?)$/iu, /^(?:benefits?|rewards?)$/iu],
4083
+ [/^old$/iu, /^new$/iu],
4084
+ [/^myth$/iu, /^(?:fact|reality|truth)$/iu],
4085
+ [/^expectations?$/iu, /^reality$/iu],
4086
+ [/^problem$/iu, /^solution$/iu],
4087
+ [/^causes?$/iu, /^effects?$/iu],
4088
+ [/^do$/iu, /^don[’']?t$/iu],
4089
+ [/^with$/iu, /^without$/iu],
4090
+ [/^for$/iu, /^against$/iu]
4091
+ ];
4092
+ var OVERLAP_CUE = /\b(?:overlap(?:s|ping)?|in common|venn|intersection|common ground)\b/iu;
4093
+ var MATRIX_CUE = /\b(?:matrix|quadrants?|2\s?[x×]\s?2|two[- ]by[- ]two|four[- ]box|eisenhower|swot)\b/iu;
4094
+ 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;
4095
+ var YEAR_LIKE = /^(?:1\d{3}|20\d{2})s?$/u;
4096
+ function figureValue(figure) {
4097
+ const match = figure.replace(/,/gu, "").match(/\d+(?:\.\d+)?/u);
4098
+ if (!match) return NaN;
4099
+ 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;
4100
+ return Number(match[0]) * scale;
4101
+ }
4102
+ function result(shape, template, confidence, reason, inputs) {
4103
+ return { shape, template, confidence, reason, ...inputs ? { inputs } : {} };
4104
+ }
4105
+ var PLAIN = (reason) => result("plain", "list", "strong", reason);
4106
+ function opposites(a, b) {
4107
+ const [x, y] = [a.trim(), b.trim()];
4108
+ return OPPOSITES.some(
4109
+ ([left, right]) => left.test(x) && right.test(y) || left.test(y) && right.test(x)
4110
+ );
4111
+ }
4112
+ function classifyListShape(input) {
4113
+ const texts = input.items.map((item) => item.replace(/\s+/gu, " ").trim()).filter(Boolean);
4114
+ if (texts.length < 2) return PLAIN("fewer than two items");
4115
+ if (texts.length > MAX_IDEAS) return PLAIN(`more than ${MAX_IDEAS} items`);
4116
+ if (texts.some((text) => text.length > MAX_IDEA_CHARACTERS)) return PLAIN("items run long");
4117
+ const items = texts.map(splitConceptItem);
4118
+ const n = items.length;
4119
+ const heading = (input.heading ?? "").replace(/\s+/gu, " ").trim();
4120
+ const context = `${heading} ${input.intro ?? ""}`;
4121
+ const labelled = items.filter((item) => item.label).length;
4122
+ const first = `${items[0].label} ${items[0].detail}`;
4123
+ const last = `${items[n - 1].label} ${items[n - 1].detail}`;
4124
+ if (timelineItems(items))
4125
+ return result("timeline", "process", "strong", "items open with dates in order");
4126
+ const stats = texts.map(splitStatItem);
4127
+ const figures = stats.map((stat) => stat.figure);
4128
+ const allFigures = figures.every(Boolean) && !figures.every((figure) => YEAR_LIKE.test(figure));
4129
+ if (allFigures) {
4130
+ const values = figures.map(figureValue);
4131
+ const narrowing = n >= 3 && values.every((value, index) => index === 0 || value < values[index - 1]);
4132
+ if (narrowing && (FUNNEL_CUE.test(context) || SEQUENCE_CUE.test(context) || input.ordered))
4133
+ return result("funnel", "funnel", "strong", "figures that shrink stage by stage");
4134
+ const explicit = texts.every((text) => {
4135
+ const { label, detail } = splitConceptItem(text);
4136
+ return !!label && (isFigureText(label) || isFigureText(detail));
4137
+ });
4138
+ const scaled = figures.every(
4139
+ (figure) => /[%‰$€£¥×,]|\d\s?(?:[xKMB]|million|billion|trillion|thousand|percent)\b|\s(?:in|of)\s\d/iu.test(
4140
+ figure
4141
+ )
4142
+ );
4143
+ if (n <= 6 && (explicit || scaled))
4144
+ return result("figures", "stats", "strong", "every item is a figure and what it measures");
4145
+ }
4146
+ if (vennOverlapIndex(items) >= 0 && n === 3)
4147
+ return result("overlap", "venn", "strong", "two sets and an item naming what they share");
4148
+ if (n === 2 && items.every((item) => item.label) && opposites(items[0].label, items[1].label))
4149
+ return result("contrast", "cards", "strong", "two items labelled as opposites", {
4150
+ versus: true
4151
+ });
4152
+ if (n >= 3 && (CYCLE_CUE.test(heading) || CYCLE_INTRO.test(input.intro ?? "") || CYCLE_CLOSE.test(last) && (input.ordered || SEQUENCE_CUE.test(context))))
4153
+ return result(
4154
+ "cycle",
4155
+ "cycle",
4156
+ "strong",
4157
+ "the heading or the last item says it comes round again"
4158
+ );
4159
+ if (n === 4 && MATRIX_CUE.test(context))
4160
+ return result("matrix", "matrix", "strong", "four items under a heading that names a matrix");
4161
+ if (n >= 3 && FUNNEL_CUE.test(context))
4162
+ return result("funnel", "funnel", "strong", "the heading says the stages narrow");
4163
+ if (n >= 3 && NESTED_CUE.test(context)) {
4164
+ if (INNERMOST.test(first) && !INNERMOST.test(last))
4165
+ return result("nested", "layers", "strong", "nested parts, listed from the inside out");
4166
+ if (INNERMOST.test(last) && (OUTERMOST.test(first) || !INNERMOST.test(first)))
4167
+ return result("nested", "layers", "strong", "nested parts, listed from the outside in", {
4168
+ items: [...texts].reverse()
4169
+ });
4170
+ return result("nested", "layers", "weak", "the heading suggests nested parts");
4171
+ }
4172
+ if (n >= 3 && HIERARCHY_CUE.test(context)) {
4173
+ if (BASE.test(first) && !BASE.test(last))
4174
+ return result("hierarchy", "pyramid", "strong", "a hierarchy, listed from the base up", {
4175
+ items: [...texts].reverse()
4176
+ });
4177
+ if (APEX.test(first) || BASE.test(last) || TOP_FIRST_CUE.test(context))
4178
+ return result("hierarchy", "pyramid", "strong", "a hierarchy, listed from the top down");
4179
+ return result("hierarchy", "pyramid", "weak", "the heading suggests a hierarchy");
4180
+ }
4181
+ if (n === 2 && CONTRAST_CUE.test(heading))
4182
+ return result("contrast", "cards", "strong", "two items under a heading that compares them", {
4183
+ versus: true
4184
+ });
4185
+ if (n === 2 && OVERLAP_CUE.test(context))
4186
+ return result("overlap", "venn", "weak", "the heading suggests two sets that overlap");
4187
+ const openers = texts.filter((text) => ORDINAL_OPENER.test(text)).length;
4188
+ if (input.ordered || openers >= 2 || SEQUENCE_CUE.test(heading))
4189
+ return result(
4190
+ "sequence",
4191
+ "process",
4192
+ "strong",
4193
+ input.ordered ? "a numbered list" : openers >= 2 ? "items open with ordering words" : "the heading names steps"
4194
+ );
4195
+ const brief = texts.every((text) => text.length <= 110);
4196
+ if (n >= 3 && brief && HUB_CUE.test(heading) && labelled * 2 >= n)
4197
+ return result(
4198
+ "hub",
4199
+ "hub",
4200
+ "strong",
4201
+ "labelled items under a heading that names what they share"
4202
+ );
4203
+ if (labelled * 3 >= n * 2)
4204
+ return result("parallel", "cards", "strong", "items written as a label and its detail");
4205
+ if (n >= 3 && brief && HUB_CUE.test(heading))
4206
+ return result("hub", "hub", "weak", "the heading names what the items share");
4207
+ if (n <= 4 && texts.every((text) => text.length <= 90))
4208
+ return result("parallel", "cards", "weak", "a few short items");
4209
+ return PLAIN("no relationship the items make clear");
4210
+ }
4211
+ var SHARED_CUE = /\b(?:both|shared?|shares|sharing|alike|similar(?:ly|ity|ities)?|in common)\b/iu;
4212
+ function shapeIsStated(template, evidence) {
4213
+ const heading = (evidence.heading ?? "").replace(/\s+/gu, " ").trim();
4214
+ const passage = (evidence.passage ?? "").replace(/\s+/gu, " ").trim();
4215
+ const texts = evidence.items.map((item) => item.replace(/\s+/gu, " ").trim()).filter(Boolean);
4216
+ const context = `${heading} ${passage}`;
4217
+ const everything = `${context} ${texts.join(" ")}`;
4218
+ switch (template) {
4219
+ case "cycle":
4220
+ return CYCLE_CUE.test(context) || CYCLE_INTRO.test(passage) || texts.length > 0 && CYCLE_CLOSE.test(texts[texts.length - 1]);
4221
+ case "layers":
4222
+ return NESTED_CUE.test(context) || INNERMOST.test(everything) && OUTERMOST.test(everything);
4223
+ case "pyramid":
4224
+ return HIERARCHY_CUE.test(context) || APEX.test(everything) && BASE.test(everything);
4225
+ case "funnel": {
4226
+ if (FUNNEL_CUE.test(context)) return true;
4227
+ const values = texts.map((text) => figureValue(splitStatItem(text).figure));
4228
+ return values.length >= 3 && values.every(
4229
+ (value, index) => Number.isFinite(value) && (!index || value < values[index - 1])
4230
+ );
4231
+ }
4232
+ case "venn":
4233
+ return OVERLAP_CUE.test(context) || SHARED_CUE.test(passage);
4234
+ case "matrix":
4235
+ return MATRIX_CUE.test(context) || !!evidence.axes?.[0]?.trim() && !!evidence.axes[1]?.trim();
4236
+ default:
4237
+ return true;
4238
+ }
4239
+ }
4240
+ function unstatedShapeFallback(template) {
4241
+ switch (template) {
4242
+ case "cycle":
4243
+ case "funnel":
4244
+ return "process";
4245
+ case "layers":
4246
+ case "pyramid":
4247
+ case "venn":
4248
+ case "matrix":
4249
+ return "cards";
4250
+ default:
4251
+ return void 0;
4252
+ }
4253
+ }
4254
+
3814
4255
  // src/recommend/templates.ts
3815
4256
  var EMPTY_PROFILE = Object.freeze({
3816
4257
  hasImage: false,
@@ -3850,7 +4291,7 @@ function htmlElementsByTag(root, tagNames) {
3850
4291
  }
3851
4292
  return out;
3852
4293
  }
3853
- function profileBlockContents(nodes) {
4294
+ function profileBlockContents(nodes, options = {}) {
3854
4295
  if (!nodes || nodes.length === 0) return { ...EMPTY_PROFILE };
3855
4296
  let imageCount = 0;
3856
4297
  let hasBlockquote = false;
@@ -3904,6 +4345,7 @@ function profileBlockContents(nodes) {
3904
4345
  let hasNumberHighlight = false;
3905
4346
  walkMarkdownTree(root, (n) => {
3906
4347
  if (hasNumberHighlight) return true;
4348
+ if (n.type === "list") return true;
3907
4349
  if (n.type !== "paragraph") return;
3908
4350
  const text = extractPlainText(n).trim();
3909
4351
  if (!text) return true;
@@ -3939,6 +4381,12 @@ function profileBlockContents(nodes) {
3939
4381
  hasTree = true;
3940
4382
  }
3941
4383
  }
4384
+ const onlyList = nodes.length === 1 && nodes[0].type === "list" ? nodes[0] : void 0;
4385
+ const listShape = onlyList ? classifyListShape({
4386
+ heading: options.heading,
4387
+ items: extractConceptItems([onlyList]),
4388
+ ordered: onlyList.ordered === true
4389
+ }) : void 0;
3942
4390
  return {
3943
4391
  hasImage: imageCount > 0,
3944
4392
  imageCount,
@@ -3952,7 +4400,8 @@ function profileBlockContents(nodes) {
3952
4400
  hasCompoundBody: nodes.length > 1 && nodes.some((node) => node.type !== "paragraph"),
3953
4401
  hasAsciiDiagram,
3954
4402
  hasTimeline,
3955
- hasTree
4403
+ hasTree,
4404
+ ...listShape ? { listShape } : {}
3956
4405
  };
3957
4406
  }
3958
4407
  var UNIVERSAL_DEFAULTS = ["title", "sectionHeader", "content", "factCard", "twoColumn"];
@@ -4001,6 +4450,11 @@ function recommendedNamesForProfile(profile) {
4001
4450
  if (profile.hasList) {
4002
4451
  anyContentSignal = true;
4003
4452
  names.add("list");
4453
+ if (profile.listShape && profile.listShape.shape !== "plain") {
4454
+ names.add(profile.listShape.template);
4455
+ names.add("cards");
4456
+ names.add("process");
4457
+ }
4004
4458
  }
4005
4459
  if (profile.hasTable) {
4006
4460
  anyContentSignal = true;
@@ -4028,7 +4482,7 @@ function recommendedNamesForProfile(profile) {
4028
4482
  }
4029
4483
  return Array.from(names);
4030
4484
  }
4031
- function pickAutoTemplate(profile, blockIndex = 0) {
4485
+ function pickAutoTemplate(profile, blockIndex = 0, options = {}) {
4032
4486
  if (profile.hasCompoundBody) return void 0;
4033
4487
  if (profile.hasAsciiDiagram) return "diagram";
4034
4488
  if (profile.hasTimeline) return "timeline";
@@ -4039,7 +4493,8 @@ function pickAutoTemplate(profile, blockIndex = 0) {
4039
4493
  if (profile.hasImage) return blockIndex % 2 === 0 ? "leftFeature" : "rightFeature";
4040
4494
  if (profile.hasBlockquote) return "quote";
4041
4495
  if (profile.hasNumberHighlight) return "statHighlight";
4042
- if (profile.hasList) return "list";
4496
+ if (profile.hasList)
4497
+ return options.concepts !== false && profile.listShape?.confidence === "strong" ? profile.listShape.template : "list";
4043
4498
  return void 0;
4044
4499
  }
4045
4500
  function recommendTemplatesForBlock(profile, allNames) {
@@ -4060,6 +4515,12 @@ export {
4060
4515
  isMediaAnnotationName,
4061
4516
  isStandaloneHtmlVideo,
4062
4517
  extractMediaFromContents,
4518
+ splitConceptItem,
4519
+ isFigureText,
4520
+ splitStatItem,
4521
+ vennOverlapIndex,
4522
+ timelineItems,
4523
+ readMatrixItems,
4063
4524
  toCells,
4064
4525
  cellWidth,
4065
4526
  isHoriz,
@@ -4114,6 +4575,7 @@ export {
4114
4575
  isPictureParagraph,
4115
4576
  extractBodyPlainText,
4116
4577
  extractListItems,
4578
+ extractConceptItems,
4117
4579
  renderMarkdownBlocksHtml,
4118
4580
  markdownBlockSeparatorLines,
4119
4581
  extractRichListItems,
@@ -4123,8 +4585,12 @@ export {
4123
4585
  extractFirstEmbeddedVideo,
4124
4586
  extractTableFromContents,
4125
4587
  extractBlockquoteText,
4588
+ extractBlockquote,
4126
4589
  autoTemplatePreservesContent,
4127
4590
  deriveTemplateInputs,
4591
+ classifyListShape,
4592
+ shapeIsStated,
4593
+ unstatedShapeFallback,
4128
4594
  profileBlockContents,
4129
4595
  pickAutoTemplate,
4130
4596
  recommendTemplatesForBlock