@bendyline/squisq 2.7.1 → 2.9.0
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/{chunk-WN27GHA3.js → chunk-HEYR3TPL.js} +1 -1
- package/dist/{chunk-FPAS63KT.js → chunk-KIAEJDZB.js} +1502 -10
- package/dist/{chunk-ENNNQIYV.js → chunk-W2GIFNLN.js} +1892 -1796
- package/dist/doc/index.d.ts +675 -3
- package/dist/doc/index.js +74 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +74 -4
- package/dist/narration/index.js +2 -2
- package/package.json +1 -1
|
@@ -1066,1909 +1066,1999 @@ function bigText(input, context) {
|
|
|
1066
1066
|
return layers;
|
|
1067
1067
|
}
|
|
1068
1068
|
|
|
1069
|
-
// src/doc/templates/
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
}
|
|
1077
|
-
function
|
|
1078
|
-
const {
|
|
1079
|
-
const
|
|
1080
|
-
const
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
color: theme.colors.text,
|
|
1093
|
-
textAlign: "left",
|
|
1094
|
-
lineHeight: 1.15,
|
|
1095
|
-
shadow: shouldUseShadow(context)
|
|
1096
|
-
}
|
|
1097
|
-
},
|
|
1098
|
-
position: { x: "8%", y: "9%", width: "84%", height: "15%", anchor: "top-left" }
|
|
1099
|
-
});
|
|
1069
|
+
// src/doc/templates/diagramLayout.ts
|
|
1070
|
+
var DEFAULT_OPTS = {
|
|
1071
|
+
gapX: 260,
|
|
1072
|
+
gapY: 140,
|
|
1073
|
+
gridPad: 80,
|
|
1074
|
+
gridOriginX: 80,
|
|
1075
|
+
gridOriginY: 80
|
|
1076
|
+
};
|
|
1077
|
+
function computeDiagramLayout(children, options = {}) {
|
|
1078
|
+
const opts = { ...DEFAULT_OPTS, ...options };
|
|
1079
|
+
const warnings = [];
|
|
1080
|
+
const entries = [];
|
|
1081
|
+
const unpositionedIdx = [];
|
|
1082
|
+
for (let i = 0; i < children.length; i++) {
|
|
1083
|
+
const c = children[i];
|
|
1084
|
+
const hasX = typeof c.x === "number";
|
|
1085
|
+
const hasY = typeof c.y === "number";
|
|
1086
|
+
if (hasX && hasY) {
|
|
1087
|
+
entries.push({ child: c, pinned: true, x: c.x, y: c.y });
|
|
1088
|
+
} else {
|
|
1089
|
+
entries.push({ child: c, pinned: false, x: 0, y: 0 });
|
|
1090
|
+
unpositionedIdx.push(i);
|
|
1091
|
+
}
|
|
1100
1092
|
}
|
|
1101
|
-
if (
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
x: "8%",
|
|
1118
|
-
y: title ? "28%" : "10%",
|
|
1119
|
-
width: "84%",
|
|
1120
|
-
height: title ? "64%" : "82%",
|
|
1121
|
-
anchor: "top-left"
|
|
1122
|
-
}
|
|
1093
|
+
if (unpositionedIdx.length > 0) {
|
|
1094
|
+
const cols = Math.max(1, Math.ceil(Math.sqrt(unpositionedIdx.length)));
|
|
1095
|
+
let baseX = opts.gridOriginX;
|
|
1096
|
+
let baseY = opts.gridOriginY;
|
|
1097
|
+
const pinned = entries.filter((e) => e.pinned);
|
|
1098
|
+
if (pinned.length > 0) {
|
|
1099
|
+
const minX = Math.min(...pinned.map((e) => e.x));
|
|
1100
|
+
const maxY = Math.max(...pinned.map((e) => e.y));
|
|
1101
|
+
baseX = minX;
|
|
1102
|
+
baseY = maxY + opts.gridPad + opts.gapY;
|
|
1103
|
+
}
|
|
1104
|
+
unpositionedIdx.forEach((entryIdx, k) => {
|
|
1105
|
+
const col = k % cols;
|
|
1106
|
+
const row = Math.floor(k / cols);
|
|
1107
|
+
entries[entryIdx].x = baseX + col * opts.gapX;
|
|
1108
|
+
entries[entryIdx].y = baseY + row * opts.gapY;
|
|
1123
1109
|
});
|
|
1124
1110
|
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
const
|
|
1134
|
-
const
|
|
1135
|
-
const
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
input.id,
|
|
1143
|
-
themedImageTreatment(context, input.imageTreatment)
|
|
1144
|
-
)
|
|
1145
|
-
);
|
|
1146
|
-
}
|
|
1147
|
-
layers.push({
|
|
1148
|
-
type: "text",
|
|
1149
|
-
id: "stat",
|
|
1150
|
-
content: {
|
|
1151
|
-
text: stat,
|
|
1152
|
-
style: {
|
|
1153
|
-
fontSize: statFontSize,
|
|
1154
|
-
fontFamily: getThemeFont(context, "title"),
|
|
1155
|
-
fontWeight: "bold",
|
|
1156
|
-
color: colors.text,
|
|
1157
|
-
shadow: shouldUseShadow(context)
|
|
1158
|
-
}
|
|
1159
|
-
},
|
|
1160
|
-
position: {
|
|
1161
|
-
x: accentLayout.textCenterX,
|
|
1162
|
-
y: adjustY("36%", accentLayout),
|
|
1163
|
-
anchor: "center"
|
|
1164
|
-
},
|
|
1165
|
-
animation: getTemplateHint(context, "statHighlight", "entrance", "subtle") === "dramatic" ? { type: "zoomIn", duration: 0.4 } : { type: "zoomIn", duration: 0.6 }
|
|
1166
|
-
});
|
|
1167
|
-
layers.push({
|
|
1168
|
-
type: "text",
|
|
1169
|
-
id: "description",
|
|
1170
|
-
content: {
|
|
1171
|
-
text: description,
|
|
1172
|
-
style: {
|
|
1173
|
-
fontSize: descFontSize,
|
|
1174
|
-
fontFamily: getThemeFont(context, "body"),
|
|
1175
|
-
color: theme.colors.textMuted,
|
|
1176
|
-
textAlign: "center",
|
|
1177
|
-
lineHeight: 1.5,
|
|
1178
|
-
shadow: shouldUseShadow(context)
|
|
1111
|
+
const nodes = entries.map((e) => ({
|
|
1112
|
+
id: e.child.id,
|
|
1113
|
+
label: e.child.title ?? e.child.id,
|
|
1114
|
+
x: e.x,
|
|
1115
|
+
y: e.y,
|
|
1116
|
+
pinned: e.pinned,
|
|
1117
|
+
...e.child.classes && e.child.classes.length > 0 ? { classes: e.child.classes } : {}
|
|
1118
|
+
}));
|
|
1119
|
+
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
1120
|
+
const edges = [];
|
|
1121
|
+
const seenEdgeIds = /* @__PURE__ */ new Set();
|
|
1122
|
+
for (const child of children) {
|
|
1123
|
+
if (!child.connectsTo) continue;
|
|
1124
|
+
for (const conn of child.connectsTo) {
|
|
1125
|
+
if (!nodeIds.has(conn.target)) {
|
|
1126
|
+
warnings.push(`Edge from "${child.id}" to "${conn.target}" dropped: no such sibling node`);
|
|
1127
|
+
continue;
|
|
1179
1128
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
layers.push({
|
|
1191
|
-
type: "text",
|
|
1192
|
-
id: "detail",
|
|
1193
|
-
content: {
|
|
1194
|
-
text: detail,
|
|
1195
|
-
style: {
|
|
1196
|
-
fontSize: detailFontSize,
|
|
1197
|
-
fontFamily: getThemeFont(context, "body"),
|
|
1198
|
-
// Scheme *text* rather than *accent*: accents in the built-in
|
|
1199
|
-
// schemes are tuned as chart/fill colors and drop below legible
|
|
1200
|
-
// contrast as body copy on light surfaces.
|
|
1201
|
-
color: colors.text,
|
|
1202
|
-
textAlign: "center",
|
|
1203
|
-
shadow: shouldUseShadow(context)
|
|
1204
|
-
}
|
|
1205
|
-
},
|
|
1206
|
-
position: {
|
|
1207
|
-
x: accentLayout.textCenterX,
|
|
1208
|
-
y: adjustY("66%", accentLayout),
|
|
1209
|
-
width: accentLayout.textWidth,
|
|
1210
|
-
anchor: "center"
|
|
1211
|
-
},
|
|
1212
|
-
animation: { type: "fadeIn", duration: 1, delay: 1 }
|
|
1213
|
-
});
|
|
1129
|
+
const edgeId = makeEdgeId(child.id, conn);
|
|
1130
|
+
if (seenEdgeIds.has(edgeId)) continue;
|
|
1131
|
+
seenEdgeIds.add(edgeId);
|
|
1132
|
+
edges.push({
|
|
1133
|
+
id: edgeId,
|
|
1134
|
+
source: child.id,
|
|
1135
|
+
target: conn.target,
|
|
1136
|
+
...conn.type ? { type: conn.type } : {}
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1214
1139
|
}
|
|
1215
|
-
return
|
|
1140
|
+
return { nodes, edges, warnings };
|
|
1141
|
+
}
|
|
1142
|
+
function makeEdgeId(source, conn) {
|
|
1143
|
+
return conn.type ? `${source}->${conn.target}:${conn.type}` : `${source}->${conn.target}`;
|
|
1216
1144
|
}
|
|
1217
1145
|
|
|
1218
|
-
// src/doc/
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
)
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1146
|
+
// src/doc/asciiDiagram/types.ts
|
|
1147
|
+
var ASCII_CHAR_W = 14;
|
|
1148
|
+
var ASCII_CHAR_H = 28;
|
|
1149
|
+
|
|
1150
|
+
// src/doc/asciiDiagram/mapping.ts
|
|
1151
|
+
function asciiCellToCanvas(col, row) {
|
|
1152
|
+
return { x: col * ASCII_CHAR_W, y: row * ASCII_CHAR_H };
|
|
1153
|
+
}
|
|
1154
|
+
function canvasToAsciiCell(x, y) {
|
|
1155
|
+
return {
|
|
1156
|
+
col: Math.max(0, Math.round(x / ASCII_CHAR_W)),
|
|
1157
|
+
row: Math.max(0, Math.round(y / ASCII_CHAR_H))
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
function asciiDiagramToTemplateData(diagram) {
|
|
1161
|
+
const nodes = diagram.nodes.map((n) => ({
|
|
1162
|
+
id: n.id,
|
|
1163
|
+
label: n.label,
|
|
1164
|
+
...asciiCellToCanvas(n.col, n.row),
|
|
1165
|
+
w: n.wCols * ASCII_CHAR_W,
|
|
1166
|
+
h: n.hRows * ASCII_CHAR_H,
|
|
1167
|
+
...n.containerId ? { container: n.containerId } : {}
|
|
1168
|
+
}));
|
|
1169
|
+
const anchors = inferEdgeAnchors(nodes, diagram.edges);
|
|
1170
|
+
const edges = diagram.edges.map((e, index) => ({
|
|
1171
|
+
source: e.source,
|
|
1172
|
+
target: e.target,
|
|
1173
|
+
...e.label ? { label: e.label } : {},
|
|
1174
|
+
...e.directed ? {} : { directed: false },
|
|
1175
|
+
...anchors[index],
|
|
1176
|
+
routing: "orthogonal"
|
|
1177
|
+
}));
|
|
1178
|
+
return { nodes, edges };
|
|
1179
|
+
}
|
|
1180
|
+
function inferEdgeAnchors(nodes, edges) {
|
|
1181
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
1182
|
+
const pairs = edges.map((edge) => {
|
|
1183
|
+
const source = nodeById.get(edge.source);
|
|
1184
|
+
const target = nodeById.get(edge.target);
|
|
1185
|
+
if (!source || !target) {
|
|
1186
|
+
return {
|
|
1187
|
+
sourceAnchor: { side: "right", offset: 0.5 },
|
|
1188
|
+
targetAnchor: { side: "left", offset: 0.5 }
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
const sourceW = source.w ?? ASCII_CHAR_W * 12;
|
|
1192
|
+
const sourceH = source.h ?? ASCII_CHAR_H * 3;
|
|
1193
|
+
const targetW = target.w ?? ASCII_CHAR_W * 12;
|
|
1194
|
+
const targetH = target.h ?? ASCII_CHAR_H * 3;
|
|
1195
|
+
const dx = target.x + targetW / 2 - (source.x + sourceW / 2);
|
|
1196
|
+
const dy = target.y + targetH / 2 - (source.y + sourceH / 2);
|
|
1197
|
+
const horizontalDistance = Math.abs(dx) / Math.max(1, (sourceW + targetW) / 2);
|
|
1198
|
+
const verticalDistance = Math.abs(dy) / Math.max(1, (sourceH + targetH) / 2);
|
|
1199
|
+
if (verticalDistance >= horizontalDistance) {
|
|
1200
|
+
return dy >= 0 ? {
|
|
1201
|
+
sourceAnchor: { side: "bottom", offset: 0.5 },
|
|
1202
|
+
targetAnchor: { side: "top", offset: 0.5 }
|
|
1203
|
+
} : {
|
|
1204
|
+
sourceAnchor: { side: "top", offset: 0.5 },
|
|
1205
|
+
targetAnchor: { side: "bottom", offset: 0.5 }
|
|
1206
|
+
};
|
|
1278
1207
|
}
|
|
1208
|
+
return dx >= 0 ? {
|
|
1209
|
+
sourceAnchor: { side: "right", offset: 0.5 },
|
|
1210
|
+
targetAnchor: { side: "left", offset: 0.5 }
|
|
1211
|
+
} : {
|
|
1212
|
+
sourceAnchor: { side: "left", offset: 0.5 },
|
|
1213
|
+
targetAnchor: { side: "right", offset: 0.5 }
|
|
1214
|
+
};
|
|
1279
1215
|
});
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
shadow: shouldUseShadow(context)
|
|
1293
|
-
}
|
|
1294
|
-
},
|
|
1295
|
-
position: {
|
|
1296
|
-
x: accentLayout.textCenterX,
|
|
1297
|
-
y: adjustY(`${quoteYPct}%`, accentLayout),
|
|
1298
|
-
anchor: "center",
|
|
1299
|
-
width: accentLayout.textWidth
|
|
1300
|
-
},
|
|
1301
|
-
animation: themedEntrance(context, "text", { type: "fadeIn", duration: 2 })
|
|
1216
|
+
distributeOffsets(pairs, edges, nodes, "source");
|
|
1217
|
+
distributeOffsets(pairs, edges, nodes, "target");
|
|
1218
|
+
coalesceAlignedFanOutAnchors(pairs, edges, nodes);
|
|
1219
|
+
return pairs;
|
|
1220
|
+
}
|
|
1221
|
+
function coalesceAlignedFanOutAnchors(pairs, edges, nodes) {
|
|
1222
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
1223
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1224
|
+
edges.forEach((edge, index) => {
|
|
1225
|
+
const group = groups.get(edge.source) ?? [];
|
|
1226
|
+
group.push(index);
|
|
1227
|
+
groups.set(edge.source, group);
|
|
1302
1228
|
});
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
const
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1229
|
+
for (const [sourceId, indexes] of groups) {
|
|
1230
|
+
if (indexes.length < 2) continue;
|
|
1231
|
+
const source = nodeById.get(sourceId);
|
|
1232
|
+
const targets = indexes.map((index) => nodeById.get(edges[index].target)).filter((node) => node !== void 0);
|
|
1233
|
+
if (!source || targets.length !== indexes.length) continue;
|
|
1234
|
+
const sourceW = source.w ?? ASCII_CHAR_W * 12;
|
|
1235
|
+
const sourceH = source.h ?? ASCII_CHAR_H * 3;
|
|
1236
|
+
const sourceRight = source.x + sourceW;
|
|
1237
|
+
const sourceBottom = source.y + sourceH;
|
|
1238
|
+
const targetRight = (target) => target.x + (target.w ?? ASCII_CHAR_W * 12);
|
|
1239
|
+
const targetBottom = (target) => target.y + (target.h ?? ASCII_CHAR_H * 3);
|
|
1240
|
+
let sourceSide;
|
|
1241
|
+
let targetSide;
|
|
1242
|
+
if (targets.every((target) => target.y >= sourceBottom)) {
|
|
1243
|
+
sourceSide = "bottom";
|
|
1244
|
+
targetSide = "top";
|
|
1245
|
+
} else if (targets.every((target) => targetBottom(target) <= source.y)) {
|
|
1246
|
+
sourceSide = "top";
|
|
1247
|
+
targetSide = "bottom";
|
|
1248
|
+
} else if (targets.every((target) => target.x >= sourceRight)) {
|
|
1249
|
+
sourceSide = "right";
|
|
1250
|
+
targetSide = "left";
|
|
1251
|
+
} else if (targets.every((target) => targetRight(target) <= source.x)) {
|
|
1252
|
+
sourceSide = "left";
|
|
1253
|
+
targetSide = "right";
|
|
1254
|
+
}
|
|
1255
|
+
if (!sourceSide || !targetSide) continue;
|
|
1256
|
+
for (const index of indexes) {
|
|
1257
|
+
pairs[index] = {
|
|
1258
|
+
sourceAnchor: { side: sourceSide, offset: 0.5 },
|
|
1259
|
+
targetAnchor: { side: targetSide, offset: 0.5 }
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
function distributeOffsets(pairs, edges, nodes, endpoint) {
|
|
1265
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
1266
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1267
|
+
for (let index = 0; index < edges.length; index++) {
|
|
1268
|
+
const edge = edges[index];
|
|
1269
|
+
const anchor = endpoint === "source" ? pairs[index].sourceAnchor : pairs[index].targetAnchor;
|
|
1270
|
+
const nodeId = endpoint === "source" ? edge.source : edge.target;
|
|
1271
|
+
const key = `${nodeId}:${anchor.side}`;
|
|
1272
|
+
const group = groups.get(key) ?? [];
|
|
1273
|
+
group.push(index);
|
|
1274
|
+
groups.set(key, group);
|
|
1275
|
+
}
|
|
1276
|
+
for (const indexes of groups.values()) {
|
|
1277
|
+
if (indexes.length < 2) continue;
|
|
1278
|
+
const side = endpoint === "source" ? pairs[indexes[0]].sourceAnchor.side : pairs[indexes[0]].targetAnchor.side;
|
|
1279
|
+
indexes.sort((a, b) => {
|
|
1280
|
+
const oppositeA = nodeById.get(endpoint === "source" ? edges[a].target : edges[a].source);
|
|
1281
|
+
const oppositeB = nodeById.get(endpoint === "source" ? edges[b].target : edges[b].source);
|
|
1282
|
+
const axisA = side === "top" || side === "bottom" ? oppositeA?.x ?? 0 : oppositeA?.y ?? 0;
|
|
1283
|
+
const axisB = side === "top" || side === "bottom" ? oppositeB?.x ?? 0 : oppositeB?.y ?? 0;
|
|
1284
|
+
return axisA - axisB || a - b;
|
|
1285
|
+
});
|
|
1286
|
+
indexes.forEach((edgeIndex, rank) => {
|
|
1287
|
+
const anchor = endpoint === "source" ? pairs[edgeIndex].sourceAnchor : pairs[edgeIndex].targetAnchor;
|
|
1288
|
+
anchor.offset = (rank + 1) / (indexes.length + 1);
|
|
1330
1289
|
});
|
|
1331
1290
|
}
|
|
1332
|
-
|
|
1291
|
+
}
|
|
1292
|
+
function asciiDiagramFromTemplateData(nodes, edges, options = {}) {
|
|
1293
|
+
const ids = new Set(nodes.map((n) => n.id));
|
|
1294
|
+
const outNodes = nodes.map((n) => {
|
|
1295
|
+
const { col, row } = canvasToAsciiCell(n.x, n.y);
|
|
1296
|
+
const labelLines = n.label.length > 0 ? n.label.split("\n") : [];
|
|
1297
|
+
const innerW = labelLines.reduce((w, l) => Math.max(w, l.length), 0);
|
|
1298
|
+
const wCols = typeof n.w === "number" ? Math.max(3, Math.round(n.w / ASCII_CHAR_W)) : innerW + 4;
|
|
1299
|
+
const hRows = typeof n.h === "number" ? Math.max(3, Math.round(n.h / ASCII_CHAR_H)) : labelLines.length + 2;
|
|
1300
|
+
return {
|
|
1301
|
+
id: n.id,
|
|
1302
|
+
label: n.label,
|
|
1303
|
+
col,
|
|
1304
|
+
row,
|
|
1305
|
+
wCols: Math.max(wCols, 3),
|
|
1306
|
+
hRows: Math.max(hRows, 3),
|
|
1307
|
+
...n.container && ids.has(n.container) && n.container !== n.id ? { containerId: n.container } : {}
|
|
1308
|
+
};
|
|
1309
|
+
});
|
|
1310
|
+
return {
|
|
1311
|
+
nodes: outNodes,
|
|
1312
|
+
edges: edges.filter((e) => ids.has(e.source) && ids.has(e.target)).map((e) => ({
|
|
1313
|
+
source: e.source,
|
|
1314
|
+
target: e.target,
|
|
1315
|
+
...e.label ? { label: e.label } : {},
|
|
1316
|
+
directed: e.directed !== false
|
|
1317
|
+
})),
|
|
1318
|
+
style: options.style ?? "unicode",
|
|
1319
|
+
warnings: []
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
function asciiDiagramFromBlocks(children, options = {}) {
|
|
1323
|
+
const layout = computeDiagramLayout(children);
|
|
1324
|
+
const nodes = layout.nodes.map((n) => ({
|
|
1325
|
+
id: n.id,
|
|
1326
|
+
label: n.label,
|
|
1327
|
+
x: n.x,
|
|
1328
|
+
y: n.y
|
|
1329
|
+
}));
|
|
1330
|
+
const edges = layout.edges.map((e) => ({
|
|
1331
|
+
source: e.source,
|
|
1332
|
+
target: e.target,
|
|
1333
|
+
...e.type ? { label: e.type } : {}
|
|
1334
|
+
}));
|
|
1335
|
+
const diagram = asciiDiagramFromTemplateData(nodes, edges, options);
|
|
1336
|
+
diagram.warnings.push(...layout.warnings);
|
|
1337
|
+
return diagram;
|
|
1333
1338
|
}
|
|
1334
1339
|
|
|
1335
|
-
// src/doc/
|
|
1336
|
-
function
|
|
1337
|
-
const
|
|
1338
|
-
const
|
|
1339
|
-
const
|
|
1340
|
-
const
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1340
|
+
// src/doc/asciiTimeline/mapping.ts
|
|
1341
|
+
function asciiTimelineToTemplateData(timeline) {
|
|
1342
|
+
const globalStart = timeline.tracks.length > 0 ? Math.min(...timeline.tracks.map((track) => track.startColumn)) : 0;
|
|
1343
|
+
const globalEnd = timeline.tracks.length > 0 ? Math.max(...timeline.tracks.map((track) => track.endColumn)) : 1;
|
|
1344
|
+
const span = Math.max(1, globalEnd - globalStart);
|
|
1345
|
+
const tracks = timeline.tracks.map((track) => ({
|
|
1346
|
+
id: track.id,
|
|
1347
|
+
...track.label ? { label: track.label } : {},
|
|
1348
|
+
...track.endLabel ? { endLabel: track.endLabel } : {},
|
|
1349
|
+
events: track.events.map(
|
|
1350
|
+
(event) => ({
|
|
1351
|
+
id: event.id,
|
|
1352
|
+
label: event.label,
|
|
1353
|
+
...event.description ? { description: event.description } : {},
|
|
1354
|
+
position: clamp01((event.column - globalStart) / span),
|
|
1355
|
+
...event.side ? { side: event.side } : {},
|
|
1356
|
+
...event.descriptionSide ? { descriptionSide: event.descriptionSide } : {},
|
|
1357
|
+
...event.callout === false ? { callout: false } : {},
|
|
1358
|
+
...event.marker ? { marker: event.marker } : {}
|
|
1359
|
+
})
|
|
1360
|
+
)
|
|
1361
|
+
}));
|
|
1362
|
+
const links = timeline.links.map((link) => ({ ...link }));
|
|
1363
|
+
return { tracks, links };
|
|
1364
|
+
}
|
|
1365
|
+
function clamp01(value) {
|
|
1366
|
+
return Math.max(0, Math.min(1, value));
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// src/doc/treeview/mapping.ts
|
|
1370
|
+
function treeToTemplateData(tree) {
|
|
1371
|
+
return { items: tree.roots.map(toItem) };
|
|
1372
|
+
}
|
|
1373
|
+
function toItem(node) {
|
|
1374
|
+
return {
|
|
1375
|
+
id: node.id,
|
|
1376
|
+
label: node.label,
|
|
1377
|
+
...node.isDir ? { isDir: true } : {},
|
|
1378
|
+
...node.comment ? { comment: node.comment } : {},
|
|
1379
|
+
children: node.children.map(toItem)
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
function treeFromTemplateData(items, options = {}) {
|
|
1383
|
+
const roots = items.map(fromItem);
|
|
1384
|
+
return { roots, style: options.style ?? "unicode", warnings: [] };
|
|
1385
|
+
}
|
|
1386
|
+
function fromItem(item) {
|
|
1387
|
+
const children = Array.isArray(item.children) ? item.children.map(fromItem) : [];
|
|
1388
|
+
return {
|
|
1389
|
+
id: typeof item.id === "string" && item.id ? item.id : "node",
|
|
1390
|
+
label: typeof item.label === "string" ? item.label : "",
|
|
1391
|
+
children,
|
|
1392
|
+
isDir: item.isDir === true || children.length > 0,
|
|
1393
|
+
...item.comment ? { comment: item.comment } : {}
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
function treeFromMarkdownList(list) {
|
|
1397
|
+
const usedIds = /* @__PURE__ */ new Map();
|
|
1398
|
+
const slugify2 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "node";
|
|
1399
|
+
const idFor = (label) => {
|
|
1400
|
+
const base = slugify2(label.replace(/\/$/, ""));
|
|
1401
|
+
const count = usedIds.get(base) ?? 0;
|
|
1402
|
+
usedIds.set(base, count + 1);
|
|
1403
|
+
return count === 0 ? base : `${base}-${count + 1}`;
|
|
1404
|
+
};
|
|
1405
|
+
const itemToNode = (item) => {
|
|
1406
|
+
const labelNode = item.children.find((c) => c.type !== "list");
|
|
1407
|
+
const label = labelNode ? extractPlainText(labelNode).trim() : "";
|
|
1408
|
+
const childLists = item.children.filter((c) => c.type === "list");
|
|
1409
|
+
const children = [];
|
|
1410
|
+
for (const cl of childLists) for (const ci of cl.children) children.push(itemToNode(ci));
|
|
1411
|
+
return {
|
|
1412
|
+
id: idFor(label),
|
|
1413
|
+
label,
|
|
1414
|
+
children,
|
|
1415
|
+
isDir: label.endsWith("/") || children.length > 0
|
|
1416
|
+
};
|
|
1417
|
+
};
|
|
1418
|
+
return { roots: list.children.map(itemToNode), style: "unicode", warnings: [] };
|
|
1419
|
+
}
|
|
1420
|
+
function findFirstList(contents) {
|
|
1421
|
+
return contents?.find((n) => n.type === "list");
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// src/doc/templateInputs.ts
|
|
1425
|
+
var VIDEO_FILE_RE = /\.(?:webm|mp4|mov|m4v|ogv)(?:[?#].*)?$/i;
|
|
1426
|
+
function extractBodyPlainText(contents) {
|
|
1427
|
+
if (!contents || contents.length === 0) return "";
|
|
1428
|
+
return contents.filter((node) => !(node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")).map((n) => extractPlainText(n)).join("\n").trim();
|
|
1429
|
+
}
|
|
1430
|
+
function extractListItems(contents) {
|
|
1431
|
+
return extractRichListItems(contents).map((item) => item.text);
|
|
1432
|
+
}
|
|
1433
|
+
function escapeInlineHtml(value) {
|
|
1434
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1435
|
+
}
|
|
1436
|
+
function renderInlineHtml(nodes) {
|
|
1437
|
+
return nodes.map((node) => {
|
|
1438
|
+
switch (node.type) {
|
|
1439
|
+
case "text":
|
|
1440
|
+
return escapeInlineHtml(node.value);
|
|
1441
|
+
case "emphasis":
|
|
1442
|
+
return `<em>${renderInlineHtml(node.children)}</em>`;
|
|
1443
|
+
case "strong":
|
|
1444
|
+
return `<strong>${renderInlineHtml(node.children)}</strong>`;
|
|
1445
|
+
case "delete":
|
|
1446
|
+
return `<del>${renderInlineHtml(node.children)}</del>`;
|
|
1447
|
+
case "inlineCode":
|
|
1448
|
+
return `<code>${escapeInlineHtml(node.value)}</code>`;
|
|
1449
|
+
case "link": {
|
|
1450
|
+
const label = renderInlineHtml(node.children);
|
|
1451
|
+
const href = sanitizeUrl(node.url, "link");
|
|
1452
|
+
if (!href) return label;
|
|
1453
|
+
const title = node.title ? ` title="${escapeInlineHtml(node.title)}"` : "";
|
|
1454
|
+
return `<a href="${escapeInlineHtml(href)}"${title}>${label}</a>`;
|
|
1455
|
+
}
|
|
1456
|
+
case "break":
|
|
1457
|
+
return "<br>";
|
|
1458
|
+
case "image":
|
|
1459
|
+
return escapeInlineHtml(node.alt ?? "");
|
|
1460
|
+
case "inlineMath":
|
|
1461
|
+
return escapeInlineHtml(`$${node.value}$`);
|
|
1462
|
+
case "footnoteReference":
|
|
1463
|
+
return escapeInlineHtml(`[^${node.label ?? node.identifier}]`);
|
|
1464
|
+
case "linkReference":
|
|
1465
|
+
case "textDirective":
|
|
1466
|
+
return renderInlineHtml(node.children);
|
|
1467
|
+
case "imageReference":
|
|
1468
|
+
return escapeInlineHtml(node.alt ?? "");
|
|
1469
|
+
case "mention":
|
|
1470
|
+
return escapeInlineHtml(`@${node.displayName}`);
|
|
1471
|
+
case "inlineIcon":
|
|
1472
|
+
return `<i class="fa-${node.family} fa-${node.name}" aria-hidden="true"></i>`;
|
|
1473
|
+
case "htmlInline":
|
|
1474
|
+
return escapeInlineHtml(node.rawHtml);
|
|
1475
|
+
}
|
|
1476
|
+
}).join("");
|
|
1477
|
+
}
|
|
1478
|
+
function renderMarkdownBlocksHtml(nodes) {
|
|
1479
|
+
const renderBlocks = (blocks) => blocks.map((node, index) => {
|
|
1480
|
+
const previous = blocks[index - 1];
|
|
1481
|
+
const extraBlankLines = previous ? Math.max(0, markdownBlockSeparatorLines(previous, node) - 2) : 0;
|
|
1482
|
+
const sourceGap = '<div data-squisq-source-gap aria-hidden="true"><br></div>'.repeat(
|
|
1483
|
+
extraBlankLines
|
|
1351
1484
|
);
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
const
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1485
|
+
return `${sourceGap}${renderBlock(node)}`;
|
|
1486
|
+
}).join("");
|
|
1487
|
+
const renderList = (node) => {
|
|
1488
|
+
const tag = node.ordered ? "ol" : "ul";
|
|
1489
|
+
const start = node.ordered && node.start != null ? ` start="${node.start}"` : "";
|
|
1490
|
+
const items = node.children.map((item) => {
|
|
1491
|
+
const taskMarker = item.checked == null ? "" : `<span>${item.checked ? "[x]" : "[ ]"} </span>`;
|
|
1492
|
+
return `<li>${taskMarker}${renderBlocks(item.children)}</li>`;
|
|
1493
|
+
}).join("");
|
|
1494
|
+
return `<${tag}${start}>${items}</${tag}>`;
|
|
1495
|
+
};
|
|
1496
|
+
const renderBlock = (node) => {
|
|
1497
|
+
switch (node.type) {
|
|
1498
|
+
case "paragraph":
|
|
1499
|
+
return `<p>${renderInlineHtml(node.children)}</p>`;
|
|
1500
|
+
case "heading":
|
|
1501
|
+
return `<h${node.depth}>${renderInlineHtml(node.children)}</h${node.depth}>`;
|
|
1502
|
+
case "blockquote":
|
|
1503
|
+
return `<blockquote>${renderBlocks(node.children)}</blockquote>`;
|
|
1504
|
+
case "list":
|
|
1505
|
+
return renderList(node);
|
|
1506
|
+
case "code":
|
|
1507
|
+
return `<pre><code>${escapeInlineHtml(node.value)}</code></pre>`;
|
|
1508
|
+
case "thematicBreak":
|
|
1509
|
+
return "<hr>";
|
|
1510
|
+
case "table": {
|
|
1511
|
+
const [head, ...body] = node.children;
|
|
1512
|
+
const row = (cells, header) => `<tr>${cells.map((cell) => {
|
|
1513
|
+
const tag = header ? "th" : "td";
|
|
1514
|
+
return `<${tag}>${renderInlineHtml(cell.children)}</${tag}>`;
|
|
1515
|
+
}).join("")}</tr>`;
|
|
1516
|
+
const thead = head ? `<thead>${row(head.children, true)}</thead>` : "";
|
|
1517
|
+
const tbody = body.length ? `<tbody>${body.map((item) => row(item.children, false)).join("")}</tbody>` : "";
|
|
1518
|
+
return `<table>${thead}${tbody}</table>`;
|
|
1378
1519
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1.5 })
|
|
1387
|
-
});
|
|
1388
|
-
layers.push({
|
|
1389
|
-
type: "text",
|
|
1390
|
-
id: "explanation",
|
|
1391
|
-
content: {
|
|
1392
|
-
text: explanation,
|
|
1393
|
-
style: {
|
|
1394
|
-
fontSize: explainFontSize,
|
|
1395
|
-
fontFamily: getThemeFont(context, "body"),
|
|
1396
|
-
color: theme.colors.textMuted,
|
|
1397
|
-
textAlign: "center",
|
|
1398
|
-
lineHeight: 1.5,
|
|
1399
|
-
shadow: shouldUseShadow(context)
|
|
1520
|
+
case "math":
|
|
1521
|
+
return `<pre><code>${escapeInlineHtml(node.value)}</code></pre>`;
|
|
1522
|
+
case "definition":
|
|
1523
|
+
return "";
|
|
1524
|
+
default: {
|
|
1525
|
+
const text = extractPlainText(node).trim();
|
|
1526
|
+
return text ? `<p>${escapeInlineHtml(text)}</p>` : "";
|
|
1400
1527
|
}
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
if (source) {
|
|
1411
|
-
layers.push({
|
|
1412
|
-
type: "text",
|
|
1413
|
-
id: "source",
|
|
1414
|
-
content: {
|
|
1415
|
-
text: source,
|
|
1416
|
-
style: {
|
|
1417
|
-
fontSize: sourceFontSize,
|
|
1418
|
-
fontFamily: getThemeFont(context, "body"),
|
|
1419
|
-
color: theme.colors.textMuted,
|
|
1420
|
-
textAlign: "center",
|
|
1421
|
-
shadow: shouldUseShadow(context)
|
|
1422
|
-
}
|
|
1423
|
-
},
|
|
1424
|
-
position: {
|
|
1425
|
-
x: accentLayout.textCenterX,
|
|
1426
|
-
y: adjustY(`${sourceY}%`, accentLayout),
|
|
1427
|
-
anchor: "center"
|
|
1428
|
-
},
|
|
1429
|
-
animation: { type: "fadeIn", duration: 0.8, delay: 1.5 }
|
|
1430
|
-
});
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1530
|
+
return renderBlocks(nodes);
|
|
1531
|
+
}
|
|
1532
|
+
function markdownBlockSeparatorLines(previous, next) {
|
|
1533
|
+
const previousEndLine = previous.position?.end.line;
|
|
1534
|
+
const nextStartLine = next.position?.start.line;
|
|
1535
|
+
if (previousEndLine == null || nextStartLine == null || nextStartLine <= previousEndLine) {
|
|
1536
|
+
return 2;
|
|
1431
1537
|
}
|
|
1432
|
-
return
|
|
1538
|
+
return Math.max(2, nextStartLine - previousEndLine);
|
|
1433
1539
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
const
|
|
1442
|
-
const
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
id: "bg",
|
|
1455
|
-
content: {
|
|
1456
|
-
shape: "rect",
|
|
1457
|
-
fill: themedSurfaceGradient(context, 135)
|
|
1458
|
-
},
|
|
1459
|
-
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
1540
|
+
function renderListItemHtml(markdown) {
|
|
1541
|
+
return markdown.map(
|
|
1542
|
+
(node) => node.type === "paragraph" || node.type === "heading" ? renderInlineHtml(node.children) : escapeInlineHtml(extractPlainText(node))
|
|
1543
|
+
).join("<br>");
|
|
1544
|
+
}
|
|
1545
|
+
function extractRichListItems(contents) {
|
|
1546
|
+
if (!contents) return [];
|
|
1547
|
+
const items = [];
|
|
1548
|
+
for (const node of contents) {
|
|
1549
|
+
if (node.type !== "list") continue;
|
|
1550
|
+
for (const item of node.children) {
|
|
1551
|
+
const text = extractPlainText(item).trim();
|
|
1552
|
+
if (!text) continue;
|
|
1553
|
+
const html = renderListItemHtml(item.children);
|
|
1554
|
+
const plainHtml = escapeInlineHtml(text).replace(/\r?\n/g, "<br>");
|
|
1555
|
+
items.push({
|
|
1556
|
+
text,
|
|
1557
|
+
markdown: item.children,
|
|
1558
|
+
...html && html !== plainHtml ? { html } : {}
|
|
1559
|
+
});
|
|
1460
1560
|
}
|
|
1461
|
-
];
|
|
1462
|
-
if (!isStacked) {
|
|
1463
|
-
const panelFill = withAlpha(theme.colors.text, 0.05);
|
|
1464
|
-
layers.push({
|
|
1465
|
-
type: "shape",
|
|
1466
|
-
id: "left-panel",
|
|
1467
|
-
content: {
|
|
1468
|
-
shape: "rect",
|
|
1469
|
-
fill: panelFill,
|
|
1470
|
-
borderRadius: 12
|
|
1471
|
-
},
|
|
1472
|
-
position: { x: "3%", y: `${panelTop}%`, width: "44%", height: `${panelHeight}%` }
|
|
1473
|
-
});
|
|
1474
|
-
layers.push({
|
|
1475
|
-
type: "shape",
|
|
1476
|
-
id: "right-panel",
|
|
1477
|
-
content: {
|
|
1478
|
-
shape: "rect",
|
|
1479
|
-
fill: panelFill,
|
|
1480
|
-
borderRadius: 12
|
|
1481
|
-
},
|
|
1482
|
-
position: { x: "53%", y: `${panelTop}%`, width: "44%", height: `${panelHeight}%` }
|
|
1483
|
-
});
|
|
1484
1561
|
}
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1562
|
+
return items;
|
|
1563
|
+
}
|
|
1564
|
+
function parseDim(raw) {
|
|
1565
|
+
if (raw === void 0) return void 0;
|
|
1566
|
+
const n = parseFloat(raw);
|
|
1567
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
1568
|
+
}
|
|
1569
|
+
function extractImages(contents, limit = Infinity) {
|
|
1570
|
+
if (!contents || contents.length === 0) return [];
|
|
1571
|
+
const found = [];
|
|
1572
|
+
function fromHtml(nodes) {
|
|
1573
|
+
for (const node of nodes) {
|
|
1574
|
+
if (found.length >= limit) return;
|
|
1575
|
+
if (!node || typeof node !== "object") continue;
|
|
1576
|
+
const n = node;
|
|
1577
|
+
if (n.type === "htmlElement" && n.tagName === "img") {
|
|
1578
|
+
const attrs = n.attributes;
|
|
1579
|
+
if (attrs && typeof attrs.src === "string" && attrs.src) {
|
|
1580
|
+
found.push({
|
|
1581
|
+
src: attrs.src,
|
|
1582
|
+
alt: typeof attrs.alt === "string" ? attrs.alt : "",
|
|
1583
|
+
width: parseDim(attrs.width),
|
|
1584
|
+
height: parseDim(attrs.height)
|
|
1585
|
+
});
|
|
1498
1586
|
}
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
});
|
|
1587
|
+
}
|
|
1588
|
+
if (Array.isArray(n.children)) fromHtml(n.children);
|
|
1589
|
+
}
|
|
1503
1590
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
rightSublabelY = `${panelCenter + 7}%`;
|
|
1591
|
+
function walk(node) {
|
|
1592
|
+
if (found.length >= limit) return;
|
|
1593
|
+
if (!node || typeof node !== "object") return;
|
|
1594
|
+
const n = node;
|
|
1595
|
+
if (n.type === "image" && typeof n.url === "string" && n.url) {
|
|
1596
|
+
found.push({ src: n.url, alt: typeof n.alt === "string" ? n.alt : "" });
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
if ((n.type === "htmlBlock" || n.type === "htmlInline") && Array.isArray(n.htmlChildren)) {
|
|
1600
|
+
fromHtml(n.htmlChildren);
|
|
1601
|
+
}
|
|
1602
|
+
if (Array.isArray(n.children)) {
|
|
1603
|
+
for (const child of n.children) walk(child);
|
|
1604
|
+
}
|
|
1519
1605
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
content: {
|
|
1524
|
-
text: left.label,
|
|
1525
|
-
style: {
|
|
1526
|
-
fontSize: labelFontSize,
|
|
1527
|
-
fontFamily: getThemeFont(context, "body"),
|
|
1528
|
-
fontWeight: "bold",
|
|
1529
|
-
color: leftColors.text,
|
|
1530
|
-
textAlign: "center",
|
|
1531
|
-
lineHeight: 1.3
|
|
1532
|
-
}
|
|
1533
|
-
},
|
|
1534
|
-
position: {
|
|
1535
|
-
x: positions.left.x,
|
|
1536
|
-
y: leftY,
|
|
1537
|
-
anchor: "center",
|
|
1538
|
-
width: columnWidth
|
|
1539
|
-
},
|
|
1540
|
-
animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1 })
|
|
1541
|
-
});
|
|
1542
|
-
if (left.sublabel) {
|
|
1543
|
-
layers.push({
|
|
1544
|
-
type: "text",
|
|
1545
|
-
id: "left-sublabel",
|
|
1546
|
-
content: {
|
|
1547
|
-
text: left.sublabel,
|
|
1548
|
-
style: {
|
|
1549
|
-
fontSize: sublabelFontSize,
|
|
1550
|
-
fontFamily: getThemeFont(context, "body"),
|
|
1551
|
-
color: theme.colors.textMuted,
|
|
1552
|
-
textAlign: "center",
|
|
1553
|
-
lineHeight: 1.4
|
|
1554
|
-
}
|
|
1555
|
-
},
|
|
1556
|
-
position: {
|
|
1557
|
-
x: positions.left.x,
|
|
1558
|
-
y: leftSublabelY,
|
|
1559
|
-
anchor: "center",
|
|
1560
|
-
width: columnWidth
|
|
1561
|
-
},
|
|
1562
|
-
animation: { type: "fadeIn", duration: 0.8, delay: 0.3 }
|
|
1563
|
-
});
|
|
1606
|
+
for (const node of contents) {
|
|
1607
|
+
if (found.length >= limit) break;
|
|
1608
|
+
walk(node);
|
|
1564
1609
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1610
|
+
return found;
|
|
1611
|
+
}
|
|
1612
|
+
function extractFirstImage(contents) {
|
|
1613
|
+
return extractImages(contents, 1)[0] ?? null;
|
|
1614
|
+
}
|
|
1615
|
+
function extractEmbeddedVideos(contents, limit = Infinity) {
|
|
1616
|
+
if (!contents || contents.length === 0) return [];
|
|
1617
|
+
const found = [];
|
|
1618
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1619
|
+
const add = (video) => {
|
|
1620
|
+
if (!video.src || found.length >= limit || seen.has(video.src)) return;
|
|
1621
|
+
seen.add(video.src);
|
|
1622
|
+
found.push(video);
|
|
1623
|
+
};
|
|
1624
|
+
function nestedSource(children) {
|
|
1625
|
+
if (!Array.isArray(children)) return void 0;
|
|
1626
|
+
for (const child of children) {
|
|
1627
|
+
if (!child || typeof child !== "object") continue;
|
|
1628
|
+
const node = child;
|
|
1629
|
+
if (node.tagName === "source") {
|
|
1630
|
+
const attrs = node.attributes;
|
|
1631
|
+
if (typeof attrs?.src === "string" && attrs.src) return attrs.src;
|
|
1579
1632
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1633
|
+
const nested = nestedSource(node.children);
|
|
1634
|
+
if (nested) return nested;
|
|
1635
|
+
}
|
|
1636
|
+
return void 0;
|
|
1637
|
+
}
|
|
1638
|
+
function walk(node) {
|
|
1639
|
+
if (found.length >= limit || !node || typeof node !== "object") return;
|
|
1640
|
+
const n = node;
|
|
1641
|
+
if (n.type === "htmlElement" && n.tagName === "video") {
|
|
1642
|
+
const attrs = n.attributes;
|
|
1643
|
+
const src = attrs?.src || nestedSource(n.children);
|
|
1644
|
+
if (src) {
|
|
1645
|
+
const startAt = attrs?.["data-squisq-video-start-at"] ? parseTimeSeconds(attrs["data-squisq-video-start-at"]) : null;
|
|
1646
|
+
const clipStart = attrs?.["data-squisq-video-clip-start"] ? parseTimeSeconds(attrs["data-squisq-video-clip-start"]) : null;
|
|
1647
|
+
const clipEnd = attrs?.["data-squisq-video-clip-end"] ? parseTimeSeconds(attrs["data-squisq-video-clip-end"]) : null;
|
|
1648
|
+
add({
|
|
1649
|
+
src,
|
|
1650
|
+
...attrs?.poster ? { posterSrc: attrs.poster } : {},
|
|
1651
|
+
alt: attrs?.["aria-label"] || attrs?.title || attrs?.alt || "",
|
|
1652
|
+
...startAt != null ? { startAt } : {},
|
|
1653
|
+
...clipStart != null ? { clipStart } : {},
|
|
1654
|
+
...clipEnd != null ? { clipEnd } : {}
|
|
1655
|
+
});
|
|
1600
1656
|
}
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
});
|
|
1610
|
-
if (right.sublabel) {
|
|
1611
|
-
layers.push({
|
|
1612
|
-
type: "text",
|
|
1613
|
-
id: "right-sublabel",
|
|
1614
|
-
content: {
|
|
1615
|
-
text: right.sublabel,
|
|
1616
|
-
style: {
|
|
1617
|
-
fontSize: sublabelFontSize,
|
|
1618
|
-
fontFamily: getThemeFont(context, "body"),
|
|
1619
|
-
color: theme.colors.textMuted,
|
|
1620
|
-
textAlign: "center",
|
|
1621
|
-
lineHeight: 1.4
|
|
1622
|
-
}
|
|
1623
|
-
},
|
|
1624
|
-
position: {
|
|
1625
|
-
x: positions.right.x,
|
|
1626
|
-
y: rightSublabelY,
|
|
1627
|
-
anchor: "center",
|
|
1628
|
-
width: columnWidth
|
|
1629
|
-
},
|
|
1630
|
-
animation: { type: "fadeIn", duration: 0.8, delay: 0.8 }
|
|
1631
|
-
});
|
|
1657
|
+
} else if ((n.type === "link" || n.type === "image") && typeof n.url === "string" && VIDEO_FILE_RE.test(n.url)) {
|
|
1658
|
+
add({
|
|
1659
|
+
src: n.url,
|
|
1660
|
+
alt: typeof n.alt === "string" ? n.alt : extractPlainText(n).trim()
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
if (Array.isArray(n.children)) n.children.forEach(walk);
|
|
1664
|
+
if (Array.isArray(n.htmlChildren)) n.htmlChildren.forEach(walk);
|
|
1632
1665
|
}
|
|
1633
|
-
|
|
1666
|
+
contents.forEach(walk);
|
|
1667
|
+
return found;
|
|
1634
1668
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
const dateFontSize = themedFontSize(96, context, true);
|
|
1649
|
-
const descFontSize = themedFontSize(30, context, false);
|
|
1650
|
-
const footerFontSize = themedFontSize(26, context, false);
|
|
1651
|
-
const layers = [
|
|
1652
|
-
// Background — theme surface gradient (diagonal to differentiate
|
|
1653
|
-
// from other text templates)
|
|
1654
|
-
{
|
|
1655
|
-
type: "shape",
|
|
1656
|
-
id: "bg",
|
|
1657
|
-
content: {
|
|
1658
|
-
shape: "rect",
|
|
1659
|
-
fill: themedSurfaceGradient(context, 135)
|
|
1660
|
-
},
|
|
1661
|
-
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
1669
|
+
function extractFirstEmbeddedVideo(contents) {
|
|
1670
|
+
return extractEmbeddedVideos(contents, 1)[0] ?? null;
|
|
1671
|
+
}
|
|
1672
|
+
function extractTableFromContents(contents) {
|
|
1673
|
+
if (!contents) return null;
|
|
1674
|
+
for (const node of contents) {
|
|
1675
|
+
if (node.type === "table") {
|
|
1676
|
+
const table = node;
|
|
1677
|
+
const [headerRow, ...bodyRows] = table.children;
|
|
1678
|
+
if (!headerRow) return null;
|
|
1679
|
+
const headers = headerRow.children.map((cell) => extractPlainText(cell).trim());
|
|
1680
|
+
const rows = bodyRows.map((row) => row.children.map((cell) => extractPlainText(cell).trim()));
|
|
1681
|
+
return { headers, rows, align: table.align };
|
|
1662
1682
|
}
|
|
1663
|
-
];
|
|
1664
|
-
if (accentImage) {
|
|
1665
|
-
layers.push(
|
|
1666
|
-
...createAccentLayers(
|
|
1667
|
-
accentImage,
|
|
1668
|
-
input.id,
|
|
1669
|
-
themedImageTreatment(context, input.imageTreatment)
|
|
1670
|
-
)
|
|
1671
|
-
);
|
|
1672
1683
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
fontSize: dateFontSize,
|
|
1680
|
-
fontFamily: getThemeFont(context, "title"),
|
|
1681
|
-
fontWeight: "bold",
|
|
1682
|
-
color: dateColor,
|
|
1683
|
-
shadow: shouldUseShadow(context)
|
|
1684
|
-
}
|
|
1685
|
-
},
|
|
1686
|
-
position: {
|
|
1687
|
-
x: accentLayout.textCenterX,
|
|
1688
|
-
y: adjustY("35%", accentLayout),
|
|
1689
|
-
anchor: "center"
|
|
1690
|
-
},
|
|
1691
|
-
animation: { type: "fadeIn", duration: 1.5 }
|
|
1692
|
-
});
|
|
1693
|
-
layers.push({
|
|
1694
|
-
type: "text",
|
|
1695
|
-
id: "description",
|
|
1696
|
-
content: {
|
|
1697
|
-
text: description,
|
|
1698
|
-
style: {
|
|
1699
|
-
fontSize: descFontSize,
|
|
1700
|
-
fontFamily: getThemeFont(context, "body"),
|
|
1701
|
-
color: theme.colors.text,
|
|
1702
|
-
textAlign: "center",
|
|
1703
|
-
lineHeight: 1.6,
|
|
1704
|
-
shadow: shouldUseShadow(context)
|
|
1705
|
-
}
|
|
1706
|
-
},
|
|
1707
|
-
position: {
|
|
1708
|
-
x: accentLayout.textCenterX,
|
|
1709
|
-
y: adjustY("58%", accentLayout),
|
|
1710
|
-
width: accentLayout.textWidth,
|
|
1711
|
-
anchor: "center"
|
|
1712
|
-
},
|
|
1713
|
-
animation: { type: "fadeIn", duration: 2, delay: 1 }
|
|
1714
|
-
});
|
|
1715
|
-
if (footer) {
|
|
1716
|
-
layers.push({
|
|
1717
|
-
type: "text",
|
|
1718
|
-
id: "footer",
|
|
1719
|
-
content: {
|
|
1720
|
-
text: footer,
|
|
1721
|
-
style: {
|
|
1722
|
-
fontSize: footerFontSize,
|
|
1723
|
-
fontFamily: getThemeFont(context, "body"),
|
|
1724
|
-
color: theme.colors.textMuted,
|
|
1725
|
-
shadow: shouldUseShadow(context)
|
|
1726
|
-
}
|
|
1727
|
-
},
|
|
1728
|
-
position: {
|
|
1729
|
-
x: accentLayout.textCenterX,
|
|
1730
|
-
y: adjustY("72%", accentLayout),
|
|
1731
|
-
anchor: "center"
|
|
1732
|
-
},
|
|
1733
|
-
animation: { type: "fadeIn", duration: 1, delay: 3 }
|
|
1734
|
-
});
|
|
1684
|
+
return null;
|
|
1685
|
+
}
|
|
1686
|
+
function extractBlockquoteText(contents) {
|
|
1687
|
+
if (!contents) return "";
|
|
1688
|
+
for (const node of contents) {
|
|
1689
|
+
if (node.type === "blockquote") return extractPlainText(node).trim();
|
|
1735
1690
|
}
|
|
1736
|
-
return
|
|
1691
|
+
return "";
|
|
1737
1692
|
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
const
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1693
|
+
function firstParagraph(contents) {
|
|
1694
|
+
if (!contents) return null;
|
|
1695
|
+
const index = contents.findIndex((node) => node.type === "paragraph");
|
|
1696
|
+
if (index < 0) return null;
|
|
1697
|
+
return {
|
|
1698
|
+
node: contents[index],
|
|
1699
|
+
index
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
function leadingStrongStat(paragraph) {
|
|
1703
|
+
for (const child of paragraph.children) {
|
|
1704
|
+
if (child.type === "text" && child.value.trim() === "") continue;
|
|
1705
|
+
if (child.type !== "strong") return null;
|
|
1706
|
+
const text = extractPlainText(child).trim();
|
|
1707
|
+
const match = text.match(/^(?:[$€£¥]\s*)?[+-]?\d+(?:[.,]\d+)*(?:\s?(?:[%‰x×]|[A-Za-z]+))?/);
|
|
1708
|
+
return match?.[0].trim() || null;
|
|
1709
|
+
}
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
function removeStatPhrase(text, start, end) {
|
|
1713
|
+
const before = text.slice(0, start).trim();
|
|
1714
|
+
let after = text.slice(end);
|
|
1715
|
+
if (!before) after = after.replace(/^\s*(?:[-–—:]\s*)?/, "");
|
|
1716
|
+
else after = after.trim();
|
|
1717
|
+
return [before, after].filter(Boolean).join(" ").trim();
|
|
1718
|
+
}
|
|
1719
|
+
function bodyWithoutParagraphStat(contents, paragraphIndex, paragraphText, start, end) {
|
|
1720
|
+
if (!contents) return "";
|
|
1721
|
+
return contents.map(
|
|
1722
|
+
(node, index) => index === paragraphIndex ? removeStatPhrase(paragraphText, start, end) : extractPlainText(node).trim()
|
|
1723
|
+
).filter(Boolean).join("\n").trim();
|
|
1724
|
+
}
|
|
1725
|
+
function deriveStatHighlightInputs(headingText, contents, bodyText3) {
|
|
1726
|
+
const paragraph = firstParagraph(contents);
|
|
1727
|
+
if (paragraph) {
|
|
1728
|
+
const paragraphText = extractPlainText(paragraph.node).trim();
|
|
1729
|
+
const strongStat = leadingStrongStat(paragraph.node);
|
|
1730
|
+
if (strongStat) {
|
|
1731
|
+
const start = paragraphText.indexOf(strongStat);
|
|
1732
|
+
const description = bodyWithoutParagraphStat(
|
|
1733
|
+
contents,
|
|
1734
|
+
paragraph.index,
|
|
1735
|
+
paragraphText,
|
|
1736
|
+
start,
|
|
1737
|
+
start + strongStat.length
|
|
1738
|
+
);
|
|
1739
|
+
return {
|
|
1740
|
+
stat: strongStat,
|
|
1741
|
+
description: description || headingText || strongStat
|
|
1742
|
+
};
|
|
1776
1743
|
}
|
|
1777
|
-
];
|
|
1778
|
-
const bg = theme.colors.background;
|
|
1779
|
-
if (isTitle && caption) {
|
|
1780
|
-
layers.push({
|
|
1781
|
-
type: "shape",
|
|
1782
|
-
id: "title-gradient",
|
|
1783
|
-
content: {
|
|
1784
|
-
shape: "rect",
|
|
1785
|
-
fill: `linear-gradient(0deg, ${withAlpha(bg, 0.85)} 0%, ${withAlpha(bg, 0.45)} 40%, ${withAlpha(bg, 0.1)} 70%, ${withAlpha(bg, 0)} 100%)`
|
|
1786
|
-
},
|
|
1787
|
-
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
1788
|
-
});
|
|
1789
|
-
layers.push({
|
|
1790
|
-
type: "text",
|
|
1791
|
-
id: "title",
|
|
1792
|
-
content: {
|
|
1793
|
-
text: caption,
|
|
1794
|
-
style: {
|
|
1795
|
-
fontSize: titleFontSize,
|
|
1796
|
-
fontFamily: getThemeFont(context, "title"),
|
|
1797
|
-
fontWeight: "bold",
|
|
1798
|
-
color: theme.colors.text,
|
|
1799
|
-
textAlign: "center",
|
|
1800
|
-
shadow: shouldUseShadow(context)
|
|
1801
|
-
}
|
|
1802
|
-
},
|
|
1803
|
-
position: {
|
|
1804
|
-
x: "50%",
|
|
1805
|
-
y: subtitle ? "70%" : "75%",
|
|
1806
|
-
anchor: "center",
|
|
1807
|
-
width: layout.maxTextWidth
|
|
1808
|
-
},
|
|
1809
|
-
animation: { type: "fadeIn", duration: 0.8 }
|
|
1810
|
-
});
|
|
1811
|
-
if (subtitle) {
|
|
1812
|
-
layers.push({
|
|
1813
|
-
type: "text",
|
|
1814
|
-
id: "subtitle",
|
|
1815
|
-
content: {
|
|
1816
|
-
text: subtitle,
|
|
1817
|
-
style: {
|
|
1818
|
-
fontSize: subtitleFontSize,
|
|
1819
|
-
fontFamily: getThemeFont(context, "body"),
|
|
1820
|
-
color: theme.colors.textMuted,
|
|
1821
|
-
textAlign: "center",
|
|
1822
|
-
lineHeight: 1.5
|
|
1823
|
-
}
|
|
1824
|
-
},
|
|
1825
|
-
position: {
|
|
1826
|
-
x: "50%",
|
|
1827
|
-
y: "82%",
|
|
1828
|
-
anchor: "center",
|
|
1829
|
-
width: layout.maxTextWidth
|
|
1830
|
-
},
|
|
1831
|
-
animation: { type: "fadeIn", duration: 0.8, delay: 0.2 }
|
|
1832
|
-
});
|
|
1833
|
-
}
|
|
1834
|
-
return layers;
|
|
1835
|
-
}
|
|
1836
|
-
if (caption) {
|
|
1837
|
-
const captionBgY = captionPosition === "top" ? "7%" : captionPosition === "center" ? "42%" : "68%";
|
|
1838
|
-
const captionY = captionPosition === "top" ? "13%" : captionPosition === "center" ? "48%" : "74%";
|
|
1839
|
-
layers.push({
|
|
1840
|
-
type: "shape",
|
|
1841
|
-
id: "caption-gradient",
|
|
1842
|
-
content: {
|
|
1843
|
-
shape: "rect",
|
|
1844
|
-
fill: `linear-gradient(90deg, ${withAlpha(bg, 0)}, ${withAlpha(bg, 0.78)} 16%, ${withAlpha(bg, 0.78)} 84%, ${withAlpha(bg, 0)})`
|
|
1845
|
-
},
|
|
1846
|
-
position: {
|
|
1847
|
-
x: 0,
|
|
1848
|
-
y: captionBgY,
|
|
1849
|
-
width: "100%",
|
|
1850
|
-
height: "16%"
|
|
1851
|
-
}
|
|
1852
|
-
});
|
|
1853
|
-
layers.push({
|
|
1854
|
-
type: "text",
|
|
1855
|
-
id: "caption",
|
|
1856
|
-
content: {
|
|
1857
|
-
text: caption,
|
|
1858
|
-
style: {
|
|
1859
|
-
fontSize: captionFontSize,
|
|
1860
|
-
fontFamily: getThemeFont(context, "body"),
|
|
1861
|
-
color: theme.colors.text,
|
|
1862
|
-
textAlign: "center",
|
|
1863
|
-
shadow: shouldUseShadow(context),
|
|
1864
|
-
lineHeight: 1.18,
|
|
1865
|
-
maxLines: 2
|
|
1866
|
-
}
|
|
1867
|
-
},
|
|
1868
|
-
position: {
|
|
1869
|
-
x: "50%",
|
|
1870
|
-
y: captionY,
|
|
1871
|
-
anchor: "center",
|
|
1872
|
-
width: "78%"
|
|
1873
|
-
},
|
|
1874
|
-
animation: { type: "fadeIn", duration: 1.5, delay: 0.5 }
|
|
1875
|
-
});
|
|
1876
1744
|
}
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
function buildFeatureLayers(input, context, side) {
|
|
1882
|
-
const { imageSrc, imageAlt, imageWidth, imageHeight, title, body } = input;
|
|
1883
|
-
const { theme, layout } = context;
|
|
1884
|
-
const treatment = themedImageTreatment(context, input.imageTreatment);
|
|
1885
|
-
const sized = (imageWidth ?? 0) > 0 || (imageHeight ?? 0) > 0;
|
|
1886
|
-
const stack = layout.stackColumns;
|
|
1887
|
-
const titleFontSize = themedFontSize(48, context, true);
|
|
1888
|
-
const bodyFontSize2 = themedFontSize(24, context, false);
|
|
1889
|
-
let imgX;
|
|
1890
|
-
let imgY;
|
|
1891
|
-
let imgW;
|
|
1892
|
-
let imgH;
|
|
1893
|
-
let imageFit;
|
|
1894
|
-
if (stack) {
|
|
1895
|
-
imgX = "0";
|
|
1896
|
-
imgY = "0";
|
|
1897
|
-
imgW = "100%";
|
|
1898
|
-
imgH = "50%";
|
|
1899
|
-
imageFit = sized ? "contain" : "cover";
|
|
1900
|
-
} else if (sized) {
|
|
1901
|
-
const halfStart = side === "left" ? 5 : 55;
|
|
1902
|
-
imgX = `${halfStart}%`;
|
|
1903
|
-
imgY = "5%";
|
|
1904
|
-
imgW = "40%";
|
|
1905
|
-
imgH = "90%";
|
|
1906
|
-
imageFit = "contain";
|
|
1907
|
-
} else {
|
|
1908
|
-
imgX = side === "left" ? "0" : "50%";
|
|
1909
|
-
imgY = "0";
|
|
1910
|
-
imgW = "50%";
|
|
1911
|
-
imgH = "100%";
|
|
1912
|
-
imageFit = "cover";
|
|
1745
|
+
const trimmedHeading = headingText.trim();
|
|
1746
|
+
const headingStat = matchNumberHighlight(trimmedHeading);
|
|
1747
|
+
if (headingStat && headingStat.index === 0 && headingStat.end === trimmedHeading.length) {
|
|
1748
|
+
return { stat: headingText, description: bodyText3 || headingText };
|
|
1913
1749
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1750
|
+
if (paragraph) {
|
|
1751
|
+
const paragraphText = extractPlainText(paragraph.node).trim();
|
|
1752
|
+
const bodyStat = matchNumberHighlight(paragraphText);
|
|
1753
|
+
if (bodyStat) {
|
|
1754
|
+
const description = bodyWithoutParagraphStat(
|
|
1755
|
+
contents,
|
|
1756
|
+
paragraph.index,
|
|
1757
|
+
paragraphText,
|
|
1758
|
+
bodyStat.index,
|
|
1759
|
+
bodyStat.end
|
|
1760
|
+
);
|
|
1761
|
+
return {
|
|
1762
|
+
stat: bodyStat.value,
|
|
1763
|
+
description: description || headingText || bodyStat.value
|
|
1764
|
+
};
|
|
1927
1765
|
}
|
|
1928
|
-
];
|
|
1929
|
-
if (imageSrc) {
|
|
1930
|
-
layers.push({
|
|
1931
|
-
type: "image",
|
|
1932
|
-
id: "feature-image",
|
|
1933
|
-
content: {
|
|
1934
|
-
src: imageSrc,
|
|
1935
|
-
alt: imageAlt ?? title ?? "",
|
|
1936
|
-
fit: imageFit,
|
|
1937
|
-
...treatment ? { treatment } : {}
|
|
1938
|
-
},
|
|
1939
|
-
position: { x: imgX, y: imgY, width: imgW, height: imgH }
|
|
1940
|
-
});
|
|
1941
|
-
}
|
|
1942
|
-
if (title) {
|
|
1943
|
-
layers.push({
|
|
1944
|
-
type: "text",
|
|
1945
|
-
id: "feature-title",
|
|
1946
|
-
content: {
|
|
1947
|
-
text: title,
|
|
1948
|
-
style: {
|
|
1949
|
-
fontSize: titleFontSize,
|
|
1950
|
-
fontFamily: getThemeFont(context, "title"),
|
|
1951
|
-
fontWeight: "bold",
|
|
1952
|
-
color: theme.colors.text,
|
|
1953
|
-
textAlign,
|
|
1954
|
-
lineHeight: 1.2
|
|
1955
|
-
}
|
|
1956
|
-
},
|
|
1957
|
-
position: {
|
|
1958
|
-
x: textX,
|
|
1959
|
-
y: titleY,
|
|
1960
|
-
anchor: textAnchor,
|
|
1961
|
-
width: `${textColumnWidth}%`
|
|
1962
|
-
},
|
|
1963
|
-
animation: themedEntrance(context, "text", { type: "fadeIn", duration: 0.8 })
|
|
1964
|
-
});
|
|
1965
|
-
}
|
|
1966
|
-
if (body) {
|
|
1967
|
-
layers.push({
|
|
1968
|
-
type: "text",
|
|
1969
|
-
id: "feature-body",
|
|
1970
|
-
content: {
|
|
1971
|
-
text: body,
|
|
1972
|
-
style: {
|
|
1973
|
-
fontSize: bodyFontSize2,
|
|
1974
|
-
fontFamily: getThemeFont(context, "body"),
|
|
1975
|
-
color: theme.colors.textMuted,
|
|
1976
|
-
textAlign,
|
|
1977
|
-
lineHeight: 1.5
|
|
1978
|
-
}
|
|
1979
|
-
},
|
|
1980
|
-
position: {
|
|
1981
|
-
x: textX,
|
|
1982
|
-
y: bodyY,
|
|
1983
|
-
anchor: textAnchor,
|
|
1984
|
-
width: `${textColumnWidth}%`
|
|
1985
|
-
},
|
|
1986
|
-
animation: themedEntrance(context, "text", { type: "fadeIn", duration: 0.8, delay: 0.2 })
|
|
1987
|
-
});
|
|
1988
1766
|
}
|
|
1989
|
-
return
|
|
1767
|
+
return { stat: headingText, description: bodyText3 || headingText };
|
|
1990
1768
|
}
|
|
1991
|
-
function
|
|
1992
|
-
return
|
|
1769
|
+
function normalizeCoverageText(value) {
|
|
1770
|
+
return value.replace(/\s+/g, " ").trim();
|
|
1993
1771
|
}
|
|
1994
|
-
function
|
|
1995
|
-
|
|
1772
|
+
function autoTemplatePreservesContent(templateName, contents) {
|
|
1773
|
+
const nodes = contents ?? [];
|
|
1774
|
+
switch (templateName) {
|
|
1775
|
+
case "quote":
|
|
1776
|
+
return nodes.length === 1 && nodes[0]?.type === "blockquote";
|
|
1777
|
+
case "dataTable":
|
|
1778
|
+
return nodes.length === 1 && nodes[0]?.type === "table";
|
|
1779
|
+
case "list":
|
|
1780
|
+
return nodes.length === 1 && nodes[0]?.type === "list";
|
|
1781
|
+
case "diagram":
|
|
1782
|
+
case "timeline":
|
|
1783
|
+
return nodes.length === 1 && nodes[0]?.type === "code";
|
|
1784
|
+
case "tree":
|
|
1785
|
+
return nodes.length === 1 && (nodes[0]?.type === "code" || nodes[0]?.type === "list");
|
|
1786
|
+
case "photoGrid": {
|
|
1787
|
+
const images = extractImages(nodes);
|
|
1788
|
+
if (images.length < 2 || images.length > 4) return false;
|
|
1789
|
+
const bodyText3 = normalizeCoverageText(extractBodyPlainText(nodes));
|
|
1790
|
+
const representedText = normalizeCoverageText(
|
|
1791
|
+
images.map((image) => image.alt).filter(Boolean).join(" ")
|
|
1792
|
+
);
|
|
1793
|
+
return bodyText3 === "" || bodyText3 === representedText;
|
|
1794
|
+
}
|
|
1795
|
+
case "videoWithCaption": {
|
|
1796
|
+
const videos = extractEmbeddedVideos(nodes);
|
|
1797
|
+
if (videos.length !== 1) return false;
|
|
1798
|
+
const bodyText3 = normalizeCoverageText(extractBodyPlainText(nodes));
|
|
1799
|
+
return bodyText3 === normalizeCoverageText(videos[0]?.alt ?? "");
|
|
1800
|
+
}
|
|
1801
|
+
case "leftFeature":
|
|
1802
|
+
case "rightFeature":
|
|
1803
|
+
case "statHighlight":
|
|
1804
|
+
return true;
|
|
1805
|
+
default:
|
|
1806
|
+
return false;
|
|
1807
|
+
}
|
|
1996
1808
|
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
const titleFontSize = themedFontSize(64, context, true);
|
|
2012
|
-
const captionFontSize = themedFontSize(32, context, false);
|
|
2013
|
-
const mapAnimation = mapAmbientMotion(ambientMotion);
|
|
2014
|
-
const layers = [
|
|
2015
|
-
// Map background
|
|
2016
|
-
{
|
|
2017
|
-
type: "map",
|
|
2018
|
-
id: "map-bg",
|
|
2019
|
-
content: {
|
|
2020
|
-
center,
|
|
2021
|
-
zoom,
|
|
2022
|
-
style: mapStyle,
|
|
2023
|
-
markers,
|
|
2024
|
-
showAttribution: true,
|
|
2025
|
-
staticSrc
|
|
2026
|
-
// Use pre-rendered image if available
|
|
2027
|
-
},
|
|
2028
|
-
position: { x: 0, y: 0, width: "100%", height: "100%" },
|
|
2029
|
-
animation: mapAnimation
|
|
1809
|
+
function deriveTemplateInputs(templateName, headingText, contents, options = {}) {
|
|
1810
|
+
const placeholders = options.placeholders === true;
|
|
1811
|
+
const bodyText3 = extractBodyPlainText(contents);
|
|
1812
|
+
switch (templateName) {
|
|
1813
|
+
case "statHighlight": {
|
|
1814
|
+
const inputs = deriveStatHighlightInputs(headingText, contents, bodyText3);
|
|
1815
|
+
if (options.preserveSourceHeading && headingText && inputs.stat !== headingText && inputs.description !== headingText) {
|
|
1816
|
+
return {
|
|
1817
|
+
...inputs,
|
|
1818
|
+
description: headingText,
|
|
1819
|
+
...inputs.description ? { detail: inputs.description } : {}
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
return inputs;
|
|
2030
1823
|
}
|
|
2031
|
-
|
|
2032
|
-
|
|
1824
|
+
case "quote": {
|
|
1825
|
+
const quote = extractBlockquoteText(contents) || bodyText3;
|
|
1826
|
+
if (quote) return { quote, ...headingText ? { title: headingText } : {} };
|
|
1827
|
+
return { quote: headingText };
|
|
1828
|
+
}
|
|
1829
|
+
case "fullBleedQuote":
|
|
1830
|
+
case "pullQuote": {
|
|
1831
|
+
const text = extractBlockquoteText(contents) || bodyText3 || headingText;
|
|
1832
|
+
return { text };
|
|
1833
|
+
}
|
|
1834
|
+
case "factCard":
|
|
1835
|
+
return { fact: headingText, explanation: bodyText3 || headingText };
|
|
1836
|
+
case "comparisonBar":
|
|
1837
|
+
return placeholders ? { leftLabel: "A", leftValue: 60, rightLabel: "B", rightValue: 40 } : null;
|
|
1838
|
+
case "list": {
|
|
1839
|
+
const items = extractListItems(contents);
|
|
1840
|
+
if (items.length > 0) return { items, ...headingText ? { title: headingText } : {} };
|
|
1841
|
+
return placeholders ? { items: ["Item 1", "Item 2", "Item 3"] } : null;
|
|
1842
|
+
}
|
|
1843
|
+
case "definitionCard":
|
|
1844
|
+
return { term: headingText, definition: bodyText3 || headingText };
|
|
1845
|
+
case "dateEvent":
|
|
1846
|
+
return { date: headingText, description: bodyText3 || headingText };
|
|
1847
|
+
case "dataTable": {
|
|
1848
|
+
const tableData = extractTableFromContents(contents);
|
|
1849
|
+
if (tableData) return { ...tableData, ...headingText ? { title: headingText } : {} };
|
|
1850
|
+
return placeholders ? { headers: ["Column"], rows: [["Data"]] } : null;
|
|
1851
|
+
}
|
|
1852
|
+
case "barChart":
|
|
1853
|
+
case "columnChart":
|
|
1854
|
+
case "pieChart":
|
|
1855
|
+
case "donutChart":
|
|
1856
|
+
case "lineChart":
|
|
1857
|
+
case "areaChart":
|
|
1858
|
+
case "scatterChart": {
|
|
1859
|
+
const tableData = extractTableFromContents(contents);
|
|
1860
|
+
if (tableData) {
|
|
1861
|
+
return {
|
|
1862
|
+
headers: tableData.headers,
|
|
1863
|
+
rows: tableData.rows,
|
|
1864
|
+
...headingText ? { title: headingText } : {}
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
return placeholders && !bodyText3.trim() ? {
|
|
1868
|
+
headers: ["Quarter", "Revenue", "Costs"],
|
|
1869
|
+
rows: [
|
|
1870
|
+
["Q1", "40", "28"],
|
|
1871
|
+
["Q2", "55", "31"],
|
|
1872
|
+
["Q3", "48", "30"],
|
|
1873
|
+
["Q4", "62", "35"]
|
|
1874
|
+
],
|
|
1875
|
+
...headingText ? { title: headingText } : {}
|
|
1876
|
+
} : null;
|
|
1877
|
+
}
|
|
1878
|
+
case "imageWithCaption": {
|
|
1879
|
+
const img = extractFirstImage(contents);
|
|
1880
|
+
if (!img) return placeholders ? { caption: headingText } : null;
|
|
1881
|
+
return { imageSrc: img.src, imageAlt: img.alt || headingText, caption: headingText };
|
|
1882
|
+
}
|
|
1883
|
+
case "bigText": {
|
|
1884
|
+
const img = extractFirstImage(contents);
|
|
1885
|
+
return {
|
|
1886
|
+
title: headingText,
|
|
1887
|
+
...img ? { imageSrc: img.src, imageAlt: img.alt || headingText } : {}
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
case "photoGrid": {
|
|
1891
|
+
const images = extractImages(contents, 4);
|
|
1892
|
+
if (images.length < 2) return placeholders ? {} : null;
|
|
1893
|
+
return {
|
|
1894
|
+
images: images.map((i) => ({ src: i.src, alt: i.alt })),
|
|
1895
|
+
caption: headingText
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
case "videoWithCaption": {
|
|
1899
|
+
const video = extractFirstEmbeddedVideo(contents);
|
|
1900
|
+
if (!video) return placeholders ? { caption: headingText } : null;
|
|
1901
|
+
return {
|
|
1902
|
+
videoSrc: video.src,
|
|
1903
|
+
posterSrc: video.posterSrc,
|
|
1904
|
+
videoAlt: video.alt || headingText,
|
|
1905
|
+
caption: headingText
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
case "leftFeature":
|
|
1909
|
+
case "rightFeature": {
|
|
1910
|
+
const img = extractFirstImage(contents);
|
|
1911
|
+
if (!img && !placeholders) return null;
|
|
1912
|
+
return {
|
|
1913
|
+
imageSrc: img?.src ?? "",
|
|
1914
|
+
imageAlt: img?.alt || headingText,
|
|
1915
|
+
imageWidth: img?.width,
|
|
1916
|
+
imageHeight: img?.height,
|
|
1917
|
+
title: headingText,
|
|
1918
|
+
body: bodyText3
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
case "diagram": {
|
|
1922
|
+
const fences = (contents ?? []).filter((n) => n.type === "code");
|
|
1923
|
+
const fence = fences.length === 1 && isEligibleAsciiFenceLang(fences[0].lang) ? fences[0] : void 0;
|
|
1924
|
+
const detection = fence ? detectAsciiDiagram(fence.value, { explicit: isExplicitDiagramLang(fence.lang) }) : void 0;
|
|
1925
|
+
if (!detection?.isDiagram || !detection.diagram) return placeholders ? {} : null;
|
|
1926
|
+
const { nodes, edges } = asciiDiagramToTemplateData(detection.diagram);
|
|
1927
|
+
return { nodes, edges, ...headingText ? { title: headingText } : {} };
|
|
1928
|
+
}
|
|
1929
|
+
case "timeline": {
|
|
1930
|
+
const fences = (contents ?? []).filter((n) => n.type === "code");
|
|
1931
|
+
const fence = fences.length === 1 && isEligibleAsciiTimelineFenceLang(fences[0].lang) ? fences[0] : void 0;
|
|
1932
|
+
const detection = fence ? detectAsciiTimeline(fence.value, { explicit: true }) : void 0;
|
|
1933
|
+
if (!detection?.isTimeline || !detection.timeline) return placeholders ? {} : null;
|
|
1934
|
+
const { tracks, links } = asciiTimelineToTemplateData(detection.timeline);
|
|
1935
|
+
return { tracks, links, ...headingText ? { title: headingText } : {} };
|
|
1936
|
+
}
|
|
1937
|
+
case "tree": {
|
|
1938
|
+
const fences = (contents ?? []).filter((n) => n.type === "code");
|
|
1939
|
+
const fence = fences.length === 1 && isEligibleTreeFenceLang(fences[0].lang) ? fences[0] : void 0;
|
|
1940
|
+
if (fence) {
|
|
1941
|
+
const detection = detectTree(fence.value, { explicit: isExplicitTreeLang(fence.lang) });
|
|
1942
|
+
if (detection.isTree && detection.tree) {
|
|
1943
|
+
const { items } = treeToTemplateData(detection.tree);
|
|
1944
|
+
return { items, ...headingText ? { title: headingText } : {} };
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
const list = findFirstList(contents);
|
|
1948
|
+
if (list) {
|
|
1949
|
+
const { items } = treeToTemplateData(treeFromMarkdownList(list));
|
|
1950
|
+
if (items.length > 0) return { items, ...headingText ? { title: headingText } : {} };
|
|
1951
|
+
}
|
|
1952
|
+
return placeholders ? {} : null;
|
|
1953
|
+
}
|
|
1954
|
+
default:
|
|
1955
|
+
return placeholders ? {} : null;
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// src/doc/templates/contentBlock.ts
|
|
1960
|
+
function bodyNodes(context) {
|
|
1961
|
+
return (context.block?.contents ?? []).filter(
|
|
1962
|
+
// Mermaid source is already materialized as a visual layer. Repeating the
|
|
1963
|
+
// fence text as prose makes the loss-averse default noisy without
|
|
1964
|
+
// preserving any additional authored meaning.
|
|
1965
|
+
(node) => !(node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")
|
|
1966
|
+
);
|
|
1967
|
+
}
|
|
1968
|
+
function plainListText(list, depth = 0) {
|
|
1969
|
+
const indent = " ".repeat(depth);
|
|
1970
|
+
return list.children.flatMap((item, index) => {
|
|
1971
|
+
const ownText = item.children.filter((node) => node.type !== "list").map((node) => extractPlainText(node).trim()).filter(Boolean).join(" ");
|
|
1972
|
+
const marker = list.ordered ? `${(list.start ?? 1) + index}.` : "\u2022";
|
|
1973
|
+
const lines = ownText ? [`${indent}${marker} ${ownText}`] : [];
|
|
1974
|
+
for (const child of item.children) {
|
|
1975
|
+
if (child.type === "list") lines.push(plainListText(child, depth + 1));
|
|
1976
|
+
}
|
|
1977
|
+
return lines;
|
|
1978
|
+
}).join("\n");
|
|
1979
|
+
}
|
|
1980
|
+
function completeBodyText(nodes) {
|
|
1981
|
+
return nodes.map((node, index) => {
|
|
1982
|
+
const text = node.type === "list" ? plainListText(node) : extractPlainText(node);
|
|
1983
|
+
if (index === 0) return text;
|
|
1984
|
+
const separator = "\n".repeat(markdownBlockSeparatorLines(nodes[index - 1], node));
|
|
1985
|
+
return `${separator}${text}`;
|
|
1986
|
+
}).join("").trim();
|
|
1987
|
+
}
|
|
1988
|
+
function bodyFontSize(body, context) {
|
|
1989
|
+
const base = body.length > 1800 ? 18 : body.length > 1100 ? 21 : body.length > 650 ? 24 : 28;
|
|
1990
|
+
return themedFontSize(base, context, false);
|
|
1991
|
+
}
|
|
1992
|
+
function contentBlock(input, context) {
|
|
1993
|
+
const { theme } = context;
|
|
1994
|
+
const title = input.title ?? context.block?.title ?? "";
|
|
1995
|
+
const markdown = bodyNodes(context);
|
|
1996
|
+
const body = completeBodyText(markdown);
|
|
1997
|
+
const bodyHtml = renderMarkdownBlocksHtml(markdown);
|
|
1998
|
+
const layers = [createBackgroundLayer("bg", theme.colors.background)];
|
|
2033
1999
|
if (title) {
|
|
2034
|
-
layers.push({
|
|
2035
|
-
type: "shape",
|
|
2036
|
-
id: "title-overlay",
|
|
2037
|
-
content: {
|
|
2038
|
-
shape: "rect",
|
|
2039
|
-
fill: `linear-gradient(180deg, ${withAlpha(bg, 0.85)} 0%, ${withAlpha(bg, 0.6)} 70%, ${withAlpha(bg, 0)} 100%)`
|
|
2040
|
-
},
|
|
2041
|
-
position: { x: 0, y: 0, width: "100%", height: "20%" }
|
|
2042
|
-
});
|
|
2043
2000
|
layers.push({
|
|
2044
2001
|
type: "text",
|
|
2045
2002
|
id: "title",
|
|
2046
2003
|
content: {
|
|
2047
2004
|
text: title,
|
|
2048
2005
|
style: {
|
|
2049
|
-
fontSize:
|
|
2006
|
+
fontSize: themedFontSize(46, context, true),
|
|
2050
2007
|
fontFamily: getThemeFont(context, "title"),
|
|
2051
2008
|
fontWeight: "bold",
|
|
2052
2009
|
color: theme.colors.text,
|
|
2053
|
-
textAlign: "
|
|
2010
|
+
textAlign: "left",
|
|
2011
|
+
lineHeight: 1.15,
|
|
2054
2012
|
shadow: shouldUseShadow(context)
|
|
2055
2013
|
}
|
|
2056
2014
|
},
|
|
2057
|
-
position: {
|
|
2058
|
-
x: "50%",
|
|
2059
|
-
y: "9%",
|
|
2060
|
-
anchor: "center",
|
|
2061
|
-
width: layout.maxTextWidth
|
|
2062
|
-
},
|
|
2063
|
-
animation: { type: "fadeIn", duration: 1, delay: 0.3 }
|
|
2015
|
+
position: { x: "8%", y: "9%", width: "84%", height: "15%", anchor: "top-left" }
|
|
2064
2016
|
});
|
|
2065
2017
|
}
|
|
2066
|
-
if (
|
|
2067
|
-
layers.push({
|
|
2068
|
-
type: "shape",
|
|
2069
|
-
id: "caption-overlay",
|
|
2070
|
-
content: {
|
|
2071
|
-
shape: "rect",
|
|
2072
|
-
fill: `linear-gradient(0deg, ${withAlpha(bg, 0.85)} 0%, ${withAlpha(bg, 0.6)} 70%, ${withAlpha(bg, 0)} 100%)`
|
|
2073
|
-
},
|
|
2074
|
-
position: { x: 0, y: "80%", width: "100%", height: "20%" }
|
|
2075
|
-
});
|
|
2018
|
+
if (body) {
|
|
2076
2019
|
layers.push({
|
|
2077
2020
|
type: "text",
|
|
2078
|
-
id: "
|
|
2021
|
+
id: "body",
|
|
2079
2022
|
content: {
|
|
2080
|
-
text:
|
|
2023
|
+
text: body,
|
|
2024
|
+
...bodyHtml ? { html: bodyHtml } : {},
|
|
2081
2025
|
style: {
|
|
2082
|
-
fontSize:
|
|
2026
|
+
fontSize: bodyFontSize(body, context),
|
|
2083
2027
|
fontFamily: getThemeFont(context, "body"),
|
|
2084
2028
|
color: theme.colors.text,
|
|
2085
|
-
textAlign: "
|
|
2029
|
+
textAlign: "left",
|
|
2030
|
+
lineHeight: 1.35,
|
|
2086
2031
|
shadow: shouldUseShadow(context)
|
|
2087
2032
|
}
|
|
2088
2033
|
},
|
|
2089
2034
|
position: {
|
|
2090
|
-
x: "
|
|
2091
|
-
y:
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2035
|
+
x: "8%",
|
|
2036
|
+
y: title ? "28%" : "10%",
|
|
2037
|
+
width: "84%",
|
|
2038
|
+
height: title ? "64%" : "82%",
|
|
2039
|
+
anchor: "top-left"
|
|
2040
|
+
}
|
|
2096
2041
|
});
|
|
2097
2042
|
}
|
|
2098
2043
|
return layers;
|
|
2099
2044
|
}
|
|
2100
2045
|
|
|
2101
|
-
// src/doc/templates/
|
|
2102
|
-
function
|
|
2103
|
-
const {
|
|
2046
|
+
// src/doc/templates/statHighlight.ts
|
|
2047
|
+
function statHighlight(input, context) {
|
|
2048
|
+
const { stat, description, detail, colorScheme = "blue", accentImage } = input;
|
|
2049
|
+
const { theme } = context;
|
|
2104
2050
|
const colors = resolveColorScheme(context, colorScheme);
|
|
2105
|
-
const
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2051
|
+
const accentLayout = accentImage ? getAccentLayout(accentImage.position) : DEFAULT_LAYOUT;
|
|
2052
|
+
const statFontSize = themedFontSize(148, context, true);
|
|
2053
|
+
const descFontSize = themedFontSize(32, context, false);
|
|
2054
|
+
const detailFontSize = themedFontSize(26, context, false);
|
|
2055
|
+
const layers = [createBackgroundLayer("bg", themedSurfaceGradient(context, 180))];
|
|
2056
|
+
if (accentImage) {
|
|
2057
|
+
layers.push(
|
|
2058
|
+
...createAccentLayers(
|
|
2059
|
+
accentImage,
|
|
2060
|
+
input.id,
|
|
2061
|
+
themedImageTreatment(context, input.imageTreatment)
|
|
2062
|
+
)
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
layers.push({
|
|
2066
|
+
type: "text",
|
|
2067
|
+
id: "stat",
|
|
2068
|
+
content: {
|
|
2069
|
+
text: stat,
|
|
2070
|
+
style: {
|
|
2071
|
+
fontSize: statFontSize,
|
|
2072
|
+
fontFamily: getThemeFont(context, "title"),
|
|
2073
|
+
fontWeight: "bold",
|
|
2074
|
+
color: colors.text,
|
|
2075
|
+
shadow: shouldUseShadow(context)
|
|
2076
|
+
}
|
|
2118
2077
|
},
|
|
2119
|
-
|
|
2120
|
-
|
|
2078
|
+
position: {
|
|
2079
|
+
x: accentLayout.textCenterX,
|
|
2080
|
+
y: adjustY("36%", accentLayout),
|
|
2081
|
+
anchor: "center"
|
|
2082
|
+
},
|
|
2083
|
+
animation: getTemplateHint(context, "statHighlight", "entrance", "subtle") === "dramatic" ? { type: "zoomIn", duration: 0.4 } : { type: "zoomIn", duration: 0.6 }
|
|
2084
|
+
});
|
|
2085
|
+
layers.push({
|
|
2086
|
+
type: "text",
|
|
2087
|
+
id: "description",
|
|
2088
|
+
content: {
|
|
2089
|
+
text: description,
|
|
2090
|
+
style: {
|
|
2091
|
+
fontSize: descFontSize,
|
|
2092
|
+
fontFamily: getThemeFont(context, "body"),
|
|
2093
|
+
color: theme.colors.textMuted,
|
|
2094
|
+
textAlign: "center",
|
|
2095
|
+
lineHeight: 1.5,
|
|
2096
|
+
shadow: shouldUseShadow(context)
|
|
2097
|
+
}
|
|
2098
|
+
},
|
|
2099
|
+
position: {
|
|
2100
|
+
x: accentLayout.textCenterX,
|
|
2101
|
+
y: adjustY("54%", accentLayout),
|
|
2102
|
+
width: accentLayout.textWidth,
|
|
2103
|
+
anchor: "center"
|
|
2104
|
+
},
|
|
2105
|
+
animation: { type: "fadeIn", duration: 1, delay: 0.3 }
|
|
2106
|
+
});
|
|
2107
|
+
if (detail) {
|
|
2108
|
+
layers.push({
|
|
2121
2109
|
type: "text",
|
|
2122
|
-
id: "
|
|
2110
|
+
id: "detail",
|
|
2123
2111
|
content: {
|
|
2124
|
-
text,
|
|
2112
|
+
text: detail,
|
|
2125
2113
|
style: {
|
|
2126
|
-
fontSize:
|
|
2127
|
-
fontFamily: getThemeFont(context, "
|
|
2128
|
-
|
|
2114
|
+
fontSize: detailFontSize,
|
|
2115
|
+
fontFamily: getThemeFont(context, "body"),
|
|
2116
|
+
// Scheme *text* rather than *accent*: accents in the built-in
|
|
2117
|
+
// schemes are tuned as chart/fill colors and drop below legible
|
|
2118
|
+
// contrast as body copy on light surfaces.
|
|
2129
2119
|
color: colors.text,
|
|
2130
2120
|
textAlign: "center",
|
|
2131
|
-
lineHeight: 1.2,
|
|
2132
2121
|
shadow: shouldUseShadow(context)
|
|
2133
2122
|
}
|
|
2134
2123
|
},
|
|
2135
2124
|
position: {
|
|
2136
|
-
x:
|
|
2137
|
-
y: "
|
|
2138
|
-
|
|
2139
|
-
|
|
2125
|
+
x: accentLayout.textCenterX,
|
|
2126
|
+
y: adjustY("66%", accentLayout),
|
|
2127
|
+
width: accentLayout.textWidth,
|
|
2128
|
+
anchor: "center"
|
|
2140
2129
|
},
|
|
2141
|
-
animation:
|
|
2142
|
-
}
|
|
2143
|
-
|
|
2130
|
+
animation: { type: "fadeIn", duration: 1, delay: 1 }
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
return layers;
|
|
2144
2134
|
}
|
|
2145
2135
|
|
|
2146
|
-
// src/doc/templates/
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
const
|
|
2156
|
-
const
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
} else {
|
|
2166
|
-
entries.push({ child: c, pinned: false, x: 0, y: 0 });
|
|
2167
|
-
unpositionedIdx.push(i);
|
|
2168
|
-
}
|
|
2136
|
+
// src/doc/templates/quoteBlock.ts
|
|
2137
|
+
function quoteBlock(input, context) {
|
|
2138
|
+
const { title, quote, attribution, accentImage } = input;
|
|
2139
|
+
const { theme, viewport } = context;
|
|
2140
|
+
const accentLayout = accentImage ? getAccentLayout(accentImage.position) : DEFAULT_LAYOUT;
|
|
2141
|
+
const quoteFontSize = themedFontSize(48, context, true);
|
|
2142
|
+
const titleFontSize = themedFontSize(34, context, true);
|
|
2143
|
+
const attrFontSize = themedFontSize(26, context, false);
|
|
2144
|
+
const quoteLineHeight = Math.max(theme.typography.titleLineHeight ?? 1.4, 1.25);
|
|
2145
|
+
const decorativeQuoteFontSize = themedFontSize(280, context, true);
|
|
2146
|
+
const layers = [createBackgroundLayer("bg", themedSurfaceGradient(context, 160))];
|
|
2147
|
+
if (accentImage) {
|
|
2148
|
+
layers.push(
|
|
2149
|
+
...createAccentLayers(
|
|
2150
|
+
accentImage,
|
|
2151
|
+
input.id,
|
|
2152
|
+
themedImageTreatment(context, input.imageTreatment)
|
|
2153
|
+
)
|
|
2154
|
+
);
|
|
2169
2155
|
}
|
|
2170
|
-
if (
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2156
|
+
if (title) {
|
|
2157
|
+
layers.push({
|
|
2158
|
+
type: "text",
|
|
2159
|
+
id: "quote-title",
|
|
2160
|
+
content: {
|
|
2161
|
+
text: title,
|
|
2162
|
+
style: {
|
|
2163
|
+
fontSize: titleFontSize,
|
|
2164
|
+
fontFamily: getThemeFont(context, "body"),
|
|
2165
|
+
fontWeight: "bold",
|
|
2166
|
+
color: theme.colors.textMuted,
|
|
2167
|
+
textAlign: "center",
|
|
2168
|
+
shadow: shouldUseShadow(context)
|
|
2169
|
+
}
|
|
2170
|
+
},
|
|
2171
|
+
position: {
|
|
2172
|
+
x: accentLayout.textCenterX,
|
|
2173
|
+
y: adjustY("14%", accentLayout),
|
|
2174
|
+
width: accentLayout.textWidth,
|
|
2175
|
+
anchor: "center"
|
|
2176
|
+
},
|
|
2177
|
+
animation: { type: "fadeIn", duration: 0.8 }
|
|
2186
2178
|
});
|
|
2187
2179
|
}
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
const seenEdgeIds = /* @__PURE__ */ new Set();
|
|
2199
|
-
for (const child of children) {
|
|
2200
|
-
if (!child.connectsTo) continue;
|
|
2201
|
-
for (const conn of child.connectsTo) {
|
|
2202
|
-
if (!nodeIds.has(conn.target)) {
|
|
2203
|
-
warnings.push(`Edge from "${child.id}" to "${conn.target}" dropped: no such sibling node`);
|
|
2204
|
-
continue;
|
|
2180
|
+
layers.push({
|
|
2181
|
+
type: "text",
|
|
2182
|
+
id: "deco-quote",
|
|
2183
|
+
content: {
|
|
2184
|
+
text: "\u201C",
|
|
2185
|
+
style: {
|
|
2186
|
+
fontSize: decorativeQuoteFontSize,
|
|
2187
|
+
fontFamily: getThemeFont(context, "title"),
|
|
2188
|
+
color: withAlpha(theme.colors.text, 0.09),
|
|
2189
|
+
textAlign: "center"
|
|
2205
2190
|
}
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
source: child.id,
|
|
2212
|
-
target: conn.target,
|
|
2213
|
-
...conn.type ? { type: conn.type } : {}
|
|
2214
|
-
});
|
|
2191
|
+
},
|
|
2192
|
+
position: {
|
|
2193
|
+
x: accentLayout.textCenterX,
|
|
2194
|
+
y: adjustY("26%", accentLayout),
|
|
2195
|
+
anchor: "center"
|
|
2215
2196
|
}
|
|
2216
|
-
}
|
|
2217
|
-
return { nodes, edges, warnings };
|
|
2218
|
-
}
|
|
2219
|
-
function makeEdgeId(source, conn) {
|
|
2220
|
-
return conn.type ? `${source}->${conn.target}:${conn.type}` : `${source}->${conn.target}`;
|
|
2221
|
-
}
|
|
2222
|
-
|
|
2223
|
-
// src/doc/asciiDiagram/types.ts
|
|
2224
|
-
var ASCII_CHAR_W = 14;
|
|
2225
|
-
var ASCII_CHAR_H = 28;
|
|
2226
|
-
|
|
2227
|
-
// src/doc/asciiDiagram/mapping.ts
|
|
2228
|
-
function asciiCellToCanvas(col, row) {
|
|
2229
|
-
return { x: col * ASCII_CHAR_W, y: row * ASCII_CHAR_H };
|
|
2230
|
-
}
|
|
2231
|
-
function canvasToAsciiCell(x, y) {
|
|
2232
|
-
return {
|
|
2233
|
-
col: Math.max(0, Math.round(x / ASCII_CHAR_W)),
|
|
2234
|
-
row: Math.max(0, Math.round(y / ASCII_CHAR_H))
|
|
2235
|
-
};
|
|
2236
|
-
}
|
|
2237
|
-
function asciiDiagramToTemplateData(diagram) {
|
|
2238
|
-
const nodes = diagram.nodes.map((n) => ({
|
|
2239
|
-
id: n.id,
|
|
2240
|
-
label: n.label,
|
|
2241
|
-
...asciiCellToCanvas(n.col, n.row),
|
|
2242
|
-
w: n.wCols * ASCII_CHAR_W,
|
|
2243
|
-
h: n.hRows * ASCII_CHAR_H,
|
|
2244
|
-
...n.containerId ? { container: n.containerId } : {}
|
|
2245
|
-
}));
|
|
2246
|
-
const anchors = inferEdgeAnchors(nodes, diagram.edges);
|
|
2247
|
-
const edges = diagram.edges.map((e, index) => ({
|
|
2248
|
-
source: e.source,
|
|
2249
|
-
target: e.target,
|
|
2250
|
-
...e.label ? { label: e.label } : {},
|
|
2251
|
-
...e.directed ? {} : { directed: false },
|
|
2252
|
-
...anchors[index],
|
|
2253
|
-
routing: "orthogonal"
|
|
2254
|
-
}));
|
|
2255
|
-
return { nodes, edges };
|
|
2256
|
-
}
|
|
2257
|
-
function inferEdgeAnchors(nodes, edges) {
|
|
2258
|
-
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
2259
|
-
const pairs = edges.map((edge) => {
|
|
2260
|
-
const source = nodeById.get(edge.source);
|
|
2261
|
-
const target = nodeById.get(edge.target);
|
|
2262
|
-
if (!source || !target) {
|
|
2263
|
-
return {
|
|
2264
|
-
sourceAnchor: { side: "right", offset: 0.5 },
|
|
2265
|
-
targetAnchor: { side: "left", offset: 0.5 }
|
|
2266
|
-
};
|
|
2267
|
-
}
|
|
2268
|
-
const sourceW = source.w ?? ASCII_CHAR_W * 12;
|
|
2269
|
-
const sourceH = source.h ?? ASCII_CHAR_H * 3;
|
|
2270
|
-
const targetW = target.w ?? ASCII_CHAR_W * 12;
|
|
2271
|
-
const targetH = target.h ?? ASCII_CHAR_H * 3;
|
|
2272
|
-
const dx = target.x + targetW / 2 - (source.x + sourceW / 2);
|
|
2273
|
-
const dy = target.y + targetH / 2 - (source.y + sourceH / 2);
|
|
2274
|
-
const horizontalDistance = Math.abs(dx) / Math.max(1, (sourceW + targetW) / 2);
|
|
2275
|
-
const verticalDistance = Math.abs(dy) / Math.max(1, (sourceH + targetH) / 2);
|
|
2276
|
-
if (verticalDistance >= horizontalDistance) {
|
|
2277
|
-
return dy >= 0 ? {
|
|
2278
|
-
sourceAnchor: { side: "bottom", offset: 0.5 },
|
|
2279
|
-
targetAnchor: { side: "top", offset: 0.5 }
|
|
2280
|
-
} : {
|
|
2281
|
-
sourceAnchor: { side: "top", offset: 0.5 },
|
|
2282
|
-
targetAnchor: { side: "bottom", offset: 0.5 }
|
|
2283
|
-
};
|
|
2284
|
-
}
|
|
2285
|
-
return dx >= 0 ? {
|
|
2286
|
-
sourceAnchor: { side: "right", offset: 0.5 },
|
|
2287
|
-
targetAnchor: { side: "left", offset: 0.5 }
|
|
2288
|
-
} : {
|
|
2289
|
-
sourceAnchor: { side: "left", offset: 0.5 },
|
|
2290
|
-
targetAnchor: { side: "right", offset: 0.5 }
|
|
2291
|
-
};
|
|
2292
2197
|
});
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2198
|
+
const quoteYPct = attribution ? 45 : title ? 52 : 50;
|
|
2199
|
+
layers.push({
|
|
2200
|
+
type: "text",
|
|
2201
|
+
id: "quote",
|
|
2202
|
+
content: {
|
|
2203
|
+
text: quote,
|
|
2204
|
+
style: {
|
|
2205
|
+
fontSize: quoteFontSize,
|
|
2206
|
+
fontFamily: getThemeFont(context, "title"),
|
|
2207
|
+
color: theme.colors.text,
|
|
2208
|
+
textAlign: "center",
|
|
2209
|
+
lineHeight: quoteLineHeight,
|
|
2210
|
+
shadow: shouldUseShadow(context)
|
|
2211
|
+
}
|
|
2212
|
+
},
|
|
2213
|
+
position: {
|
|
2214
|
+
x: accentLayout.textCenterX,
|
|
2215
|
+
y: adjustY(`${quoteYPct}%`, accentLayout),
|
|
2216
|
+
anchor: "center",
|
|
2217
|
+
width: accentLayout.textWidth
|
|
2218
|
+
},
|
|
2219
|
+
animation: themedEntrance(context, "text", { type: "fadeIn", duration: 2 })
|
|
2305
2220
|
});
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
const
|
|
2309
|
-
const
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
pairs[index] = {
|
|
2335
|
-
sourceAnchor: { side: sourceSide, offset: 0.5 },
|
|
2336
|
-
targetAnchor: { side: targetSide, offset: 0.5 }
|
|
2337
|
-
};
|
|
2338
|
-
}
|
|
2221
|
+
if (attribution) {
|
|
2222
|
+
const quoteWidthPx = parseFloat(accentLayout.textWidth) / 100 * viewport.width;
|
|
2223
|
+
const quoteHeightPx = estimateTextHeight(quote, quoteFontSize, quoteWidthPx, quoteLineHeight);
|
|
2224
|
+
const gapPx = quoteFontSize * 1.7;
|
|
2225
|
+
const attrYPct = Math.min(
|
|
2226
|
+
82,
|
|
2227
|
+
quoteYPct + (quoteHeightPx / 2 + gapPx) / viewport.height * 100
|
|
2228
|
+
);
|
|
2229
|
+
layers.push({
|
|
2230
|
+
type: "text",
|
|
2231
|
+
id: "attribution",
|
|
2232
|
+
content: {
|
|
2233
|
+
text: `\u2014 ${attribution}`,
|
|
2234
|
+
style: {
|
|
2235
|
+
fontSize: attrFontSize,
|
|
2236
|
+
fontFamily: getThemeFont(context, "body"),
|
|
2237
|
+
color: theme.colors.textMuted,
|
|
2238
|
+
textAlign: "center",
|
|
2239
|
+
shadow: shouldUseShadow(context)
|
|
2240
|
+
}
|
|
2241
|
+
},
|
|
2242
|
+
position: {
|
|
2243
|
+
x: accentLayout.textCenterX,
|
|
2244
|
+
y: adjustY(`${attrYPct}%`, accentLayout),
|
|
2245
|
+
anchor: "center"
|
|
2246
|
+
},
|
|
2247
|
+
animation: { type: "fadeIn", duration: 1, delay: 1.5 }
|
|
2248
|
+
});
|
|
2339
2249
|
}
|
|
2250
|
+
return layers;
|
|
2340
2251
|
}
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2252
|
+
|
|
2253
|
+
// src/doc/templates/factCard.ts
|
|
2254
|
+
function factCard(input, context) {
|
|
2255
|
+
const { fact, explanation, source, accentImage } = input;
|
|
2256
|
+
const { theme, viewport } = context;
|
|
2257
|
+
const accentLayout = accentImage ? getAccentLayout(accentImage.position) : DEFAULT_LAYOUT;
|
|
2258
|
+
const factFontSize = themedFontSize(56, context, true);
|
|
2259
|
+
const explainFontSize = themedFontSize(34, context, false);
|
|
2260
|
+
const sourceFontSize = themedFontSize(20, context, false);
|
|
2261
|
+
const layers = [createBackgroundLayer("bg", themedSurfaceGradient(context, 170))];
|
|
2262
|
+
if (accentImage) {
|
|
2263
|
+
layers.push(
|
|
2264
|
+
...createAccentLayers(
|
|
2265
|
+
accentImage,
|
|
2266
|
+
input.id,
|
|
2267
|
+
themedImageTreatment(context, input.imageTreatment)
|
|
2268
|
+
)
|
|
2269
|
+
);
|
|
2352
2270
|
}
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2271
|
+
const textWidthPx = parseFloat(accentLayout.textWidth) / 100 * viewport.width;
|
|
2272
|
+
const factH = estimateTextHeight(fact, factFontSize, textWidthPx, 1.3);
|
|
2273
|
+
const explainH = estimateTextHeight(explanation, explainFontSize, textWidthPx, 1.5);
|
|
2274
|
+
const sourceH = source ? sourceFontSize * 1.4 : 0;
|
|
2275
|
+
const gap = factFontSize * 0.9;
|
|
2276
|
+
const sourceGap = source ? explainFontSize * 1.1 : 0;
|
|
2277
|
+
const totalH = factH + gap + explainH + sourceGap + sourceH;
|
|
2278
|
+
const groupTopPct = 47 - totalH / 2 / viewport.height * 100;
|
|
2279
|
+
const pct = (px) => groupTopPct + px / viewport.height * 100;
|
|
2280
|
+
const factY = pct(factH / 2);
|
|
2281
|
+
const explainY = pct(factH + gap + explainH / 2);
|
|
2282
|
+
const sourceY = pct(factH + gap + explainH + sourceGap + sourceH / 2);
|
|
2283
|
+
layers.push({
|
|
2284
|
+
type: "text",
|
|
2285
|
+
id: "fact",
|
|
2286
|
+
content: {
|
|
2287
|
+
text: fact,
|
|
2288
|
+
style: {
|
|
2289
|
+
fontSize: factFontSize,
|
|
2290
|
+
fontFamily: getThemeFont(context, "title"),
|
|
2291
|
+
fontWeight: "bold",
|
|
2292
|
+
color: theme.colors.text,
|
|
2293
|
+
textAlign: "center",
|
|
2294
|
+
lineHeight: 1.3,
|
|
2295
|
+
shadow: shouldUseShadow(context)
|
|
2296
|
+
}
|
|
2297
|
+
},
|
|
2298
|
+
position: {
|
|
2299
|
+
x: accentLayout.textCenterX,
|
|
2300
|
+
y: adjustY(`${factY}%`, accentLayout),
|
|
2301
|
+
width: accentLayout.textWidth,
|
|
2302
|
+
anchor: "center"
|
|
2303
|
+
},
|
|
2304
|
+
animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1.5 })
|
|
2305
|
+
});
|
|
2306
|
+
layers.push({
|
|
2307
|
+
type: "text",
|
|
2308
|
+
id: "explanation",
|
|
2309
|
+
content: {
|
|
2310
|
+
text: explanation,
|
|
2311
|
+
style: {
|
|
2312
|
+
fontSize: explainFontSize,
|
|
2313
|
+
fontFamily: getThemeFont(context, "body"),
|
|
2314
|
+
color: theme.colors.textMuted,
|
|
2315
|
+
textAlign: "center",
|
|
2316
|
+
lineHeight: 1.5,
|
|
2317
|
+
shadow: shouldUseShadow(context)
|
|
2318
|
+
}
|
|
2319
|
+
},
|
|
2320
|
+
position: {
|
|
2321
|
+
x: accentLayout.textCenterX,
|
|
2322
|
+
y: adjustY(`${explainY}%`, accentLayout),
|
|
2323
|
+
width: accentLayout.textWidth,
|
|
2324
|
+
anchor: "center"
|
|
2325
|
+
},
|
|
2326
|
+
animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1, delay: 0.8 })
|
|
2327
|
+
});
|
|
2328
|
+
if (source) {
|
|
2329
|
+
layers.push({
|
|
2330
|
+
type: "text",
|
|
2331
|
+
id: "source",
|
|
2332
|
+
content: {
|
|
2333
|
+
text: source,
|
|
2334
|
+
style: {
|
|
2335
|
+
fontSize: sourceFontSize,
|
|
2336
|
+
fontFamily: getThemeFont(context, "body"),
|
|
2337
|
+
color: theme.colors.textMuted,
|
|
2338
|
+
textAlign: "center",
|
|
2339
|
+
shadow: shouldUseShadow(context)
|
|
2340
|
+
}
|
|
2341
|
+
},
|
|
2342
|
+
position: {
|
|
2343
|
+
x: accentLayout.textCenterX,
|
|
2344
|
+
y: adjustY(`${sourceY}%`, accentLayout),
|
|
2345
|
+
anchor: "center"
|
|
2346
|
+
},
|
|
2347
|
+
animation: { type: "fadeIn", duration: 0.8, delay: 1.5 }
|
|
2366
2348
|
});
|
|
2367
2349
|
}
|
|
2368
|
-
|
|
2369
|
-
function asciiDiagramFromTemplateData(nodes, edges, options = {}) {
|
|
2370
|
-
const ids = new Set(nodes.map((n) => n.id));
|
|
2371
|
-
const outNodes = nodes.map((n) => {
|
|
2372
|
-
const { col, row } = canvasToAsciiCell(n.x, n.y);
|
|
2373
|
-
const labelLines = n.label.length > 0 ? n.label.split("\n") : [];
|
|
2374
|
-
const innerW = labelLines.reduce((w, l) => Math.max(w, l.length), 0);
|
|
2375
|
-
const wCols = typeof n.w === "number" ? Math.max(3, Math.round(n.w / ASCII_CHAR_W)) : innerW + 4;
|
|
2376
|
-
const hRows = typeof n.h === "number" ? Math.max(3, Math.round(n.h / ASCII_CHAR_H)) : labelLines.length + 2;
|
|
2377
|
-
return {
|
|
2378
|
-
id: n.id,
|
|
2379
|
-
label: n.label,
|
|
2380
|
-
col,
|
|
2381
|
-
row,
|
|
2382
|
-
wCols: Math.max(wCols, 3),
|
|
2383
|
-
hRows: Math.max(hRows, 3),
|
|
2384
|
-
...n.container && ids.has(n.container) && n.container !== n.id ? { containerId: n.container } : {}
|
|
2385
|
-
};
|
|
2386
|
-
});
|
|
2387
|
-
return {
|
|
2388
|
-
nodes: outNodes,
|
|
2389
|
-
edges: edges.filter((e) => ids.has(e.source) && ids.has(e.target)).map((e) => ({
|
|
2390
|
-
source: e.source,
|
|
2391
|
-
target: e.target,
|
|
2392
|
-
...e.label ? { label: e.label } : {},
|
|
2393
|
-
directed: e.directed !== false
|
|
2394
|
-
})),
|
|
2395
|
-
style: options.style ?? "unicode",
|
|
2396
|
-
warnings: []
|
|
2397
|
-
};
|
|
2398
|
-
}
|
|
2399
|
-
function asciiDiagramFromBlocks(children, options = {}) {
|
|
2400
|
-
const layout = computeDiagramLayout(children);
|
|
2401
|
-
const nodes = layout.nodes.map((n) => ({
|
|
2402
|
-
id: n.id,
|
|
2403
|
-
label: n.label,
|
|
2404
|
-
x: n.x,
|
|
2405
|
-
y: n.y
|
|
2406
|
-
}));
|
|
2407
|
-
const edges = layout.edges.map((e) => ({
|
|
2408
|
-
source: e.source,
|
|
2409
|
-
target: e.target,
|
|
2410
|
-
...e.type ? { label: e.type } : {}
|
|
2411
|
-
}));
|
|
2412
|
-
const diagram = asciiDiagramFromTemplateData(nodes, edges, options);
|
|
2413
|
-
diagram.warnings.push(...layout.warnings);
|
|
2414
|
-
return diagram;
|
|
2350
|
+
return layers;
|
|
2415
2351
|
}
|
|
2416
2352
|
|
|
2417
|
-
// src/doc/
|
|
2418
|
-
function
|
|
2419
|
-
const
|
|
2420
|
-
|
|
2421
|
-
const
|
|
2422
|
-
const
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
}
|
|
2442
|
-
function clamp01(value) {
|
|
2443
|
-
return Math.max(0, Math.min(1, value));
|
|
2444
|
-
}
|
|
2445
|
-
|
|
2446
|
-
// src/doc/treeview/mapping.ts
|
|
2447
|
-
function treeToTemplateData(tree) {
|
|
2448
|
-
return { items: tree.roots.map(toItem) };
|
|
2449
|
-
}
|
|
2450
|
-
function toItem(node) {
|
|
2451
|
-
return {
|
|
2452
|
-
id: node.id,
|
|
2453
|
-
label: node.label,
|
|
2454
|
-
...node.isDir ? { isDir: true } : {},
|
|
2455
|
-
...node.comment ? { comment: node.comment } : {},
|
|
2456
|
-
children: node.children.map(toItem)
|
|
2457
|
-
};
|
|
2458
|
-
}
|
|
2459
|
-
function treeFromTemplateData(items, options = {}) {
|
|
2460
|
-
const roots = items.map(fromItem);
|
|
2461
|
-
return { roots, style: options.style ?? "unicode", warnings: [] };
|
|
2462
|
-
}
|
|
2463
|
-
function fromItem(item) {
|
|
2464
|
-
const children = Array.isArray(item.children) ? item.children.map(fromItem) : [];
|
|
2465
|
-
return {
|
|
2466
|
-
id: typeof item.id === "string" && item.id ? item.id : "node",
|
|
2467
|
-
label: typeof item.label === "string" ? item.label : "",
|
|
2468
|
-
children,
|
|
2469
|
-
isDir: item.isDir === true || children.length > 0,
|
|
2470
|
-
...item.comment ? { comment: item.comment } : {}
|
|
2471
|
-
};
|
|
2472
|
-
}
|
|
2473
|
-
function treeFromMarkdownList(list) {
|
|
2474
|
-
const usedIds = /* @__PURE__ */ new Map();
|
|
2475
|
-
const slugify2 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "node";
|
|
2476
|
-
const idFor = (label) => {
|
|
2477
|
-
const base = slugify2(label.replace(/\/$/, ""));
|
|
2478
|
-
const count = usedIds.get(base) ?? 0;
|
|
2479
|
-
usedIds.set(base, count + 1);
|
|
2480
|
-
return count === 0 ? base : `${base}-${count + 1}`;
|
|
2481
|
-
};
|
|
2482
|
-
const itemToNode = (item) => {
|
|
2483
|
-
const labelNode = item.children.find((c) => c.type !== "list");
|
|
2484
|
-
const label = labelNode ? extractPlainText(labelNode).trim() : "";
|
|
2485
|
-
const childLists = item.children.filter((c) => c.type === "list");
|
|
2486
|
-
const children = [];
|
|
2487
|
-
for (const cl of childLists) for (const ci of cl.children) children.push(itemToNode(ci));
|
|
2488
|
-
return {
|
|
2489
|
-
id: idFor(label),
|
|
2490
|
-
label,
|
|
2491
|
-
children,
|
|
2492
|
-
isDir: label.endsWith("/") || children.length > 0
|
|
2493
|
-
};
|
|
2494
|
-
};
|
|
2495
|
-
return { roots: list.children.map(itemToNode), style: "unicode", warnings: [] };
|
|
2496
|
-
}
|
|
2497
|
-
function findFirstList(contents) {
|
|
2498
|
-
return contents?.find((n) => n.type === "list");
|
|
2499
|
-
}
|
|
2500
|
-
|
|
2501
|
-
// src/doc/templateInputs.ts
|
|
2502
|
-
var VIDEO_FILE_RE = /\.(?:webm|mp4|mov|m4v|ogv)(?:[?#].*)?$/i;
|
|
2503
|
-
function extractBodyPlainText(contents) {
|
|
2504
|
-
if (!contents || contents.length === 0) return "";
|
|
2505
|
-
return contents.filter((node) => !(node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")).map((n) => extractPlainText(n)).join("\n").trim();
|
|
2506
|
-
}
|
|
2507
|
-
function extractListItems(contents) {
|
|
2508
|
-
return extractRichListItems(contents).map((item) => item.text);
|
|
2509
|
-
}
|
|
2510
|
-
function escapeInlineHtml(value) {
|
|
2511
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2512
|
-
}
|
|
2513
|
-
function renderInlineHtml(nodes) {
|
|
2514
|
-
return nodes.map((node) => {
|
|
2515
|
-
switch (node.type) {
|
|
2516
|
-
case "text":
|
|
2517
|
-
return escapeInlineHtml(node.value);
|
|
2518
|
-
case "emphasis":
|
|
2519
|
-
return `<em>${renderInlineHtml(node.children)}</em>`;
|
|
2520
|
-
case "strong":
|
|
2521
|
-
return `<strong>${renderInlineHtml(node.children)}</strong>`;
|
|
2522
|
-
case "delete":
|
|
2523
|
-
return `<del>${renderInlineHtml(node.children)}</del>`;
|
|
2524
|
-
case "inlineCode":
|
|
2525
|
-
return `<code>${escapeInlineHtml(node.value)}</code>`;
|
|
2526
|
-
case "link": {
|
|
2527
|
-
const label = renderInlineHtml(node.children);
|
|
2528
|
-
const href = sanitizeUrl(node.url, "link");
|
|
2529
|
-
if (!href) return label;
|
|
2530
|
-
const title = node.title ? ` title="${escapeInlineHtml(node.title)}"` : "";
|
|
2531
|
-
return `<a href="${escapeInlineHtml(href)}"${title}>${label}</a>`;
|
|
2532
|
-
}
|
|
2533
|
-
case "break":
|
|
2534
|
-
return "<br>";
|
|
2535
|
-
case "image":
|
|
2536
|
-
return escapeInlineHtml(node.alt ?? "");
|
|
2537
|
-
case "inlineMath":
|
|
2538
|
-
return escapeInlineHtml(`$${node.value}$`);
|
|
2539
|
-
case "footnoteReference":
|
|
2540
|
-
return escapeInlineHtml(`[^${node.label ?? node.identifier}]`);
|
|
2541
|
-
case "linkReference":
|
|
2542
|
-
case "textDirective":
|
|
2543
|
-
return renderInlineHtml(node.children);
|
|
2544
|
-
case "imageReference":
|
|
2545
|
-
return escapeInlineHtml(node.alt ?? "");
|
|
2546
|
-
case "mention":
|
|
2547
|
-
return escapeInlineHtml(`@${node.displayName}`);
|
|
2548
|
-
case "inlineIcon":
|
|
2549
|
-
return `<i class="fa-${node.family} fa-${node.name}" aria-hidden="true"></i>`;
|
|
2550
|
-
case "htmlInline":
|
|
2551
|
-
return escapeInlineHtml(node.rawHtml);
|
|
2552
|
-
}
|
|
2553
|
-
}).join("");
|
|
2554
|
-
}
|
|
2555
|
-
function renderListItemHtml(markdown) {
|
|
2556
|
-
return markdown.map(
|
|
2557
|
-
(node) => node.type === "paragraph" || node.type === "heading" ? renderInlineHtml(node.children) : escapeInlineHtml(extractPlainText(node))
|
|
2558
|
-
).join("<br>");
|
|
2559
|
-
}
|
|
2560
|
-
function extractRichListItems(contents) {
|
|
2561
|
-
if (!contents) return [];
|
|
2562
|
-
const items = [];
|
|
2563
|
-
for (const node of contents) {
|
|
2564
|
-
if (node.type !== "list") continue;
|
|
2565
|
-
for (const item of node.children) {
|
|
2566
|
-
const text = extractPlainText(item).trim();
|
|
2567
|
-
if (!text) continue;
|
|
2568
|
-
const html = renderListItemHtml(item.children);
|
|
2569
|
-
const plainHtml = escapeInlineHtml(text).replace(/\r?\n/g, "<br>");
|
|
2570
|
-
items.push({
|
|
2571
|
-
text,
|
|
2572
|
-
markdown: item.children,
|
|
2573
|
-
...html && html !== plainHtml ? { html } : {}
|
|
2574
|
-
});
|
|
2575
|
-
}
|
|
2576
|
-
}
|
|
2577
|
-
return items;
|
|
2578
|
-
}
|
|
2579
|
-
function parseDim(raw) {
|
|
2580
|
-
if (raw === void 0) return void 0;
|
|
2581
|
-
const n = parseFloat(raw);
|
|
2582
|
-
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
2583
|
-
}
|
|
2584
|
-
function extractImages(contents, limit = Infinity) {
|
|
2585
|
-
if (!contents || contents.length === 0) return [];
|
|
2586
|
-
const found = [];
|
|
2587
|
-
function fromHtml(nodes) {
|
|
2588
|
-
for (const node of nodes) {
|
|
2589
|
-
if (found.length >= limit) return;
|
|
2590
|
-
if (!node || typeof node !== "object") continue;
|
|
2591
|
-
const n = node;
|
|
2592
|
-
if (n.type === "htmlElement" && n.tagName === "img") {
|
|
2593
|
-
const attrs = n.attributes;
|
|
2594
|
-
if (attrs && typeof attrs.src === "string" && attrs.src) {
|
|
2595
|
-
found.push({
|
|
2596
|
-
src: attrs.src,
|
|
2597
|
-
alt: typeof attrs.alt === "string" ? attrs.alt : "",
|
|
2598
|
-
width: parseDim(attrs.width),
|
|
2599
|
-
height: parseDim(attrs.height)
|
|
2600
|
-
});
|
|
2601
|
-
}
|
|
2602
|
-
}
|
|
2603
|
-
if (Array.isArray(n.children)) fromHtml(n.children);
|
|
2604
|
-
}
|
|
2605
|
-
}
|
|
2606
|
-
function walk(node) {
|
|
2607
|
-
if (found.length >= limit) return;
|
|
2608
|
-
if (!node || typeof node !== "object") return;
|
|
2609
|
-
const n = node;
|
|
2610
|
-
if (n.type === "image" && typeof n.url === "string" && n.url) {
|
|
2611
|
-
found.push({ src: n.url, alt: typeof n.alt === "string" ? n.alt : "" });
|
|
2612
|
-
return;
|
|
2613
|
-
}
|
|
2614
|
-
if ((n.type === "htmlBlock" || n.type === "htmlInline") && Array.isArray(n.htmlChildren)) {
|
|
2615
|
-
fromHtml(n.htmlChildren);
|
|
2616
|
-
}
|
|
2617
|
-
if (Array.isArray(n.children)) {
|
|
2618
|
-
for (const child of n.children) walk(child);
|
|
2353
|
+
// src/doc/templates/twoColumn.ts
|
|
2354
|
+
function twoColumn(input, context) {
|
|
2355
|
+
const { left, right, header, leftColor = "green", rightColor = "blue" } = input;
|
|
2356
|
+
if (!left?.label || !right?.label) return [];
|
|
2357
|
+
const { theme, layout, orientation } = context;
|
|
2358
|
+
const leftColors = resolveColorScheme(context, leftColor);
|
|
2359
|
+
const rightColors = resolveColorScheme(context, rightColor);
|
|
2360
|
+
const positions = getTwoColumnPositions(orientation);
|
|
2361
|
+
const isStacked = layout.stackColumns;
|
|
2362
|
+
const columnWidth = isStacked ? "85%" : "42%";
|
|
2363
|
+
const headerFontSize = themedFontSize(36, context, true);
|
|
2364
|
+
const labelFontSize = themedFontSize(44, context, true);
|
|
2365
|
+
const sublabelFontSize = themedFontSize(22, context, false);
|
|
2366
|
+
const panelTop = header && !isStacked ? 22 : 15;
|
|
2367
|
+
const panelHeight = header && !isStacked ? 64 : 70;
|
|
2368
|
+
const layers = [
|
|
2369
|
+
// Background — subtle gradient to add depth
|
|
2370
|
+
{
|
|
2371
|
+
type: "shape",
|
|
2372
|
+
id: "bg",
|
|
2373
|
+
content: {
|
|
2374
|
+
shape: "rect",
|
|
2375
|
+
fill: themedSurfaceGradient(context, 135)
|
|
2376
|
+
},
|
|
2377
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
2619
2378
|
}
|
|
2379
|
+
];
|
|
2380
|
+
if (!isStacked) {
|
|
2381
|
+
const panelFill = withAlpha(theme.colors.text, 0.05);
|
|
2382
|
+
layers.push({
|
|
2383
|
+
type: "shape",
|
|
2384
|
+
id: "left-panel",
|
|
2385
|
+
content: {
|
|
2386
|
+
shape: "rect",
|
|
2387
|
+
fill: panelFill,
|
|
2388
|
+
borderRadius: 12
|
|
2389
|
+
},
|
|
2390
|
+
position: { x: "3%", y: `${panelTop}%`, width: "44%", height: `${panelHeight}%` }
|
|
2391
|
+
});
|
|
2392
|
+
layers.push({
|
|
2393
|
+
type: "shape",
|
|
2394
|
+
id: "right-panel",
|
|
2395
|
+
content: {
|
|
2396
|
+
shape: "rect",
|
|
2397
|
+
fill: panelFill,
|
|
2398
|
+
borderRadius: 12
|
|
2399
|
+
},
|
|
2400
|
+
position: { x: "53%", y: `${panelTop}%`, width: "44%", height: `${panelHeight}%` }
|
|
2401
|
+
});
|
|
2620
2402
|
}
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2403
|
+
const headerY = isStacked ? "8%" : "12%";
|
|
2404
|
+
if (header) {
|
|
2405
|
+
layers.push({
|
|
2406
|
+
type: "text",
|
|
2407
|
+
id: "header",
|
|
2408
|
+
content: {
|
|
2409
|
+
text: header,
|
|
2410
|
+
style: {
|
|
2411
|
+
fontSize: headerFontSize,
|
|
2412
|
+
fontFamily: getThemeFont(context, "title"),
|
|
2413
|
+
fontWeight: "bold",
|
|
2414
|
+
color: theme.colors.text,
|
|
2415
|
+
textAlign: "center"
|
|
2416
|
+
}
|
|
2417
|
+
},
|
|
2418
|
+
position: { x: "50%", y: headerY, anchor: "center" },
|
|
2419
|
+
animation: themedEntrance(context, "text", { type: "fadeIn", duration: 0.8 })
|
|
2420
|
+
});
|
|
2624
2421
|
}
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
if (!Array.isArray(children)) return void 0;
|
|
2641
|
-
for (const child of children) {
|
|
2642
|
-
if (!child || typeof child !== "object") continue;
|
|
2643
|
-
const node = child;
|
|
2644
|
-
if (node.tagName === "source") {
|
|
2645
|
-
const attrs = node.attributes;
|
|
2646
|
-
if (typeof attrs?.src === "string" && attrs.src) return attrs.src;
|
|
2647
|
-
}
|
|
2648
|
-
const nested = nestedSource(node.children);
|
|
2649
|
-
if (nested) return nested;
|
|
2650
|
-
}
|
|
2651
|
-
return void 0;
|
|
2422
|
+
let leftY;
|
|
2423
|
+
let leftSublabelY;
|
|
2424
|
+
let rightY;
|
|
2425
|
+
let rightSublabelY;
|
|
2426
|
+
if (isStacked) {
|
|
2427
|
+
leftY = header ? "30%" : "25%";
|
|
2428
|
+
leftSublabelY = header ? "40%" : "35%";
|
|
2429
|
+
rightY = header ? "60%" : "55%";
|
|
2430
|
+
rightSublabelY = header ? "70%" : "65%";
|
|
2431
|
+
} else {
|
|
2432
|
+
const panelCenter = panelTop + panelHeight / 2;
|
|
2433
|
+
leftY = `${panelCenter - 4}%`;
|
|
2434
|
+
leftSublabelY = `${panelCenter + 7}%`;
|
|
2435
|
+
rightY = `${panelCenter - 4}%`;
|
|
2436
|
+
rightSublabelY = `${panelCenter + 7}%`;
|
|
2652
2437
|
}
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
...attrs?.poster ? { posterSrc: attrs.poster } : {},
|
|
2666
|
-
alt: attrs?.["aria-label"] || attrs?.title || attrs?.alt || "",
|
|
2667
|
-
...startAt != null ? { startAt } : {},
|
|
2668
|
-
...clipStart != null ? { clipStart } : {},
|
|
2669
|
-
...clipEnd != null ? { clipEnd } : {}
|
|
2670
|
-
});
|
|
2438
|
+
layers.push({
|
|
2439
|
+
type: "text",
|
|
2440
|
+
id: "left-label",
|
|
2441
|
+
content: {
|
|
2442
|
+
text: left.label,
|
|
2443
|
+
style: {
|
|
2444
|
+
fontSize: labelFontSize,
|
|
2445
|
+
fontFamily: getThemeFont(context, "body"),
|
|
2446
|
+
fontWeight: "bold",
|
|
2447
|
+
color: leftColors.text,
|
|
2448
|
+
textAlign: "center",
|
|
2449
|
+
lineHeight: 1.3
|
|
2671
2450
|
}
|
|
2672
|
-
}
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
}
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
}
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
for (const node of contents) {
|
|
2704
|
-
if (node.type === "blockquote") return extractPlainText(node).trim();
|
|
2451
|
+
},
|
|
2452
|
+
position: {
|
|
2453
|
+
x: positions.left.x,
|
|
2454
|
+
y: leftY,
|
|
2455
|
+
anchor: "center",
|
|
2456
|
+
width: columnWidth
|
|
2457
|
+
},
|
|
2458
|
+
animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1 })
|
|
2459
|
+
});
|
|
2460
|
+
if (left.sublabel) {
|
|
2461
|
+
layers.push({
|
|
2462
|
+
type: "text",
|
|
2463
|
+
id: "left-sublabel",
|
|
2464
|
+
content: {
|
|
2465
|
+
text: left.sublabel,
|
|
2466
|
+
style: {
|
|
2467
|
+
fontSize: sublabelFontSize,
|
|
2468
|
+
fontFamily: getThemeFont(context, "body"),
|
|
2469
|
+
color: theme.colors.textMuted,
|
|
2470
|
+
textAlign: "center",
|
|
2471
|
+
lineHeight: 1.4
|
|
2472
|
+
}
|
|
2473
|
+
},
|
|
2474
|
+
position: {
|
|
2475
|
+
x: positions.left.x,
|
|
2476
|
+
y: leftSublabelY,
|
|
2477
|
+
anchor: "center",
|
|
2478
|
+
width: columnWidth
|
|
2479
|
+
},
|
|
2480
|
+
animation: { type: "fadeIn", duration: 0.8, delay: 0.3 }
|
|
2481
|
+
});
|
|
2705
2482
|
}
|
|
2706
|
-
|
|
2707
|
-
}
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2483
|
+
const connectorFontSize = themedFontSize(64, context, true);
|
|
2484
|
+
const connectorY = isStacked ? "48%" : `${panelTop + panelHeight / 2}%`;
|
|
2485
|
+
const connectorSymbol = isStacked ? "\u2193" : "\u2192";
|
|
2486
|
+
layers.push({
|
|
2487
|
+
type: "text",
|
|
2488
|
+
id: "connector",
|
|
2489
|
+
content: {
|
|
2490
|
+
text: connectorSymbol,
|
|
2491
|
+
style: {
|
|
2492
|
+
fontSize: connectorFontSize,
|
|
2493
|
+
fontFamily: getThemeFont(context, "title"),
|
|
2494
|
+
fontWeight: "bold",
|
|
2495
|
+
color: withAlpha(theme.colors.text, 0.45),
|
|
2496
|
+
textAlign: "center"
|
|
2497
|
+
}
|
|
2498
|
+
},
|
|
2499
|
+
position: {
|
|
2500
|
+
x: "50%",
|
|
2501
|
+
y: connectorY,
|
|
2502
|
+
anchor: "center"
|
|
2503
|
+
},
|
|
2504
|
+
animation: { type: "fadeIn", duration: 0.6, delay: 0.4 }
|
|
2505
|
+
});
|
|
2506
|
+
layers.push({
|
|
2507
|
+
type: "text",
|
|
2508
|
+
id: "right-label",
|
|
2509
|
+
content: {
|
|
2510
|
+
text: right.label,
|
|
2511
|
+
style: {
|
|
2512
|
+
fontSize: labelFontSize,
|
|
2513
|
+
fontFamily: getThemeFont(context, "body"),
|
|
2514
|
+
fontWeight: "bold",
|
|
2515
|
+
color: rightColors.text,
|
|
2516
|
+
textAlign: "center",
|
|
2517
|
+
lineHeight: 1.3
|
|
2518
|
+
}
|
|
2519
|
+
},
|
|
2520
|
+
position: {
|
|
2521
|
+
x: positions.right.x,
|
|
2522
|
+
y: rightY,
|
|
2523
|
+
anchor: "center",
|
|
2524
|
+
width: columnWidth
|
|
2525
|
+
},
|
|
2526
|
+
animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1, delay: 0.5 })
|
|
2527
|
+
});
|
|
2528
|
+
if (right.sublabel) {
|
|
2529
|
+
layers.push({
|
|
2530
|
+
type: "text",
|
|
2531
|
+
id: "right-sublabel",
|
|
2532
|
+
content: {
|
|
2533
|
+
text: right.sublabel,
|
|
2534
|
+
style: {
|
|
2535
|
+
fontSize: sublabelFontSize,
|
|
2536
|
+
fontFamily: getThemeFont(context, "body"),
|
|
2537
|
+
color: theme.colors.textMuted,
|
|
2538
|
+
textAlign: "center",
|
|
2539
|
+
lineHeight: 1.4
|
|
2540
|
+
}
|
|
2541
|
+
},
|
|
2542
|
+
position: {
|
|
2543
|
+
x: positions.right.x,
|
|
2544
|
+
y: rightSublabelY,
|
|
2545
|
+
anchor: "center",
|
|
2546
|
+
width: columnWidth
|
|
2547
|
+
},
|
|
2548
|
+
animation: { type: "fadeIn", duration: 0.8, delay: 0.8 }
|
|
2549
|
+
});
|
|
2724
2550
|
}
|
|
2725
|
-
return
|
|
2726
|
-
}
|
|
2727
|
-
function removeStatPhrase(text, start, end) {
|
|
2728
|
-
const before = text.slice(0, start).trim();
|
|
2729
|
-
let after = text.slice(end);
|
|
2730
|
-
if (!before) after = after.replace(/^\s*(?:[-–—:]\s*)?/, "");
|
|
2731
|
-
else after = after.trim();
|
|
2732
|
-
return [before, after].filter(Boolean).join(" ").trim();
|
|
2733
|
-
}
|
|
2734
|
-
function bodyWithoutParagraphStat(contents, paragraphIndex, paragraphText, start, end) {
|
|
2735
|
-
if (!contents) return "";
|
|
2736
|
-
return contents.map(
|
|
2737
|
-
(node, index) => index === paragraphIndex ? removeStatPhrase(paragraphText, start, end) : extractPlainText(node).trim()
|
|
2738
|
-
).filter(Boolean).join("\n").trim();
|
|
2551
|
+
return layers;
|
|
2739
2552
|
}
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2553
|
+
|
|
2554
|
+
// src/doc/templates/dateEvent.ts
|
|
2555
|
+
var MOOD_ACCENTS = {
|
|
2556
|
+
neutral: "#63b3ed",
|
|
2557
|
+
somber: "#e53e3e",
|
|
2558
|
+
celebratory: "#68d391"
|
|
2559
|
+
};
|
|
2560
|
+
function dateEvent(input, context) {
|
|
2561
|
+
const { date, description, footer, mood = "neutral", accentImage } = input;
|
|
2562
|
+
const { theme } = context;
|
|
2563
|
+
const moodAccent = MOOD_ACCENTS[mood];
|
|
2564
|
+
const dateColor = relativeLuminance(theme.colors.background) > 0.5 ? oklchDarken(moodAccent, 0.25) : moodAccent;
|
|
2565
|
+
const accentLayout = accentImage ? getAccentLayout(accentImage.position) : DEFAULT_LAYOUT;
|
|
2566
|
+
const dateFontSize = themedFontSize(96, context, true);
|
|
2567
|
+
const descFontSize = themedFontSize(30, context, false);
|
|
2568
|
+
const footerFontSize = themedFontSize(26, context, false);
|
|
2569
|
+
const layers = [
|
|
2570
|
+
// Background — theme surface gradient (diagonal to differentiate
|
|
2571
|
+
// from other text templates)
|
|
2572
|
+
{
|
|
2573
|
+
type: "shape",
|
|
2574
|
+
id: "bg",
|
|
2575
|
+
content: {
|
|
2576
|
+
shape: "rect",
|
|
2577
|
+
fill: themedSurfaceGradient(context, 135)
|
|
2578
|
+
},
|
|
2579
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
2758
2580
|
}
|
|
2581
|
+
];
|
|
2582
|
+
if (accentImage) {
|
|
2583
|
+
layers.push(
|
|
2584
|
+
...createAccentLayers(
|
|
2585
|
+
accentImage,
|
|
2586
|
+
input.id,
|
|
2587
|
+
themedImageTreatment(context, input.imageTreatment)
|
|
2588
|
+
)
|
|
2589
|
+
);
|
|
2759
2590
|
}
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2591
|
+
layers.push({
|
|
2592
|
+
type: "text",
|
|
2593
|
+
id: "date",
|
|
2594
|
+
content: {
|
|
2595
|
+
text: date,
|
|
2596
|
+
style: {
|
|
2597
|
+
fontSize: dateFontSize,
|
|
2598
|
+
fontFamily: getThemeFont(context, "title"),
|
|
2599
|
+
fontWeight: "bold",
|
|
2600
|
+
color: dateColor,
|
|
2601
|
+
shadow: shouldUseShadow(context)
|
|
2602
|
+
}
|
|
2603
|
+
},
|
|
2604
|
+
position: {
|
|
2605
|
+
x: accentLayout.textCenterX,
|
|
2606
|
+
y: adjustY("35%", accentLayout),
|
|
2607
|
+
anchor: "center"
|
|
2608
|
+
},
|
|
2609
|
+
animation: { type: "fadeIn", duration: 1.5 }
|
|
2610
|
+
});
|
|
2611
|
+
layers.push({
|
|
2612
|
+
type: "text",
|
|
2613
|
+
id: "description",
|
|
2614
|
+
content: {
|
|
2615
|
+
text: description,
|
|
2616
|
+
style: {
|
|
2617
|
+
fontSize: descFontSize,
|
|
2618
|
+
fontFamily: getThemeFont(context, "body"),
|
|
2619
|
+
color: theme.colors.text,
|
|
2620
|
+
textAlign: "center",
|
|
2621
|
+
lineHeight: 1.6,
|
|
2622
|
+
shadow: shouldUseShadow(context)
|
|
2623
|
+
}
|
|
2624
|
+
},
|
|
2625
|
+
position: {
|
|
2626
|
+
x: accentLayout.textCenterX,
|
|
2627
|
+
y: adjustY("58%", accentLayout),
|
|
2628
|
+
width: accentLayout.textWidth,
|
|
2629
|
+
anchor: "center"
|
|
2630
|
+
},
|
|
2631
|
+
animation: { type: "fadeIn", duration: 2, delay: 1 }
|
|
2632
|
+
});
|
|
2633
|
+
if (footer) {
|
|
2634
|
+
layers.push({
|
|
2635
|
+
type: "text",
|
|
2636
|
+
id: "footer",
|
|
2637
|
+
content: {
|
|
2638
|
+
text: footer,
|
|
2639
|
+
style: {
|
|
2640
|
+
fontSize: footerFontSize,
|
|
2641
|
+
fontFamily: getThemeFont(context, "body"),
|
|
2642
|
+
color: theme.colors.textMuted,
|
|
2643
|
+
shadow: shouldUseShadow(context)
|
|
2644
|
+
}
|
|
2645
|
+
},
|
|
2646
|
+
position: {
|
|
2647
|
+
x: accentLayout.textCenterX,
|
|
2648
|
+
y: adjustY("72%", accentLayout),
|
|
2649
|
+
anchor: "center"
|
|
2650
|
+
},
|
|
2651
|
+
animation: { type: "fadeIn", duration: 1, delay: 3 }
|
|
2652
|
+
});
|
|
2764
2653
|
}
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2654
|
+
return layers;
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
// src/doc/templates/imageWithCaption.ts
|
|
2658
|
+
function imageWithCaption(input, context) {
|
|
2659
|
+
const {
|
|
2660
|
+
imageSrc,
|
|
2661
|
+
imageAlt,
|
|
2662
|
+
caption: rawCaption,
|
|
2663
|
+
captionPosition = "bottom",
|
|
2664
|
+
ambientMotion,
|
|
2665
|
+
isTitle,
|
|
2666
|
+
subtitle,
|
|
2667
|
+
imageCredit,
|
|
2668
|
+
imageLicense
|
|
2669
|
+
} = input;
|
|
2670
|
+
const caption = rawCaption ? cleanCaption(rawCaption) : rawCaption;
|
|
2671
|
+
const { theme, layout } = context;
|
|
2672
|
+
const treatment = themedImageTreatment(context, input.imageTreatment);
|
|
2673
|
+
const captionFontSize = themedFontSize(30, context, false);
|
|
2674
|
+
const titleFontSize = themedFontSize(96, context, true);
|
|
2675
|
+
const subtitleFontSize = themedFontSize(36, context, false);
|
|
2676
|
+
const legacyKenBurns = input.kenBurns;
|
|
2677
|
+
const motion = ambientMotion || legacyKenBurns;
|
|
2678
|
+
const imageAnimation = mapAmbientMotion(motion);
|
|
2679
|
+
const layers = [
|
|
2680
|
+
// Background image
|
|
2681
|
+
{
|
|
2682
|
+
type: "image",
|
|
2683
|
+
id: "bg-image",
|
|
2684
|
+
content: {
|
|
2685
|
+
src: imageSrc,
|
|
2686
|
+
alt: imageAlt,
|
|
2687
|
+
fit: "cover",
|
|
2688
|
+
credit: imageCredit,
|
|
2689
|
+
license: imageLicense,
|
|
2690
|
+
...treatment ? { treatment } : {}
|
|
2691
|
+
},
|
|
2692
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" },
|
|
2693
|
+
animation: imageAnimation
|
|
2694
|
+
}
|
|
2695
|
+
];
|
|
2696
|
+
const bg = theme.colors.background;
|
|
2697
|
+
if (isTitle && caption) {
|
|
2698
|
+
layers.push({
|
|
2699
|
+
type: "shape",
|
|
2700
|
+
id: "title-gradient",
|
|
2701
|
+
content: {
|
|
2702
|
+
shape: "rect",
|
|
2703
|
+
fill: `linear-gradient(0deg, ${withAlpha(bg, 0.85)} 0%, ${withAlpha(bg, 0.45)} 40%, ${withAlpha(bg, 0.1)} 70%, ${withAlpha(bg, 0)} 100%)`
|
|
2704
|
+
},
|
|
2705
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
2706
|
+
});
|
|
2707
|
+
layers.push({
|
|
2708
|
+
type: "text",
|
|
2709
|
+
id: "title",
|
|
2710
|
+
content: {
|
|
2711
|
+
text: caption,
|
|
2712
|
+
style: {
|
|
2713
|
+
fontSize: titleFontSize,
|
|
2714
|
+
fontFamily: getThemeFont(context, "title"),
|
|
2715
|
+
fontWeight: "bold",
|
|
2716
|
+
color: theme.colors.text,
|
|
2717
|
+
textAlign: "center",
|
|
2718
|
+
shadow: shouldUseShadow(context)
|
|
2719
|
+
}
|
|
2720
|
+
},
|
|
2721
|
+
position: {
|
|
2722
|
+
x: "50%",
|
|
2723
|
+
y: subtitle ? "70%" : "75%",
|
|
2724
|
+
anchor: "center",
|
|
2725
|
+
width: layout.maxTextWidth
|
|
2726
|
+
},
|
|
2727
|
+
animation: { type: "fadeIn", duration: 0.8 }
|
|
2728
|
+
});
|
|
2729
|
+
if (subtitle) {
|
|
2730
|
+
layers.push({
|
|
2731
|
+
type: "text",
|
|
2732
|
+
id: "subtitle",
|
|
2733
|
+
content: {
|
|
2734
|
+
text: subtitle,
|
|
2735
|
+
style: {
|
|
2736
|
+
fontSize: subtitleFontSize,
|
|
2737
|
+
fontFamily: getThemeFont(context, "body"),
|
|
2738
|
+
color: theme.colors.textMuted,
|
|
2739
|
+
textAlign: "center",
|
|
2740
|
+
lineHeight: 1.5
|
|
2741
|
+
}
|
|
2742
|
+
},
|
|
2743
|
+
position: {
|
|
2744
|
+
x: "50%",
|
|
2745
|
+
y: "82%",
|
|
2746
|
+
anchor: "center",
|
|
2747
|
+
width: layout.maxTextWidth
|
|
2748
|
+
},
|
|
2749
|
+
animation: { type: "fadeIn", duration: 0.8, delay: 0.2 }
|
|
2750
|
+
});
|
|
2780
2751
|
}
|
|
2752
|
+
return layers;
|
|
2781
2753
|
}
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2754
|
+
if (caption) {
|
|
2755
|
+
const captionBgY = captionPosition === "top" ? "7%" : captionPosition === "center" ? "42%" : "68%";
|
|
2756
|
+
const captionY = captionPosition === "top" ? "13%" : captionPosition === "center" ? "48%" : "74%";
|
|
2757
|
+
layers.push({
|
|
2758
|
+
type: "shape",
|
|
2759
|
+
id: "caption-gradient",
|
|
2760
|
+
content: {
|
|
2761
|
+
shape: "rect",
|
|
2762
|
+
fill: `linear-gradient(90deg, ${withAlpha(bg, 0)}, ${withAlpha(bg, 0.78)} 16%, ${withAlpha(bg, 0.78)} 84%, ${withAlpha(bg, 0)})`
|
|
2763
|
+
},
|
|
2764
|
+
position: {
|
|
2765
|
+
x: 0,
|
|
2766
|
+
y: captionBgY,
|
|
2767
|
+
width: "100%",
|
|
2768
|
+
height: "16%"
|
|
2769
|
+
}
|
|
2770
|
+
});
|
|
2771
|
+
layers.push({
|
|
2772
|
+
type: "text",
|
|
2773
|
+
id: "caption",
|
|
2774
|
+
content: {
|
|
2775
|
+
text: caption,
|
|
2776
|
+
style: {
|
|
2777
|
+
fontSize: captionFontSize,
|
|
2778
|
+
fontFamily: getThemeFont(context, "body"),
|
|
2779
|
+
color: theme.colors.text,
|
|
2780
|
+
textAlign: "center",
|
|
2781
|
+
shadow: shouldUseShadow(context),
|
|
2782
|
+
lineHeight: 1.18,
|
|
2783
|
+
maxLines: 2
|
|
2784
|
+
}
|
|
2785
|
+
},
|
|
2786
|
+
position: {
|
|
2787
|
+
x: "50%",
|
|
2788
|
+
y: captionY,
|
|
2789
|
+
anchor: "center",
|
|
2790
|
+
width: "78%"
|
|
2791
|
+
},
|
|
2792
|
+
animation: { type: "fadeIn", duration: 1.5, delay: 0.5 }
|
|
2793
|
+
});
|
|
2822
2794
|
}
|
|
2795
|
+
return layers;
|
|
2823
2796
|
}
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
case "areaChart":
|
|
2873
|
-
case "scatterChart": {
|
|
2874
|
-
const tableData = extractTableFromContents(contents);
|
|
2875
|
-
if (tableData) {
|
|
2876
|
-
return {
|
|
2877
|
-
headers: tableData.headers,
|
|
2878
|
-
rows: tableData.rows,
|
|
2879
|
-
...headingText ? { title: headingText } : {}
|
|
2880
|
-
};
|
|
2881
|
-
}
|
|
2882
|
-
return placeholders && !bodyText3.trim() ? {
|
|
2883
|
-
headers: ["Quarter", "Revenue", "Costs"],
|
|
2884
|
-
rows: [
|
|
2885
|
-
["Q1", "40", "28"],
|
|
2886
|
-
["Q2", "55", "31"],
|
|
2887
|
-
["Q3", "48", "30"],
|
|
2888
|
-
["Q4", "62", "35"]
|
|
2889
|
-
],
|
|
2890
|
-
...headingText ? { title: headingText } : {}
|
|
2891
|
-
} : null;
|
|
2892
|
-
}
|
|
2893
|
-
case "imageWithCaption": {
|
|
2894
|
-
const img = extractFirstImage(contents);
|
|
2895
|
-
if (!img) return placeholders ? { caption: headingText } : null;
|
|
2896
|
-
return { imageSrc: img.src, imageAlt: img.alt || headingText, caption: headingText };
|
|
2897
|
-
}
|
|
2898
|
-
case "bigText": {
|
|
2899
|
-
const img = extractFirstImage(contents);
|
|
2900
|
-
return {
|
|
2901
|
-
title: headingText,
|
|
2902
|
-
...img ? { imageSrc: img.src, imageAlt: img.alt || headingText } : {}
|
|
2903
|
-
};
|
|
2904
|
-
}
|
|
2905
|
-
case "photoGrid": {
|
|
2906
|
-
const images = extractImages(contents, 4);
|
|
2907
|
-
if (images.length < 2) return placeholders ? {} : null;
|
|
2908
|
-
return {
|
|
2909
|
-
images: images.map((i) => ({ src: i.src, alt: i.alt })),
|
|
2910
|
-
caption: headingText
|
|
2911
|
-
};
|
|
2912
|
-
}
|
|
2913
|
-
case "videoWithCaption": {
|
|
2914
|
-
const video = extractFirstEmbeddedVideo(contents);
|
|
2915
|
-
if (!video) return placeholders ? { caption: headingText } : null;
|
|
2916
|
-
return {
|
|
2917
|
-
videoSrc: video.src,
|
|
2918
|
-
posterSrc: video.posterSrc,
|
|
2919
|
-
videoAlt: video.alt || headingText,
|
|
2920
|
-
caption: headingText
|
|
2921
|
-
};
|
|
2922
|
-
}
|
|
2923
|
-
case "leftFeature":
|
|
2924
|
-
case "rightFeature": {
|
|
2925
|
-
const img = extractFirstImage(contents);
|
|
2926
|
-
if (!img && !placeholders) return null;
|
|
2927
|
-
return {
|
|
2928
|
-
imageSrc: img?.src ?? "",
|
|
2929
|
-
imageAlt: img?.alt || headingText,
|
|
2930
|
-
imageWidth: img?.width,
|
|
2931
|
-
imageHeight: img?.height,
|
|
2932
|
-
title: headingText,
|
|
2933
|
-
body: bodyText3
|
|
2934
|
-
};
|
|
2935
|
-
}
|
|
2936
|
-
case "diagram": {
|
|
2937
|
-
const fences = (contents ?? []).filter((n) => n.type === "code");
|
|
2938
|
-
const fence = fences.length === 1 && isEligibleAsciiFenceLang(fences[0].lang) ? fences[0] : void 0;
|
|
2939
|
-
const detection = fence ? detectAsciiDiagram(fence.value, { explicit: isExplicitDiagramLang(fence.lang) }) : void 0;
|
|
2940
|
-
if (!detection?.isDiagram || !detection.diagram) return placeholders ? {} : null;
|
|
2941
|
-
const { nodes, edges } = asciiDiagramToTemplateData(detection.diagram);
|
|
2942
|
-
return { nodes, edges, ...headingText ? { title: headingText } : {} };
|
|
2943
|
-
}
|
|
2944
|
-
case "timeline": {
|
|
2945
|
-
const fences = (contents ?? []).filter((n) => n.type === "code");
|
|
2946
|
-
const fence = fences.length === 1 && isEligibleAsciiTimelineFenceLang(fences[0].lang) ? fences[0] : void 0;
|
|
2947
|
-
const detection = fence ? detectAsciiTimeline(fence.value, { explicit: true }) : void 0;
|
|
2948
|
-
if (!detection?.isTimeline || !detection.timeline) return placeholders ? {} : null;
|
|
2949
|
-
const { tracks, links } = asciiTimelineToTemplateData(detection.timeline);
|
|
2950
|
-
return { tracks, links, ...headingText ? { title: headingText } : {} };
|
|
2797
|
+
|
|
2798
|
+
// src/doc/templates/featureBlock.ts
|
|
2799
|
+
function buildFeatureLayers(input, context, side) {
|
|
2800
|
+
const { imageSrc, imageAlt, imageWidth, imageHeight, title, body } = input;
|
|
2801
|
+
const { theme, layout } = context;
|
|
2802
|
+
const treatment = themedImageTreatment(context, input.imageTreatment);
|
|
2803
|
+
const sized = (imageWidth ?? 0) > 0 || (imageHeight ?? 0) > 0;
|
|
2804
|
+
const stack = layout.stackColumns;
|
|
2805
|
+
const titleFontSize = themedFontSize(48, context, true);
|
|
2806
|
+
const bodyFontSize2 = themedFontSize(24, context, false);
|
|
2807
|
+
let imgX;
|
|
2808
|
+
let imgY;
|
|
2809
|
+
let imgW;
|
|
2810
|
+
let imgH;
|
|
2811
|
+
let imageFit;
|
|
2812
|
+
if (stack) {
|
|
2813
|
+
imgX = "0";
|
|
2814
|
+
imgY = "0";
|
|
2815
|
+
imgW = "100%";
|
|
2816
|
+
imgH = "50%";
|
|
2817
|
+
imageFit = sized ? "contain" : "cover";
|
|
2818
|
+
} else if (sized) {
|
|
2819
|
+
const halfStart = side === "left" ? 5 : 55;
|
|
2820
|
+
imgX = `${halfStart}%`;
|
|
2821
|
+
imgY = "5%";
|
|
2822
|
+
imgW = "40%";
|
|
2823
|
+
imgH = "90%";
|
|
2824
|
+
imageFit = "contain";
|
|
2825
|
+
} else {
|
|
2826
|
+
imgX = side === "left" ? "0" : "50%";
|
|
2827
|
+
imgY = "0";
|
|
2828
|
+
imgW = "50%";
|
|
2829
|
+
imgH = "100%";
|
|
2830
|
+
imageFit = "cover";
|
|
2831
|
+
}
|
|
2832
|
+
const COLUMN_INSET = 4;
|
|
2833
|
+
const textColumnWidth = stack ? 90 : 42;
|
|
2834
|
+
const textX = stack ? `${(100 - textColumnWidth) / 2}%` : side === "left" ? `${50 + COLUMN_INSET}%` : `${COLUMN_INSET + 2}%`;
|
|
2835
|
+
const titleY = stack ? "60%" : body ? "42%" : "50%";
|
|
2836
|
+
const bodyY = stack ? "78%" : title ? "55%" : "50%";
|
|
2837
|
+
const textAnchor = "top-left";
|
|
2838
|
+
const textAlign = "left";
|
|
2839
|
+
const layers = [
|
|
2840
|
+
{
|
|
2841
|
+
type: "shape",
|
|
2842
|
+
id: "feature-bg",
|
|
2843
|
+
content: { shape: "rect", fill: theme.colors.background },
|
|
2844
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
2951
2845
|
}
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2846
|
+
];
|
|
2847
|
+
if (imageSrc) {
|
|
2848
|
+
layers.push({
|
|
2849
|
+
type: "image",
|
|
2850
|
+
id: "feature-image",
|
|
2851
|
+
content: {
|
|
2852
|
+
src: imageSrc,
|
|
2853
|
+
alt: imageAlt ?? title ?? "",
|
|
2854
|
+
fit: imageFit,
|
|
2855
|
+
...treatment ? { treatment } : {}
|
|
2856
|
+
},
|
|
2857
|
+
position: { x: imgX, y: imgY, width: imgW, height: imgH }
|
|
2858
|
+
});
|
|
2859
|
+
}
|
|
2860
|
+
if (title) {
|
|
2861
|
+
layers.push({
|
|
2862
|
+
type: "text",
|
|
2863
|
+
id: "feature-title",
|
|
2864
|
+
content: {
|
|
2865
|
+
text: title,
|
|
2866
|
+
style: {
|
|
2867
|
+
fontSize: titleFontSize,
|
|
2868
|
+
fontFamily: getThemeFont(context, "title"),
|
|
2869
|
+
fontWeight: "bold",
|
|
2870
|
+
color: theme.colors.text,
|
|
2871
|
+
textAlign,
|
|
2872
|
+
lineHeight: 1.2
|
|
2960
2873
|
}
|
|
2961
|
-
}
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2874
|
+
},
|
|
2875
|
+
position: {
|
|
2876
|
+
x: textX,
|
|
2877
|
+
y: titleY,
|
|
2878
|
+
anchor: textAnchor,
|
|
2879
|
+
width: `${textColumnWidth}%`
|
|
2880
|
+
},
|
|
2881
|
+
animation: themedEntrance(context, "text", { type: "fadeIn", duration: 0.8 })
|
|
2882
|
+
});
|
|
2883
|
+
}
|
|
2884
|
+
if (body) {
|
|
2885
|
+
layers.push({
|
|
2886
|
+
type: "text",
|
|
2887
|
+
id: "feature-body",
|
|
2888
|
+
content: {
|
|
2889
|
+
text: body,
|
|
2890
|
+
style: {
|
|
2891
|
+
fontSize: bodyFontSize2,
|
|
2892
|
+
fontFamily: getThemeFont(context, "body"),
|
|
2893
|
+
color: theme.colors.textMuted,
|
|
2894
|
+
textAlign,
|
|
2895
|
+
lineHeight: 1.5
|
|
2896
|
+
}
|
|
2897
|
+
},
|
|
2898
|
+
position: {
|
|
2899
|
+
x: textX,
|
|
2900
|
+
y: bodyY,
|
|
2901
|
+
anchor: textAnchor,
|
|
2902
|
+
width: `${textColumnWidth}%`
|
|
2903
|
+
},
|
|
2904
|
+
animation: themedEntrance(context, "text", { type: "fadeIn", duration: 0.8, delay: 0.2 })
|
|
2905
|
+
});
|
|
2906
|
+
}
|
|
2907
|
+
return layers;
|
|
2908
|
+
}
|
|
2909
|
+
function leftFeature(input, context) {
|
|
2910
|
+
return buildFeatureLayers(input, context, "left");
|
|
2911
|
+
}
|
|
2912
|
+
function rightFeature(input, context) {
|
|
2913
|
+
return buildFeatureLayers(input, context, "right");
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
// src/doc/templates/mapBlock.ts
|
|
2917
|
+
function mapBlock(input, context) {
|
|
2918
|
+
const {
|
|
2919
|
+
center,
|
|
2920
|
+
zoom,
|
|
2921
|
+
mapStyle = "terrain",
|
|
2922
|
+
title,
|
|
2923
|
+
caption,
|
|
2924
|
+
markers,
|
|
2925
|
+
ambientMotion,
|
|
2926
|
+
staticSrc
|
|
2927
|
+
} = input;
|
|
2928
|
+
const { theme, layout } = context;
|
|
2929
|
+
const titleFontSize = themedFontSize(64, context, true);
|
|
2930
|
+
const captionFontSize = themedFontSize(32, context, false);
|
|
2931
|
+
const mapAnimation = mapAmbientMotion(ambientMotion);
|
|
2932
|
+
const layers = [
|
|
2933
|
+
// Map background
|
|
2934
|
+
{
|
|
2935
|
+
type: "map",
|
|
2936
|
+
id: "map-bg",
|
|
2937
|
+
content: {
|
|
2938
|
+
center,
|
|
2939
|
+
zoom,
|
|
2940
|
+
style: mapStyle,
|
|
2941
|
+
markers,
|
|
2942
|
+
showAttribution: true,
|
|
2943
|
+
staticSrc
|
|
2944
|
+
// Use pre-rendered image if available
|
|
2945
|
+
},
|
|
2946
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" },
|
|
2947
|
+
animation: mapAnimation
|
|
2968
2948
|
}
|
|
2969
|
-
|
|
2970
|
-
|
|
2949
|
+
];
|
|
2950
|
+
const bg = theme.colors.background;
|
|
2951
|
+
if (title) {
|
|
2952
|
+
layers.push({
|
|
2953
|
+
type: "shape",
|
|
2954
|
+
id: "title-overlay",
|
|
2955
|
+
content: {
|
|
2956
|
+
shape: "rect",
|
|
2957
|
+
fill: `linear-gradient(180deg, ${withAlpha(bg, 0.85)} 0%, ${withAlpha(bg, 0.6)} 70%, ${withAlpha(bg, 0)} 100%)`
|
|
2958
|
+
},
|
|
2959
|
+
position: { x: 0, y: 0, width: "100%", height: "20%" }
|
|
2960
|
+
});
|
|
2961
|
+
layers.push({
|
|
2962
|
+
type: "text",
|
|
2963
|
+
id: "title",
|
|
2964
|
+
content: {
|
|
2965
|
+
text: title,
|
|
2966
|
+
style: {
|
|
2967
|
+
fontSize: titleFontSize,
|
|
2968
|
+
fontFamily: getThemeFont(context, "title"),
|
|
2969
|
+
fontWeight: "bold",
|
|
2970
|
+
color: theme.colors.text,
|
|
2971
|
+
textAlign: "center",
|
|
2972
|
+
shadow: shouldUseShadow(context)
|
|
2973
|
+
}
|
|
2974
|
+
},
|
|
2975
|
+
position: {
|
|
2976
|
+
x: "50%",
|
|
2977
|
+
y: "9%",
|
|
2978
|
+
anchor: "center",
|
|
2979
|
+
width: layout.maxTextWidth
|
|
2980
|
+
},
|
|
2981
|
+
animation: { type: "fadeIn", duration: 1, delay: 0.3 }
|
|
2982
|
+
});
|
|
2983
|
+
}
|
|
2984
|
+
if (caption) {
|
|
2985
|
+
layers.push({
|
|
2986
|
+
type: "shape",
|
|
2987
|
+
id: "caption-overlay",
|
|
2988
|
+
content: {
|
|
2989
|
+
shape: "rect",
|
|
2990
|
+
fill: `linear-gradient(0deg, ${withAlpha(bg, 0.85)} 0%, ${withAlpha(bg, 0.6)} 70%, ${withAlpha(bg, 0)} 100%)`
|
|
2991
|
+
},
|
|
2992
|
+
position: { x: 0, y: "80%", width: "100%", height: "20%" }
|
|
2993
|
+
});
|
|
2994
|
+
layers.push({
|
|
2995
|
+
type: "text",
|
|
2996
|
+
id: "caption",
|
|
2997
|
+
content: {
|
|
2998
|
+
text: caption,
|
|
2999
|
+
style: {
|
|
3000
|
+
fontSize: captionFontSize,
|
|
3001
|
+
fontFamily: getThemeFont(context, "body"),
|
|
3002
|
+
color: theme.colors.text,
|
|
3003
|
+
textAlign: "center",
|
|
3004
|
+
shadow: shouldUseShadow(context)
|
|
3005
|
+
}
|
|
3006
|
+
},
|
|
3007
|
+
position: {
|
|
3008
|
+
x: "50%",
|
|
3009
|
+
y: layout.captionY,
|
|
3010
|
+
anchor: "center",
|
|
3011
|
+
width: layout.maxTextWidth
|
|
3012
|
+
},
|
|
3013
|
+
animation: { type: "fadeIn", duration: 1, delay: 0.5 }
|
|
3014
|
+
});
|
|
2971
3015
|
}
|
|
3016
|
+
return layers;
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
// src/doc/templates/fullBleedQuote.ts
|
|
3020
|
+
function fullBleedQuote(input, context) {
|
|
3021
|
+
const { text, colorScheme = "blue" } = input;
|
|
3022
|
+
const colors = resolveColorScheme(context, colorScheme);
|
|
3023
|
+
const textFontSize = themedFontSize(120, context, true);
|
|
3024
|
+
return [
|
|
3025
|
+
// Background — radial vignette built from the color scheme's own
|
|
3026
|
+
// surface, so the card takes on the scheme (and the theme behind it)
|
|
3027
|
+
// instead of vignetting every theme into hard black.
|
|
3028
|
+
{
|
|
3029
|
+
type: "shape",
|
|
3030
|
+
id: "bg",
|
|
3031
|
+
content: {
|
|
3032
|
+
shape: "rect",
|
|
3033
|
+
fill: `radial-gradient(ellipse at 50% 50%, ${colors.bg} 0%, ${oklchDarken(colors.bg, 0.12)} 100%)`
|
|
3034
|
+
},
|
|
3035
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
3036
|
+
},
|
|
3037
|
+
// The text — massive, centered
|
|
3038
|
+
{
|
|
3039
|
+
type: "text",
|
|
3040
|
+
id: "impact-text",
|
|
3041
|
+
content: {
|
|
3042
|
+
text,
|
|
3043
|
+
style: {
|
|
3044
|
+
fontSize: textFontSize,
|
|
3045
|
+
fontFamily: getThemeFont(context, "title"),
|
|
3046
|
+
fontWeight: "bold",
|
|
3047
|
+
color: colors.text,
|
|
3048
|
+
textAlign: "center",
|
|
3049
|
+
lineHeight: 1.2,
|
|
3050
|
+
shadow: shouldUseShadow(context)
|
|
3051
|
+
}
|
|
3052
|
+
},
|
|
3053
|
+
position: {
|
|
3054
|
+
x: "50%",
|
|
3055
|
+
y: "50%",
|
|
3056
|
+
anchor: "center",
|
|
3057
|
+
width: "85%"
|
|
3058
|
+
},
|
|
3059
|
+
animation: getTemplateHint(context, "fullBleedQuote", "entrance", "subtle") === "dramatic" ? { type: "zoomIn", duration: 0.8 } : { type: "fadeIn", duration: 1.5 }
|
|
3060
|
+
}
|
|
3061
|
+
];
|
|
2972
3062
|
}
|
|
2973
3063
|
|
|
2974
3064
|
// src/doc/templates/listBlock.ts
|
|
@@ -10657,8 +10747,10 @@ function buildStartBlock(markdownDoc, rootBlocks, fileName) {
|
|
|
10657
10747
|
const headingTitle = headingBlock ? headingBlock.title ?? extractPlainText(headingBlock.sourceHeading) : void 0;
|
|
10658
10748
|
const title = frontmatterTitle || headingTitle || fileNameCoverTitle(fileName);
|
|
10659
10749
|
if (!title) return void 0;
|
|
10750
|
+
const frontmatterSubtitle = typeof markdownDoc.frontmatter?.subtitle === "string" ? markdownDoc.frontmatter.subtitle.trim() : void 0;
|
|
10660
10751
|
const subtitleBlock = headingBlock ?? rootBlocks[0];
|
|
10661
|
-
const
|
|
10752
|
+
const inferredSubtitle = subtitleBlock?.contents?.[0]?.type === "paragraph" ? extractPlainText(subtitleBlock.contents[0]) : void 0;
|
|
10753
|
+
const subtitle = frontmatterSubtitle || inferredSubtitle;
|
|
10662
10754
|
const firstImage = findFirstImage(markdownDoc);
|
|
10663
10755
|
const config = {
|
|
10664
10756
|
title,
|
|
@@ -11008,17 +11100,6 @@ export {
|
|
|
11008
11100
|
sectionHeader,
|
|
11009
11101
|
fitBigTextSize,
|
|
11010
11102
|
bigText,
|
|
11011
|
-
contentBlock,
|
|
11012
|
-
statHighlight,
|
|
11013
|
-
quoteBlock,
|
|
11014
|
-
factCard,
|
|
11015
|
-
twoColumn,
|
|
11016
|
-
dateEvent,
|
|
11017
|
-
imageWithCaption,
|
|
11018
|
-
leftFeature,
|
|
11019
|
-
rightFeature,
|
|
11020
|
-
mapBlock,
|
|
11021
|
-
fullBleedQuote,
|
|
11022
11103
|
computeDiagramLayout,
|
|
11023
11104
|
ASCII_CHAR_W,
|
|
11024
11105
|
ASCII_CHAR_H,
|
|
@@ -11034,6 +11115,8 @@ export {
|
|
|
11034
11115
|
findFirstList,
|
|
11035
11116
|
extractBodyPlainText,
|
|
11036
11117
|
extractListItems,
|
|
11118
|
+
renderMarkdownBlocksHtml,
|
|
11119
|
+
markdownBlockSeparatorLines,
|
|
11037
11120
|
extractRichListItems,
|
|
11038
11121
|
extractImages,
|
|
11039
11122
|
extractFirstImage,
|
|
@@ -11043,6 +11126,17 @@ export {
|
|
|
11043
11126
|
extractBlockquoteText,
|
|
11044
11127
|
autoTemplatePreservesContent,
|
|
11045
11128
|
deriveTemplateInputs,
|
|
11129
|
+
contentBlock,
|
|
11130
|
+
statHighlight,
|
|
11131
|
+
quoteBlock,
|
|
11132
|
+
factCard,
|
|
11133
|
+
twoColumn,
|
|
11134
|
+
dateEvent,
|
|
11135
|
+
imageWithCaption,
|
|
11136
|
+
leftFeature,
|
|
11137
|
+
rightFeature,
|
|
11138
|
+
mapBlock,
|
|
11139
|
+
fullBleedQuote,
|
|
11046
11140
|
listBlock,
|
|
11047
11141
|
photoGrid,
|
|
11048
11142
|
definitionCard,
|
|
@@ -11079,7 +11173,9 @@ export {
|
|
|
11079
11173
|
fallbackBlockLayers,
|
|
11080
11174
|
BLOCK_MEDIA_LAYOUT_POLICIES,
|
|
11081
11175
|
getBlockMediaLayoutPolicy,
|
|
11176
|
+
placeLayersInRect,
|
|
11082
11177
|
materializeBlockLayers,
|
|
11178
|
+
materializeBlockLayersWithRuntime,
|
|
11083
11179
|
TEMPLATE_METADATA,
|
|
11084
11180
|
TEMPLATE_AUTHORING_METADATA,
|
|
11085
11181
|
coverBlock,
|