@bendyline/squisq 2.13.11 → 2.13.13

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 (38) hide show
  1. package/dist/{Doc-DqYt_b2b.d.ts → Doc-BhvotZx_.d.ts} +48 -2
  2. package/dist/{ImageEditDoc-CXXiXVJ-.d.ts → ImageEditDoc-D8hYio6b.d.ts} +1 -1
  3. package/dist/{chunk-S76W65BB.js → chunk-5DFFVW75.js} +1 -1
  4. package/dist/{chunk-DOOUVVOM.js → chunk-7MS26OXD.js} +4 -0
  5. package/dist/{chunk-XDOSE5M3.js → chunk-C4SB3NN4.js} +1 -1
  6. package/dist/{chunk-SKGMNBQX.js → chunk-CTT4XAGO.js} +2033 -375
  7. package/dist/{chunk-SG3HXPMF.js → chunk-DBVCSVNV.js} +2 -1
  8. package/dist/chunk-FCB6JLWB.js +125 -0
  9. package/dist/{chunk-WNEH3BZP.js → chunk-JNUNNQ74.js} +3 -3
  10. package/dist/chunk-L4AFSOT3.js +26 -0
  11. package/dist/{chunk-65WL5ILB.js → chunk-OIQ2GO2O.js} +1 -1
  12. package/dist/{chunk-3WUI2N6C.js → chunk-SGNWO7OF.js} +20 -1
  13. package/dist/{chunk-ESGKI6CD.js → chunk-TCGGJUKC.js} +25 -6
  14. package/dist/{chunk-GLWQTZ4C.js → chunk-XEQALK5B.js} +1 -1
  15. package/dist/{chunk-N7I6JP6Q.js → chunk-YQQWE4HG.js} +413 -216
  16. package/dist/doc/index.d.ts +225 -9
  17. package/dist/doc/index.js +32 -7
  18. package/dist/fence/index.d.ts +1 -1
  19. package/dist/generate/index.d.ts +1 -1
  20. package/dist/imageEdit/index.d.ts +3 -3
  21. package/dist/index.d.ts +7 -7
  22. package/dist/index.js +79 -11
  23. package/dist/jsonForm/index.d.ts +1 -1
  24. package/dist/jsonForm/index.js +3 -3
  25. package/dist/markdown/index.js +3 -3
  26. package/dist/{materializePageSection-S2hxZ_lO.d.ts → materializePageSection-CWxou4mX.d.ts} +62 -2
  27. package/dist/mediaEdit/index.d.ts +2 -2
  28. package/dist/narration/index.d.ts +1 -1
  29. package/dist/narration/index.js +5 -5
  30. package/dist/recommend/index.js +1 -1
  31. package/dist/schemas/index.d.ts +4 -4
  32. package/dist/schemas/index.js +2 -2
  33. package/dist/{themeLibrary-CFJbeItn.d.ts → themeLibrary-BIYE6aLw.d.ts} +1 -1
  34. package/dist/timing/index.d.ts +58 -2
  35. package/dist/timing/index.js +9 -1
  36. package/dist/transform/index.d.ts +173 -72
  37. package/dist/transform/index.js +43 -6
  38. package/package.json +1 -1
