@diagrammo/dgmo 0.39.0 → 0.41.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",
@@ -15096,6 +15099,8 @@ function parseEventLine(content, palette) {
15096
15099
  let currentTagGroup = null;
15097
15100
  let currentEvent = null;
15098
15101
  let currentEra = null;
15102
+ let eraIndent = -1;
15103
+ let currentEventIndent = 0;
15099
15104
  const aliasMap = /* @__PURE__ */ new Map();
15100
15105
  for (let i = 0; i < lines.length; i++) {
15101
15106
  const line11 = lines[i];
@@ -15170,52 +15175,15 @@ function parseEventLine(content, palette) {
15170
15175
  });
15171
15176
  continue;
15172
15177
  }
15173
- if (indent > 0) {
15174
- if (!currentEvent) {
15175
- pushWarning(
15176
- lineNumber,
15177
- `Indented description "${trimmed}" has no event above it \u2014 add an event line first. (\xA728)`
15178
- );
15179
- continue;
15180
- }
15178
+ if (currentEvent && indent > currentEventIndent) {
15181
15179
  const descLine = trimmed.startsWith("- ") ? `\u2022 ${trimmed.substring(2)}` : trimmed;
15182
15180
  currentEvent.description.push(descLine);
15183
15181
  continue;
15184
15182
  }
15185
- currentTagGroup = null;
15186
- if (trimmed.toLowerCase() === "no-scale") {
15187
- options.scale = false;
15188
- continue;
15189
- }
15190
- const sideMatch = trimmed.match(SIDE_RE);
15191
- if (sideMatch) {
15192
- options.side = sideMatch[1].toLowerCase();
15193
- continue;
15194
- }
15195
- if (trimmed.toLowerCase() === "no-box") {
15196
- options.noBox = true;
15197
- continue;
15198
- }
15199
- if (trimmed.toLowerCase() === "no-legend") {
15200
- options.noLegend = true;
15201
- continue;
15202
- }
15203
- const dirMatch = trimmed.match(DIRECTION_RE);
15204
- if (dirMatch) {
15205
- const dir = dirMatch[1].toUpperCase();
15206
- if (dir !== "LR") {
15207
- pushWarning(
15208
- lineNumber,
15209
- `event-line is horizontal-only in v1; \`direction ${dir}\` (vertical orientation) is a fast-follow.`,
15210
- EVENT_LINE_DIAGNOSTIC_CODES.UNSUPPORTED
15211
- );
15212
- }
15213
- continue;
15214
- }
15215
- if (tryParseSharedOption(trimmed, sharedOptions)) continue;
15216
15183
  const eraMatch = trimmed.match(ERA_RE);
15217
15184
  if (eraMatch) {
15218
15185
  contentStarted = true;
15186
+ currentTagGroup = null;
15219
15187
  currentEvent = null;
15220
15188
  const name = eraMatch[1].trim();
15221
15189
  let rest = (eraMatch[2] ?? "").trim();
@@ -15239,16 +15207,52 @@ function parseEventLine(content, palette) {
15239
15207
  const era = { name, color, collapsed, lineNumber };
15240
15208
  result.eras.push(era);
15241
15209
  currentEra = name;
15210
+ eraIndent = indent;
15242
15211
  continue;
15243
15212
  }
15244
- if (SECTION_SEAM_RE.test(trimmed)) {
15245
- pushWarning(
15246
- lineNumber,
15247
- "Group events with `[Name]` era brackets (\xA728.6a), not `section`.",
15248
- EVENT_LINE_DIAGNOSTIC_CODES.UNSUPPORTED
15249
- );
15250
- continue;
15213
+ if (indent === 0) {
15214
+ currentTagGroup = null;
15215
+ if (trimmed.toLowerCase() === "no-scale") {
15216
+ options.scale = false;
15217
+ continue;
15218
+ }
15219
+ const sideMatch = trimmed.match(SIDE_RE);
15220
+ if (sideMatch) {
15221
+ options.side = sideMatch[1].toLowerCase();
15222
+ continue;
15223
+ }
15224
+ if (trimmed.toLowerCase() === "no-box") {
15225
+ options.noBox = true;
15226
+ continue;
15227
+ }
15228
+ if (trimmed.toLowerCase() === "no-legend") {
15229
+ options.noLegend = true;
15230
+ continue;
15231
+ }
15232
+ const dirMatch = trimmed.match(DIRECTION_RE);
15233
+ if (dirMatch) {
15234
+ const dir = dirMatch[1].toUpperCase();
15235
+ if (dir !== "LR") {
15236
+ pushWarning(
15237
+ lineNumber,
15238
+ `event-line is horizontal-only in v1; \`direction ${dir}\` (vertical orientation) is a fast-follow.`,
15239
+ EVENT_LINE_DIAGNOSTIC_CODES.UNSUPPORTED
15240
+ );
15241
+ }
15242
+ continue;
15243
+ }
15244
+ if (tryParseSharedOption(trimmed, sharedOptions)) continue;
15245
+ if (SECTION_SEAM_RE.test(trimmed)) {
15246
+ pushWarning(
15247
+ lineNumber,
15248
+ "Group events with `[Name]` era brackets (\xA728.6a), not `section`.",
15249
+ EVENT_LINE_DIAGNOSTIC_CODES.UNSUPPORTED
15250
+ );
15251
+ continue;
15252
+ }
15251
15253
  }
15254
+ const inEra = currentEra !== null && indent > eraIndent;
15255
+ if (!inEra) currentEra = null;
15252
15256
  contentStarted = true;
15253
15257
  const event = parseEventHeader(
15254
15258
  trimmed,
@@ -15260,6 +15264,7 @@ function parseEventLine(content, palette) {
15260
15264
  );
15261
15265
  result.events.push(event);
15262
15266
  currentEvent = event;
15267
+ currentEventIndent = indent;
15263
15268
  }
15264
15269
  options.noTitle = sharedOptions["no-title"] === "on";
15265
15270
  finalizeAutoTagColors(result.tagGroups, palette);
@@ -15274,8 +15279,61 @@ function parseEventLine(content, palette) {
15274
15279
  EVENT_LINE_DIAGNOSTIC_CODES.NO_EVENTS
15275
15280
  );
15276
15281
  }
15282
+ if (options.scale) {
15283
+ validateEraDateOrder(result.events, result.eras, pushWarning);
15284
+ }
15277
15285
  return result;
15278
15286
  }
15287
+ function validateEraDateOrder(events, eras, pushWarning) {
15288
+ if (eras.length < 2) return;
15289
+ const order = new Map(eras.map((e, i) => [e.name, i]));
15290
+ const dated = events.filter(
15291
+ (e) => e.dateValue !== null && e.era !== null && order.has(e.era)
15292
+ );
15293
+ if (dated.length === 0) return;
15294
+ const perEra = /* @__PURE__ */ new Map();
15295
+ for (const ev of dated) {
15296
+ const idx = order.get(ev.era);
15297
+ const cur = perEra.get(idx);
15298
+ if (!cur) {
15299
+ perEra.set(idx, { min: ev, max: ev });
15300
+ } else {
15301
+ if (ev.dateValue < cur.min.dateValue) cur.min = ev;
15302
+ if (ev.dateValue > cur.max.dateValue) cur.max = ev;
15303
+ }
15304
+ }
15305
+ const n = eras.length;
15306
+ const prefixMax = new Array(n).fill(null);
15307
+ const suffixMin = new Array(n).fill(null);
15308
+ for (let k = 1; k < n; k++) {
15309
+ const prev = perEra.get(k - 1)?.max ?? null;
15310
+ const carried = prefixMax[k - 1] ?? null;
15311
+ prefixMax[k] = prev && (!carried || prev.dateValue > carried.dateValue) ? prev : carried;
15312
+ }
15313
+ for (let k = n - 2; k >= 0; k--) {
15314
+ const next = perEra.get(k + 1)?.min ?? null;
15315
+ const carried = suffixMin[k + 1] ?? null;
15316
+ suffixMin[k] = next && (!carried || next.dateValue < carried.dateValue) ? next : carried;
15317
+ }
15318
+ for (const ev of dated) {
15319
+ const k = order.get(ev.era);
15320
+ const ahead = suffixMin[k];
15321
+ const behind = prefixMax[k];
15322
+ if (ahead && ev.dateValue > ahead.dateValue) {
15323
+ pushWarning(
15324
+ ev.lineNumber,
15325
+ `"${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.`,
15326
+ EVENT_LINE_DIAGNOSTIC_CODES.ERA_DATE_ORDER
15327
+ );
15328
+ } else if (behind && ev.dateValue < behind.dateValue) {
15329
+ pushWarning(
15330
+ ev.lineNumber,
15331
+ `"${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.`,
15332
+ EVENT_LINE_DIAGNOSTIC_CODES.ERA_DATE_ORDER
15333
+ );
15334
+ }
15335
+ }
15336
+ }
15279
15337
  function parseEventHeader(trimmed, lineNumber, era, aliasMap, diagnostics, pushWarning) {
15280
15338
  let date = null;
15281
15339
  let dateValue = null;
@@ -15342,7 +15400,8 @@ var init_parser13 = __esm({
15342
15400
  EVENT_LINE_DIAGNOSTIC_CODES = {
15343
15401
  NO_EVENTS: "E_EVENT_LINE_NO_EVENTS",
15344
15402
  BAD_DATE: "E_EVENT_LINE_BAD_DATE",
15345
- UNSUPPORTED: "E_EVENT_LINE_UNSUPPORTED"
15403
+ UNSUPPORTED: "E_EVENT_LINE_UNSUPPORTED",
15404
+ ERA_DATE_ORDER: "E_EVENT_LINE_ERA_DATE_ORDER"
15346
15405
  };
15347
15406
  NON_ISO_DATE_RE = /^\d{1,4}[/.]\d/;
15348
15407
  SIDE_RE = /^side\s+(above|below|alternate)\b/i;
@@ -26278,9 +26337,6 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26278
26337
  const hasExpandedEra = parsed.events.some(
26279
26338
  (e) => e.era && eraByName.has(e.era) && !collapsedSet.has(e.era)
26280
26339
  );
26281
- const anyCollapsed = parsed.events.some(
26282
- (e) => e.era && collapsedSet.has(e.era)
26283
- );
26284
26340
  const sideOpt = parsed.options.side;
26285
26341
  const alternate = sideOpt === "alternate";
26286
26342
  const eraSide = alternate ? "below" : sideOpt === "above" ? "below" : "above";
@@ -26347,7 +26403,8 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26347
26403
  x: 0,
26348
26404
  side,
26349
26405
  lane: 0,
26350
- left: 0
26406
+ left: 0,
26407
+ spanHalf: 0
26351
26408
  };
26352
26409
  }
26353
26410
  const era = slot.era;
@@ -26360,19 +26417,19 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26360
26417
  palette.textOnFillLight,
26361
26418
  palette.textOnFillDark
26362
26419
  );
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(
26420
+ let spanLo = null;
26421
+ let spanHi = null;
26422
+ for (const m of slot.members) {
26423
+ if (m.dateValue === null) continue;
26424
+ if (spanLo === null || m.dateValue < spanLo) spanLo = m.dateValue;
26425
+ if (spanHi === null || m.dateValue > spanHi) spanHi = m.dateValue;
26426
+ }
26427
+ const repDateValue = spanLo !== null && spanHi !== null ? (spanLo + spanHi) / 2 : null;
26428
+ const memberStrs = slot.members.map(
26366
26429
  (m) => `\u2022 ${m.date ? `${m.date} ` : ""}${m.label}`
26367
26430
  );
26368
- const bulletColors = shown.map(eventColor);
26431
+ const bulletColors = slot.members.map(eventColor);
26369
26432
  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
26433
  return {
26377
26434
  kind: "era",
26378
26435
  event: null,
@@ -26380,7 +26437,7 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26380
26437
  members: slot.members,
26381
26438
  label: era.name,
26382
26439
  date: null,
26383
- dateValue: null,
26440
+ dateValue: repDateValue,
26384
26441
  lineNumber: era.lineNumber,
26385
26442
  eraName: era.name,
26386
26443
  color: solid,
@@ -26392,87 +26449,189 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26392
26449
  x: 0,
26393
26450
  side,
26394
26451
  lane: 0,
26395
- left: 0
26452
+ left: 0,
26453
+ spanHalf: 0
26396
26454
  };
26397
26455
  });
