@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.js CHANGED
@@ -103,7 +103,7 @@ function vennAliasKeywordRemovedMessage(args) {
103
103
  return `Venn 'alias' keyword was removed. Use 'as' instead \u2014 '${args.name} as ${args.alias}'.`;
104
104
  }
105
105
  function pipeOperatorRemovedMessage() {
106
- return `'|' removed \u2014 'Node | c: red' \u2192 'Node c: red'`;
106
+ return `'|' is no longer metadata syntax \u2014 write metadata inline as 'key: value' (e.g. "Name color: red"), or remove the '|'.`;
107
107
  }
108
108
  function ganttBarePercentRemovedMessage(percent) {
109
109
  return `Bare-percent progress shorthand '| ${percent}' was removed. Use 'progress: ${percent.replace(/%$/, "")}' (integer 0\u2013100, no '%').`;
@@ -13629,7 +13629,8 @@ function buildScatterOption(parsed, palette, isDark, textColor, axisLineColor, g
13629
13629
  solid: parsed.solidFill === true
13630
13630
  }),
13631
13631
  borderColor: catColor,
13632
- borderWidth: CHART_BORDER_WIDTH
13632
+ borderWidth: CHART_BORDER_WIDTH,
13633
+ opacity: SCATTER_FILL_OPACITY
13633
13634
  },
13634
13635
  label: labelConfig,
13635
13636
  emphasis: emphasisConfig,