@@ -32,7 +32,7 @@ import {
32
32
  profileBlockContents,
33
33
  renderMarkdownBlocksHtml,
34
34
  treeToTemplateData
35
- } from "./chunk-3WUI2N6C.js";
35
+ } from "./chunk-SGNWO7OF.js";
36
36
  import {
37
37
  anchorPoint,
38
38
  base64ToUtf8,
@@ -62,12 +62,12 @@ import {
62
62
  validateCustomTemplateDefinition,
63
63
  validateTheme,
64
64
  withAlpha
65
- } from "./chunk-WNEH3BZP.js";
65
+ } from "./chunk-JNUNNQ74.js";
66
66
  import {
67
67
  TABLE_FED_TEMPLATES,
68
68
  isContainerTemplate,
69
69
  resolveTemplateName
70
- } from "./chunk-DOOUVVOM.js";
70
+ } from "./chunk-7MS26OXD.js";
71
71
  import {
72
72
  KNOWN_BLOCK_META_KEYS,
73
73
  coerceAnnotationValues,
@@ -766,11 +766,11 @@ function estimateProseLineCount(text, fontSize, maxWidthPx) {
766
766
  }
767
767
  function fitProse(options) {
768
768
  const { text, baseFontSize, minFontSize, maxWidthPx, maxHeightPx, lineHeight } = options;
769
- const step = Math.max(0.5, options.step ?? 2);
769
+ const step2 = Math.max(0.5, options.step ?? 2);
770
770
  const floor = Math.min(baseFontSize, minFontSize);
771
771
  const heightAt = (lines, size) => lines * size * lineHeight;
772
772
  const candidates = [];
773
- for (let size = baseFontSize; size > floor; size -= step) candidates.push(size);
773
+ for (let size = baseFontSize; size > floor; size -= step2) candidates.push(size);
774
774
  candidates.push(floor);
775
775
  for (const size of candidates) {
776
776
  const lines = estimateProseLineCount(text, size, maxWidthPx);
@@ -1924,6 +1924,1239 @@ function formatFinal(count) {
1924
1924
  return `${count.prefix ?? ""}${formatCountValue(count.to, count.decimals ?? 0, count.grouping ?? false)}${count.suffix ?? ""}`;
1925
1925
  }
1926
1926
 
1927
+ // src/doc/templates/conceptBlocks.ts
1928
+ var MAX_ITEMS = 6;
1929
+ var LABEL_WORDS = 7;
1930
+ function splitConceptItem(text) {
1931
+ const clean = text.replace(/\s+/gu, " ").trim();
1932
+ const match = clean.match(/^(.{1,64}?)(:\s|\s[—–-]\s)(.+)$/u);
1933
+ const range = !!match && !match[2].startsWith(":") && /\d$/u.test(match[1]);
1934
+ if (match && !range && !/[.!?]\s/u.test(match[1]) && match[1].split(" ").length <= LABEL_WORDS)
1935
+ return { label: match[1].trim(), detail: match[3].trim() };
1936
+ return { label: "", detail: clean };
1937
+ }
1938
+ var HEADLINE_BASE = 60;
1939
+ var HEADLINE_MIN = 40;
1940
+ var HEADLINE_LINE_HEIGHT = 1.15;
1941
+ var LABEL_BASE = 44;
1942
+ var DETAIL_BASE = 33;
1943
+ var PLAIN_BASE = 38;
1944
+ var LABEL_LINE_HEIGHT = 1.2;
1945
+ var DETAIL_LINE_HEIGHT = 1.4;
1946
+ var MIN_SCALE = 0.62;
1947
+ var MAX_SCALE = 1.18;
1948
+ var GROW_SHARE = 0.9;
1949
+ function ramp(context, scale) {
1950
+ return {
1951
+ label: Math.round(themedFontSize(LABEL_BASE, context, false) * scale),
1952
+ detail: Math.round(themedFontSize(DETAIL_BASE, context, false) * scale),
1953
+ plain: Math.round(themedFontSize(PLAIN_BASE, context, false) * scale)
1954
+ };
1955
+ }
1956
+ function measureItem(item, sizes, width) {
1957
+ const labelH = item.label ? estimateProseLineCount(item.label, sizes.label, width) * sizes.label * LABEL_LINE_HEIGHT : 0;
1958
+ const size = item.label ? sizes.detail : sizes.plain;
1959
+ const detailH = item.detail ? estimateProseLineCount(item.detail, size, width) * size * DETAIL_LINE_HEIGHT : 0;
1960
+ const gap = item.label && item.detail ? sizes.label * 0.35 : 0;
1961
+ const widest = (text, size2, em) => Math.max(0, ...text.split(/\s+/u).map((word) => word.length)) * size2 * em;
1962
+ const overflows = widest(item.label, sizes.label, 0.62) > width || widest(item.detail, size, 0.56) > width;
1963
+ return { labelH, gap, detailH, height: overflows ? Infinity : labelH + gap + detailH };
1964
+ }
1965
+ var SCALES = [MAX_SCALE, 1, 0.82, MIN_SCALE];
1966
+ function fitRamp(items, context, width, height) {
1967
+ for (const scale of SCALES) {
1968
+ const sizes = ramp(context, scale);
1969
+ const room = height(scale) * (scale > 1 ? GROW_SHARE : 1);
1970
+ if (items.every((item) => measureItem(item, sizes, width(scale)).height <= room))
1971
+ return { sizes, scale };
1972
+ }
1973
+ return { sizes: ramp(context, MIN_SCALE), scale: MIN_SCALE };
1974
+ }
1975
+ function accentColor(input, context) {
1976
+ return input.colorScheme ? resolveColorScheme(context, input.colorScheme).accent : context.theme.colors.highlight || context.theme.colors.primary;
1977
+ }
1978
+ function frame(input, context) {
1979
+ const { theme, viewport } = context;
1980
+ const { width: W, height: H } = viewport;
1981
+ const portrait = H > W;
1982
+ const accent = accentColor(input, context);
1983
+ const margin = Math.round(W * (portrait ? 0.08 : 0.07));
1984
+ const layers = [createBackgroundLayer("bg", themedSurfaceGradient(context, 170))];
1985
+ const title = (input.title ?? "").trim();
1986
+ let top = H * 0.1;
1987
+ if (title) {
1988
+ const headlineTop = H * (portrait ? 0.08 : 0.11);
1989
+ const base = themedFontSize(HEADLINE_BASE, context, true);
1990
+ const fit = fitProse({
1991
+ text: title,
1992
+ baseFontSize: base,
1993
+ minFontSize: Math.min(base, themedFontSize(HEADLINE_MIN, context, true)),
1994
+ maxWidthPx: W - margin * 2,
1995
+ maxHeightPx: base * HEADLINE_LINE_HEIGHT * 2,
1996
+ lineHeight: HEADLINE_LINE_HEIGHT
1997
+ });
1998
+ const ruleH = Math.max(4, Math.round(H * 6e-3));
1999
+ layers.push({
2000
+ type: "shape",
2001
+ id: "accent-rule",
2002
+ position: {
2003
+ x: margin,
2004
+ y: headlineTop - ruleH - fit.fontSize * 0.55,
2005
+ width: W * 0.045,
2006
+ height: ruleH
2007
+ },
2008
+ content: { shape: "rect", fill: accent, borderRadius: ruleH / 2 },
2009
+ animation: entrance(context, "rule", 0)
2010
+ });
2011
+ layers.push({
2012
+ ...textLayer("title", title, box(margin, headlineTop, W - margin * 2, fit.heightPx), {
2013
+ fontSize: fit.fontSize,
2014
+ fontFamily: getThemeFont(context, "title"),
2015
+ fontWeight: "bold",
2016
+ color: theme.colors.text,
2017
+ lineHeight: HEADLINE_LINE_HEIGHT,
2018
+ shadow: shouldUseShadow(context),
2019
+ ...fit.maxLines ? { maxLines: fit.maxLines } : {}
2020
+ }),
2021
+ animation: entrance(context, "text", 0.1)
2022
+ });
2023
+ top = headlineTop + fit.heightPx + H * (portrait ? 0.05 : 0.07);
2024
+ }
2025
+ return {
2026
+ layers,
2027
+ accent,
2028
+ left: margin,
2029
+ right: W - margin,
2030
+ top,
2031
+ bottom: H * (portrait ? 0.92 : 0.9),
2032
+ portrait
2033
+ };
2034
+ }
2035
+ function box(left, top, width, height) {
2036
+ return { x: left + width / 2, y: top + height / 2, width, height, anchor: "center" };
2037
+ }
2038
+ function textLayer(id, text, position, style) {
2039
+ return {
2040
+ type: "text",
2041
+ id,
2042
+ position,
2043
+ content: {
2044
+ text,
2045
+ style: { textAlign: "left", verticalAlign: "top", shrinkToFit: true, ...style }
2046
+ }
2047
+ };
2048
+ }
2049
+ function itemText(id, item, measure, sizes, left, top, width, align, context) {
2050
+ const { theme } = context;
2051
+ const slack = (size) => size * 0.4;
2052
+ const layers = [];
2053
+ if (item.label)
2054
+ layers.push(
2055
+ textLayer(
2056
+ `${id}-label`,
2057
+ item.label,
2058
+ box(left, top, width, measure.labelH + slack(sizes.label)),
2059
+ {
2060
+ fontSize: sizes.label,
2061
+ fontFamily: getThemeFont(context, "title"),
2062
+ fontWeight: "bold",
2063
+ color: theme.colors.text,
2064
+ lineHeight: LABEL_LINE_HEIGHT,
2065
+ textAlign: align
2066
+ }
2067
+ )
2068
+ );
2069
+ if (item.detail) {
2070
+ const size = item.label ? sizes.detail : sizes.plain;
2071
+ layers.push(
2072
+ textLayer(
2073
+ `${id}-detail`,
2074
+ item.detail,
2075
+ box(left, top + measure.labelH + measure.gap, width, measure.detailH + slack(size)),
2076
+ {
2077
+ fontSize: size,
2078
+ fontFamily: getThemeFont(context, "body"),
2079
+ // Details read as secondary by weight and a little transparency, not a
2080
+ // muted hue that loses contrast on dark themes.
2081
+ color: item.label ? withAlpha(theme.colors.text, 0.8) : theme.colors.text,
2082
+ lineHeight: DETAIL_LINE_HEIGHT,
2083
+ textAlign: align
2084
+ }
2085
+ )
2086
+ );
2087
+ }
2088
+ return layers;
2089
+ }
2090
+ function itemsOf(input) {
2091
+ return (Array.isArray(input.items) ? input.items : []).map((item) => String(item ?? "").trim()).filter(Boolean).slice(0, MAX_ITEMS).map(splitConceptItem);
2092
+ }
2093
+ function conceptBuildStepTexts(template, items) {
2094
+ const shown = itemsOf({ items: [...items] });
2095
+ const dated = template === "process" ? timelineItems(shown) : void 0;
2096
+ if (dated)
2097
+ return dated.map((item) => [item.when, item.label, item.detail].filter(Boolean).join(" "));
2098
+ return shown.map(
2099
+ (item, index) => [
2100
+ template === "cards" || template === "layers" ? "" : String(index + 1),
2101
+ item.label,
2102
+ item.detail
2103
+ ].filter(Boolean).join(" ")
2104
+ );
2105
+ }
2106
+ var FIRST_TURN = 0.6;
2107
+ var TURN_STAGGER = 0.45;
2108
+ var SETTLE = "cubic-bezier(0.22, 1, 0.36, 1)";
2109
+ function entrance(context, kind, delay) {
2110
+ const profile = getMotionProfile(context);
2111
+ if (kind === "text" || !profile.diagramBuild)
2112
+ return motionEntrance(context, { type: "fadeIn", duration: 0.7, delay });
2113
+ const at = motionDelay(context, delay);
2114
+ switch (kind) {
2115
+ case "words":
2116
+ return { type: "wordReveal", duration: 0.5, stagger: 0.07, delay: at, easing: SETTLE };
2117
+ case "surface":
2118
+ return { type: "fadeInUp", duration: 0.6, delay: at, easing: SETTLE };
2119
+ case "rule":
2120
+ return { type: "grow", origin: "left", duration: 0.55, delay: at, easing: SETTLE };
2121
+ case "bar":
2122
+ return { type: "grow", origin: "top", duration: 0.6, delay: at, easing: SETTLE };
2123
+ case "pop":
2124
+ return {
2125
+ type: "grow",
2126
+ origin: "center",
2127
+ duration: 0.5,
2128
+ delay: at,
2129
+ easing: profile.name === "vibrant" ? "cubic-bezier(0.34, 1.56, 0.64, 1)" : "cubic-bezier(0.34, 1.3, 0.64, 1)"
2130
+ };
2131
+ case "draw":
2132
+ return { type: "drawOn", duration: 0.5, delay: at, easing: "ease-in-out" };
2133
+ }
2134
+ }
2135
+ function step(parts, index, context) {
2136
+ const turn = FIRST_TURN + index * TURN_STAGGER;
2137
+ return parts.map(([layer, kind, at]) => ({
2138
+ ...layer,
2139
+ buildStep: index,
2140
+ animation: entrance(context, kind, turn + at)
2141
+ }));
2142
+ }
2143
+ function pops(layers, at) {
2144
+ return layers.map((layer) => [layer, "pop", at]);
2145
+ }
2146
+ function words(layers, at) {
2147
+ return layers.map((layer, i) => [layer, "text", at + i * 0.12]);
2148
+ }
2149
+ var LEAD_BASE = 44;
2150
+ var LEAD_MIN = 28;
2151
+ var LEAD_LINE_HEIGHT = 1.4;
2152
+ function lead(area, text, context) {
2153
+ const { theme, viewport } = context;
2154
+ const barW = Math.max(5, Math.round(viewport.width * 4e-3));
2155
+ const indent = barW + Math.round(viewport.width * 0.018);
2156
+ const width = (area.right - area.left) * (area.portrait ? 1 : 0.8) - indent;
2157
+ const base = themedFontSize(
2158
+ text.length <= 90 ? LEAD_BASE * 1.3 : text.length <= 160 ? LEAD_BASE * 1.15 : LEAD_BASE,
2159
+ context,
2160
+ false
2161
+ );
2162
+ const fit = fitProse({
2163
+ text,
2164
+ baseFontSize: base,
2165
+ minFontSize: Math.min(base, themedFontSize(LEAD_MIN, context, false)),
2166
+ maxWidthPx: width,
2167
+ maxHeightPx: (area.bottom - area.top) * 0.9,
2168
+ lineHeight: LEAD_LINE_HEIGHT
2169
+ });
2170
+ const top = area.top + Math.max(0, (area.bottom - area.top - fit.heightPx) * 0.25);
2171
+ return [
2172
+ ...area.layers,
2173
+ ...step(
2174
+ [
2175
+ [
2176
+ {
2177
+ type: "shape",
2178
+ id: "lead-bar",
2179
+ position: {
2180
+ x: area.left,
2181
+ y: top + fit.fontSize * 0.2,
2182
+ width: barW,
2183
+ height: fit.heightPx - fit.fontSize * 0.3
2184
+ },
2185
+ content: { shape: "rect", fill: area.accent, borderRadius: barW / 2 }
2186
+ },
2187
+ "bar",
2188
+ 0
2189
+ ],
2190
+ [
2191
+ textLayer(
2192
+ "lead",
2193
+ text,
2194
+ box(area.left + indent, top, width, fit.heightPx + fit.fontSize * 0.4),
2195
+ {
2196
+ fontSize: fit.fontSize,
2197
+ fontFamily: getThemeFont(context, "body"),
2198
+ color: theme.colors.text,
2199
+ lineHeight: LEAD_LINE_HEIGHT,
2200
+ shadow: shouldUseShadow(context),
2201
+ ...fit.maxLines ? { maxLines: fit.maxLines } : {}
2202
+ }
2203
+ ),
2204
+ "text",
2205
+ 0.15
2206
+ ]
2207
+ ],
2208
+ 0,
2209
+ context
2210
+ )
2211
+ ];
2212
+ }
2213
+ var STATEMENT_BASE = 84;
2214
+ var STATEMENT_MIN = 48;
2215
+ function statement(area, text, context) {
2216
+ const { theme, viewport } = context;
2217
+ const W = area.right - area.left;
2218
+ const H = area.bottom - area.top;
2219
+ const base = themedFontSize(STATEMENT_BASE, context, true);
2220
+ const fit = fitProse({
2221
+ text,
2222
+ baseFontSize: base,
2223
+ minFontSize: Math.min(base, themedFontSize(STATEMENT_MIN, context, true)),
2224
+ maxWidthPx: W * (area.portrait ? 1 : 0.86),
2225
+ maxHeightPx: H * 0.8,
2226
+ lineHeight: HEADLINE_LINE_HEIGHT
2227
+ });
2228
+ const top = area.top + Math.max(0, (H - fit.heightPx) / 2);
2229
+ const ruleH = Math.max(5, Math.round(viewport.height * 7e-3));
2230
+ return [
2231
+ ...area.layers,
2232
+ ...step(
2233
+ [
2234
+ [
2235
+ {
2236
+ type: "shape",
2237
+ id: "statement-rule",
2238
+ position: {
2239
+ x: area.left,
2240
+ y: top - ruleH - fit.fontSize * 0.45,
2241
+ width: W * 0.06,
2242
+ height: ruleH
2243
+ },
2244
+ content: { shape: "rect", fill: area.accent, borderRadius: ruleH / 2 }
2245
+ },
2246
+ "rule",
2247
+ 0
2248
+ ],
2249
+ [
2250
+ textLayer(
2251
+ "statement",
2252
+ text,
2253
+ box(area.left, top, W * (area.portrait ? 1 : 0.86), fit.heightPx + fit.fontSize * 0.3),
2254
+ {
2255
+ fontSize: fit.fontSize,
2256
+ fontFamily: getThemeFont(context, "title"),
2257
+ fontWeight: "bold",
2258
+ color: theme.colors.text,
2259
+ lineHeight: HEADLINE_LINE_HEIGHT,
2260
+ shadow: shouldUseShadow(context),
2261
+ ...fit.maxLines ? { maxLines: fit.maxLines } : {}
2262
+ }
2263
+ ),
2264
+ "words",
2265
+ 0.15
2266
+ ]
2267
+ ],
2268
+ 0,
2269
+ context
2270
+ )
2271
+ ];
2272
+ }
2273
+ function cardsBlock(input, context) {
2274
+ const items = itemsOf(input);
2275
+ const area = frame(input, context);
2276
+ const { theme, viewport } = context;
2277
+ if (!items.length) return area.layers;
2278
+ if (items.length === 1 && !items[0].label)
2279
+ return (input.title?.trim() ? lead : statement)(area, items[0].detail, context);
2280
+ const contrast = String(input.versus) === "true";
2281
+ if (contrast && items.length === 2 && !area.portrait) return versus(area, items, context);
2282
+ const W = area.right - area.left;
2283
+ const H = area.bottom - area.top;
2284
+ const brief = items.every((item) => item.label.length + item.detail.length <= 28);
2285
+ const columns2 = area.portrait ? 1 : items.length <= 3 ? items.length : items.length === 4 ? brief ? 4 : 2 : 3;
2286
+ const rows = Math.ceil(items.length / columns2);
2287
+ const gap = Math.round(viewport.width * 0.02);
2288
+ const cardW = (W - gap * (columns2 - 1)) / columns2;
2289
+ const pad = Math.round(Math.min(44, Math.max(26, cardW * 0.08)));
2290
+ const ruleH = Math.max(4, Math.round(viewport.height * 6e-3));
2291
+ const ruleGap = Math.round(viewport.height * 0.022);
2292
+ const chrome = pad * 2 + ruleH + ruleGap;
2293
+ const { sizes } = fitRamp(
2294
+ items,
2295
+ context,
2296
+ () => cardW - pad * 2,
2297
+ () => (H - gap * (rows - 1)) / rows - chrome
2298
+ );
2299
+ const measures = items.map((item) => measureItem(item, sizes, cardW - pad * 2));
2300
+ const cardH = Math.min(
2301
+ (H - gap * (rows - 1)) / rows,
2302
+ Math.max(...measures.map((m) => m.height)) + chrome + sizes.detail * 0.4
2303
+ );
2304
+ const gridH = rows * cardH + (rows - 1) * gap;
2305
+ const gridTop = area.top + Math.max(0, (H - gridH) * 0.35);
2306
+ const layers = [...area.layers];
2307
+ items.forEach((item, index) => {
2308
+ const row = Math.floor(index / columns2);
2309
+ const inRow = Math.min(columns2, items.length - row * columns2);
2310
+ const rowLeft = area.left + (columns2 - inRow) * (cardW + gap) / 2;
2311
+ const left = rowLeft + index % columns2 * (cardW + gap);
2312
+ const top = gridTop + row * (cardH + gap);
2313
+ const card = {
2314
+ type: "shape",
2315
+ id: `card-${index}`,
2316
+ position: { x: left, y: top, width: cardW, height: cardH },
2317
+ content: {
2318
+ shape: "rect",
2319
+ fill: withAlpha(theme.colors.text, 0.06),
2320
+ stroke: withAlpha(theme.colors.text, 0.12),
2321
+ strokeWidth: 2,
2322
+ borderRadius: Math.round(Math.min(cardW, cardH) * 0.06)
2323
+ }
2324
+ };
2325
+ const rule = {
2326
+ type: "shape",
2327
+ id: `card-${index}-rule`,
2328
+ position: { x: left + pad, y: top + pad, width: Math.min(cardW * 0.22, 64), height: ruleH },
2329
+ content: { shape: "rect", fill: area.accent, borderRadius: ruleH / 2 }
2330
+ };
2331
+ layers.push(
2332
+ ...step(
2333
+ [
2334
+ [card, "surface", 0],
2335
+ [rule, "rule", 0.2],
2336
+ ...words(
2337
+ itemText(
2338
+ `card-${index}`,
2339
+ item,
2340
+ measures[index],
2341
+ sizes,
2342
+ left + pad,
2343
+ top + pad + ruleH + ruleGap,
2344
+ cardW - pad * 2,
2345
+ "left",
2346
+ context
2347
+ ),
2348
+ 0.22
2349
+ )
2350
+ ],
2351
+ index,
2352
+ context
2353
+ )
2354
+ );
2355
+ });
2356
+ return layers;
2357
+ }
2358
+ function versus(area, items, context) {
2359
+ const { theme, viewport } = context;
2360
+ const W = area.right - area.left;
2361
+ const H = area.bottom - area.top;
2362
+ const seam = Math.round(viewport.width * 0.08);
2363
+ const panelW = (W - seam) / 2;
2364
+ const pad = Math.round(Math.min(56, Math.max(30, panelW * 0.08)));
2365
+ const ruleH = Math.max(4, Math.round(viewport.height * 6e-3));
2366
+ const ruleGap = Math.round(viewport.height * 0.022);
2367
+ const chrome = pad * 2 + ruleH + ruleGap;
2368
+ const { sizes } = fitRamp(
2369
+ items,
2370
+ context,
2371
+ () => panelW - pad * 2,
2372
+ () => H * 0.8 - chrome
2373
+ );
2374
+ const measures = items.map((item) => measureItem(item, sizes, panelW - pad * 2));
2375
+ const panelH = Math.min(
2376
+ H,
2377
+ Math.max(...measures.map((m) => m.height)) + chrome + sizes.detail * 0.4
2378
+ );
2379
+ const top = area.top + Math.max(0, (H - panelH) * 0.35);
2380
+ const mark = Math.round(Math.min(viewport.height * 0.1, seam * 0.85));
2381
+ const markFill = area.accent;
2382
+ const layers = [...area.layers];
2383
+ items.forEach((item, index) => {
2384
+ const left = area.left + index * (panelW + seam);
2385
+ const radius = Math.round(Math.min(panelW, panelH) * 0.05);
2386
+ const parts = [
2387
+ [
2388
+ {
2389
+ type: "shape",
2390
+ id: `side-${index}`,
2391
+ position: { x: left, y: top, width: panelW, height: panelH },
2392
+ content: {
2393
+ shape: "rect",
2394
+ fill: withAlpha(theme.colors.text, 0.06),
2395
+ stroke: withAlpha(theme.colors.text, 0.12),
2396
+ strokeWidth: 2,
2397
+ borderRadius: radius
2398
+ }
2399
+ },
2400
+ "surface",
2401
+ 0
2402
+ ],
2403
+ [
2404
+ {
2405
+ type: "shape",
2406
+ id: `side-${index}-rule`,
2407
+ position: {
2408
+ x: left + pad,
2409
+ y: top + pad,
2410
+ width: Math.min(panelW * 0.22, 64),
2411
+ height: ruleH
2412
+ },
2413
+ content: { shape: "rect", fill: area.accent, borderRadius: ruleH / 2 }
2414
+ },
2415
+ "rule",
2416
+ 0.15
2417
+ ],
2418
+ ...words(
2419
+ itemText(
2420
+ `side-${index}`,
2421
+ item,
2422
+ measures[index],
2423
+ sizes,
2424
+ left + pad,
2425
+ top + pad + ruleH + ruleGap,
2426
+ panelW - pad * 2,
2427
+ "left",
2428
+ context
2429
+ ),
2430
+ 0.2
2431
+ )
2432
+ ];
2433
+ layers.push(...step(parts, index, context));
2434
+ });
2435
+ const cx = area.left + panelW + seam / 2;
2436
+ const cy = top + panelH / 2;
2437
+ const at = FIRST_TURN + 0.45;
2438
+ layers.push(
2439
+ {
2440
+ type: "shape",
2441
+ id: "versus-mark",
2442
+ position: { x: cx - mark / 2, y: cy - mark / 2, width: mark, height: mark },
2443
+ content: { shape: "circle", fill: markFill },
2444
+ animation: entrance(context, "pop", at)
2445
+ },
2446
+ {
2447
+ type: "text",
2448
+ id: "versus-mark-text",
2449
+ position: { x: cx, y: cy, width: mark, height: mark, anchor: "center" },
2450
+ content: {
2451
+ text: "vs",
2452
+ style: {
2453
+ fontSize: Math.round(mark * 0.38),
2454
+ fontFamily: getThemeFont(context, "title"),
2455
+ fontWeight: "bold",
2456
+ fontStyle: "italic",
2457
+ color: pickContrastingText(markFill),
2458
+ textAlign: "center",
2459
+ verticalAlign: "middle"
2460
+ }
2461
+ },
2462
+ animation: entrance(context, "pop", at)
2463
+ }
2464
+ );
2465
+ return layers;
2466
+ }
2467
+ function badge(id, number, cx, cy, diameter, accent, context) {
2468
+ return [
2469
+ {
2470
+ type: "shape",
2471
+ id,
2472
+ position: { x: cx - diameter / 2, y: cy - diameter / 2, width: diameter, height: diameter },
2473
+ content: { shape: "circle", fill: accent }
2474
+ },
2475
+ {
2476
+ type: "text",
2477
+ id: `${id}-number`,
2478
+ position: { x: cx, y: cy, width: diameter, height: diameter, anchor: "center" },
2479
+ content: {
2480
+ text: String(number),
2481
+ style: {
2482
+ fontSize: Math.round(diameter * 0.44),
2483
+ fontFamily: getThemeFont(context, "title"),
2484
+ fontWeight: "bold",
2485
+ color: pickContrastingText(accent),
2486
+ textAlign: "center",
2487
+ verticalAlign: "middle",
2488
+ baselineShift: 0.04
2489
+ }
2490
+ }
2491
+ }
2492
+ ];
2493
+ }
2494
+ function connector(id, d, color, width, arrow = true) {
2495
+ return {
2496
+ type: "path",
2497
+ id,
2498
+ position: { x: 0, y: 0, width: "100%", height: "100%" },
2499
+ content: {
2500
+ d,
2501
+ stroke: color,
2502
+ strokeWidth: width,
2503
+ fill: "none",
2504
+ ...arrow ? { endMarker: "arrow" } : {}
2505
+ }
2506
+ };
2507
+ }
2508
+ function processBlock(input, context) {
2509
+ const items = itemsOf(input);
2510
+ const area = frame(input, context);
2511
+ const { viewport } = context;
2512
+ if (!items.length) return area.layers;
2513
+ const dated = timelineItems(items);
2514
+ if (dated) return timeline(area, dated, context);
2515
+ const W = area.right - area.left;
2516
+ const H = area.bottom - area.top;
2517
+ const line = withAlpha(area.accent, 0.75);
2518
+ const stroke = Math.max(3, Math.round(viewport.height * 4e-3));
2519
+ const layers = [...area.layers];
2520
+ const across = !area.portrait && items.length <= 4 && items.every((item) => item.detail.length <= 110 && item.label.length <= 40);
2521
+ if (across) {
2522
+ const columnW = W / items.length;
2523
+ const diameter2 = Math.round(Math.min(viewport.height * 0.1, columnW * 0.3));
2524
+ const textGap = Math.round(diameter2 * 0.45);
2525
+ const textW2 = columnW - Math.round(viewport.width * 0.02);
2526
+ const { sizes: sizes2 } = fitRamp(
2527
+ items,
2528
+ context,
2529
+ () => textW2,
2530
+ () => H - diameter2 - textGap
2531
+ );
2532
+ const measures2 = items.map((item) => measureItem(item, sizes2, textW2));
2533
+ const groupH = diameter2 + textGap + Math.max(...measures2.map((m) => m.height));
2534
+ const groupTop = area.top + Math.max(0, (H - groupH) * 0.2);
2535
+ const cy = groupTop + diameter2 / 2;
2536
+ items.forEach((item, index) => {
2537
+ const cx2 = area.left + columnW * (index + 0.5);
2538
+ const parts = [];
2539
+ const arrives = index > 0 ? 0.3 : 0;
2540
+ if (index > 0) {
2541
+ const from = cx2 - columnW + diameter2 / 2 + diameter2 * 0.25;
2542
+ const to = cx2 - diameter2 / 2 - diameter2 * 0.25;
2543
+ parts.push([
2544
+ connector(`step-${index}-arrow`, `M ${from} ${cy} L ${to} ${cy}`, line, stroke),
2545
+ "draw",
2546
+ 0
2547
+ ]);
2548
+ }
2549
+ parts.push(
2550
+ ...pops(badge(`step-${index}`, index + 1, cx2, cy, diameter2, area.accent, context), arrives)
2551
+ );
2552
+ parts.push(
2553
+ ...words(
2554
+ itemText(
2555
+ `step-${index}`,
2556
+ item,
2557
+ measures2[index],
2558
+ sizes2,
2559
+ cx2 - textW2 / 2,
2560
+ groupTop + diameter2 + textGap,
2561
+ textW2,
2562
+ "center",
2563
+ context
2564
+ ),
2565
+ arrives + 0.18
2566
+ )
2567
+ );
2568
+ layers.push(...step(parts, index, context));
2569
+ });
2570
+ return layers;
2571
+ }
2572
+ const diameter = Math.round(Math.min(viewport.height * 0.07, viewport.width * 0.07));
2573
+ const textLeft = area.left + diameter + Math.round(viewport.width * 0.022);
2574
+ const textW = area.right - textLeft;
2575
+ const rowGap = Math.round(viewport.height * 0.028);
2576
+ const { sizes } = fitRamp(
2577
+ items,
2578
+ context,
2579
+ () => textW,
2580
+ () => (H - rowGap * (items.length - 1)) / items.length
2581
+ );
2582
+ const measures = items.map((item) => measureItem(item, sizes, textW));
2583
+ const rowH = measures.map((m) => Math.max(diameter, m.height));
2584
+ const rowsH = rowH.reduce((sum, h) => sum + h, 0);
2585
+ const gap = items.length > 1 ? rowGap + Math.min(
2586
+ viewport.height * 0.05,
2587
+ Math.max(0, (H - rowsH - rowGap * (items.length - 1)) * 0.5) / (items.length - 1)
2588
+ ) : 0;
2589
+ const totalH = rowsH + gap * (items.length - 1);
2590
+ let top = area.top + Math.max(0, (H - totalH) * 0.2);
2591
+ const cx = area.left + diameter / 2;
2592
+ let previous = 0;
2593
+ items.forEach((item, index) => {
2594
+ const parts = [];
2595
+ const firstLine = item.label ? sizes.label * LABEL_LINE_HEIGHT : sizes.plain * DETAIL_LINE_HEIGHT;
2596
+ const cy = top + Math.max(firstLine, diameter) / 2;
2597
+ let arrives = 0;
2598
+ if (index > 0) {
2599
+ const from = previous + diameter / 2 + diameter * 0.12;
2600
+ const to = cy - diameter / 2 - diameter * 0.12;
2601
+ if (to > from) {
2602
+ parts.push([
2603
+ connector(
2604
+ `step-${index}-arrow`,
2605
+ `M ${cx} ${from} L ${cx} ${to}`,
2606
+ line,
2607
+ stroke,
2608
+ to - from >= diameter * 0.6
2609
+ ),
2610
+ "draw",
2611
+ 0
2612
+ ]);
2613
+ arrives = 0.25;
2614
+ }
2615
+ }
2616
+ previous = cy;
2617
+ parts.push(
2618
+ ...pops(badge(`step-${index}`, index + 1, cx, cy, diameter, area.accent, context), arrives)
2619
+ );
2620
+ parts.push(
2621
+ ...words(
2622
+ itemText(
2623
+ `step-${index}`,
2624
+ item,
2625
+ measures[index],
2626
+ sizes,
2627
+ textLeft,
2628
+ top,
2629
+ textW,
2630
+ "left",
2631
+ context
2632
+ ),
2633
+ arrives + 0.15
2634
+ )
2635
+ );
2636
+ layers.push(...step(parts, index, context));
2637
+ top += rowH[index] + gap;
2638
+ });
2639
+ return layers;
2640
+ }
2641
+ var MONTH = "(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\\.?\\s+";
2642
+ var LEADING_DATE = new RegExp(
2643
+ `^((?:${MONTH}(?:\\d{1,2},?\\s+)?)?(?:\\d{4}s?|\\d{1,4}\\s?(?:BCE?|AD|CE))(?![.,]?\\d))[\\s,:;\u2014\u2013-]*(.*)$`,
2644
+ "u"
2645
+ );
2646
+ function timelineItems(items) {
2647
+ if (items.length < 2) return void 0;
2648
+ let last = -Infinity;
2649
+ const dated = [];
2650
+ for (const item of items) {
2651
+ const match = (item.label || item.detail).match(LEADING_DATE);
2652
+ if (!match) return void 0;
2653
+ const when = match[1];
2654
+ const year = Number(when.match(/\d{1,4}(?=s?\s?(?:BCE?|AD|CE)?$)/u)?.[0] ?? NaN);
2655
+ const signed = /BCE?$/u.test(when) ? -year : year;
2656
+ if (!Number.isFinite(signed) || signed < last) return void 0;
2657
+ last = signed;
2658
+ const rest = match[2].trim();
2659
+ dated.push(
2660
+ item.label ? { when, label: rest, detail: item.detail } : { when, label: "", detail: rest }
2661
+ );
2662
+ }
2663
+ return dated;
2664
+ }
2665
+ function timeline(area, items, context) {
2666
+ const { viewport } = context;
2667
+ const W = area.right - area.left;
2668
+ const H = area.bottom - area.top;
2669
+ const n = items.length;
2670
+ const dot = Math.max(14, Math.round(viewport.height * 0.026));
2671
+ const whenSize = Math.round(themedFontSize(LABEL_BASE, context, true) * 1.1);
2672
+ const whenH = whenSize * LABEL_LINE_HEIGHT;
2673
+ const gap = Math.round(viewport.height * 0.03);
2674
+ const stroke = Math.max(3, Math.round(viewport.height * 4e-3));
2675
+ const line = withAlpha(area.accent, 0.6);
2676
+ const layers = [...area.layers];
2677
+ const station = (index, cx, cy) => ({
2678
+ type: "shape",
2679
+ id: `station-${index}`,
2680
+ position: { x: cx - dot / 2, y: cy - dot / 2, width: dot, height: dot },
2681
+ content: { shape: "circle", fill: area.accent }
2682
+ });
2683
+ const whenText = (index, item, left, top2, width, align) => textLayer(`station-${index}-when`, item.when, box(left, top2, width, whenH + whenSize * 0.3), {
2684
+ fontSize: whenSize,
2685
+ fontFamily: getThemeFont(context, "title"),
2686
+ fontWeight: "bold",
2687
+ color: area.accent,
2688
+ lineHeight: LABEL_LINE_HEIGHT,
2689
+ textAlign: align
2690
+ });
2691
+ const columnW = W / n;
2692
+ const across = !area.portrait && n <= 5 && items.every((item) => item.detail.length <= 120 && item.label.length <= 40);
2693
+ if (across) {
2694
+ const textW2 = columnW - Math.round(viewport.width * 0.02);
2695
+ const { sizes: sizes2 } = fitRamp(
2696
+ items,
2697
+ context,
2698
+ () => textW2,
2699
+ () => H - whenH - dot - gap * 2
2700
+ );
2701
+ const measures2 = items.map((item) => measureItem(item, sizes2, textW2));
2702
+ const groupH = whenH + gap + dot + gap + Math.max(...measures2.map((m) => m.height));
2703
+ const groupTop = area.top + Math.max(0, (H - groupH) * 0.3);
2704
+ const trackY = groupTop + whenH + gap + dot / 2;
2705
+ let from2 = area.left;
2706
+ items.forEach((item, index) => {
2707
+ const cx = area.left + columnW * (index + 0.5);
2708
+ const parts = [
2709
+ [
2710
+ connector(
2711
+ `station-${index}-track`,
2712
+ `M ${from2} ${trackY} L ${cx - dot} ${trackY}`,
2713
+ line,
2714
+ stroke,
2715
+ false
2716
+ ),
2717
+ "draw",
2718
+ 0
2719
+ ],
2720
+ [station(index, cx, trackY), "pop", 0.3],
2721
+ [whenText(index, item, cx - textW2 / 2, groupTop, textW2, "center"), "text", 0.35],
2722
+ ...words(
2723
+ itemText(
2724
+ `station-${index}`,
2725
+ item,
2726
+ measures2[index],
2727
+ sizes2,
2728
+ cx - textW2 / 2,
2729
+ trackY + dot / 2 + gap,
2730
+ textW2,
2731
+ "center",
2732
+ context
2733
+ ),
2734
+ 0.47
2735
+ )
2736
+ ];
2737
+ if (index === n - 1)
2738
+ parts.push([
2739
+ connector(
2740
+ "station-tail",
2741
+ `M ${cx + dot} ${trackY} L ${area.right} ${trackY}`,
2742
+ withAlpha(area.accent, 0.3),
2743
+ stroke
2744
+ ),
2745
+ "draw",
2746
+ 0.65
2747
+ ]);
2748
+ from2 = cx + dot;
2749
+ layers.push(...step(parts, index, context));
2750
+ });
2751
+ return layers;
2752
+ }
2753
+ const whenW = Math.min(
2754
+ W * 0.28,
2755
+ Math.max(...items.map((item) => item.when.length)) * whenSize * 0.62 + whenSize * 0.2
2756
+ );
2757
+ const trackX = area.left + whenW + gap + dot / 2;
2758
+ const textLeft = trackX + dot / 2 + gap;
2759
+ const textW = area.right - textLeft;
2760
+ const rowGap = Math.round(viewport.height * 0.028);
2761
+ const { sizes } = fitRamp(
2762
+ items,
2763
+ context,
2764
+ () => textW,
2765
+ () => (H - rowGap * (n - 1)) / n
2766
+ );
2767
+ const measures = items.map((item) => measureItem(item, sizes, textW));
2768
+ const rowH = measures.map((m) => Math.max(whenH, m.height));
2769
+ const rowsH = rowH.reduce((sum, h) => sum + h, 0);
2770
+ const spread = n > 1 ? rowGap + Math.min(
2771
+ viewport.height * 0.05,
2772
+ Math.max(0, (H - rowsH - rowGap * (n - 1)) * 0.5) / (n - 1)
2773
+ ) : 0;
2774
+ let top = area.top + Math.max(0, (H - rowsH - spread * (n - 1)) * 0.2);
2775
+ let from = 0;
2776
+ items.forEach((item, index) => {
2777
+ const cy = top + whenH / 2;
2778
+ const parts = [];
2779
+ if (index > 0)
2780
+ parts.push([
2781
+ connector(
2782
+ `station-${index}-track`,
2783
+ `M ${trackX} ${from} L ${trackX} ${cy - dot}`,
2784
+ line,
2785
+ stroke,
2786
+ false
2787
+ ),
2788
+ "draw",
2789
+ 0
2790
+ ]);
2791
+ parts.push(
2792
+ [station(index, trackX, cy), "pop", index > 0 ? 0.3 : 0],
2793
+ [whenText(index, item, area.left, top, whenW, "right"), "text", 0.35],
2794
+ ...words(
2795
+ itemText(
2796
+ `station-${index}`,
2797
+ item,
2798
+ measures[index],
2799
+ sizes,
2800
+ textLeft,
2801
+ top,
2802
+ textW,
2803
+ "left",
2804
+ context
2805
+ ),
2806
+ 0.47
2807
+ )
2808
+ );
2809
+ from = cy + dot;
2810
+ top += rowH[index] + spread;
2811
+ if (index === n - 1)
2812
+ parts.push([
2813
+ connector(
2814
+ "station-tail",
2815
+ `M ${trackX} ${from} L ${trackX} ${Math.min(area.bottom, from + spread + whenH)}`,
2816
+ withAlpha(area.accent, 0.3),
2817
+ stroke
2818
+ ),
2819
+ "draw",
2820
+ 0.65
2821
+ ]);
2822
+ layers.push(...step(parts, index, context));
2823
+ });
2824
+ return layers;
2825
+ }
2826
+ function cycleBlock(input, context) {
2827
+ const items = itemsOf(input);
2828
+ const area = frame(input, context);
2829
+ const { viewport } = context;
2830
+ if (items.length < 2) return processBlock({ ...input, template: "process" }, context);
2831
+ const H = area.bottom - area.top;
2832
+ const n = items.length;
2833
+ const cx = (area.left + area.right) / 2;
2834
+ const cy = area.top + H / 2;
2835
+ const angle = (i) => -Math.PI / 2 + 2 * Math.PI * i / n;
2836
+ const vertical = (i) => Math.abs(Math.cos(angle(i))) < 0.35;
2837
+ const diameter = Math.round(Math.min(viewport.height * 0.1, viewport.width * 0.075));
2838
+ const gap = Math.round(diameter * 0.3);
2839
+ const layout = (radius2) => {
2840
+ const above = H / 2 - radius2 - diameter / 2 - gap;
2841
+ const besideW = (area.right - area.left) / 2 - radius2 - diameter / 2 - gap;
2842
+ const verticalW = Math.round(Math.min(besideW * 2, (area.right - area.left) * 0.5));
2843
+ const labelW2 = (i) => vertical(i) ? verticalW : besideW;
2844
+ const boxH = (i) => vertical(i) ? above : radius2 * 1.1;
2845
+ const scale2 = Math.min(
2846
+ ...items.map(
2847
+ (_, i) => fitRamp(
2848
+ [items[i]],
2849
+ context,
2850
+ () => labelW2(i),
2851
+ () => boxH(i)
2852
+ ).scale
2853
+ )
2854
+ );
2855
+ return { labelW: labelW2, scale: scale2 };
2856
+ };
2857
+ const radii = [0.3, 0.27, 0.24, 0.21, 0.18].map(
2858
+ (share) => Math.round(Math.max(diameter * 1.2, Math.min(H * share, viewport.width * 0.16)))
2859
+ );
2860
+ const radius = radii.find((r) => layout(r).scale >= 0.85) ?? radii[radii.length - 1];
2861
+ const { labelW, scale } = layout(radius);
2862
+ const sizes = ramp(context, scale);
2863
+ const measures = items.map((item, i) => measureItem(item, sizes, labelW(i)));
2864
+ const line = withAlpha(area.accent, 0.75);
2865
+ const stroke = Math.max(3, Math.round(viewport.height * 4e-3));
2866
+ const clear = (diameter / 2 + diameter * 0.22) / radius;
2867
+ const point = (a) => [cx + radius * Math.cos(a), cy + radius * Math.sin(a)];
2868
+ const layers = [...area.layers];
2869
+ items.forEach((item, index) => {
2870
+ const a = angle(index);
2871
+ const [x, y] = point(a);
2872
+ const parts = [];
2873
+ const arcInto = (from, to, id) => {
2874
+ const [x1, y1] = point(from + clear);
2875
+ const [x2, y2] = point(to - clear);
2876
+ return connector(id, `M ${x1} ${y1} A ${radius} ${radius} 0 0 1 ${x2} ${y2}`, line, stroke);
2877
+ };
2878
+ const arrives = index > 0 ? 0.35 : 0;
2879
+ if (index > 0) parts.push([arcInto(angle(index - 1), a, `stage-${index}-arrow`), "draw", 0]);
2880
+ parts.push(
2881
+ ...pops(badge(`stage-${index}`, index + 1, x, y, diameter, area.accent, context), arrives)
2882
+ );
2883
+ const m = measures[index];
2884
+ const cos = Math.cos(a);
2885
+ let left;
2886
+ let top;
2887
+ let align;
2888
+ const width = labelW(index);
2889
+ if (cos > 0.35) {
2890
+ left = x + diameter / 2 + gap;
2891
+ top = y - Math.min(m.height / 2, diameter / 2);
2892
+ align = "left";
2893
+ } else if (cos < -0.35) {
2894
+ left = x - diameter / 2 - gap - width;
2895
+ top = y - Math.min(m.height / 2, diameter / 2);
2896
+ align = "right";
2897
+ } else {
2898
+ left = x - width / 2;
2899
+ top = Math.sin(a) < 0 ? y - diameter / 2 - gap - m.height : y + diameter / 2 + gap;
2900
+ align = "center";
2901
+ }
2902
+ parts.push(
2903
+ ...words(
2904
+ itemText(`stage-${index}`, item, m, sizes, left, top, width, align, context),
2905
+ arrives + 0.18
2906
+ )
2907
+ );
2908
+ if (index === n - 1)
2909
+ parts.push([arcInto(a, angle(n), "stage-close-arrow"), "draw", arrives + 0.5]);
2910
+ layers.push(...step(parts, index, context));
2911
+ });
2912
+ return layers;
2913
+ }
2914
+ function layersBlock(input, context) {
2915
+ const items = itemsOf(input);
2916
+ if (items.length < 2) return cardsBlock({ ...input, template: "cards" }, context);
2917
+ const area = frame(input, context);
2918
+ const { theme, viewport } = context;
2919
+ const W = area.right - area.left;
2920
+ const H = area.bottom - area.top;
2921
+ const n = items.length;
2922
+ const portrait = area.portrait;
2923
+ const R = Math.round(portrait ? Math.min(W * 0.4, H * 0.24) : Math.min(H * 0.48, W * 0.22));
2924
+ const cx = portrait ? area.left + W / 2 : area.left + R;
2925
+ const cy = portrait ? area.top + R : area.top + H / 2;
2926
+ const gap = Math.round(viewport.width * (portrait ? 0.02 : 0.05));
2927
+ const textLeft = portrait ? area.left + gap * 1.5 : cx + R + gap;
2928
+ const textW = area.right - textLeft;
2929
+ const textTop = portrait ? cy + R + viewport.height * 0.05 : area.top;
2930
+ const textH = area.bottom - textTop;
2931
+ const rowGap = Math.round(viewport.height * 0.025);
2932
+ const { sizes } = fitRamp(
2933
+ items,
2934
+ context,
2935
+ () => textW,
2936
+ () => (textH - rowGap * (n - 1)) / n
2937
+ );
2938
+ const measures = items.map((item) => measureItem(item, sizes, textW));
2939
+ const rowsH = measures.reduce((sum, m) => sum + m.height, 0) + rowGap * (n - 1);
2940
+ let rowTop = portrait ? textTop : Math.max(area.top, cy - rowsH / 2);
2941
+ const radius = (index) => R * (index + 1) / n;
2942
+ const tint = (index) => withAlpha(area.accent, 0.85 - 0.6 * index / (n - 1));
2943
+ const stroke = Math.max(2, Math.round(viewport.height * 3e-3));
2944
+ const rings = [];
2945
+ const callouts = [];
2946
+ items.forEach((item, index) => {
2947
+ const r = radius(index);
2948
+ rings.unshift(
2949
+ ...step(
2950
+ [
2951
+ [
2952
+ {
2953
+ type: "shape",
2954
+ id: `layer-${index}`,
2955
+ position: { x: cx - r, y: cy - r, width: r * 2, height: r * 2 },
2956
+ content: {
2957
+ shape: "circle",
2958
+ fill: tint(index),
2959
+ stroke: theme.colors.background,
2960
+ strokeWidth: stroke
2961
+ }
2962
+ },
2963
+ "pop",
2964
+ 0
2965
+ ]
2966
+ ],
2967
+ index,
2968
+ context
2969
+ )
2970
+ );
2971
+ const m = measures[index];
2972
+ const firstLine = item.label ? sizes.label * LABEL_LINE_HEIGHT : sizes.plain * DETAIL_LINE_HEIGHT;
2973
+ const labelY = rowTop + firstLine / 2;
2974
+ const parts = [];
2975
+ if (portrait) {
2976
+ const swatch = Math.round(sizes.label * 0.55);
2977
+ parts.push([
2978
+ {
2979
+ type: "shape",
2980
+ id: `layer-${index}-swatch`,
2981
+ position: { x: area.left, y: labelY - swatch / 2, width: swatch, height: swatch },
2982
+ content: { shape: "circle", fill: tint(index) }
2983
+ },
2984
+ "pop",
2985
+ 0.2
2986
+ ]);
2987
+ } else {
2988
+ const inner = index ? radius(index - 1) : 0;
2989
+ const middle = index ? (inner + r) / 2 : r * 0.45;
2990
+ const angle = (-40 + 80 * index / Math.max(1, n - 1)) * Math.PI / 180;
2991
+ const x = cx + middle * Math.cos(angle);
2992
+ const y = cy + middle * Math.sin(angle);
2993
+ const dot = Math.max(8, Math.round(viewport.height * 9e-3));
2994
+ parts.push(
2995
+ [
2996
+ connector(
2997
+ `layer-${index}-callout`,
2998
+ `M ${x} ${y} L ${cx + R + gap * 0.3} ${labelY} L ${textLeft - gap * 0.25} ${labelY}`,
2999
+ withAlpha(theme.colors.text, 0.55),
3000
+ stroke,
3001
+ false
3002
+ ),
3003
+ "draw",
3004
+ 0.25
3005
+ ],
3006
+ [
3007
+ {
3008
+ type: "shape",
3009
+ id: `layer-${index}-pin`,
3010
+ position: { x: x - dot / 2, y: y - dot / 2, width: dot, height: dot },
3011
+ content: { shape: "circle", fill: theme.colors.text }
3012
+ },
3013
+ "pop",
3014
+ 0.2
3015
+ ]
3016
+ );
3017
+ }
3018
+ parts.push(
3019
+ ...words(
3020
+ itemText(`layer-${index}`, item, m, sizes, textLeft, rowTop, textW, "left", context),
3021
+ 0.4
3022
+ )
3023
+ );
3024
+ callouts.push(...step(parts, index, context));
3025
+ rowTop += m.height + rowGap;
3026
+ });
3027
+ return [...area.layers, ...rings, ...callouts];
3028
+ }
3029
+ var FIGURE_BASE = 280;
3030
+ var FIGURE_MIN = 120;
3031
+ var FIGURE_LINE_HEIGHT = 1;
3032
+ function splitFigure(stat) {
3033
+ const match = stat.trim().match(/^(\D{0,2}[\d.,]+\s?[%×x]?)\s+(\p{L}[\p{L} -]{0,20})$/u);
3034
+ return match ? { number: match[1].trim(), unit: match[2].trim() } : { number: stat.trim(), unit: "" };
3035
+ }
3036
+ function figureLockup(input, context) {
3037
+ const area = frame({ title: input.headline, colorScheme: input.colorScheme }, context);
3038
+ const { theme, viewport } = context;
3039
+ const W = area.right - area.left;
3040
+ const H = area.bottom - area.top;
3041
+ const { number, unit } = splitFigure(input.stat);
3042
+ const gutter = Math.round(viewport.width * 0.05);
3043
+ const figureW = area.portrait ? W : W * 0.4;
3044
+ const textLeft = area.portrait ? area.left : area.left + figureW + gutter;
3045
+ const textW = area.right - textLeft;
3046
+ const base = themedFontSize(FIGURE_BASE, context, true);
3047
+ const figure = fitProse({
3048
+ text: number,
3049
+ baseFontSize: base,
3050
+ minFontSize: Math.min(base, themedFontSize(FIGURE_MIN, context, true)),
3051
+ maxWidthPx: figureW,
3052
+ maxHeightPx: base * FIGURE_LINE_HEIGHT * 1.05,
3053
+ lineHeight: FIGURE_LINE_HEIGHT
3054
+ });
3055
+ const unitSize = themedFontSize(LABEL_BASE, context, true);
3056
+ const unitH = unit ? unitSize * LABEL_LINE_HEIGHT : 0;
3057
+ const figureH = figure.fontSize * 1.08 + unitH;
3058
+ const description = (input.description ?? "").trim();
3059
+ const detail = (input.detail ?? "").trim();
3060
+ const leadBase = themedFontSize(LEAD_BASE, context, false);
3061
+ const room = area.portrait ? H - figureH - viewport.height * 0.05 : H;
3062
+ const lead2 = fitProse({
3063
+ text: description,
3064
+ baseFontSize: leadBase,
3065
+ minFontSize: Math.min(leadBase, themedFontSize(LEAD_MIN, context, false)),
3066
+ maxWidthPx: textW,
3067
+ maxHeightPx: room * (detail ? 0.6 : 0.9),
3068
+ lineHeight: LEAD_LINE_HEIGHT
3069
+ });
3070
+ const detailSize = themedFontSize(DETAIL_BASE, context, false);
3071
+ const detailGap = detail && description ? leadBase * 0.6 : 0;
3072
+ const detailH = detail ? estimateProseLineCount(detail, detailSize, textW) * detailSize * DETAIL_LINE_HEIGHT : 0;
3073
+ const wordsH = (description ? lead2.heightPx : 0) + detailGap + detailH;
3074
+ const groupH = area.portrait ? figureH + viewport.height * 0.05 + wordsH : Math.max(figureH, wordsH);
3075
+ const top = area.top + Math.max(0, (H - groupH) * 0.4);
3076
+ const figureTop = area.portrait ? top : top + Math.max(0, (wordsH - figureH) / 2);
3077
+ const wordsTop = area.portrait ? top + figureH + viewport.height * 0.05 : top + Math.max(0, (figureH - wordsH) / 2);
3078
+ const motion = getMotionProfile(context);
3079
+ const count = motion.countUp ? countUpSpecFor(number) : null;
3080
+ const layers = [...area.layers];
3081
+ layers.push({
3082
+ ...textLayer("stat", number, box(area.left, figureTop, figureW, figure.fontSize * 1.08), {
3083
+ fontSize: figure.fontSize,
3084
+ fontFamily: getThemeFont(context, "title"),
3085
+ fontWeight: "bold",
3086
+ color: area.accent,
3087
+ lineHeight: FIGURE_LINE_HEIGHT,
3088
+ shadow: shouldUseShadow(context)
3089
+ }),
3090
+ animation: count ? { type: "countUp", duration: motion.countUpDuration, delay: 0.3, count } : entrance(context, "pop", 0.3)
3091
+ });
3092
+ if (unit)
3093
+ layers.push({
3094
+ ...textLayer(
3095
+ "stat-unit",
3096
+ unit,
3097
+ box(area.left, figureTop + figure.fontSize * 1.08, figureW, unitH),
3098
+ {
3099
+ fontSize: unitSize,
3100
+ fontFamily: getThemeFont(context, "title"),
3101
+ fontWeight: "bold",
3102
+ color: area.accent,
3103
+ lineHeight: LABEL_LINE_HEIGHT
3104
+ }
3105
+ ),
3106
+ animation: entrance(context, "text", 0.45)
3107
+ });
3108
+ if (!area.portrait && (description || detail))
3109
+ layers.push({
3110
+ type: "shape",
3111
+ id: "stat-divider",
3112
+ position: {
3113
+ x: textLeft - gutter / 2 - 1,
3114
+ y: Math.min(figureTop, wordsTop),
3115
+ width: 2,
3116
+ height: Math.max(figureH, wordsH)
3117
+ },
3118
+ content: { shape: "rect", fill: withAlpha(theme.colors.text, 0.18) },
3119
+ animation: entrance(context, "bar", 0.35)
3120
+ });
3121
+ if (description)
3122
+ layers.push({
3123
+ ...textLayer(
3124
+ "description",
3125
+ description,
3126
+ box(textLeft, wordsTop, textW, lead2.heightPx + lead2.fontSize * 0.4),
3127
+ {
3128
+ fontSize: lead2.fontSize,
3129
+ fontFamily: getThemeFont(context, "body"),
3130
+ color: theme.colors.text,
3131
+ lineHeight: LEAD_LINE_HEIGHT,
3132
+ ...lead2.maxLines ? { maxLines: lead2.maxLines } : {}
3133
+ }
3134
+ ),
3135
+ animation: entrance(context, "text", 0.6)
3136
+ });
3137
+ if (detail)
3138
+ layers.push({
3139
+ ...textLayer(
3140
+ "detail",
3141
+ detail,
3142
+ box(
3143
+ textLeft,
3144
+ wordsTop + (description ? lead2.heightPx : 0) + detailGap,
3145
+ textW,
3146
+ detailH + detailSize * 0.4
3147
+ ),
3148
+ {
3149
+ fontSize: detailSize,
3150
+ fontFamily: getThemeFont(context, "body"),
3151
+ color: withAlpha(theme.colors.text, 0.8),
3152
+ lineHeight: DETAIL_LINE_HEIGHT
3153
+ }
3154
+ ),
3155
+ animation: entrance(context, "text", 0.75)
3156
+ });
3157
+ return layers;
3158
+ }
3159
+
1927
3160
  // src/doc/templates/statHighlight.ts
1928
3161
  var SAFE_TOP = 0.08;
1929
3162
  var SAFE_BOTTOM = 0.92;
@@ -1931,19 +3164,25 @@ var OPTICAL_CENTER = 0.47;
1931
3164
  var HERO_MAX_SHARE = 0.38;
1932
3165
  var STAT_LINE_HEIGHT = 1.1;
1933
3166
  var DESC_LINE_HEIGHT = 1.5;
1934
- var DETAIL_LINE_HEIGHT = 1.4;
3167
+ var DETAIL_LINE_HEIGHT2 = 1.4;
1935
3168
  function statHighlight(input, context) {
1936
- const { stat, colorScheme = "blue" } = input;
3169
+ if (input.headline?.trim() && !input.accentImage) return figureLockup(input, context);
3170
+ const { stat, colorScheme } = input;
1937
3171
  const { theme, viewport } = context;
1938
3172
  const accentImage = accentForViewport(input.accentImage, viewport);
1939
- const colors = resolveColorScheme(context, colorScheme);
3173
+ const colors = colorScheme ? resolveColorScheme(context, colorScheme) : {
3174
+ ...resolveColorScheme(context, "blue"),
3175
+ text: theme.colors.highlight || theme.colors.primary,
3176
+ detail: theme.colors.text
3177
+ };
3178
+ const detailColor = "detail" in colors ? colors.detail : colors.text;
1940
3179
  const rawDescription = (input.description ?? "").trim();
1941
3180
  const description = rawDescription === stat.trim() ? "" : rawDescription;
1942
3181
  const detail = (input.detail ?? "").trim();
1943
3182
  const accentLayout = accentImage ? getAccentLayout(accentImage.position) : DEFAULT_LAYOUT;
1944
3183
  const statFontSize = themedFontSize(148, context, true);
1945
- const descFontSize = themedFontSize(32, context, false);
1946
- const detailFontSize = themedFontSize(26, context, false);
3184
+ const descFontSize = themedFontSize(40, context, false);
3185
+ const detailFontSize = themedFontSize(30, context, false);
1947
3186
  const layers = [createBackgroundLayer("bg", themedSurfaceGradient(context, 180))];
1948
3187
  const motion = getMotionProfile(context);
1949
3188
  const countSpec = motion.countUp ? countUpSpecFor(stat) : null;
@@ -1978,8 +3217,8 @@ function statHighlight(input, context) {
1978
3217
  baseFontSize: detailFontSize,
1979
3218
  minFontSize: detailFontSize,
1980
3219
  maxWidthPx: columnPx,
1981
- maxHeightPx: 2 * detailFontSize * DETAIL_LINE_HEIGHT,
1982
- lineHeight: DETAIL_LINE_HEIGHT
3220
+ maxHeightPx: 2 * detailFontSize * DETAIL_LINE_HEIGHT2,
3221
+ lineHeight: DETAIL_LINE_HEIGHT2
1983
3222
  }) : null;
1984
3223
  const detailBoxH = detailFit ? detailFit.heightPx + detailFontSize * 0.3 : 0;
1985
3224
  const detailGap = detailFit && description ? Math.round(descFontSize * 0.9) : 0;
@@ -2006,7 +3245,7 @@ function statHighlight(input, context) {
2006
3245
  const descTop = statTop + statBoxH + gap;
2007
3246
  const detailTop = descTop + descBoxH + detailGap;
2008
3247
  const pct2 = (px) => `${Number((px / frameH * 100).toFixed(3))}%`;
2009
- const box = (top, height) => ({
3248
+ const box2 = (top, height) => ({
2010
3249
  x: accentLayout.textCenterX,
2011
3250
  y: pct2(top + height / 2),
2012
3251
  width: accentLayout.textWidth,
@@ -2030,7 +3269,7 @@ function statHighlight(input, context) {
2030
3269
  ...statFit.maxLines ? { maxLines: statFit.maxLines } : {}
2031
3270
  }
2032
3271
  },
2033
- position: box(statTop, statBoxH),
3272
+ position: box2(statTop, statBoxH),
2034
3273
  animation: statAnimation
2035
3274
  });
2036
3275
  if (descFit) {
@@ -2050,7 +3289,7 @@ function statHighlight(input, context) {
2050
3289
  ...descFit.maxLines ? { maxLines: descFit.maxLines } : {}
2051
3290
  }
2052
3291
  },
2053
- position: box(descTop, descBoxH),
3292
+ position: box2(descTop, descBoxH),
2054
3293
  animation: motionEntrance(context, { type: "fadeIn", duration: 1, delay: 0.3 })
2055
3294
  });
2056
3295
  }
@@ -2066,15 +3305,15 @@ function statHighlight(input, context) {
2066
3305
  // Scheme *text* rather than *accent*: accents in the built-in
2067
3306
  // schemes are tuned as chart/fill colors and drop below legible
2068
3307
  // contrast as body copy on light surfaces.
2069
- color: colors.text,
3308
+ color: detailColor,
2070
3309
  textAlign: "left",
2071
- lineHeight: DETAIL_LINE_HEIGHT,
3310
+ lineHeight: DETAIL_LINE_HEIGHT2,
2072
3311
  shadow: shouldUseShadow(context),
2073
3312
  shrinkToFit: true,
2074
3313
  ...detailFit.maxLines ? { maxLines: detailFit.maxLines } : {}
2075
3314
  }
2076
3315
  },
2077
- position: box(detailTop, detailBoxH),
3316
+ position: box2(detailTop, detailBoxH),
2078
3317
  animation: motionEntrance(context, { type: "fadeIn", duration: 1, delay: 1 })
2079
3318
  });