26398
- const scaled = parsed.options.scale && !anyCollapsed && placed.every((p) => p.dateValue !== null);
26456
+ const scaled = parsed.options.scale && placed.every((p) => p.dateValue !== null);
26399
26457
  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);
26458
+ const MIN_DOT_GAP = 16;
26459
+ const STRETCH_CAP = 8;
26460
+ const COLLAPSE_W = 64;
26461
+ const SEG_GAP = 16;
26462
+ const segments = [];
26463
+ let run = [];
26464
+ const flushRun = () => {
26465
+ if (!run.length) return;
26466
+ const ds = run.map((e) => e.dateValue);
26467
+ segments.push({
26468
+ kind: "run",
26469
+ events: run,
26470
+ lo: Math.min(...ds),
26471
+ hi: Math.max(...ds)
26406
26472
  });
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;
26473
+ run = [];
26474
+ };
26475
+ for (const p of placed) {
26476
+ if (p.kind === "era") {
26477
+ flushRun();
26478
+ segments.push({ kind: "capsule", p });
26479
+ } else {
26480
+ run.push(p);
26481
+ }
26482
+ }
26483
+ flushRun();
26484
+ const totalRunTime = segments.reduce(
26485
+ (s, seg) => s + (seg.kind === "run" ? seg.hi - seg.lo : 0),
26486
+ 0
26487
+ );
26488
+ const place = (stretch) => {
26489
+ if (scaled) {
26490
+ const pxPerUnit = totalRunTime > 0 ? innerW * stretch / totalRunTime : 0;
26491
+ let cursor = H_MARGIN;
26492
+ for (const seg of segments) {
26493
+ if (seg.kind === "capsule") {
26494
+ seg.p.x = cursor + COLLAPSE_W / 2;
26495
+ seg.p.spanHalf = COLLAPSE_W / 2;
26496
+ cursor += COLLAPSE_W + SEG_GAP;
26497
+ } else {
26498
+ for (const e of seg.events) {
26499
+ e.x = cursor + (e.dateValue - seg.lo) * pxPerUnit;
26500
+ e.spanHalf = 0;
26501
+ }
26502
+ cursor += (seg.hi - seg.lo) * pxPerUnit + SEG_GAP;
26503
+ }
26504
+ }
26505
+ let prevX = -Infinity;
26506
+ let prevVal = null;
26507
+ for (const p of [...placed].sort((a, b) => a.x - b.x)) {
26508
+ if (p.kind === "era") {
26509
+ prevX = p.x + p.spanHalf;
26510
+ prevVal = null;
26511
+ continue;
26512
+ }
26513
+ if (p.dateValue === prevVal) {
26514
+ p.x = prevX;
26515
+ continue;
26516
+ }
26517
+ if (p.x < prevX + MIN_DOT_GAP) p.x = prevX + MIN_DOT_GAP;
26518
+ prevX = p.x;
26519
+ prevVal = p.dateValue;
26414
26520
  }
26415
- if (p.x < prevX + MIN_DOT_GAP) p.x = prevX + MIN_DOT_GAP;
26416
- prevX = p.x;
26417
- prevVal = p.dateValue;
26521
+ } else {
26522
+ placed.forEach((p) => {
26523
+ p.spanHalf = 0;
26524
+ });
26525
+ const n = placed.length;
26526
+ const spacing = n > 1 ? Math.max(MIN_SPACING, innerW / (n - 1)) : 0;
26527
+ placed.forEach((p, i) => {
26528
+ p.x = H_MARGIN + i * spacing;
26529
+ });
26418
26530
  }
26531
+ for (const side of ["above", "below"]) {
26532
+ const arr = placed.filter((p) => p.side === side).sort((a, b) => a.x - b.x);
26533
+ const laneRight = [];
26534
+ for (let i = 0; i < arr.length; i++) {
26535
+ const p = arr[i];
26536
+ const next = arr[i + 1];
26537
+ const crowdedRight = !!next && next.x - p.x < CARD_W + FAN_GAP;
26538
+ const preferred = crowdedRight ? p.x - CARD_W + CARD_INSET : p.x - CARD_W / 2;
26539
+ const maxLeft = p.x - CARD_INSET;
26540
+ const minLeft = p.x - CARD_W + CARD_INSET;
26541
+ let lane = laneRight.length;
26542
+ let left = Math.max(preferred, minLeft);
26543
+ for (let l = 0; l < laneRight.length; l++) {
26544
+ const want = Math.max(preferred, laneRight[l] + FAN_GAP, minLeft);
26545
+ if (want <= maxLeft) {
26546
+ lane = l;
26547
+ left = want;
26548
+ break;
26549
+ }
26550
+ }
26551
+ p.lane = lane;
26552
+ p.left = Math.max(6, left);
26553
+ laneRight[lane] = p.left + CARD_W;
26554
+ }
26555
+ }
26556
+ };
26557
+ const derive = () => {
26558
+ const contentW2 = Math.max(
26559
+ width,
26560
+ Math.max(...placed.map((p) => p.x + p.spanHalf)) + H_MARGIN,
26561
+ Math.max(...placed.map((p) => p.left + CARD_W)) + 6
26562
+ );
26563
+ const laneOffset = { above: [], below: [] };
26564
+ for (const side of ["above", "below"]) {
26565
+ const laneH = [];
26566
+ for (const p of placed) {
26567
+ if (p.side !== side) continue;
26568
+ laneH[p.lane] = Math.max(laneH[p.lane] ?? 0, p.cardH);
26569
+ }
26570
+ const off = [];
26571
+ let acc = 0;
26572
+ for (let l = 0; l < laneH.length; l++) {
26573
+ off[l] = acc;
26574
+ acc += (laneH[l] ?? 0) + LANE_GAP;
26575
+ }
26576
+ laneOffset[side] = off;
26577
+ }
26578
+ const laneNear2 = (p) => (p.side === "above" ? LEADER_ABOVE : LEADER_BELOW) + (laneOffset[p.side][p.lane] ?? 0);
26579
+ const ext = (side) => Math.max(
26580
+ 0,
26581
+ ...placed.filter((p) => p.side === side).map((p) => laneNear2(p) + p.cardH)
26582
+ );
26583
+ const dateAbove = placed.some((p) => p.side === "below" && p.date);
26584
+ const dateBelow = placed.some((p) => p.side === "above" && p.date);
26585
+ const contentAbove = Math.max(
26586
+ ext("above"),
26587
+ dateAbove ? DATE_OFFSET + 10 : 0
26588
+ );
26589
+ const contentBelow = Math.max(
26590
+ ext("below"),
26591
+ dateBelow ? DATE_OFFSET + 10 : 0
26592
+ );
26593
+ const aboveExt = contentAbove + (hasExpandedEra && eraSide === "above" ? ERA_BLOCK : 0);
26594
+ const belowExt = contentBelow + (hasExpandedEra && eraSide === "below" ? ERA_BLOCK : 0);
26595
+ const TOP_PAD = 14;
26596
+ const BOT_PAD = 14;
26597
+ const spineY2 = topUsed + TOP_PAD + aboveExt;
26598
+ const eraBaseY2 = eraSide === "above" ? spineY2 - (contentAbove + 14) : spineY2 + (contentBelow + 14);
26599
+ const contentH = spineY2 + belowExt + BOT_PAD;
26600
+ const totalH2 = Math.max(heightHint, contentH);
26601
+ return { contentW: contentW2, contentH, totalH: totalH2, spineY: spineY2, eraBaseY: eraBaseY2, laneNear: laneNear2 };
26602
+ };
26603
+ const envStretch = Number(
26604
+ globalThis.process?.env?.["DGMO_EVT_STRETCH"]
26605
+ );
26606
+ let layout;
26607
+ if (Number.isFinite(envStretch) && envStretch > 0) {
26608
+ place(envStretch);
26609
+ layout = derive();
26419
26610
  } 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;
