@founderhq/journeys 0.3.64 → 0.3.66

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/index.cjs CHANGED
@@ -1197,19 +1197,225 @@ var renderTemplate = (template, variables, fallback) => {
1197
1197
  };
1198
1198
 
1199
1199
  // src/blocks/resolve-template.ts
1200
- function resolveTemplate(template, answers) {
1200
+ var VALID_IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
1201
+ var IDENTIFIER_TOKEN_RE = /[A-Za-z_$][A-Za-z0-9_$]*/g;
1202
+ var RESERVED_IDENTIFIERS = /* @__PURE__ */ new Set([
1203
+ "arguments",
1204
+ "await",
1205
+ "break",
1206
+ "case",
1207
+ "catch",
1208
+ "class",
1209
+ "const",
1210
+ "continue",
1211
+ "debugger",
1212
+ "default",
1213
+ "delete",
1214
+ "do",
1215
+ "else",
1216
+ "enum",
1217
+ "eval",
1218
+ "export",
1219
+ "extends",
1220
+ "false",
1221
+ "finally",
1222
+ "for",
1223
+ "function",
1224
+ "if",
1225
+ "implements",
1226
+ "import",
1227
+ "in",
1228
+ "instanceof",
1229
+ "interface",
1230
+ "let",
1231
+ "new",
1232
+ "null",
1233
+ "package",
1234
+ "private",
1235
+ "protected",
1236
+ "public",
1237
+ "return",
1238
+ "static",
1239
+ "super",
1240
+ "switch",
1241
+ "this",
1242
+ "throw",
1243
+ "true",
1244
+ "try",
1245
+ "typeof",
1246
+ "undefined",
1247
+ "var",
1248
+ "void",
1249
+ "while",
1250
+ "with",
1251
+ "yield"
1252
+ ]);
1253
+ var GLOBAL_IDENTIFIERS = /* @__PURE__ */ new Set([
1254
+ "Array",
1255
+ "BigInt",
1256
+ "Boolean",
1257
+ "Date",
1258
+ "Error",
1259
+ "Infinity",
1260
+ "Intl",
1261
+ "JSON",
1262
+ "Map",
1263
+ "Math",
1264
+ "NaN",
1265
+ "Number",
1266
+ "Object",
1267
+ "Promise",
1268
+ "RegExp",
1269
+ "Set",
1270
+ "String",
1271
+ "Symbol",
1272
+ "URL",
1273
+ "URLSearchParams",
1274
+ "WeakMap",
1275
+ "WeakSet",
1276
+ "console",
1277
+ "decodeURI",
1278
+ "decodeURIComponent",
1279
+ "encodeURI",
1280
+ "encodeURIComponent",
1281
+ "globalThis",
1282
+ "isFinite",
1283
+ "isNaN",
1284
+ "parseFloat",
1285
+ "parseInt"
1286
+ ]);
1287
+ function previousNonWhitespace(value, index) {
1288
+ for (let i = index - 1; i >= 0; i--) {
1289
+ if (!/\s/.test(value[i])) return value[i];
1290
+ }
1291
+ return void 0;
1292
+ }
1293
+ function stripLiteralsAndComments(expression) {
1294
+ let stripped = "";
1295
+ let i = 0;
1296
+ while (i < expression.length) {
1297
+ const char = expression[i];
1298
+ const next = expression[i + 1];
1299
+ if (char === "/" && next === "/") {
1300
+ stripped += " ";
1301
+ i += 2;
1302
+ while (i < expression.length && expression[i] !== "\n") {
1303
+ stripped += " ";
1304
+ i++;
1305
+ }
1306
+ continue;
1307
+ }
1308
+ if (char === "/" && next === "*") {
1309
+ stripped += " ";
1310
+ i += 2;
1311
+ while (i < expression.length) {
1312
+ const end = expression[i] === "*" && expression[i + 1] === "/";
1313
+ stripped += " ";
1314
+ i++;
1315
+ if (end) {
1316
+ stripped += " ";
1317
+ i++;
1318
+ break;
1319
+ }
1320
+ }
1321
+ continue;
1322
+ }
1323
+ if (char === "'" || char === '"' || char === "`") {
1324
+ const quote = char;
1325
+ stripped += " ";
1326
+ i++;
1327
+ while (i < expression.length) {
1328
+ const current = expression[i];
1329
+ stripped += " ";
1330
+ i++;
1331
+ if (current === "\\") {
1332
+ if (i < expression.length) {
1333
+ stripped += " ";
1334
+ i++;
1335
+ }
1336
+ continue;
1337
+ }
1338
+ if (current === quote) break;
1339
+ }
1340
+ continue;
1341
+ }
1342
+ stripped += char;
1343
+ i++;
1344
+ }
1345
+ return stripped;
1346
+ }
1347
+ function extractTemplateExpressions(template) {
1348
+ const expressions = [];
1349
+ let i = 0;
1350
+ while (i < template.length) {
1351
+ if (template[i] !== "$" || template[i + 1] !== "{") {
1352
+ i++;
1353
+ continue;
1354
+ }
1355
+ let depth = 1;
1356
+ let quote = null;
1357
+ let escaped = false;
1358
+ const start = i + 2;
1359
+ i = start;
1360
+ while (i < template.length) {
1361
+ const char = template[i];
1362
+ if (quote) {
1363
+ if (escaped) {
1364
+ escaped = false;
1365
+ } else if (char === "\\") {
1366
+ escaped = true;
1367
+ } else if (char === quote) {
1368
+ quote = null;
1369
+ }
1370
+ i++;
1371
+ continue;
1372
+ }
1373
+ if (char === "'" || char === '"' || char === "`") {
1374
+ quote = char;
1375
+ i++;
1376
+ continue;
1377
+ }
1378
+ if (char === "{") {
1379
+ depth++;
1380
+ } else if (char === "}") {
1381
+ depth--;
1382
+ if (depth === 0) {
1383
+ expressions.push(template.slice(start, i));
1384
+ i++;
1385
+ break;
1386
+ }
1387
+ }
1388
+ i++;
1389
+ }
1390
+ }
1391
+ return expressions;
1392
+ }
1393
+ function collectTemplateVariables(expressions, answers) {
1201
1394
  var _a;
1202
1395
  const vars = {};
1203
- const regex = /\$\{(\w+)/g;
1204
- let match;
1205
- while ((match = regex.exec(template)) !== null) {
1206
- const key = match[1];
1207
- vars[key] = (_a = answers[key]) != null ? _a : void 0;
1396
+ for (const expression of expressions) {
1397
+ const stripped = stripLiteralsAndComments(expression);
1398
+ IDENTIFIER_TOKEN_RE.lastIndex = 0;
1399
+ let match;
1400
+ while ((match = IDENTIFIER_TOKEN_RE.exec(stripped)) !== null) {
1401
+ const key = match[0];
1402
+ const isPropertyAccess = previousNonWhitespace(stripped, match.index) === ".";
1403
+ if (isPropertyAccess || !VALID_IDENTIFIER_RE.test(key) || RESERVED_IDENTIFIERS.has(key) || GLOBAL_IDENTIFIERS.has(key) && !(key in answers)) {
1404
+ continue;
1405
+ }
1406
+ vars[key] = (_a = answers[key]) != null ? _a : void 0;
1407
+ }
1208
1408
  }
1209
- if (Object.keys(vars).length === 0) return template;
1409
+ return vars;
1410
+ }
1411
+ function resolveTemplate(template, answers) {
1412
+ if (!template.includes("${")) return template;
1413
+ const expressions = extractTemplateExpressions(template);
1414
+ if (expressions.length === 0) return template;
1415
+ const vars = collectTemplateVariables(expressions, answers);
1210
1416
  return renderTemplate(template, vars, "");
1211
1417
  }
1212
- var WHOLE_REF_RE = /^\$\{(\w+)\}$/;
1418
+ var WHOLE_REF_RE = /^\$\{([A-Za-z_$][A-Za-z0-9_$]*)\}$/;
1213
1419
  function resolveStringField(value, answers) {
1214
1420
  const wholeRef = value.match(WHOLE_REF_RE);
1215
1421
  if (wholeRef) {
@@ -1268,7 +1474,7 @@ function RadioGroupItem(_a) {
1268
1474
  __spreadProps(__spreadValues({
1269
1475
  "data-slot": "radio-group-item",
1270
1476
  className: cn(
1271
- "aspect-square size-4 shrink-0 rounded-full border border-input text-primary shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
1477
+ "aspect-square size-4 shrink-0 rounded-full border border-input text-primary shadow-xs transition-[color,border-color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
1272
1478
  className
1273
1479
  )
1274
1480
  }, props), {
@@ -1646,7 +1852,7 @@ function SingleSelectWidget({
1646
1852
  RadioGroupItem,
1647
1853
  {
1648
1854
  value: opt.id,
1649
- className: isGrid ? "sr-only" : "mt-1"
1855
+ className: isGrid ? "sr-only" : opt.description ? "mt-1" : ""
1650
1856
  }
1651
1857
  ),
1652
1858
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -3716,16 +3922,17 @@ function ChecklistItemRow({
3716
3922
  animate: animate2,
3717
3923
  entranceDelay
3718
3924
  }) {
3925
+ var _a, _b;
3719
3926
  const hasLoadSequence = item.loadDelay != null && !item.checked;
3720
3927
  const uid = sanitizeSvgId(React.useId());
3721
3928
  const gradientId = `checklist-grad-${uid}-${index}`;
3722
3929
  const initialPhase = item.checked ? "checked" : "unchecked";
3723
3930
  const [phase, setPhase] = React.useState(initialPhase);
3724
3931
  React.useEffect(() => {
3725
- var _a;
3932
+ var _a2;
3726
3933
  if (!hasLoadSequence) return;
3727
3934
  const loadDelay = item.loadDelay * 1e3;
3728
- const loadDuration = ((_a = item.loadDuration) != null ? _a : 4) * 1e3;
3935
+ const loadDuration = ((_a2 = item.loadDuration) != null ? _a2 : 4) * 1e3;
3729
3936
  const loadTimer = setTimeout(() => setPhase("loading"), loadDelay);
3730
3937
  const checkTimer = setTimeout(
3731
3938
  () => setPhase("checked"),
@@ -3739,6 +3946,8 @@ function ChecklistItemRow({
3739
3946
  const CustomIcon = item.icon ? lucideIcons7[item.icon] : void 0;
3740
3947
  const IconComp = CustomIcon && ("render" in CustomIcon || typeof CustomIcon === "function") ? CustomIcon : icons.Check;
3741
3948
  const hasCustomIcon = IconComp !== icons.Check;
3949
+ const textContent = (_b = (_a = item.segments) != null ? _a : item.text) != null ? _b : "";
3950
+ const textStyle = item.textColor || item.lineHeight != null ? { color: item.textColor, lineHeight: item.lineHeight } : void 0;
3742
3951
  const iconProps = item.iconGradient ? { stroke: `url(#${gradientId})` } : item.iconColor ? { color: item.iconColor } : {};
3743
3952
  const content = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
3744
3953
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -3801,13 +4010,14 @@ function ChecklistItemRow({
3801
4010
  }
3802
4011
  ),
3803
4012
  /* @__PURE__ */ jsxRuntime.jsx(
3804
- "span",
4013
+ RichText,
3805
4014
  {
4015
+ content: textContent,
3806
4016
  className: cn(
3807
4017
  "text-sm transition-colors duration-300",
3808
4018
  phase === "checked" ? "text-foreground" : "text-muted-foreground"
3809
4019
  ),
3810
- children: item.text
4020
+ style: textStyle
3811
4021
  }
3812
4022
  )
3813
4023
  ] });
@@ -4153,6 +4363,9 @@ function CircularProgressBlock({
4153
4363
  );
4154
4364
  }
4155
4365
  var lucideIcons8 = icons__namespace;
4366
+ var DEFAULT_CHART_HEIGHT = 280;
4367
+ var DEFAULT_Y_AXIS_LABEL_GUTTER = 64;
4368
+ var Y_AXIS_LABEL_GAP = 8;
4156
4369
  function toSvg(points, w, h, pad) {
4157
4370
  const drawW = w - pad.left - pad.right;
4158
4371
  const drawH = h - pad.top - pad.bottom;
@@ -4218,10 +4431,36 @@ function xValToPercent(xVal, svgW, pad) {
4218
4431
  const drawW = svgW - pad.left - pad.right;
4219
4432
  return (pad.left + xVal / 100 * drawW) / svgW * 100;
4220
4433
  }
4434
+ function toCssLength(value, fallback) {
4435
+ if (value === void 0) return fallback;
4436
+ if (typeof value === "number") {
4437
+ return Number.isFinite(value) && value > 0 ? `${value}px` : fallback;
4438
+ }
4439
+ const trimmed = value.trim();
4440
+ if (!trimmed) return fallback;
4441
+ return /^-?\d+(\.\d+)?$/.test(trimmed) ? `${trimmed}px` : trimmed;
4442
+ }
4443
+ function toViewBoxHeight(value) {
4444
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
4445
+ return value;
4446
+ }
4447
+ if (typeof value === "string") {
4448
+ const match = value.trim().match(/^(\d+(?:\.\d+)?)(?:px)?$/i);
4449
+ if (match) {
4450
+ const parsed = Number(match[1]);
4451
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
4452
+ }
4453
+ }
4454
+ return DEFAULT_CHART_HEIGHT;
4455
+ }
4456
+ function toPositiveNumber(value, fallback) {
4457
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
4458
+ }
4221
4459
  function LineChartBlock({
4222
4460
  lines = [],
4223
4461
  width = "100%",
4224
4462
  height = 280,
4463
+ yLabelGutter,
4225
4464
  yLabels,
4226
4465
  xLabels,
4227
4466
  animationDuration = 1.5,
@@ -4236,12 +4475,19 @@ function LineChartBlock({
4236
4475
  const [animate2, setAnimate] = React.useState(false);
4237
4476
  const pathRefs = React.useRef([]);
4238
4477
  const dotRefs = React.useRef([]);
4239
- const svgH = height;
4478
+ const svgH = toViewBoxHeight(height);
4479
+ const chartWidth = toCssLength(width, "100%");
4480
+ const chartHeight = toCssLength(height, `${DEFAULT_CHART_HEIGHT}px`);
4240
4481
  const hasEndLabels = lines.some((l) => l.endLabel || l.endIcon);
4241
4482
  const hasEndDots = lines.some((l) => l.endDot);
4242
4483
  const hasYLabels = !!((yLabels == null ? void 0 : yLabels.top) || (yLabels == null ? void 0 : yLabels.bottom));
4243
4484
  const padRight = hasEndDots ? 80 : hasEndLabels ? 60 : 16;
4244
- const pad = { top: 16, right: padRight, bottom: 16, left: hasYLabels ? 48 : 16 };
4485
+ const pad = {
4486
+ top: 16,
4487
+ right: padRight,
4488
+ bottom: 16,
4489
+ left: hasYLabels ? toPositiveNumber(yLabelGutter, DEFAULT_Y_AXIS_LABEL_GUTTER) : 16
4490
+ };
4245
4491
  React.useEffect(() => {
4246
4492
  const timer = setTimeout(() => setAnimate(true), startDelay * 1e3);
4247
4493
  return () => clearTimeout(timer);
@@ -4256,6 +4502,7 @@ function LineChartBlock({
4256
4502
  });
4257
4503
  const durationMs = animationDuration * 1e3;
4258
4504
  const hasDots = lines.some((l) => l.endDot);
4505
+ const yAxisLabelWidth = `max(0px, calc(${pad.left / svgW * 100}% - ${Y_AXIS_LABEL_GAP}px))`;
4259
4506
  React.useEffect(() => {
4260
4507
  if (!animate2 || !hasDots) return;
4261
4508
  const startTime = performance.now();
@@ -4281,299 +4528,329 @@ function LineChartBlock({
4281
4528
  return () => cancelAnimationFrame(rafId);
4282
4529
  }, [animate2, hasDots, durationMs, svgW, svgH]);
4283
4530
  const toPctY = (px) => `${px / svgH * 100}%`;
4284
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("w-full overflow-visible", className), style: { width }, children: [
4285
- /* @__PURE__ */ jsxRuntime.jsxs(
4286
- "div",
4287
- {
4288
- className: "relative overflow-visible",
4289
- style: { aspectRatio: `5 / 4`, minHeight: 220 },
4290
- children: [
4291
- (yLabels == null ? void 0 : yLabels.top) && /* @__PURE__ */ jsxRuntime.jsx(
4292
- "span",
4293
- {
4294
- className: "absolute left-0 text-xs font-medium",
4295
- style: {
4296
- top: toPctY(
4297
- yLabels.topAt != null ? yValToPixel(yLabels.topAt, svgH, pad) : pad.top
4298
- ),
4299
- transform: "translateY(-50%)",
4300
- color: labelColor
4301
- },
4302
- children: yLabels.top
4303
- }
4304
- ),
4305
- (yLabels == null ? void 0 : yLabels.bottom) && /* @__PURE__ */ jsxRuntime.jsx(
4306
- "span",
4307
- {
4308
- className: "absolute left-0 text-xs font-medium",
4309
- style: {
4310
- top: toPctY(
4311
- yLabels.bottomAt != null ? yValToPixel(yLabels.bottomAt, svgH, pad) : svgH - pad.bottom
4312
- ),
4313
- transform: "translateY(-50%)",
4314
- color: labelColor
4315
- },
4316
- children: yLabels.bottom
4317
- }
4318
- ),
4319
- /* @__PURE__ */ jsxRuntime.jsxs(
4320
- "svg",
4321
- {
4322
- viewBox: `0 0 ${svgW} ${svgH}`,
4323
- preserveAspectRatio: "none",
4324
- className: "w-full h-full",
4325
- style: { overflow: "visible" },
4326
- children: [
4327
- /* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
4328
- linesData.map(
4329
- (line, i) => {
4531
+ const yAxisLabelStyle = (top) => ({
4532
+ top,
4533
+ left: 0,
4534
+ width: yAxisLabelWidth,
4535
+ maxWidth: yAxisLabelWidth,
4536
+ transform: "translateY(-50%)",
4537
+ color: labelColor,
4538
+ textAlign: "right",
4539
+ whiteSpace: "pre-line",
4540
+ overflowWrap: "anywhere",
4541
+ lineHeight: 1.1,
4542
+ pointerEvents: "none"
4543
+ });
4544
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4545
+ "div",
4546
+ {
4547
+ className: cn("w-full overflow-visible", className),
4548
+ style: { width: chartWidth },
4549
+ children: [
4550
+ /* @__PURE__ */ jsxRuntime.jsxs(
4551
+ "div",
4552
+ {
4553
+ className: "relative overflow-visible",
4554
+ style: { height: chartHeight },
4555
+ children: [
4556
+ (yLabels == null ? void 0 : yLabels.top) && /* @__PURE__ */ jsxRuntime.jsx(
4557
+ "span",
4558
+ {
4559
+ className: "absolute text-xs font-medium",
4560
+ style: yAxisLabelStyle(
4561
+ toPctY(
4562
+ yLabels.topAt != null ? yValToPixel(yLabels.topAt, svgH, pad) : pad.top
4563
+ )
4564
+ ),
4565
+ children: yLabels.top
4566
+ }
4567
+ ),
4568
+ (yLabels == null ? void 0 : yLabels.bottom) && /* @__PURE__ */ jsxRuntime.jsx(
4569
+ "span",
4570
+ {
4571
+ className: "absolute text-xs font-medium",
4572
+ style: yAxisLabelStyle(
4573
+ toPctY(
4574
+ yLabels.bottomAt != null ? yValToPixel(yLabels.bottomAt, svgH, pad) : svgH - pad.bottom
4575
+ )
4576
+ ),
4577
+ children: yLabels.bottom
4578
+ }
4579
+ ),
4580
+ /* @__PURE__ */ jsxRuntime.jsxs(
4581
+ "svg",
4582
+ {
4583
+ viewBox: `0 0 ${svgW} ${svgH}`,
4584
+ preserveAspectRatio: "none",
4585
+ className: "w-full h-full",
4586
+ style: { overflow: "visible" },
4587
+ children: [
4588
+ /* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
4589
+ linesData.map(
4590
+ (line, i) => {
4591
+ var _a2;
4592
+ return line.fill ? /* @__PURE__ */ jsxRuntime.jsxs(
4593
+ "linearGradient",
4594
+ {
4595
+ id: `lc-fill-${uid}-${i}`,
4596
+ x1: "0",
4597
+ y1: "0",
4598
+ x2: "0",
4599
+ y2: "1",
4600
+ children: [
4601
+ /* @__PURE__ */ jsxRuntime.jsx(
4602
+ "stop",
4603
+ {
4604
+ offset: "0%",
4605
+ stopColor: line.fill.from,
4606
+ stopOpacity: (_a2 = line.fill.opacity) != null ? _a2 : 0.4
4607
+ }
4608
+ ),
4609
+ /* @__PURE__ */ jsxRuntime.jsx(
4610
+ "stop",
4611
+ {
4612
+ offset: "100%",
4613
+ stopColor: line.fill.to,
4614
+ stopOpacity: 0
4615
+ }
4616
+ )
4617
+ ]
4618
+ },
4619
+ `fill-${i}`
4620
+ ) : null;
4621
+ }
4622
+ ),
4623
+ linesData.map(
4624
+ (line, i) => line.fill ? /* @__PURE__ */ jsxRuntime.jsxs(
4625
+ "linearGradient",
4626
+ {
4627
+ id: `lc-mask-grad-${uid}-${i}`,
4628
+ x1: "0",
4629
+ y1: "0",
4630
+ x2: "0",
4631
+ y2: "1",
4632
+ children: [
4633
+ /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "white" }),
4634
+ /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "70%", stopColor: "white" }),
4635
+ /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "black" })
4636
+ ]
4637
+ },
4638
+ `mask-${i}`
4639
+ ) : null
4640
+ ),
4641
+ linesData.map(
4642
+ (line, i) => line.fill ? /* @__PURE__ */ jsxRuntime.jsx("mask", { id: `lc-mask-${uid}-${i}`, children: /* @__PURE__ */ jsxRuntime.jsx(
4643
+ "rect",
4644
+ {
4645
+ x: "0",
4646
+ y: animate2 ? "0" : `-${svgH}`,
4647
+ width: svgW,
4648
+ height: svgH,
4649
+ fill: `url(#lc-mask-grad-${uid}-${i})`,
4650
+ style: {
4651
+ transition: `y ${durationMs}ms cubic-bezier(0.25, 0.4, 0.25, 1)`
4652
+ }
4653
+ }
4654
+ ) }, `mask-el-${i}`) : null
4655
+ )
4656
+ ] }),
4657
+ showGrid && [25, 50, 75].map((pct) => {
4658
+ const y = pad.top + (100 - pct) / 100 * (svgH - pad.top - pad.bottom);
4659
+ return /* @__PURE__ */ jsxRuntime.jsx(
4660
+ "line",
4661
+ {
4662
+ x1: pad.left,
4663
+ y1: y,
4664
+ x2: svgW - pad.right,
4665
+ y2: y,
4666
+ stroke: axisColor,
4667
+ strokeWidth: 0.5,
4668
+ strokeDasharray: "4 4"
4669
+ },
4670
+ pct
4671
+ );
4672
+ }),
4673
+ linesData.map(
4674
+ (line, i) => line.fillDPath ? /* @__PURE__ */ jsxRuntime.jsx(
4675
+ "path",
4676
+ {
4677
+ d: line.fillDPath,
4678
+ fill: `url(#lc-fill-${uid}-${i})`,
4679
+ mask: `url(#lc-mask-${uid}-${i})`
4680
+ },
4681
+ `area-${i}`
4682
+ ) : null
4683
+ ),
4684
+ linesData.map((line, i) => {
4330
4685
  var _a2;
4331
- return line.fill ? /* @__PURE__ */ jsxRuntime.jsxs(
4332
- "linearGradient",
4686
+ return /* @__PURE__ */ jsxRuntime.jsx(
4687
+ "path",
4333
4688
  {
4334
- id: `lc-fill-${uid}-${i}`,
4335
- x1: "0",
4336
- y1: "0",
4337
- x2: "0",
4338
- y2: "1",
4339
- children: [
4340
- /* @__PURE__ */ jsxRuntime.jsx(
4341
- "stop",
4342
- {
4343
- offset: "0%",
4344
- stopColor: line.fill.from,
4345
- stopOpacity: (_a2 = line.fill.opacity) != null ? _a2 : 0.4
4346
- }
4347
- ),
4348
- /* @__PURE__ */ jsxRuntime.jsx(
4349
- "stop",
4350
- {
4351
- offset: "100%",
4352
- stopColor: line.fill.to,
4353
- stopOpacity: 0
4354
- }
4355
- )
4356
- ]
4689
+ ref: (el) => {
4690
+ pathRefs.current[i] = el;
4691
+ },
4692
+ d: line.lineDPath,
4693
+ fill: "none",
4694
+ stroke: line.color,
4695
+ strokeWidth: (_a2 = line.strokeWidth) != null ? _a2 : 3,
4696
+ strokeLinecap: "round",
4697
+ strokeLinejoin: "round",
4698
+ pathLength: 1,
4699
+ style: line.dashed ? {
4700
+ strokeDasharray: "8 6",
4701
+ opacity: animate2 ? 1 : 0,
4702
+ transition: `opacity ${durationMs}ms ease`
4703
+ } : {
4704
+ strokeDasharray: 1,
4705
+ strokeDashoffset: animate2 ? 0 : 1,
4706
+ transition: `stroke-dashoffset ${durationMs}ms cubic-bezier(0.25, 0.4, 0.25, 1)`
4707
+ }
4357
4708
  },
4358
- `fill-${i}`
4359
- ) : null;
4360
- }
4361
- ),
4362
- linesData.map(
4363
- (line, i) => line.fill ? /* @__PURE__ */ jsxRuntime.jsxs(
4364
- "linearGradient",
4709
+ `line-${i}`
4710
+ );
4711
+ }),
4712
+ /* @__PURE__ */ jsxRuntime.jsx(
4713
+ "line",
4365
4714
  {
4366
- id: `lc-mask-grad-${uid}-${i}`,
4367
- x1: "0",
4368
- y1: "0",
4369
- x2: "0",
4370
- y2: "1",
4371
- children: [
4372
- /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "white" }),
4373
- /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "70%", stopColor: "white" }),
4374
- /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "black" })
4375
- ]
4376
- },
4377
- `mask-${i}`
4378
- ) : null
4379
- ),
4380
- linesData.map(
4381
- (line, i) => line.fill ? /* @__PURE__ */ jsxRuntime.jsx("mask", { id: `lc-mask-${uid}-${i}`, children: /* @__PURE__ */ jsxRuntime.jsx(
4382
- "rect",
4715
+ x1: pad.left,
4716
+ y1: pad.top,
4717
+ x2: pad.left,
4718
+ y2: svgH - pad.bottom,
4719
+ stroke: axisColor,
4720
+ strokeWidth: 2
4721
+ }
4722
+ ),
4723
+ /* @__PURE__ */ jsxRuntime.jsx(
4724
+ "line",
4383
4725
  {
4384
- x: "0",
4385
- y: animate2 ? "0" : `-${svgH}`,
4386
- width: svgW,
4387
- height: svgH,
4388
- fill: `url(#lc-mask-grad-${uid}-${i})`,
4389
- style: {
4390
- transition: `y ${durationMs}ms cubic-bezier(0.25, 0.4, 0.25, 1)`
4391
- }
4726
+ x1: pad.left,
4727
+ y1: svgH - pad.bottom,
4728
+ x2: svgW - pad.right,
4729
+ y2: svgH - pad.bottom,
4730
+ stroke: axisColor,
4731
+ strokeWidth: 2
4392
4732
  }
4393
- ) }, `mask-el-${i}`) : null
4394
- )
4395
- ] }),
4396
- showGrid && [25, 50, 75].map((pct) => {
4397
- const y = pad.top + (100 - pct) / 100 * (svgH - pad.top - pad.bottom);
4398
- return /* @__PURE__ */ jsxRuntime.jsx(
4399
- "line",
4400
- {
4401
- x1: pad.left,
4402
- y1: y,
4403
- x2: svgW - pad.right,
4404
- y2: y,
4405
- stroke: axisColor,
4406
- strokeWidth: 0.5,
4407
- strokeDasharray: "4 4"
4733
+ )
4734
+ ]
4735
+ }
4736
+ ),
4737
+ linesData.map((line, i) => {
4738
+ var _a2, _b2, _c, _d, _e;
4739
+ if (!line.endDot) return null;
4740
+ const dotSize = (_a2 = line.endDot.size) != null ? _a2 : 28;
4741
+ const iconSize = dotSize * 0.55;
4742
+ const imageScale = typeof line.endDot.imageScale === "number" && Number.isFinite(line.endDot.imageScale) ? Math.min(Math.max(line.endDot.imageScale, 0), 1) : 0.72;
4743
+ const imageSize = dotSize * imageScale;
4744
+ const LucideIcon = line.endDot.lucideIcon ? lucideIcons8[line.endDot.lucideIcon] : null;
4745
+ return /* @__PURE__ */ jsxRuntime.jsx(
4746
+ "div",
4747
+ {
4748
+ ref: (el) => {
4749
+ dotRefs.current[i] = el;
4408
4750
  },
4409
- pct
4410
- );
4411
- }),
4412
- linesData.map(
4413
- (line, i) => line.fillDPath ? /* @__PURE__ */ jsxRuntime.jsx(
4414
- "path",
4415
- {
4416
- d: line.fillDPath,
4417
- fill: `url(#lc-fill-${uid}-${i})`,
4418
- mask: `url(#lc-mask-${uid}-${i})`
4751
+ className: "absolute flex items-center justify-center rounded-full shadow-lg",
4752
+ style: {
4753
+ width: dotSize,
4754
+ height: dotSize,
4755
+ background: line.endDot.gradient ? `linear-gradient(${(_b2 = line.endDot.gradient.angle) != null ? _b2 : 180}deg, ${line.endDot.gradient.from}, ${line.endDot.gradient.to})` : (_c = line.endDot.color) != null ? _c : line.color,
4756
+ transform: "translate(-50%, -50%)",
4757
+ opacity: 0,
4758
+ left: 0,
4759
+ top: 0,
4760
+ zIndex: 10
4419
4761
  },
4420
- `area-${i}`
4421
- ) : null
4422
- ),
4423
- linesData.map((line, i) => {
4424
- var _a2;
4425
- return /* @__PURE__ */ jsxRuntime.jsx(
4426
- "path",
4427
- {
4428
- ref: (el) => {
4429
- pathRefs.current[i] = el;
4430
- },
4431
- d: line.lineDPath,
4432
- fill: "none",
4433
- stroke: line.color,
4434
- strokeWidth: (_a2 = line.strokeWidth) != null ? _a2 : 3,
4435
- strokeLinecap: "round",
4436
- strokeLinejoin: "round",
4437
- pathLength: 1,
4438
- style: line.dashed ? {
4439
- strokeDasharray: "8 6",
4440
- opacity: animate2 ? 1 : 0,
4441
- transition: `opacity ${durationMs}ms ease`
4442
- } : {
4443
- strokeDasharray: 1,
4444
- strokeDashoffset: animate2 ? 0 : 1,
4445
- transition: `stroke-dashoffset ${durationMs}ms cubic-bezier(0.25, 0.4, 0.25, 1)`
4762
+ children: line.endDot.imageSrc ? /* @__PURE__ */ jsxRuntime.jsx(
4763
+ "img",
4764
+ {
4765
+ src: line.endDot.imageSrc,
4766
+ alt: (_d = line.endDot.imageAlt) != null ? _d : "",
4767
+ draggable: false,
4768
+ className: "rounded-full object-contain",
4769
+ style: {
4770
+ width: imageSize,
4771
+ height: imageSize
4772
+ }
4446
4773
  }
4447
- },
4448
- `line-${i}`
4449
- );
4450
- }),
4451
- /* @__PURE__ */ jsxRuntime.jsx(
4452
- "line",
4453
- {
4454
- x1: pad.left,
4455
- y1: pad.top,
4456
- x2: pad.left,
4457
- y2: svgH - pad.bottom,
4458
- stroke: axisColor,
4459
- strokeWidth: 2
4460
- }
4461
- ),
4462
- /* @__PURE__ */ jsxRuntime.jsx(
4463
- "line",
4774
+ ) : LucideIcon ? /* @__PURE__ */ jsxRuntime.jsx(
4775
+ LucideIcon,
4776
+ {
4777
+ size: iconSize,
4778
+ color: (_e = line.endDot.lucideIconColor) != null ? _e : "#fff",
4779
+ strokeWidth: 2.5
4780
+ }
4781
+ ) : line.endDot.icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: iconSize, lineHeight: 1 }, children: line.endDot.icon }) : null
4782
+ },
4783
+ `dot-${i}`
4784
+ );
4785
+ }),
4786
+ linesData.map((line, i) => {
4787
+ var _a2, _b2, _c, _d, _e, _f, _g;
4788
+ if (!line.endIcon && !line.endLabel) return null;
4789
+ const cx = line.lastPt[0];
4790
+ const cy = line.lastPt[1];
4791
+ const labelLines = (_b2 = (_a2 = line.endLabel) == null ? void 0 : _a2.split("\n")) != null ? _b2 : [];
4792
+ const hasDot = !!line.endDot;
4793
+ const offX = (_d = (_c = line.endLabelOffset) == null ? void 0 : _c.x) != null ? _d : 0;
4794
+ const offY = (_f = (_e = line.endLabelOffset) == null ? void 0 : _e.y) != null ? _f : 0;
4795
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4796
+ "div",
4464
4797
  {
4465
- x1: pad.left,
4466
- y1: svgH - pad.bottom,
4467
- x2: svgW - pad.right,
4468
- y2: svgH - pad.bottom,
4469
- stroke: axisColor,
4470
- strokeWidth: 2
4471
- }
4472
- )
4473
- ]
4798
+ className: "absolute flex flex-col items-center",
4799
+ style: __spreadProps(__spreadValues({}, hasDot ? {
4800
+ left: `calc(${cx / svgW * 100}% + ${offX}px)`,
4801
+ transform: "translateX(-50%)"
4802
+ } : { right: 0 }), {
4803
+ top: toPctY(cy + offY),
4804
+ opacity: animate2 ? 1 : 0,
4805
+ transition: `opacity 400ms ease ${durationMs * 0.8}ms`,
4806
+ textAlign: "center",
4807
+ whiteSpace: "nowrap"
4808
+ }),
4809
+ children: [
4810
+ line.endIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-lg", children: line.endIcon }),
4811
+ labelLines.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
4812
+ "span",
4813
+ {
4814
+ className: "text-xs font-semibold leading-tight",
4815
+ style: { color: (_g = line.endLabelColor) != null ? _g : labelColor },
4816
+ children: labelLines.map((ln, j) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block", children: ln }, j))
4817
+ }
4818
+ )
4819
+ ]
4820
+ },
4821
+ `end-${i}`
4822
+ );
4823
+ })
4824
+ ]
4825
+ }
4826
+ ),
4827
+ xLabels && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative text-xs font-medium mt-1", style: { color: labelColor, height: 20 }, children: [
4828
+ xLabels.left && /* @__PURE__ */ jsxRuntime.jsx(
4829
+ "span",
4830
+ {
4831
+ className: "absolute",
4832
+ style: {
4833
+ left: `${xValToPercent((_a = xLabels.leftAt) != null ? _a : 0, svgW, pad)}%`,
4834
+ transform: "translateX(-50%)"
4835
+ },
4836
+ children: xLabels.left
4474
4837
  }
4475
4838
  ),
4476
- linesData.map((line, i) => {
4477
- var _a2, _b2, _c, _d;
4478
- if (!line.endDot) return null;
4479
- const dotSize = (_a2 = line.endDot.size) != null ? _a2 : 28;
4480
- const iconSize = dotSize * 0.55;
4481
- const LucideIcon = line.endDot.lucideIcon ? lucideIcons8[line.endDot.lucideIcon] : null;
4482
- return /* @__PURE__ */ jsxRuntime.jsx(
4483
- "div",
4484
- {
4485
- ref: (el) => {
4486
- dotRefs.current[i] = el;
4487
- },
4488
- className: "absolute flex items-center justify-center rounded-full shadow-lg",
4489
- style: {
4490
- width: dotSize,
4491
- height: dotSize,
4492
- background: line.endDot.gradient ? `linear-gradient(${(_b2 = line.endDot.gradient.angle) != null ? _b2 : 180}deg, ${line.endDot.gradient.from}, ${line.endDot.gradient.to})` : (_c = line.endDot.color) != null ? _c : line.color,
4493
- transform: "translate(-50%, -50%)",
4494
- opacity: 0,
4495
- left: 0,
4496
- top: 0,
4497
- zIndex: 10
4498
- },
4499
- children: LucideIcon ? /* @__PURE__ */ jsxRuntime.jsx(
4500
- LucideIcon,
4501
- {
4502
- size: iconSize,
4503
- color: (_d = line.endDot.lucideIconColor) != null ? _d : "#fff",
4504
- strokeWidth: 2.5
4505
- }
4506
- ) : line.endDot.icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: iconSize, lineHeight: 1 }, children: line.endDot.icon }) : null
4507
- },
4508
- `dot-${i}`
4509
- );
4510
- }),
4511
- linesData.map((line, i) => {
4512
- var _a2, _b2, _c, _d, _e, _f, _g;
4513
- if (!line.endIcon && !line.endLabel) return null;
4514
- const cx = line.lastPt[0];
4515
- const cy = line.lastPt[1];
4516
- const labelLines = (_b2 = (_a2 = line.endLabel) == null ? void 0 : _a2.split("\n")) != null ? _b2 : [];
4517
- const hasDot = !!line.endDot;
4518
- const offX = (_d = (_c = line.endLabelOffset) == null ? void 0 : _c.x) != null ? _d : 0;
4519
- const offY = (_f = (_e = line.endLabelOffset) == null ? void 0 : _e.y) != null ? _f : 0;
4520
- return /* @__PURE__ */ jsxRuntime.jsxs(
4521
- "div",
4522
- {
4523
- className: "absolute flex flex-col items-center",
4524
- style: __spreadProps(__spreadValues({}, hasDot ? {
4525
- left: `calc(${cx / svgW * 100}% + ${offX}px)`,
4526
- transform: "translateX(-50%)"
4527
- } : { right: 0 }), {
4528
- top: toPctY(cy + offY),
4529
- opacity: animate2 ? 1 : 0,
4530
- transition: `opacity 400ms ease ${durationMs * 0.8}ms`,
4531
- textAlign: "center",
4532
- whiteSpace: "nowrap"
4533
- }),
4534
- children: [
4535
- line.endIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-lg", children: line.endIcon }),
4536
- labelLines.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
4537
- "span",
4538
- {
4539
- className: "text-xs font-semibold leading-tight",
4540
- style: { color: (_g = line.endLabelColor) != null ? _g : labelColor },
4541
- children: labelLines.map((ln, j) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block", children: ln }, j))
4542
- }
4543
- )
4544
- ]
4839
+ xLabels.right && /* @__PURE__ */ jsxRuntime.jsx(
4840
+ "span",
4841
+ {
4842
+ className: "absolute",
4843
+ style: {
4844
+ left: `${xValToPercent((_b = xLabels.rightAt) != null ? _b : 100, svgW, pad)}%`,
4845
+ transform: "translateX(-50%)"
4545
4846
  },
4546
- `end-${i}`
4547
- );
4548
- })
4549
- ]
4550
- }
4551
- ),
4552
- xLabels && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative text-xs font-medium mt-1", style: { color: labelColor, height: 20 }, children: [
4553
- xLabels.left && /* @__PURE__ */ jsxRuntime.jsx(
4554
- "span",
4555
- {
4556
- className: "absolute",
4557
- style: {
4558
- left: `${xValToPercent((_a = xLabels.leftAt) != null ? _a : 0, svgW, pad)}%`,
4559
- transform: "translateX(-50%)"
4560
- },
4561
- children: xLabels.left
4562
- }
4563
- ),
4564
- xLabels.right && /* @__PURE__ */ jsxRuntime.jsx(
4565
- "span",
4566
- {
4567
- className: "absolute",
4568
- style: {
4569
- left: `${xValToPercent((_b = xLabels.rightAt) != null ? _b : 100, svgW, pad)}%`,
4570
- transform: "translateX(-50%)"
4571
- },
4572
- children: xLabels.right
4573
- }
4574
- )
4575
- ] })
4576
- ] });
4847
+ children: xLabels.right
4848
+ }
4849
+ )
4850
+ ] })
4851
+ ]
4852
+ }
4853
+ );
4577
4854
  }