2080
3319
  }
@@ -2348,7 +3587,7 @@ function factCard(input, context) {
2348
3587
  const explainTop = factTop + factBoxH + gap;
2349
3588
  const sourceTop = explainTop + explainBoxH + sourceGap;
2350
3589
  const pct2 = (px) => `${Number((px / frameH * 100).toFixed(3))}%`;
2351
- const box = (top, height) => ({
3590
+ const box2 = (top, height) => ({
2352
3591
  x: accentLayout.textCenterX,
2353
3592
  y: pct2(top + height / 2),
2354
3593
  width: accentLayout.textWidth,
@@ -2372,7 +3611,7 @@ function factCard(input, context) {
2372
3611
  ...factFit.maxLines ? { maxLines: factFit.maxLines } : {}
2373
3612
  }
2374
3613
  },
2375
- position: box(factTop, factBoxH),
3614
+ position: box2(factTop, factBoxH),
2376
3615
  animation: motionEntrance(context, { type: "fadeIn", duration: 1.5 })
2377
3616
  });
2378
3617
  if (explainFit) {
@@ -2392,7 +3631,7 @@ function factCard(input, context) {
2392
3631
  ...explainFit.maxLines ? { maxLines: explainFit.maxLines } : {}
2393
3632
  }
2394
3633
  },
2395
- position: box(explainTop, explainBoxH),
3634
+ position: box2(explainTop, explainBoxH),
2396
3635
  animation: motionEntrance(context, { type: "fadeIn", duration: 1, delay: 0.8 })
2397
3636
  });
