@bendyline/squisq 2.13.11 → 2.13.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/{Doc-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-65WL5ILB.js → chunk-CQSCYJQU.js} +1 -1
  7. package/dist/{chunk-SG3HXPMF.js → chunk-DBVCSVNV.js} +2 -1
  8. package/dist/{chunk-ESGKI6CD.js → chunk-IFZZI4XY.js} +25 -6
  9. package/dist/{chunk-WNEH3BZP.js → chunk-JNUNNQ74.js} +3 -3
  10. package/dist/{chunk-N7I6JP6Q.js → chunk-OUSEEUEC.js} +388 -216
  11. package/dist/{chunk-SKGMNBQX.js → chunk-QJQQO3K4.js} +1857 -386
  12. package/dist/{chunk-3WUI2N6C.js → chunk-SGNWO7OF.js} +20 -1
  13. package/dist/{chunk-GLWQTZ4C.js → chunk-XEQALK5B.js} +1 -1
  14. package/dist/doc/index.d.ts +124 -9
  15. package/dist/doc/index.js +19 -7
  16. package/dist/fence/index.d.ts +1 -1
  17. package/dist/generate/index.d.ts +1 -1
  18. package/dist/imageEdit/index.d.ts +3 -3
  19. package/dist/index.d.ts +5 -5
  20. package/dist/index.js +23 -11
  21. package/dist/jsonForm/index.d.ts +1 -1
  22. package/dist/jsonForm/index.js +3 -3
  23. package/dist/markdown/index.js +3 -3
  24. package/dist/{materializePageSection-S2hxZ_lO.d.ts → materializePageSection-bLfqIy6j.d.ts} +1 -1
  25. package/dist/mediaEdit/index.d.ts +2 -2
  26. package/dist/narration/index.d.ts +1 -1
  27. package/dist/narration/index.js +5 -5
  28. package/dist/recommend/index.js +1 -1
  29. package/dist/schemas/index.d.ts +4 -4
  30. package/dist/schemas/index.js +2 -2
  31. package/dist/{themeLibrary-CFJbeItn.d.ts → themeLibrary-BIYE6aLw.d.ts} +1 -1
  32. package/dist/timing/index.d.ts +1 -1
  33. package/dist/transform/index.d.ts +23 -14
  34. package/dist/transform/index.js +6 -6
  35. 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";
@@ -4725,16 +5964,16 @@ function computeChartFrame(options) {
4725
5964
  cursor = titleBand.y + titleBand.height;
4726
5965
  }
4727
5966
  let tableBand;
4728
- let tableHeight = 0;
5967
+ let tableHeight2 = 0;
4729
5968
  if (options.tableRowCount > 0) {
4730
- tableHeight = Math.min(H * 0.4, (options.tableRowCount + 1) * options.tableFontSize * 2.4);
5969
+ tableHeight2 = Math.min(H * 0.4, (options.tableRowCount + 1) * options.tableFontSize * 2.4);
4731
5970
  }
4732
5971
  const legendHeight = options.legendRows > 0 ? options.legendRows * options.legendRowHeight : 0;
4733
5972
  const chartArea = {
4734
5973
  x: marginX,
4735
5974
  y: cursor,
4736
5975
  width: innerWidth,
4737
- height: Math.max(40, H - marginY - cursor - legendHeight - tableHeight)
5976
+ height: Math.max(40, H - marginY - cursor - legendHeight - tableHeight2)
4738
5977
  };
4739
5978
  cursor = chartArea.y + chartArea.height;
4740
5979
  let legendBand;
@@ -4742,8 +5981,8 @@ function computeChartFrame(options) {
4742
5981
  legendBand = { x: marginX, y: cursor, width: innerWidth, height: legendHeight };
4743
5982
  cursor += legendHeight;
4744
5983
  }