26611
+ place(1);
26612
+ layout = derive();
26613
+ const panelAspect = width / heightHint;
26614
+ const aspectOf = (l) => l.contentW / l.contentH;
26615
+ if (scaled && layout.contentH > heightHint && aspectOf(layout) < panelAspect) {
26616
+ place(STRETCH_CAP);
26617
+ const capped = derive();
26618
+ if (aspectOf(capped) <= panelAspect) {
26619
+ layout = capped;
26620
+ } else {
26621
+ let loF = 1;
26622
+ let hiF = STRETCH_CAP;
26623
+ for (let it = 0; it < 8; it++) {
26624
+ const mid = (loF + hiF) / 2;
26625
+ place(mid);
26626
+ if (aspectOf(derive()) >= panelAspect) hiF = mid;
26627
+ else loF = mid;
26444
26628
  }
26629
+ place(hiF);
26630
+ layout = derive();
26445
26631
  }
26446
- p.lane = lane;
26447
- p.left = Math.max(6, left);
26448
- laneRight[lane] = p.left + CARD_W;
26449
26632
  }
26450
26633
  }
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);
26634
+ const { contentW, totalH, spineY, eraBaseY, laneNear } = layout;
26476
26635
  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
26636
  svg.append("rect").attr("width", contentW).attr("height", totalH).attr("fill", palette.bg);
