@bendyline/squisq 2.13.10 → 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.
- package/dist/{Doc-DqYt_b2b.d.ts → Doc-BhvotZx_.d.ts} +48 -2
- package/dist/{ImageEditDoc-CXXiXVJ-.d.ts → ImageEditDoc-D8hYio6b.d.ts} +1 -1
- package/dist/{chunk-S76W65BB.js → chunk-5DFFVW75.js} +1 -1
- package/dist/{chunk-DOOUVVOM.js → chunk-7MS26OXD.js} +4 -0
- package/dist/{chunk-XDOSE5M3.js → chunk-C4SB3NN4.js} +1 -1
- package/dist/{chunk-FXQ7C2FF.js → chunk-CQSCYJQU.js} +1 -1
- package/dist/{chunk-SG3HXPMF.js → chunk-DBVCSVNV.js} +2 -1
- package/dist/{chunk-WK6LEZ3U.js → chunk-IFZZI4XY.js} +132 -11
- package/dist/{chunk-WNEH3BZP.js → chunk-JNUNNQ74.js} +3 -3
- package/dist/{chunk-KRMAEYNM.js → chunk-OUSEEUEC.js} +389 -217
- package/dist/{chunk-LWNT6M4N.js → chunk-QJQQO3K4.js} +1857 -386
- package/dist/{chunk-DIY3NBIS.js → chunk-SGNWO7OF.js} +22 -1
- package/dist/{chunk-GLWQTZ4C.js → chunk-XEQALK5B.js} +1 -1
- package/dist/doc/index.d.ts +124 -9
- package/dist/doc/index.js +19 -7
- package/dist/fence/index.d.ts +1 -1
- package/dist/generate/index.d.ts +1 -1
- package/dist/imageEdit/index.d.ts +3 -3
- package/dist/index.d.ts +5 -5
- package/dist/index.js +23 -11
- package/dist/jsonForm/index.d.ts +1 -1
- package/dist/jsonForm/index.js +3 -3
- package/dist/markdown/index.js +3 -3
- package/dist/{materializePageSection-S2hxZ_lO.d.ts → materializePageSection-bLfqIy6j.d.ts} +1 -1
- package/dist/mediaEdit/index.d.ts +2 -2
- package/dist/narration/index.d.ts +1 -1
- package/dist/narration/index.js +5 -5
- package/dist/recommend/index.js +1 -1
- package/dist/schemas/index.d.ts +4 -4
- package/dist/schemas/index.js +2 -2
- package/dist/{themeLibrary-CFJbeItn.d.ts → themeLibrary-BIYE6aLw.d.ts} +1 -1
- package/dist/timing/index.d.ts +1 -1
- package/dist/transform/index.d.ts +23 -14
- package/dist/transform/index.js +6 -6
- package/package.json +1 -1
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
profileBlockContents,
|
|
33
33
|
renderMarkdownBlocksHtml,
|
|
34
34
|
treeToTemplateData
|
|
35
|
-
} from "./chunk-
|
|
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-
|
|
65
|
+
} from "./chunk-JNUNNQ74.js";
|
|
66
66
|
import {
|
|
67
67
|
TABLE_FED_TEMPLATES,
|
|
68
68
|
isContainerTemplate,
|
|
69
69
|
resolveTemplateName
|
|
70
|
-
} from "./chunk-
|
|
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
|
|
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 -=
|
|
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
|
|
3167
|
+
var DETAIL_LINE_HEIGHT2 = 1.4;
|
|
1935
3168
|
function statHighlight(input, context) {
|
|
1936
|
-
|
|
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(
|
|
1946
|
-
const detailFontSize = themedFontSize(
|
|
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 *
|
|
1982
|
-
lineHeight:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
3308
|
+
color: detailColor,
|
|
2070
3309
|
textAlign: "left",
|
|
2071
|
-
lineHeight:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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 -=
|
|
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
|
|
3411
|
-
const lineCount = estimateProseLineCount(
|
|
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
|
|
3562
|
-
const itemHtml = richItems[i]?.text ===
|
|
3563
|
-
const lineCount = estimateProseLineCount(
|
|
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:
|
|
4835
|
+
text: itemText2,
|
|
3597
4836
|
...itemHtml ? { html: itemHtml } : {},
|
|
3598
4837
|
paragraphs: [
|
|
3599
4838
|
{
|
|
3600
|
-
text:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
4642
|
-
const niceMin = Math.floor(lo /
|
|
4643
|
-
const niceMax = Math.ceil(hi /
|
|
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 +
|
|
4646
|
-
ticks.push(Math.abs(tick) <
|
|
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
|
|
5967
|
+
let tableHeight2 = 0;
|
|
4729
5968
|
if (options.tableRowCount > 0) {
|
|
4730
|
-
|
|
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 -
|
|
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 (
|
|
4746
|
-
tableBand = { x: marginX, y: cursor, width: innerWidth, height:
|
|
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
|
|
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
|
|
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
|
|
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 &&
|
|
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:
|
|
4952
|
-
y:
|
|
4953
|
-
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 &&
|
|
4979
|
-
layers.push(...paintLegend(paint, legend,
|
|
6217
|
+
if (legend && frame2.legendBand) {
|
|
6218
|
+
layers.push(...paintLegend(paint, legend, frame2.legendBand, legendFontSize));
|
|
4980
6219
|
}
|
|
4981
|
-
if (input.showTable === true &&
|
|
4982
|
-
layers.push(tableLayer(table,
|
|
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:
|
|
6314
|
+
x: frame2.plot.x,
|
|
5076
6315
|
y: y - (tick === 0 ? 1 : 0.5),
|
|
5077
|
-
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:
|
|
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
|
|
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 %
|
|
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:
|
|
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) =>
|
|
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 =
|
|
6374
|
+
const band = frame2.plot.width / Math.max(1, n);
|
|
5136
6375
|
const innerPad = band * 0.15;
|
|
5137
|
-
layers.push(...paintCategoryAxisX(paint, (i) =>
|
|
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 ?
|
|
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,
|
|
5193
|
-
const xFor = (v) =>
|
|
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:
|
|
6445
|
+
y: frame2.plot.y,
|
|
5207
6446
|
width: tick === 0 ? 2 : 1,
|
|
5208
|
-
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:
|
|
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 =
|
|
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:
|
|
5249
|
-
y:
|
|
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 ?
|
|
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 =
|
|
5294
|
-
const cy =
|
|
5295
|
-
const rOuter = Math.min(
|
|
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,
|
|
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) =>
|
|
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 =
|
|
5349
|
-
const xForCenter = (i) =>
|
|
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:
|
|
5381
|
-
y:
|
|
5382
|
-
width:
|
|
5383
|
-
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:
|
|
5395
|
-
y:
|
|
5396
|
-
width:
|
|
5397
|
-
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) =>
|
|
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 :
|
|
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 =
|
|
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:
|
|
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:
|
|
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 =
|
|
5476
|
-
xFor = (i) =>
|
|
5477
|
-
layers.push(...paintCategoryAxisX(paint, (i) =>
|
|
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
|
|
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 /
|
|
5971
|
-
const width = region.width /
|
|
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
|
|
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 =
|
|
6491
|
-
const 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(
|
|
6840
|
-
const scale = Math.min(
|
|
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 =
|
|
6844
|
-
const groupTop =
|
|
6845
|
-
const offsetX =
|
|
6846
|
-
const offsetY = groupTop +
|
|
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,
|
|
6858
|
-
const fit =
|
|
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:
|
|
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,
|
|
6885
|
-
const 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:
|
|
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
|
|
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(
|
|
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,
|
|
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",
|
|
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
|
|
7450
|
-
const branchColor = withAlpha(
|
|
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
|
-
|
|
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 :
|
|
7694
|
-
stroke:
|
|
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
|
|
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(
|
|
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,
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
10980
|
+
headers: table.headers,
|
|
10981
|
+
rows: table.rows,
|
|
10982
|
+
...table.align ? { align: table.align } : {},
|
|
10983
|
+
wrap: true,
|
|
9426
10984
|
style: {
|
|
9427
|
-
|
|
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
|
-
|
|
9430
|
-
|
|
10993
|
+
headerFontFamily: getThemeFont(context, "title"),
|
|
10994
|
+
borderRadius: 8
|
|
9431
10995
|
}
|
|
9432
|
-
}
|
|
9433
|
-
|
|
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
|
|
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 /
|
|
9695
|
-
const width = (inner.width - gap * (
|
|
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 %
|
|
9699
|
-
y: inner.y + Math.floor(index /
|
|
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
|
|
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, ...
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
11991
|
-
if (
|
|
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(
|
|
12294
|
-
if (
|
|
13748
|
+
function wordIndexAtTime(words2, tSec) {
|
|
13749
|
+
if (words2.length === 0) return 0;
|
|
12295
13750
|
let lo = 0;
|
|
12296
|
-
let hi =
|
|
13751
|
+
let hi = words2.length - 1;
|
|
12297
13752
|
while (lo < hi) {
|
|
12298
13753
|
const mid = lo + hi + 1 >> 1;
|
|
12299
|
-
if (
|
|
13754
|
+
if (words2[mid].tSec <= tSec) lo = mid;
|
|
12300
13755
|
else hi = mid - 1;
|
|
12301
13756
|
}
|
|
12302
|
-
return
|
|
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
|
-
|
|
12337
|
-
|
|
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((
|
|
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
|
|
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 -
|
|
12350
|
-
const weights = steps.map((
|
|
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
|
-
|
|
12367
|
-
|
|
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(
|
|
13836
|
+
function buildCursorAtTime(timeline2, time) {
|
|
12373
13837
|
let revealed = 0;
|
|
12374
|
-
for (const start of
|
|
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 :
|
|
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((_,
|
|
12414
|
-
const event = recorded.find((event2) => event2.step ===
|
|
12415
|
-
const following = recorded.filter((event2) => event2.step >
|
|
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[
|
|
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
|
|
12436
|
-
const text =
|
|
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,
|