4745
- if (tableHeight > 0) {
4746
- tableBand = { x: marginX, y: cursor, width: innerWidth, height: tableHeight };
5984
+ if (tableHeight2 > 0) {
5985
+ tableBand = { x: marginX, y: cursor, width: innerWidth, height: tableHeight2 };
4747
5986
  }
4748
5987
  const plot = {
4749
5988
  x: chartArea.x + options.leftGutter,
@@ -4853,7 +6092,7 @@ function renderChart(kind, input, context) {
4853
6092
  const innerWidth = viewport.width * 0.88;
4854
6093
  const legend = legendLabels.length > 0 ? layoutLegend(legendLabels, legendFontSize, innerWidth) : null;
4855
6094
  const gutters = computeGutters(kind, data, input, context, axisFontSize, labelFontSize);
4856
- const frame = computeChartFrame({
6095
+ const frame2 = computeChartFrame({
4857
6096
  viewportWidth: viewport.width,
4858
6097
  viewportHeight: viewport.height,
4859
6098
  hasTitle: title.length > 0,
@@ -4867,7 +6106,7 @@ function renderChart(kind, input, context) {
4867
6106
  });
4868
6107
  const entranceHint = getTemplateHint(context, templateId, "entrance", "staggered");
4869
6108
  const markDelay = (index) => entranceHint === "subtle" ? 0.1 : Math.min(1.2, 0.2 + index * 0.08);
4870
- const entrance = (index) => ({
6109
+ const entrance2 = (index) => ({
4871
6110
  type: "fadeIn",
4872
6111
  duration: 0.6,
4873
6112
  delay: markDelay(index)
@@ -4877,7 +6116,7 @@ function renderChart(kind, input, context) {
4877
6116
  const growEasing = motion.textEntrance.easing;
4878
6117
  const eased = (animation) => growEasing ? { ...animation, easing: growEasing } : animation;
4879
6118
  const mark = (index, kind2, along = 0) => {
4880
- if (!motion.chartGrowth) return entrance(index);
6119
+ if (!motion.chartGrowth) return entrance2(index);
4881
6120
  const delay = motionDelay(context, markDelay(index));
4882
6121
  switch (kind2) {
4883
6122
  case "bar":
@@ -4920,19 +6159,19 @@ function renderChart(kind, input, context) {
4920
6159
  input,
4921
6160
  context,
4922
6161
  data,
4923
- frame,
6162
+ frame: frame2,
4924
6163
  colors,
4925
6164
  axisFontSize,
4926
6165
  labelFontSize,
4927
6166
  bodyFont,
4928
6167
  gridColor: withAlpha(theme.colors.text, 0.12),
4929
6168
  axisTextColor: theme.colors.textMuted,
4930
- entrance,
6169
+ entrance: entrance2,
4931
6170
  mark,
4932
6171
  labelAnimation
4933
6172
  };
4934
6173
  const layers = [createBackgroundLayer("bg", themedSurfaceGradient(context, 170))];
4935
- if (title && frame.titleBand) {
6174
+ if (title && frame2.titleBand) {
4936
6175
  layers.push({
4937
6176
  type: "text",
4938
6177
  id: "title",
@@ -4948,9 +6187,9 @@ function renderChart(kind, input, context) {
4948
6187
  }
4949
6188
  },
4950
6189
  position: {
4951
- x: frame.titleBand.x + frame.titleBand.width / 2,
4952
- y: frame.titleBand.y + frame.titleBand.height / 2,
4953
- width: frame.titleBand.width,
6190
+ x: frame2.titleBand.x + frame2.titleBand.width / 2,
6191
+ y: frame2.titleBand.y + frame2.titleBand.height / 2,
6192
+ width: frame2.titleBand.width,
4954
6193
  anchor: "center"
4955
6194
  },
4956
6195
  animation: motionEntrance(context, { type: "fadeIn", duration: 0.8 })
@@ -4975,11 +6214,11 @@ function renderChart(kind, input, context) {
4975
6214
  layers.push(...paintScatter(paint));
4976
6215
  break;
4977
6216
  }
4978
- if (legend && frame.legendBand) {
4979
- layers.push(...paintLegend(paint, legend, frame.legendBand, legendFontSize));
6217
+ if (legend && frame2.legendBand) {
6218
+ layers.push(...paintLegend(paint, legend, frame2.legendBand, legendFontSize));
4980
6219
  }
4981
- if (input.showTable === true && frame.tableBand) {
4982
- layers.push(tableLayer(table, frame.tableBand, tableFontSize, input, context));
6220
+ if (input.showTable === true && frame2.tableBand) {
6221
+ layers.push(tableLayer(table, frame2.tableBand, tableFontSize, input, context));
4983
6222
  }
4984
6223
  return layers;
4985
6224
  }
@@ -5060,7 +6299,7 @@ function valueTicks(kind, data, input) {
5060
6299
  return niceTicks(min, max, 5);
5061
6300
  }
5062
6301
  function paintValueAxisY(paint, scale, yFor) {
5063
- const { frame, gridColor, axisTextColor, axisFontSize, bodyFont, input } = paint;
6302
+ const { frame: frame2, gridColor, axisTextColor, axisFontSize, bodyFont, input } = paint;
5064
6303
  const layers = [];
5065
6304
  scale.ticks.forEach((tick, index) => {
5066
6305
  const y = yFor(tick);
@@ -5072,9 +6311,9 @@ function paintValueAxisY(paint, scale, yFor) {
5072
6311
  fill: tick === 0 ? withAlpha(paint.context.theme.colors.text, 0.35) : gridColor
5073
6312
  },
5074
6313
  position: {
5075
- x: frame.plot.x,
6314
+ x: frame2.plot.x,
5076
6315
  y: y - (tick === 0 ? 1 : 0.5),
5077
- width: frame.plot.width,
6316
+ width: frame2.plot.width,
5078
6317
  height: tick === 0 ? 2 : 1
5079
6318
  }
5080
6319
  });
@@ -5091,7 +6330,7 @@ function paintValueAxisY(paint, scale, yFor) {
5091
6330
  }
5092
6331
  },
5093
6332
  position: {
5094
- x: frame.chartArea.x + (frame.plot.x - frame.chartArea.x) / 2,
6333
+ x: frame2.chartArea.x + (frame2.plot.x - frame2.chartArea.x) / 2,
5095
6334
  y,
5096
6335
  anchor: "center"
5097
6336
  }
@@ -5100,11 +6339,11 @@ function paintValueAxisY(paint, scale, yFor) {
5100
6339
  return layers;
5101
6340
  }
5102
6341
  function paintCategoryAxisX(paint, xForCenter) {
5103
- const { frame, data, labelFontSize, axisTextColor, bodyFont } = paint;
5104
- const step = categoryLabelStep(data.labels.length, frame.plot.width, labelFontSize);
6342
+ const { frame: frame2, data, labelFontSize, axisTextColor, bodyFont } = paint;
6343
+ const step2 = categoryLabelStep(data.labels.length, frame2.plot.width, labelFontSize);
5105
6344
  const layers = [];
5106
6345
  data.labels.forEach((label, index) => {
5107
- if (index % step !== 0 || !label) return;
6346
+ if (index % step2 !== 0 || !label) return;
5108
6347
  layers.push({
5109
6348
  type: "text",
5110
6349
  id: `cat-${index}`,
@@ -5119,7 +6358,7 @@ function paintCategoryAxisX(paint, xForCenter) {
5119
6358
  },
5120
6359
  position: {
5121
6360
  x: xForCenter(index),
5122
- y: frame.plot.y + frame.plot.height + labelFontSize * 1.2,
6361
+ y: frame2.plot.y + frame2.plot.height + labelFontSize * 1.2,
5123
6362
  anchor: "center"
5124
6363
  }
5125
6364
  });
@@ -5127,14 +6366,14 @@ function paintCategoryAxisX(paint, xForCenter) {
5127
6366
  return layers;
5128
6367
  }
5129
6368
  function paintColumns(paint) {
5130
- const { frame, data, colors, input, mark, labelAnimation } = paint;
6369
+ const { frame: frame2, data, colors, input, mark, labelAnimation } = paint;
5131
6370
  const scale = valueTicks("column", data, input);
5132
- const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
6371
+ const yFor = (v) => frame2.plot.y + frame2.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
5133
6372
  const layers = paintValueAxisY(paint, scale, yFor);
5134
6373
  const n = data.labels.length;
5135
- const band = frame.plot.width / Math.max(1, n);
6374
+ const band = frame2.plot.width / Math.max(1, n);
5136
6375
  const innerPad = band * 0.15;
5137
- layers.push(...paintCategoryAxisX(paint, (i) => frame.plot.x + (i + 0.5) * band));
6376
+ layers.push(...paintCategoryAxisX(paint, (i) => frame2.plot.x + (i + 0.5) * band));
5138
6377
  const stacked = input.stacked === true && data.series.length > 1;
5139
6378
  const extents = stacked ? stackSeries(data.series) : null;
5140
6379
  const groupWidth = band - innerPad * 2;
@@ -5146,7 +6385,7 @@ function paintColumns(paint) {
5146
6385
  if (!extent || extent.y0 === extent.y1) return;
5147
6386
  const top = yFor(extent.y1);
5148
6387
  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);
6388
+ const x = stacked ? frame2.plot.x + i * band + innerPad : frame2.plot.x + i * band + innerPad + s * (barWidth + 4);
5150
6389
  layers.push({
5151
6390
  type: "shape",
5152
6391
  id: `mark-${s}-${i}`,
@@ -5172,7 +6411,7 @@ function paintColumns(paint) {
5172
6411
  }
5173
6412
  function paintBars(paint) {
5174
6413
  const {
5175
- frame,
6414
+ frame: frame2,
5176
6415
  data,
5177
6416
  colors,
5178
6417
  input,
@@ -5189,8 +6428,8 @@ function paintBars(paint) {
5189
6428
  const labelWidthPx = (value) => formatChartValue(value, input.unit).length * axisFontSize * CHART_CHAR_WIDTH_EM;
5190
6429
  const showValueLabels = input.showValues === true && !(input.stacked === true);
5191
6430
  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));
6431
+ const axisWidth = Math.max(20, frame2.plot.width - reservedForLabels);
6432
+ const xFor = (v) => frame2.plot.x + axisWidth * ((v - scale.niceMin) / (scale.niceMax - scale.niceMin));
5194
6433
  const layers = [];
5195
6434
  scale.ticks.forEach((tick, index) => {
5196
6435
  const x = xFor(tick);
@@ -5203,9 +6442,9 @@ function paintBars(paint) {
5203
6442
  },
5204
6443
  position: {
5205
6444
  x: x - (tick === 0 ? 1 : 0.5),
5206
- y: frame.plot.y,
6445
+ y: frame2.plot.y,
5207
6446
  width: tick === 0 ? 2 : 1,
5208
- height: frame.plot.height
6447
+ height: frame2.plot.height
5209
6448
  }
5210
6449
  });
5211
6450
  layers.push({
@@ -5220,11 +6459,11 @@ function paintBars(paint) {
5220
6459
  textAlign: "center"
5221
6460
  }
5222
6461
  },
5223
- position: { x, y: frame.plot.y + frame.plot.height + axisFontSize * 1.2, anchor: "center" }
6462
+ position: { x, y: frame2.plot.y + frame2.plot.height + axisFontSize * 1.2, anchor: "center" }
5224
6463
  });
5225
6464
  });
5226
6465
  const n = data.labels.length;
5227
- const band = frame.plot.height / Math.max(1, n);
6466
+ const band = frame2.plot.height / Math.max(1, n);
5228
6467
  const innerPad = band * 0.15;
5229
6468
  const stacked = input.stacked === true && data.series.length > 1;
5230
6469
  const extents = stacked ? stackSeries(data.series) : null;
@@ -5245,8 +6484,8 @@ function paintBars(paint) {
5245
6484
  }
5246
6485
  },
5247
6486
  position: {
5248
- x: frame.chartArea.x + (frame.plot.x - frame.chartArea.x) / 2,
5249
- y: frame.plot.y + (i + 0.5) * band,
6487
+ x: frame2.chartArea.x + (frame2.plot.x - frame2.chartArea.x) / 2,
6488
+ y: frame2.plot.y + (i + 0.5) * band,
5250
6489
  anchor: "center"
5251
6490
  }
5252
6491
  });
@@ -5258,7 +6497,7 @@ function paintBars(paint) {
5258
6497
  if (!extent || extent.y0 === extent.y1) return;
5259
6498
  const left = xFor(extent.y0);
5260
6499
  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);
6500
+ const y = stacked ? frame2.plot.y + i * band + innerPad : frame2.plot.y + i * band + innerPad + s * (barHeight + 4);
5262
6501
  layers.push({
5263
6502
  type: "shape",
5264
6503
  id: `mark-${s}-${i}`,
@@ -5285,14 +6524,14 @@ function paintBars(paint) {
5285
6524
  return layers;
5286
6525
  }
5287
6526
  function paintRadial(paint) {
5288
- const { kind, frame, data, colors, input, entrance, mark, labelAnimation, context } = paint;
6527
+ const { kind, frame: frame2, data, colors, input, entrance: entrance2, mark, labelAnimation, context } = paint;
5289
6528
  const slices = data.labels.map((label, index) => ({ label, value: data.series[0].values[index], index })).filter(
5290
6529
  (s) => s.value !== null && Number.isFinite(s.value) && s.value > 0
5291
6530
  );
5292
6531
  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;
6532
+ const cx = frame2.chartArea.x + frame2.chartArea.width / 2;
6533
+ const cy = frame2.chartArea.y + frame2.chartArea.height / 2;
6534
+ const rOuter = Math.min(frame2.chartArea.width, frame2.chartArea.height) * 0.42;
5296
6535
  const rInner = kind === "donut" ? rOuter * 0.55 : 0;
5297
6536
  const layers = [];
5298
6537
  let startDeg = 0;
@@ -5333,7 +6572,7 @@ function paintRadial(paint) {
5333
6572
  y: cy + rLabel * Math.sin(rad),
5334
6573
  anchor: "center"
5335
6574
  },
5336
- animation: labelAnimation(order, entrance(order))
6575
+ animation: labelAnimation(order, entrance2(order))
5337
6576
  });
5338
6577
  }
5339
6578
  startDeg += sweep;
@@ -5341,12 +6580,12 @@ function paintRadial(paint) {
5341
6580
  return layers;
5342
6581
  }
5343
6582
  function paintLines(paint) {
5344
- const { kind, frame, data, colors, input, mark, labelAnimation } = paint;
6583
+ const { kind, frame: frame2, data, colors, input, mark, labelAnimation } = paint;
5345
6584
  const scale = valueTicks(kind, data, input);
5346
- const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
6585
+ const yFor = (v) => frame2.plot.y + frame2.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
5347
6586
  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;
6587
+ const band = frame2.plot.width / Math.max(1, n);
6588
+ const xForCenter = (i) => frame2.plot.x + (i + 0.5) * band;
5350
6589
  const layers = paintValueAxisY(paint, scale, yFor);
5351
6590
  layers.push(...paintCategoryAxisX(paint, xForCenter));
5352
6591
  const baselineY = yFor(Math.max(scale.niceMin, Math.min(scale.niceMax, 0)));
@@ -5377,10 +6616,10 @@ function paintLines(paint) {
5377
6616
  strokeWidth: 0
5378
6617
  },
5379
6618
  position: {
5380
- x: frame.plot.x,
5381
- y: frame.plot.y,
5382
- width: frame.plot.width,
5383
- height: frame.plot.height
6619
+ x: frame2.plot.x,
6620
+ y: frame2.plot.y,
6621
+ width: frame2.plot.width,
6622
+ height: frame2.plot.height
5384
6623
  },
5385
6624
  animation: mark(s, "area")
5386
6625
  });
@@ -5391,10 +6630,10 @@ function paintLines(paint) {
5391
6630
  id: `line-${s}-${seg}`,
5392
6631
  content: { d: polylinePath(segment), stroke: colors[s], strokeWidth: 4, fill: "none" },
5393
6632
  position: {
5394
- x: frame.plot.x,
5395
- y: frame.plot.y,
5396
- width: frame.plot.width,
5397
- height: frame.plot.height
6633
+ x: frame2.plot.x,
6634
+ y: frame2.plot.y,
6635
+ width: frame2.plot.width,
6636
+ height: frame2.plot.height
5398
6637
  },
5399
6638
  animation: mark(s, "line")
5400
6639
  });
@@ -5429,9 +6668,9 @@ function paintLines(paint) {
5429
6668
  return layers;
5430
6669
  }
5431
6670
  function paintScatter(paint) {
5432
- const { frame, data, colors, input, mark } = paint;
6671
+ const { frame: frame2, data, colors, input, mark } = paint;
5433
6672
  const scale = valueTicks("scatter", data, input);
5434
- const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
6673
+ const yFor = (v) => frame2.plot.y + frame2.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
5435
6674
  const layers = paintValueAxisY(paint, scale, yFor);
5436
6675
  const numericCount = data.labelNumbers.filter((v) => v !== null).length;
5437
6676
  const numericX = data.labels.length > 0 && numericCount / data.labels.length >= 0.6;
@@ -5442,15 +6681,15 @@ function paintScatter(paint) {
5442
6681
  const span = xScale.niceMax - xScale.niceMin;
5443
6682
  xFor = (i) => {
5444
6683
  const v = data.labelNumbers[i];
5445
- return v === null ? null : frame.plot.x + frame.plot.width * ((v - xScale.niceMin) / span);
6684
+ return v === null ? null : frame2.plot.x + frame2.plot.width * ((v - xScale.niceMin) / span);
5446
6685
  };
5447
6686
  xScale.ticks.forEach((tick, index) => {
5448
- const x = frame.plot.x + frame.plot.width * ((tick - xScale.niceMin) / span);
6687
+ const x = frame2.plot.x + frame2.plot.width * ((tick - xScale.niceMin) / span);
5449
6688
  layers.push({
5450
6689
  type: "shape",
5451
6690
  id: `xgrid-${index}`,
5452
6691
  content: { shape: "rect", fill: paint.gridColor },
5453
- position: { x: x - 0.5, y: frame.plot.y, width: 1, height: frame.plot.height }
6692
+ position: { x: x - 0.5, y: frame2.plot.y, width: 1, height: frame2.plot.height }
5454
6693
  });
5455
6694
  layers.push({
5456
6695
  type: "text",
@@ -5466,15 +6705,15 @@ function paintScatter(paint) {
5466
6705
  },
5467
6706
  position: {
5468
6707
  x,
5469
- y: frame.plot.y + frame.plot.height + paint.axisFontSize * 1.2,
6708
+ y: frame2.plot.y + frame2.plot.height + paint.axisFontSize * 1.2,
5470
6709
  anchor: "center"
5471
6710
  }
5472
6711
  });
5473
6712
  });
5474
6713
  } 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));
6714
+ const band = frame2.plot.width / Math.max(1, data.labels.length);
6715
+ xFor = (i) => frame2.plot.x + (i + 0.5) * band;
6716
+ layers.push(...paintCategoryAxisX(paint, (i) => frame2.plot.x + (i + 0.5) * band));
5478
6717
  }
5479
6718
  const r = 7;
5480
6719
  data.series.forEach((series, s) => {
@@ -5598,19 +6837,22 @@ var TITLE_VARIANTS = {
5598
6837
  wide: "title-stack",
5599
6838
  tall: "split",
5600
6839
  square: "title-stack",
5601
- multiple: "title-stack"
6840
+ multiple: "title-stack",
6841
+ table: "title-stack"
5602
6842
  },
5603
6843
  square: {
5604
6844
  wide: "title-stack",
5605
6845
  tall: "split",
5606
6846
  square: "title-stack",
5607
- multiple: "title-stack"
6847
+ multiple: "title-stack",
6848
+ table: "title-stack"
5608
6849
  },
5609
6850
  portrait: {
5610
6851
  wide: "title-stack",
5611
6852
  tall: "title-stack",
5612
6853
  square: "title-stack",
5613
- multiple: "title-stack"
6854
+ multiple: "title-stack",
6855
+ table: "title-stack"
5614
6856
  }
5615
6857
  };
5616
6858
  var TEXT_VARIANTS = {
@@ -5618,19 +6860,22 @@ var TEXT_VARIANTS = {
5618
6860
  wide: "split",
5619
6861
  tall: "split",
5620
6862
  square: "split",
5621
- multiple: "split"
6863
+ multiple: "split",
6864
+ table: "text-stack"
5622
6865
  },
5623
6866
  square: {
5624
6867
  wide: "text-stack",
5625
6868
  tall: "split",
5626
6869
  square: "text-stack",
5627
- multiple: "text-stack"
6870
+ multiple: "text-stack",
6871
+ table: "text-stack"
5628
6872
  },
5629
6873
  portrait: {
5630
6874
  wide: "text-stack",
5631
6875
  tall: "text-stack",
5632
6876
  square: "text-stack",
5633
- multiple: "text-stack"
6877
+ multiple: "text-stack",
6878
+ table: "text-stack"
5634
6879
  }
5635
6880
  };
5636
6881
  var DENSE_VARIANTS = {
@@ -5638,19 +6883,22 @@ var DENSE_VARIANTS = {
5638
6883
  wide: "dense-stack",
5639
6884
  tall: "dense-stack",
5640
6885
  square: "dense-stack",
5641
- multiple: "dense-stack"
6886
+ multiple: "dense-stack",
6887
+ table: "dense-stack"
5642
6888
  },
5643
6889
  square: {
5644
6890
  wide: "dense-stack",
5645
6891
  tall: "dense-stack",
5646
6892
  square: "dense-stack",
5647
- multiple: "dense-stack"
6893
+ multiple: "dense-stack",
6894
+ table: "dense-stack"
5648
6895
  },
5649
6896
  portrait: {
5650
6897
  wide: "dense-stack",
5651
6898
  tall: "dense-stack",
5652
6899
  square: "dense-stack",
5653
- multiple: "dense-stack"
6900
+ multiple: "dense-stack",
6901
+ table: "dense-stack"
5654
6902
  }
5655
6903
  };
5656
6904
  var BLOCK_MEDIA_LAYOUT_POLICIES = {
@@ -5810,6 +7058,34 @@ var BLOCK_MEDIA_LAYOUT_POLICIES = {
5810
7058
  unconsumedMedia: "reserve-when-no-native-media",
5811
7059
  variants: TEXT_VARIANTS
5812
7060
  },
7061
+ cards: {
7062
+ summary: "Text-only cards by default; supplemental media is paired with the cards.",
7063
+ noMedia: "template-default",
7064
+ ownership: "supplemental",
7065
+ unconsumedMedia: "reserved-slot",
7066
+ variants: DENSE_VARIANTS
7067
+ },
7068
+ process: {
7069
+ summary: "A drawn process by default; supplemental media is paired with the stages.",
7070
+ noMedia: "template-default",
7071
+ ownership: "supplemental",
7072
+ unconsumedMedia: "reserved-slot",
7073
+ variants: DENSE_VARIANTS
7074
+ },
7075
+ cycle: {
7076
+ summary: "A drawn cycle by default; supplemental media is paired with the ring.",
7077
+ noMedia: "template-default",
7078
+ ownership: "supplemental",
7079
+ unconsumedMedia: "reserved-slot",
7080
+ variants: TEXT_VARIANTS
7081
+ },
7082
+ layers: {
7083
+ summary: "Drawn concentric layers by default; supplemental media is paired with the rings.",
7084
+ noMedia: "template-default",
7085
+ ownership: "supplemental",
7086
+ unconsumedMedia: "reserved-slot",
7087
+ variants: TEXT_VARIANTS
7088
+ },
5813
7089
  photoGrid: {
5814
7090
  summary: "Requires a primary image collection and owns the complete tiled media field.",
5815
7091
  noMedia: "unsupported",
@@ -5963,12 +7239,12 @@ function getBlockMediaLayoutPolicy(template) {
5963
7239
  // src/doc/richMediaLayout.ts
5964
7240
  function containedFeatureArea(region, aspects) {
5965
7241
  const count = aspects.length;
5966
- const columns = Math.min(
7242
+ const columns2 = Math.min(
5967
7243
  count,
5968
7244
  Math.max(1, Math.ceil(Math.sqrt(count * region.width / region.height)))
5969
7245
  );
5970
- const rows = Math.ceil(count / columns);
5971
- const width = region.width / columns;
7246
+ const rows = Math.ceil(count / columns2);
7247
+ const width = region.width / columns2;
5972
7248
  const height = region.height / rows;
5973
7249
  return aspects.reduce((area, aspect) => {
5974
7250
  const ratio = aspect && Number.isFinite(aspect) && aspect > 0 ? aspect : 16 / 9;
@@ -6056,7 +7332,8 @@ function rect(viewport, x, y, width, height) {
6056
7332
  height: viewport.height * height
6057
7333
  };
6058
7334
  }
6059
- function mediaShape(mediaCount, aspectRatios) {
7335
+ function mediaShape(mediaCount, aspectRatios, table = false) {
7336
+ if (table) return "table";
6060
7337
  if (mediaCount > 1) return "multiple";
6061
7338
  const aspect = aspectRatios[0] ?? 16 / 9;
6062
7339
  if (aspect < 0.8) return "tall";
@@ -6122,6 +7399,7 @@ function overlayMediaRect(viewport, shape) {
6122
7399
  case "square":
6123
7400
  return rect(viewport, 0.49, 0.58, 0.44, 0.35);
6124
7401
  case "multiple":
7402
+ case "table":
6125
7403
  return rect(viewport, 0.07, 0.67, 0.86, 0.27);
6126
7404
  case "wide":
6127
7405
  return rect(viewport, 0.07, 0.71, 0.86, 0.23);
@@ -6134,6 +7412,7 @@ function overlayMediaRect(viewport, shape) {
6134
7412
  case "square":
6135
7413
  return rect(viewport, 0.55, 0.52, 0.4, 0.4);
6136
7414
  case "multiple":
7415
+ case "table":
6137
7416
  return rect(viewport, 0.08, 0.62, 0.84, 0.31);
6138
7417
  case "wide":
6139
7418
  return rect(viewport, 0.08, 0.68, 0.84, 0.27);
@@ -6145,6 +7424,7 @@ function overlayMediaRect(viewport, shape) {
6145
7424
  case "square":
6146
7425
  return rect(viewport, 0.62, 0.22, 0.34, 0.56);
6147
7426
  case "multiple":
7427
+ case "table":
6148
7428
  return rect(viewport, 0.52, 0.5, 0.44, 0.42);
6149
7429
  case "wide":
6150
7430
  return rect(viewport, 0.54, 0.58, 0.42, 0.34);
@@ -6481,14 +7761,14 @@ var MEDIA_BANDS = {
6481
7761
  "full-frame": "full"
6482
7762
  };
6483
7763
  var BLOCK_GAP_EM = 0.55;
6484
- function textLayer(layers, id) {
7764
+ function textLayer2(layers, id) {
6485
7765
  const layer = layers.find((candidate) => candidate.id === id);
6486
7766
  return layer?.type === "text" ? layer : void 0;
6487
7767
  }
6488
7768
  function mediaBandLayout(layers, viewport, band, aspect) {
6489
7769
  const backdrop = layers.filter((layer) => isFullViewportBackground(layer, viewport));
6490
- const title = textLayer(layers, "title");
6491
- const body = textLayer(layers, "body");
7770
+ const title = textLayer2(layers, "title");
7771
+ const body = textLayer2(layers, "body");
6492
7772
  if (band === "full" || !title && !body) {
6493
7773
  return { layers: backdrop, mediaRect: rect(viewport, 0, 0, 1, 1), framed: false };
6494
7774
  }
@@ -6604,7 +7884,7 @@ function mediaBandLayout(layers, viewport, band, aspect) {
6604
7884
  }
6605
7885
  function resolveSupplementalMediaLayout(layers, template, viewport, mediaCount, aspectRatios, featureOptions = {}) {
6606
7886
  const policy = getBlockMediaLayoutPolicy(template);
6607
- const shape = mediaShape(mediaCount, aspectRatios);
7887
+ const shape = mediaShape(mediaCount, aspectRatios, featureOptions.table);
6608
7888
  const alreadyOwnsMedia = layers.some(
6609
7889
  (layer) => layer.type === "image" || layer.type === "video" || layer.type === "videoEmbed" || layer.type === "map" || layer.type === "mermaid" || layer.type === "svg"
6610
7890
  );
@@ -6836,14 +8116,14 @@ function planCanvasFrame(template, title, context) {
6836
8116
  ...mediaRect ? { mediaRect } : {}
6837
8117
  };
6838
8118
  }
6839
- function placeCanvasGroup(frame, contentW, contentH) {
6840
- const scale = Math.min(frame.availW / contentW, frame.availH / contentH, CANVAS_MAX_SCALE);
8119
+ function placeCanvasGroup(frame2, contentW, contentH) {
8120
+ const scale = Math.min(frame2.availW / contentW, frame2.availH / contentH, CANVAS_MAX_SCALE);
6841
8121
  const scaledW = contentW * scale;
6842
8122
  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;
8123
+ const groupH = frame2.titleBlockPx + scaledH + frame2.descriptionBlockPx;
8124
+ const groupTop = frame2.region.y + Math.max(PADDING / 2, (frame2.region.height - groupH) / 2);
8125
+ const offsetX = frame2.region.x + PADDING + (frame2.availW - scaledW) / 2;
8126
+ const offsetY = groupTop + frame2.titleBlockPx;
6847
8127
  return {
6848
8128
  scale,
6849
8129
  scaledW,
@@ -6854,8 +8134,8 @@ function placeCanvasGroup(frame, contentW, contentH) {
6854
8134
  descriptionY: offsetY + scaledH + CANVAS_DESCRIPTION_GAP_PX
6855
8135
  };
6856
8136
  }
6857
- function canvasTitleLayer(id, text, frame, placement, context) {
6858
- const fit = frame.title;
8137
+ function canvasTitleLayer(id, text, frame2, placement, context) {
8138
+ const fit = frame2.title;
6859
8139
  if (!fit) return void 0;
6860
8140
  return {
6861
8141
  type: "text",
@@ -6874,15 +8154,15 @@ function canvasTitleLayer(id, text, frame, placement, context) {
6874
8154
  }
6875
8155
  },
6876
8156
  position: {
6877
- x: frame.region.x + PADDING,
8157
+ x: frame2.region.x + PADDING,
6878
8158
  y: placement.groupTop,
6879
8159
  width: fit.widthPx,
6880
8160
  anchor: "top-left"
6881
8161
  }
6882
8162
  };
6883
8163
  }
6884
- function canvasDescriptionLayer(id, frame, placement, context) {
6885
- const description = frame.description;
8164
+ function canvasDescriptionLayer(id, frame2, placement, context) {
8165
+ const description = frame2.description;
6886
8166
  if (!description) return void 0;
6887
8167
  const { fit, widthPx } = description;
6888
8168
  return {
@@ -6900,7 +8180,7 @@ function canvasDescriptionLayer(id, frame, placement, context) {
6900
8180
  }
6901
8181
  },
6902
8182
  position: {
6903
- x: frame.region.x + (frame.region.width - widthPx) / 2,
8183
+ x: frame2.region.x + (frame2.region.width - widthPx) / 2,
6904
8184
  y: placement.descriptionY,
6905
8185
  width: widthPx,
6906
8186
  anchor: "top-left"
@@ -7009,10 +8289,10 @@ function diagramBlock(input, context) {
7009
8289
  const maxX = Math.max(...resolved.nodes.map((n) => n.x + n.w));
7010
8290
  const minY = Math.min(...resolved.nodes.map((n) => n.y));
7011
8291
  const maxY = Math.max(...resolved.nodes.map((n) => n.y + n.h));
7012
- const frame = planCanvasFrame("diagram", input.title, context);
8292
+ const frame2 = planCanvasFrame("diagram", input.title, context);
7013
8293
  const contentW = Math.max(1, maxX - minX);
7014
8294
  const contentH = Math.max(1, maxY - minY);
7015
- const placement = placeCanvasGroup(frame, contentW, contentH);
8295
+ const placement = placeCanvasGroup(frame2, contentW, contentH);
7016
8296
  const { scale, offsetX, offsetY } = placement;
7017
8297
  const fontAdj = Math.min(Math.max(scale, 1), 1.5);
7018
8298
  const strokeW = Math.round(2 * fontAdj);
@@ -7024,7 +8304,7 @@ function diagramBlock(input, context) {
7024
8304
  });
7025
8305
  const layers = [...background];
7026
8306
  if (input.title) {
7027
- const titleLayer = canvasTitleLayer("diagram-title", input.title, frame, placement, context);
8307
+ const titleLayer = canvasTitleLayer("diagram-title", input.title, frame2, placement, context);
7028
8308
  if (titleLayer) layers.push(titleLayer);
7029
8309
  }
7030
8310
  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 +8468,7 @@ function diagramBlock(input, context) {
7188
8468
  };
7189
8469
  layers.push(label);
7190
8470
  }
7191
- const descriptionLayer = canvasDescriptionLayer("diagram-description", frame, placement, context);
8471
+ const descriptionLayer = canvasDescriptionLayer("diagram-description", frame2, placement, context);
7192
8472
  if (descriptionLayer) layers.push(descriptionLayer);
7193
8473
  return layers;
7194
8474
  }
@@ -7446,8 +8726,8 @@ function timelineBlock(input, context) {
7446
8726
  const axisWidth = Math.max(1, axisEndX - axisStartX);
7447
8727
  const markerRadius = Math.max(6, Math.min(11, Math.min(viewport.width, viewport.height) / 105));
7448
8728
  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);
8729
+ const accentColor2 = colors.accent ?? theme.colors.primary;
8730
+ const branchColor = withAlpha(accentColor2, 0.78);
7451
8731
  const calloutColor = withAlpha(theme.colors.text, 0.48);
7452
8732
  const maxCalloutEvents = Math.max(
7453
8733
  1,
@@ -7679,7 +8959,7 @@ function timelineBlock(input, context) {
7679
8959
  x,
7680
8960
  y,
7681
8961
  pointMarkerRadius,
7682
- accentColor,
8962
+ accentColor2,
7683
8963
  delay,
7684
8964
  build
7685
8965
  )
@@ -7690,8 +8970,8 @@ function timelineBlock(input, context) {
7690
8970
  id: `timeline-marker-${eventLayerId}`,
7691
8971
  content: {
7692
8972
  shape: "circle",
7693
- fill: marker === "hollow" ? theme.colors.background : accentColor,
7694
- stroke: accentColor,
8973
+ fill: marker === "hollow" ? theme.colors.background : accentColor2,
8974
+ stroke: accentColor2,
7695
8975
  strokeWidth: Math.max(1.5, pointMarkerRadius * 0.28)
7696
8976
  },
7697
8977
  position: {
@@ -8110,17 +9390,17 @@ function drawingBlock(input, context) {
8110
9390
  const maxX = Math.max(...layout.shapes.map((s) => s.x + s.width));
8111
9391
  const minY = Math.min(...layout.shapes.map((s) => s.y));
8112
9392
  const maxY = Math.max(...layout.shapes.map((s) => s.y + s.height));
8113
- const frame = planCanvasFrame("drawing", input.title, context);
9393
+ const frame2 = planCanvasFrame("drawing", input.title, context);
8114
9394
  const contentW = Math.max(1, maxX - minX);
8115
9395
  const contentH = Math.max(1, maxY - minY);
8116
- const placement = placeCanvasGroup(frame, contentW, contentH);
9396
+ const placement = placeCanvasGroup(frame2, contentW, contentH);
8117
9397
  const { scale, offsetX, offsetY } = placement;
8118
9398
  const tx = (x) => offsetX + (x - minX) * scale;
8119
9399
  const ty = (y) => offsetY + (y - minY) * scale;
8120
9400
  const fontAdj = Math.min(Math.max(scale, 1), 1.5);
8121
9401
  const layers = [];
8122
9402
  if (input.title) {
8123
- const titleLayer = canvasTitleLayer("drawing-title", input.title, frame, placement, context);
9403
+ const titleLayer = canvasTitleLayer("drawing-title", input.title, frame2, placement, context);
8124
9404
  if (titleLayer) layers.push(titleLayer);
8125
9405
  }
8126
9406
  const boxes = /* @__PURE__ */ new Map();
@@ -8165,7 +9445,7 @@ function drawingBlock(input, context) {
8165
9445
  const h = s.height * scale;
8166
9446
  layers.push(...shapeLayers(s, x, y, w, h, context, defaultStroke, defaultFill, fontAdj));
8167
9447
  }
8168
- const descriptionLayer = canvasDescriptionLayer("drawing-description", frame, placement, context);
9448
+ const descriptionLayer = canvasDescriptionLayer("drawing-description", frame2, placement, context);
8169
9449
  if (descriptionLayer) layers.push(descriptionLayer);
8170
9450
  return layers;
8171
9451
  }
@@ -8175,7 +9455,7 @@ function shapeLayers(s, x, y, w, h, context, defaultStroke, defaultFill, fontAdj
8175
9455
  const fill = s.fill ?? defaultFill;
8176
9456
  if (s.kind === "text") {
8177
9457
  out.push(
8178
- textLayer2(
9458
+ textLayer3(
8179
9459
  `shape-${s.id}`,
8180
9460
  s.text ?? s.label ?? "",
8181
9461
  x + w / 2,
@@ -8234,7 +9514,7 @@ function shapeLayers(s, x, y, w, h, context, defaultStroke, defaultFill, fontAdj
8234
9514
  }
8235
9515
  if (s.label) {
8236
9516
  out.push(
8237
- textLayer2(
9517
+ textLayer3(
8238
9518
  `shape-label-${s.id}`,
8239
9519
  s.label,
8240
9520
  x + w / 2,
@@ -8249,7 +9529,7 @@ function shapeLayers(s, x, y, w, h, context, defaultStroke, defaultFill, fontAdj
8249
9529
  }
8250
9530
  if (s.sublabel) {
8251
9531
  out.push(
8252
- textLayer2(
9532
+ textLayer3(
8253
9533
  `shape-sublabel-${s.id}`,
8254
9534
  s.sublabel,
8255
9535
  x + w / 2,
@@ -8264,7 +9544,7 @@ function shapeLayers(s, x, y, w, h, context, defaultStroke, defaultFill, fontAdj
8264
9544
  }
8265
9545
  return out;
8266
9546
  }
8267
- function textLayer2(id, text, cx, cy, width, context, color, bold, fontAdj) {
9547
+ function textLayer3(id, text, cx, cy, width, context, color, bold, fontAdj) {
8268
9548
  return {
8269
9549
  type: "text",
8270
9550
  id,
@@ -8815,6 +10095,10 @@ var templateRegistry = {
8815
10095
  map: mapBlock,
8816
10096
  fullBleedQuote,
8817
10097
  list: listBlock,
10098
+ cards: cardsBlock,
10099
+ process: processBlock,
10100
+ cycle: cycleBlock,
10101
+ layers: layersBlock,
8818
10102
  photoGrid,
8819
10103
  definitionCard,
8820
10104
  comparisonBar,
@@ -9418,27 +10702,299 @@ function fallbackBlockLayers(block, context, notice) {
9418
10702
  }
9419
10703
  });
9420
10704
  }
9421
- layers.push({
9422
- type: "text",
9423
- id: "fallback-notice",
10705
+ layers.push({
10706
+ type: "text",
10707
+ id: "fallback-notice",
10708
+ content: {
10709
+ text: `\u26A0 ${notice}`,
10710
+ style: {
10711
+ fontSize: themedFontSize(18, context),
10712
+ fontFamily: getThemeFont(context, "body"),
10713
+ color: withAlpha(colors.text, 0.6),
10714
+ textAlign: "center"
10715
+ }
10716
+ },
10717
+ position: { x: "50%", y: "92%", anchor: "center", width: layout.maxTextWidth }
10718
+ });
10719
+ return layers;
10720
+ }
10721
+ function extractBodyText(block) {
10722
+ if (!block.contents || block.contents.length === 0) return "";
10723
+ const text = block.contents.filter((node) => !isPictureFence(node)).map((node) => extractPlainText(node)).join("\n").trim();
10724
+ if (text.length <= MAX_BODY_CHARS) return text;
10725
+ return `${text.slice(0, MAX_BODY_CHARS).trimEnd()}\u2026`;
10726
+ }
10727
+
10728
+ // src/doc/structuredData.ts
10729
+ var DATA_FENCE_LANGS = /* @__PURE__ */ new Set(["json", "yaml", "yml"]);
10730
+ function isDataFence(node) {
10731
+ if (!node.lang || !DATA_FENCE_LANGS.has(node.lang.toLowerCase())) return false;
10732
+ const meta = node.meta ?? "";
10733
+ return /\bdata\b/.test(meta);
10734
+ }
10735
+ function parseDataFence(node) {
10736
+ const lang = node.lang?.toLowerCase();
10737
+ if (lang === "json") {
10738
+ try {
10739
+ const parsed = JSON.parse(node.value);
10740
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
10741
+ return { error: 'JSON data fence must contain a top-level object ({"key": \u2026})' };
10742
+ }
10743
+ return { data: parsed };
10744
+ } catch (err) {
10745
+ return { error: `invalid JSON: ${err instanceof Error ? err.message : String(err)}` };
10746
+ }
10747
+ }
10748
+ try {
10749
+ return { data: parseYamlSubset(node.value) };
10750
+ } catch (err) {
10751
+ return { error: `invalid YAML: ${err instanceof Error ? err.message : String(err)}` };
10752
+ }
10753
+ }
10754
+ function replaceDataFence(contents, data) {
10755
+ const value = JSON.stringify(data, null, 2);
10756
+ const index = contents.findIndex((node) => node.type === "code" && isDataFence(node));
10757
+ if (index < 0) {
10758
+ const fence = { type: "code", lang: "json", meta: "data", value };
10759
+ return [...contents, fence];
10760
+ }
10761
+ const next = [...contents];
10762
+ next[index] = { ...contents[index], value };
10763
+ return next;
10764
+ }
10765
+ function parseScalar(raw) {
10766
+ const trimmed = raw.trim();
10767
+ if (trimmed.length >= 2) {
10768
+ const q = trimmed[0];
10769
+ if ((q === '"' || q === "'") && trimmed.endsWith(q)) {
10770
+ return trimmed.slice(1, -1);
10771
+ }
10772
+ }
10773
+ if (trimmed === "true") return true;
10774
+ if (trimmed === "false") return false;
10775
+ if (trimmed === "null" || trimmed === "~") return null;
10776
+ if (trimmed !== "" && !isNaN(Number(trimmed))) return Number(trimmed);
10777
+ return trimmed;
10778
+ }
10779
+ function splitInlineArray(interior) {
10780
+ const items = [];
10781
+ let current = "";
10782
+ let quote = null;
10783
+ for (const ch of interior) {
10784
+ if (quote) {
10785
+ current += ch;
10786
+ if (ch === quote) quote = null;
10787
+ } else if (ch === '"' || ch === "'") {
10788
+ quote = ch;
10789
+ current += ch;
10790
+ } else if (ch === ",") {
10791
+ items.push(current);
10792
+ current = "";
10793
+ } else {
10794
+ current += ch;
10795
+ }
10796
+ }
10797
+ items.push(current);
10798
+ return items.map((s) => s.trim()).filter((s) => s !== "");
10799
+ }
10800
+ function parseInlineValue(raw) {
10801
+ const trimmed = raw.trim();
10802
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
10803
+ return splitInlineArray(trimmed.slice(1, -1)).map(parseScalar);
10804
+ }
10805
+ return parseScalar(trimmed);
10806
+ }
10807
+ function parseYamlSubset(src) {
10808
+ const result = {};
10809
+ const lines = src.split("\n");
10810
+ let pendingKey = null;
10811
+ let pendingKind = null;
10812
+ let childIndent = -1;
10813
+ for (let i = 0; i < lines.length; i++) {
10814
+ const line = lines[i];
10815
+ const trimmed = line.trim();
10816
+ if (!trimmed || trimmed.startsWith("#")) continue;
10817
+ const indent = line.length - line.trimStart().length;
10818
+ if (indent > 0) {
10819
+ if (!pendingKey) {
10820
+ throw new Error(`line ${i + 1}: unexpected indented line without a preceding "key:" line`);
10821
+ }
10822
+ if (childIndent < 0) childIndent = indent;
10823
+ if (indent > childIndent) {
10824
+ throw new Error(
10825
+ `line ${i + 1}: nesting deeper than one level is not supported \u2014 use a \`\`\`json data fence for nested input`
10826
+ );
10827
+ }
10828
+ if (trimmed.startsWith("- ")) {
10829
+ if (pendingKind === "map") {
10830
+ throw new Error(
10831
+ `line ${i + 1}: cannot mix list items and mapping keys under "${pendingKey}"`
10832
+ );
10833
+ }
10834
+ pendingKind = "list";
10835
+ result[pendingKey].push(parseInlineValue(trimmed.slice(2)));
10836
+ continue;
10837
+ }
10838
+ if (pendingKind === "list") {
10839
+ throw new Error(
10840
+ `line ${i + 1}: cannot mix mapping keys and list items under "${pendingKey}"`
10841
+ );
10842
+ }
10843
+ const subColon = trimmed.indexOf(":");
10844
+ if (subColon < 1) {
10845
+ throw new Error(`line ${i + 1}: expected "key: value"`);
10846
+ }
10847
+ const subValue = trimmed.slice(subColon + 1).trim();
10848
+ if (subValue === "") {
10849
+ throw new Error(
10850
+ `line ${i + 1}: nesting deeper than one level is not supported \u2014 use a \`\`\`json data fence for nested input`
10851
+ );
10852
+ }
10853
+ if (pendingKind !== "map") {
10854
+ pendingKind = "map";
10855
+ result[pendingKey] = {};
10856
+ }
10857
+ result[pendingKey][trimmed.slice(0, subColon).trim()] = parseInlineValue(subValue);
10858
+ continue;
10859
+ }
10860
+ pendingKey = null;
10861
+ pendingKind = null;
10862
+ childIndent = -1;
10863
+ const colonIdx = trimmed.indexOf(":");
10864
+ if (colonIdx < 1) {
10865
+ throw new Error(`line ${i + 1}: expected "key: value"`);
10866
+ }
10867
+ const key = trimmed.slice(0, colonIdx).trim();
10868
+ const rawValue = trimmed.slice(colonIdx + 1).trim();
10869
+ if (rawValue === "") {
10870
+ result[key] = [];
10871
+ pendingKey = key;
10872
+ } else {
10873
+ result[key] = parseInlineValue(rawValue);
10874
+ }
10875
+ }
10876
+ return result;
10877
+ }
10878
+ function findFirstTable(contents) {
10879
+ return contents?.find((n) => n.type === "table");
10880
+ }
10881
+ function extractTableData(table) {
10882
+ const toCells = (row) => row.children.map((cell) => extractPlainText(cell).trim());
10883
+ const [headerRow, ...bodyRows] = table.children;
10884
+ return {
10885
+ headers: headerRow ? toCells(headerRow) : [],
10886
+ rows: bodyRows.map(toCells),
10887
+ ...table.align && table.align.some((a) => a != null) ? { align: table.align } : {}
10888
+ };
10889
+ }
10890
+
10891
+ // src/doc/featureTable.ts
10892
+ var CHARACTER_EM = 0.55;
10893
+ var HEADER_CHARACTER_EM = 0.6;
10894
+ var CELL_SIDE_EM = 1.5;
10895
+ var LINE_EM = 1.2;
10896
+ var CELL_TOP_EM = 1.2;
10897
+ var SAFETY = 0.92;
10898
+ function rowsOf(table) {
10899
+ return [
10900
+ ...table.headers.length ? [{ cells: table.headers, em: HEADER_CHARACTER_EM }] : [],
10901
+ ...table.rows.map((cells) => ({ cells, em: CHARACTER_EM }))
10902
+ ];
10903
+ }
10904
+ function columns(table) {
10905
+ const count = Math.max(1, ...rowsOf(table).map((row) => row.cells.length));
10906
+ return Array.from(
10907
+ { length: count },
10908
+ (_, index) => rowsOf(table).reduce(
10909
+ (column, row) => {
10910
+ const lines = (row.cells[index] ?? "").split("\n");
10911
+ const words2 = lines.flatMap((line) => line.split(/\s+/u));
10912
+ return {
10913
+ word: Math.max(column.word, ...words2.map((word) => word.length * row.em)),
10914
+ line: Math.max(column.line, ...lines.map((line) => line.trim().length * row.em))
10915
+ };
10916
+ },
10917
+ { word: 0, line: 0 }
10918
+ )
10919
+ );
10920
+ }
10921
+ function columnWidths(cols, space) {
10922
+ const words2 = cols.reduce((sum, column) => sum + column.word, 0);
10923
+ const lines = cols.reduce((sum, column) => sum + column.line, 0);
10924
+ if (words2 > space) return null;
10925
+ if (lines <= space) return cols.map((column) => column.line);
10926
+ const share = (space - words2) / Math.max(1e-6, lines - words2);
10927
+ return cols.map((column) => column.word + (column.line - column.word) * share);
10928
+ }
10929
+ function tableHeight(table, widths) {
10930
+ return rowsOf(table).reduce((height, row) => {
10931
+ const lines = Math.max(
10932
+ 1,
10933
+ ...row.cells.map(
10934
+ (cell, index) => cell.split("\n").reduce(
10935
+ (sum, line) => sum + Math.max(1, Math.ceil(line.trim().length * row.em / (widths[index] || 1))),
10936
+ 0
10937
+ )
10938
+ )
10939
+ );
10940
+ return height + lines * LINE_EM + CELL_TOP_EM;
10941
+ }, 0);
10942
+ }
10943
+ function featureTableAspect(table) {
10944
+ const cols = columns(table);
10945
+ const widths = cols.map((column) => column.line);
10946
+ const width = widths.reduce((sum, value) => sum + value + CELL_SIDE_EM, 0);
10947
+ return width / Math.max(1, tableHeight(table, widths));
10948
+ }
10949
+ function fitFeatureTable(table, box2, max, min) {
10950
+ const cols = columns(table);
10951
+ const height = (size) => {
10952
+ const widths = columnWidths(cols, box2.width * SAFETY / size - cols.length * CELL_SIDE_EM);
10953
+ return widths ? tableHeight(table, widths) * size + 4 : null;
10954
+ };
10955
+ for (let size = Math.floor(max); size > min; size--) {
10956
+ const needed = height(size);
10957
+ if (needed !== null && needed <= box2.height * SAFETY)
10958
+ return { fontSize: size, height: Math.min(box2.height, needed / SAFETY) };
10959
+ }
10960
+ return { fontSize: min, height: box2.height };
10961
+ }
10962
+ function featureTableLayer(id, table, rect2, context) {
10963
+ const { theme, viewport } = context;
10964
+ const unit = Math.min(viewport.width, viewport.height);
10965
+ const margin = unit * 0.045;
10966
+ const left = Math.max(rect2.x, margin);
10967
+ const box2 = {
10968
+ x: left,
10969
+ y: rect2.y,
10970
+ width: Math.min(rect2.x + rect2.width, viewport.width - margin) - left,
10971
+ height: Math.min(rect2.y + rect2.height, viewport.height - margin) - rect2.y
10972
+ };
10973
+ const { fontSize, height } = fitFeatureTable(table, box2, unit * 0.03, Math.max(8, unit * 8e-3));
10974
+ return {
10975
+ type: "table",
10976
+ id,
10977
+ // As tall as its rows, centered in its box.
10978
+ position: { x: box2.x, y: box2.y + (box2.height - height) / 2, width: box2.width, height },
9424
10979
  content: {
9425
- text: `\u26A0 ${notice}`,
10980
+ headers: table.headers,
10981
+ rows: table.rows,
10982
+ ...table.align ? { align: table.align } : {},
10983
+ wrap: true,
9426
10984
  style: {
9427
- fontSize: themedFontSize(18, context),
10985
+ // The theme's own color: a summary slide has no color scheme to borrow.
10986
+ headerBackground: theme.colors.primary,
10987
+ headerColor: pickContrastingText(theme.colors.primary),
10988
+ cellBackground: withAlpha(theme.colors.text, 0.04),
10989
+ cellColor: theme.colors.text,
10990
+ borderColor: withAlpha(theme.colors.text, 0.15),
10991
+ fontSize,
9428
10992
  fontFamily: getThemeFont(context, "body"),
9429
- color: withAlpha(colors.text, 0.6),
9430
- textAlign: "center"
10993
+ headerFontFamily: getThemeFont(context, "title"),
10994
+ borderRadius: 8
9431
10995
  }
9432
- },
9433
- position: { x: "50%", y: "92%", anchor: "center", width: layout.maxTextWidth }
9434
- });
9435
- return layers;
9436
- }
9437
- function extractBodyText(block) {
9438
- if (!block.contents || block.contents.length === 0) return "";
9439
- const text = block.contents.filter((node) => !isPictureFence(node)).map((node) => extractPlainText(node)).join("\n").trim();
9440
- if (text.length <= MAX_BODY_CHARS) return text;
9441
- return `${text.slice(0, MAX_BODY_CHARS).trimEnd()}\u2026`;
10996
+ }
10997
+ };
9442
10998
  }
9443
10999
 
9444
11000
  // src/doc/materializeBlockLayers.ts
@@ -9566,6 +11122,11 @@ function svgIslandAspectRatio(source) {
9566
11122
  const result = parseSvgIsland(source);
9567
11123
  return result.ok ? result.island.aspectRatio : 16 / 9;
9568
11124
  }
11125
+ function featureTables(layers, block) {
11126
+ if (!block.summaryMode || layers.some((layer) => layer.type === "table")) return [];
11127
+ if (block.template && TABLE_FED_TEMPLATES.has(resolveTemplateName(block.template))) return [];
11128
+ return (block.contents ?? []).filter((node) => node.type === "table").map(extractTableData).filter((table) => table.headers.length > 0 || table.rows.length > 0);
11129
+ }
9569
11130
  function embeddedSpatialMedia(block) {
9570
11131
  const canonicalTemplate = block.template ? resolveTemplateName(block.template) : void 0;
9571
11132
  const fences = (block.contents ?? []).filter(
@@ -9674,6 +11235,11 @@ function collectRichMediaItems(layers, block) {
9674
11235
  source,
9675
11236
  aspectRatio: svgIslandAspectRatio(source)
9676
11237
  })),
11238
+ ...featureTables(layers, block).map((table) => ({
11239
+ kind: "table",
11240
+ table,
11241
+ aspectRatio: featureTableAspect(table)
11242
+ })),
9677
11243
  ...embeddedSpatialMedia(block)
9678
11244
  ];
9679
11245
  }
@@ -9687,16 +11253,16 @@ function gridCells(region, count, viewport, inset) {
9687
11253
  width: Math.max(0, region.width - padding * 2),
9688
11254
  height: Math.max(0, region.height - padding * 2)
9689
11255
  };
9690
- const columns = Math.min(
11256
+ const columns2 = Math.min(
9691
11257
  count,
9692
11258
  Math.max(1, Math.ceil(Math.sqrt(count * (inner.width / Math.max(1, inner.height)))))
9693
11259
  );
9694
- const rows = Math.ceil(count / columns);
9695
- const width = (inner.width - gap * (columns - 1)) / columns;
11260
+ const rows = Math.ceil(count / columns2);
11261
+ const width = (inner.width - gap * (columns2 - 1)) / columns2;
9696
11262
  const height = (inner.height - gap * (rows - 1)) / rows;
9697
11263
  return Array.from({ length: count }, (_, index) => ({
9698
- x: inner.x + index % columns * (width + gap),
9699
- y: inner.y + Math.floor(index / columns) * (height + gap),
11264
+ x: inner.x + index % columns2 * (width + gap),
11265
+ y: inner.y + Math.floor(index / columns2) * (height + gap),
9700
11266
  width,
9701
11267
  height
9702
11268
  }));
@@ -9708,7 +11274,7 @@ function effectiveImageTreatment(theme, block) {
9708
11274
  if (!base || base.type === "none") return void 0;
9709
11275
  return base.type === "duotone" && !base.color ? { ...base, color: theme.colors.primary } : base;
9710
11276
  }
9711
- function richMediaLayers(item, position, block, theme, kindIndex) {
11277
+ function richMediaLayers(item, position, block, theme, viewport, kindIndex) {
9712
11278
  if (item.kind === "image") {
9713
11279
  const treatment = effectiveImageTreatment(theme, block);
9714
11280
  return [
@@ -9780,6 +11346,16 @@ function richMediaLayers(item, position, block, theme, kindIndex) {
9780
11346
  }
9781
11347
  ];
9782
11348
  }
11349
+ if (item.kind === "table") {
11350
+ return [
11351
+ featureTableLayer(
11352
+ `${block.id}-table-${kindIndex}`,
11353
+ item.table,
11354
+ position,
11355
+ createTemplateContext(theme, 0, 1, viewport)
11356
+ )
11357
+ ];
11358
+ }
9783
11359
  const localViewport = {
9784
11360
  width: position.width,
9785
11361
  height: position.height,
@@ -9833,7 +11409,7 @@ function appendRichContentLayers(layers, block, theme, viewport, mediaAspectRati
9833
11409
  const key = item.kind === "image" || item.kind === "video" ? item.src : item.kind === "mermaid" ? item.source : void 0;
9834
11410
  const intrinsic = key ? mediaAspectRatios?.[key] : void 0;
9835
11411
  if (intrinsic && Number.isFinite(intrinsic) && intrinsic > 0) return intrinsic;
9836
- return item.kind === "video" ? void 0 : item.aspectRatio;
11412
+ return item.kind === "video" || item.kind === "table" ? void 0 : item.aspectRatio;
9837
11413
  });
9838
11414
  const aspects = items.map((item, index) => knownAspects[index] ?? item.aspectRatio);
9839
11415
  const layout = block.summaryLayout === "feature" ? resolveFeatureSummaryLayout(timedLayers, viewport, aspects) : resolveSupplementalMediaLayout(
@@ -9845,7 +11421,8 @@ function appendRichContentLayers(layers, block, theme, viewport, mediaAspectRati
9845
11421
  {
9846
11422
  summaryFeature: block.summaryMode === "headings-and-features" && !timedLayers.some((layer) => layer.id === "feature-body"),
9847
11423
  stackColumns: createTemplateContext(theme, 0, 1, viewport).layout.stackColumns,
9848
- knownAspectRatios: knownAspects
11424
+ knownAspectRatios: knownAspects,
11425
+ table: items.some((item) => item.kind === "table")
9849
11426
  }
9850
11427
  );
9851
11428
  const cells = gridCells(layout.mediaRect, items.length, viewport, layout.framed);
@@ -9855,13 +11432,14 @@ function appendRichContentLayers(layers, block, theme, viewport, mediaAspectRati
9855
11432
  videoEmbed: 0,
9856
11433
  mermaid: 0,
9857
11434
  svg: 0,
11435
+ table: 0,
9858
11436
  spatial: 0
9859
11437
  };
9860
11438
  const mediaLayers = items.flatMap((item, index) => {
9861
11439
  counters[item.kind] += 1;
9862
- return richMediaLayers(item, cells[index], block, theme, counters[item.kind]);
11440
+ return richMediaLayers(item, cells[index], block, theme, viewport, counters[item.kind]);
9863
11441
  });
9864
- const frame = layout.framed ? {
11442
+ const frame2 = layout.framed ? {
9865
11443
  id: `${block.id}-rich-media-frame`,
9866
11444
  type: "shape",
9867
11445
  position: layout.mediaRect,
@@ -9873,7 +11451,7 @@ function appendRichContentLayers(layers, block, theme, viewport, mediaAspectRati
9873
11451
  borderRadius: Math.max(12, Math.round(Math.min(viewport.width, viewport.height) * 0.018))
9874
11452
  }
9875
11453
  } : void 0;
9876
- return [...layout.layers, ...frame ? [frame] : [], ...mediaLayers];
11454
+ return [...layout.layers, ...frame2 ? [frame2] : [], ...mediaLayers];
9877
11455
  }
9878
11456
  function executeTemplateMaterialization(templateBlock, context, registry) {
9879
11457
  const safeTemplateBlock = cloneData(templateBlock);
@@ -10031,6 +11609,22 @@ var TEMPLATE_METADATA = {
10031
11609
  label: "List",
10032
11610
  description: "Renders a bulleted or numbered list in a clean, card-style layout."
10033
11611
  },
11612
+ cards: {
11613
+ label: "Cards",
11614
+ description: "Shows two to six parallel ideas side by side as cards, each revealed in turn."
11615
+ },
11616
+ process: {
11617
+ label: "Process",
11618
+ description: "Shows an ordered process as numbered stages joined by arrows."
11619
+ },
11620
+ cycle: {
11621
+ label: "Cycle",
11622
+ description: "Arranges the stages of a cycle on a ring, the last leading back to the first."
11623
+ },
11624
+ layers: {
11625
+ label: "Layers",
11626
+ description: "Draws nested parts as concentric rings, innermost first, each one called out."
11627
+ },
10034
11628
  photoGrid: {
10035
11629
  label: "Photo Grid",
10036
11630
  description: "Arranges up to four photos in a mosaic grid."
@@ -10220,6 +11814,30 @@ var TEMPLATE_AUTHORING_METADATA = {
10220
11814
  safeForContentFirst: false,
10221
11815
  placement: "heading"
10222
11816
  },
11817
+ cards: {
11818
+ role: "content",
11819
+ bodyPolicy: "derived",
11820
+ safeForContentFirst: false,
11821
+ placement: "heading"
11822
+ },
11823
+ process: {
11824
+ role: "content",
11825
+ bodyPolicy: "derived",
11826
+ safeForContentFirst: false,
11827
+ placement: "heading"
11828
+ },
11829
+ cycle: {
11830
+ role: "content",
11831
+ bodyPolicy: "derived",
11832
+ safeForContentFirst: false,
11833
+ placement: "heading"
11834
+ },
11835
+ layers: {
11836
+ role: "content",
11837
+ bodyPolicy: "derived",
11838
+ safeForContentFirst: false,
11839
+ placement: "heading"
11840
+ },
10223
11841
  photoGrid: {
10224
11842
  role: "media",
10225
11843
  bodyPolicy: "structured",
@@ -10430,7 +12048,7 @@ function frostedCoverLayout(title, subtitle, context, coverLayout = "stacked") {
10430
12048
  };
10431
12049
  };
10432
12050
  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);
12051
+ const { fontSize: fontSize2, lines: lines2, textWidth: textWidth2, textHeight: textHeight2, padX: padX2, padY: padY2, radius: radius2, box: box3 } = fitTitle(FROSTED_SPLIT_TITLE_SCALE);
10434
12052
  const deckMin = themedFontSize(COVER_SUBTITLE_BASE_PX, context, false);
10435
12053
  const deck = fitFrostedText(
10436
12054
  subtitle.trim(),
@@ -10464,7 +12082,7 @@ function frostedCoverLayout(title, subtitle, context, coverLayout = "stacked") {
10464
12082
  fontSize: fontSize2,
10465
12083
  lines: lines2,
10466
12084
  plate: titlePlate,
10467
- title: box2(titlePlate.y),
12085
+ title: box3(titlePlate.y),
10468
12086
  subtitle: {
10469
12087
  cx,
10470
12088
  cy: deckPlate.y + deckHeight / 2,
@@ -10480,7 +12098,7 @@ function frostedCoverLayout(title, subtitle, context, coverLayout = "stacked") {
10480
12098
  };
10481
12099
  }
10482
12100
  }
10483
- const { fontSize, lines, column, textWidth, textHeight, padX, padY, radius, box } = fitTitle(1);
12101
+ const { fontSize, lines, column, textWidth, textHeight, padX, padY, radius, box: box2 } = fitTitle(1);
10484
12102
  let sub;
10485
12103
  if (subtitle?.trim()) {
10486
12104
  const subSize = themedFontSize(COVER_SUBTITLE_BASE_PX, context, false);
@@ -10509,7 +12127,7 @@ function frostedCoverLayout(title, subtitle, context, coverLayout = "stacked") {
10509
12127
  height: plateHeight,
10510
12128
  radius
10511
12129
  },
10512
- title: box(plateY),
12130
+ title: box2(plateY),
10513
12131
  ...sub ? {
10514
12132
  subtitle: {
10515
12133
  cx,
@@ -11188,169 +12806,6 @@ function hasTemplate(name) {
11188
12806
  return resolveTemplateName(name) in templateRegistry;
11189
12807
  }
11190
12808
 
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
12809
  // src/doc/annotationBlocks.ts
11355
12810
  function applyBlockMeta(block, m) {
11356
12811
  if (m.build) block.build = m.build;
@@ -11987,8 +13442,8 @@ function splitIntoSentences(text) {
11987
13442
  if (fragments.length === 0) return [];
11988
13443
  const split = [];
11989
13444
  for (const frag of fragments) {
11990
- const words = frag.split(/\s+/);
11991
- if (words.length > MAX_PHRASE_WORDS) {
13445
+ const words2 = frag.split(/\s+/);
13446
+ if (words2.length > MAX_PHRASE_WORDS) {
11992
13447
  const mid = Math.floor(frag.length / 2);
11993
13448
  const clauseRe = /[,;]\s+|\s+—\s+|\s+-\s+/g;
11994
13449
  let bestSplit = -1;
@@ -12290,16 +13745,16 @@ function wordIndexAtChar(script, charOffset) {
12290
13745
  }
12291
13746
  return lo;
12292
13747
  }
12293
- function wordIndexAtTime(words, tSec) {
12294
- if (words.length === 0) return 0;
13748
+ function wordIndexAtTime(words2, tSec) {
13749
+ if (words2.length === 0) return 0;
12295
13750
  let lo = 0;
12296
- let hi = words.length - 1;
13751
+ let hi = words2.length - 1;
12297
13752
  while (lo < hi) {
12298
13753
  const mid = lo + hi + 1 >> 1;
12299
- if (words[mid].tSec <= tSec) lo = mid;
13754
+ if (words2[mid].tSec <= tSec) lo = mid;
12300
13755
  else hi = mid - 1;
12301
13756
  }
12302
- return words[lo].tSec <= tSec ? lo : 0;
13757
+ return words2[lo].tSec <= tSec ? lo : 0;
12303
13758
  }
12304
13759
 
12305
13760
  // src/doc/builds.ts
@@ -12333,21 +13788,30 @@ function planContentBuilds(nodes, spec) {
12333
13788
  }
12334
13789
  function blockBuildSteps(block) {
12335
13790
  if (!block.build || block.build.mode === "off") return [];
12336
- return block.layers?.flatMap(
12337
- (layer) => layer.type === "text" ? layer.content.buildSteps ?? [] : []
12338
- ) ?? [];
13791
+ const layers = block.layers ?? [];
13792
+ const tagged = layers.filter((layer) => layer.buildStep !== void 0);
13793
+ if (tagged.length) {
13794
+ const count = Math.max(...tagged.map((layer) => layer.buildStep)) + 1;
13795
+ return Array.from({ length: count }, (_, index) => ({
13796
+ id: `build-${index}`,
13797
+ text: tagged.flatMap(
13798
+ (layer) => layer.buildStep === index && layer.type === "text" ? [layer.content.text] : []
13799
+ ).join(" ").trim()
13800
+ }));
13801
+ }
13802
+ return layers.flatMap((layer) => layer.type === "text" ? layer.content.buildSteps ?? [] : []);
12339
13803
  }
12340
13804
  function buildSourceText(steps) {
12341
- return JSON.stringify(steps.map((step) => [step.text, step.ancestors ?? []]));
13805
+ return JSON.stringify(steps.map((step2) => [step2.text, step2.ancestors ?? []]));
12342
13806
  }
12343
13807
  function resolveBuildTimeline(steps, spec, duration, timing) {
12344
13808
  const end = Number.isFinite(duration) ? Math.max(0, duration) : 0;
12345
13809
  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));
13810
+ let entrance2 = Math.min(requested, end / Math.max(4, steps.length * 2));
12347
13811
  const intro = Math.min(0.5, end * 0.08);
12348
13812
  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));
13813
+ const latest = Math.max(0, end - hold - entrance2);
13814
+ const weights = steps.map((step2) => Math.max(1, step2.text.trim().split(/\s+/).length));
12351
13815
  const total = weights.reduce((sum, weight) => sum + weight, 0);
12352
13816
  let consumed = 0;
12353
13817
  let previous = 0;
@@ -12363,21 +13827,21 @@ function resolveBuildTimeline(steps, spec, duration, timing) {
12363
13827
  return start;
12364
13828
  });
12365
13829
  if (matched && starts.length)
12366
- entrance = Math.min(
12367
- entrance,
13830
+ entrance2 = Math.min(
13831
+ entrance2,
12368
13832
  Math.max(0, end - starts[starts.length - 1] - Math.min(0.04, end / 8))
12369
13833
  );
12370
- return { starts, entrance, compressed: steps.length > 0 && end < steps.length * 0.8 };
13834
+ return { starts, entrance: entrance2, compressed: steps.length > 0 && end < steps.length * 0.8 };
12371
13835
  }
12372
- function buildCursorAtTime(timeline, time) {
13836
+ function buildCursorAtTime(timeline2, time) {
12373
13837
  let revealed = 0;
12374
- for (const start of timeline.starts) {
13838
+ for (const start of timeline2.starts) {
12375
13839
  if (time < start) break;
12376
13840
  revealed++;
12377
13841
  }
12378
13842
  return {
12379
13843
  revealed,
12380
- progress: revealed === 0 ? 0 : timeline.entrance === 0 ? 1 : Math.min(1, Math.max(0, (time - timeline.starts[revealed - 1]) / timeline.entrance))
13844
+ progress: revealed === 0 ? 0 : timeline2.entrance === 0 ? 1 : Math.min(1, Math.max(0, (time - timeline2.starts[revealed - 1]) / timeline2.entrance))
12381
13845
  };
12382
13846
  }
12383
13847
  function buildTargetState(index, steps, cursor, spec, motion = true) {
@@ -12410,11 +13874,11 @@ function resolveDocBuildTiming(doc, blocks) {
12410
13874
  if (recorded?.length) {
12411
13875
  const automatic = resolveBuildTimeline(steps, block.build, block.duration);
12412
13876
  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];
13877
+ const starts2 = steps.map((_, step2) => {
13878
+ const event = recorded.find((event2) => event2.step === step2);
13879
+ const following = recorded.filter((event2) => event2.step > step2).sort((a, b) => a.step - b.step)[0];
12416
13880
  const estimate = Math.min(
12417
- automatic.starts[step],
13881
+ automatic.starts[step2],
12418
13882
  following ? Math.max(0, following.time - block.startTime) : block.duration
12419
13883
  );
12420
13884
  last = Math.max(last, event ? event.time - block.startTime : estimate);
@@ -12432,8 +13896,8 @@ function resolveDocBuildTiming(doc, blocks) {
12432
13896
  if (!source) return block;
12433
13897
  let cursor = 0;
12434
13898
  const starts = [];
12435
- for (const step of steps) {
12436
- const text = step.text.trim();
13899
+ for (const step2 of steps) {
13900
+ const text = step2.text.trim();
12437
13901
  const offset = text ? source.text.indexOf(text, cursor) : -1;
12438
13902
  if (offset < 0) return block;
12439
13903
  const word = narration.bookmarks.find(
@@ -12450,13 +13914,14 @@ function resolveDocBuildTiming(doc, blocks) {
12450
13914
  // src/doc/templates/contentBlock.ts
12451
13915
  function bodyNodes(context) {
12452
13916
  if (context.block?.summaryMode === "headings-and-features") return [];
13917
+ const summary = !!context.block?.summaryMode;
12453
13918
  return (context.block?.contents ?? []).filter(
12454
13919
  // Mermaid source, hosted-video links and picture-only paragraphs are
12455
13920
  // already materialized as visual layers (a diagram, a player, the picture
12456
13921
  // itself). Repeating them as prose makes the loss-averse default noisy
12457
13922
  // without preserving any additional meaning — a pasted picture's alt text
12458
13923
  // is just its generated file name.
12459
- (node) => !isMaterializedMediaNode(node) && !isPictureParagraph(node)
13924
+ (node) => !isMaterializedMediaNode(node) && !isPictureParagraph(node) && !(summary && (node.type === "table" || node.type === "code"))
12460
13925
  );
12461
13926
  }
12462
13927
  function listParagraphs(list, level = 0) {
@@ -12599,6 +14064,12 @@ export {
12599
14064
  countUpSpecFor,
12600
14065
  formatCountValue,
12601
14066
  formatCountUp,
14067
+ splitConceptItem,
14068
+ conceptBuildStepTexts,
14069
+ cardsBlock,
14070
+ processBlock,
14071
+ cycleBlock,
14072
+ layersBlock,
12602
14073
  statHighlight,
12603
14074
  quoteBlock,
12604
14075
  factCard,
@@ -12652,6 +14123,12 @@ export {
12652
14123
  coerceTemplateParams,
12653
14124
  lintTemplateParams,
12654
14125
  fallbackBlockLayers,
14126
+ isDataFence,
14127
+ parseDataFence,
14128
+ replaceDataFence,
14129
+ parseYamlSubset,
14130
+ findFirstTable,
14131
+ extractTableData,
12655
14132
  materializeBlockLayers,
12656
14133
  materializeBlockLayersWithRuntime,
12657
14134
  TEMPLATE_METADATA,
@@ -12661,12 +14138,6 @@ export {
12661
14138
  expandDocBlocks,
12662
14139
  getAvailableTemplates,
12663
14140
  hasTemplate,
12664
- isDataFence,
12665
- parseDataFence,
12666
- replaceDataFence,
12667
- parseYamlSubset,
12668
- findFirstTable,
12669
- extractTableData,
12670
14141
  detectPromotableBodyAnnotation,
12671
14142
  getPinnedBlockMeta,
12672
14143
  markdownToDoc,