26478
26637
  if (showTitle) {
@@ -26500,12 +26659,11 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26500
26659
  activeGroup
26501
26660
  });
26502
26661
  }
26503
- const xs = placed.map((p) => p.x);
26504
- const x0 = Math.min(...xs);
26505
- const x1 = Math.max(...xs);
26662
+ const x0 = Math.min(...placed.map((p) => p.x - p.spanHalf));
26663
+ const x1 = Math.max(...placed.map((p) => p.x + p.spanHalf));
26506
26664
  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
26665
  const geo = placed.map((p) => {
26508
- const near = p.side === "above" ? spineY - LEADER_ABOVE - p.lane * rowGapA : spineY + LEADER_BELOW + p.lane * rowGapB;
26666
+ const near = p.side === "above" ? spineY - laneNear(p) : spineY + laneNear(p);
26509
26667
  const top = p.side === "above" ? near - p.cardH : near;
26510
26668
  return { p, near, top };
26511
26669
  });
@@ -26617,7 +26775,7 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26617
26775
  for (const p of placed) {
26618
26776
  if (p.kind === "era") {
26619
26777
  const col = p.color;
26620
- const half = Math.min(46, 16 + (p.members.length - 1) * 9);
26778
+ const half = Math.max(16, p.spanHalf);
26621
26779
  const spineLeft = x0 - 20;
26622
26780
  const spineRight = x1 + 20;
26623
26781
  const bx0 = Math.max(spineLeft, p.x - half);
@@ -26628,6 +26786,11 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26628
26786
  "d",
26629
26787
  `M${bx0},${spineY} L${bx0},${barY} L${bx1},${barY} L${bx1},${spineY}`
26630
26788
  ).attr("fill", "none").attr("stroke", col).attr("stroke-width", 1.5).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
26789
+ const breakCx = (bx0 + bx1) / 2;
26790
+ 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");
26791
+ for (const d of axisBreakPaths(breakCx, spineY)) {
26792
+ 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);
26793
+ }
26631
26794
  if (onClickItem) {
26632
26795
  const ln = p.lineNumber;
26633
26796
  eg.style("cursor", "pointer").on("click", () => onClickItem(ln));
@@ -26661,13 +26824,23 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26661
26824
  }
26662
26825
  const clampX = (v) => Math.max(4, Math.min(contentW - 4, v));
26663
26826
  const cap = eraSide === "above" ? 1 : -1;
