@diagrammo/dgmo 0.39.0 → 0.40.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/index.cjs CHANGED
@@ -105,7 +105,7 @@ function vennAliasKeywordRemovedMessage(args) {
105
105
  return `Venn 'alias' keyword was removed. Use 'as' instead \u2014 '${args.name} as ${args.alias}'.`;
106
106
  }
107
107
  function pipeOperatorRemovedMessage() {
108
- return `'|' removed \u2014 'Node | c: red' \u2192 'Node c: red'`;
108
+ return `'|' is no longer metadata syntax \u2014 write metadata inline as 'key: value' (e.g. "Name color: red"), or remove the '|'.`;
109
109
  }
110
110
  function ganttBarePercentRemovedMessage(percent) {
111
111
  return `Bare-percent progress shorthand '| ${percent}' was removed. Use 'progress: ${percent.replace(/%$/, "")}' (integer 0\u2013100, no '%').`;
@@ -13609,7 +13609,8 @@ function buildScatterOption(parsed, palette, isDark, textColor, axisLineColor, g
13609
13609
  solid: parsed.solidFill === true
13610
13610
  }),
13611
13611
  borderColor: catColor,
13612
- borderWidth: CHART_BORDER_WIDTH
13612
+ borderWidth: CHART_BORDER_WIDTH,
13613
+ opacity: SCATTER_FILL_OPACITY
13613
13614
  },
13614
13615
  label: labelConfig,
13615
13616
  emphasis: emphasisConfig,