2398
3637
  }
@@ -2413,7 +3652,7 @@ function factCard(input, context) {
2413
3652
  ...sourceFit.maxLines ? { maxLines: sourceFit.maxLines } : {}
2414
3653
  }
2415
3654
  },
2416
- position: box(sourceTop, sourceBoxH),
3655
+ position: box2(sourceTop, sourceBoxH),
2417
3656
  animation: { type: "fadeIn", duration: 0.8, delay: 1.5 }
2418
3657
  });
2419
3658
  }
@@ -2704,7 +3943,7 @@ function dateEvent(input, context) {
2704
3943
  const dateTop = groupTopPx;
2705
3944
  const descTop = dateTop + dateH + descGap;
2706
3945
  const footerTop = descTop + descH + footerGap;
2707
- const box = (topPx, heightPx) => {
3946
+ const box2 = (topPx, heightPx) => {
2708
3947
  const height = Math.round(heightPx);
2709
3948
  return {
2710
3949
  x: accentLayout.textCenterX,
@@ -2754,7 +3993,7 @@ function dateEvent(input, context) {
2754
3993
  shadow: shouldUseShadow(context)
2755
3994
  }
2756
3995
  },
2757
- position: box(dateTop, dateH + dateFit.fontSize * BOX_SLACK_EM),
3996
+ position: box2(dateTop, dateH + dateFit.fontSize * BOX_SLACK_EM),
2758
3997
  animation: { type: "fadeIn", duration: 1.5 }
2759
3998
  });
2760
3999
  if (descFit) {
@@ -2775,7 +4014,7 @@ function dateEvent(input, context) {
2775
4014
  shadow: shouldUseShadow(context)
2776
4015
  }
2777
4016
  },
2778
- position: box(descTop, descH + descFit.fontSize * BOX_SLACK_EM),
4017
+ position: box2(descTop, descH + descFit.fontSize * BOX_SLACK_EM),
2779
4018
  animation: { type: "fadeIn", duration: 2, delay: 1 }
2780
4019
  });
2781
4020
  }
@@ -2796,7 +4035,7 @@ function dateEvent(input, context) {
2796
4035
  shadow: shouldUseShadow(context)
2797
4036
  }
2798
4037
  },
2799
- position: box(footerTop, footerH + footerFontSize * BOX_SLACK_EM),
4038
+ position: box2(footerTop, footerH + footerFontSize * BOX_SLACK_EM),
2800
4039
  animation: { type: "fadeIn", duration: 1, delay: 3 }
2801
4040
  });
2802
4041
  }
@@ -3033,13 +4272,13 @@ function fitWrapWidth(text, fontSize, columnWidthPx, fontFamily) {
3033
4272
  const mono = isMonoFamily(fontFamily);
3034
4273
  if (!mono && !WIDE_GLYPH_RE.test(text)) return columnWidthPx;
3035
4274
  const floor = columnWidthPx * 0.5;
3036
- const step = columnWidthPx * 0.02;
4275
+ const step2 = columnWidthPx * 0.02;
3037
4276
  let width = columnWidthPx;
3038
4277
  while (width > floor) {
3039
4278
  const lines = simulateRendererLines(text, fontSize, width);
3040
4279
  const widest = Math.max(...lines.map((line) => estimateLineEm(line, mono))) * fontSize;
3041
4280
  if (widest <= columnWidthPx) return width;
3042
- width -= step;
4281
+ width -= step2;
3043
4282
  }
3044
4283
  return floor;
3045
4284
  }
@@ -3407,8 +4646,8 @@ function listItemGeometry(fontSize, baseFontSize, itemCount, textWidthPx, ordere
3407
4646
  }