26664
- for (const r of runs) {
26665
- if (r.collapsed) continue;
26827
+ const drawn = runs.filter((r) => !r.collapsed).map((r) => ({
26828
+ run: r,
26829
+ x0: clampX(r.firstX - ERA_BRACKET_PAD),
26830
+ x1: clampX(r.lastX + ERA_BRACKET_PAD)
26831
+ })).sort((a, b) => a.run.firstX - b.run.firstX);
26832
+ for (let i = 1; i < drawn.length; i++) {
26833
+ const prev = drawn[i - 1];
26834
+ const cur = drawn[i];
26835
+ if (prev.x1 <= cur.x0) continue;
26836
+ const mid = (prev.run.lastX + cur.run.firstX) / 2;
26837
+ prev.x1 = Math.max(prev.run.lastX, Math.min(prev.x1, mid - ERA_SEAM_GAP));
26838
+ cur.x0 = Math.min(cur.run.firstX, Math.max(cur.x0, mid + ERA_SEAM_GAP));
26839
+ }
26840
+ for (const { run: r, x0: x02, x1: x12 } of drawn) {
26666
26841
  const neutral = !r.era.color;
26667
26842
  const col = neutral ? palette.text : resolveColor(r.era.color, palette) ?? palette.text;
26668
26843
  const op = neutral ? 0.5 : 0.85;
26669
- const x02 = clampX(r.firstX - 14);
26670
- const x12 = clampX(r.lastX + 14);
26671
26844
  const y = eraBaseY;
26672
26845
  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
26846
  eg.append("path").attr(
@@ -26693,6 +26866,22 @@ function renderEventLineForExport(container, parsed, palette, isDark, exportDims
26693
26866
  tagOverride
26694
26867
  );
26695
26868
  }
26869
+ function axisBreakPaths(cx, cy) {
26870
+ const HUMPS = 2;
26871
+ const STEPS = 14;
26872
+ const wave = (x0) => {
26873
+ let d = "";
26874
+ for (let i = 0; i <= STEPS; i++) {
26875
+ const t = i / STEPS;
26876
+ const y = cy - BREAK_HALF_H + t * 2 * BREAK_HALF_H;
26877
+ const x = x0 + BREAK_AMP * Math.sin(t * Math.PI * HUMPS);
26878
+ d += `${i === 0 ? "M" : "L"}${x.toFixed(2)},${y.toFixed(2)}`;
26879
+ if (i < STEPS) d += " ";
26880
+ }
26881
+ return d;
26882
+ };
26883
+ return [wave(cx - BREAK_GAP / 2), wave(cx + BREAK_GAP / 2)];
26884
+ }
26696
26885
  function wrapDescription(lines, charsPerLine) {
26697
26886
  const out = [];
26698
26887
  for (const line11 of lines) {
@@ -26722,7 +26911,7 @@ function renderBody(cardG, lines, bodyColor, palette, startBaseline = CARD_BODY_
26722
26911
  y += DESC_LINE_H;
26723
26912
  }
26724
26913
  }
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;
26914
+ 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
26915
  var init_renderer = __esm({
26727
26916
  "src/event-line/renderer.ts"() {
26728
26917
  "use strict";
@@ -26757,9 +26946,13 @@ var init_renderer = __esm({
26757
26946
  DATE_OFFSET = 28;
26758
26947
  ERA_BLOCK = 30;
26759
26948
  ERA_BRACKET_CAP = 8;
26949
+ ERA_BRACKET_PAD = 14;
26950
+ ERA_SEAM_GAP = 3;
26951
+ BREAK_HALF_H = 6;
26952
+ BREAK_AMP = 2.2;
26953
+ BREAK_GAP = 5;
26760
26954
  ERA_LEG = 13;
26761
26955
  ERA_LABEL_FONT = 11.5;
26762
- ERA_MEMBER_MAX = 6;
26763
26956
  }
26764
26957
  });
26765
26958
 
@@ -26901,13 +27094,13 @@ function renderVersionControl(container, parsed, palette, isDark, onClickItem, e
26901
27094
  }
26902
27095
  const showTitle = !!parsed.title;
26903
27096
  const titleH = showTitle ? TITLE_AREA2 : 0;
26904
- const W2 = Math.round(contentW);
27097
+ const W3 = Math.round(contentW);
26905
27098
  const H2 = Math.round(contentH + titleH);
26906
27099
  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);
27100
+ 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);
27101
+ svg.append("rect").attr("width", W3).attr("height", H2).attr("fill", palette.bg);
26909
27102
  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);
27103
+ 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
27104
  if (onClickItem && parsed.titleLineNumber && !exportMode) {
26912
27105
  const ln = parsed.titleLineNumber;
26913
27106
  t.style("cursor", "pointer").on("click", () => onClickItem(ln));
@@ -32689,7 +32882,7 @@ function findClearPosition(w, h, points, obstacles) {
32689
32882
  }
32690
32883
  function resolveLabelOverlaps(boxes) {
32691
32884
  const MAX_PASSES = 8;
32692
- const PAD = 4;
32885
+ const PAD2 = 4;
32693
32886
  for (let pass = 0; pass < MAX_PASSES; pass++) {
32694
32887
  let moved = false;
32695
32888
  for (let i = 0; i < boxes.length; i++) {
@@ -32698,8 +32891,8 @@ function resolveLabelOverlaps(boxes) {
32698
32891
  const b = boxes[j];
32699
32892
  const dx = Math.abs(a.cx - b.cx);
32700
32893
  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;
32894
+ const overlapX = (a.w + b.w) / 2 + PAD2 - dx;
32895
+ const overlapY = (a.h + b.h) / 2 + PAD2 - dy;
32703
32896
  if (overlapX > 0 && overlapY > 0) {
32704
32897
  const shift = overlapY / 2 + 1;
32705
32898
  if (a.cy < b.cy) {
@@ -43078,7 +43271,7 @@ function buildPathD(pts, direction) {
43078
43271
  function computePortPts(edges, nodeMap, direction) {
43079
43272
  const srcPts = /* @__PURE__ */ new Map();
43080
43273
  const tgtPts = /* @__PURE__ */ new Map();
43081
- const PAD = 0.1;
43274
+ const PAD2 = 0.1;
43082
43275
  const activeEdges = edges.filter((e) => e.points.length > 0);
43083
43276
  const bySource = /* @__PURE__ */ new Map();
43084
43277
  for (const e of activeEdges) {
@@ -43094,7 +43287,7 @@ function computePortPts(edges, nodeMap, direction) {
43094
43287
  ).sort((a, b) => direction === "LR" ? a.t.y - b.t.y : a.t.x - b.t.x);
43095
43288
  const n = sorted.length;
43096
43289
  for (let i = 0; i < n; i++) {
43097
- const frac = n === 1 ? 0.5 : PAD + (1 - 2 * PAD) * i / (n - 1);
43290
+ const frac = n === 1 ? 0.5 : PAD2 + (1 - 2 * PAD2) * i / (n - 1);
43098
43291
  const { e, t } = sorted[i];
43099
43292
  const isBackward = direction === "LR" ? t.x < source.x : t.y < source.y;
43100
43293
  if (direction === "LR") {
@@ -43124,7 +43317,7 @@ function computePortPts(edges, nodeMap, direction) {
43124
43317
  ).sort((a, b) => direction === "LR" ? a.s.y - b.s.y : a.s.x - b.s.x);
43125
43318
  const n = sorted.length;
43126
43319
  for (let i = 0; i < n; i++) {
43127
- const frac = n === 1 ? 0.5 : PAD + (1 - 2 * PAD) * i / (n - 1);
43320
+ const frac = n === 1 ? 0.5 : PAD2 + (1 - 2 * PAD2) * i / (n - 1);
43128
43321
  const { e, s } = sorted[i];
43129
43322
  const isBackward = direction === "LR" ? target.x < s.x : target.y < s.y;
43130
43323
  if (direction === "LR") {
@@ -52917,6 +53110,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
52917
53110
  y,
52918
53111
  score,
52919
53112
  palette,
53113
+ isDark,
52920
53114
  SCORE_LABEL_R
52921
53115
  );
52922
53116
  if (!exportDims) {
@@ -52948,7 +53142,14 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
52948
53142
  const allSteps = parsed.phases.length > 0 ? parsed.phases.flatMap((p) => p.steps) : parsed.steps;
52949
53143
  for (const pt of layout.curvePoints) {
52950
53144
  const step = allSteps[pt.stepIndex];
52951
- const faceG = renderScoreFace(curveG, pt.x, pt.y, pt.score, palette);
53145
+ const faceG = renderScoreFace(
53146
+ curveG,
53147
+ pt.x,
53148
+ pt.y,
53149
+ pt.score,
53150
+ palette,
53151
+ isDark
53152
+ );
52952
53153
  addEmotionLabel(faceG, pt, palette);
52953
53154
  if (step) {
52954
53155
  faceG.attr("data-line-number", step.lineNumber);
@@ -52974,7 +53175,14 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
52974
53175
  const pt = layout.curvePoints[0];
52975
53176
  const allSteps = parsed.phases.length > 0 ? parsed.phases.flatMap((p) => p.steps) : parsed.steps;
52976
53177
  const step = allSteps[pt.stepIndex];
52977
- const faceG = renderScoreFace(curveG, pt.x, pt.y, pt.score, palette);
53178
+ const faceG = renderScoreFace(
53179
+ curveG,
53180
+ pt.x,
53181
+ pt.y,
53182
+ pt.score,
53183
+ palette,
53184
+ isDark
53185
+ );
52978
53186
  addEmotionLabel(faceG, pt, palette);
52979
53187
  if (step) {
52980
53188
  faceG.attr("data-line-number", step.lineNumber);
@@ -53071,6 +53279,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53071
53279
  faceCy,
53072
53280
  step.score,
53073
53281
  palette,
53282
+ isDark,
53074
53283
  COLLAPSED_FACE_R
53075
53284
  );
53076
53285
  faceG.attr("data-line-number", step.lineNumber);
@@ -53174,7 +53383,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53174
53383
  const fcy = parseFloat(el.attr("data-cy") ?? "0");
53175
53384
  el.attr(
53176
53385
  "transform",
53177
- `translate(${fcx},${fcy}) scale(1.5) translate(${-fcx},${-fcy})`
53386
+ `translate(${fcx},${fcy}) scale(${FACE_HOVER_SCALE}) translate(${-fcx},${-fcy})`
53178
53387
  );
53179
53388
  } else {
53180
53389
  el.attr("transform", null);
@@ -53220,12 +53429,12 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53220
53429
  PADDING2,
53221
53430
  Math.min(fcx - bw / 2, layout.totalWidth - PADDING2 - bw)
53222
53431
  );
53223
- const by = Math.max(PADDING2, fcy - FACE_RADIUS - THOUGHT_GAP - bh);
53432
+ const by = Math.max(PADDING2, fcy - FACE_HOVER_R - THOUGHT_GAP - bh);
53224
53433
  const scoreColor = scoreToColor(score, palette);
53225
53434
  const tintedBg = mix(scoreColor, palette.surface, 20);
53226
53435
  const g = overlayG.append("g").attr("class", "journey-thought-hover");
53227
53436
  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);
53437
+ 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
53438
  const centerX = bx + bw / 2;
53230
53439
  for (let i = 0; i < lines.length; i++) {
53231
53440
  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 +53677,23 @@ function renderPersonaSilhouette(parent, cx, cy, color, palette, scale = 1) {
53468
53677
  `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
53678
  ).attr("fill", fill2).attr("stroke", stroke2).attr("stroke-width", 1.2);
53470
53679
  }
53471
- function renderScoreFace(parent, cx, cy, score, palette, radius) {
53680
+ function renderScoreFace(parent, cx, cy, score, palette, isDark, radius) {
53472
53681
  const r = radius ?? FACE_RADIUS;
53473
53682
  const color = scoreToColor(score, palette);
53474
53683
  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
- );
53684
+ const faceFill = shapeFill(palette, color, isDark);
53685
+ g.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r + 1).attr("fill", palette.bg);
53686
+ g.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r).attr("fill", faceFill).attr("stroke", color).attr("stroke-width", 2);
53687
+ const eyeColor = color;
53481
53688
  const eyeY = cy - r * 0.15;
53482
53689
  const eyeSpacing = r * 0.32;
53483
53690
  const eyeR = r * 0.12;
53484
53691
  g.append("circle").attr("cx", cx - eyeSpacing).attr("cy", eyeY).attr("r", eyeR).attr("fill", eyeColor);
53485
53692
  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;
53693
+ const mouthW = r * 0.46;
53694
+ const curve = (score - 3) / 2 * r * 0.42;
53695
+ const apexY = cy + r * 0.32;
53696
+ const mouthY = apexY - curve / 2;
53489
53697
  g.append("path").attr(
53490
53698
  "d",
53491
53699
  `M ${cx - mouthW} ${mouthY} Q ${cx} ${mouthY + curve} ${cx + mouthW} ${mouthY}`
@@ -53556,7 +53764,7 @@ function renderJourneyMapForExport(content, theme, palette) {
53556
53764
  }
53557
53765
  return svgEl.outerHTML;
53558
53766
  }
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;
53767
+ 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
53768
  var init_renderer17 = __esm({
53561
53769
  "src/journey-map/renderer.ts"() {
53562
53770
  "use strict";
@@ -53592,6 +53800,8 @@ var init_renderer17 = __esm({
53592
53800
  GRID_LINE_OPACITY = 0.15;
53593
53801
  CURVE_STROKE_WIDTH = 2.5;
53594
53802
  FACE_RADIUS = 14;
53803
+ FACE_HOVER_SCALE = 1.5;
53804
+ FACE_HOVER_R = (FACE_RADIUS + 1) * FACE_HOVER_SCALE;
53595
53805
  DIM_HOVER = 0.25;
53596
53806
  TITLE_LINE_HEIGHT = 16;
53597
53807
  EMOTION_LABEL_MAX_WIDTH = 180;
@@ -56630,7 +56840,7 @@ function layoutMap(resolvedIn, data, size, opts) {
56630
56840
  const akRef = resolved.regions.some((r) => r.iso === "US-AK") || resolved.pois.some((p) => inAlaska(p.lon, p.lat));
56631
56841
  const hiRef = resolved.regions.some((r) => r.iso === "US-HI") || resolved.pois.some((p) => inHawaii(p.lon, p.lat));
56632
56842
  if (resolved.projection === "albers-usa" && usLayer && (akRef || hiRef)) {
56633
- const PAD = 8;
56843
+ const PAD2 = 8;
56634
56844
  const GAP = 12;
56635
56845
  const yB = height - FIT_PAD;
56636
56846
  const BW = 8;
@@ -56673,22 +56883,22 @@ function layoutMap(resolvedIn, data, size, opts) {
56673
56883
  const f = usLayer.get(iso);
56674
56884
  if (!f) return boxX;
56675
56885
  const x0 = boxX;
56676
- const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
56886
+ const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD2);
56677
56887
  if (iw < 24) return boxX;
56678
- const xr = x0 + iw + 2 * PAD;
56888
+ const xr = x0 + iw + 2 * PAD2;
56679
56889
  const floor = coastFloor(x0, xr);
56680
56890
  const topGuess = floor > -Infinity ? floor + GAP : yB - height * 0.42;
56681
56891
  proj.fitWidth(iw, f);
56682
56892
  const bb = (0, import_d3_geo2.geoPath)(proj).bounds(f);
56683
56893
  const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
56684
- const needH = sh + 2 * PAD;
56894
+ const needH = sh + 2 * PAD2;
56685
56895
  let topFit = topGuess;
56686
56896
  const bottom = Math.min(topFit + needH, yB);
56687
56897
  if (bottom - topFit < needH) topFit = bottom - needH;
56688
56898
  proj.fitExtent(
56689
56899
  [
56690
- [x0 + PAD, topFit + PAD],
56691
- [xr - PAD, bottom - PAD]
56900
+ [x0 + PAD2, topFit + PAD2],
56901
+ [xr - PAD2, bottom - PAD2]
56692
56902
  ],
56693
56903
  f
56694
56904
  );
@@ -58212,15 +58422,15 @@ function layoutMap(resolvedIn, data, size, opts) {
58212
58422
  }
58213
58423
  }
58214
58424
  if (regionLabelGuards.length > 0) {
58215
- const PAD = 2;
58425
+ const PAD2 = 2;
58216
58426
  const poiRects = labels.filter((l) => l.poiId !== void 0 && l.hidden !== true).map((l) => {
58217
58427
  const w = measureLegendText(l.text, FONT2);
58218
58428
  const x = l.anchor === "start" ? l.x : l.anchor === "end" ? l.x - w : l.x - w / 2;
58219
58429
  return {
58220
- x: x - PAD,
58430
+ x: x - PAD2,
58221
58431
  y: l.y - FONT2,
58222
- w: w + 2 * PAD,
58223
- h: FONT2 * 1.4 + 2 * PAD
58432
+ w: w + 2 * PAD2,
58433
+ h: FONT2 * 1.4 + 2 * PAD2
58224
58434
  };
58225
58435
  });
58226
58436
  for (const g of regionLabelGuards) {
@@ -58845,7 +59055,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
58845
59055
  if (poiLabels.length) {
58846
59056
  const patchBlurId = nid("dgmo-map-label-patch-blur");
58847
59057
  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;
59058
+ const PAD2 = 8;
58849
59059
  const buildPatch = (labels, maskId, decoCluster) => {
58850
59060
  if (!labels.length) return;
58851
59061
  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 +59071,10 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
58861
59071
  const h = th + 6;
58862
59072
  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
59073
  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
59074
+ x0: cx - w / 2 - PAD2,
59075
+ y0: cy - h / 2 - PAD2,
59076
+ x1: cx + w / 2 + PAD2,
59077
+ y1: cy + h / 2 + PAD2
58868
59078
  });
58869
59079
  }
58870
59080
  const gPatch = svg.append("g").attr("class", "dgmo-map-label-patch").attr("mask", `url(#${maskId})`).style("pointer-events", "none");
@@ -60282,13 +60492,13 @@ function renderTreemap(container, parsed, palette, isDark, exportDims, options =
60282
60492
  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
60493
  }
60284
60494
  }
60285
- const PAD = Math.max(4, Math.min(14, Math.floor(Math.min(w, h) / 5)));
60495
+ const PAD2 = Math.max(4, Math.min(14, Math.floor(Math.min(w, h) / 5)));
60286
60496
  const MIN_FS = 7;
60287
- if (!cell.isContainer && w >= 2 * PAD + 8 && h >= 2 * PAD + 8) {
60288
- const maxW = w - 2 * PAD;
60497
+ if (!cell.isContainer && w >= 2 * PAD2 + 8 && h >= 2 * PAD2 + 8) {
60498
+ const maxW = w - 2 * PAD2;
60289
60499
  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);
60500
+ if (horizMinW > maxW && h > w * 1.25 && h - 2 * PAD2 > maxW) {
60501
+ drawVerticalLabel(g, cell.label, w, h, PAD2, ink);
60292
60502
  if (drillable && w > 30 && h > 22) drawFocusIcon(g, w, ink, 9);
60293
60503
  continue;
60294
60504
  }
@@ -60299,8 +60509,8 @@ function renderTreemap(container, parsed, palette, isDark, exportDims, options =
60299
60509
  const nameOpacity = 0.95 - tg * 0.5;
60300
60510
  const vfs = clamp3(Math.round(fs * 0.42), 11, 28);
60301
60511
  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));
60512
+ let y = PAD2 + Math.round(fs * 0.78);
60513
+ 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
60514
  const valParts = [
60305
60515
  opts.noValues ? "" : compactNumber2(cell.value),
60306
60516
  opts.noPercent ? "" : formatPct(cell.pctOfRoot)
@@ -60308,12 +60518,12 @@ function renderTreemap(container, parsed, palette, isDark, exportDims, options =
60308
60518
  const combined = valParts.join(" \xB7 ");
60309
60519
  const nameBaseY = y;
60310
60520
  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);
60521
+ 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
60522
  };
60313
60523
  if (combined) {
60314
60524
  const fitsW = measureText(combined, vfs) <= maxW;
60315
60525
  const valY = y + lineDrop(fs, vfs);
60316
- const roomBelow = valY + descent(vfs) <= h - PAD;
60526
+ const roomBelow = valY + descent(vfs) <= h - PAD2;
60317
60527
  if (fitsW && roomBelow) {
60318
60528
  addLineAt(combined, valY, vfs, valOpacity);
60319
60529
  } else if (fitsW) {
@@ -60321,24 +60531,24 @@ function renderTreemap(container, parsed, palette, isDark, exportDims, options =
60321
60531
  const rendered = clipLabel(cell.label, maxW, fs);
60322
60532
  const nW = measureText(rendered, fs) * 1.12;
60323
60533
  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);
60534
+ if (PAD2 + nW + gap + measureText(combined, ivfs) <= w - PAD2) {
60535
+ 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
60536
  }
60327
60537
  } else if (valParts.length === 2) {
60328
60538
  const d1 = lineDrop(fs, vfs);
60329
- if (y + d1 + descent(vfs) <= h - PAD) {
60539
+ if (y + d1 + descent(vfs) <= h - PAD2) {
60330
60540
  y += d1;
60331
60541
  addLineAt(valParts[0], y, vfs, valOpacity);
60332
60542
  }
60333
60543
  const pfs = Math.max(10, Math.round(vfs * 0.9));
60334
60544
  const d2 = lineDrop(vfs, pfs);
60335
- if (y + d2 + descent(pfs) <= h - PAD) {
60545
+ if (y + d2 + descent(pfs) <= h - PAD2) {
60336
60546
  y += d2;
60337
60547
  addLineAt(valParts[1], y, pfs, valOpacity);
60338
60548
  }
60339
60549
  } else {
60340
60550
  const onlyY = y + lineDrop(fs, vfs);
60341
- if (onlyY + descent(vfs) <= h - PAD) {
60551
+ if (onlyY + descent(vfs) <= h - PAD2) {
60342
60552
  addLineAt(combined, onlyY, vfs, valOpacity);
60343
60553
  }
60344
60554
  }
@@ -68485,6 +68695,144 @@ function decodeDiagramUrl(hash) {
68485
68695
  init_dgmo_router();
68486
68696
  init_palettes();
68487
68697
  init_diagnostics();
68698
+
68699
+ // src/error-card.ts
68700
+ init_text_measure();
68701
+ init_fonts();
68702
+ var MONO_FAMILY = "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace";
68703
+ var W2 = 660;
68704
+ var PAD = 24;
68705
+ var CONTENT_W = W2 - PAD * 2;
68706
+ var MAX_ERRORS = 4;
68707
+ var MONO_CH = 0.6;
68708
+ var CODE_FS = 12.5;
68709
+ function renderErrorCard(errors, source, palette, theme) {
68710
+ const c = palette[theme === "dark" ? "dark" : "light"];
68711
+ const isTransparent = theme === "transparent";
68712
+ const card = c.surface;
68713
+ const codeBg = c.bg;
68714
+ const border = c.border;
68715
+ const danger = c.destructive ?? c.colors.red;
68716
+ const text = c.text;
68717
+ const muted = c.textMuted;
68718
+ const srcLines = source.split("\n");
68719
+ const shown = errors.slice(0, MAX_ERRORS);
68720
+ const overflow = errors.length - shown.length;
68721
+ const parts = [];
68722
+ let y = PAD + 8;
68723
+ parts.push(frownMark(PAD + 14, y + 12, danger));
68724
+ parts.push(
68725
+ `<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>`,
68726
+ `<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>`
68727
+ );
68728
+ y += 24;
68729
+ const errLabel = errors.length === 1 ? "1 error" : `${errors.length} errors`;
68730
+ parts.push(
68731
+ `<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>`
68732
+ );
68733
+ y += 22;
68734
+ parts.push(
68735
+ `<line x1="${PAD}" y1="${y}" x2="${W2 - PAD}" y2="${y}" stroke="${border}" stroke-width="1"/>`
68736
+ );
68737
+ y += 24;
68738
+ for (const e of shown) {
68739
+ const hasLine = e.line > 0;
68740
+ let msgX = PAD;
68741
+ if (hasLine) {
68742
+ const label = e.column ? `Line ${e.line}:${e.column}` : `Line ${e.line}`;
68743
+ const badgeW = 14 + label.length * 7;
68744
+ parts.push(
68745
+ `<rect x="${PAD}" y="${y - 13}" width="${badgeW}" height="19" rx="4" fill="${danger}"/>`,
68746
+ `<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>`
68747
+ );
68748
+ msgX = PAD + badgeW + 12;
68749
+ }
68750
+ const lines = wrapMessage(e.message, CONTENT_W - (msgX - PAD), CONTENT_W);
68751
+ lines.forEach((line11, i) => {
68752
+ parts.push(
68753
+ `<text x="${i === 0 ? msgX : PAD}" y="${y + 1}" fill="${text}" font-family="${FONT_FAMILY}" font-size="13">${escapeXml(line11)}</text>`
68754
+ );
68755
+ if (i < lines.length - 1) y += 18;
68756
+ });
68757
+ y += 24;
68758
+ const raw = hasLine ? srcLines[e.line - 1] : void 0;
68759
+ if (raw !== void 0 && raw.trim().length > 0) {
68760
+ const gutter = `${e.line}`;
68761
+ const gutterW = gutter.length * CODE_FS * MONO_CH + 16;
68762
+ const codeX = PAD + gutterW;
68763
+ const codeMaxW = CONTENT_W - gutterW - 12;
68764
+ const codeText = truncateText(
68765
+ raw.replace(/\t/g, " "),
68766
+ CODE_FS,
68767
+ codeMaxW
68768
+ );
68769
+ const hasCaret = e.column !== void 0 && e.column > 0;
68770
+ const blockH = hasCaret ? 40 : 26;
68771
+ parts.push(
68772
+ `<rect x="${PAD}" y="${y - 14}" width="${CONTENT_W}" height="${blockH}" rx="5" fill="${codeBg}" stroke="${border}" stroke-width="1"/>`,
68773
+ `<line x1="${codeX - 8}" y1="${y - 14}" x2="${codeX - 8}" y2="${y - 14 + blockH}" stroke="${border}" stroke-width="1"/>`,
68774
+ `<text x="${PAD + 8}" y="${y + 3}" fill="${muted}" font-family="${MONO_FAMILY}" font-size="${CODE_FS}">${escapeXml(gutter)}</text>`,
68775
+ `<text x="${codeX}" y="${y + 3}" fill="${text}" font-family="${MONO_FAMILY}" font-size="${CODE_FS}" xml:space="preserve">${escapeXml(codeText)}</text>`
68776
+ );
68777
+ if (hasCaret) {
68778
+ const caretX = codeX + (e.column - 1) * CODE_FS * MONO_CH;
68779
+ parts.push(
68780
+ `<text x="${caretX}" y="${y + 17}" fill="${danger}" font-family="${MONO_FAMILY}" font-size="${CODE_FS}" font-weight="700">^</text>`
68781
+ );
68782
+ y += 14;
68783
+ }
68784
+ y += 24;
68785
+ }
68786
+ y += 14;
68787
+ }
68788
+ if (overflow > 0) {
68789
+ parts.push(
68790
+ `<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>`
68791
+ );
68792
+ y += 22;
68793
+ }
68794
+ const height = Math.round(y + PAD - 14);
68795
+ const pageBg = isTransparent ? "" : `<rect width="100%" height="100%" fill="${c.bg}"/>`;
68796
+ return [
68797
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${W2}" height="${height}" viewBox="0 0 ${W2} ${height}" font-family="${FONT_FAMILY}">`,
68798
+ pageBg,
68799
+ `<rect x="0.5" y="0.5" width="${W2 - 1}" height="${height - 1}" rx="10" fill="${card}" stroke="${border}" stroke-width="1"/>`,
68800
+ parts.join(""),
68801
+ `</svg>`
68802
+ ].join("");
68803
+ }
68804
+ function wrapMessage(message, firstWidth, fullWidth) {
68805
+ const words = message.split(/\s+/).filter(Boolean);
68806
+ if (words.length === 0) return [""];
68807
+ const lines = [];
68808
+ let cur = "";
68809
+ for (const word of words) {
68810
+ const limit = lines.length === 0 ? firstWidth : fullWidth;
68811
+ const test = cur ? `${cur} ${word}` : word;
68812
+ if (measureText(test, 13) <= limit || !cur) {
68813
+ cur = test;
68814
+ } else {
68815
+ lines.push(cur);
68816
+ cur = word;
68817
+ }
68818
+ }
68819
+ if (cur) lines.push(cur);
68820
+ return lines;
68821
+ }
68822
+ function frownMark(cx, cy, color) {
68823
+ const r = 12;
68824
+ return [
68825
+ `<circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="${color}" stroke-width="2"/>`,
68826
+ `<circle cx="${cx - 4.5}" cy="${cy - 3}" r="1.6" fill="${color}"/>`,
68827
+ `<circle cx="${cx + 4.5}" cy="${cy - 3}" r="1.6" fill="${color}"/>`,
68828
+ `<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"/>`
68829
+ ].join("");
68830
+ }
68831
+ function escapeXml(s) {
68832
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
68833
+ }
68834
+
68835
+ // src/index.ts
68488
68836
  init_palettes();
68489
68837
 
68490
68838
  // src/themes.ts
@@ -68824,25 +69172,10 @@ async function render2(text, options) {
68824
69172
  return result;
68825
69173
  }
68826
69174
  return {
68827
- svg: result.svg || renderErrorSvg(errors, palette, options?.theme),
69175
+ svg: renderErrorCard(errors, text, palette, options?.theme),
68828
69176
  diagnostics: result.diagnostics
68829
69177
  };
68830
69178
  }
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
69179
  function encodeDiagramUrl2(text, options) {
68847
69180
  const internalTheme = options?.theme === "light" || options?.theme === "dark" ? options.theme : void 0;
68848
69181
  const result = encodeDiagramUrl(text, {