@@ -13656,6 +13657,7 @@ function buildScatterOption(parsed, palette, isDark, textColor, axisLineColor, g
13656
13657
  {
13657
13658
  type: "scatter",
13658
13659
  data,
13660
+ itemStyle: { opacity: SCATTER_FILL_OPACITY },
13659
13661
  label: labelConfig,
13660
13662
  emphasis: emphasisConfig,
13661
13663
  blur: blurConfig
@@ -14961,7 +14963,7 @@ async function renderExtendedChartForExport(content, theme, palette) {
14961
14963
  chart.dispose();
14962
14964
  }
14963
14965
  }
14964
- var 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;
14966
+ var 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;
14965
14967
  var init_echarts = __esm({
14966
14968
  "src/echarts.ts"() {
14967
14969
  "use strict";
@@ -15015,6 +15017,7 @@ var init_echarts = __esm({
15015
15017
  tooltip: { show: false }
15016
15018
  };
15017
15019
  CHART_BORDER_WIDTH = 2;
15020
+ SCATTER_FILL_OPACITY = 0.65;
15018
15021
  VALID_EXTENDED_TYPES = /* @__PURE__ */ new Set([
15019
15022
  "sankey",
15020
15023
  "chord",
@@ -15112,6 +15115,8 @@ function parseEventLine(content, palette) {
15112
15115
  let currentTagGroup = null;
15113
15116
  let currentEvent = null;
15114
15117
  let currentEra = null;
15118
+ let eraIndent = -1;
15119
+ let currentEventIndent = 0;
15115
15120
  const aliasMap = /* @__PURE__ */ new Map();
15116
15121
  for (let i = 0; i < lines.length; i++) {
15117
15122
  const line11 = lines[i];
@@ -15186,52 +15191,15 @@ function parseEventLine(content, palette) {
15186
15191
  });
15187
15192
  continue;
15188
15193
  }
15189
- if (indent > 0) {
15190
- if (!currentEvent) {
15191
- pushWarning(
15192
- lineNumber,
15193
- `Indented description "${trimmed}" has no event above it \u2014 add an event line first. (\xA728)`
15194
- );
15195
- continue;
15196
- }
15194
+ if (currentEvent && indent > currentEventIndent) {
15197
15195
  const descLine = trimmed.startsWith("- ") ? `\u2022 ${trimmed.substring(2)}` : trimmed;
15198
15196
  currentEvent.description.push(descLine);
15199
15197
  continue;
15200
15198
  }
15201
- currentTagGroup = null;
15202
- if (trimmed.toLowerCase() === "no-scale") {
15203
- options.scale = false;
15204
- continue;
15205
- }
15206
- const sideMatch = trimmed.match(SIDE_RE);
15207
- if (sideMatch) {
15208
- options.side = sideMatch[1].toLowerCase();
15209
- continue;
15210
- }
15211
- if (trimmed.toLowerCase() === "no-box") {
15212
- options.noBox = true;
15213
- continue;
15214
- }
15215
- if (trimmed.toLowerCase() === "no-legend") {
15216
- options.noLegend = true;
15217
- continue;
15218
- }
15219
- const dirMatch = trimmed.match(DIRECTION_RE);
15220
- if (dirMatch) {
15221
- const dir = dirMatch[1].toUpperCase();
15222
- if (dir !== "LR") {
15223
- pushWarning(
15224
- lineNumber,
15225
- `event-line is horizontal-only in v1; \`direction ${dir}\` (vertical orientation) is a fast-follow.`,
15226
- EVENT_LINE_DIAGNOSTIC_CODES.UNSUPPORTED
15227
- );
15228
- }
15229
- continue;
15230
- }
15231
- if (tryParseSharedOption(trimmed, sharedOptions)) continue;
15232
15199
  const eraMatch = trimmed.match(ERA_RE);
15233
15200
  if (eraMatch) {
15234
15201
  contentStarted = true;
15202
+ currentTagGroup = null;
15235
15203
  currentEvent = null;
15236
15204
  const name = eraMatch[1].trim();
15237
15205
  let rest = (eraMatch[2] ?? "").trim();
@@ -15255,16 +15223,52 @@ function parseEventLine(content, palette) {
15255
15223
  const era = { name, color, collapsed, lineNumber };
15256
15224
  result.eras.push(era);
15257
15225
  currentEra = name;
15226
+ eraIndent = indent;
15258
15227
  continue;
15259
15228
  }
15260
- if (SECTION_SEAM_RE.test(trimmed)) {
15261
- pushWarning(
15262
- lineNumber,
15263
- "Group events with `[Name]` era brackets (\xA728.6a), not `section`.",
15264
- EVENT_LINE_DIAGNOSTIC_CODES.UNSUPPORTED
15265
- );
15266
- continue;
15229
+ if (indent === 0) {
15230
+ currentTagGroup = null;
15231
+ if (trimmed.toLowerCase() === "no-scale") {
15232
+ options.scale = false;
15233
+ continue;
15234
+ }
15235
+ const sideMatch = trimmed.match(SIDE_RE);
15236
+ if (sideMatch) {
15237
+ options.side = sideMatch[1].toLowerCase();
15238
+ continue;
15239
+ }
15240
+ if (trimmed.toLowerCase() === "no-box") {
15241
+ options.noBox = true;
15242
+ continue;
15243
+ }
15244
+ if (trimmed.toLowerCase() === "no-legend") {
15245
+ options.noLegend = true;
15246
+ continue;
15247
+ }
15248
+ const dirMatch = trimmed.match(DIRECTION_RE);
15249
+ if (dirMatch) {
15250
+ const dir = dirMatch[1].toUpperCase();
15251
+ if (dir !== "LR") {
15252
+ pushWarning(
15253
+ lineNumber,
15254
+ `event-line is horizontal-only in v1; \`direction ${dir}\` (vertical orientation) is a fast-follow.`,
15255
+ EVENT_LINE_DIAGNOSTIC_CODES.UNSUPPORTED
15256
+ );
15257
+ }
15258
+ continue;
15259
+ }
15260
+ if (tryParseSharedOption(trimmed, sharedOptions)) continue;
15261
+ if (SECTION_SEAM_RE.test(trimmed)) {
15262
+ pushWarning(
15263
+ lineNumber,
15264
+ "Group events with `[Name]` era brackets (\xA728.6a), not `section`.",
15265
+ EVENT_LINE_DIAGNOSTIC_CODES.UNSUPPORTED
15266
+ );
15267
+ continue;
15268
+ }
15267
15269
  }
15270
+ const inEra = currentEra !== null && indent > eraIndent;
15271
+ if (!inEra) currentEra = null;
15268
15272
  contentStarted = true;
15269
15273
  const event = parseEventHeader(
15270
15274
  trimmed,
@@ -15276,6 +15280,7 @@ function parseEventLine(content, palette) {
15276
15280
  );
15277
15281
  result.events.push(event);
15278
15282
  currentEvent = event;
15283
+ currentEventIndent = indent;
15279
15284
  }
15280
15285
  options.noTitle = sharedOptions["no-title"] === "on";
15281
15286
  finalizeAutoTagColors(result.tagGroups, palette);
@@ -15290,8 +15295,61 @@ function parseEventLine(content, palette) {
15290
15295
  EVENT_LINE_DIAGNOSTIC_CODES.NO_EVENTS
15291
15296
  );
15292
15297
  }
15298
+ if (options.scale) {
15299
+ validateEraDateOrder(result.events, result.eras, pushWarning);
15300
+ }
15293
15301
  return result;
15294
15302
  }
15303
+ function validateEraDateOrder(events, eras, pushWarning) {
15304
+ if (eras.length < 2) return;
15305
+ const order = new Map(eras.map((e, i) => [e.name, i]));
15306
+ const dated = events.filter(
15307
+ (e) => e.dateValue !== null && e.era !== null && order.has(e.era)
15308
+ );
15309
+ if (dated.length === 0) return;
15310
+ const perEra = /* @__PURE__ */ new Map();
15311
+ for (const ev of dated) {
15312
+ const idx = order.get(ev.era);
15313
+ const cur = perEra.get(idx);
15314
+ if (!cur) {
15315
+ perEra.set(idx, { min: ev, max: ev });
15316
+ } else {
15317
+ if (ev.dateValue < cur.min.dateValue) cur.min = ev;
15318
+ if (ev.dateValue > cur.max.dateValue) cur.max = ev;
15319
+ }
15320
+ }
15321
+ const n = eras.length;
15322
+ const prefixMax = new Array(n).fill(null);
15323
+ const suffixMin = new Array(n).fill(null);
15324
+ for (let k = 1; k < n; k++) {
15325
+ const prev = perEra.get(k - 1)?.max ?? null;
15326
+ const carried = prefixMax[k - 1] ?? null;
15327
+ prefixMax[k] = prev && (!carried || prev.dateValue > carried.dateValue) ? prev : carried;
15328
+ }
15329
+ for (let k = n - 2; k >= 0; k--) {
15330
+ const next = perEra.get(k + 1)?.min ?? null;
15331
+ const carried = suffixMin[k + 1] ?? null;
15332
+ suffixMin[k] = next && (!carried || next.dateValue < carried.dateValue) ? next : carried;
15333
+ }
15334
+ for (const ev of dated) {
15335
+ const k = order.get(ev.era);
15336
+ const ahead = suffixMin[k];
15337
+ const behind = prefixMax[k];
15338
+ if (ahead && ev.dateValue > ahead.dateValue) {
15339
+ pushWarning(
15340
+ ev.lineNumber,
15341
+ `"${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.`,
15342
+ EVENT_LINE_DIAGNOSTIC_CODES.ERA_DATE_ORDER
15343
+ );
15344
+ } else if (behind && ev.dateValue < behind.dateValue) {
15345
+ pushWarning(
15346
+ ev.lineNumber,
15347
+ `"${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.`,
15348
+ EVENT_LINE_DIAGNOSTIC_CODES.ERA_DATE_ORDER
15349
+ );
15350
+ }
15351
+ }
15352
+ }
15295
15353
  function parseEventHeader(trimmed, lineNumber, era, aliasMap, diagnostics, pushWarning) {
15296
15354
  let date = null;
15297
15355
  let dateValue = null;
@@ -15358,7 +15416,8 @@ var init_parser13 = __esm({
15358
15416
  EVENT_LINE_DIAGNOSTIC_CODES = {
15359
15417
  NO_EVENTS: "E_EVENT_LINE_NO_EVENTS",
15360
15418
  BAD_DATE: "E_EVENT_LINE_BAD_DATE",
15361
- UNSUPPORTED: "E_EVENT_LINE_UNSUPPORTED"
15419
+ UNSUPPORTED: "E_EVENT_LINE_UNSUPPORTED",
15420
+ ERA_DATE_ORDER: "E_EVENT_LINE_ERA_DATE_ORDER"
15362
15421
  };
15363
15422
  NON_ISO_DATE_RE = /^\d{1,4}[/.]\d/;
15364
15423
  SIDE_RE = /^side\s+(above|below|alternate)\b/i;
@@ -26295,9 +26354,6 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26295
26354
  const hasExpandedEra = parsed.events.some(
26296
26355
  (e) => e.era && eraByName.has(e.era) && !collapsedSet.has(e.era)
26297
26356
  );
26298
- const anyCollapsed = parsed.events.some(
26299
- (e) => e.era && collapsedSet.has(e.era)
26300
- );
26301
26357
  const sideOpt = parsed.options.side;
26302
26358
  const alternate = sideOpt === "alternate";
26303
26359
  const eraSide = alternate ? "below" : sideOpt === "above" ? "below" : "above";
@@ -26364,7 +26420,8 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26364
26420
  x: 0,
26365
26421
  side,
26366
26422
  lane: 0,
26367
- left: 0
26423
+ left: 0,
26424
+ spanHalf: 0
26368
26425
  };
26369
26426
  }
26370
26427
  const era = slot.era;
@@ -26377,19 +26434,19 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26377
26434
  palette.textOnFillLight,
26378
26435
  palette.textOnFillDark
26379
26436
  );
26380
- const overflow = slot.members.length > ERA_MEMBER_MAX;
26381
- const shown = overflow ? slot.members.slice(0, ERA_MEMBER_MAX - 1) : slot.members;
26382
- const memberStrs = shown.map(
26437
+ let spanLo = null;
26438
+ let spanHi = null;
26439
+ for (const m of slot.members) {
26440
+ if (m.dateValue === null) continue;
26441
+ if (spanLo === null || m.dateValue < spanLo) spanLo = m.dateValue;
26442
+ if (spanHi === null || m.dateValue > spanHi) spanHi = m.dateValue;
26443
+ }
26444
+ const repDateValue = spanLo !== null && spanHi !== null ? (spanLo + spanHi) / 2 : null;
26445
+ const memberStrs = slot.members.map(
26383
26446
  (m) => `\u2022 ${m.date ? `${m.date} ` : ""}${m.label}`
26384
26447
  );
26385
- const bulletColors = shown.map(eventColor);
26448
+ const bulletColors = slot.members.map(eventColor);
26386
26449
  const lines = wrapDescription(memberStrs, charsPerLine);
26387
- if (overflow) {
26388
- lines.push({
26389
- text: `+${slot.members.length - (ERA_MEMBER_MAX - 1)} more`,
26390
- kind: "plain"
26391
- });
26392
- }
26393
26450
  return {
26394
26451
  kind: "era",
26395
26452
  event: null,
@@ -26397,7 +26454,7 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26397
26454
  members: slot.members,
26398
26455
  label: era.name,
26399
26456
  date: null,
26400
- dateValue: null,
26457
+ dateValue: repDateValue,
26401
26458
  lineNumber: era.lineNumber,
26402
26459
  eraName: era.name,
26403
26460
  color: solid,
@@ -26409,87 +26466,189 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26409
26466
  x: 0,
26410
26467
  side,
26411
26468
  lane: 0,
26412
- left: 0
26469
+ left: 0,
26470
+ spanHalf: 0
26413
26471
  };
26414
26472
  });
26415
- const scaled = parsed.options.scale && !anyCollapsed && placed.every((p) => p.dateValue !== null);
26473
+ const scaled = parsed.options.scale && placed.every((p) => p.dateValue !== null);
26416
26474
  const innerW = width - H_MARGIN * 2;
26417
- if (scaled) {
26418
- const vals = placed.map((p) => p.dateValue);
26419
- const lo = Math.min(...vals);
26420
- const hi = Math.max(...vals);
26421
- placed.forEach((p) => {
26422
- p.x = H_MARGIN + (hi > lo ? (p.dateValue - lo) / (hi - lo) * innerW : innerW / 2);
26475
+ const MIN_DOT_GAP = 16;
26476
+ const STRETCH_CAP = 8;
26477
+ const COLLAPSE_W = 64;
26478
+ const SEG_GAP = 16;
26479
+ const segments = [];
26480
+ let run = [];
26481
+ const flushRun = () => {
26482
+ if (!run.length) return;
26483
+ const ds = run.map((e) => e.dateValue);
26484
+ segments.push({
26485
+ kind: "run",
26486
+ events: run,
26487
+ lo: Math.min(...ds),
26488
+ hi: Math.max(...ds)
26423
26489
  });
26424
- const MIN_DOT_GAP = 16;
26425
- let prevX = -Infinity;
26426
- let prevVal = null;
26427
- for (const p of [...placed].sort((a, b) => a.x - b.x)) {
26428
- if (p.dateValue === prevVal) {
26429
- p.x = prevX;
26430
- continue;
26490
+ run = [];
26491
+ };
26492
+ for (const p of placed) {
26493
+ if (p.kind === "era") {
26494
+ flushRun();
26495
+ segments.push({ kind: "capsule", p });
26496
+ } else {
26497
+ run.push(p);
26498
+ }
26499
+ }
26500
+ flushRun();
26501
+ const totalRunTime = segments.reduce(
26502
+ (s, seg) => s + (seg.kind === "run" ? seg.hi - seg.lo : 0),
26503
+ 0
26504
+ );
26505
+ const place = (stretch) => {
26506
+ if (scaled) {
26507
+ const pxPerUnit = totalRunTime > 0 ? innerW * stretch / totalRunTime : 0;
26508
+ let cursor = H_MARGIN;
26509
+ for (const seg of segments) {
26510
+ if (seg.kind === "capsule") {
26511
+ seg.p.x = cursor + COLLAPSE_W / 2;
26512
+ seg.p.spanHalf = COLLAPSE_W / 2;
26513
+ cursor += COLLAPSE_W + SEG_GAP;
26514
+ } else {
26515
+ for (const e of seg.events) {
26516
+ e.x = cursor + (e.dateValue - seg.lo) * pxPerUnit;
26517
+ e.spanHalf = 0;
26518
+ }
26519
+ cursor += (seg.hi - seg.lo) * pxPerUnit + SEG_GAP;
26520
+ }
26521
+ }
26522
+ let prevX = -Infinity;
26523
+ let prevVal = null;
26524
+ for (const p of [...placed].sort((a, b) => a.x - b.x)) {
26525
+ if (p.kind === "era") {
26526
+ prevX = p.x + p.spanHalf;
26527
+ prevVal = null;
26528
+ continue;
26529
+ }
26530
+ if (p.dateValue === prevVal) {
26531
+ p.x = prevX;
26532
+ continue;
26533
+ }
26534
+ if (p.x < prevX + MIN_DOT_GAP) p.x = prevX + MIN_DOT_GAP;
26535
+ prevX = p.x;
26536
+ prevVal = p.dateValue;
26431
26537
  }
26432
- if (p.x < prevX + MIN_DOT_GAP) p.x = prevX + MIN_DOT_GAP;
26433
- prevX = p.x;
26434
- prevVal = p.dateValue;
26538
+ } else {
26539
+ placed.forEach((p) => {
26540
+ p.spanHalf = 0;
26541
+ });
26542
+ const n = placed.length;
26543
+ const spacing = n > 1 ? Math.max(MIN_SPACING, innerW / (n - 1)) : 0;
26544
+ placed.forEach((p, i) => {
26545
+ p.x = H_MARGIN + i * spacing;
26546
+ });
26435
26547
  }
26548
+ for (const side of ["above", "below"]) {
26549
+ const arr = placed.filter((p) => p.side === side).sort((a, b) => a.x - b.x);
26550
+ const laneRight = [];
26551
+ for (let i = 0; i < arr.length; i++) {
26552
+ const p = arr[i];
26553
+ const next = arr[i + 1];
26554
+ const crowdedRight = !!next && next.x - p.x < CARD_W + FAN_GAP;
26555
+ const preferred = crowdedRight ? p.x - CARD_W + CARD_INSET : p.x - CARD_W / 2;
26556
+ const maxLeft = p.x - CARD_INSET;
26557
+ const minLeft = p.x - CARD_W + CARD_INSET;
26558
+ let lane = laneRight.length;
26559
+ let left = Math.max(preferred, minLeft);
26560
+ for (let l = 0; l < laneRight.length; l++) {
26561
+ const want = Math.max(preferred, laneRight[l] + FAN_GAP, minLeft);
26562
+ if (want <= maxLeft) {
26563
+ lane = l;
26564
+ left = want;
26565
+ break;
26566
+ }
26567
+ }
26568
+ p.lane = lane;
26569
+ p.left = Math.max(6, left);
26570
+ laneRight[lane] = p.left + CARD_W;
26571
+ }
26572
+ }
26573
+ };
26574
+ const derive = () => {
26575
+ const contentW2 = Math.max(
26576
+ width,
26577
+ Math.max(...placed.map((p) => p.x + p.spanHalf)) + H_MARGIN,
26578
+ Math.max(...placed.map((p) => p.left + CARD_W)) + 6
26579
+ );
26580
+ const laneOffset = { above: [], below: [] };
26581
+ for (const side of ["above", "below"]) {
26582
+ const laneH = [];
26583
+ for (const p of placed) {
26584
+ if (p.side !== side) continue;
26585
+ laneH[p.lane] = Math.max(laneH[p.lane] ?? 0, p.cardH);
26586
+ }
26587
+ const off = [];
26588
+ let acc = 0;
26589
+ for (let l = 0; l < laneH.length; l++) {
26590
+ off[l] = acc;
26591
+ acc += (laneH[l] ?? 0) + LANE_GAP;
26592
+ }
26593
+ laneOffset[side] = off;
26594
+ }
26595
+ const laneNear2 = (p) => (p.side === "above" ? LEADER_ABOVE : LEADER_BELOW) + (laneOffset[p.side][p.lane] ?? 0);
26596
+ const ext = (side) => Math.max(
26597
+ 0,
26598
+ ...placed.filter((p) => p.side === side).map((p) => laneNear2(p) + p.cardH)
26599
+ );
26600
+ const dateAbove = placed.some((p) => p.side === "below" && p.date);
26601
+ const dateBelow = placed.some((p) => p.side === "above" && p.date);
26602
+ const contentAbove = Math.max(
26603
+ ext("above"),
26604
+ dateAbove ? DATE_OFFSET + 10 : 0
26605
+ );
26606
+ const contentBelow = Math.max(
26607
+ ext("below"),
26608
+ dateBelow ? DATE_OFFSET + 10 : 0
26609
+ );
26610
+ const aboveExt = contentAbove + (hasExpandedEra && eraSide === "above" ? ERA_BLOCK : 0);
26611
+ const belowExt = contentBelow + (hasExpandedEra && eraSide === "below" ? ERA_BLOCK : 0);
26612
+ const TOP_PAD = 14;
26613
+ const BOT_PAD = 14;
26614
+ const spineY2 = topUsed + TOP_PAD + aboveExt;
26615
+ const eraBaseY2 = eraSide === "above" ? spineY2 - (contentAbove + 14) : spineY2 + (contentBelow + 14);
26616
+ const contentH = spineY2 + belowExt + BOT_PAD;
26617
+ const totalH2 = Math.max(heightHint, contentH);
26618
+ return { contentW: contentW2, contentH, totalH: totalH2, spineY: spineY2, eraBaseY: eraBaseY2, laneNear: laneNear2 };
26619
+ };
26620
+ const envStretch = Number(
26621
+ globalThis.process?.env?.["DGMO_EVT_STRETCH"]
26622
+ );
26623
+ let layout;
26624
+ if (Number.isFinite(envStretch) && envStretch > 0) {
26625
+ place(envStretch);
26626
+ layout = derive();
26436
26627
  } else {
26437
- const n = placed.length;
26438
- const spacing = n > 1 ? Math.max(MIN_SPACING, innerW / (n - 1)) : 0;
26439
- placed.forEach((p, i) => {
26440
- p.x = H_MARGIN + i * spacing;
26441
- });
26442
- }
26443
- for (const side of ["above", "below"]) {
26444
- const arr = placed.filter((p) => p.side === side).sort((a, b) => a.x - b.x);
26445
- const laneRight = [];
26446
- for (let i = 0; i < arr.length; i++) {
26447
- const p = arr[i];
26448
- const next = arr[i + 1];
26449
- const crowdedRight = !!next && next.x - p.x < CARD_W + FAN_GAP;
26450
- const preferred = crowdedRight ? p.x - CARD_W + CARD_INSET : p.x - CARD_W / 2;
26451
- const maxLeft = p.x - CARD_INSET;
26452
- const minLeft = p.x - CARD_W + CARD_INSET;
26453
- let lane = laneRight.length;
26454
- let left = Math.max(preferred, minLeft);
26455
- for (let l = 0; l < laneRight.length; l++) {
26456
- const want = Math.max(preferred, laneRight[l] + FAN_GAP, minLeft);
26457
- if (want <= maxLeft) {
26458
- lane = l;
26459
- left = want;
26460
- break;
26628
+ place(1);
26629
+ layout = derive();
26630
+ const panelAspect = width / heightHint;
26631
+ const aspectOf = (l) => l.contentW / l.contentH;
26632
+ if (scaled && layout.contentH > heightHint && aspectOf(layout) < panelAspect) {
26633
+ place(STRETCH_CAP);
26634
+ const capped = derive();
26635
+ if (aspectOf(capped) <= panelAspect) {
26636
+ layout = capped;
26637
+ } else {
26638
+ let loF = 1;
26639
+ let hiF = STRETCH_CAP;
26640
+ for (let it = 0; it < 8; it++) {
26641
+ const mid = (loF + hiF) / 2;
26642
+ place(mid);
26643
+ if (aspectOf(derive()) >= panelAspect) hiF = mid;
26644
+ else loF = mid;
26461
26645
  }
26646
+ place(hiF);
26647
+ layout = derive();
26462
26648
  }
26463
- p.lane = lane;
26464
- p.left = Math.max(6, left);
26465
- laneRight[lane] = p.left + CARD_W;
26466
26649
  }
26467
26650
  }
26468
- const contentW = Math.max(
26469
- width,
26470
- Math.max(...placed.map((p) => p.x)) + H_MARGIN,
26471
- Math.max(...placed.map((p) => p.left + CARD_W)) + 6
26472
- );
26473
- const rowGap = (side) => Math.max(0, ...placed.filter((p) => p.side === side).map((p) => p.cardH)) + LANE_GAP;
26474
- const rowGapA = rowGap("above");
26475
- const rowGapB = rowGap("below");
26476
- const ext = (side) => Math.max(
26477
- 0,
26478
- ...placed.filter((p) => p.side === side).map(
26479
- (p) => (side === "above" ? LEADER_ABOVE : LEADER_BELOW) + p.lane * (side === "above" ? rowGapA : rowGapB) + p.cardH
26480
- )
26481
- );
26482
- const dateAbove = placed.some((p) => p.side === "below" && p.date);
26483
- const dateBelow = placed.some((p) => p.side === "above" && p.date);
26484
- const contentAbove = Math.max(ext("above"), dateAbove ? DATE_OFFSET + 10 : 0);
26485
- const contentBelow = Math.max(ext("below"), dateBelow ? DATE_OFFSET + 10 : 0);
26486
- const aboveExt = contentAbove + (hasExpandedEra && eraSide === "above" ? ERA_BLOCK : 0);
26487
- const belowExt = contentBelow + (hasExpandedEra && eraSide === "below" ? ERA_BLOCK : 0);
26488
- const TOP_PAD = 14;
26489
- const BOT_PAD = 14;
26490
- const spineY = topUsed + TOP_PAD + aboveExt;
26491
- const eraBaseY = eraSide === "above" ? spineY - (contentAbove + 14) : spineY + (contentBelow + 14);
26492
- const totalH = Math.max(heightHint, spineY + belowExt + BOT_PAD);
26651
+ const { contentW, totalH, spineY, eraBaseY, laneNear } = layout;
26493
26652
  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);
26494
26653
  svg.append("rect").attr("width", contentW).attr("height", totalH).attr("fill", palette.bg);
26495
26654
  if (showTitle) {
@@ -26517,12 +26676,11 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26517
26676
  activeGroup
26518
26677
  });
26519
26678
  }
26520
- const xs = placed.map((p) => p.x);
26521
- const x0 = Math.min(...xs);
26522
- const x1 = Math.max(...xs);
26679
+ const x0 = Math.min(...placed.map((p) => p.x - p.spanHalf));
26680
+ const x1 = Math.max(...placed.map((p) => p.x + p.spanHalf));
26523
26681
  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");
26524
26682
  const geo = placed.map((p) => {
26525
- const near = p.side === "above" ? spineY - LEADER_ABOVE - p.lane * rowGapA : spineY + LEADER_BELOW + p.lane * rowGapB;
26683
+ const near = p.side === "above" ? spineY - laneNear(p) : spineY + laneNear(p);
26526
26684
  const top = p.side === "above" ? near - p.cardH : near;
26527
26685
  return { p, near, top };
26528
26686
  });
@@ -26634,7 +26792,7 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26634
26792
  for (const p of placed) {
26635
26793
  if (p.kind === "era") {
26636
26794
  const col = p.color;
26637
- const half = Math.min(46, 16 + (p.members.length - 1) * 9);
26795
+ const half = Math.max(16, p.spanHalf);
26638
26796
  const spineLeft = x0 - 20;
26639
26797
  const spineRight = x1 + 20;
26640
26798
  const bx0 = Math.max(spineLeft, p.x - half);
@@ -26645,6 +26803,11 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26645
26803
  "d",
26646
26804
  `M${bx0},${spineY} L${bx0},${barY} L${bx1},${barY} L${bx1},${spineY}`
26647
26805
  ).attr("fill", "none").attr("stroke", col).attr("stroke-width", 1.5).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
26806
+ const breakCx = (bx0 + bx1) / 2;
26807
+ 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");
26808
+ for (const d of axisBreakPaths(breakCx, spineY)) {
26809
+ 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);
26810
+ }
26648
26811
  if (onClickItem) {
26649
26812
  const ln = p.lineNumber;
26650
26813
  eg.style("cursor", "pointer").on("click", () => onClickItem(ln));
@@ -26678,13 +26841,23 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
26678
26841
  }
26679
26842
  const clampX = (v) => Math.max(4, Math.min(contentW - 4, v));
26680
26843
  const cap = eraSide === "above" ? 1 : -1;
26681
- for (const r of runs) {
26682
- if (r.collapsed) continue;
26844
+ const drawn = runs.filter((r) => !r.collapsed).map((r) => ({
26845
+ run: r,
26846
+ x0: clampX(r.firstX - ERA_BRACKET_PAD),
26847
+ x1: clampX(r.lastX + ERA_BRACKET_PAD)
26848
+ })).sort((a, b) => a.run.firstX - b.run.firstX);
26849
+ for (let i = 1; i < drawn.length; i++) {
26850
+ const prev = drawn[i - 1];
26851
+ const cur = drawn[i];
26852
+ if (prev.x1 <= cur.x0) continue;
26853
+ const mid = (prev.run.lastX + cur.run.firstX) / 2;
26854
+ prev.x1 = Math.max(prev.run.lastX, Math.min(prev.x1, mid - ERA_SEAM_GAP));
26855
+ cur.x0 = Math.min(cur.run.firstX, Math.max(cur.x0, mid + ERA_SEAM_GAP));
26856
+ }
26857
+ for (const { run: r, x0: x02, x1: x12 } of drawn) {
26683
26858
  const neutral = !r.era.color;
26684
26859
  const col = neutral ? palette.text : resolveColor(r.era.color, palette) ?? palette.text;
26685
26860
  const op = neutral ? 0.5 : 0.85;
26686
- const x02 = clampX(r.firstX - 14);
26687
- const x12 = clampX(r.lastX + 14);
26688
26861
  const y = eraBaseY;
26689
26862
  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);
26690
26863
  eg.append("path").attr(
@@ -26710,6 +26883,22 @@ function renderEventLineForExport(container, parsed, palette, isDark, exportDims
26710
26883
  tagOverride
26711
26884
  );
26712
26885
  }
26886
+ function axisBreakPaths(cx, cy) {
26887
+ const HUMPS = 2;
26888
+ const STEPS = 14;
26889
+ const wave = (x0) => {
26890
+ let d = "";
26891
+ for (let i = 0; i <= STEPS; i++) {
26892
+ const t = i / STEPS;
26893
+ const y = cy - BREAK_HALF_H + t * 2 * BREAK_HALF_H;
26894
+ const x = x0 + BREAK_AMP * Math.sin(t * Math.PI * HUMPS);
26895
+ d += `${i === 0 ? "M" : "L"}${x.toFixed(2)},${y.toFixed(2)}`;
26896
+ if (i < STEPS) d += " ";
26897
+ }
26898
+ return d;
26899
+ };
26900
+ return [wave(cx - BREAK_GAP / 2), wave(cx + BREAK_GAP / 2)];
26901
+ }
26713
26902
  function wrapDescription(lines, charsPerLine) {
26714
26903
  const out = [];
26715
26904
  for (const line11 of lines) {
@@ -26739,7 +26928,7 @@ function renderBody(cardG, lines, bodyColor, palette, startBaseline = CARD_BODY_
26739
26928
  y += DESC_LINE_H;
26740
26929
  }
26741
26930
  }
26742
- var 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;
26931
+ var 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;
26743
26932
  var init_renderer = __esm({
26744
26933
  "src/event-line/renderer.ts"() {
26745
26934
  "use strict";
@@ -26773,9 +26962,13 @@ var init_renderer = __esm({
26773
26962
  DATE_OFFSET = 28;
26774
26963
  ERA_BLOCK = 30;
26775
26964
  ERA_BRACKET_CAP = 8;
26965
+ ERA_BRACKET_PAD = 14;
26966
+ ERA_SEAM_GAP = 3;
26967
+ BREAK_HALF_H = 6;
26968
+ BREAK_AMP = 2.2;
26969
+ BREAK_GAP = 5;
26776
26970
  ERA_LEG = 13;
26777
26971
  ERA_LABEL_FONT = 11.5;
26778
- ERA_MEMBER_MAX = 6;
26779
26972
  }
26780
26973
  });
26781
26974
 
@@ -26918,13 +27111,13 @@ function renderVersionControl(container, parsed, palette, isDark, onClickItem, e
26918
27111
  }
26919
27112
  const showTitle = !!parsed.title;
26920
27113
  const titleH = showTitle ? TITLE_AREA2 : 0;
26921
- const W2 = Math.round(contentW);
27114
+ const W3 = Math.round(contentW);
26922
27115
  const H2 = Math.round(contentH + titleH);
26923
27116
  const exportMode = !!exportDims;
26924
- 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);
26925
- svg.append("rect").attr("width", W2).attr("height", H2).attr("fill", palette.bg);
27117
+ 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);
27118
+ svg.append("rect").attr("width", W3).attr("height", H2).attr("fill", palette.bg);
26926
27119
  if (showTitle) {
26927
- 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);
27120
+ 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);
26928
27121
  if (onClickItem && parsed.titleLineNumber && !exportMode) {
26929
27122
  const ln = parsed.titleLineNumber;
26930
27123
  t.style("cursor", "pointer").on("click", () => onClickItem(ln));
@@ -32705,7 +32898,7 @@ function findClearPosition(w, h, points, obstacles) {
32705
32898
  }
32706
32899
  function resolveLabelOverlaps(boxes) {
32707
32900
  const MAX_PASSES = 8;
32708
- const PAD = 4;
32901
+ const PAD2 = 4;
32709
32902
  for (let pass = 0; pass < MAX_PASSES; pass++) {
32710
32903
  let moved = false;
32711
32904
  for (let i = 0; i < boxes.length; i++) {
@@ -32714,8 +32907,8 @@ function resolveLabelOverlaps(boxes) {
32714
32907
  const b = boxes[j];
32715
32908
  const dx = Math.abs(a.cx - b.cx);
32716
32909
  const dy = Math.abs(a.cy - b.cy);
32717
- const overlapX = (a.w + b.w) / 2 + PAD - dx;
32718
- const overlapY = (a.h + b.h) / 2 + PAD - dy;
32910
+ const overlapX = (a.w + b.w) / 2 + PAD2 - dx;
32911
+ const overlapY = (a.h + b.h) / 2 + PAD2 - dy;
32719
32912
  if (overlapX > 0 && overlapY > 0) {
32720
32913
  const shift = overlapY / 2 + 1;
32721
32914
  if (a.cy < b.cy) {
@@ -43096,7 +43289,7 @@ function buildPathD(pts, direction) {
43096
43289
  function computePortPts(edges, nodeMap, direction) {
43097
43290
  const srcPts = /* @__PURE__ */ new Map();
43098
43291
  const tgtPts = /* @__PURE__ */ new Map();
43099
- const PAD = 0.1;
43292
+ const PAD2 = 0.1;
43100
43293
  const activeEdges = edges.filter((e) => e.points.length > 0);
43101
43294
  const bySource = /* @__PURE__ */ new Map();
43102
43295
  for (const e of activeEdges) {
@@ -43112,7 +43305,7 @@ function computePortPts(edges, nodeMap, direction) {
43112
43305
  ).sort((a, b) => direction === "LR" ? a.t.y - b.t.y : a.t.x - b.t.x);
43113
43306
  const n = sorted.length;
43114
43307
  for (let i = 0; i < n; i++) {
43115
- const frac = n === 1 ? 0.5 : PAD + (1 - 2 * PAD) * i / (n - 1);
43308
+ const frac = n === 1 ? 0.5 : PAD2 + (1 - 2 * PAD2) * i / (n - 1);
43116
43309
  const { e, t } = sorted[i];
43117
43310
  const isBackward = direction === "LR" ? t.x < source.x : t.y < source.y;
43118
43311
  if (direction === "LR") {
@@ -43142,7 +43335,7 @@ function computePortPts(edges, nodeMap, direction) {
43142
43335
  ).sort((a, b) => direction === "LR" ? a.s.y - b.s.y : a.s.x - b.s.x);
43143
43336
  const n = sorted.length;
43144
43337
  for (let i = 0; i < n; i++) {
43145
- const frac = n === 1 ? 0.5 : PAD + (1 - 2 * PAD) * i / (n - 1);
43338
+ const frac = n === 1 ? 0.5 : PAD2 + (1 - 2 * PAD2) * i / (n - 1);
43146
43339
  const { e, s } = sorted[i];
43147
43340
  const isBackward = direction === "LR" ? target.x < s.x : target.y < s.y;
43148
43341
  if (direction === "LR") {
@@ -52935,6 +53128,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
52935
53128
  y,
52936
53129
  score,
52937
53130
  palette,
53131
+ isDark,
52938
53132
  SCORE_LABEL_R
52939
53133
  );
52940
53134
  if (!exportDims) {
@@ -52966,7 +53160,14 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
52966
53160
  const allSteps = parsed.phases.length > 0 ? parsed.phases.flatMap((p) => p.steps) : parsed.steps;
52967
53161
  for (const pt of layout.curvePoints) {
52968
53162
  const step = allSteps[pt.stepIndex];
52969
- const faceG = renderScoreFace(curveG, pt.x, pt.y, pt.score, palette);
53163
+ const faceG = renderScoreFace(
53164
+ curveG,
53165
+ pt.x,
53166
+ pt.y,
53167
+ pt.score,
53168
+ palette,
53169
+ isDark
53170
+ );
52970
53171
  addEmotionLabel(faceG, pt, palette);
52971
53172
  if (step) {
52972
53173
  faceG.attr("data-line-number", step.lineNumber);
@@ -52992,7 +53193,14 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
52992
53193
  const pt = layout.curvePoints[0];
52993
53194
  const allSteps = parsed.phases.length > 0 ? parsed.phases.flatMap((p) => p.steps) : parsed.steps;
52994
53195
  const step = allSteps[pt.stepIndex];
52995
- const faceG = renderScoreFace(curveG, pt.x, pt.y, pt.score, palette);
53196
+ const faceG = renderScoreFace(
53197
+ curveG,
53198
+ pt.x,
53199
+ pt.y,
53200
+ pt.score,
53201
+ palette,
53202
+ isDark
53203
+ );
52996
53204
  addEmotionLabel(faceG, pt, palette);
52997
53205
  if (step) {
52998
53206
  faceG.attr("data-line-number", step.lineNumber);
@@ -53089,6 +53297,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53089
53297
  faceCy,
53090
53298
  step.score,
53091
53299
  palette,
53300
+ isDark,
53092
53301
  COLLAPSED_FACE_R
53093
53302
  );
53094
53303
  faceG.attr("data-line-number", step.lineNumber);
@@ -53192,7 +53401,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53192
53401
  const fcy = parseFloat(el.attr("data-cy") ?? "0");
53193
53402
  el.attr(
53194
53403
  "transform",
53195
- `translate(${fcx},${fcy}) scale(1.5) translate(${-fcx},${-fcy})`
53404
+ `translate(${fcx},${fcy}) scale(${FACE_HOVER_SCALE}) translate(${-fcx},${-fcy})`
53196
53405
  );
53197
53406
  } else {
53198
53407
  el.attr("transform", null);
@@ -53238,12 +53447,12 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
53238
53447
  PADDING2,
53239
53448
  Math.min(fcx - bw / 2, layout.totalWidth - PADDING2 - bw)
53240
53449
  );
53241
- const by = Math.max(PADDING2, fcy - FACE_RADIUS - THOUGHT_GAP - bh);
53450
+ const by = Math.max(PADDING2, fcy - FACE_HOVER_R - THOUGHT_GAP - bh);
53242
53451
  const scoreColor = scoreToColor(score, palette);
53243
53452
  const tintedBg = mix(scoreColor, palette.surface, 20);
53244
53453
  const g = overlayG.append("g").attr("class", "journey-thought-hover");
53245
53454
  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);
53246
- 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);
53455
+ 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);
53247
53456
  const centerX = bx + bw / 2;
53248
53457
  for (let i = 0; i < lines.length; i++) {
53249
53458
  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]);
@@ -53486,24 +53695,23 @@ function renderPersonaSilhouette(parent, cx, cy, color, palette, scale = 1) {
53486
53695
  `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`
53487
53696
  ).attr("fill", fill2).attr("stroke", stroke2).attr("stroke-width", 1.2);
53488
53697
  }
53489
- function renderScoreFace(parent, cx, cy, score, palette, radius) {
53698
+ function renderScoreFace(parent, cx, cy, score, palette, isDark, radius) {
53490
53699
  const r = radius ?? FACE_RADIUS;
53491
53700
  const color = scoreToColor(score, palette);
53492
53701
  const g = parent.append("g").attr("class", "journey-face").attr("data-cx", cx).attr("data-cy", cy);
53493
- g.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r).attr("fill", color).attr("stroke", palette.bg).attr("stroke-width", 1.5);
53494
- const eyeColor = contrastText(
53495
- color,
53496
- palette.textOnFillLight,
53497
- palette.textOnFillDark
53498
- );
53702
+ const faceFill = shapeFill(palette, color, isDark);
53703
+ g.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r + 1).attr("fill", palette.bg);
53704
+ g.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r).attr("fill", faceFill).attr("stroke", color).attr("stroke-width", 2);
53705
+ const eyeColor = color;
53499
53706
  const eyeY = cy - r * 0.15;
53500
53707
  const eyeSpacing = r * 0.32;
53501
53708
  const eyeR = r * 0.12;
53502
53709
  g.append("circle").attr("cx", cx - eyeSpacing).attr("cy", eyeY).attr("r", eyeR).attr("fill", eyeColor);
53503
53710
  g.append("circle").attr("cx", cx + eyeSpacing).attr("cy", eyeY).attr("r", eyeR).attr("fill", eyeColor);
53504
- const mouthY = cy + r * 0.25;
53505
- const mouthW = r * 0.45;
53506
- const curve = (score - 3) / 2 * r * 0.35;
53711
+ const mouthW = r * 0.46;
53712
+ const curve = (score - 3) / 2 * r * 0.42;
53713
+ const apexY = cy + r * 0.32;
53714
+ const mouthY = apexY - curve / 2;
53507
53715
  g.append("path").attr(
53508
53716
  "d",
53509
53717
  `M ${cx - mouthW} ${mouthY} Q ${cx} ${mouthY + curve} ${cx + mouthW} ${mouthY}`
@@ -53574,7 +53782,7 @@ function renderJourneyMapForExport(content, theme, palette) {
53574
53782
  }
53575
53783
  return svgEl.outerHTML;
53576
53784
  }
53577
- var 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;
53785
+ var 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;
53578
53786
  var init_renderer17 = __esm({
53579
53787
  "src/journey-map/renderer.ts"() {
53580
53788
  "use strict";
@@ -53608,6 +53816,8 @@ var init_renderer17 = __esm({
53608
53816
  GRID_LINE_OPACITY = 0.15;
53609
53817
  CURVE_STROKE_WIDTH = 2.5;
53610
53818
  FACE_RADIUS = 14;
53819
+ FACE_HOVER_SCALE = 1.5;
53820
+ FACE_HOVER_R = (FACE_RADIUS + 1) * FACE_HOVER_SCALE;
53611
53821
  DIM_HOVER = 0.25;
53612
53822
  TITLE_LINE_HEIGHT = 16;
53613
53823
  EMOTION_LABEL_MAX_WIDTH = 180;
@@ -56657,7 +56867,7 @@ function layoutMap(resolvedIn, data, size, opts) {
56657
56867
  const akRef = resolved.regions.some((r) => r.iso === "US-AK") || resolved.pois.some((p) => inAlaska(p.lon, p.lat));
56658
56868
  const hiRef = resolved.regions.some((r) => r.iso === "US-HI") || resolved.pois.some((p) => inHawaii(p.lon, p.lat));
56659
56869
  if (resolved.projection === "albers-usa" && usLayer && (akRef || hiRef)) {
56660
- const PAD = 8;
56870
+ const PAD2 = 8;
56661
56871
  const GAP = 12;
56662
56872
  const yB = height - FIT_PAD;
56663
56873
  const BW = 8;
@@ -56700,22 +56910,22 @@ function layoutMap(resolvedIn, data, size, opts) {
56700
56910
  const f = usLayer.get(iso);
56701
56911
  if (!f) return boxX;
56702
56912
  const x0 = boxX;
56703
- const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
56913
+ const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD2);
56704
56914
  if (iw < 24) return boxX;
56705
- const xr = x0 + iw + 2 * PAD;
56915
+ const xr = x0 + iw + 2 * PAD2;
56706
56916
  const floor = coastFloor(x0, xr);
56707
56917
  const topGuess = floor > -Infinity ? floor + GAP : yB - height * 0.42;
56708
56918
  proj.fitWidth(iw, f);
56709
56919
  const bb = geoPath(proj).bounds(f);
56710
56920
  const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
56711
- const needH = sh + 2 * PAD;
56921
+ const needH = sh + 2 * PAD2;
56712
56922
  let topFit = topGuess;
56713
56923
  const bottom = Math.min(topFit + needH, yB);
56714
56924
  if (bottom - topFit < needH) topFit = bottom - needH;
56715
56925
  proj.fitExtent(
56716
56926
  [
56717
- [x0 + PAD, topFit + PAD],
56718
- [xr - PAD, bottom - PAD]
56927
+ [x0 + PAD2, topFit + PAD2],
56928
+ [xr - PAD2, bottom - PAD2]
56719
56929
  ],
56720
56930
  f
56721
56931
  );
@@ -58239,15 +58449,15 @@ function layoutMap(resolvedIn, data, size, opts) {
58239
58449
  }
58240
58450
  }
58241
58451
  if (regionLabelGuards.length > 0) {
58242
- const PAD = 2;
58452
+ const PAD2 = 2;
58243
58453
  const poiRects = labels.filter((l) => l.poiId !== void 0 && l.hidden !== true).map((l) => {
58244
58454
  const w = measureLegendText(l.text, FONT2);
58245
58455
  const x = l.anchor === "start" ? l.x : l.anchor === "end" ? l.x - w : l.x - w / 2;
58246
58456
  return {
58247
- x: x - PAD,
58457
+ x: x - PAD2,
58248
58458
  y: l.y - FONT2,
58249
- w: w + 2 * PAD,
58250
- h: FONT2 * 1.4 + 2 * PAD
58459
+ w: w + 2 * PAD2,
58460
+ h: FONT2 * 1.4 + 2 * PAD2
58251
58461
  };
58252
58462
  });
58253
58463
  for (const g of regionLabelGuards) {
@@ -58871,7 +59081,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
58871
59081
  if (poiLabels.length) {
58872
59082
  const patchBlurId = nid("dgmo-map-label-patch-blur");
58873
59083
  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);
58874
- const PAD = 8;
59084
+ const PAD2 = 8;
58875
59085
  const buildPatch = (labels, maskId, decoCluster) => {
58876
59086
  if (!labels.length) return;
58877
59087
  const patchMask = defs.append("mask").attr("id", maskId).attr("maskUnits", "userSpaceOnUse").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
@@ -58887,10 +59097,10 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
58887
59097
  const h = th + 6;
58888
59098
  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");
58889
59099
  blobRects.push({
58890
- x0: cx - w / 2 - PAD,
58891
- y0: cy - h / 2 - PAD,
58892
- x1: cx + w / 2 + PAD,
58893
- y1: cy + h / 2 + PAD
59100
+ x0: cx - w / 2 - PAD2,
59101
+ y0: cy - h / 2 - PAD2,
59102
+ x1: cx + w / 2 + PAD2,
59103
+ y1: cy + h / 2 + PAD2
58894
59104
  });
58895
59105
  }
58896
59106
  const gPatch = svg.append("g").attr("class", "dgmo-map-label-patch").attr("mask", `url(#${maskId})`).style("pointer-events", "none");
@@ -60311,13 +60521,13 @@ function renderTreemap(container, parsed, palette, isDark, exportDims, options =
60311
60521
  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);
60312
60522
  }
60313
60523
  }
60314
- const PAD = Math.max(4, Math.min(14, Math.floor(Math.min(w, h) / 5)));
60524
+ const PAD2 = Math.max(4, Math.min(14, Math.floor(Math.min(w, h) / 5)));
60315
60525
  const MIN_FS = 7;
60316
- if (!cell.isContainer && w >= 2 * PAD + 8 && h >= 2 * PAD + 8) {
60317
- const maxW = w - 2 * PAD;
60526
+ if (!cell.isContainer && w >= 2 * PAD2 + 8 && h >= 2 * PAD2 + 8) {
60527
+ const maxW = w - 2 * PAD2;
60318
60528
  const horizMinW = measureText(cell.label, MIN_FS) * 1.06;
60319
- if (horizMinW > maxW && h > w * 1.25 && h - 2 * PAD > maxW) {
60320
- drawVerticalLabel(g, cell.label, w, h, PAD, ink);
60529
+ if (horizMinW > maxW && h > w * 1.25 && h - 2 * PAD2 > maxW) {
60530
+ drawVerticalLabel(g, cell.label, w, h, PAD2, ink);
60321
60531
  if (drillable && w > 30 && h > 22) drawFocusIcon(g, w, ink, 9);
60322
60532
  continue;
60323
60533
  }
@@ -60328,8 +60538,8 @@ function renderTreemap(container, parsed, palette, isDark, exportDims, options =
60328
60538
  const nameOpacity = 0.95 - tg * 0.5;
60329
60539
  const vfs = clamp3(Math.round(fs * 0.42), 11, 28);
60330
60540
  const valOpacity = Math.max(0.45, nameOpacity - 0.1);
60331
- let y = PAD + Math.round(fs * 0.78);
60332
- 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));
60541
+ let y = PAD2 + Math.round(fs * 0.78);
60542
+ 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));
60333
60543
  const valParts = [
60334
60544
  opts.noValues ? "" : compactNumber2(cell.value),
60335
60545
  opts.noPercent ? "" : formatPct(cell.pctOfRoot)
@@ -60337,12 +60547,12 @@ function renderTreemap(container, parsed, palette, isDark, exportDims, options =
60337
60547
  const combined = valParts.join(" \xB7 ");
60338
60548
  const nameBaseY = y;
60339
60549
  const addLineAt = (text, yy, size, opacity) => {
60340
- 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);
60550
+ 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);
60341
60551
  };
60342
60552
  if (combined) {
60343
60553
  const fitsW = measureText(combined, vfs) <= maxW;
60344
60554
  const valY = y + lineDrop(fs, vfs);
60345
- const roomBelow = valY + descent(vfs) <= h - PAD;
60555
+ const roomBelow = valY + descent(vfs) <= h - PAD2;
60346
60556
  if (fitsW && roomBelow) {
60347
60557
  addLineAt(combined, valY, vfs, valOpacity);
60348
60558
  } else if (fitsW) {
@@ -60350,24 +60560,24 @@ function renderTreemap(container, parsed, palette, isDark, exportDims, options =
60350
60560
  const rendered = clipLabel(cell.label, maxW, fs);
60351
60561
  const nW = measureText(rendered, fs) * 1.12;
60352
60562
  const gap = Math.max(12, Math.round(fs * 0.25));
60353
- if (PAD + nW + gap + measureText(combined, ivfs) <= w - PAD) {
60354
- 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);
60563
+ if (PAD2 + nW + gap + measureText(combined, ivfs) <= w - PAD2) {
60564
+ 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);
60355
60565
  }
60356
60566
  } else if (valParts.length === 2) {
60357
60567
  const d1 = lineDrop(fs, vfs);
60358
- if (y + d1 + descent(vfs) <= h - PAD) {
60568
+ if (y + d1 + descent(vfs) <= h - PAD2) {
60359
60569
  y += d1;
60360
60570
  addLineAt(valParts[0], y, vfs, valOpacity);
60361
60571
  }
60362
60572
  const pfs = Math.max(10, Math.round(vfs * 0.9));
60363
60573
  const d2 = lineDrop(vfs, pfs);
60364
- if (y + d2 + descent(pfs) <= h - PAD) {
60574
+ if (y + d2 + descent(pfs) <= h - PAD2) {
60365
60575
  y += d2;
60366
60576
  addLineAt(valParts[1], y, pfs, valOpacity);
60367
60577
  }
60368
60578
  } else {
60369
60579
  const onlyY = y + lineDrop(fs, vfs);
60370
- if (onlyY + descent(vfs) <= h - PAD) {
60580
+ if (onlyY + descent(vfs) <= h - PAD2) {
60371
60581
  addLineAt(combined, onlyY, vfs, valOpacity);
60372
60582
  }
60373
60583
  }
@@ -68490,6 +68700,144 @@ function decodeDiagramUrl(hash) {
68490
68700
  init_dgmo_router();
68491
68701
  init_palettes();
68492
68702
  init_diagnostics();
68703
+
68704
+ // src/error-card.ts
68705
+ init_text_measure();
68706
+ init_fonts();
68707
+ var MONO_FAMILY = "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace";
68708
+ var W2 = 660;
68709
+ var PAD = 24;
68710
+ var CONTENT_W = W2 - PAD * 2;
68711
+ var MAX_ERRORS = 4;
68712
+ var MONO_CH = 0.6;
68713
+ var CODE_FS = 12.5;
68714
+ function renderErrorCard(errors, source, palette, theme) {
68715
+ const c = palette[theme === "dark" ? "dark" : "light"];
68716
+ const isTransparent = theme === "transparent";
68717
+ const card = c.surface;
68718
+ const codeBg = c.bg;
68719
+ const border = c.border;
68720
+ const danger = c.destructive ?? c.colors.red;
68721
+ const text = c.text;
68722
+ const muted = c.textMuted;
68723
+ const srcLines = source.split("\n");
68724
+ const shown = errors.slice(0, MAX_ERRORS);
68725
+ const overflow = errors.length - shown.length;
68726
+ const parts = [];
68727
+ let y = PAD + 8;
68728
+ parts.push(frownMark(PAD + 14, y + 12, danger));
68729
+ parts.push(
68730
+ `<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>`,
68731
+ `<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>`
68732
+ );
68733
+ y += 24;
68734
+ const errLabel = errors.length === 1 ? "1 error" : `${errors.length} errors`;
68735
+ parts.push(
68736
+ `<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>`
68737
+ );
68738
+ y += 22;
68739
+ parts.push(
68740
+ `<line x1="${PAD}" y1="${y}" x2="${W2 - PAD}" y2="${y}" stroke="${border}" stroke-width="1"/>`
68741
+ );
68742
+ y += 24;
68743
+ for (const e of shown) {
68744
+ const hasLine = e.line > 0;
68745
+ let msgX = PAD;
68746
+ if (hasLine) {
68747
+ const label = e.column ? `Line ${e.line}:${e.column}` : `Line ${e.line}`;
68748
+ const badgeW = 14 + label.length * 7;
68749
+ parts.push(
68750
+ `<rect x="${PAD}" y="${y - 13}" width="${badgeW}" height="19" rx="4" fill="${danger}"/>`,
68751
+ `<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>`
68752
+ );
68753
+ msgX = PAD + badgeW + 12;
68754
+ }
68755
+ const lines = wrapMessage(e.message, CONTENT_W - (msgX - PAD), CONTENT_W);
68756
+ lines.forEach((line11, i) => {
68757
+ parts.push(
68758
+ `<text x="${i === 0 ? msgX : PAD}" y="${y + 1}" fill="${text}" font-family="${FONT_FAMILY}" font-size="13">${escapeXml(line11)}</text>`
68759
+ );
68760
+ if (i < lines.length - 1) y += 18;
68761
+ });
68762
+ y += 24;
68763
+ const raw = hasLine ? srcLines[e.line - 1] : void 0;
68764
+ if (raw !== void 0 && raw.trim().length > 0) {
68765
+ const gutter = `${e.line}`;
68766
+ const gutterW = gutter.length * CODE_FS * MONO_CH + 16;
68767
+ const codeX = PAD + gutterW;
68768
+ const codeMaxW = CONTENT_W - gutterW - 12;
68769
+ const codeText = truncateText(
68770
+ raw.replace(/\t/g, " "),
68771
+ CODE_FS,
68772
+ codeMaxW
68773
+ );
68774
+ const hasCaret = e.column !== void 0 && e.column > 0;
68775
+ const blockH = hasCaret ? 40 : 26;
68776
+ parts.push(
68777
+ `<rect x="${PAD}" y="${y - 14}" width="${CONTENT_W}" height="${blockH}" rx="5" fill="${codeBg}" stroke="${border}" stroke-width="1"/>`,
68778
+ `<line x1="${codeX - 8}" y1="${y - 14}" x2="${codeX - 8}" y2="${y - 14 + blockH}" stroke="${border}" stroke-width="1"/>`,
68779
+ `<text x="${PAD + 8}" y="${y + 3}" fill="${muted}" font-family="${MONO_FAMILY}" font-size="${CODE_FS}">${escapeXml(gutter)}</text>`,
68780
+ `<text x="${codeX}" y="${y + 3}" fill="${text}" font-family="${MONO_FAMILY}" font-size="${CODE_FS}" xml:space="preserve">${escapeXml(codeText)}</text>`
68781
+ );
68782
+ if (hasCaret) {
68783
+ const caretX = codeX + (e.column - 1) * CODE_FS * MONO_CH;
68784
+ parts.push(
68785
+ `<text x="${caretX}" y="${y + 17}" fill="${danger}" font-family="${MONO_FAMILY}" font-size="${CODE_FS}" font-weight="700">^</text>`
68786
+ );
68787
+ y += 14;
68788
+ }
68789
+ y += 24;
68790
+ }
68791
+ y += 14;
68792
+ }
68793
+ if (overflow > 0) {
68794
+ parts.push(
68795
+ `<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>`
68796
+ );
68797
+ y += 22;
68798
+ }
68799
+ const height = Math.round(y + PAD - 14);
68800
+ const pageBg = isTransparent ? "" : `<rect width="100%" height="100%" fill="${c.bg}"/>`;
68801
+ return [
68802
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${W2}" height="${height}" viewBox="0 0 ${W2} ${height}" font-family="${FONT_FAMILY}">`,
68803
+ pageBg,
68804
+ `<rect x="0.5" y="0.5" width="${W2 - 1}" height="${height - 1}" rx="10" fill="${card}" stroke="${border}" stroke-width="1"/>`,
68805
+ parts.join(""),
68806
+ `</svg>`
68807
+ ].join("");
68808
+ }
68809
+ function wrapMessage(message, firstWidth, fullWidth) {
68810
+ const words = message.split(/\s+/).filter(Boolean);
68811
+ if (words.length === 0) return [""];
68812
+ const lines = [];
68813
+ let cur = "";
68814
+ for (const word of words) {
68815
+ const limit = lines.length === 0 ? firstWidth : fullWidth;
68816
+ const test = cur ? `${cur} ${word}` : word;
68817
+ if (measureText(test, 13) <= limit || !cur) {
68818
+ cur = test;
68819
+ } else {
68820
+ lines.push(cur);
68821
+ cur = word;
68822
+ }
68823
+ }
68824
+ if (cur) lines.push(cur);
68825
+ return lines;
68826
+ }
68827
+ function frownMark(cx, cy, color) {
68828
+ const r = 12;
68829
+ return [
68830
+ `<circle cx="${cx}" cy="${cy}" r="${r}" fill="none" stroke="${color}" stroke-width="2"/>`,
68831
+ `<circle cx="${cx - 4.5}" cy="${cy - 3}" r="1.6" fill="${color}"/>`,
68832
+ `<circle cx="${cx + 4.5}" cy="${cy - 3}" r="1.6" fill="${color}"/>`,
68833
+ `<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"/>`
68834
+ ].join("");
68835
+ }
68836
+ function escapeXml(s) {
68837
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
68838
+ }
68839
+
68840
+ // src/index.ts
68493
68841
  init_palettes();
68494
68842
 
68495
68843
  // src/themes.ts
@@ -68829,25 +69177,10 @@ async function render2(text, options) {
68829
69177
  return result;
68830
69178
  }
68831
69179
  return {
68832
- svg: result.svg || renderErrorSvg(errors, palette, options?.theme),
69180
+ svg: renderErrorCard(errors, text, palette, options?.theme),
68833
69181
  diagnostics: result.diagnostics
68834
69182
  };
68835
69183
  }
68836
- function renderErrorSvg(errors, palette, theme) {
68837
- const colors = palette[theme === "dark" ? "dark" : "light"];
68838
- const bg = theme === "transparent" ? "transparent" : colors.bg;
68839
- const fg = colors.destructive ?? colors.text;
68840
- const muted = colors.textMuted ?? colors.text;
68841
- const lines = errors.slice(0, 5).map((e) => formatDgmoError(e));
68842
- const height = 60 + lines.length * 20;
68843
- const body = lines.map(
68844
- (line11, i) => `<text x="20" y="${60 + i * 20}" fill="${fg}" font-family="ui-monospace, monospace" font-size="12">${escapeXml(line11)}</text>`
68845
- ).join("");
68846
- 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>`;
68847
- }
68848
- function escapeXml(s) {
68849
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
68850
- }
68851
69184
  function encodeDiagramUrl2(text, options) {
68852
69185
  const internalTheme = options?.theme === "light" || options?.theme === "dark" ? options.theme : void 0;
68853
69186
  const result = encodeDiagramUrl(text, {