3408
4647
  function measureListHeightPx(items, fontSize, geometry) {
3409
4648
  let heightPx = 0;
3410
- for (const itemText of items) {
3411
- const lineCount = estimateProseLineCount(itemText, fontSize, geometry.bodyWidthPx);
4649
+ for (const itemText2 of items) {
4650
+ const lineCount = estimateProseLineCount(itemText2, fontSize, geometry.bodyWidthPx);
3412
4651
  heightPx += lineCount * fontSize * LIST_ITEM_LINE_HEIGHT + geometry.gapPx;
3413
4652
  }
3414
4653
  return heightPx;
@@ -3558,9 +4797,9 @@ function listBlock(input, context) {
3558
4797
  const bodyLeftX = `${leftPct + bodyIndentPx / context.viewport.width * 100}%`;
3559
4798
  let itemY = startY;
3560
4799
  for (let i = 0; i < items.length; i++) {
3561
- const itemText = items[i];
3562
- const itemHtml = richItems[i]?.text === itemText ? richItems[i]?.html : void 0;
3563
- const lineCount = estimateProseLineCount(itemText, itemFontSize, bodyWidthPx);
4800
+ const itemText2 = items[i];
4801
+ const itemHtml = richItems[i]?.text === itemText2 ? richItems[i]?.html : void 0;
4802
+ const lineCount = estimateProseLineCount(itemText2, itemFontSize, bodyWidthPx);
3564
4803
  const animation = motionEntrance(context, {
3565
4804
  type: "fadeIn",
3566
4805
  duration: 0.8,
@@ -3593,11 +4832,11 @@ function listBlock(input, context) {
3593
4832
  type: "text",
3594
4833
  id: `item-${i}`,
3595
4834
  content: {
3596
- text: itemText,
4835
+ text: itemText2,
3597
4836
  ...itemHtml ? { html: itemHtml } : {},
3598
4837
  paragraphs: [
3599
4838
  {
3600
- text: itemText,
4839
+ text: itemText2,
3601
4840
  list: { ordered, level: 0, ...ordered ? { number: i + 1 } : {} }
3602
4841
  }
3603
4842
  ],
@@ -3831,7 +5070,7 @@ function definitionCard(input, context) {
3831
5070
  const defTop = ruleTop + RULE_HEIGHT_PX + defGap;
3832
5071
  const originTop = defTop + defH + originGap;
3833
5072
  const pct2 = (px) => `${(px / viewport.height * 100).toFixed(3)}%`;
3834
- const box = (topPx, heightPx) => {
5073
+ const box2 = (topPx, heightPx) => {
3835
5074
  const height = Math.round(heightPx);
3836
5075
  return {
3837
5076
  x: accentLayout.textCenterX,
@@ -3869,7 +5108,7 @@ function definitionCard(input, context) {
3869
5108
  shadow: shouldUseShadow(context)
3870
5109
  }
3871
5110
  },
3872
- position: box(termTop, termH + termFit.fontSize * BOX_SLACK_EM2),
5111
+ position: box2(termTop, termH + termFit.fontSize * BOX_SLACK_EM2),
3873
5112
  animation: motionEntrance(context, { type: "fadeIn", duration: 1.5 })
3874
5113
  });
3875
5114
  layers.push({
@@ -3906,7 +5145,7 @@ function definitionCard(input, context) {
3906
5145
  shadow: shouldUseShadow(context)
3907
5146
  }
3908
5147
  },
3909
- position: box(defTop, defH + defFit.fontSize * BOX_SLACK_EM2),
5148
+ position: box2(defTop, defH + defFit.fontSize * BOX_SLACK_EM2),
3910
5149
  animation: motionEntrance(context, { type: "fadeIn", duration: 1, delay: 0.8 })
3911
5150
  });
3912
5151
  }
@@ -3927,7 +5166,7 @@ function definitionCard(input, context) {
3927
5166
  shadow: shouldUseShadow(context)
3928
5167
  }
3929
5168
  },
3930
- position: box(originTop, originH + originFontSize * BOX_SLACK_EM2),
5169
+ position: box2(originTop, originH + originFontSize * BOX_SLACK_EM2),
3931
5170
  animation: { type: "fadeIn", duration: 0.8, delay: 1.5 }
3932
5171
  });
3933
5172
  }
@@ -4638,14 +5877,14 @@ function niceTicks(min, max, targetCount = 5) {
4638
5877
  }
4639
5878
  const count = Math.max(2, targetCount);
4640
5879
  const range = niceNum(hi - lo, false);
4641
- const step = niceNum(range / (count - 1), true);
4642
- const niceMin = Math.floor(lo / step) * step;
4643
- const niceMax = Math.ceil(hi / step) * step;
5880
+ const step2 = niceNum(range / (count - 1), true);
5881
+ const niceMin = Math.floor(lo / step2) * step2;
5882
+ const niceMax = Math.ceil(hi / step2) * step2;
4644
5883
  const ticks = [];
4645
- for (let tick = niceMin; tick <= niceMax + step * 1e-6; tick += step) {
4646
- ticks.push(Math.abs(tick) < step * 1e-6 ? 0 : Number(tick.toPrecision(12)));
5884
+ for (let tick = niceMin; tick <= niceMax + step2 * 1e-6; tick += step2) {
5885
+ ticks.push(Math.abs(tick) < step2 * 1e-6 ? 0 : Number(tick.toPrecision(12)));
4647
5886
  }
4648
- return { ticks, niceMin, niceMax, step };
5887
+ return { ticks, niceMin, niceMax, step: step2 };
4649
5888
  }
4650
5889
  function formatChartValue(value, unit) {
4651
5890
  if (!Number.isFinite(value)) return "\u2014";
@@ -4661,6 +5900,12 @@ function formatChartValue(value, unit) {
4661
5900
  }
4662
5901
  return unit ? `${text} ${unit}` : text;
4663
5902
  }
5903
+ function formatChartDataValue(value, unit) {
5904
+ if (!Number.isFinite(value) || Math.abs(value) >= 1e4) return formatChartValue(value, unit);
5905
+ const sign = value < 0 ? "-" : "";
5906
+ const text = `${sign}${Math.abs(value).toLocaleString("en-US", { maximumFractionDigits: 2 })}`;
5907
+ return unit ? `${text} ${unit}` : text;
5908
+ }
4664
5909
  function trimTrailingZero(text) {
4665
5910
  return text.replace(/\.0$/, "");
4666
5911
  }
@@ -4725,16 +5970,16 @@ function computeChartFrame(options) {
4725
5970
  cursor = titleBand.y + titleBand.height;
4726
5971
  }
4727
5972
  let tableBand;
4728
- let tableHeight = 0;
5973
+ let tableHeight2 = 0;
4729
5974
  if (options.tableRowCount > 0) {
4730
- tableHeight = Math.min(H * 0.4, (options.tableRowCount + 1) * options.tableFontSize * 2.4);
5975
+ tableHeight2 = Math.min(H * 0.4, (options.tableRowCount + 1) * options.tableFontSize * 2.4);
4731
5976
  }
4732
5977
  const legendHeight = options.legendRows > 0 ? options.legendRows * options.legendRowHeight : 0;
4733
5978
  const chartArea = {
4734
5979
  x: marginX,
4735
5980
  y: cursor,
4736
5981
  width: innerWidth,
4737
- height: Math.max(40, H - marginY - cursor - legendHeight - tableHeight)
5982
+ height: Math.max(40, H - marginY - cursor - legendHeight - tableHeight2)
4738
5983
  };
4739
5984
  cursor = chartArea.y + chartArea.height;
4740
5985
  let legendBand;
@@ -4742,8 +5987,8 @@ function computeChartFrame(options) {
4742
5987
  legendBand = { x: marginX, y: cursor, width: innerWidth, height: legendHeight };
4743
5988
  cursor += legendHeight;
4744
5989
  }
4745
- if (tableHeight > 0) {
4746
- tableBand = { x: marginX, y: cursor, width: innerWidth, height: tableHeight };
5990
+ if (tableHeight2 > 0) {
5991
+ tableBand = { x: marginX, y: cursor, width: innerWidth, height: tableHeight2 };
4747
5992
  }
4748
5993
  const plot = {
4749
5994
  x: chartArea.x + options.leftGutter,
@@ -4853,7 +6098,7 @@ function renderChart(kind, input, context) {
4853
6098
  const innerWidth = viewport.width * 0.88;
4854
6099
  const legend = legendLabels.length > 0 ? layoutLegend(legendLabels, legendFontSize, innerWidth) : null;
4855
6100
  const gutters = computeGutters(kind, data, input, context, axisFontSize, labelFontSize);
4856
- const frame = computeChartFrame({
6101
+ const frame2 = computeChartFrame({
4857
6102
  viewportWidth: viewport.width,
4858
6103
  viewportHeight: viewport.height,
4859
6104
  hasTitle: title.length > 0,
@@ -4867,7 +6112,7 @@ function renderChart(kind, input, context) {
4867
6112
  });
4868
6113
  const entranceHint = getTemplateHint(context, templateId, "entrance", "staggered");
4869
6114
  const markDelay = (index) => entranceHint === "subtle" ? 0.1 : Math.min(1.2, 0.2 + index * 0.08);
4870
- const entrance = (index) => ({
6115
+ const entrance2 = (index) => ({
4871
6116
  type: "fadeIn",
4872
6117
  duration: 0.6,
4873
6118
  delay: markDelay(index)
@@ -4877,7 +6122,7 @@ function renderChart(kind, input, context) {
4877
6122
  const growEasing = motion.textEntrance.easing;
4878
6123
  const eased = (animation) => growEasing ? { ...animation, easing: growEasing } : animation;
4879
6124
  const mark = (index, kind2, along = 0) => {
4880
- if (!motion.chartGrowth) return entrance(index);
6125
+ if (!motion.chartGrowth) return entrance2(index);
4881
6126
  const delay = motionDelay(context, markDelay(index));
4882
6127
  switch (kind2) {
4883
6128
  case "bar":
@@ -4920,19 +6165,19 @@ function renderChart(kind, input, context) {
4920
6165
  input,
4921
6166
  context,
4922
6167
  data,
4923
- frame,
6168
+ frame: frame2,
4924
6169
  colors,
4925
6170
  axisFontSize,
4926
6171
  labelFontSize,
4927
6172
  bodyFont,
4928
6173
  gridColor: withAlpha(theme.colors.text, 0.12),
4929
6174
  axisTextColor: theme.colors.textMuted,
4930
- entrance,
6175
+ entrance: entrance2,
4931
6176
  mark,
4932
6177
  labelAnimation
4933
6178
  };
4934
6179
  const layers = [createBackgroundLayer("bg", themedSurfaceGradient(context, 170))];
4935
- if (title && frame.titleBand) {
6180
+ if (title && frame2.titleBand) {
4936
6181
  layers.push({
4937
6182
  type: "text",
4938
6183
  id: "title",
@@ -4948,9 +6193,9 @@ function renderChart(kind, input, context) {
4948
6193
  }
4949
6194
  },
4950
6195
  position: {
4951
- x: frame.titleBand.x + frame.titleBand.width / 2,
4952
- y: frame.titleBand.y + frame.titleBand.height / 2,
4953
- width: frame.titleBand.width,
6196
+ x: frame2.titleBand.x + frame2.titleBand.width / 2,
6197
+ y: frame2.titleBand.y + frame2.titleBand.height / 2,
6198
+ width: frame2.titleBand.width,
4954
6199
  anchor: "center"
4955
6200
  },
4956
6201
  animation: motionEntrance(context, { type: "fadeIn", duration: 0.8 })
@@ -4975,11 +6220,11 @@ function renderChart(kind, input, context) {
4975
6220
  layers.push(...paintScatter(paint));
4976
6221
  break;
4977
6222
  }
4978
- if (legend && frame.legendBand) {
4979
- layers.push(...paintLegend(paint, legend, frame.legendBand, legendFontSize));
6223
+ if (legend && frame2.legendBand) {
6224
+ layers.push(...paintLegend(paint, legend, frame2.legendBand, legendFontSize));
4980
6225
  }
4981
- if (input.showTable === true && frame.tableBand) {
4982
- layers.push(tableLayer(table, frame.tableBand, tableFontSize, input, context));
6226
+ if (input.showTable === true && frame2.tableBand) {
6227
+ layers.push(tableLayer(table, frame2.tableBand, tableFontSize, input, context));
4983
6228
  }
4984
6229
  return layers;
4985
6230
  }
@@ -5060,7 +6305,7 @@ function valueTicks(kind, data, input) {
5060
6305
  return niceTicks(min, max, 5);
5061
6306
  }
5062
6307
  function paintValueAxisY(paint, scale, yFor) {
5063
- const { frame, gridColor, axisTextColor, axisFontSize, bodyFont, input } = paint;
6308
+ const { frame: frame2, gridColor, axisTextColor, axisFontSize, bodyFont, input } = paint;
5064
6309
  const layers = [];
5065
6310
  scale.ticks.forEach((tick, index) => {
5066
6311
  const y = yFor(tick);
@@ -5072,9 +6317,9 @@ function paintValueAxisY(paint, scale, yFor) {
5072
6317
  fill: tick === 0 ? withAlpha(paint.context.theme.colors.text, 0.35) : gridColor
5073
6318
  },
5074
6319
  position: {
5075
- x: frame.plot.x,
6320
+ x: frame2.plot.x,
5076
6321
  y: y - (tick === 0 ? 1 : 0.5),
5077
- width: frame.plot.width,
6322
+ width: frame2.plot.width,
5078
6323
  height: tick === 0 ? 2 : 1
5079
6324
  }
5080
6325
  });
@@ -5091,7 +6336,7 @@ function paintValueAxisY(paint, scale, yFor) {
5091
6336
  }
5092
6337
  },
5093
6338
  position: {
5094
- x: frame.chartArea.x + (frame.plot.x - frame.chartArea.x) / 2,
6339
+ x: frame2.chartArea.x + (frame2.plot.x - frame2.chartArea.x) / 2,
5095
6340
  y,
5096
6341
  anchor: "center"
5097
6342
  }
@@ -5100,11 +6345,11 @@ function paintValueAxisY(paint, scale, yFor) {
5100
6345
  return layers;
5101
6346
  }
5102
6347
  function paintCategoryAxisX(paint, xForCenter) {
5103
- const { frame, data, labelFontSize, axisTextColor, bodyFont } = paint;
5104
- const step = categoryLabelStep(data.labels.length, frame.plot.width, labelFontSize);
6348
+ const { frame: frame2, data, labelFontSize, axisTextColor, bodyFont } = paint;
6349
+ const step2 = categoryLabelStep(data.labels.length, frame2.plot.width, labelFontSize);
5105
6350
  const layers = [];
5106
6351
  data.labels.forEach((label, index) => {
5107
- if (index % step !== 0 || !label) return;
6352
+ if (index % step2 !== 0 || !label) return;
5108
6353
  layers.push({
5109
6354
  type: "text",
5110
6355
  id: `cat-${index}`,
@@ -5119,7 +6364,7 @@ function paintCategoryAxisX(paint, xForCenter) {
5119
6364
  },
5120
6365
  position: {
5121
6366
  x: xForCenter(index),
5122
- y: frame.plot.y + frame.plot.height + labelFontSize * 1.2,
6367
+ y: frame2.plot.y + frame2.plot.height + labelFontSize * 1.2,
5123
6368
  anchor: "center"
5124
6369
  }
5125
6370
  });
@@ -5127,14 +6372,14 @@ function paintCategoryAxisX(paint, xForCenter) {
5127
6372
  return layers;
5128
6373
  }
5129
6374
  function paintColumns(paint) {
5130
- const { frame, data, colors, input, mark, labelAnimation } = paint;
6375
+ const { frame: frame2, data, colors, input, mark, labelAnimation } = paint;
5131
6376
  const scale = valueTicks("column", data, input);
5132
- const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
6377
+ const yFor = (v) => frame2.plot.y + frame2.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
5133
6378
  const layers = paintValueAxisY(paint, scale, yFor);
5134
6379
  const n = data.labels.length;
5135
- const band = frame.plot.width / Math.max(1, n);
6380
+ const band = frame2.plot.width / Math.max(1, n);
5136
6381
  const innerPad = band * 0.15;
5137
- layers.push(...paintCategoryAxisX(paint, (i) => frame.plot.x + (i + 0.5) * band));
6382
+ layers.push(...paintCategoryAxisX(paint, (i) => frame2.plot.x + (i + 0.5) * band));
5138
6383
  const stacked = input.stacked === true && data.series.length > 1;
5139
6384
  const extents = stacked ? stackSeries(data.series) : null;
5140
6385
  const groupWidth = band - innerPad * 2;
@@ -5146,7 +6391,7 @@ function paintColumns(paint) {
5146
6391
  if (!extent || extent.y0 === extent.y1) return;
5147
6392
  const top = yFor(extent.y1);
5148
6393
  const height = Math.max(1, yFor(extent.y0) - top);
5149
- const x = stacked ? frame.plot.x + i * band + innerPad : frame.plot.x + i * band + innerPad + s * (barWidth + 4);
6394
+ const x = stacked ? frame2.plot.x + i * band + innerPad : frame2.plot.x + i * band + innerPad + s * (barWidth + 4);
5150
6395
  layers.push({
5151
6396
  type: "shape",
5152
6397
  id: `mark-${s}-${i}`,
@@ -5172,7 +6417,7 @@ function paintColumns(paint) {
5172
6417
  }
5173
6418
  function paintBars(paint) {
5174
6419
  const {
5175
- frame,
6420
+ frame: frame2,
5176
6421
  data,
5177
6422
  colors,
5178
6423
  input,
@@ -5186,11 +6431,11 @@ function paintBars(paint) {
5186
6431
  } = paint;
5187
6432
  const scale = valueTicks("bar", data, input);
5188
6433
  const labelGap = axisFontSize * 0.6;
5189
- const labelWidthPx = (value) => formatChartValue(value, input.unit).length * axisFontSize * CHART_CHAR_WIDTH_EM;
6434
+ const labelWidthPx = (value) => formatChartDataValue(value, input.unit).length * axisFontSize * CHART_CHAR_WIDTH_EM;
5190
6435
  const showValueLabels = input.showValues === true && !(input.stacked === true);
5191
6436
  const reservedForLabels = showValueLabels ? data.series.flatMap((series) => series.values).filter((v) => v !== null).reduce((max, v) => Math.max(max, labelWidthPx(v)), 0) + labelGap : 0;
5192
- const axisWidth = Math.max(20, frame.plot.width - reservedForLabels);
5193
- const xFor = (v) => frame.plot.x + axisWidth * ((v - scale.niceMin) / (scale.niceMax - scale.niceMin));
6437
+ const axisWidth = Math.max(20, frame2.plot.width - reservedForLabels);
6438
+ const xFor = (v) => frame2.plot.x + axisWidth * ((v - scale.niceMin) / (scale.niceMax - scale.niceMin));
5194
6439
  const layers = [];
5195
6440
  scale.ticks.forEach((tick, index) => {
5196
6441
  const x = xFor(tick);
@@ -5203,9 +6448,9 @@ function paintBars(paint) {
5203
6448
  },
5204
6449
  position: {
5205
6450
  x: x - (tick === 0 ? 1 : 0.5),
5206
- y: frame.plot.y,
6451
+ y: frame2.plot.y,
5207
6452
  width: tick === 0 ? 2 : 1,
5208
- height: frame.plot.height
6453
+ height: frame2.plot.height
5209
6454
  }
5210
6455
  });
5211
6456
  layers.push({
@@ -5220,11 +6465,11 @@ function paintBars(paint) {
5220
6465
  textAlign: "center"
5221
6466
  }
5222
6467
  },
5223
- position: { x, y: frame.plot.y + frame.plot.height + axisFontSize * 1.2, anchor: "center" }
6468
+ position: { x, y: frame2.plot.y + frame2.plot.height + axisFontSize * 1.2, anchor: "center" }
5224
6469
  });
5225
6470
  });
5226
6471
  const n = data.labels.length;
5227
- const band = frame.plot.height / Math.max(1, n);
6472
+ const band = frame2.plot.height / Math.max(1, n);
5228
6473
  const innerPad = band * 0.15;
5229
6474
  const stacked = input.stacked === true && data.series.length > 1;
5230
6475
  const extents = stacked ? stackSeries(data.series) : null;
@@ -5245,8 +6490,8 @@ function paintBars(paint) {
5245
6490
  }
5246
6491
  },
5247
6492
  position: {
5248
- x: frame.chartArea.x + (frame.plot.x - frame.chartArea.x) / 2,
5249
- y: frame.plot.y + (i + 0.5) * band,
6493
+ x: frame2.chartArea.x + (frame2.plot.x - frame2.chartArea.x) / 2,
6494
+ y: frame2.plot.y + (i + 0.5) * band,
5250
6495
  anchor: "center"
5251
6496
  }
5252
6497
  });
@@ -5258,7 +6503,7 @@ function paintBars(paint) {
5258
6503
  if (!extent || extent.y0 === extent.y1) return;
5259
6504
  const left = xFor(extent.y0);
5260
6505
  const width = Math.max(1, xFor(extent.y1) - left);
5261
- const y = stacked ? frame.plot.y + i * band + innerPad : frame.plot.y + i * band + innerPad + s * (barHeight + 4);
6506
+ const y = stacked ? frame2.plot.y + i * band + innerPad : frame2.plot.y + i * band + innerPad + s * (barHeight + 4);
5262
6507
  layers.push({
5263
6508
  type: "shape",
5264
6509
  id: `mark-${s}-${i}`,
@@ -5285,14 +6530,14 @@ function paintBars(paint) {
5285
6530
  return layers;
5286
6531
  }
5287
6532
  function paintRadial(paint) {
5288
- const { kind, frame, data, colors, input, entrance, mark, labelAnimation, context } = paint;
6533
+ const { kind, frame: frame2, data, colors, input, entrance: entrance2, mark, labelAnimation, context } = paint;
5289
6534
  const slices = data.labels.map((label, index) => ({ label, value: data.series[0].values[index], index })).filter(
5290
6535
  (s) => s.value !== null && Number.isFinite(s.value) && s.value > 0
5291
6536
  );
5292
6537
  const total = slices.reduce((sum, s) => sum + s.value, 0);
5293
- const cx = frame.chartArea.x + frame.chartArea.width / 2;
5294
- const cy = frame.chartArea.y + frame.chartArea.height / 2;
5295
- const rOuter = Math.min(frame.chartArea.width, frame.chartArea.height) * 0.42;
6538
+ const cx = frame2.chartArea.x + frame2.chartArea.width / 2;
6539
+ const cy = frame2.chartArea.y + frame2.chartArea.height / 2;
6540
+ const rOuter = Math.min(frame2.chartArea.width, frame2.chartArea.height) * 0.42;
5296
6541
  const rInner = kind === "donut" ? rOuter * 0.55 : 0;
5297
6542
  const layers = [];
5298
6543
  let startDeg = 0;
@@ -5333,7 +6578,7 @@ function paintRadial(paint) {
5333
6578
  y: cy + rLabel * Math.sin(rad),
5334
6579
  anchor: "center"
5335
6580
  },
5336
- animation: labelAnimation(order, entrance(order))
6581
+ animation: labelAnimation(order, entrance2(order))
5337
6582
  });
5338
6583
  }
5339
6584
  startDeg += sweep;
@@ -5341,12 +6586,12 @@ function paintRadial(paint) {
5341
6586
  return layers;
5342
6587
  }
5343
6588
  function paintLines(paint) {
5344
- const { kind, frame, data, colors, input, mark, labelAnimation } = paint;
6589
+ const { kind, frame: frame2, data, colors, input, mark, labelAnimation } = paint;
5345
6590
  const scale = valueTicks(kind, data, input);
5346
- const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
6591
+ const yFor = (v) => frame2.plot.y + frame2.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
5347
6592
  const n = data.labels.length;
5348
- const band = frame.plot.width / Math.max(1, n);
5349
- const xForCenter = (i) => frame.plot.x + (i + 0.5) * band;
6593
+ const band = frame2.plot.width / Math.max(1, n);
6594
+ const xForCenter = (i) => frame2.plot.x + (i + 0.5) * band;
5350
6595
  const layers = paintValueAxisY(paint, scale, yFor);
5351
6596
  layers.push(...paintCategoryAxisX(paint, xForCenter));
5352
6597
  const baselineY = yFor(Math.max(scale.niceMin, Math.min(scale.niceMax, 0)));
@@ -5377,10 +6622,10 @@ function paintLines(paint) {
5377
6622
  strokeWidth: 0
5378
6623
  },
5379
6624
  position: {
5380
- x: frame.plot.x,
5381
- y: frame.plot.y,
5382
- width: frame.plot.width,
5383
- height: frame.plot.height
6625
+ x: frame2.plot.x,
6626
+ y: frame2.plot.y,
6627
+ width: frame2.plot.width,
6628
+ height: frame2.plot.height
5384
6629
  },
5385
6630
  animation: mark(s, "area")
5386
6631
  });
@@ -5391,10 +6636,10 @@ function paintLines(paint) {
5391
6636
  id: `line-${s}-${seg}`,
5392
6637
  content: { d: polylinePath(segment), stroke: colors[s], strokeWidth: 4, fill: "none" },
5393
6638
  position: {
5394
- x: frame.plot.x,
5395
- y: frame.plot.y,
5396
- width: frame.plot.width,
5397
- height: frame.plot.height
6639
+ x: frame2.plot.x,
6640
+ y: frame2.plot.y,
6641
+ width: frame2.plot.width,
6642
+ height: frame2.plot.height
5398
6643
  },
5399
6644
  animation: mark(s, "line")
5400
6645
  });
@@ -5429,9 +6674,9 @@ function paintLines(paint) {
5429
6674
  return layers;
5430
6675
  }
5431
6676
  function paintScatter(paint) {
5432
- const { frame, data, colors, input, mark } = paint;
6677
+ const { frame: frame2, data, colors, input, mark } = paint;
5433
6678
  const scale = valueTicks("scatter", data, input);
5434
- const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
6679
+ const yFor = (v) => frame2.plot.y + frame2.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
5435
6680
  const layers = paintValueAxisY(paint, scale, yFor);
5436
6681
  const numericCount = data.labelNumbers.filter((v) => v !== null).length;
5437
6682
  const numericX = data.labels.length > 0 && numericCount / data.labels.length >= 0.6;
@@ -5442,15 +6687,15 @@ function paintScatter(paint) {
5442
6687
  const span = xScale.niceMax - xScale.niceMin;
5443
6688
  xFor = (i) => {
5444
6689
  const v = data.labelNumbers[i];
5445
- return v === null ? null : frame.plot.x + frame.plot.width * ((v - xScale.niceMin) / span);
6690
+ return v === null ? null : frame2.plot.x + frame2.plot.width * ((v - xScale.niceMin) / span);
5446
6691
  };
5447
6692
  xScale.ticks.forEach((tick, index) => {
5448
- const x = frame.plot.x + frame.plot.width * ((tick - xScale.niceMin) / span);
6693
+ const x = frame2.plot.x + frame2.plot.width * ((tick - xScale.niceMin) / span);
5449
6694
  layers.push({
5450
6695
  type: "shape",
5451
6696
  id: `xgrid-${index}`,
5452
6697
  content: { shape: "rect", fill: paint.gridColor },
5453
- position: { x: x - 0.5, y: frame.plot.y, width: 1, height: frame.plot.height }
6698
+ position: { x: x - 0.5, y: frame2.plot.y, width: 1, height: frame2.plot.height }
5454
6699
  });
5455
6700
  layers.push({
5456
6701
  type: "text",
@@ -5466,15 +6711,15 @@ function paintScatter(paint) {
5466
6711
  },
5467
6712
  position: {
5468
6713
  x,
5469
- y: frame.plot.y + frame.plot.height + paint.axisFontSize * 1.2,
6714
+ y: frame2.plot.y + frame2.plot.height + paint.axisFontSize * 1.2,
5470
6715
  anchor: "center"
5471
6716
  }
5472
6717
  });
5473
6718
  });
5474
6719
  } else {
5475
- const band = frame.plot.width / Math.max(1, data.labels.length);
5476
- xFor = (i) => frame.plot.x + (i + 0.5) * band;
5477
- layers.push(...paintCategoryAxisX(paint, (i) => frame.plot.x + (i + 0.5) * band));
6720
+ const band = frame2.plot.width / Math.max(1, data.labels.length);
6721
+ xFor = (i) => frame2.plot.x + (i + 0.5) * band;
6722
+ layers.push(...paintCategoryAxisX(paint, (i) => frame2.plot.x + (i + 0.5) * band));
5478
6723
  }
5479
6724
  const r = 7;
5480
6725
  data.series.forEach((series, s) => {
@@ -5529,7 +6774,7 @@ function valueLabel(paint, id, value, x, y, animation) {
5529
6774
  type: "text",
5530
6775
  id,
5531
6776
  content: {
5532
- text: formatChartValue(value, paint.input.unit),
6777
+ text: formatChartDataValue(value, paint.input.unit),
5533
6778
  style: {
5534
6779
  fontSize: paint.axisFontSize,
5535
6780
  fontFamily: paint.bodyFont,
@@ -5598,19 +6843,22 @@ var TITLE_VARIANTS = {
5598
6843
  wide: "title-stack",
5599
6844
  tall: "split",
5600
6845
  square: "title-stack",
5601
- multiple: "title-stack"
6846
+ multiple: "title-stack",
6847
+ table: "title-stack"
5602
6848
  },
5603
6849
  square: {
5604
6850
  wide: "title-stack",
5605
6851
  tall: "split",
5606
6852
  square: "title-stack",
5607
- multiple: "title-stack"
6853
+ multiple: "title-stack",
6854
+ table: "title-stack"
5608
6855
  },
5609
6856
  portrait: {
5610
6857
  wide: "title-stack",
5611
6858
  tall: "title-stack",
5612
6859
  square: "title-stack",
5613
- multiple: "title-stack"
6860
+ multiple: "title-stack",
6861
+ table: "title-stack"
5614
6862
  }
5615
6863
  };
5616
6864
  var TEXT_VARIANTS = {
@@ -5618,19 +6866,22 @@ var TEXT_VARIANTS = {
5618
6866
  wide: "split",
5619
6867
  tall: "split",
5620
6868
  square: "split",
5621
- multiple: "split"
6869
+ multiple: "split",
6870
+ table: "text-stack"
5622
6871
  },
5623
6872
  square: {
5624
6873
  wide: "text-stack",
5625
6874
  tall: "split",
5626
6875
  square: "text-stack",
5627
- multiple: "text-stack"
6876
+ multiple: "text-stack",
6877
+ table: "text-stack"
5628
6878
  },
5629
6879
  portrait: {
5630
6880
  wide: "text-stack",
5631
6881
  tall: "text-stack",
5632
6882
  square: "text-stack",
5633
- multiple: "text-stack"
6883
+ multiple: "text-stack",
6884
+ table: "text-stack"
5634
6885
  }
5635
6886
  };
5636
6887
  var DENSE_VARIANTS = {
@@ -5638,19 +6889,22 @@ var DENSE_VARIANTS = {
5638
6889
  wide: "dense-stack",
5639
6890
  tall: "dense-stack",
5640
6891
  square: "dense-stack",
5641
- multiple: "dense-stack"
6892
+ multiple: "dense-stack",
6893
+ table: "dense-stack"
5642
6894
  },
5643
6895
  square: {
5644
6896
  wide: "dense-stack",
5645
6897
  tall: "dense-stack",
5646
6898
  square: "dense-stack",
5647
- multiple: "dense-stack"
6899
+ multiple: "dense-stack",
6900
+ table: "dense-stack"
5648
6901
  },
5649
6902
  portrait: {
5650
6903
  wide: "dense-stack",
5651
6904
  tall: "dense-stack",
5652
6905
  square: "dense-stack",
5653
- multiple: "dense-stack"
6906
+ multiple: "dense-stack",
6907
+ table: "dense-stack"
5654
6908
  }
5655
6909
  };
5656
6910
  var BLOCK_MEDIA_LAYOUT_POLICIES = {
@@ -5810,6 +7064,34 @@ var BLOCK_MEDIA_LAYOUT_POLICIES = {
5810
7064
  unconsumedMedia: "reserve-when-no-native-media",
5811
7065
  variants: TEXT_VARIANTS
5812
7066
  },
7067
+ cards: {
7068
+ summary: "Text-only cards by default; supplemental media is paired with the cards.",
7069
+ noMedia: "template-default",
7070
+ ownership: "supplemental",
7071
+ unconsumedMedia: "reserved-slot",
7072
+ variants: DENSE_VARIANTS
7073
+ },
7074
+ process: {
7075
+ summary: "A drawn process by default; supplemental media is paired with the stages.",
7076
+ noMedia: "template-default",
7077
+ ownership: "supplemental",
7078
+ unconsumedMedia: "reserved-slot",
7079
+ variants: DENSE_VARIANTS
7080
+ },
7081
+ cycle: {
7082
+ summary: "A drawn cycle by default; supplemental media is paired with the ring.",
7083
+ noMedia: "template-default",
7084
+ ownership: "supplemental",
7085
+ unconsumedMedia: "reserved-slot",
7086
+ variants: TEXT_VARIANTS
7087
+ },
7088
+ layers: {
7089
+ summary: "Drawn concentric layers by default; supplemental media is paired with the rings.",
7090
+ noMedia: "template-default",
7091
+ ownership: "supplemental",
7092
+ unconsumedMedia: "reserved-slot",
7093
+ variants: TEXT_VARIANTS
7094
+ },
5813
7095
  photoGrid: {
5814
7096
  summary: "Requires a primary image collection and owns the complete tiled media field.",
5815
7097
  noMedia: "unsupported",
@@ -5963,12 +7245,12 @@ function getBlockMediaLayoutPolicy(template) {
5963
7245
  // src/doc/richMediaLayout.ts
5964
7246
  function containedFeatureArea(region, aspects) {
5965
7247
  const count = aspects.length;
5966
- const columns = Math.min(
7248
+ const columns2 = Math.min(
5967
7249
  count,
5968
7250
  Math.max(1, Math.ceil(Math.sqrt(count * region.width / region.height)))
5969
7251
  );
5970
- const rows = Math.ceil(count / columns);
5971
- const width = region.width / columns;
7252
+ const rows = Math.ceil(count / columns2);
7253
+ const width = region.width / columns2;
5972
7254
  const height = region.height / rows;
5973
7255
  return aspects.reduce((area, aspect) => {
5974
7256
  const ratio = aspect && Number.isFinite(aspect) && aspect > 0 ? aspect : 16 / 9;
@@ -6056,7 +7338,8 @@ function rect(viewport, x, y, width, height) {
6056
7338
  height: viewport.height * height
6057
7339
  };
6058
7340
  }
6059
- function mediaShape(mediaCount, aspectRatios) {
7341
+ function mediaShape(mediaCount, aspectRatios, table = false) {
7342
+ if (table) return "table";
6060
7343
  if (mediaCount > 1) return "multiple";
6061
7344
  const aspect = aspectRatios[0] ?? 16 / 9;
6062
7345
  if (aspect < 0.8) return "tall";
@@ -6122,6 +7405,7 @@ function overlayMediaRect(viewport, shape) {
6122
7405
  case "square":
6123
7406
  return rect(viewport, 0.49, 0.58, 0.44, 0.35);
6124
7407
  case "multiple":
7408
+ case "table":
6125
7409
  return rect(viewport, 0.07, 0.67, 0.86, 0.27);
6126
7410
  case "wide":
6127
7411
  return rect(viewport, 0.07, 0.71, 0.86, 0.23);
@@ -6134,6 +7418,7 @@ function overlayMediaRect(viewport, shape) {
6134
7418
  case "square":
6135
7419
  return rect(viewport, 0.55, 0.52, 0.4, 0.4);
6136
7420
  case "multiple":
7421
+ case "table":
6137
7422
  return rect(viewport, 0.08, 0.62, 0.84, 0.31);
6138
7423
  case "wide":
6139
7424
  return rect(viewport, 0.08, 0.68, 0.84, 0.27);
@@ -6145,6 +7430,7 @@ function overlayMediaRect(viewport, shape) {
6145
7430
  case "square":
6146
7431
  return rect(viewport, 0.62, 0.22, 0.34, 0.56);
6147
7432
  case "multiple":
7433
+ case "table":
6148
7434
  return rect(viewport, 0.52, 0.5, 0.44, 0.42);
6149
7435
  case "wide":
6150
7436
  return rect(viewport, 0.54, 0.58, 0.42, 0.34);
@@ -6481,14 +7767,14 @@ var MEDIA_BANDS = {
6481
7767
  "full-frame": "full"
6482
7768
  };
6483
7769
  var BLOCK_GAP_EM = 0.55;
6484
- function textLayer(layers, id) {
7770
+ function textLayer2(layers, id) {
6485
7771
  const layer = layers.find((candidate) => candidate.id === id);
6486
7772
  return layer?.type === "text" ? layer : void 0;
6487
7773
  }
6488
7774
  function mediaBandLayout(layers, viewport, band, aspect) {
6489
7775
  const backdrop = layers.filter((layer) => isFullViewportBackground(layer, viewport));
6490
- const title = textLayer(layers, "title");
6491
- const body = textLayer(layers, "body");
7776
+ const title = textLayer2(layers, "title");
7777
+ const body = textLayer2(layers, "body");
6492
7778
  if (band === "full" || !title && !body) {
6493
7779
  return { layers: backdrop, mediaRect: rect(viewport, 0, 0, 1, 1), framed: false };
6494
7780
  }
@@ -6604,7 +7890,7 @@ function mediaBandLayout(layers, viewport, band, aspect) {
6604
7890
  }
6605
7891
  function resolveSupplementalMediaLayout(layers, template, viewport, mediaCount, aspectRatios, featureOptions = {}) {
6606
7892
  const policy = getBlockMediaLayoutPolicy(template);
6607
- const shape = mediaShape(mediaCount, aspectRatios);
7893
+ const shape = mediaShape(mediaCount, aspectRatios, featureOptions.table);
6608
7894
  const alreadyOwnsMedia = layers.some(
6609
7895
  (layer) => layer.type === "image" || layer.type === "video" || layer.type === "videoEmbed" || layer.type === "map" || layer.type === "mermaid" || layer.type === "svg"
6610
7896
  );
@@ -6836,14 +8122,14 @@ function planCanvasFrame(template, title, context) {
6836
8122
  ...mediaRect ? { mediaRect } : {}
6837
8123
  };
6838
8124
  }
6839
- function placeCanvasGroup(frame, contentW, contentH) {
6840
- const scale = Math.min(frame.availW / contentW, frame.availH / contentH, CANVAS_MAX_SCALE);
8125
+ function placeCanvasGroup(frame2, contentW, contentH) {
8126
+ const scale = Math.min(frame2.availW / contentW, frame2.availH / contentH, CANVAS_MAX_SCALE);
6841
8127
  const scaledW = contentW * scale;
6842
8128
  const scaledH = contentH * scale;
6843
- const groupH = frame.titleBlockPx + scaledH + frame.descriptionBlockPx;
6844
- const groupTop = frame.region.y + Math.max(PADDING / 2, (frame.region.height - groupH) / 2);
6845
- const offsetX = frame.region.x + PADDING + (frame.availW - scaledW) / 2;
6846
- const offsetY = groupTop + frame.titleBlockPx;
8129
+ const groupH = frame2.titleBlockPx + scaledH + frame2.descriptionBlockPx;
8130
+ const groupTop = frame2.region.y + Math.max(PADDING / 2, (frame2.region.height - groupH) / 2);
8131
+ const offsetX = frame2.region.x + PADDING + (frame2.availW - scaledW) / 2;
8132
+ const offsetY = groupTop + frame2.titleBlockPx;
6847
8133
  return {
6848
8134
  scale,
6849
8135
  scaledW,
@@ -6854,8 +8140,8 @@ function placeCanvasGroup(frame, contentW, contentH) {
6854
8140
  descriptionY: offsetY + scaledH + CANVAS_DESCRIPTION_GAP_PX
6855
8141
  };
6856
8142
  }
6857
- function canvasTitleLayer(id, text, frame, placement, context) {
6858
- const fit = frame.title;
8143
+ function canvasTitleLayer(id, text, frame2, placement, context) {
8144
+ const fit = frame2.title;
6859
8145
  if (!fit) return void 0;
6860
8146
  return {
6861
8147
  type: "text",
@@ -6874,15 +8160,15 @@ function canvasTitleLayer(id, text, frame, placement, context) {
6874
8160
  }
6875
8161
  },
6876
8162
  position: {
6877
- x: frame.region.x + PADDING,
8163
+ x: frame2.region.x + PADDING,
6878
8164
  y: placement.groupTop,
6879
8165
  width: fit.widthPx,
6880
8166
  anchor: "top-left"
6881
8167
  }
6882
8168
  };
6883
8169
  }
6884
- function canvasDescriptionLayer(id, frame, placement, context) {
6885
- const description = frame.description;
8170
+ function canvasDescriptionLayer(id, frame2, placement, context) {
8171
+ const description = frame2.description;
6886
8172
  if (!description) return void 0;
6887
8173
  const { fit, widthPx } = description;
6888
8174
  return {
@@ -6900,7 +8186,7 @@ function canvasDescriptionLayer(id, frame, placement, context) {
6900
8186
  }
6901
8187
  },
6902
8188
  position: {
6903
- x: frame.region.x + (frame.region.width - widthPx) / 2,
8189
+ x: frame2.region.x + (frame2.region.width - widthPx) / 2,
6904
8190
  y: placement.descriptionY,
6905
8191
  width: widthPx,
6906
8192
  anchor: "top-left"
@@ -7009,10 +8295,10 @@ function diagramBlock(input, context) {
7009
8295
  const maxX = Math.max(...resolved.nodes.map((n) => n.x + n.w));
7010
8296
  const minY = Math.min(...resolved.nodes.map((n) => n.y));
7011
8297
  const maxY = Math.max(...resolved.nodes.map((n) => n.y + n.h));
7012
- const frame = planCanvasFrame("diagram", input.title, context);
8298
+ const frame2 = planCanvasFrame("diagram", input.title, context);
7013
8299
  const contentW = Math.max(1, maxX - minX);
7014
8300
  const contentH = Math.max(1, maxY - minY);
7015
- const placement = placeCanvasGroup(frame, contentW, contentH);
8301
+ const placement = placeCanvasGroup(frame2, contentW, contentH);
7016
8302
  const { scale, offsetX, offsetY } = placement;
7017
8303
  const fontAdj = Math.min(Math.max(scale, 1), 1.5);
7018
8304
  const strokeW = Math.round(2 * fontAdj);
@@ -7024,7 +8310,7 @@ function diagramBlock(input, context) {
7024
8310
  });
7025
8311
  const layers = [...background];
7026
8312
  if (input.title) {
7027
- const titleLayer = canvasTitleLayer("diagram-title", input.title, frame, placement, context);
8313
+ const titleLayer = canvasTitleLayer("diagram-title", input.title, frame2, placement, context);
7028
8314
  if (titleLayer) layers.push(titleLayer);
7029
8315
  }
7030
8316
  const containers = resolved.nodes.filter((n) => n.isContainer).sort((a, b) => a.depth - b.depth || a.y - b.y || a.x - b.x);
@@ -7188,7 +8474,7 @@ function diagramBlock(input, context) {
7188
8474
  };
7189
8475
  layers.push(label);
7190
8476
  }
7191
- const descriptionLayer = canvasDescriptionLayer("diagram-description", frame, placement, context);
8477
+ const descriptionLayer = canvasDescriptionLayer("diagram-description", frame2, placement, context);
7192
8478
  if (descriptionLayer) layers.push(descriptionLayer);
7193
8479
  return layers;
7194
8480
  }
@@ -7446,8 +8732,8 @@ function timelineBlock(input, context) {
7446
8732
  const axisWidth = Math.max(1, axisEndX - axisStartX);
7447
8733
  const markerRadius = Math.max(6, Math.min(11, Math.min(viewport.width, viewport.height) / 105));
7448
8734
  const trackColor = withAlpha(colors.text ?? theme.colors.primary, 0.72);
7449
- const accentColor = colors.accent ?? theme.colors.primary;
7450
- const branchColor = withAlpha(accentColor, 0.78);
8735
+ const accentColor2 = colors.accent ?? theme.colors.primary;
8736
+ const branchColor = withAlpha(accentColor2, 0.78);
7451
8737
  const calloutColor = withAlpha(theme.colors.text, 0.48);
7452
8738
  const maxCalloutEvents = Math.max(
7453
8739
  1,
@@ -7679,7 +8965,7 @@ function timelineBlock(input, context) {
7679
8965
  x,
7680
8966
  y,
7681
8967
  pointMarkerRadius,
7682
- accentColor,
8968
+ accentColor2,
7683
8969
  delay,
7684
8970
  build
7685
8971
  )
@@ -7690,8 +8976,8 @@ function timelineBlock(input, context) {
7690
8976
  id: `timeline-marker-${eventLayerId}`,
7691
8977
  content: {
7692
8978
  shape: "circle",
7693
- fill: marker === "hollow" ? theme.colors.background : accentColor,
7694
- stroke: accentColor,
8979
+ fill: marker === "hollow" ? theme.colors.background : accentColor2,
8980
+ stroke: accentColor2,
7695
8981
  strokeWidth: Math.max(1.5, pointMarkerRadius * 0.28)
7696
8982
  },
7697
8983
  position: {
@@ -8110,17 +9396,17 @@ function drawingBlock(input, context) {
8110
9396
  const maxX = Math.max(...layout.shapes.map((s) => s.x + s.width));
8111
9397
  const minY = Math.min(...layout.shapes.map((s) => s.y));
8112
9398
  const maxY = Math.max(...layout.shapes.map((s) => s.y + s.height));
8113
- const frame = planCanvasFrame("drawing", input.title, context);
9399
+ const frame2 = planCanvasFrame("drawing", input.title, context);
8114
9400
  const contentW = Math.max(1, maxX - minX);
8115
9401
  const contentH = Math.max(1, maxY - minY);
8116
- const placement = placeCanvasGroup(frame, contentW, contentH);
9402
+ const placement = placeCanvasGroup(frame2, contentW, contentH);
8117
9403
  const { scale, offsetX, offsetY } = placement;
8118
9404
  const tx = (x) => offsetX + (x - minX) * scale;
8119
9405
  const ty = (y) => offsetY + (y - minY) * scale;
8120
9406
  const fontAdj = Math.min(Math.max(scale, 1), 1.5);
8121
9407
  const layers = [];
8122
9408
  if (input.title) {
8123
- const titleLayer = canvasTitleLayer("drawing-title", input.title, frame, placement, context);
9409
+ const titleLayer = canvasTitleLayer("drawing-title", input.title, frame2, placement, context);
8124
9410
  if (titleLayer) layers.push(titleLayer);
8125
9411
  }
8126
9412
  const boxes = /* @__PURE__ */ new Map();
@@ -8165,7 +9451,7 @@ function drawingBlock(input, context) {
8165
9451
  const h = s.height * scale;
8166
9452
  layers.push(...shapeLayers(s, x, y, w, h, context, defaultStroke, defaultFill, fontAdj));
8167
9453
  }
8168
- const descriptionLayer = canvasDescriptionLayer("drawing-description", frame, placement, context);
9454
+ const descriptionLayer = canvasDescriptionLayer("drawing-description", frame2, placement, context);
8169
9455
  if (descriptionLayer) layers.push(descriptionLayer);
8170
9456
  return layers;
8171
9457
  }
@@ -8175,7 +9461,7 @@ function shapeLayers(s, x, y, w, h, context, defaultStroke, defaultFill, fontAdj
8175
9461
  const fill = s.fill ?? defaultFill;
8176
9462
  if (s.kind === "text") {
8177
9463
  out.push(
8178
- textLayer2(
9464
+ textLayer3(
8179
9465
  `shape-${s.id}`,
8180
9466
  s.text ?? s.label ?? "",
8181
9467
  x + w / 2,
@@ -8234,7 +9520,7 @@ function shapeLayers(s, x, y, w, h, context, defaultStroke, defaultFill, fontAdj
8234
9520
  }
8235
9521
  if (s.label) {
8236
9522
  out.push(
8237
- textLayer2(
9523
+ textLayer3(
8238
9524
  `shape-label-${s.id}`,
8239
9525
  s.label,
8240
9526
  x + w / 2,
@@ -8249,7 +9535,7 @@ function shapeLayers(s, x, y, w, h, context, defaultStroke, defaultFill, fontAdj
8249
9535
  }
8250
9536
  if (s.sublabel) {
8251
9537
  out.push(
8252
- textLayer2(
9538
+ textLayer3(
8253
9539
  `shape-sublabel-${s.id}`,
8254
9540
  s.sublabel,
8255
9541
  x + w / 2,
@@ -8264,7 +9550,7 @@ function shapeLayers(s, x, y, w, h, context, defaultStroke, defaultFill, fontAdj
8264
9550
  }
8265
9551
  return out;
8266
9552
  }
8267
- function textLayer2(id, text, cx, cy, width, context, color, bold, fontAdj) {
9553
+ function textLayer3(id, text, cx, cy, width, context, color, bold, fontAdj) {
8268
9554
  return {
8269
9555
  type: "text",
8270
9556
  id,
@@ -8815,6 +10101,10 @@ var templateRegistry = {
8815
10101
  map: mapBlock,
8816
10102
  fullBleedQuote,
8817
10103
  list: listBlock,
10104
+ cards: cardsBlock,
10105
+ process: processBlock,
10106
+ cycle: cycleBlock,
10107
+ layers: layersBlock,
8818
10108
  photoGrid,
8819
10109
  definitionCard,
8820
10110
  comparisonBar,
@@ -9441,6 +10731,278 @@ function extractBodyText(block) {
9441
10731
  return `${text.slice(0, MAX_BODY_CHARS).trimEnd()}\u2026`;
9442
10732
  }
9443
10733
 
10734
+ // src/doc/structuredData.ts
10735
+ var DATA_FENCE_LANGS = /* @__PURE__ */ new Set(["json", "yaml", "yml"]);
10736
+ function isDataFence(node) {
10737
+ if (!node.lang || !DATA_FENCE_LANGS.has(node.lang.toLowerCase())) return false;
10738
+ const meta = node.meta ?? "";
10739
+ return /\bdata\b/.test(meta);
10740
+ }
10741
+ function parseDataFence(node) {
10742
+ const lang = node.lang?.toLowerCase();
10743
+ if (lang === "json") {
10744
+ try {
10745
+ const parsed = JSON.parse(node.value);
10746
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
10747
+ return { error: 'JSON data fence must contain a top-level object ({"key": \u2026})' };
10748
+ }
10749
+ return { data: parsed };
10750
+ } catch (err) {
10751
+ return { error: `invalid JSON: ${err instanceof Error ? err.message : String(err)}` };
10752
+ }
10753
+ }
10754
+ try {
10755
+ return { data: parseYamlSubset(node.value) };
10756
+ } catch (err) {
10757
+ return { error: `invalid YAML: ${err instanceof Error ? err.message : String(err)}` };
10758
+ }
10759
+ }
10760
+ function replaceDataFence(contents, data) {
10761
+ const value = JSON.stringify(data, null, 2);
10762
+ const index = contents.findIndex((node) => node.type === "code" && isDataFence(node));
10763
+ if (index < 0) {
10764
+ const fence = { type: "code", lang: "json", meta: "data", value };
10765
+ return [...contents, fence];
10766
+ }
10767
+ const next = [...contents];
10768
+ next[index] = { ...contents[index], value };
10769
+ return next;
10770
+ }
10771
+ function parseScalar(raw) {
10772
+ const trimmed = raw.trim();
10773
+ if (trimmed.length >= 2) {
10774
+ const q = trimmed[0];
10775
+ if ((q === '"' || q === "'") && trimmed.endsWith(q)) {
10776
+ return trimmed.slice(1, -1);
10777
+ }
10778
+ }
10779
+ if (trimmed === "true") return true;
10780
+ if (trimmed === "false") return false;
10781
+ if (trimmed === "null" || trimmed === "~") return null;
10782
+ if (trimmed !== "" && !isNaN(Number(trimmed))) return Number(trimmed);
10783
+ return trimmed;
10784
+ }
10785
+ function splitInlineArray(interior) {
10786
+ const items = [];
10787
+ let current = "";
10788
+ let quote = null;
10789
+ for (const ch of interior) {
10790
+ if (quote) {
10791
+ current += ch;
10792
+ if (ch === quote) quote = null;
10793
+ } else if (ch === '"' || ch === "'") {
10794
+ quote = ch;
10795
+ current += ch;
10796
+ } else if (ch === ",") {
10797
+ items.push(current);
10798
+ current = "";
10799
+ } else {
10800
+ current += ch;
10801
+ }
10802
+ }
10803
+ items.push(current);
10804
+ return items.map((s) => s.trim()).filter((s) => s !== "");
10805
+ }
10806
+ function parseInlineValue(raw) {
10807
+ const trimmed = raw.trim();
10808
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
10809
+ return splitInlineArray(trimmed.slice(1, -1)).map(parseScalar);
10810
+ }
10811
+ return parseScalar(trimmed);
10812
+ }
10813
+ function parseYamlSubset(src) {
10814
+ const result = {};
10815
+ const lines = src.split("\n");
10816
+ let pendingKey = null;
10817
+ let pendingKind = null;
10818
+ let childIndent = -1;
10819
+ for (let i = 0; i < lines.length; i++) {
10820
+ const line = lines[i];
10821
+ const trimmed = line.trim();
10822
+ if (!trimmed || trimmed.startsWith("#")) continue;
10823
+ const indent = line.length - line.trimStart().length;
10824
+ if (indent > 0) {
10825
+ if (!pendingKey) {
10826
+ throw new Error(`line ${i + 1}: unexpected indented line without a preceding "key:" line`);
10827
+ }
10828
+ if (childIndent < 0) childIndent = indent;
10829
+ if (indent > childIndent) {
10830
+ throw new Error(
10831
+ `line ${i + 1}: nesting deeper than one level is not supported \u2014 use a \`\`\`json data fence for nested input`
10832
+ );
10833
+ }
10834
+ if (trimmed.startsWith("- ")) {
10835
+ if (pendingKind === "map") {
10836
+ throw new Error(
10837
+ `line ${i + 1}: cannot mix list items and mapping keys under "${pendingKey}"`
10838
+ );
10839
+ }
10840
+ pendingKind = "list";
10841
+ result[pendingKey].push(parseInlineValue(trimmed.slice(2)));
10842
+ continue;
10843
+ }
10844
+ if (pendingKind === "list") {
10845
+ throw new Error(
10846
+ `line ${i + 1}: cannot mix mapping keys and list items under "${pendingKey}"`
10847
+ );
10848
+ }
10849
+ const subColon = trimmed.indexOf(":");
10850
+ if (subColon < 1) {
10851
+ throw new Error(`line ${i + 1}: expected "key: value"`);
10852
+ }
10853
+ const subValue = trimmed.slice(subColon + 1).trim();
10854
+ if (subValue === "") {
10855
+ throw new Error(
10856
+ `line ${i + 1}: nesting deeper than one level is not supported \u2014 use a \`\`\`json data fence for nested input`
10857
+ );
10858
+ }
10859
+ if (pendingKind !== "map") {
10860
+ pendingKind = "map";
10861
+ result[pendingKey] = {};
10862
+ }
10863
+ result[pendingKey][trimmed.slice(0, subColon).trim()] = parseInlineValue(subValue);
10864
+ continue;
10865
+ }
10866
+ pendingKey = null;
10867
+ pendingKind = null;
10868
+ childIndent = -1;
10869
+ const colonIdx = trimmed.indexOf(":");
10870
+ if (colonIdx < 1) {
10871
+ throw new Error(`line ${i + 1}: expected "key: value"`);
10872
+ }
10873
+ const key = trimmed.slice(0, colonIdx).trim();
10874
+ const rawValue = trimmed.slice(colonIdx + 1).trim();
10875
+ if (rawValue === "") {
10876
+ result[key] = [];
10877
+ pendingKey = key;
10878
+ } else {
10879
+ result[key] = parseInlineValue(rawValue);
10880
+ }
10881
+ }
10882
+ return result;
10883
+ }
10884
+ function findFirstTable(contents) {
10885
+ return contents?.find((n) => n.type === "table");
10886
+ }
10887
+ function extractTableData(table) {
10888
+ const toCells = (row) => row.children.map((cell) => extractPlainText(cell).trim());
10889
+ const [headerRow, ...bodyRows] = table.children;
10890
+ return {
10891
+ headers: headerRow ? toCells(headerRow) : [],
10892
+ rows: bodyRows.map(toCells),
10893
+ ...table.align && table.align.some((a) => a != null) ? { align: table.align } : {}
10894
+ };
10895
+ }
10896
+
10897
+ // src/doc/featureTable.ts
10898
+ var CHARACTER_EM = 0.55;
10899
+ var HEADER_CHARACTER_EM = 0.6;
10900
+ var CELL_SIDE_EM = 1.5;
10901
+ var LINE_EM = 1.2;
10902
+ var CELL_TOP_EM = 1.2;
10903
+ var SAFETY = 0.92;
10904
+ function rowsOf(table) {
10905
+ return [
10906
+ ...table.headers.length ? [{ cells: table.headers, em: HEADER_CHARACTER_EM }] : [],
10907
+ ...table.rows.map((cells) => ({ cells, em: CHARACTER_EM }))
10908
+ ];
10909
+ }
10910
+ function columns(table) {
10911
+ const count = Math.max(1, ...rowsOf(table).map((row) => row.cells.length));
10912
+ return Array.from(
10913
+ { length: count },
10914
+ (_, index) => rowsOf(table).reduce(
10915
+ (column, row) => {
10916
+ const lines = (row.cells[index] ?? "").split("\n");
10917
+ const words2 = lines.flatMap((line) => line.split(/\s+/u));
10918
+ return {
10919
+ word: Math.max(column.word, ...words2.map((word) => word.length * row.em)),
10920
+ line: Math.max(column.line, ...lines.map((line) => line.trim().length * row.em))
10921
+ };
10922
+ },
10923
+ { word: 0, line: 0 }
10924
+ )
10925
+ );
10926
+ }
10927
+ function columnWidths(cols, space) {
10928
+ const words2 = cols.reduce((sum, column) => sum + column.word, 0);
10929
+ const lines = cols.reduce((sum, column) => sum + column.line, 0);
10930
+ if (words2 > space) return null;
10931
+ if (lines <= space) return cols.map((column) => column.line);
10932
+ const share = (space - words2) / Math.max(1e-6, lines - words2);
10933
+ return cols.map((column) => column.word + (column.line - column.word) * share);
10934
+ }
10935
+ function tableHeight(table, widths) {
10936
+ return rowsOf(table).reduce((height, row) => {
10937
+ const lines = Math.max(
10938
+ 1,
10939
+ ...row.cells.map(
10940
+ (cell, index) => cell.split("\n").reduce(
10941
+ (sum, line) => sum + Math.max(1, Math.ceil(line.trim().length * row.em / (widths[index] || 1))),
10942
+ 0
10943
+ )
10944
+ )
10945
+ );
10946
+ return height + lines * LINE_EM + CELL_TOP_EM;
10947
+ }, 0);
10948
+ }
10949
+ function featureTableAspect(table) {
10950
+ const cols = columns(table);
10951
+ const widths = cols.map((column) => column.line);
10952
+ const width = widths.reduce((sum, value) => sum + value + CELL_SIDE_EM, 0);
10953
+ return width / Math.max(1, tableHeight(table, widths));
10954
+ }
10955
+ function fitFeatureTable(table, box2, max, min) {
10956
+ const cols = columns(table);
10957
+ const height = (size) => {
10958
+ const widths = columnWidths(cols, box2.width * SAFETY / size - cols.length * CELL_SIDE_EM);
10959
+ return widths ? tableHeight(table, widths) * size + 4 : null;
10960
+ };
10961
+ for (let size = Math.floor(max); size > min; size--) {
10962
+ const needed = height(size);
10963
+ if (needed !== null && needed <= box2.height * SAFETY)
10964
+ return { fontSize: size, height: Math.min(box2.height, needed / SAFETY) };
10965
+ }
10966
+ return { fontSize: min, height: box2.height };
10967
+ }
10968
+ function featureTableLayer(id, table, rect2, context) {
10969
+ const { theme, viewport } = context;
10970
+ const unit = Math.min(viewport.width, viewport.height);
10971
+ const margin = unit * 0.045;
10972
+ const left = Math.max(rect2.x, margin);
10973
+ const box2 = {
10974
+ x: left,
10975
+ y: rect2.y,
10976
+ width: Math.min(rect2.x + rect2.width, viewport.width - margin) - left,
10977
+ height: Math.min(rect2.y + rect2.height, viewport.height - margin) - rect2.y
10978
+ };
10979
+ const { fontSize, height } = fitFeatureTable(table, box2, unit * 0.03, Math.max(8, unit * 8e-3));
10980
+ return {
10981
+ type: "table",
10982
+ id,
10983
+ // As tall as its rows, centered in its box.
10984
+ position: { x: box2.x, y: box2.y + (box2.height - height) / 2, width: box2.width, height },
10985
+ content: {
10986
+ headers: table.headers,
10987
+ rows: table.rows,
10988
+ ...table.align ? { align: table.align } : {},
10989
+ wrap: true,
10990
+ style: {
10991
+ // The theme's own color: a summary slide has no color scheme to borrow.
10992
+ headerBackground: theme.colors.primary,
10993
+ headerColor: pickContrastingText(theme.colors.primary),
10994
+ cellBackground: withAlpha(theme.colors.text, 0.04),
10995
+ cellColor: theme.colors.text,
10996
+ borderColor: withAlpha(theme.colors.text, 0.15),
10997
+ fontSize,
10998
+ fontFamily: getThemeFont(context, "body"),
10999
+ headerFontFamily: getThemeFont(context, "title"),
11000
+ borderRadius: 8
11001
+ }
11002
+ }
11003
+ };
11004
+ }
11005
+
9444
11006
  // src/doc/materializeBlockLayers.ts
9445
11007
  function materializeBlockLayers(block, options = {}) {
9446
11008
  const registry = options.customTemplates && options.customTemplates.length > 0 ? buildRegistry(options.customTemplates) : templateRegistry;
@@ -9566,6 +11128,11 @@ function svgIslandAspectRatio(source) {
9566
11128
  const result = parseSvgIsland(source);
9567
11129
  return result.ok ? result.island.aspectRatio : 16 / 9;
9568
11130
  }
11131
+ function featureTables(layers, block) {
11132
+ if (!block.summaryMode || layers.some((layer) => layer.type === "table")) return [];
11133
+ if (block.template && TABLE_FED_TEMPLATES.has(resolveTemplateName(block.template))) return [];
11134
+ return (block.contents ?? []).filter((node) => node.type === "table").map(extractTableData).filter((table) => table.headers.length > 0 || table.rows.length > 0);
11135
+ }
9569
11136
  function embeddedSpatialMedia(block) {
9570
11137
  const canonicalTemplate = block.template ? resolveTemplateName(block.template) : void 0;
9571
11138
  const fences = (block.contents ?? []).filter(
@@ -9674,6 +11241,11 @@ function collectRichMediaItems(layers, block) {
9674
11241
  source,
9675
11242
  aspectRatio: svgIslandAspectRatio(source)
9676
11243
  })),
11244
+ ...featureTables(layers, block).map((table) => ({
11245
+ kind: "table",
11246
+ table,
11247
+ aspectRatio: featureTableAspect(table)
11248
+ })),
9677
11249
  ...embeddedSpatialMedia(block)
9678
11250
  ];
9679
11251
  }
@@ -9687,16 +11259,16 @@ function gridCells(region, count, viewport, inset) {
9687
11259
  width: Math.max(0, region.width - padding * 2),
9688
11260
  height: Math.max(0, region.height - padding * 2)
9689
11261
  };
9690
- const columns = Math.min(
11262
+ const columns2 = Math.min(
9691
11263
  count,
9692
11264
  Math.max(1, Math.ceil(Math.sqrt(count * (inner.width / Math.max(1, inner.height)))))
9693
11265
  );
9694
- const rows = Math.ceil(count / columns);
9695
- const width = (inner.width - gap * (columns - 1)) / columns;
11266
+ const rows = Math.ceil(count / columns2);
11267
+ const width = (inner.width - gap * (columns2 - 1)) / columns2;
9696
11268
  const height = (inner.height - gap * (rows - 1)) / rows;
9697
11269
  return Array.from({ length: count }, (_, index) => ({
9698
- x: inner.x + index % columns * (width + gap),
9699
- y: inner.y + Math.floor(index / columns) * (height + gap),
11270
+ x: inner.x + index % columns2 * (width + gap),
11271
+ y: inner.y + Math.floor(index / columns2) * (height + gap),
9700
11272
  width,
9701
11273
  height
9702
11274
  }));
@@ -9708,7 +11280,7 @@ function effectiveImageTreatment(theme, block) {
9708
11280
  if (!base || base.type === "none") return void 0;
9709
11281
  return base.type === "duotone" && !base.color ? { ...base, color: theme.colors.primary } : base;
9710
11282
  }
9711
- function richMediaLayers(item, position, block, theme, kindIndex) {
11283
+ function richMediaLayers(item, position, block, theme, viewport, kindIndex) {
9712
11284
  if (item.kind === "image") {
9713
11285
  const treatment = effectiveImageTreatment(theme, block);
9714
11286
  return [
@@ -9780,6 +11352,16 @@ function richMediaLayers(item, position, block, theme, kindIndex) {
9780
11352
  }
9781
11353
  ];
9782
11354
  }
11355
+ if (item.kind === "table") {
11356
+ return [
11357
+ featureTableLayer(
11358
+ `${block.id}-table-${kindIndex}`,
11359
+ item.table,
11360
+ position,
11361
+ createTemplateContext(theme, 0, 1, viewport)
11362
+ )
11363
+ ];
11364
+ }
9783
11365
  const localViewport = {
9784
11366
  width: position.width,
9785
11367
  height: position.height,
@@ -9833,7 +11415,7 @@ function appendRichContentLayers(layers, block, theme, viewport, mediaAspectRati
9833
11415
  const key = item.kind === "image" || item.kind === "video" ? item.src : item.kind === "mermaid" ? item.source : void 0;
9834
11416
  const intrinsic = key ? mediaAspectRatios?.[key] : void 0;
9835
11417
  if (intrinsic && Number.isFinite(intrinsic) && intrinsic > 0) return intrinsic;
9836
- return item.kind === "video" ? void 0 : item.aspectRatio;
11418
+ return item.kind === "video" || item.kind === "table" ? void 0 : item.aspectRatio;
9837
11419
  });
9838
11420
  const aspects = items.map((item, index) => knownAspects[index] ?? item.aspectRatio);
9839
11421
  const layout = block.summaryLayout === "feature" ? resolveFeatureSummaryLayout(timedLayers, viewport, aspects) : resolveSupplementalMediaLayout(
@@ -9845,7 +11427,8 @@ function appendRichContentLayers(layers, block, theme, viewport, mediaAspectRati
9845
11427
  {
9846
11428
  summaryFeature: block.summaryMode === "headings-and-features" && !timedLayers.some((layer) => layer.id === "feature-body"),
9847
11429
  stackColumns: createTemplateContext(theme, 0, 1, viewport).layout.stackColumns,
9848
- knownAspectRatios: knownAspects
11430
+ knownAspectRatios: knownAspects,
11431
+ table: items.some((item) => item.kind === "table")
9849
11432
  }
9850
11433
  );
9851
11434
  const cells = gridCells(layout.mediaRect, items.length, viewport, layout.framed);
@@ -9855,13 +11438,14 @@ function appendRichContentLayers(layers, block, theme, viewport, mediaAspectRati
9855
11438
  videoEmbed: 0,
9856
11439
  mermaid: 0,
9857
11440
  svg: 0,
11441
+ table: 0,
9858
11442
  spatial: 0
9859
11443
  };
9860
11444
  const mediaLayers = items.flatMap((item, index) => {
9861
11445
  counters[item.kind] += 1;
9862
- return richMediaLayers(item, cells[index], block, theme, counters[item.kind]);
11446
+ return richMediaLayers(item, cells[index], block, theme, viewport, counters[item.kind]);
9863
11447
  });
9864
- const frame = layout.framed ? {
11448
+ const frame2 = layout.framed ? {
9865
11449
  id: `${block.id}-rich-media-frame`,
9866
11450
  type: "shape",
9867
11451
  position: layout.mediaRect,
@@ -9873,7 +11457,7 @@ function appendRichContentLayers(layers, block, theme, viewport, mediaAspectRati
9873
11457
  borderRadius: Math.max(12, Math.round(Math.min(viewport.width, viewport.height) * 0.018))
9874
11458
  }
9875
11459
  } : void 0;
9876
- return [...layout.layers, ...frame ? [frame] : [], ...mediaLayers];
11460
+ return [...layout.layers, ...frame2 ? [frame2] : [], ...mediaLayers];
9877
11461
  }
9878
11462
  function executeTemplateMaterialization(templateBlock, context, registry) {
9879
11463
  const safeTemplateBlock = cloneData(templateBlock);
@@ -10031,6 +11615,22 @@ var TEMPLATE_METADATA = {
10031
11615
  label: "List",
10032
11616
  description: "Renders a bulleted or numbered list in a clean, card-style layout."
10033
11617
  },
11618
+ cards: {
11619
+ label: "Cards",
11620
+ description: "Shows two to six parallel ideas side by side as cards, each revealed in turn."
11621
+ },
11622
+ process: {
11623
+ label: "Process",
11624
+ description: "Shows an ordered process as numbered stages joined by arrows."
11625
+ },
11626
+ cycle: {
11627
+ label: "Cycle",
11628
+ description: "Arranges the stages of a cycle on a ring, the last leading back to the first."
11629
+ },
11630
+ layers: {
11631
+ label: "Layers",
11632
+ description: "Draws nested parts as concentric rings, innermost first, each one called out."
11633
+ },
10034
11634
  photoGrid: {
10035
11635
  label: "Photo Grid",
10036
11636
  description: "Arranges up to four photos in a mosaic grid."
@@ -10220,6 +11820,30 @@ var TEMPLATE_AUTHORING_METADATA = {
10220
11820
  safeForContentFirst: false,
10221
11821
  placement: "heading"
10222
11822
  },
11823
+ cards: {
11824
+ role: "content",
11825
+ bodyPolicy: "derived",
11826
+ safeForContentFirst: false,
11827
+ placement: "heading"
11828
+ },
11829
+ process: {
11830
+ role: "content",
11831
+ bodyPolicy: "derived",
11832
+ safeForContentFirst: false,
11833
+ placement: "heading"
11834
+ },
11835
+ cycle: {
11836
+ role: "content",
11837
+ bodyPolicy: "derived",
11838
+ safeForContentFirst: false,
11839
+ placement: "heading"
11840
+ },
11841
+ layers: {
11842
+ role: "content",
11843
+ bodyPolicy: "derived",
11844
+ safeForContentFirst: false,
11845
+ placement: "heading"
11846
+ },
10223
11847
  photoGrid: {
10224
11848
  role: "media",
10225
11849
  bodyPolicy: "structured",
@@ -10336,6 +11960,177 @@ var TEMPLATE_AUTHORING_METADATA = {
10336
11960
  }
10337
11961
  };
10338
11962
 
11963
+ // src/doc/templates/authoringGuide.ts
11964
+ var CHART = {
11965
+ fields: "title, headers [label, value], rows [[label, value], \u2026] 2\u20136 rows, showValues: true",
11966
+ use: "only values the {source} states"
11967
+ };
11968
+ var CONCEPT_BUILD = 'build: {mode: "items"} to reveal them as they are spoken';
11969
+ var TEMPLATE_GUIDES = {
11970
+ imageWithCaption: {
11971
+ fields: "imageSrc, imageAlt, caption \u2264 90 chars, ambientMotion: zoomIn|zoomOut|panLeft|panRight",
11972
+ use: "a photograph with the narration's idea as caption"
11973
+ },
11974
+ pullQuote: {
11975
+ fields: "text, backgroundImage {src, alt}, ambientMotion",
11976
+ use: "a VERBATIM sentence or clause from the {source} over a photograph"
11977
+ },
11978
+ fullBleedQuote: {
11979
+ fields: "text",
11980
+ use: "a VERBATIM short sentence from the {source}, text only"
11981
+ },
11982
+ factCard: {
11983
+ fields: "fact \u2264 4 words, explanation \u2264 160 chars, accentImage {src, alt, position: left-strip|right-strip}"
11984
+ },
11985
+ dateEvent: {
11986
+ fields: "date, description \u2264 160 chars, mood: neutral|somber|celebratory, accentImage optional",
11987
+ use: "a year or period the {source} states"
11988
+ },
11989
+ statHighlight: {
11990
+ fields: 'stat like "98%" or "36", description \u2264 60 chars, detail \u2264 80 chars, accentImage optional',
11991
+ use: "counts up; the number must be in the {source}"
11992
+ },
11993
+ comparisonBar: {
11994
+ fields: "leftLabel, leftValue, rightLabel, rightValue, unit optional",
11995
+ use: "two quantities the {source} gives in the same unit"
11996
+ },
11997
+ columnChart: CHART,
11998
+ barChart: CHART,
11999
+ pieChart: CHART,
12000
+ timeline: {
12001
+ fields: "title, tracks: [{ id, label, events: [{ id, label: year, position: 0..1 in date order, description \u2264 40 chars, marker: 'diamond' optional }] }]",
12002
+ use: "3\u20137 dated events the {source} states"
12003
+ },
12004
+ diagram: {
12005
+ fields: "title, nodeShape: 'pill', nodes: [{ id, label \u2264 3 words, x, y }] 3\u20136 nodes with x in {0, 280, 560} and y in {0, 160, 320}, edges: [{ source, target, label \u2264 3 words }]",
12006
+ use: "relationships the {source} describes"
12007
+ },
12008
+ list: {
12009
+ fields: "title, items: 2\u20135 short items taken from the {source}'s own words"
12010
+ },
12011
+ photoGrid: {
12012
+ fields: "images: [{src, alt}] \xD7 2\u20134, caption \u2264 90 chars",
12013
+ use: "several related photographs"
12014
+ },
12015
+ map: {
12016
+ fields: "center: {lat, lng}, zoom, mapStyle: 'terrain', title, ambientMotion",
12017
+ use: "where the place is, at its first mention"
12018
+ },
12019
+ sectionHeader: {
12020
+ fields: "title = the section title, imageSrc, imageAlt, ambientMotion",
12021
+ use: "opens a section on a photograph"
12022
+ },
12023
+ cards: {
12024
+ fields: `title, items: ["Label: detail", \u2026] 2\u20134, versus: true for exactly two set against each other, ${CONCEPT_BUILD}`,
12025
+ use: "parallel ideas side by side, such as kinds, causes or features"
12026
+ },
12027
+ process: {
12028
+ fields: `title, items: ["Label: detail", \u2026] 2\u20134 in order, ${CONCEPT_BUILD}`,
12029
+ use: "ordered stages or dated events, drawn as numbered steps joined by arrows"
12030
+ },
12031
+ cycle: {
12032
+ fields: `title, items: ["Label: detail", \u2026] 3\u20135, ${CONCEPT_BUILD}`,
12033
+ use: "stages that repeat in a loop, drawn on a ring"
12034
+ },
12035
+ layers: {
12036
+ fields: `title, items: ["Label: detail", \u2026] 3\u20135 innermost first, ${CONCEPT_BUILD}`,
12037
+ use: "parts nested inside one another, drawn as concentric rings"
12038
+ }
12039
+ };
12040
+ function guideText(text, wording = {}) {
12041
+ return text.replace(/\{source\}/gu, wording.source ?? "source");
12042
+ }
12043
+ function templateCatalog(entries, wording = {}) {
12044
+ return entries.map((entry) => {
12045
+ const [name, guide] = typeof entry === "string" ? [entry, TEMPLATE_GUIDES[entry]] : "templates" in entry ? [entry.templates.join(" | "), TEMPLATE_GUIDES[entry.templates[0]]] : [entry.template, entry];
12046
+ const use = guide.use ? ` \u2014 ${guide.use}` : "";
12047
+ return guideText(`- ${name} (${guide.fields})${use}`, wording);
12048
+ });
12049
+ }
12050
+ var PRESENTATION_LAYOUT_GUIDE = {
12051
+ title: "an opening",
12052
+ divider: "a new topic begins; only the headline shows",
12053
+ statement: "one idea",
12054
+ emphasis: "one striking takeaway; only the headline shows, so keep it under 60 characters",
12055
+ quote: "the headline is one sentence copied word for word from the passage, without quotation marks; only the headline shows",
12056
+ stat: "the passage\u2019s key figure, such as 40% or $3 million, stated in the headline or a point",
12057
+ definition: "the headline is a term and the points define it",
12058
+ list: "2\u20133 points",
12059
+ comparison: "exactly 2 genuinely contrasting points",
12060
+ steps: "2\u20133 ordered actions",
12061
+ image: 'only when the current layout is "image"; the headline is then its caption',
12062
+ cards: "2\u20134 parallel ideas side by side, such as kinds, causes or features",
12063
+ process: "2\u20134 ordered stages or dated events, drawn as numbered steps joined by arrows",
12064
+ cycle: "3\u20135 stages that repeat in a loop, drawn on a ring",
12065
+ layers: "3\u20135 parts nested inside one another, innermost first, such as a core and the layers around it, drawn as concentric rings"
12066
+ };
12067
+ var DRAWN = ["cards", "process", "cycle", "layers"];
12068
+ var NUMBERS = ["statHighlight", "comparisonBar", "columnChart", "barChart", "pieChart"];
12069
+ var ACCENTED = [
12070
+ "factCard",
12071
+ "statHighlight",
12072
+ "dateEvent",
12073
+ "quote",
12074
+ "list",
12075
+ "definitionCard"
12076
+ ];
12077
+ var AUTHORING_TRICKS = [
12078
+ {
12079
+ id: "one-idea",
12080
+ text: "One idea per slide: each slide shows what the sentence being spoken is about. Captions and labels restate its idea briefly; they never add a fact."
12081
+ },
12082
+ {
12083
+ id: "numbers-as-numbers",
12084
+ text: "Draw numbers as numbers: one striking quantity \u2192 statHighlight (it counts up); two quantities in one unit \u2192 comparisonBar; three or more values in one unit \u2192 a chart; three to seven dated events \u2192 timeline.",
12085
+ templates: [...NUMBERS, "timeline"]
12086
+ },
12087
+ {
12088
+ id: "ranges",
12089
+ text: 'A range ("3 to 7 miles deep") is not one value: never chart it as a single bar or count up to one end. Write it as the {source} gives it, on a card or in a list.',
12090
+ templates: NUMBERS
12091
+ },
12092
+ {
12093
+ id: "draw-ideas",
12094
+ text: `Slides that draw their ideas explain best: several parallel ideas \u2192 cards, ordered stages \u2192 process, a loop that comes back to its start \u2192 cycle, parts nested inside one another \u2192 layers; prefer them to a list. Write each item as "Label: detail", a label of 1\u20134 words and a detail of at most 70 characters, in the {source}'s own words. For dated events, start each label with its date: a process of dated stages draws as a timeline.`,
12095
+ summary: `Slides that draw their ideas explain best: when the passage has several parallel ideas, ordered stages, a repeating loop or nested parts, choose ${DRAWN.map((layout) => `"${layout}"`).join(", ")} rather than "statement" or "list", and write each of their points (up to 4) as "Label: detail", a label of 1\u20134 words and a detail of at most 70 characters. For dated events, start each label with its date.`,
12096
+ templates: DRAWN
12097
+ },
12098
+ {
12099
+ id: "reveal-as-spoken",
12100
+ text: 'A drawn slide can reveal its items one at a time as the narration says them (build {mode: "items"}). Put the items in the order they are spoken and let each share its key words with the sentence that says it, so every reveal lands on its words.',
12101
+ templates: DRAWN
12102
+ },
12103
+ {
12104
+ id: "chain-not-diagram",
12105
+ text: "A chain \u2014 one thing leads to the next, which leads to the next \u2014 is a process, not a diagram. Keep diagrams for relationships that branch or meet: causes on the left and effects on the right, every edge labelled with the verb the {source} uses, and no edges that cross.",
12106
+ templates: ["diagram", "process"]
12107
+ },
12108
+ {
12109
+ id: "no-empty-frames",
12110
+ text: "A text card on its own is small type on an empty field. Give each factCard, statHighlight, dateEvent, quote, list and definitionCard an accentImage showing what its sentence is about, and alternate the side from card to card.",
12111
+ templates: ACCENTED
12112
+ },
12113
+ {
12114
+ id: "motion-follows-picture",
12115
+ text: "Motion comes from the theme's motion profile; never hand-author animations. On a photograph, choose the ambientMotion the picture suggests \u2014 zoomIn toward a detail, panLeft or panRight along a skyline or coast \u2014 and alternate directions between neighbouring slides.",
12116
+ templates: ["imageWithCaption", "pullQuote", "sectionHeader", "map"]
12117
+ },
12118
+ {
12119
+ id: "vary",
12120
+ text: "Vary the slides: when two templates fit equally well, choose the one not just shown. A long run of photo-and-caption slides is where a narrated story feels static."
12121
+ }
12122
+ ];
12123
+ function authoringTricks(options = {}) {
12124
+ const offered = options.templates ? new Set(options.templates) : void 0;
12125
+ const selected = options.ids ? options.ids.flatMap((id) => AUTHORING_TRICKS.filter((trick) => trick.id === id)) : AUTHORING_TRICKS;
12126
+ return selected.filter(
12127
+ (trick) => !offered || !trick.templates || trick.templates.some((template) => offered.has(template))
12128
+ ).flatMap((trick) => {
12129
+ const text = options.surface === "summary" ? trick.summary : trick.text;
12130
+ return text ? [guideText(text, options.wording)] : [];
12131
+ });
12132
+ }
12133
+
10339
12134
  // src/doc/templates/coverBlock.ts
10340
12135
  var COVER_TITLE_BASE_PX = 120;
10341
12136
  var COVER_TITLE_COMFORTABLE_CHARS = 26;
@@ -10430,7 +12225,7 @@ function frostedCoverLayout(title, subtitle, context, coverLayout = "stacked") {
10430
12225
  };
10431
12226
  };
10432
12227
  if (coverLayout === "split" && subtitle?.trim()) {
10433
- const { fontSize: fontSize2, lines: lines2, textWidth: textWidth2, textHeight: textHeight2, padX: padX2, padY: padY2, radius: radius2, box: box2 } = fitTitle(FROSTED_SPLIT_TITLE_SCALE);
12228
+ const { fontSize: fontSize2, lines: lines2, textWidth: textWidth2, textHeight: textHeight2, padX: padX2, padY: padY2, radius: radius2, box: box3 } = fitTitle(FROSTED_SPLIT_TITLE_SCALE);
10434
12229
  const deckMin = themedFontSize(COVER_SUBTITLE_BASE_PX, context, false);
10435
12230
  const deck = fitFrostedText(
10436
12231
  subtitle.trim(),
@@ -10464,7 +12259,7 @@ function frostedCoverLayout(title, subtitle, context, coverLayout = "stacked") {
10464
12259
  fontSize: fontSize2,
10465
12260
  lines: lines2,
10466
12261
  plate: titlePlate,
10467
- title: box2(titlePlate.y),
12262
+ title: box3(titlePlate.y),
10468
12263
  subtitle: {
10469
12264
  cx,
10470
12265
  cy: deckPlate.y + deckHeight / 2,
@@ -10480,7 +12275,7 @@ function frostedCoverLayout(title, subtitle, context, coverLayout = "stacked") {
10480
12275
  };
10481
12276
  }
10482
12277
  }
10483
- const { fontSize, lines, column, textWidth, textHeight, padX, padY, radius, box } = fitTitle(1);
12278
+ const { fontSize, lines, column, textWidth, textHeight, padX, padY, radius, box: box2 } = fitTitle(1);
10484
12279
  let sub;
10485
12280
  if (subtitle?.trim()) {
10486
12281
  const subSize = themedFontSize(COVER_SUBTITLE_BASE_PX, context, false);
@@ -10509,7 +12304,7 @@ function frostedCoverLayout(title, subtitle, context, coverLayout = "stacked") {
10509
12304
  height: plateHeight,
10510
12305
  radius
10511
12306
  },
10512
- title: box(plateY),
12307
+ title: box2(plateY),
10513
12308
  ...sub ? {
10514
12309
  subtitle: {
10515
12310
  cx,
@@ -10948,9 +12743,9 @@ function expandDocBlocks(blocks, options = {}) {
10948
12743
  return expandedBlock;
10949
12744
  });
10950
12745
  }
10951
- const containsBuilds = blocks.some(hasBlockBuilds);
10952
- const splitLongBlocks = requestedSplit && !containsBuilds;
10953
- const mergeShortBlocks = requestedMerge && !containsBuilds;
12746
+ const buildSegments = new Set(
12747
+ blocks.filter(hasBlockBuilds).map((block) => block.audioSegment ?? 0)
12748
+ );
10954
12749
  const blocksBySegment = /* @__PURE__ */ new Map();
10955
12750
  blocks.forEach((block, index) => {
10956
12751
  const segmentIndex = block.audioSegment ?? 0;
@@ -10963,6 +12758,8 @@ function expandDocBlocks(blocks, options = {}) {
10963
12758
  const splitParts = /* @__PURE__ */ new Map();
10964
12759
  for (const [segmentIndex, segmentBlocks] of blocksBySegment) {
10965
12760
  const audioSegment = audioSegments[segmentIndex];
12761
+ const splitLongBlocks = requestedSplit && !buildSegments.has(segmentIndex);
12762
+ const mergeShortBlocks = requestedMerge && !buildSegments.has(segmentIndex);
10966
12763
  if (segmentBlocks.every(({ block }) => block.timelineLocked === true)) {
10967
12764
  for (const { block, originalIndex } of segmentBlocks) {
10968
12765
  const expanded = materializeScheduledBlock(
@@ -11179,7 +12976,9 @@ function expandDocBlocks(blocks, options = {}) {
11179
12976
  }
11180
12977
  expandedBlocks.splice(0, expandedBlocks.length, ...sequenced);
11181
12978
  }
11182
- return mergeShortBlocks ? expandedBlocks.filter((block) => block && block.duration > 0) : expandedBlocks.filter(Boolean);
12979
+ return requestedMerge ? expandedBlocks.filter(
12980
+ (block) => block && (block.duration > 0 || buildSegments.has(block.audioSegment ?? 0))
12981
+ ) : expandedBlocks.filter(Boolean);
11183
12982
  }
11184
12983
  function getAvailableTemplates() {
11185
12984
  return Object.keys(templateRegistry);
@@ -11188,169 +12987,6 @@ function hasTemplate(name) {
11188
12987
  return resolveTemplateName(name) in templateRegistry;
11189
12988
  }
11190
12989
 
11191
- // src/doc/structuredData.ts
11192
- var DATA_FENCE_LANGS = /* @__PURE__ */ new Set(["json", "yaml", "yml"]);
11193
- function isDataFence(node) {
11194
- if (!node.lang || !DATA_FENCE_LANGS.has(node.lang.toLowerCase())) return false;
11195
- const meta = node.meta ?? "";
11196
- return /\bdata\b/.test(meta);
11197
- }
11198
- function parseDataFence(node) {
11199
- const lang = node.lang?.toLowerCase();
11200
- if (lang === "json") {
11201
- try {
11202
- const parsed = JSON.parse(node.value);
11203
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
11204
- return { error: 'JSON data fence must contain a top-level object ({"key": \u2026})' };
11205
- }
11206
- return { data: parsed };
11207
- } catch (err) {
11208
- return { error: `invalid JSON: ${err instanceof Error ? err.message : String(err)}` };
11209
- }
11210
- }
11211
- try {
11212
- return { data: parseYamlSubset(node.value) };
11213
- } catch (err) {
11214
- return { error: `invalid YAML: ${err instanceof Error ? err.message : String(err)}` };
11215
- }
11216
- }
11217
- function replaceDataFence(contents, data) {
11218
- const value = JSON.stringify(data, null, 2);
11219
- const index = contents.findIndex((node) => node.type === "code" && isDataFence(node));
11220
- if (index < 0) {
11221
- const fence = { type: "code", lang: "json", meta: "data", value };
11222
- return [...contents, fence];
11223
- }
11224
- const next = [...contents];
11225
- next[index] = { ...contents[index], value };
11226
- return next;
11227
- }
11228
- function parseScalar(raw) {
11229
- const trimmed = raw.trim();
11230
- if (trimmed.length >= 2) {
11231
- const q = trimmed[0];
11232
- if ((q === '"' || q === "'") && trimmed.endsWith(q)) {
11233
- return trimmed.slice(1, -1);
11234
- }
11235
- }
11236
- if (trimmed === "true") return true;
11237
- if (trimmed === "false") return false;
11238
- if (trimmed === "null" || trimmed === "~") return null;
11239
- if (trimmed !== "" && !isNaN(Number(trimmed))) return Number(trimmed);
11240
- return trimmed;
11241
- }
11242
- function splitInlineArray(interior) {
11243
- const items = [];
11244
- let current = "";
11245
- let quote = null;
11246
- for (const ch of interior) {
11247
- if (quote) {
11248
- current += ch;
11249
- if (ch === quote) quote = null;
11250
- } else if (ch === '"' || ch === "'") {
11251
- quote = ch;
11252
- current += ch;
11253
- } else if (ch === ",") {
11254
- items.push(current);
11255
- current = "";
11256
- } else {
11257
- current += ch;
11258
- }
11259
- }
11260
- items.push(current);
11261
- return items.map((s) => s.trim()).filter((s) => s !== "");
11262
- }
11263
- function parseInlineValue(raw) {
11264
- const trimmed = raw.trim();
11265
- if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
11266
- return splitInlineArray(trimmed.slice(1, -1)).map(parseScalar);
11267
- }
11268
- return parseScalar(trimmed);
11269
- }
11270
- function parseYamlSubset(src) {
11271
- const result = {};
11272
- const lines = src.split("\n");
11273
- let pendingKey = null;
11274
- let pendingKind = null;
11275
- let childIndent = -1;
11276
- for (let i = 0; i < lines.length; i++) {
11277
- const line = lines[i];
11278
- const trimmed = line.trim();
11279
- if (!trimmed || trimmed.startsWith("#")) continue;
11280
- const indent = line.length - line.trimStart().length;
11281
- if (indent > 0) {
11282
- if (!pendingKey) {
11283
- throw new Error(`line ${i + 1}: unexpected indented line without a preceding "key:" line`);
11284
- }
11285
- if (childIndent < 0) childIndent = indent;
11286
- if (indent > childIndent) {
11287
- throw new Error(
11288
- `line ${i + 1}: nesting deeper than one level is not supported \u2014 use a \`\`\`json data fence for nested input`
11289
- );
11290
- }
11291
- if (trimmed.startsWith("- ")) {
11292
- if (pendingKind === "map") {
11293
- throw new Error(
11294
- `line ${i + 1}: cannot mix list items and mapping keys under "${pendingKey}"`
11295
- );
11296
- }
11297
- pendingKind = "list";
11298
- result[pendingKey].push(parseInlineValue(trimmed.slice(2)));
11299
- continue;
11300
- }
11301
- if (pendingKind === "list") {
11302
- throw new Error(
11303
- `line ${i + 1}: cannot mix mapping keys and list items under "${pendingKey}"`
11304
- );
11305
- }
11306
- const subColon = trimmed.indexOf(":");
11307
- if (subColon < 1) {
11308
- throw new Error(`line ${i + 1}: expected "key: value"`);
11309
- }
11310
- const subValue = trimmed.slice(subColon + 1).trim();
11311
- if (subValue === "") {
11312
- throw new Error(
11313
- `line ${i + 1}: nesting deeper than one level is not supported \u2014 use a \`\`\`json data fence for nested input`
11314
- );
11315
- }
11316
- if (pendingKind !== "map") {
11317
- pendingKind = "map";
11318
- result[pendingKey] = {};
11319
- }
11320
- result[pendingKey][trimmed.slice(0, subColon).trim()] = parseInlineValue(subValue);
11321
- continue;
11322
- }
11323
- pendingKey = null;
11324
- pendingKind = null;
11325
- childIndent = -1;
11326
- const colonIdx = trimmed.indexOf(":");
11327
- if (colonIdx < 1) {
11328
- throw new Error(`line ${i + 1}: expected "key: value"`);
11329
- }
11330
- const key = trimmed.slice(0, colonIdx).trim();
11331
- const rawValue = trimmed.slice(colonIdx + 1).trim();
11332
- if (rawValue === "") {
11333
- result[key] = [];
11334
- pendingKey = key;
11335
- } else {
11336
- result[key] = parseInlineValue(rawValue);
11337
- }
11338
- }
11339
- return result;
11340
- }
11341
- function findFirstTable(contents) {
11342
- return contents?.find((n) => n.type === "table");
11343
- }
11344
- function extractTableData(table) {
11345
- const toCells = (row) => row.children.map((cell) => extractPlainText(cell).trim());
11346
- const [headerRow, ...bodyRows] = table.children;
11347
- return {
11348
- headers: headerRow ? toCells(headerRow) : [],
11349
- rows: bodyRows.map(toCells),
11350
- ...table.align && table.align.some((a) => a != null) ? { align: table.align } : {}
11351
- };
11352
- }
11353
-
11354
12990
  // src/doc/annotationBlocks.ts
11355
12991
  function applyBlockMeta(block, m) {
11356
12992
  if (m.build) block.build = m.build;
@@ -11987,8 +13623,8 @@ function splitIntoSentences(text) {
11987
13623
  if (fragments.length === 0) return [];
11988
13624
  const split = [];
11989
13625
  for (const frag of fragments) {
11990
- const words = frag.split(/\s+/);
11991
- if (words.length > MAX_PHRASE_WORDS) {
13626
+ const words2 = frag.split(/\s+/);
13627
+ if (words2.length > MAX_PHRASE_WORDS) {
11992
13628
  const mid = Math.floor(frag.length / 2);
11993
13629
  const clauseRe = /[,;]\s+|\s+—\s+|\s+-\s+/g;
11994
13630
  let bestSplit = -1;
@@ -12290,16 +13926,16 @@ function wordIndexAtChar(script, charOffset) {
12290
13926
  }
12291
13927
  return lo;
12292
13928
  }
12293
- function wordIndexAtTime(words, tSec) {
12294
- if (words.length === 0) return 0;
13929
+ function wordIndexAtTime(words2, tSec) {
13930
+ if (words2.length === 0) return 0;
12295
13931
  let lo = 0;
12296
- let hi = words.length - 1;
13932
+ let hi = words2.length - 1;
12297
13933
  while (lo < hi) {
12298
13934
  const mid = lo + hi + 1 >> 1;
12299
- if (words[mid].tSec <= tSec) lo = mid;
13935
+ if (words2[mid].tSec <= tSec) lo = mid;
12300
13936
  else hi = mid - 1;
12301
13937
  }
12302
- return words[lo].tSec <= tSec ? lo : 0;
13938
+ return words2[lo].tSec <= tSec ? lo : 0;
12303
13939
  }
12304
13940
 
12305
13941
  // src/doc/builds.ts
@@ -12333,21 +13969,30 @@ function planContentBuilds(nodes, spec) {
12333
13969
  }
12334
13970
  function blockBuildSteps(block) {
12335
13971
  if (!block.build || block.build.mode === "off") return [];
12336
- return block.layers?.flatMap(
12337
- (layer) => layer.type === "text" ? layer.content.buildSteps ?? [] : []
12338
- ) ?? [];
13972
+ const layers = block.layers ?? [];
13973
+ const tagged = layers.filter((layer) => layer.buildStep !== void 0);
13974
+ if (tagged.length) {
13975
+ const count = Math.max(...tagged.map((layer) => layer.buildStep)) + 1;
13976
+ return Array.from({ length: count }, (_, index) => ({
13977
+ id: `build-${index}`,
13978
+ text: tagged.flatMap(
13979
+ (layer) => layer.buildStep === index && layer.type === "text" ? [layer.content.text] : []
13980
+ ).join(" ").trim()
13981
+ }));
13982
+ }
13983
+ return layers.flatMap((layer) => layer.type === "text" ? layer.content.buildSteps ?? [] : []);
12339
13984
  }
12340
13985
  function buildSourceText(steps) {
12341
- return JSON.stringify(steps.map((step) => [step.text, step.ancestors ?? []]));
13986
+ return JSON.stringify(steps.map((step2) => [step2.text, step2.ancestors ?? []]));
12342
13987
  }
12343
13988
  function resolveBuildTimeline(steps, spec, duration, timing) {
12344
13989
  const end = Number.isFinite(duration) ? Math.max(0, duration) : 0;
12345
13990
  const requested = Number.isFinite(spec.duration) ? Math.max(0, spec.duration) : 0.4;
12346
- let entrance = Math.min(requested, end / Math.max(4, steps.length * 2));
13991
+ let entrance2 = Math.min(requested, end / Math.max(4, steps.length * 2));
12347
13992
  const intro = Math.min(0.5, end * 0.08);
12348
13993
  const hold = Math.min(1.5, end * 0.2);
12349
- const latest = Math.max(0, end - hold - entrance);
12350
- const weights = steps.map((step) => Math.max(1, step.text.trim().split(/\s+/).length));
13994
+ const latest = Math.max(0, end - hold - entrance2);
13995
+ const weights = steps.map((step2) => Math.max(1, step2.text.trim().split(/\s+/).length));
12351
13996
  const total = weights.reduce((sum, weight) => sum + weight, 0);
12352
13997
  let consumed = 0;
12353
13998
  let previous = 0;
@@ -12363,21 +14008,21 @@ function resolveBuildTimeline(steps, spec, duration, timing) {
12363
14008
  return start;
12364
14009
  });
12365
14010
  if (matched && starts.length)
12366
- entrance = Math.min(
12367
- entrance,
14011
+ entrance2 = Math.min(
14012
+ entrance2,
12368
14013
  Math.max(0, end - starts[starts.length - 1] - Math.min(0.04, end / 8))
12369
14014
  );
12370
- return { starts, entrance, compressed: steps.length > 0 && end < steps.length * 0.8 };
14015
+ return { starts, entrance: entrance2, compressed: steps.length > 0 && end < steps.length * 0.8 };
12371
14016
  }
12372
- function buildCursorAtTime(timeline, time) {
14017
+ function buildCursorAtTime(timeline2, time) {
12373
14018
  let revealed = 0;
12374
- for (const start of timeline.starts) {
14019
+ for (const start of timeline2.starts) {
12375
14020
  if (time < start) break;
12376
14021
  revealed++;
12377
14022
  }
12378
14023
  return {
12379
14024
  revealed,
12380
- progress: revealed === 0 ? 0 : timeline.entrance === 0 ? 1 : Math.min(1, Math.max(0, (time - timeline.starts[revealed - 1]) / timeline.entrance))
14025
+ progress: revealed === 0 ? 0 : timeline2.entrance === 0 ? 1 : Math.min(1, Math.max(0, (time - timeline2.starts[revealed - 1]) / timeline2.entrance))
12381
14026
  };
12382
14027
  }
12383
14028
  function buildTargetState(index, steps, cursor, spec, motion = true) {
@@ -12410,11 +14055,11 @@ function resolveDocBuildTiming(doc, blocks) {
12410
14055
  if (recorded?.length) {
12411
14056
  const automatic = resolveBuildTimeline(steps, block.build, block.duration);
12412
14057
  let last = 0;
12413
- const starts2 = steps.map((_, step) => {
12414
- const event = recorded.find((event2) => event2.step === step);
12415
- const following = recorded.filter((event2) => event2.step > step).sort((a, b) => a.step - b.step)[0];
14058
+ const starts2 = steps.map((_, step2) => {
14059
+ const event = recorded.find((event2) => event2.step === step2);
14060
+ const following = recorded.filter((event2) => event2.step > step2).sort((a, b) => a.step - b.step)[0];
12416
14061
  const estimate = Math.min(
12417
- automatic.starts[step],
14062
+ automatic.starts[step2],
12418
14063
  following ? Math.max(0, following.time - block.startTime) : block.duration
12419
14064
  );
12420
14065
  last = Math.max(last, event ? event.time - block.startTime : estimate);
@@ -12432,8 +14077,8 @@ function resolveDocBuildTiming(doc, blocks) {
12432
14077
  if (!source) return block;
12433
14078
  let cursor = 0;
12434
14079
  const starts = [];
12435
- for (const step of steps) {
12436
- const text = step.text.trim();
14080
+ for (const step2 of steps) {
14081
+ const text = step2.text.trim();
12437
14082
  const offset = text ? source.text.indexOf(text, cursor) : -1;
12438
14083
  if (offset < 0) return block;
12439
14084
  const word = narration.bookmarks.find(
@@ -12450,13 +14095,14 @@ function resolveDocBuildTiming(doc, blocks) {
12450
14095
  // src/doc/templates/contentBlock.ts
12451
14096
  function bodyNodes(context) {
12452
14097
  if (context.block?.summaryMode === "headings-and-features") return [];
14098
+ const summary = !!context.block?.summaryMode;
12453
14099
  return (context.block?.contents ?? []).filter(
12454
14100
  // Mermaid source, hosted-video links and picture-only paragraphs are
12455
14101
  // already materialized as visual layers (a diagram, a player, the picture
12456
14102
  // itself). Repeating them as prose makes the loss-averse default noisy
12457
14103
  // without preserving any additional meaning — a pasted picture's alt text
12458
14104
  // is just its generated file name.
12459
- (node) => !isMaterializedMediaNode(node) && !isPictureParagraph(node)
14105
+ (node) => !isMaterializedMediaNode(node) && !isPictureParagraph(node) && !(summary && (node.type === "table" || node.type === "code"))
12460
14106
  );
12461
14107
  }
12462
14108
  function listParagraphs(list, level = 0) {
@@ -12599,6 +14245,12 @@ export {
12599
14245
  countUpSpecFor,
12600
14246
  formatCountValue,
12601
14247
  formatCountUp,
14248
+ splitConceptItem,
14249
+ conceptBuildStepTexts,
14250
+ cardsBlock,
14251
+ processBlock,
14252
+ cycleBlock,
14253
+ layersBlock,
12602
14254
  statHighlight,
12603
14255
  quoteBlock,
12604
14256
  factCard,
@@ -12652,21 +14304,27 @@ export {
12652
14304
  coerceTemplateParams,
12653
14305
  lintTemplateParams,
12654
14306
  fallbackBlockLayers,
14307
+ isDataFence,
14308
+ parseDataFence,
14309
+ replaceDataFence,
14310
+ parseYamlSubset,
14311
+ findFirstTable,
14312
+ extractTableData,
12655
14313
  materializeBlockLayers,
12656
14314
  materializeBlockLayersWithRuntime,
12657
14315
  TEMPLATE_METADATA,
12658
14316
  TEMPLATE_AUTHORING_METADATA,
14317
+ TEMPLATE_GUIDES,
14318
+ guideText,
14319
+ templateCatalog,
14320
+ PRESENTATION_LAYOUT_GUIDE,
14321
+ AUTHORING_TRICKS,
14322
+ authoringTricks,
12659
14323
  coverBlock,
12660
14324
  expandCoverBlock,
12661
14325
  expandDocBlocks,
12662
14326
  getAvailableTemplates,
12663
14327
  hasTemplate,
12664
- isDataFence,
12665
- parseDataFence,
12666
- replaceDataFence,
12667
- parseYamlSubset,
12668
- findFirstTable,
12669
- extractTableData,
12670
14328
  detectPromotableBodyAnnotation,
12671
14329
  getPinnedBlockMeta,
12672
14330
  markdownToDoc,