4578
4855
  function getInitials2(name) {
4579
4856
  return name.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
@@ -5560,6 +5837,7 @@ function TimelineBlock({
5560
5837
  } : {};
5561
5838
  const renderNodeIcon = iconPlacement === "node" && item.icon;
5562
5839
  const renderContentIcon = iconPlacement === "content" && item.icon;
5840
+ const renderTitleIcon = iconPlacement === "before-title" && item.icon;
5563
5841
  const showLabel = labelPlacement === "above" && item.label;
5564
5842
  const showOutgoing = !isLast || !!(endLine == null ? void 0 : endLine.show);
5565
5843
  const outgoingColor = isLast ? resolveColor((_a2 = endLine == null ? void 0 : endLine.color) != null ? _a2 : lineColor, blockLineValue) : resolveColor((_b = item.lineColor) != null ? _b : lineColor, blockLineValue);
@@ -5575,6 +5853,45 @@ function TimelineBlock({
5575
5853
  const defaultItemSpacing = Math.max(16, nodeSize * 0.4);
5576
5854
  const bottomPad = isLast ? (endLine == null ? void 0 : endLine.show) ? EDGE_LINE_HEIGHT : 0 : itemSpacing != null ? itemSpacing : defaultItemSpacing;
5577
5855
  const nodeTopMargin = iconPlacement === "node" ? 0 : 4;
5856
+ const contentBody = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5857
+ renderContentIcon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-1.5", children: renderIcon3(item.icon) }),
5858
+ /* @__PURE__ */ jsxRuntime.jsxs(
5859
+ "div",
5860
+ {
5861
+ style: iconPlacement === "node" ? {
5862
+ minHeight: nodeSize,
5863
+ display: "flex",
5864
+ flexDirection: "column",
5865
+ justifyContent: "center"
5866
+ } : void 0,
5867
+ children: [
5868
+ showLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-muted-foreground/70 uppercase tracking-wider", children: item.label }),
5869
+ /* @__PURE__ */ jsxRuntime.jsx(
5870
+ "h4",
5871
+ {
5872
+ className: cn(
5873
+ "font-semibold text-foreground",
5874
+ iconPlacement === "node" ? "text-xl leading-tight" : "text-sm",
5875
+ renderTitleIcon ? "flex items-center gap-2" : null
5876
+ ),
5877
+ children: renderTitleIcon ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5878
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex shrink-0", children: renderIcon3(item.icon) }),
5879
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0", children: item.title })
5880
+ ] }) : item.title
5881
+ }
5882
+ )
5883
+ ]
5884
+ }
5885
+ ),
5886
+ item.description && /* @__PURE__ */ jsxRuntime.jsx(
5887
+ "p",
5888
+ {
5889
+ className: "text-sm text-muted-foreground",
5890
+ style: { marginTop: resolvedTitleDescGap },
5891
+ children: item.description
5892
+ }
5893
+ )
5894
+ ] });
5578
5895
  return /* @__PURE__ */ jsxRuntime.jsxs(Wrapper, __spreadProps(__spreadValues({ className: "relative flex" }, animProps), { children: [
5579
5896
  /* @__PURE__ */ jsxRuntime.jsxs(
5580
5897
  "div",
@@ -5638,46 +5955,19 @@ function TimelineBlock({
5638
5955
  ]
5639
5956
  }
5640
5957
  ),
5641
- /* @__PURE__ */ jsxRuntime.jsxs(
5958
+ /* @__PURE__ */ jsxRuntime.jsx(
5642
5959
  "div",
5643
5960
  {
5644
5961
  className: "flex-1 min-w-0",
5645
5962
  style: { paddingBottom: bottomPad, paddingTop: isFirst && showIncoming ? EDGE_LINE_HEIGHT : 0, paddingLeft: contentGap },
5646
- children: [
5647
- renderContentIcon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-1.5", children: renderIcon3(item.icon) }),
5648
- /* @__PURE__ */ jsxRuntime.jsxs(
5649
- "div",
5650
- {
5651
- style: iconPlacement === "node" ? {
5652
- minHeight: nodeSize,
5653
- display: "flex",
5654
- flexDirection: "column",
5655
- justifyContent: "center"
5656
- } : void 0,
5657
- children: [
5658
- showLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-muted-foreground/70 uppercase tracking-wider", children: item.label }),
5659
- /* @__PURE__ */ jsxRuntime.jsx(
5660
- "h4",
5661
- {
5662
- className: cn(
5663
- "font-semibold text-foreground",
5664
- iconPlacement === "node" ? "text-xl leading-tight" : "text-sm"
5665
- ),
5666
- children: item.title
5667
- }
5668
- )
5669
- ]
5670
- }
5671
- ),
5672
- item.description && /* @__PURE__ */ jsxRuntime.jsx(
5673
- "p",
5674
- {
5675
- className: "text-sm text-muted-foreground",
5676
- style: { marginTop: resolvedTitleDescGap },
5677
- children: item.description
5678
- }
5679
- )
5680
- ]
5963
+ children: item.contentCard ? /* @__PURE__ */ jsxRuntime.jsx(
5964
+ "div",
5965
+ {
5966
+ className: "rounded-lg border border-border/60 bg-card/80 p-3 shadow-sm",
5967
+ "data-timeline-content-card": "true",
5968
+ children: contentBody
5969
+ }
5970
+ ) : contentBody
5681
5971
  }
5682
5972
  )
5683
5973
  ] }), i);