@@ -13636,6 +13637,7 @@ function buildScatterOption(parsed, palette, isDark, textColor, axisLineColor, g
13636
13637
  {
13637
13638
  type: "scatter",
13638
13639
  data,
13640
+ itemStyle: { opacity: SCATTER_FILL_OPACITY },
13639
13641
  label: labelConfig,
13640
13642
  emphasis: emphasisConfig,
13641
13643
  blur: blurConfig
@@ -14941,7 +14943,7 @@ async function renderExtendedChartForExport(content, theme, palette) {
14941
14943
  chart.dispose();
14942
14944
  }
14943
14945
  }
14944
- var echarts, import_charts, import_components, import_renderers, EMPHASIS_SELF, EMPHASIS_SERIES, BLUR_DIM, EMPHASIS_LINE, CHART_BASE, CHART_BORDER_WIDTH, VALID_EXTENDED_TYPES, KNOWN_EXTENDED_OPTIONS, EXTENDED_CHART_DOORS, FIT_PADDING, OFFSCREEN, fitStackedLabelToSegment, ECHART_EXPORT_WIDTH, ECHART_EXPORT_HEIGHT, STANDARD_CHART_TYPES;
14946
+ var echarts, import_charts, import_components, import_renderers, EMPHASIS_SELF, EMPHASIS_SERIES, BLUR_DIM, EMPHASIS_LINE, CHART_BASE, CHART_BORDER_WIDTH, SCATTER_FILL_OPACITY, VALID_EXTENDED_TYPES, KNOWN_EXTENDED_OPTIONS, EXTENDED_CHART_DOORS, FIT_PADDING, OFFSCREEN, fitStackedLabelToSegment, ECHART_EXPORT_WIDTH, ECHART_EXPORT_HEIGHT, STANDARD_CHART_TYPES;
14945
14947
  var init_echarts = __esm({
14946
14948
  "src/echarts.ts"() {
14947
14949
  "use strict";
@@ -14999,6 +15001,7 @@ var init_echarts = __esm({
14999
15001
  tooltip: { show: false }
15000
15002
  };
15001
15003
  CHART_BORDER_WIDTH = 2;
15004
+ SCATTER_FILL_OPACITY = 0.65;
15002
15005
  VALID_EXTENDED_TYPES = /* @__PURE__ */ new Set([
15003
15006
  "sankey",
15004
15007
  "chord",
@@ -15274,8 +15277,61 @@ function parseEventLine(content, palette) {
15274
15277
  EVENT_LINE_DIAGNOSTIC_CODES.NO_EVENTS
15275
15278
  );
15276
15279
  }
15280
+ if (options.scale) {
15281
+ validateEraDateOrder(result.events, result.eras, pushWarning);
15282
+ }
15277
15283
  return result;
15278
15284
  }
15285
+ function validateEraDateOrder(events, eras, pushWarning) {
15286
+ if (eras.length < 2) return;
15287
+ const order = new Map(eras.map((e, i) => [e.name, i]));
15288
+ const dated = events.filter(
15289
+ (e) => e.dateValue !== null && e.era !== null && order.has(e.era)
15290
+ );
15291
+ if (dated.length === 0) return;
15292
+ const perEra = /* @__PURE__ */ new Map();
15293
+ for (const ev of dated) {
15294
+ const idx = order.get(ev.era);
15295
+ const cur = perEra.get(idx);
15296
+ if (!cur) {
15297
+ perEra.set(idx, { min: ev, max: ev });
15298
+ } else {
15299
+ if (ev.dateValue < cur.min.dateValue) cur.min = ev;
15300
+ if (ev.dateValue > cur.max.dateValue) cur.max = ev;
15301
+ }
15302
+ }
15303
+ const n = eras.length;
15304
+ const prefixMax = new Array(n).fill(null);
15305
+ const suffixMin = new Array(n).fill(null);
15306
+ for (let k = 1; k < n; k++) {
15307
+ const prev = perEra.get(k - 1)?.max ?? null;
15308
+ const carried = prefixMax[k - 1] ?? null;
15309
+ prefixMax[k] = prev && (!carried || prev.dateValue > carried.dateValue) ? prev : carried;
15310
+ }
15311
+ for (let k = n - 2; k >= 0; k--) {
15312
+ const next = perEra.get(k + 1)?.min ?? null;
15313
+ const carried = suffixMin[k + 1] ?? null;
15314
+ suffixMin[k] = next && (!carried || next.dateValue < carried.dateValue) ? next : carried;
15315
+ }
15316
+ for (const ev of dated) {
15317
+ const k = order.get(ev.era);
15318
+ const ahead = suffixMin[k];
15319
+ const behind = prefixMax[k];
15320
+ if (ahead && ev.dateValue > ahead.dateValue) {
15321
+ pushWarning(
15322
+ ev.lineNumber,
15323
+ `"${ev.label}" (${ev.date}) is in era "${ev.era}" but dated after era "${ahead.era}" begins (${ahead.date}). event-line eras run left-to-right by date \u2014 fix the date or move it to the right era, or their brackets will overlap.`,
15324
+ EVENT_LINE_DIAGNOSTIC_CODES.ERA_DATE_ORDER
15325
+ );
15326
+ } else if (behind && ev.dateValue < behind.dateValue) {
15327
+ pushWarning(
15328
+ ev.lineNumber,
15329
+ `"${ev.label}" (${ev.date}) is in era "${ev.era}" but dated before era "${behind.era}" ends (${behind.date}). event-line eras run left-to-right by date \u2014 fix the date or move it to the right era, or their brackets will overlap.`,
15330
+ EVENT_LINE_DIAGNOSTIC_CODES.ERA_DATE_ORDER
15331
+ );
15332
+ }
15333
+ }
15334
+ }
15279
15335
  function parseEventHeader(trimmed, lineNumber, era, aliasMap, diagnostics, pushWarning) {
15280
15336
  let date = null;
15281
15337
  let dateValue = null;
@@ -15342,7 +15398,8 @@ var init_parser13 = __esm({
15342
15398
  EVENT_LINE_DIAGNOSTIC_CODES = {
15343
15399
  NO_EVENTS: "E_EVENT_LINE_NO_EVENTS",
15344
15400
  BAD_DATE: "E_EVENT_LINE_BAD_DATE",
15345
- UNSUPPORTED: "E_EVENT_LINE_UNSUPPORTED"
15401
+ UNSUPPORTED: "E_EVENT_LINE_UNSUPPORTED",
15402
+ ERA_DATE_ORDER: "E_EVENT_LINE_ERA_DATE_ORDER"
15346
15403
  };
15347
15404
  NON_ISO_DATE_RE = /^\d{1,4}[/.]\d/;
15348
15405
  SIDE_RE = /^side\s+(above|below|alternate)\b/i;
@@ -26278,9 +26335,6 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26278
26335
  const hasExpandedEra = parsed.events.some(
26279
26336
  (e) => e.era && eraByName.has(e.era) && !collapsedSet.has(e.era)
26280
26337
  );
26281
- const anyCollapsed = parsed.events.some(
26282
- (e) => e.era && collapsedSet.has(e.era)
26283
- );
26284
26338
  const sideOpt = parsed.options.side;
26285
26339
  const alternate = sideOpt === "alternate";
26286
26340
  const eraSide = alternate ? "below" : sideOpt === "above" ? "below" : "above";
@@ -26347,7 +26401,8 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26347
26401
  x: 0,
26348
26402
  side,
26349
26403
  lane: 0,
26350
- left: 0
26404
+ left: 0,
26405
+ spanHalf: 0
26351
26406
  };
26352
26407
  }
26353
26408
  const era = slot.era;
@@ -26360,19 +26415,19 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26360
26415
  palette.textOnFillLight,
26361
26416
  palette.textOnFillDark
26362
26417
  );
26363
- const overflow = slot.members.length > ERA_MEMBER_MAX;
26364
- const shown = overflow ? slot.members.slice(0, ERA_MEMBER_MAX - 1) : slot.members;
26365
- const memberStrs = shown.map(
26418
+ let spanLo = null;
26419
+ let spanHi = null;
26420
+ for (const m of slot.members) {
26421
+ if (m.dateValue === null) continue;
26422
+ if (spanLo === null || m.dateValue < spanLo) spanLo = m.dateValue;
26423
+ if (spanHi === null || m.dateValue > spanHi) spanHi = m.dateValue;
26424
+ }
26425
+ const repDateValue = spanLo !== null && spanHi !== null ? (spanLo + spanHi) / 2 : null;
26426
+ const memberStrs = slot.members.map(
26366
26427
  (m) => `\u2022 ${m.date ? `${m.date} ` : ""}${m.label}`
26367
26428
  );
26368
- const bulletColors = shown.map(eventColor);
26429
+ const bulletColors = slot.members.map(eventColor);
26369
26430
  const lines = wrapDescription(memberStrs, charsPerLine);
26370
- if (overflow) {
26371
- lines.push({
26372
- text: `+${slot.members.length - (ERA_MEMBER_MAX - 1)} more`,
26373
- kind: "plain"
26374
- });
26375
- }
26376
26431
  return {
26377
26432
  kind: "era",
26378
26433
  event: null,
@@ -26380,7 +26435,7 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26380
26435
  members: slot.members,
26381
26436
  label: era.name,
26382
26437
  date: null,
26383
- dateValue: null,
26438
+ dateValue: repDateValue,
26384
26439
  lineNumber: era.lineNumber,
26385
26440
  eraName: era.name,
26386
26441
  color: solid,
@@ -26392,87 +26447,189 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26392
26447
  x: 0,
26393
26448
  side,
26394
26449
  lane: 0,
26395
- left: 0
26450
+ left: 0,
26451
+ spanHalf: 0
26396
26452
  };
26397
26453
  });
26398
- const scaled = parsed.options.scale && !anyCollapsed && placed.every((p) => p.dateValue !== null);
26454
+ const scaled = parsed.options.scale && placed.every((p) => p.dateValue !== null);
26399
26455
  const innerW = width - H_MARGIN * 2;
26400
- if (scaled) {
26401
- const vals = placed.map((p) => p.dateValue);
26402
- const lo = Math.min(...vals);
26403
- const hi = Math.max(...vals);
26404
- placed.forEach((p) => {
26405
- p.x = H_MARGIN + (hi > lo ? (p.dateValue - lo) / (hi - lo) * innerW : innerW / 2);
26456
+ const MIN_DOT_GAP = 16;
26457
+ const STRETCH_CAP = 8;
26458
+ const COLLAPSE_W = 64;
26459
+ const SEG_GAP = 16;
26460
+ const segments = [];
26461
+ let run = [];
26462
+ const flushRun = () => {
26463
+ if (!run.length) return;
26464
+ const ds = run.map((e) => e.dateValue);
26465
+ segments.push({
26466
+ kind: "run",
26467
+ events: run,
26468
+ lo: Math.min(...ds),
26469
+ hi: Math.max(...ds)
26406
26470
  });
26407
- const MIN_DOT_GAP = 16;
26408
- let prevX = -Infinity;
26409
- let prevVal = null;
26410
- for (const p of [...placed].sort((a, b) => a.x - b.x)) {
26411
- if (p.dateValue === prevVal) {
26412
- p.x = prevX;
26413
- continue;
26471
+ run = [];
26472
+ };
26473
+ for (const p of placed) {
26474
+ if (p.kind === "era") {
26475
+ flushRun();
26476
+ segments.push({ kind: "capsule", p });
26477
+ } else {
26478
+ run.push(p);
26479
+ }
26480
+ }
26481
+ flushRun();
26482
+ const totalRunTime = segments.reduce(
26483
+ (s, seg) => s + (seg.kind === "run" ? seg.hi - seg.lo : 0),
26484
+ 0
26485
+ );
26486
+ const place = (stretch) => {
26487
+ if (scaled) {
26488
+ const pxPerUnit = totalRunTime > 0 ? innerW * stretch / totalRunTime : 0;
26489
+ let cursor = H_MARGIN;
26490
+ for (const seg of segments) {
26491
+ if (seg.kind === "capsule") {
26492
+ seg.p.x = cursor + COLLAPSE_W / 2;
26493
+ seg.p.spanHalf = COLLAPSE_W / 2;
26494
+ cursor += COLLAPSE_W + SEG_GAP;
26495
+ } else {
26496
+ for (const e of seg.events) {
26497
+ e.x = cursor + (e.dateValue - seg.lo) * pxPerUnit;
26498
+ e.spanHalf = 0;
26499
+ }
26500
+ cursor += (seg.hi - seg.lo) * pxPerUnit + SEG_GAP;
26501
+ }
26502
+ }
26503
+ let prevX = -Infinity;
26504
+ let prevVal = null;
26505
+ for (const p of [...placed].sort((a, b) => a.x - b.x)) {
26506
+ if (p.kind === "era") {
26507
+ prevX = p.x + p.spanHalf;
26508
+ prevVal = null;
26509
+ continue;
26510
+ }
26511
+ if (p.dateValue === prevVal) {
26512
+ p.x = prevX;
26513
+ continue;
26514
+ }
26515
+ if (p.x < prevX + MIN_DOT_GAP) p.x = prevX + MIN_DOT_GAP;
26516
+ prevX = p.x;
26517
+ prevVal = p.dateValue;
26518
+ }
26519
+ } else {
26520
+ placed.forEach((p) => {
26521
+ p.spanHalf = 0;
26522
+ });
26523
+ const n = placed.length;
26524
+ const spacing = n > 1 ? Math.max(MIN_SPACING, innerW / (n - 1)) : 0;
26525
+ placed.forEach((p, i) => {
26526
+ p.x = H_MARGIN + i * spacing;
26527
+ });
26528
+ }
26529
+ for (const side of ["above", "below"]) {
26530
+ const arr = placed.filter((p) => p.side === side).sort((a, b) => a.x - b.x);
26531
+ const laneRight = [];
26532
+ for (let i = 0; i < arr.length; i++) {
26533
+ const p = arr[i];
26534
+ const next = arr[i + 1];
26535
+ const crowdedRight = !!next && next.x - p.x < CARD_W + FAN_GAP;
26536
+ const preferred = crowdedRight ? p.x - CARD_W + CARD_INSET : p.x - CARD_W / 2;
26537
+ const maxLeft = p.x - CARD_INSET;
26538
+ const minLeft = p.x - CARD_W + CARD_INSET;
26539
+ let lane = laneRight.length;
26540
+ let left = Math.max(preferred, minLeft);
26541
+ for (let l = 0; l < laneRight.length; l++) {
26542
+ const want = Math.max(preferred, laneRight[l] + FAN_GAP, minLeft);
26543
+ if (want <= maxLeft) {
26544
+ lane = l;
26545
+ left = want;
26546
+ break;
26547
+ }
26548
+ }
26549
+ p.lane = lane;
26550
+ p.left = Math.max(6, left);
26551
+ laneRight[lane] = p.left + CARD_W;
26552
+ }
26553
+ }
26554
+ };
26555
+ const derive = () => {
26556
+ const contentW2 = Math.max(
26557
+ width,
26558
+ Math.max(...placed.map((p) => p.x + p.spanHalf)) + H_MARGIN,
26559
+ Math.max(...placed.map((p) => p.left + CARD_W)) + 6
26560
+ );
26561
+ const laneOffset = { above: [], below: [] };
26562
+ for (const side of ["above", "below"]) {
26563
+ const laneH = [];
26564
+ for (const p of placed) {
26565
+ if (p.side !== side) continue;
26566
+ laneH[p.lane] = Math.max(laneH[p.lane] ?? 0, p.cardH);
26567
+ }
26568
+ const off = [];
26569
+ let acc = 0;
26570
+ for (let l = 0; l < laneH.length; l++) {
26571
+ off[l] = acc;
26572
+ acc += (laneH[l] ?? 0) + LANE_GAP;
26414
26573
  }
26415
- if (p.x < prevX + MIN_DOT_GAP) p.x = prevX + MIN_DOT_GAP;
26416
- prevX = p.x;
26417
- prevVal = p.dateValue;
26574
+ laneOffset[side] = off;
26418
26575
  }
26576
+ const laneNear2 = (p) => (p.side === "above" ? LEADER_ABOVE : LEADER_BELOW) + (laneOffset[p.side][p.lane] ?? 0);
26577
+ const ext = (side) => Math.max(
26578
+ 0,
26579
+ ...placed.filter((p) => p.side === side).map((p) => laneNear2(p) + p.cardH)
26580
+ );
26581
+ const dateAbove = placed.some((p) => p.side === "below" && p.date);
26582
+ const dateBelow = placed.some((p) => p.side === "above" && p.date);
26583
+ const contentAbove = Math.max(
26584
+ ext("above"),
26585
+ dateAbove ? DATE_OFFSET + 10 : 0
26586
+ );
26587
+ const contentBelow = Math.max(
26588
+ ext("below"),
26589
+ dateBelow ? DATE_OFFSET + 10 : 0
26590
+ );
26591
+ const aboveExt = contentAbove + (hasExpandedEra && eraSide === "above" ? ERA_BLOCK : 0);
26592
+ const belowExt = contentBelow + (hasExpandedEra && eraSide === "below" ? ERA_BLOCK : 0);
26593
+ const TOP_PAD = 14;
26594
+ const BOT_PAD = 14;
26595
+ const spineY2 = topUsed + TOP_PAD + aboveExt;
26596
+ const eraBaseY2 = eraSide === "above" ? spineY2 - (contentAbove + 14) : spineY2 + (contentBelow + 14);
26597
+ const contentH = spineY2 + belowExt + BOT_PAD;
26598
+ const totalH2 = Math.max(heightHint, contentH);
26599
+ return { contentW: contentW2, contentH, totalH: totalH2, spineY: spineY2, eraBaseY: eraBaseY2, laneNear: laneNear2 };
26600
+ };
26601
+ const envStretch = Number(
26602
+ globalThis.process?.env?.["DGMO_EVT_STRETCH"]
26603
+ );
26604
+ let layout;
26605
+ if (Number.isFinite(envStretch) && envStretch > 0) {
26606
+ place(envStretch);
26607
+ layout = derive();
26419
26608
  } else {
26420
- const n = placed.length;
26421
- const spacing = n > 1 ? Math.max(MIN_SPACING, innerW / (n - 1)) : 0;
26422
- placed.forEach((p, i) => {
26423
- p.x = H_MARGIN + i * spacing;
26424
- });
26425
- }
26426
- for (const side of ["above", "below"]) {
26427
- const arr = placed.filter((p) => p.side === side).sort((a, b) => a.x - b.x);
26428
- const laneRight = [];
26429
- for (let i = 0; i < arr.length; i++) {
26430
- const p = arr[i];
26431
- const next = arr[i + 1];
26432
- const crowdedRight = !!next && next.x - p.x < CARD_W + FAN_GAP;
26433
- const preferred = crowdedRight ? p.x - CARD_W + CARD_INSET : p.x - CARD_W / 2;
26434
- const maxLeft = p.x - CARD_INSET;
26435
- const minLeft = p.x - CARD_W + CARD_INSET;
26436
- let lane = laneRight.length;
26437
- let left = Math.max(preferred, minLeft);
26438
- for (let l = 0; l < laneRight.length; l++) {
26439
- const want = Math.max(preferred, laneRight[l] + FAN_GAP, minLeft);
26440
- if (want <= maxLeft) {
26441
- lane = l;
26442
- left = want;
26443
- break;
26609
+ place(1);
26610
+ layout = derive();
26611
+ const panelAspect = width / heightHint;
26612
+ const aspectOf = (l) => l.contentW / l.contentH;
26613
+ if (scaled && layout.contentH > heightHint && aspectOf(layout) < panelAspect) {
26614
+ place(STRETCH_CAP);
26615
+ const capped = derive();
26616
+ if (aspectOf(capped) <= panelAspect) {
26617
+ layout = capped;
26618
+ } else {
26619
+ let loF = 1;
26620
+ let hiF = STRETCH_CAP;
26621
+ for (let it = 0; it < 8; it++) {
26622
+ const mid = (loF + hiF) / 2;
26623
+ place(mid);
26624
+ if (aspectOf(derive()) >= panelAspect) hiF = mid;
26625
+ else loF = mid;
26444
26626
  }
26627
+ place(hiF);
26628
+ layout = derive();
26445
26629
  }
26446
- p.lane = lane;
26447
- p.left = Math.max(6, left);
26448
- laneRight[lane] = p.left + CARD_W;
26449
26630
  }
26450
26631
  }
26451
- const contentW = Math.max(
26452
- width,
26453
- Math.max(...placed.map((p) => p.x)) + H_MARGIN,
26454
- Math.max(...placed.map((p) => p.left + CARD_W)) + 6
26455
- );
26456
- const rowGap = (side) => Math.max(0, ...placed.filter((p) => p.side === side).map((p) => p.cardH)) + LANE_GAP;
26457
- const rowGapA = rowGap("above");
26458
- const rowGapB = rowGap("below");
26459
- const ext = (side) => Math.max(
26460
- 0,
26461
- ...placed.filter((p) => p.side === side).map(
26462
- (p) => (side === "above" ? LEADER_ABOVE : LEADER_BELOW) + p.lane * (side === "above" ? rowGapA : rowGapB) + p.cardH
26463
- )
26464
- );
26465
- const dateAbove = placed.some((p) => p.side === "below" && p.date);
26466
- const dateBelow = placed.some((p) => p.side === "above" && p.date);
26467
- const contentAbove = Math.max(ext("above"), dateAbove ? DATE_OFFSET + 10 : 0);
26468
- const contentBelow = Math.max(ext("below"), dateBelow ? DATE_OFFSET + 10 : 0);
26469
- const aboveExt = contentAbove + (hasExpandedEra && eraSide === "above" ? ERA_BLOCK : 0);
26470
- const belowExt = contentBelow + (hasExpandedEra && eraSide === "below" ? ERA_BLOCK : 0);
26471
- const TOP_PAD = 14;
26472
- const BOT_PAD = 14;
26473
- const spineY = topUsed + TOP_PAD + aboveExt;
26474
- const eraBaseY = eraSide === "above" ? spineY - (contentAbove + 14) : spineY + (contentBelow + 14);
26475
- const totalH = Math.max(heightHint, spineY + belowExt + BOT_PAD);
26632
+ const { contentW, totalH, spineY, eraBaseY, laneNear } = layout;
26476
26633
  const svg = d3Selection6.select(container).append("svg").attr("width", contentW).attr("height", totalH).attr("viewBox", `0 0 ${contentW} ${totalH}`).attr("preserveAspectRatio", "xMidYMin meet").attr("xmlns", "http://www.w3.org/2000/svg").style("font-family", FONT_FAMILY);
26477
26634
  svg.append("rect").attr("width", contentW).attr("height", totalH).attr("fill", palette.bg);
26478
26635
  if (showTitle) {
@@ -26500,12 +26657,11 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26500
26657
  activeGroup
26501
26658
  });
26502
26659
  }
26503
- const xs = placed.map((p) => p.x);
26504
- const x0 = Math.min(...xs);
26505
- const x1 = Math.max(...xs);
26660
+ const x0 = Math.min(...placed.map((p) => p.x - p.spanHalf));
26661
+ const x1 = Math.max(...placed.map((p) => p.x + p.spanHalf));
26506
26662
  svg.append("line").attr("x1", x0 - 20).attr("y1", spineY).attr("x2", x1 + 20).attr("y2", spineY).attr("stroke", palette.text).attr("stroke-width", 2.5).attr("stroke-linecap", "round");
26507
26663
  const geo = placed.map((p) => {
26508
- const near = p.side === "above" ? spineY - LEADER_ABOVE - p.lane * rowGapA : spineY + LEADER_BELOW + p.lane * rowGapB;
26664
+ const near = p.side === "above" ? spineY - laneNear(p) : spineY + laneNear(p);
26509
26665
  const top = p.side === "above" ? near - p.cardH : near;
26510
26666
  return { p, near, top };
26511
26667
  });
@@ -26617,7 +26773,7 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26617
26773
  for (const p of placed) {
26618
26774
  if (p.kind === "era") {
26619
26775
  const col = p.color;
26620
- const half = Math.min(46, 16 + (p.members.length - 1) * 9);
26776
+ const half = Math.max(16, p.spanHalf);
26621
26777
  const spineLeft = x0 - 20;
26622
26778
  const spineRight = x1 + 20;
26623
26779
  const bx0 = Math.max(spineLeft, p.x - half);
@@ -26628,6 +26784,11 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26628
26784
  "d",
26629
26785
  `M${bx0},${spineY} L${bx0},${barY} L${bx1},${barY} L${bx1},${spineY}`
26630
26786
  ).attr("fill", "none").attr("stroke", col).attr("stroke-width", 1.5).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
26787
+ const breakCx = (bx0 + bx1) / 2;
26788
+ eg.append("line").attr("x1", breakCx - BREAK_GAP / 2).attr("y1", spineY).attr("x2", breakCx + BREAK_GAP / 2).attr("y2", spineY).attr("stroke", palette.bg).attr("stroke-width", 4).attr("stroke-linecap", "butt");
26789
+ for (const d of axisBreakPaths(breakCx, spineY)) {
26790
+ eg.append("path").attr("d", d).attr("fill", "none").attr("stroke", col).attr("stroke-width", 1.25).attr("stroke-linecap", "round").attr("stroke-linejoin", "round").attr("stroke-opacity", 0.85);
26791
+ }
26631
26792
  if (onClickItem) {
26632
26793
  const ln = p.lineNumber;
26633
26794
  eg.style("cursor", "pointer").on("click", () => onClickItem(ln));
@@ -26661,13 +26822,23 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26661
26822
  }
26662
26823
  const clampX = (v) => Math.max(4, Math.min(contentW - 4, v));
26663
26824
  const cap = eraSide === "above" ? 1 : -1;
26664
- for (const r of runs) {
26665
- if (r.collapsed) continue;
26825
+ const drawn = runs.filter((r) => !r.collapsed).map((r) => ({
26826
+ run: r,
26827
+ x0: clampX(r.firstX - ERA_BRACKET_PAD),
26828
+ x1: clampX(r.lastX + ERA_BRACKET_PAD)
26829
+ })).sort((a, b) => a.run.firstX - b.run.firstX);
26830
+ for (let i = 1; i < drawn.length; i++) {
26831
+ const prev = drawn[i - 1];
26832
+ const cur = drawn[i];
26833
+ if (prev.x1 <= cur.x0) continue;
26834
+ const mid = (prev.run.lastX + cur.run.firstX) / 2;
26835
+ prev.x1 = Math.max(prev.run.lastX, Math.min(prev.x1, mid - ERA_SEAM_GAP));
26836
+ cur.x0 = Math.min(cur.run.firstX, Math.max(cur.x0, mid + ERA_SEAM_GAP));
26837
+ }
26838
+ for (const { run: r, x0: x02, x1: x12 } of drawn) {
26666
26839
  const neutral = !r.era.color;
26667
26840
  const col = neutral ? palette.text : resolveColor(r.era.color, palette) ?? palette.text;
26668
26841
  const op = neutral ? 0.5 : 0.85;
26669
- const x02 = clampX(r.firstX - 14);
26670
- const x12 = clampX(r.lastX + 14);
26671
26842
  const y = eraBaseY;
26672
26843
  const eg = svg.append("g").attr("data-era", r.era.name).attr("data-era-collapsed", String(r.collapsed)).attr("data-line-number", r.era.lineNumber);
26673
26844
  eg.append("path").attr(
@@ -26693,6 +26864,22 @@ function renderEventLineForExport(container, parsed, palette, isDark, exportDims
26693
26864
  tagOverride
26694
26865
  );
26695
26866
  }
26867
+ function axisBreakPaths(cx, cy) {
26868
+ const HUMPS = 2;
26869
+ const STEPS = 14;
26870
+ const wave = (x0) => {
26871
+ let d = "";
26872
+ for (let i = 0; i <= STEPS; i++) {
26873
+ const t = i / STEPS;
26874
+ const y = cy - BREAK_HALF_H + t * 2 * BREAK_HALF_H;
26875
+ const x = x0 + BREAK_AMP * Math.sin(t * Math.PI * HUMPS);
26876
+ d += `${i === 0 ? "M" : "L"}${x.toFixed(2)},${y.toFixed(2)}`;
26877
+ if (i < STEPS) d += " ";
26878
+ }
26879
+ return d;
26880
+ };
26881
+ return [wave(cx - BREAK_GAP / 2), wave(cx + BREAK_GAP / 2)];
26882
+ }
26696
26883
  function wrapDescription(lines, charsPerLine) {
26697
26884
  const out = [];
26698
26885
  for (const line11 of lines) {
@@ -26722,7 +26909,7 @@ function renderBody(cardG, lines, bodyColor, palette, startBaseline = CARD_BODY_
26722
26909
  y += DESC_LINE_H;
26723
26910
  }
26724
26911
  }
26725
- var d3Selection6, CARD_W, H_MARGIN, TITLE_AREA, LEGEND_BAND, LEADER_ABOVE, LEADER_BELOW, DESC_FONT, DESC_LINE_H, CARD_PAD, CARD_BODY_TOP, DOT_R, MIN_SPACING, LANE_GAP, NEUTRAL_TAG, CARD_INSET, FAN_GAP, DATE_OFFSET, ERA_BLOCK, ERA_BRACKET_CAP, ERA_LEG, ERA_LABEL_FONT, ERA_MEMBER_MAX;
26912
+ var d3Selection6, CARD_W, H_MARGIN, TITLE_AREA, LEGEND_BAND, LEADER_ABOVE, LEADER_BELOW, DESC_FONT, DESC_LINE_H, CARD_PAD, CARD_BODY_TOP, DOT_R, MIN_SPACING, LANE_GAP, NEUTRAL_TAG, CARD_INSET, FAN_GAP, DATE_OFFSET, ERA_BLOCK, ERA_BRACKET_CAP, ERA_BRACKET_PAD, ERA_SEAM_GAP, BREAK_HALF_H, BREAK_AMP, BREAK_GAP, ERA_LEG, ERA_LABEL_FONT;
26726
26913
  var init_renderer = __esm({
26727
26914
  "src/event-line/renderer.ts"() {
26728
26915
  "use strict";
@@ -26757,9 +26944,13 @@ var init_renderer = __esm({
26757
26944
  DATE_OFFSET = 28;
26758
26945
  ERA_BLOCK = 30;
26759
26946
  ERA_BRACKET_CAP = 8;
26947
+ ERA_BRACKET_PAD = 14;
26948
+ ERA_SEAM_GAP = 3;
26949
+ BREAK_HALF_H = 6;
26950
+ BREAK_AMP = 2.2;
26951
+ BREAK_GAP = 5;
26760
26952
  ERA_LEG = 13;
26761
26953
  ERA_LABEL_FONT = 11.5;
26762
- ERA_MEMBER_MAX = 6;
26763
26954
  }
26764
26955
  });
26765
26956
 
@@ -26901,13 +27092,13 @@ function renderVersionControl(container, parsed, palette, isDark, onClickItem, e
26901
27092
  }
26902
27093
  const showTitle = !!parsed.title;
26903
27094
  const titleH = showTitle ? TITLE_AREA2 : 0;
26904
- const W2 = Math.round(contentW);
27095
+ const W3 = Math.round(contentW);
26905
27096
  const H2 = Math.round(contentH + titleH);
26906
27097
  const exportMode = !!exportDims;
26907
- const svg = d3Selection7.select(container).append("svg").attr("width", W2).attr("height", H2).attr("viewBox", `0 0 ${W2} ${H2}`).attr("preserveAspectRatio", "xMidYMin meet").attr("xmlns", "http://www.w3.org/2000/svg").style("font-family", FONT_FAMILY);
26908
- svg.append("rect").attr("width", W2).attr("height", H2).attr("fill", palette.bg);
27098
+ const svg = d3Selection7.select(container).append("svg").attr("width", W3).attr("height", H2).attr("viewBox", `0 0 ${W3} ${H2}`).attr("preserveAspectRatio", "xMidYMin meet").attr("xmlns", "http://www.w3.org/2000/svg").style("font-family", FONT_FAMILY);
27099
+ svg.append("rect").attr("width", W3).attr("height", H2).attr("fill", palette.bg);
26909
27100
  if (showTitle) {
26910
- const t = svg.append("text").attr("class", "chart-title").attr("x", W2 / 2).attr("y", TITLE_Y).attr("text-anchor", "middle").attr("fill", palette.text).attr("font-family", FONT_FAMILY).attr("font-size", TITLE_FONT_SIZE).attr("font-weight", TITLE_FONT_WEIGHT).attr("data-line-number", parsed.titleLineNumber ?? 1).text(parsed.title);
27101
+ const t = svg.append("text").attr("class", "chart-title").attr("x", W3 / 2).attr("y", TITLE_Y).attr("text-anchor", "middle").attr("fill", palette.text).attr("font-family", FONT_FAMILY).attr("font-size", TITLE_FONT_SIZE).attr("font-weight", TITLE_FONT_WEIGHT).attr("data-line-number", parsed.titleLineNumber ?? 1).text(parsed.title);
26911
27102
  if (onClickItem && parsed.titleLineNumber && !exportMode) {
26912
27103
  const ln = parsed.titleLineNumber;
26913
27104
  t.style("cursor", "pointer").on("click", () => onClickItem(ln));
@@ -32689,7 +32880,7 @@ function findClearPosition(w, h, points, obstacles) {
32689
32880
  }
32690
32881
  function resolveLabelOverlaps(boxes) {
32691
32882
  const MAX_PASSES = 8;
32692
- const PAD = 4;
32883
+ const PAD2 = 4;
32693
32884
  for (let pass = 0; pass < MAX_PASSES; pass++) {
32694
32885
  let moved = false;
32695
32886
  for (let i = 0; i < boxes.length; i++) {
@@ -32698,8 +32889,8 @@ function resolveLabelOverlaps(boxes) {
32698
32889
  const b = boxes[j];
32699
32890
  const dx = Math.abs(a.cx - b.cx);
32700
32891
  const dy = Math.abs(a.cy - b.cy);
32701
- const overlapX = (a.w + b.w) / 2 + PAD - dx;
32702
- const overlapY = (a.h + b.h) / 2 + PAD - dy;
32892
+ const overlapX = (a.w + b.w) / 2 + PAD2 - dx;
32893
+ const overlapY = (a.h + b.h) / 2 + PAD2 - dy;
32703
32894
  if (overlapX > 0 && overlapY > 0) {
32704
32895
  const shift = overlapY / 2 + 1;
32705
32896
  if (a.cy < b.cy) {
@@ -43078,7 +43269,7 @@ function buildPathD(pts, direction) {
43078
43269
  function computePortPts(edges, nodeMap, direction) {
43079
43270
  const srcPts = /* @__PURE__ */ new Map();
43080
43271
  const tgtPts = /* @__PURE__ */ new Map();
43081
- const PAD = 0.1;
43272
+ const PAD2 = 0.1;
43082
43273
  const activeEdges = edges.filter((e) => e.points.length > 0);
43083
43274
  const bySource = /* @__PURE__ */ new Map();
43084
43275
  for (const e of activeEdges) {
@@ -43094,7 +43285,7 @@ function computePortPts(edges, nodeMap, direction) {
43094
43285
  ).sort((a, b) => direction === "LR" ? a.t.y - b.t.y : a.t.x - b.t.x);
43095
43286
  const n = sorted.length;
43096
43287
  for (let i = 0; i < n; i++) {
43097
- const frac = n === 1 ? 0.5 : PAD + (1 - 2 * PAD) * i / (n - 1);
43288
+ const frac = n === 1 ? 0.5 : PAD2 + (1 - 2 * PAD2) * i / (n - 1);
43098
43289
  const { e, t } = sorted[i];
43099
43290
  const isBackward = direction === "LR" ? t.x < source.x : t.y < source.y;
43100
43291
  if (direction === "LR") {
@@ -43124,7 +43315,7 @@ function computePortPts(edges, nodeMap, direction) {
43124
43315
  ).sort((a, b) => direction === "LR" ? a.s.y - b.s.y : a.s.x - b.s.x);
43125
43316
  const n = sorted.length;
43126
43317
  for (let i = 0; i < n; i++) {
43127
- const frac = n === 1 ? 0.5 : PAD + (1 - 2 * PAD) * i / (n - 1);
43318
+ const frac = n === 1 ? 0.5 : PAD2 + (1 - 2 * PAD2) * i / (n - 1);
43128
43319
  const { e, s } = sorted[i];
43129
43320
  const isBackward = direction === "LR" ? target.x < s.x : target.y < s.y;
43130
43321
  if (direction === "LR") {
@@ -52917,6 +53108,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
52917
53108
  y,
52918
53109
  score,
52919
53110
  palette,
53111
+ isDark,
52920
53112
  SCORE_LABEL_R
52921
53113
  );
52922
53114
  if (!exportDims) {
@@ -52948,7 +53140,14 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
52948
53140
  const allSteps = parsed.phases.length > 0 ? parsed.phases.flatMap((p) => p.steps) : parsed.steps;
52949
53141
  for (const pt of layout.curvePoints) {
52950
53142
  const step = allSteps[pt.stepIndex];
52951
- const faceG = renderScoreFace(curveG, pt.x, pt.y, pt.score, palette);
53143
+ const faceG = renderScoreFace(
53144
+ curveG,
53145
+ pt.x,
53146
+ pt.y,
53147
+ pt.score,
53148
+ palette,
53149
+ isDark
53150
+ );
52952
53151
  addEmotionLabel(faceG, pt, palette);
52953
53152
  if (step) {
52954
53153
  faceG.attr("data-line-number", step.lineNumber);
@@ -52974,7 +53173,14 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
52974
53173
  const pt = layout.curvePoints[0];
52975
53174
  const allSteps = parsed.phases.length > 0 ? parsed.phases.flatMap((p) => p.steps) : parsed.steps;
52976
53175
  const step = allSteps[pt.stepIndex];
52977
- const faceG = renderScoreFace(curveG, pt.x, pt.y, pt.score, palette);
53176
+ const faceG = renderScoreFace(
53177
+ curveG,
53178
+ pt.x,
53179
+ pt.y,
53180
+ pt.score,
53181
+ palette,
53182
+ isDark
53183
+ );
52978
53184
  addEmotionLabel(faceG, pt, palette);
52979
53185
  if (step) {
52980
53186
  faceG.attr("data-line-number", step.lineNumber);
@@ -53071,6 +53277,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53071
53277
  faceCy,
53072
53278
  step.score,
53073
53279
  palette,
53280
+ isDark,
53074
53281
  COLLAPSED_FACE_R
53075
53282
  );
53076
53283
  faceG.attr("data-line-number", step.lineNumber);
@@ -53174,7 +53381,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53174
53381
  const fcy = parseFloat(el.attr("data-cy") ?? "0");
53175
53382
  el.attr(
53176
53383
  "transform",
53177
- `translate(${fcx},${fcy}) scale(1.5) translate(${-fcx},${-fcy})`
53384
+ `translate(${fcx},${fcy}) scale(${FACE_HOVER_SCALE}) translate(${-fcx},${-fcy})`
53178
53385
  );
53179
53386
  } else {
53180
53387
  el.attr("transform", null);
@@ -53220,12 +53427,12 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53220
53427
  PADDING2,
53221
53428
  Math.min(fcx - bw / 2, layout.totalWidth - PADDING2 - bw)
53222
53429
  );
53223
- const by = Math.max(PADDING2, fcy - FACE_RADIUS - THOUGHT_GAP - bh);
53430
+ const by = Math.max(PADDING2, fcy - FACE_HOVER_R - THOUGHT_GAP - bh);
53224
53431
  const scoreColor = scoreToColor(score, palette);
53225
53432
  const tintedBg = mix(scoreColor, palette.surface, 20);
53226
53433
  const g = overlayG.append("g").attr("class", "journey-thought-hover");
53227
53434
  g.append("rect").attr("x", bx).attr("y", by).attr("width", bw).attr("height", bh).attr("rx", CARD_RADIUS).attr("fill", tintedBg).attr("stroke", scoreColor).attr("stroke-width", CARD_STROKE_WIDTH2);
53228
- g.append("line").attr("x1", fcx).attr("y1", by + bh).attr("x2", fcx).attr("y2", fcy - FACE_RADIUS - 1).attr("stroke", scoreColor).attr("stroke-width", CARD_STROKE_WIDTH2);
53435
+ g.append("line").attr("x1", fcx).attr("y1", by + bh).attr("x2", fcx).attr("y2", fcy - FACE_HOVER_R - 1).attr("stroke", scoreColor).attr("stroke-width", CARD_STROKE_WIDTH2);
53229
53436
  const centerX = bx + bw / 2;
53230
53437
  for (let i = 0; i < lines.length; i++) {
53231
53438
  g.append("text").attr("x", centerX).attr("y", by + THOUGHT_PAD_Y + (i + 1) * THOUGHT_LINE_H - 2).attr("text-anchor", "middle").attr("font-size", THOUGHT_FONT).attr("font-style", "italic").attr("fill", palette.textMuted).text(lines[i]);
@@ -53468,24 +53675,23 @@ function renderPersonaSilhouette(parent, cx, cy, color, palette, scale = 1) {
53468
53675
  `M ${cx} ${cy - 12 * s} C ${cx + 10 * s} ${cy - 12 * s}, ${cx + 9 * s} ${cy + 2 * s}, ${cx + 6 * s} ${cy + 6 * s} Q ${cx} ${cy + 9 * s}, ${cx - 6 * s} ${cy + 6 * s} C ${cx - 9 * s} ${cy + 2 * s}, ${cx - 10 * s} ${cy - 12 * s}, ${cx} ${cy - 12 * s} Z`
53469
53676
  ).attr("fill", fill2).attr("stroke", stroke2).attr("stroke-width", 1.2);
53470
53677
  }
53471
- function renderScoreFace(parent, cx, cy, score, palette, radius) {
53678
+ function renderScoreFace(parent, cx, cy, score, palette, isDark, radius) {
53472
53679
  const r = radius ?? FACE_RADIUS;
53473
53680
  const color = scoreToColor(score, palette);
53474
53681
  const g = parent.append("g").attr("class", "journey-face").attr("data-cx", cx).attr("data-cy", cy);
53475
- g.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r).attr("fill", color).attr("stroke", palette.bg).attr("stroke-width", 1.5);
53476
- const eyeColor = contrastText(
53477
- color,
53478
- palette.textOnFillLight,
53479
- palette.textOnFillDark
53480
- );
53682
+ const faceFill = shapeFill(palette, color, isDark);
53683
+ g.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r + 1).attr("fill", palette.bg);
53684
+ g.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r).attr("fill", faceFill).attr("stroke", color).attr("stroke-width", 2);
53685
+ const eyeColor = color;
53481
53686
  const eyeY = cy - r * 0.15;
53482
53687
  const eyeSpacing = r * 0.32;
53483
53688
  const eyeR = r * 0.12;
53484
53689
  g.append("circle").attr("cx", cx - eyeSpacing).attr("cy", eyeY).attr("r", eyeR).attr("fill", eyeColor);
53485
53690
  g.append("circle").attr("cx", cx + eyeSpacing).attr("cy", eyeY).attr("r", eyeR).attr("fill", eyeColor);
53486
- const mouthY = cy + r * 0.25;
53487
- const mouthW = r * 0.45;
53488
- const curve = (score - 3) / 2 * r * 0.35;
53691
+ const mouthW = r * 0.46;
53692
+ const curve = (score - 3) / 2 * r * 0.42;
53693
+ const apexY = cy + r * 0.32;
53694
+ const mouthY = apexY - curve / 2;
53489
53695
  g.append("path").attr(
53490
53696
  "d",
53491
53697
  `M ${cx - mouthW} ${mouthY} Q ${cx} ${mouthY + curve} ${cx + mouthW} ${mouthY}`
@@ -53556,7 +53762,7 @@ function renderJourneyMapForExport(content, theme, palette) {
53556
53762
  }
53557
53763
  return svgEl.outerHTML;
53558
53764
  }
53559
- var d3, d3Shape10, DIAGRAM_PADDING13, PADDING2, CARD_PADDING_X3, CARD_PADDING_Y3, CARD_HEADER_HEIGHT3, CARD_STROKE_WIDTH2, CARD_META_LINE_HEIGHT3, CARD_GAP_INTERNAL, COLUMN_RADIUS2, COLUMN_HEADER_HEIGHT2, COLUMN_PADDING3, FONT_SIZE_TITLE, FONT_SIZE_PHASE, FONT_SIZE_STEP, FONT_SIZE_META, GRID_LINE_OPACITY, CURVE_STROKE_WIDTH, FACE_RADIUS, DIM_HOVER, TITLE_LINE_HEIGHT, EMOTION_LABEL_MAX_WIDTH, EMOTION_LABEL_FONT_SIZE, ICON_THUMBS_DOWN, ICON_THUMBS_UP, ICON_THOUGHT;
53765
+ var d3, d3Shape10, DIAGRAM_PADDING13, PADDING2, CARD_PADDING_X3, CARD_PADDING_Y3, CARD_HEADER_HEIGHT3, CARD_STROKE_WIDTH2, CARD_META_LINE_HEIGHT3, CARD_GAP_INTERNAL, COLUMN_RADIUS2, COLUMN_HEADER_HEIGHT2, COLUMN_PADDING3, FONT_SIZE_TITLE, FONT_SIZE_PHASE, FONT_SIZE_STEP, FONT_SIZE_META, GRID_LINE_OPACITY, CURVE_STROKE_WIDTH, FACE_RADIUS, FACE_HOVER_SCALE, FACE_HOVER_R, DIM_HOVER, TITLE_LINE_HEIGHT, EMOTION_LABEL_MAX_WIDTH, EMOTION_LABEL_FONT_SIZE, ICON_THUMBS_DOWN, ICON_THUMBS_UP, ICON_THOUGHT;
53560
53766
  var init_renderer17 = __esm({
53561
53767
  "src/journey-map/renderer.ts"() {
53562
53768
  "use strict";
@@ -53592,6 +53798,8 @@ var init_renderer17 = __esm({
53592
53798
  GRID_LINE_OPACITY = 0.15;
53593
53799
  CURVE_STROKE_WIDTH = 2.5;
53594
53800
  FACE_RADIUS = 14;
53801
+ FACE_HOVER_SCALE = 1.5;
53802
+ FACE_HOVER_R = (FACE_RADIUS + 1) * FACE_HOVER_SCALE;
53595
53803
  DIM_HOVER = 0.25;
53596
53804
  TITLE_LINE_HEIGHT = 16;
53597
53805
  EMOTION_LABEL_MAX_WIDTH = 180;
@@ -56630,7 +56838,7 @@ function layoutMap(resolvedIn, data, size, opts) {
56630
56838
  const akRef = resolved.regions.some((r) => r.iso === "US-AK") || resolved.pois.some((p) => inAlaska(p.lon, p.lat));
56631
56839
  const hiRef = resolved.regions.some((r) => r.iso === "US-HI") || resolved.pois.some((p) => inHawaii(p.lon, p.lat));
56632
56840
  if (resolved.projection === "albers-usa" && usLayer && (akRef || hiRef)) {
56633
- const PAD = 8;
56841
+ const PAD2 = 8;
56634
56842
  const GAP = 12;
56635
56843
  const yB = height - FIT_PAD;
56636
56844
  const BW = 8;
@@ -56673,22 +56881,22 @@ function layoutMap(resolvedIn, data, size, opts) {
56673
56881
  const f = usLayer.get(iso);
56674
56882
  if (!f) return boxX;
56675
56883
  const x0 = boxX;
56676
- const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
56884
+ const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD2);
56677
56885
  if (iw < 24) return boxX;
56678
- const xr = x0 + iw + 2 * PAD;
56886
+ const xr = x0 + iw + 2 * PAD2;
56679
56887
  const floor = coastFloor(x0, xr);
56680
56888
  const topGuess = floor > -Infinity ? floor + GAP : yB - height * 0.42;
56681
56889
  proj.fitWidth(iw, f);
56682
56890
  const bb = (0, import_d3_geo2.geoPath)(proj).bounds(f);
56683
56891
  const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
56684
- const needH = sh + 2 * PAD;
56892
+ const needH = sh + 2 * PAD2;
56685
56893
  let topFit = topGuess;
56686
56894
  const bottom = Math.min(topFit + needH, yB);
56687
56895
  if (bottom - topFit < needH) topFit = bottom - needH;
56688
56896
  proj.fitExtent(
56689
56897
  [
56690
- [x0 + PAD, topFit + PAD],
56691
- [xr - PAD, bottom - PAD]
56898
+ [x0 + PAD2, topFit + PAD2],
56899
+ [xr - PAD2, bottom - PAD2]
56692
56900
  ],
56693
56901
  f
56694
56902
  );
@@ -58212,15 +58420,15 @@ function layoutMap(resolvedIn, data, size, opts) {
58212
58420
  }
58213
58421
  }
58214
58422
  if (regionLabelGuards.length > 0) {
58215
- const PAD = 2;
58423
+ const PAD2 = 2;
58216
58424
  const poiRects = labels.filter((l) => l.poiId !== void 0 && l.hidden !== true).map((l) => {
58217
58425
  const w = measureLegendText(l.text, FONT2);
58218
58426
  const x = l.anchor === "start" ? l.x : l.anchor === "end" ? l.x - w : l.x - w / 2;
58219
58427
  return {
58220
- x: x - PAD,
58428
+ x: x - PAD2,
58221
58429
  y: l.y - FONT2,
58222
- w: w + 2 * PAD,
58223
- h: FONT2 * 1.4 + 2 * PAD
58430
+ w: w + 2 * PAD2,
58431
+ h: FONT2 * 1.4 + 2 * PAD2
58224
58432
  };
58225
58433
  });
58226
58434
  for (const g of regionLabelGuards) {
@@ -58845,7 +59053,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
58845
59053
  if (poiLabels.length) {
58846
59054
  const patchBlurId = nid("dgmo-map-label-patch-blur");
58847
59055
  defs.append("filter").attr("id", patchBlurId).attr("x", "-50%").attr("y", "-50%").attr("width", "200%").attr("height", "200%").append("feGaussianBlur").attr("in", "SourceGraphic").attr("stdDeviation", 2.5);
58848
- const PAD = 8;
59056
+ const PAD2 = 8;
58849
59057
  const buildPatch = (labels, maskId, decoCluster) => {
58850
59058
  if (!labels.length) return;
58851
59059
  const patchMask = defs.append("mask").attr("id", maskId).attr("maskUnits", "userSpaceOnUse").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
@@ -58861,10 +59069,10 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
58861
59069
  const h = th + 6;
58862
59070
  blobs.append("rect").attr("x", cx - w / 2).attr("y", cy - h / 2).attr("width", w).attr("height", h).attr("rx", h / 2).attr("fill", "white");
58863
59071
  blobRects.push({
58864
- x0: cx - w / 2 - PAD,
58865
- y0: cy - h / 2 - PAD,
58866
- x1: cx + w / 2 + PAD,
58867
- y1: cy + h / 2 + PAD
59072
+ x0: cx - w / 2 - PAD2,
59073
+ y0: cy - h / 2 - PAD2,
59074
+ x1: cx + w / 2 + PAD2,
59075
+ y1: cy + h / 2 + PAD2
58868
59076
  });
58869
59077
  }
58870
59078
  const gPatch = svg.append("g").attr("class", "dgmo-map-label-patch").attr("mask", `url(#${maskId})`).style("pointer-events", "none");
@@ -60282,13 +60490,13 @@ function renderTreemap(container, parsed, palette, isDark, exportDims, options =
60282
60490
  g.append("text").attr("class", "dgmo-treemap-header-value").attr("x", w - ICON_RESERVE - 4).attr("y", 13).attr("text-anchor", "end").attr("font-size", 10.5).attr("fill", ink).attr("opacity", 0.75).text(valStr);
60283
60491
  }
60284
60492
  }
60285
- const PAD = Math.max(4, Math.min(14, Math.floor(Math.min(w, h) / 5)));
60493
+ const PAD2 = Math.max(4, Math.min(14, Math.floor(Math.min(w, h) / 5)));
60286
60494
  const MIN_FS = 7;
60287
- if (!cell.isContainer && w >= 2 * PAD + 8 && h >= 2 * PAD + 8) {
60288
- const maxW = w - 2 * PAD;
60495
+ if (!cell.isContainer && w >= 2 * PAD2 + 8 && h >= 2 * PAD2 + 8) {
60496
+ const maxW = w - 2 * PAD2;
60289
60497
  const horizMinW = measureText(cell.label, MIN_FS) * 1.06;
60290
- if (horizMinW > maxW && h > w * 1.25 && h - 2 * PAD > maxW) {
60291
- drawVerticalLabel(g, cell.label, w, h, PAD, ink);
60498
+ if (horizMinW > maxW && h > w * 1.25 && h - 2 * PAD2 > maxW) {
60499
+ drawVerticalLabel(g, cell.label, w, h, PAD2, ink);
60292
60500
  if (drillable && w > 30 && h > 22) drawFocusIcon(g, w, ink, 9);
60293
60501
  continue;
60294
60502
  }
@@ -60299,8 +60507,8 @@ function renderTreemap(container, parsed, palette, isDark, exportDims, options =
60299
60507
  const nameOpacity = 0.95 - tg * 0.5;
60300
60508
  const vfs = clamp3(Math.round(fs * 0.42), 11, 28);
60301
60509
  const valOpacity = Math.max(0.45, nameOpacity - 0.1);
60302
- let y = PAD + Math.round(fs * 0.78);
60303
- g.append("text").attr("class", "dgmo-treemap-label").attr("x", PAD).attr("y", y).attr("font-size", fs).attr("font-weight", 600).attr("fill", ink).attr("opacity", nameOpacity).text(clipLabel(cell.label, maxW, fs));
60510
+ let y = PAD2 + Math.round(fs * 0.78);
60511
+ g.append("text").attr("class", "dgmo-treemap-label").attr("x", PAD2).attr("y", y).attr("font-size", fs).attr("font-weight", 600).attr("fill", ink).attr("opacity", nameOpacity).text(clipLabel(cell.label, maxW, fs));
60304
60512
  const valParts = [
60305
60513
  opts.noValues ? "" : compactNumber2(cell.value),
60306
60514
  opts.noPercent ? "" : formatPct(cell.pctOfRoot)
@@ -60308,12 +60516,12 @@ function renderTreemap(container, parsed, palette, isDark, exportDims, options =
60308
60516
  const combined = valParts.join(" \xB7 ");
60309
60517
  const nameBaseY = y;
60310
60518
  const addLineAt = (text, yy, size, opacity) => {
60311
- g.append("text").attr("class", "dgmo-treemap-value").attr("x", PAD).attr("y", yy).attr("font-size", size).attr("fill", ink).attr("opacity", opacity).text(text);
60519
+ g.append("text").attr("class", "dgmo-treemap-value").attr("x", PAD2).attr("y", yy).attr("font-size", size).attr("fill", ink).attr("opacity", opacity).text(text);
60312
60520
  };
60313
60521
  if (combined) {
60314
60522
  const fitsW = measureText(combined, vfs) <= maxW;
60315
60523
  const valY = y + lineDrop(fs, vfs);
60316
- const roomBelow = valY + descent(vfs) <= h - PAD;
60524
+ const roomBelow = valY + descent(vfs) <= h - PAD2;
60317
60525
  if (fitsW && roomBelow) {
60318
60526
  addLineAt(combined, valY, vfs, valOpacity);
60319
60527
  } else if (fitsW) {
@@ -60321,24 +60529,24 @@ function renderTreemap(container, parsed, palette, isDark, exportDims, options =
60321
60529
  const rendered = clipLabel(cell.label, maxW, fs);
60322
60530
  const nW = measureText(rendered, fs) * 1.12;
60323
60531
  const gap = Math.max(12, Math.round(fs * 0.25));
60324
- if (PAD + nW + gap + measureText(combined, ivfs) <= w - PAD) {
60325
- g.append("text").attr("class", "dgmo-treemap-value").attr("x", PAD + nW + gap).attr("y", nameBaseY).attr("font-size", ivfs).attr("fill", ink).attr("opacity", valOpacity).text(combined);
60532
+ if (PAD2 + nW + gap + measureText(combined, ivfs) <= w - PAD2) {
60533
+ g.append("text").attr("class", "dgmo-treemap-value").attr("x", PAD2 + nW + gap).attr("y", nameBaseY).attr("font-size", ivfs).attr("fill", ink).attr("opacity", valOpacity).text(combined);
60326
60534
  }
60327
60535
  } else if (valParts.length === 2) {
60328
60536
  const d1 = lineDrop(fs, vfs);
60329
- if (y + d1 + descent(vfs) <= h - PAD) {
60537
+ if (y + d1 + descent(vfs) <= h - PAD2) {
60330
60538
  y += d1;
60331
60539
  addLineAt(valParts[0], y, vfs, valOpacity);
60332
60540
  }
60333
60541
  const pfs = Math.max(10, Math.round(vfs * 0.9));
60334
60542
  const d2 = lineDrop(vfs, pfs);
60335
- if (y + d2 + descent(pfs) <= h - PAD) {
60543
+ if (y + d2 + descent(pfs) <= h - PAD2) {
60336
60544
  y += d2;
60337
60545
  addLineAt(valParts[1], y, pfs, valOpacity);
60338
60546
  }
60339
60547
  } else {
60340
60548
  const onlyY = y + lineDrop(fs, vfs);
60341
- if (onlyY + descent(vfs) <= h - PAD) {
60549
+ if (onlyY + descent(vfs) <= h - PAD2) {
60342
60550
  addLineAt(combined, onlyY, vfs, valOpacity);
60343
60551
  }
60344
60552
  }
@@ -68485,6 +68693,144 @@ function decodeDiagramUrl(hash) {
68485
68693
  init_dgmo_router();
68486
68694
  init_palettes();
68487
68695
  init_diagnostics();
68696
+
68697
+ // src/error-card.ts
68698
+ init_text_measure();
68699
+ init_fonts();
68700
+ var MONO_FAMILY = "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace";
68701
+ var W2 = 660;
68702
+ var PAD = 24;
68703
+ var CONTENT_W = W2 - PAD * 2;
68704
+ var MAX_ERRORS = 4;
68705
+ var MONO_CH = 0.6;
68706
+ var CODE_FS = 12.5;
68707
+ function renderErrorCard(errors, source, palette, theme) {
68708
+ const c = palette[theme === "dark" ? "dark" : "light"];
68709
+ const isTransparent = theme === "transparent";
68710
+ const card = c.surface;
68711
+ const codeBg = c.bg;
68712
+ const border = c.border;
68713
+ const danger = c.destructive ?? c.colors.red;
68714
+ const text = c.text;
68715
+ const muted = c.textMuted;
68716
+ const srcLines = source.split("\n");
68717
+ const shown = errors.slice(0, MAX_ERRORS);
68718
+ const overflow = errors.length - shown.length;
68719
+ const parts = [];
68720
+ let y = PAD + 8;
68721
+ parts.push(frownMark(PAD + 14, y + 12, danger));
68722
+ parts.push(
68723
+ `<text x="${PAD + 38}" y="${y + 11}" fill="${text}" font-family="${FONT_FAMILY}" font-size="16" font-weight="700">Couldn't render this diagram</text>`,
68724
+ `<text x="${W2 - PAD}" y="${y + 10}" text-anchor="end" fill="${muted}" font-family="${FONT_FAMILY}" font-size="12" font-weight="600" letter-spacing="0.5">dgmo</text>`
68725
+ );
68726
+ y += 24;
68727
+ const errLabel = errors.length === 1 ? "1 error" : `${errors.length} errors`;
68728
+ parts.push(
68729
+ `<text x="${PAD + 38}" y="${y + 4}" fill="${muted}" font-family="${FONT_FAMILY}" font-size="12.5">${errLabel} \u2014 fix the lines below and re-render</text>`
68730
+ );
68731
+ y += 22;
68732
+ parts.push(
68733
+ `<line x1="${PAD}" y1="${y}" x2="${W2 - PAD}" y2="${y}" stroke="${border}" stroke-width="1"/>`
68734
+ );
68735
+ y += 24;
68736
+ for (const e of shown) {
68737
+ const hasLine = e.line > 0;
68738
+ let msgX = PAD;
68739
+ if (hasLine) {
68740
+ const label = e.column ? `Line ${e.line}:${e.column}` : `Line ${e.line}`;
68741
+ const badgeW = 14 + label.length * 7;
68742
+ parts.push(
68743
+ `<rect x="${PAD}" y="${y - 13}" width="${badgeW}" height="19" rx="4" fill="${danger}"/>`,
68744
+ `<text x="${PAD + badgeW / 2}" y="${y + 1}" text-anchor="middle" fill="${c.bg}" font-family="${FONT_FAMILY}" font-size="11.5" font-weight="700">${escapeXml(label)}</text>`
68745
+ );
68746
+ msgX = PAD + badgeW + 12;
68747
+ }
68748
+ const lines = wrapMessage(e.message, CONTENT_W - (msgX - PAD), CONTENT_W);
68749
+ lines.forEach((line11, i) => {
68750
+ parts.push(
68751
+ `<text x="${i === 0 ? msgX : PAD}" y="${y + 1}" fill="${text}" font-family="${FONT_FAMILY}" font-size="13">${escapeXml(line11)}</text>`
68752
+ );
68753
+ if (i < lines.length - 1) y += 18;
68754
+ });
68755
+ y += 24;
68756
+ const raw = hasLine ? srcLines[e.line - 1] : void 0;
68757
+ if (raw !== void 0 && raw.trim().length > 0) {
68758
+ const gutter = `${e.line}`;
68759
+ const gutterW = gutter.length * CODE_FS * MONO_CH + 16;
68760
+ const codeX = PAD + gutterW;
68761
+ const codeMaxW = CONTENT_W - gutterW - 12;
68762
+ const codeText = truncateText(
68763
+ raw.replace(/\t/g, " "),
68764
+ CODE_FS,
68765
+ codeMaxW
68766
+ );
68767
+ const hasCaret = e.column !== void 0 && e.column > 0;
68768
+ const blockH = hasCaret ? 40 : 26;
68769
+ parts.push(
68770
+ `<rect x="${PAD}" y="${y - 14}" width="${CONTENT_W}" height="${blockH}" rx="5" fill="${codeBg}" stroke="${border}" stroke-width="1"/>`,
68771
+ `<line x1="${codeX - 8}" y1="${y - 14}" x2="${codeX - 8}" y2="${y - 14 + blockH}" stroke="${border}" stroke-width="1"/>`,
68772
+ `<text x="${PAD + 8}" y="${y + 3}" fill="${muted}" font-family="${MONO_FAMILY}" font-size="${CODE_FS}">${escapeXml(gutter)}</text>`,
68773
+ `<text x="${codeX}" y="${y + 3}" fill="${text}" font-family="${MONO_FAMILY}" font-size="${CODE_FS}" xml:space="preserve">${escapeXml(codeText)}</text>`
68774
+ );
68775
+ if (hasCaret) {
68776
+ const caretX = codeX + (e.column - 1) * CODE_FS * MONO_CH;
68777
+ parts.push(
68778
+ `<text x="${caretX}" y="${y + 17}" fill="${danger}" font-family="${MONO_FAMILY}" font-size="${CODE_FS}" font-weight="700">^</text>`
68779
+ );
68780
+ y += 14;
68781
+ }
68782
+ y += 24;
68783
+ }
68784
+ y += 14;
68785
+ }
68786
+ if (overflow > 0) {
68787
+ parts.push(
68788
+ `<text x="${PAD}" y="${y}" fill="${muted}" font-family="${FONT_FAMILY}" font-size="12.5" font-style="italic">+ ${overflow} more error${overflow === 1 ? "" : "s"}</text>`
68789
+ );
68790
+ y += 22;
68791
+ }
68792
+ const height = Math.round(y + PAD - 14);
68793
+ const pageBg = isTransparent ? "" : `<rect width="100%" height="100%" fill="${c.bg}"/>`;
68794
+ return [
68795
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${W2}" height="${height}" viewBox="0 0 ${W2} ${height}" font-family="${FONT_FAMILY}">`,
68796
+ pageBg,
68797
+ `<rect x="0.5" y="0.5" width="${W2 - 1}" height="${height - 1}" rx="10" fill="${card}" stroke="${border}" stroke-width="1"/>`,
68798
+ parts.join(""),
68799
+ `</svg>`
68800
+ ].join("");
68801
+ }
68802
+ function wrapMessage(message, firstWidth, fullWidth) {
68803
+ const words = message.split(/\s+/).filter(Boolean);
68804
+ if (words.length === 0) return [""];
68805
+ const lines = [];
68806
+ let cur = "";
68807
+ for (const word of words) {
68808
+ const limit = lines.length === 0 ? firstWidth : fullWidth;
68809
+ const test = cur ? `${cur} ${word}` : word;
68810
+ if (measureText(test, 13) <= limit || !cur) {
68811
+ cur = test;
68812
+ } else {
68813
+ lines.push(cur);
68814
+ cur = word;
68815
+ }
68816
+ }
68817
+ if (cur) lines.push(cur);
68818
+ return lines;
68819
+ }
68820
+ function frownMark(cx, cy, color) {
68821
+ const r = 12;
68822
+ return [
68823
+ `<circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="${color}" stroke-width="2"/>`,
68824
+ `<circle cx="${cx - 4.5}" cy="${cy - 3}" r="1.6" fill="${color}"/>`,
68825
+ `<circle cx="${cx + 4.5}" cy="${cy - 3}" r="1.6" fill="${color}"/>`,
68826
+ `<path d="M ${cx - 5} ${cy + 6} Q ${cx} ${cy + 1} ${cx + 5} ${cy + 6}" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round"/>`
68827
+ ].join("");
68828
+ }
68829
+ function escapeXml(s) {
68830
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
68831
+ }
68832
+
68833
+ // src/index.ts
68488
68834
  init_palettes();
68489
68835
 
68490
68836
  // src/themes.ts
@@ -68824,25 +69170,10 @@ async function render2(text, options) {
68824
69170
  return result;
68825
69171
  }
68826
69172
  return {
68827
- svg: result.svg || renderErrorSvg(errors, palette, options?.theme),
69173
+ svg: renderErrorCard(errors, text, palette, options?.theme),
68828
69174
  diagnostics: result.diagnostics
68829
69175
  };
68830
69176
  }
68831
- function renderErrorSvg(errors, palette, theme) {
68832
- const colors = palette[theme === "dark" ? "dark" : "light"];
68833
- const bg = theme === "transparent" ? "transparent" : colors.bg;
68834
- const fg = colors.destructive ?? colors.text;
68835
- const muted = colors.textMuted ?? colors.text;
68836
- const lines = errors.slice(0, 5).map((e) => formatDgmoError(e));
68837
- const height = 60 + lines.length * 20;
68838
- const body = lines.map(
68839
- (line11, i) => `<text x="20" y="${60 + i * 20}" fill="${fg}" font-family="ui-monospace, monospace" font-size="12">${escapeXml(line11)}</text>`
68840
- ).join("");
68841
- return `<svg xmlns="http://www.w3.org/2000/svg" width="640" height="${height}" viewBox="0 0 640 ${height}"><rect width="100%" height="100%" fill="${bg}"/><text x="20" y="34" fill="${muted}" font-family="system-ui, sans-serif" font-size="13" font-weight="600">DGMO parse error</text>${body}</svg>`;
68842
- }
68843
- function escapeXml(s) {
68844
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
68845
- }
68846
69177
  function encodeDiagramUrl2(text, options) {
68847
69178
  const internalTheme = options?.theme === "light" || options?.theme === "dark" ? options.theme : void 0;
68848
69179
  const result = encodeDiagramUrl(text, {