@diagrammo/dgmo 0.6.0 → 0.6.1

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
@@ -1445,6 +1445,29 @@ var init_tag_groups = __esm({
1445
1445
  }
1446
1446
  });
1447
1447
 
1448
+ // src/utils/legend-constants.ts
1449
+ var LEGEND_HEIGHT, LEGEND_PILL_PAD, LEGEND_PILL_FONT_SIZE, LEGEND_PILL_FONT_W, LEGEND_CAPSULE_PAD, LEGEND_DOT_R, LEGEND_ENTRY_FONT_SIZE, LEGEND_ENTRY_FONT_W, LEGEND_ENTRY_DOT_GAP, LEGEND_ENTRY_TRAIL, LEGEND_GROUP_GAP, LEGEND_EYE_SIZE, LEGEND_EYE_GAP, EYE_OPEN_PATH, EYE_CLOSED_PATH;
1450
+ var init_legend_constants = __esm({
1451
+ "src/utils/legend-constants.ts"() {
1452
+ "use strict";
1453
+ LEGEND_HEIGHT = 28;
1454
+ LEGEND_PILL_PAD = 16;
1455
+ LEGEND_PILL_FONT_SIZE = 11;
1456
+ LEGEND_PILL_FONT_W = LEGEND_PILL_FONT_SIZE * 0.6;
1457
+ LEGEND_CAPSULE_PAD = 4;
1458
+ LEGEND_DOT_R = 4;
1459
+ LEGEND_ENTRY_FONT_SIZE = 10;
1460
+ LEGEND_ENTRY_FONT_W = LEGEND_ENTRY_FONT_SIZE * 0.6;
1461
+ LEGEND_ENTRY_DOT_GAP = 4;
1462
+ LEGEND_ENTRY_TRAIL = 8;
1463
+ LEGEND_GROUP_GAP = 12;
1464
+ LEGEND_EYE_SIZE = 14;
1465
+ LEGEND_EYE_GAP = 6;
1466
+ EYE_OPEN_PATH = "M1 7s2.5-5 6-5 6 5 6 5-2.5 5-6 5-6-5-6-5z M7 9.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z";
1467
+ EYE_CLOSED_PATH = "M2.5 2.5l9 9 M1.5 7s2.2-4 5.5-4c1.2 0 2.2.5 3 1.1 M12.5 7s-2.2 4-5.5 4c-1.2 0-2.2-.5-3-1.1";
1468
+ }
1469
+ });
1470
+
1448
1471
  // src/sequence/participant-inference.ts
1449
1472
  function inferParticipantType(name) {
1450
1473
  for (const rule of PARTICIPANT_RULES) {
@@ -4756,10 +4779,12 @@ function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacit
4756
4779
  if (type === "category" && data && data.length > 0) {
4757
4780
  const maxLabelLen = Math.max(...data.map((l) => l.length));
4758
4781
  const count = data.length;
4759
- if (count > 10 || maxLabelLen > 20) catFontSize = 10;
4760
- else if (count > 5 || maxLabelLen > 14) catFontSize = 11;
4782
+ const step = intervalOverride != null && intervalOverride > 0 ? intervalOverride + 1 : 1;
4783
+ const visibleCount = Math.ceil(count / step);
4784
+ if (visibleCount > 10 || maxLabelLen > 20) catFontSize = 10;
4785
+ else if (visibleCount > 5 || maxLabelLen > 14) catFontSize = 11;
4761
4786
  else if (maxLabelLen > 8) catFontSize = 12;
4762
- if (chartWidthHint && count > 0) {
4787
+ if ((intervalOverride == null || intervalOverride === 0) && chartWidthHint && count > 0) {
4763
4788
  const availPerLabel = Math.floor(chartWidthHint * 0.85 / count);
4764
4789
  catLabelExtras = {
4765
4790
  width: availPerLabel,
@@ -4777,6 +4802,9 @@ function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacit
4777
4802
  fontFamily: FONT_FAMILY,
4778
4803
  ...type === "category" && {
4779
4804
  interval: intervalOverride ?? 0,
4805
+ // Prevent ECharts auto-rotation: it measures raw slot width (chartWidth/N),
4806
+ // which is too narrow when an interval skips most labels, and rotates to 90°.
4807
+ rotate: 0,
4780
4808
  formatter: (value) => value.replace(/([a-z])([A-Z])/g, "$1\n$2"),
4781
4809
  ...catLabelExtras
4782
4810
  }
@@ -4852,21 +4880,13 @@ function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOp
4852
4880
  ]
4853
4881
  };
4854
4882
  }
4855
- function buildIntervalCallback(labels, eras) {
4883
+ function buildIntervalStep(labels) {
4856
4884
  const count = labels.length;
4857
- if (count <= 8) return () => true;
4858
- const snapSteps = [1, 2, 4, 5, 10, 20, 25, 50];
4859
- const raw = Math.ceil(count / 8);
4885
+ if (count <= 6) return 0;
4886
+ const snapSteps = [1, 2, 5, 10, 25, 50, 100];
4887
+ const raw = Math.ceil(count / 5);
4860
4888
  const N = [...snapSteps].reverse().find((s) => s <= raw) ?? 1;
4861
- const pinned = /* @__PURE__ */ new Set();
4862
- for (let i = 0; i < count; i += N) pinned.add(i);
4863
- for (const era of eras) {
4864
- const si = labels.indexOf(era.start);
4865
- const ei = labels.indexOf(era.end);
4866
- if (si >= 0) pinned.add(si);
4867
- if (ei >= 0) pinned.add(ei);
4868
- }
4869
- return (index) => pinned.has(index);
4889
+ return N - 1;
4870
4890
  }
4871
4891
  function buildMarkArea(eras, labels, textColor, defaultColor) {
4872
4892
  if (eras.length === 0) return void 0;
@@ -4901,7 +4921,7 @@ function buildLineOption(parsed, palette, textColor, axisLineColor, splitLineCol
4901
4921
  const labels = parsed.data.map((d) => d.label);
4902
4922
  const values = parsed.data.map((d) => d.value);
4903
4923
  const eras = parsed.eras ?? [];
4904
- const interval = buildIntervalCallback(labels, eras);
4924
+ const interval = buildIntervalStep(labels);
4905
4925
  const markArea = buildMarkArea(eras, labels, textColor, palette.colors.blue);
4906
4926
  return {
4907
4927
  ...CHART_BASE,
@@ -4933,7 +4953,7 @@ function buildMultiLineOption(parsed, palette, textColor, axisLineColor, splitLi
4933
4953
  const seriesNames = parsed.seriesNames ?? [];
4934
4954
  const labels = parsed.data.map((d) => d.label);
4935
4955
  const eras = parsed.eras ?? [];
4936
- const interval = buildIntervalCallback(labels, eras);
4956
+ const interval = buildIntervalStep(labels);
4937
4957
  const markArea = buildMarkArea(eras, labels, textColor, palette.colors.blue);
4938
4958
  const series = seriesNames.map((name, idx) => {
4939
4959
  const color = parsed.seriesNameColors?.[idx] ?? colors[idx % colors.length];
@@ -4977,7 +4997,7 @@ function buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineCol
4977
4997
  const labels = parsed.data.map((d) => d.label);
4978
4998
  const values = parsed.data.map((d) => d.value);
4979
4999
  const eras = parsed.eras ?? [];
4980
- const interval = buildIntervalCallback(labels, eras);
5000
+ const interval = buildIntervalStep(labels);
4981
5001
  const markArea = buildMarkArea(eras, labels, textColor, palette.colors.blue);
4982
5002
  return {
4983
5003
  ...CHART_BASE,
@@ -6990,7 +7010,6 @@ function parseInfra(content) {
6990
7010
  edges: [],
6991
7011
  groups: [],
6992
7012
  tagGroups: [],
6993
- scenarios: [],
6994
7013
  options: {},
6995
7014
  diagnostics: [],
6996
7015
  error: null
@@ -7093,16 +7112,7 @@ function parseInfra(content) {
7093
7112
  continue;
7094
7113
  }
7095
7114
  if (/^scenario\s*:/i.test(trimmed)) {
7096
- finishCurrentNode();
7097
- finishCurrentTagGroup();
7098
- currentGroup = null;
7099
- const scenarioName = trimmed.replace(/^scenario\s*:\s*/i, "").trim();
7100
- const scenario = {
7101
- name: scenarioName,
7102
- overrides: {},
7103
- lineNumber
7104
- };
7105
- let scenarioNodeId = null;
7115
+ console.warn("[dgmo warn] scenario syntax is deprecated and will be ignored");
7106
7116
  let si = i + 1;
7107
7117
  while (si < lines.length) {
7108
7118
  const sLine = lines[si];
@@ -7113,23 +7123,9 @@ function parseInfra(content) {
7113
7123
  }
7114
7124
  const sIndent = sLine.length - sLine.trimStart().length;
7115
7125
  if (sIndent === 0) break;
7116
- if (sIndent <= 2) {
7117
- scenarioNodeId = nodeId2(sTrimmed.replace(/\|.*$/, "").trim());
7118
- if (!scenario.overrides[scenarioNodeId]) {
7119
- scenario.overrides[scenarioNodeId] = {};
7120
- }
7121
- } else if (scenarioNodeId) {
7122
- const pm = sTrimmed.match(PROPERTY_RE);
7123
- if (pm) {
7124
- const key = pm[1].toLowerCase();
7125
- const val = parsePropertyValue(pm[2].trim());
7126
- scenario.overrides[scenarioNodeId][key] = val;
7127
- }
7128
- }
7129
7126
  si++;
7130
7127
  }
7131
7128
  i = si - 1;
7132
- result.scenarios.push(scenario);
7133
7129
  continue;
7134
7130
  }
7135
7131
  const tagMatch = trimmed.match(TAG_GROUP_RE);
@@ -7662,14 +7658,14 @@ function computeLegendGroups(tagGroups, showEyeIcons, usedValuesByGroup) {
7662
7658
  const usedValues = usedValuesByGroup?.get(group.name.toLowerCase());
7663
7659
  const visibleEntries = usedValues ? group.entries.filter((e) => usedValues.has(e.value.toLowerCase())) : group.entries;
7664
7660
  if (visibleEntries.length === 0) continue;
7665
- const pillWidth = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
7661
+ const pillWidth = group.name.length * LEGEND_PILL_FONT_W2 + LEGEND_PILL_PAD2;
7666
7662
  const minPillWidth = pillWidth;
7667
7663
  let entriesWidth = 0;
7668
7664
  for (const entry of visibleEntries) {
7669
- entriesWidth += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
7665
+ entriesWidth += LEGEND_DOT_R2 * 2 + LEGEND_ENTRY_DOT_GAP2 + entry.value.length * LEGEND_ENTRY_FONT_W2 + LEGEND_ENTRY_TRAIL2;
7670
7666
  }
7671
- const eyeSpace = showEyeIcons ? LEGEND_EYE_SIZE + LEGEND_EYE_GAP : 0;
7672
- const capsuleWidth = LEGEND_CAPSULE_PAD * 2 + pillWidth + 4 + eyeSpace + entriesWidth;
7667
+ const eyeSpace = showEyeIcons ? LEGEND_EYE_SIZE2 + LEGEND_EYE_GAP2 : 0;
7668
+ const capsuleWidth = LEGEND_CAPSULE_PAD2 * 2 + pillWidth + 4 + eyeSpace + entriesWidth;
7673
7669
  groups.push({
7674
7670
  name: group.name,
7675
7671
  alias: group.alias,
@@ -7680,9 +7676,9 @@ function computeLegendGroups(tagGroups, showEyeIcons, usedValuesByGroup) {
7680
7676
  x: 0,
7681
7677
  y: 0,
7682
7678
  width: capsuleWidth,
7683
- height: LEGEND_HEIGHT,
7679
+ height: LEGEND_HEIGHT2,
7684
7680
  minifiedWidth: minPillWidth,
7685
- minifiedHeight: LEGEND_HEIGHT
7681
+ minifiedHeight: LEGEND_HEIGHT2
7686
7682
  });
7687
7683
  }
7688
7684
  return groups;
@@ -7712,7 +7708,7 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
7712
7708
  for (const g of legendGroups2) {
7713
7709
  g.x = MARGIN;
7714
7710
  g.y = cy;
7715
- cy += LEGEND_HEIGHT + LEGEND_GROUP_GAP;
7711
+ cy += LEGEND_HEIGHT2 + LEGEND_GROUP_GAP2;
7716
7712
  if (g.width > maxWidth2) maxWidth2 = g.width;
7717
7713
  }
7718
7714
  return {
@@ -7721,7 +7717,7 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
7721
7717
  containers: [],
7722
7718
  legend: legendGroups2,
7723
7719
  width: maxWidth2 + MARGIN * 2,
7724
- height: cy - LEGEND_GROUP_GAP + MARGIN
7720
+ height: cy - LEGEND_GROUP_GAP2 + MARGIN
7725
7721
  };
7726
7722
  }
7727
7723
  injectDefaultMetadata(parsed.roots, parsed.tagGroups);
@@ -8251,7 +8247,7 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
8251
8247
  const effectiveH = (g) => activeTagGroup != null || allExpanded ? g.height : g.minifiedHeight;
8252
8248
  if (visibleGroups.length > 0) {
8253
8249
  if (legendPosition === "bottom") {
8254
- const totalGroupsWidth = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP;
8250
+ const totalGroupsWidth = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP2;
8255
8251
  const neededWidth = totalGroupsWidth + MARGIN * 2;
8256
8252
  if (neededWidth > totalWidth) {
8257
8253
  finalWidth = neededWidth;
@@ -8269,22 +8265,22 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
8269
8265
  for (const g of visibleGroups) {
8270
8266
  g.x = cx;
8271
8267
  g.y = legendY;
8272
- cx += effectiveW(g) + LEGEND_GROUP_GAP;
8268
+ cx += effectiveW(g) + LEGEND_GROUP_GAP2;
8273
8269
  }
8274
- finalHeight = totalHeight + LEGEND_GAP + LEGEND_HEIGHT;
8270
+ finalHeight = totalHeight + LEGEND_GAP + LEGEND_HEIGHT2;
8275
8271
  } else {
8276
- const legendShift = LEGEND_HEIGHT + LEGEND_GROUP_GAP;
8272
+ const legendShift = LEGEND_HEIGHT2 + LEGEND_GROUP_GAP2;
8277
8273
  for (const n of layoutNodes) n.y += legendShift;
8278
8274
  for (const c of containers) c.y += legendShift;
8279
8275
  for (const e of layoutEdges) {
8280
8276
  for (const p of e.points) p.y += legendShift;
8281
8277
  }
8282
- const totalGroupsWidth = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP;
8278
+ const totalGroupsWidth = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP2;
8283
8279
  let cx = MARGIN;
8284
8280
  for (const g of visibleGroups) {
8285
8281
  g.x = cx;
8286
8282
  g.y = MARGIN;
8287
- cx += effectiveW(g) + LEGEND_GROUP_GAP;
8283
+ cx += effectiveW(g) + LEGEND_GROUP_GAP2;
8288
8284
  }
8289
8285
  finalHeight += legendShift;
8290
8286
  const neededWidth = totalGroupsWidth + MARGIN * 2;
@@ -8302,7 +8298,7 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
8302
8298
  height: finalHeight
8303
8299
  };
8304
8300
  }
8305
- var CHAR_WIDTH, META_LINE_HEIGHT, HEADER_HEIGHT, SEPARATOR_GAP, CARD_H_PAD, CARD_V_PAD, MIN_CARD_WIDTH, H_GAP, V_GAP, MARGIN, CONTAINER_PAD_X, CONTAINER_PAD_BOTTOM, CONTAINER_LABEL_HEIGHT, CONTAINER_META_LINE_HEIGHT, STACK_V_GAP, LEGEND_GAP, LEGEND_HEIGHT, LEGEND_PILL_PAD, LEGEND_PILL_FONT_W, LEGEND_CAPSULE_PAD, LEGEND_DOT_R, LEGEND_ENTRY_FONT_W, LEGEND_ENTRY_DOT_GAP, LEGEND_ENTRY_TRAIL, LEGEND_GROUP_GAP, LEGEND_EYE_SIZE, LEGEND_EYE_GAP;
8301
+ var CHAR_WIDTH, META_LINE_HEIGHT, HEADER_HEIGHT, SEPARATOR_GAP, CARD_H_PAD, CARD_V_PAD, MIN_CARD_WIDTH, H_GAP, V_GAP, MARGIN, CONTAINER_PAD_X, CONTAINER_PAD_BOTTOM, CONTAINER_LABEL_HEIGHT, CONTAINER_META_LINE_HEIGHT, STACK_V_GAP, LEGEND_GAP, LEGEND_HEIGHT2, LEGEND_PILL_PAD2, LEGEND_PILL_FONT_W2, LEGEND_CAPSULE_PAD2, LEGEND_DOT_R2, LEGEND_ENTRY_FONT_W2, LEGEND_ENTRY_DOT_GAP2, LEGEND_ENTRY_TRAIL2, LEGEND_GROUP_GAP2, LEGEND_EYE_SIZE2, LEGEND_EYE_GAP2;
8306
8302
  var init_layout = __esm({
8307
8303
  "src/org/layout.ts"() {
8308
8304
  "use strict";
@@ -8323,17 +8319,17 @@ var init_layout = __esm({
8323
8319
  CONTAINER_META_LINE_HEIGHT = 16;
8324
8320
  STACK_V_GAP = 20;
8325
8321
  LEGEND_GAP = 30;
8326
- LEGEND_HEIGHT = 28;
8327
- LEGEND_PILL_PAD = 16;
8328
- LEGEND_PILL_FONT_W = 11 * 0.6;
8329
- LEGEND_CAPSULE_PAD = 4;
8330
- LEGEND_DOT_R = 4;
8331
- LEGEND_ENTRY_FONT_W = 10 * 0.6;
8332
- LEGEND_ENTRY_DOT_GAP = 4;
8333
- LEGEND_ENTRY_TRAIL = 8;
8334
- LEGEND_GROUP_GAP = 12;
8335
- LEGEND_EYE_SIZE = 14;
8336
- LEGEND_EYE_GAP = 6;
8322
+ LEGEND_HEIGHT2 = 28;
8323
+ LEGEND_PILL_PAD2 = 16;
8324
+ LEGEND_PILL_FONT_W2 = 11 * 0.6;
8325
+ LEGEND_CAPSULE_PAD2 = 4;
8326
+ LEGEND_DOT_R2 = 4;
8327
+ LEGEND_ENTRY_FONT_W2 = 10 * 0.6;
8328
+ LEGEND_ENTRY_DOT_GAP2 = 4;
8329
+ LEGEND_ENTRY_TRAIL2 = 8;
8330
+ LEGEND_GROUP_GAP2 = 12;
8331
+ LEGEND_EYE_SIZE2 = 14;
8332
+ LEGEND_EYE_GAP2 = 6;
8337
8333
  }
8338
8334
  });
8339
8335
 
@@ -8431,11 +8427,11 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
8431
8427
  if (width <= 0 || height <= 0) return;
8432
8428
  const titleOffset = parsed.title ? TITLE_HEIGHT : 0;
8433
8429
  const legendOnly = layout.nodes.length === 0;
8434
- const legendPosition = parsed.options?.["legend-position"] ?? "top";
8430
+ const legendPosition = parsed.options?.["legend-position"] ?? "bottom";
8435
8431
  const hasLegend = layout.legend.length > 0;
8436
- const layoutLegendShift = LEGEND_HEIGHT2 + LEGEND_GROUP_GAP2;
8432
+ const layoutLegendShift = LEGEND_HEIGHT + LEGEND_GROUP_GAP;
8437
8433
  const fixedLegend = !exportDims && hasLegend && !legendOnly;
8438
- const legendReserve = fixedLegend ? LEGEND_HEIGHT2 + LEGEND_FIXED_GAP : 0;
8434
+ const legendReserve = fixedLegend ? LEGEND_HEIGHT + LEGEND_FIXED_GAP : 0;
8439
8435
  const fixedTitle = !exportDims && !!parsed.title;
8440
8436
  const titleReserve = fixedTitle ? TITLE_HEIGHT : 0;
8441
8437
  const diagramW = layout.width;
@@ -8590,56 +8586,60 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
8590
8586
  if (fixedLegend && visibleGroups.length > 0) {
8591
8587
  fixedPositions = /* @__PURE__ */ new Map();
8592
8588
  const effectiveW = (g) => activeTagGroup != null ? g.width : g.minifiedWidth;
8593
- const totalW = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP2;
8589
+ const totalW = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP;
8594
8590
  let cx = (width - totalW) / 2;
8595
8591
  for (const g of visibleGroups) {
8596
8592
  fixedPositions.set(g.name, cx);
8597
- cx += effectiveW(g) + LEGEND_GROUP_GAP2;
8593
+ cx += effectiveW(g) + LEGEND_GROUP_GAP;
8598
8594
  }
8599
8595
  }
8600
- const legendParent = fixedLegend ? svg.append("g").attr("class", "org-legend-fixed").attr(
8596
+ const legendParentBase = fixedLegend ? svg.append("g").attr("class", "org-legend-fixed").attr(
8601
8597
  "transform",
8602
- legendPosition === "bottom" ? `translate(0, ${height - DIAGRAM_PADDING - LEGEND_HEIGHT2})` : `translate(0, ${DIAGRAM_PADDING + titleReserve})`
8598
+ legendPosition === "bottom" ? `translate(0, ${height - DIAGRAM_PADDING - LEGEND_HEIGHT})` : `translate(0, ${DIAGRAM_PADDING + titleReserve})`
8603
8599
  ) : contentG;
8600
+ const legendParent = legendParentBase;
8601
+ if (fixedLegend && activeTagGroup) {
8602
+ legendParentBase.attr("data-legend-active", activeTagGroup.toLowerCase());
8603
+ }
8604
8604
  for (const group of visibleGroups) {
8605
8605
  const isActive = legendOnly || activeTagGroup != null && group.name.toLowerCase() === activeTagGroup.toLowerCase();
8606
8606
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
8607
8607
  const pillLabel = group.name;
8608
- const pillWidth = pillLabel.length * LEGEND_PILL_FONT_W2 + LEGEND_PILL_PAD2;
8608
+ const pillWidth = pillLabel.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
8609
8609
  const gX = fixedPositions?.get(group.name) ?? group.x;
8610
8610
  const gY = fixedPositions ? 0 : group.y;
8611
8611
  const gEl = legendParent.append("g").attr("transform", `translate(${gX}, ${gY})`).attr("class", "org-legend-group").attr("data-legend-group", group.name.toLowerCase()).style("cursor", legendOnly ? "default" : "pointer");
8612
8612
  if (isActive) {
8613
- gEl.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT2).attr("rx", LEGEND_HEIGHT2 / 2).attr("fill", groupBg);
8613
+ gEl.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
8614
8614
  }
8615
- const pillXOff = isActive ? LEGEND_CAPSULE_PAD2 : 0;
8616
- const pillYOff = isActive ? LEGEND_CAPSULE_PAD2 : 0;
8617
- const pillH = LEGEND_HEIGHT2 - (isActive ? LEGEND_CAPSULE_PAD2 * 2 : 0);
8615
+ const pillXOff = isActive ? LEGEND_CAPSULE_PAD : 0;
8616
+ const pillYOff = isActive ? LEGEND_CAPSULE_PAD : 0;
8617
+ const pillH = LEGEND_HEIGHT - (isActive ? LEGEND_CAPSULE_PAD * 2 : 0);
8618
8618
  gEl.append("rect").attr("x", pillXOff).attr("y", pillYOff).attr("width", pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", isActive ? palette.bg : groupBg);
8619
8619
  if (isActive) {
8620
8620
  gEl.append("rect").attr("x", pillXOff).attr("y", pillYOff).attr("width", pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", "none").attr("stroke", mix(palette.textMuted, palette.bg, 50)).attr("stroke-width", 0.75);
8621
8621
  }
8622
- gEl.append("text").attr("x", pillXOff + pillWidth / 2).attr("y", LEGEND_HEIGHT2 / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", "500").attr("fill", isActive ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(pillLabel);
8622
+ gEl.append("text").attr("x", pillXOff + pillWidth / 2).attr("y", LEGEND_HEIGHT / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", "500").attr("fill", isActive ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(pillLabel);
8623
8623
  if (isActive && fixedLegend) {
8624
8624
  const groupKey = group.name.toLowerCase();
8625
8625
  const isHidden = hiddenAttributes?.has(groupKey) ?? false;
8626
- const eyeX = pillXOff + pillWidth + LEGEND_EYE_GAP2;
8627
- const eyeY = (LEGEND_HEIGHT2 - LEGEND_EYE_SIZE2) / 2;
8626
+ const eyeX = pillXOff + pillWidth + LEGEND_EYE_GAP;
8627
+ const eyeY = (LEGEND_HEIGHT - LEGEND_EYE_SIZE) / 2;
8628
8628
  const hitPad = 6;
8629
8629
  const eyeG = gEl.append("g").attr("class", "org-legend-eye").attr("data-legend-visibility", groupKey).style("cursor", "pointer").attr("opacity", isHidden ? 0.4 : 0.7);
8630
- eyeG.append("rect").attr("x", eyeX - hitPad).attr("y", eyeY - hitPad).attr("width", LEGEND_EYE_SIZE2 + hitPad * 2).attr("height", LEGEND_EYE_SIZE2 + hitPad * 2).attr("fill", "transparent").attr("pointer-events", "all");
8630
+ eyeG.append("rect").attr("x", eyeX - hitPad).attr("y", eyeY - hitPad).attr("width", LEGEND_EYE_SIZE + hitPad * 2).attr("height", LEGEND_EYE_SIZE + hitPad * 2).attr("fill", "transparent").attr("pointer-events", "all");
8631
8631
  eyeG.append("path").attr("d", isHidden ? EYE_CLOSED_PATH : EYE_OPEN_PATH).attr("transform", `translate(${eyeX}, ${eyeY})`).attr("fill", "none").attr("stroke", palette.textMuted).attr("stroke-width", 1.2).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
8632
8632
  }
8633
8633
  if (isActive) {
8634
- const eyeShift = fixedLegend ? LEGEND_EYE_SIZE2 + LEGEND_EYE_GAP2 : 0;
8634
+ const eyeShift = fixedLegend ? LEGEND_EYE_SIZE + LEGEND_EYE_GAP : 0;
8635
8635
  let entryX = pillXOff + pillWidth + 4 + eyeShift;
8636
8636
  for (const entry of group.entries) {
8637
8637
  const entryG = gEl.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
8638
- entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R2).attr("cy", LEGEND_HEIGHT2 / 2).attr("r", LEGEND_DOT_R2).attr("fill", entry.color);
8639
- const textX = entryX + LEGEND_DOT_R2 * 2 + LEGEND_ENTRY_DOT_GAP2;
8638
+ entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
8639
+ const textX = entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
8640
8640
  const entryLabel = entry.value;
8641
- entryG.append("text").attr("x", textX).attr("y", LEGEND_HEIGHT2 / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1).attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).text(entryLabel);
8642
- entryX = textX + entryLabel.length * LEGEND_ENTRY_FONT_W2 + LEGEND_ENTRY_TRAIL2;
8641
+ entryG.append("text").attr("x", textX).attr("y", LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1).attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).text(entryLabel);
8642
+ entryX = textX + entryLabel.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
8643
8643
  }
8644
8644
  }
8645
8645
  }
@@ -8678,7 +8678,7 @@ function renderOrgForExport(content, theme, palette) {
8678
8678
  document.body.removeChild(container);
8679
8679
  }
8680
8680
  }
8681
- var DIAGRAM_PADDING, MAX_SCALE, TITLE_HEIGHT, TITLE_FONT_SIZE, LABEL_FONT_SIZE, META_FONT_SIZE, META_LINE_HEIGHT2, HEADER_HEIGHT2, SEPARATOR_GAP2, EDGE_STROKE_WIDTH, NODE_STROKE_WIDTH, CARD_RADIUS, CONTAINER_RADIUS, CONTAINER_LABEL_FONT_SIZE, CONTAINER_META_FONT_SIZE, CONTAINER_META_LINE_HEIGHT2, CONTAINER_HEADER_HEIGHT, COLLAPSE_BAR_HEIGHT, COLLAPSE_BAR_INSET, LEGEND_HEIGHT2, LEGEND_PILL_PAD2, LEGEND_PILL_FONT_SIZE, LEGEND_PILL_FONT_W2, LEGEND_CAPSULE_PAD2, LEGEND_DOT_R2, LEGEND_ENTRY_FONT_SIZE, LEGEND_ENTRY_FONT_W2, LEGEND_ENTRY_DOT_GAP2, LEGEND_ENTRY_TRAIL2, LEGEND_GROUP_GAP2, LEGEND_EYE_SIZE2, LEGEND_EYE_GAP2, LEGEND_FIXED_GAP, EYE_OPEN_PATH, EYE_CLOSED_PATH;
8681
+ var DIAGRAM_PADDING, MAX_SCALE, TITLE_HEIGHT, TITLE_FONT_SIZE, LABEL_FONT_SIZE, META_FONT_SIZE, META_LINE_HEIGHT2, HEADER_HEIGHT2, SEPARATOR_GAP2, EDGE_STROKE_WIDTH, NODE_STROKE_WIDTH, CARD_RADIUS, CONTAINER_RADIUS, CONTAINER_LABEL_FONT_SIZE, CONTAINER_META_FONT_SIZE, CONTAINER_META_LINE_HEIGHT2, CONTAINER_HEADER_HEIGHT, COLLAPSE_BAR_HEIGHT, COLLAPSE_BAR_INSET, LEGEND_FIXED_GAP;
8682
8682
  var init_renderer = __esm({
8683
8683
  "src/org/renderer.ts"() {
8684
8684
  "use strict";
@@ -8686,6 +8686,7 @@ var init_renderer = __esm({
8686
8686
  init_color_utils();
8687
8687
  init_parser4();
8688
8688
  init_layout();
8689
+ init_legend_constants();
8689
8690
  DIAGRAM_PADDING = 20;
8690
8691
  MAX_SCALE = 3;
8691
8692
  TITLE_HEIGHT = 30;
@@ -8705,22 +8706,7 @@ var init_renderer = __esm({
8705
8706
  CONTAINER_HEADER_HEIGHT = 28;
8706
8707
  COLLAPSE_BAR_HEIGHT = 6;
8707
8708
  COLLAPSE_BAR_INSET = 0;
8708
- LEGEND_HEIGHT2 = 28;
8709
- LEGEND_PILL_PAD2 = 16;
8710
- LEGEND_PILL_FONT_SIZE = 11;
8711
- LEGEND_PILL_FONT_W2 = LEGEND_PILL_FONT_SIZE * 0.6;
8712
- LEGEND_CAPSULE_PAD2 = 4;
8713
- LEGEND_DOT_R2 = 4;
8714
- LEGEND_ENTRY_FONT_SIZE = 10;
8715
- LEGEND_ENTRY_FONT_W2 = LEGEND_ENTRY_FONT_SIZE * 0.6;
8716
- LEGEND_ENTRY_DOT_GAP2 = 4;
8717
- LEGEND_ENTRY_TRAIL2 = 8;
8718
- LEGEND_GROUP_GAP2 = 12;
8719
- LEGEND_EYE_SIZE2 = 14;
8720
- LEGEND_EYE_GAP2 = 6;
8721
8709
  LEGEND_FIXED_GAP = 8;
8722
- EYE_OPEN_PATH = "M1 7s2.5-5 6-5 6 5 6 5-2.5 5-6 5-6-5-6-5z M7 9.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z";
8723
- EYE_CLOSED_PATH = "M2.5 2.5l9 9 M1.5 7s2.2-4 5.5-4c1.2 0 2.2.5 3 1.1 M12.5 7s-2.2 4-5.5 4c-1.2 0-2.2-.5-3-1.1";
8724
8710
  }
8725
8711
  });
8726
8712
 
@@ -9145,23 +9131,17 @@ function layoutSitemap(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, e
9145
9131
  const effectiveW = (g2) => activeTagGroup != null || allExpanded ? g2.width : g2.minifiedWidth;
9146
9132
  if (visibleGroups.length > 0) {
9147
9133
  const legendShift = LEGEND_HEIGHT3 + LEGEND_GROUP_GAP3;
9148
- for (const n of layoutNodes) n.y += legendShift;
9149
- for (const c of layoutContainers) c.y += legendShift;
9150
- for (const e of layoutEdges) {
9151
- for (const p of e.points) p.y += legendShift;
9152
- }
9153
9134
  const totalGroupsWidth = visibleGroups.reduce((s, g2) => s + effectiveW(g2), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP3;
9154
- let cx = MARGIN2;
9135
+ const neededWidth = totalGroupsWidth + MARGIN2 * 2;
9136
+ if (neededWidth > totalWidth) totalWidth = neededWidth;
9137
+ let cx = (totalWidth - totalGroupsWidth) / 2;
9138
+ const legendY = totalHeight + LEGEND_GROUP_GAP3;
9155
9139
  for (const g2 of visibleGroups) {
9156
9140
  g2.x = cx;
9157
- g2.y = MARGIN2;
9141
+ g2.y = legendY;
9158
9142
  cx += effectiveW(g2) + LEGEND_GROUP_GAP3;
9159
9143
  }
9160
9144
  totalHeight += legendShift;
9161
- const neededWidth = totalGroupsWidth + MARGIN2 * 2;
9162
- if (neededWidth > totalWidth) {
9163
- totalWidth = neededWidth;
9164
- }
9165
9145
  }
9166
9146
  return {
9167
9147
  nodes: layoutNodes,
@@ -9356,25 +9336,26 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
9356
9336
  const height = exportDims?.height ?? container.clientHeight;
9357
9337
  if (width <= 0 || height <= 0) return;
9358
9338
  const hasLegend = layout.legend.length > 0;
9359
- const layoutLegendShift = LEGEND_HEIGHT4 + LEGEND_GROUP_GAP4;
9339
+ const layoutLegendShift = LEGEND_HEIGHT + LEGEND_GROUP_GAP;
9360
9340
  const fixedLegend = !exportDims && hasLegend;
9361
9341
  const fixedTitle = fixedLegend && !!parsed.title;
9362
9342
  const fixedTitleH = fixedTitle ? TITLE_HEIGHT2 : 0;
9363
- const legendReserveH = fixedLegend ? LEGEND_HEIGHT4 + LEGEND_FIXED_GAP2 : 0;
9364
- const fixedReserve = fixedTitleH + legendReserveH;
9343
+ const legendReserveH = fixedLegend ? LEGEND_HEIGHT + LEGEND_FIXED_GAP2 : 0;
9344
+ const fixedReserveTop = fixedTitleH;
9345
+ const fixedReserveBottom = legendReserveH;
9365
9346
  const titleOffset = !fixedTitle && parsed.title ? TITLE_HEIGHT2 : 0;
9366
9347
  const diagramW = layout.width;
9367
9348
  let diagramH = layout.height + titleOffset;
9368
9349
  if (fixedLegend) {
9369
9350
  diagramH -= layoutLegendShift;
9370
9351
  }
9371
- const availH = height - DIAGRAM_PADDING2 * 2 - fixedReserve;
9352
+ const availH = height - DIAGRAM_PADDING2 * 2 - fixedReserveTop - fixedReserveBottom;
9372
9353
  const scaleX = (width - DIAGRAM_PADDING2 * 2) / diagramW;
9373
9354
  const scaleY = availH / diagramH;
9374
9355
  const scale = Math.min(MAX_SCALE2, scaleX, scaleY);
9375
9356
  const scaledW = diagramW * scale;
9376
9357
  const offsetX = (width - scaledW) / 2;
9377
- const offsetY = DIAGRAM_PADDING2 + fixedReserve;
9358
+ const offsetY = DIAGRAM_PADDING2 + fixedReserveTop;
9378
9359
  const svg = d3Selection2.select(container).append("svg").attr("width", width).attr("height", height).style("font-family", FONT_FAMILY);
9379
9360
  const defs = svg.append("defs");
9380
9361
  defs.append("marker").attr("id", "sm-arrow").attr("viewBox", `0 0 ${ARROWHEAD_W} ${ARROWHEAD_H}`).attr("refX", ARROWHEAD_W).attr("refY", ARROWHEAD_H / 2).attr("markerWidth", ARROWHEAD_W).attr("markerHeight", ARROWHEAD_H).attr("orient", "auto").append("polygon").attr("points", `0,0 ${ARROWHEAD_W},${ARROWHEAD_H / 2} 0,${ARROWHEAD_H}`).attr("fill", palette.textMuted);
@@ -9515,7 +9496,10 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
9515
9496
  titleEl.text(parsed.title);
9516
9497
  }
9517
9498
  if (fixedLegend) {
9518
- const legendParent = svg.append("g").attr("class", "sitemap-legend-fixed").attr("transform", `translate(0, ${DIAGRAM_PADDING2 + fixedTitleH})`);
9499
+ const legendParent = svg.append("g").attr("class", "sitemap-legend-fixed").attr("transform", `translate(0, ${height - DIAGRAM_PADDING2 - LEGEND_HEIGHT})`);
9500
+ if (activeTagGroup) {
9501
+ legendParent.attr("data-legend-active", activeTagGroup.toLowerCase());
9502
+ }
9519
9503
  renderLegend(legendParent, layout.legend, palette, isDark, activeTagGroup, width, hiddenAttributes);
9520
9504
  }
9521
9505
  }
@@ -9527,49 +9511,49 @@ function renderLegend(parent, legendGroups, palette, isDark, activeTagGroup, fix
9527
9511
  if (fixedWidth != null && visibleGroups.length > 0) {
9528
9512
  fixedPositions = /* @__PURE__ */ new Map();
9529
9513
  const effectiveW = (g) => activeTagGroup != null ? g.width : g.minifiedWidth;
9530
- const totalW = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP4;
9514
+ const totalW = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP;
9531
9515
  let cx = (fixedWidth - totalW) / 2;
9532
9516
  for (const g of visibleGroups) {
9533
9517
  fixedPositions.set(g.name, cx);
9534
- cx += effectiveW(g) + LEGEND_GROUP_GAP4;
9518
+ cx += effectiveW(g) + LEGEND_GROUP_GAP;
9535
9519
  }
9536
9520
  }
9537
9521
  for (const group of visibleGroups) {
9538
9522
  const isActive = activeTagGroup != null;
9539
- const pillW = group.name.length * LEGEND_PILL_FONT_W4 + LEGEND_PILL_PAD4;
9523
+ const pillW = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
9540
9524
  const gX = fixedPositions?.get(group.name) ?? group.x;
9541
9525
  const gY = fixedPositions ? 0 : group.y;
9542
9526
  const legendG = parent.append("g").attr("transform", `translate(${gX}, ${gY})`).attr("class", "sitemap-legend-group").attr("data-legend-group", group.name.toLowerCase()).style("cursor", "pointer");
9543
9527
  if (isActive) {
9544
- legendG.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT4).attr("rx", LEGEND_HEIGHT4 / 2).attr("fill", groupBg);
9528
+ legendG.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
9545
9529
  }
9546
- const pillXOff = isActive ? LEGEND_CAPSULE_PAD4 : 0;
9547
- const pillYOff = isActive ? LEGEND_CAPSULE_PAD4 : 0;
9548
- const pillH = LEGEND_HEIGHT4 - (isActive ? LEGEND_CAPSULE_PAD4 * 2 : 0);
9530
+ const pillXOff = isActive ? LEGEND_CAPSULE_PAD : 0;
9531
+ const pillYOff = isActive ? LEGEND_CAPSULE_PAD : 0;
9532
+ const pillH = LEGEND_HEIGHT - (isActive ? LEGEND_CAPSULE_PAD * 2 : 0);
9549
9533
  legendG.append("rect").attr("x", pillXOff).attr("y", pillYOff).attr("width", pillW).attr("height", pillH).attr("rx", pillH / 2).attr("fill", isActive ? palette.bg : groupBg);
9550
9534
  if (isActive) {
9551
9535
  legendG.append("rect").attr("x", pillXOff).attr("y", pillYOff).attr("width", pillW).attr("height", pillH).attr("rx", pillH / 2).attr("fill", "none").attr("stroke", mix(palette.textMuted, palette.bg, 50)).attr("stroke-width", 0.75);
9552
9536
  }
9553
- legendG.append("text").attr("x", pillXOff + pillW / 2).attr("y", LEGEND_HEIGHT4 / 2 + LEGEND_PILL_FONT_SIZE2 / 2 - 2).attr("font-size", LEGEND_PILL_FONT_SIZE2).attr("font-weight", "500").attr("fill", isActive ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(group.name);
9537
+ legendG.append("text").attr("x", pillXOff + pillW / 2).attr("y", LEGEND_HEIGHT / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", "500").attr("fill", isActive ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(group.name);
9554
9538
  if (isActive && fixedWidth != null) {
9555
9539
  const groupKey = group.name.toLowerCase();
9556
9540
  const isHidden = hiddenAttributes?.has(groupKey) ?? false;
9557
- const eyeX = pillXOff + pillW + LEGEND_EYE_GAP4;
9558
- const eyeY = (LEGEND_HEIGHT4 - LEGEND_EYE_SIZE4) / 2;
9541
+ const eyeX = pillXOff + pillW + LEGEND_EYE_GAP;
9542
+ const eyeY = (LEGEND_HEIGHT - LEGEND_EYE_SIZE) / 2;
9559
9543
  const hitPad = 6;
9560
9544
  const eyeG = legendG.append("g").attr("class", "sitemap-legend-eye").attr("data-legend-visibility", groupKey).style("cursor", "pointer").attr("opacity", isHidden ? 0.4 : 0.7);
9561
- eyeG.append("rect").attr("x", eyeX - hitPad).attr("y", eyeY - hitPad).attr("width", LEGEND_EYE_SIZE4 + hitPad * 2).attr("height", LEGEND_EYE_SIZE4 + hitPad * 2).attr("fill", "transparent").attr("pointer-events", "all");
9562
- eyeG.append("path").attr("d", isHidden ? EYE_CLOSED_PATH2 : EYE_OPEN_PATH2).attr("transform", `translate(${eyeX}, ${eyeY})`).attr("fill", "none").attr("stroke", palette.textMuted).attr("stroke-width", 1.2).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
9545
+ eyeG.append("rect").attr("x", eyeX - hitPad).attr("y", eyeY - hitPad).attr("width", LEGEND_EYE_SIZE + hitPad * 2).attr("height", LEGEND_EYE_SIZE + hitPad * 2).attr("fill", "transparent").attr("pointer-events", "all");
9546
+ eyeG.append("path").attr("d", isHidden ? EYE_CLOSED_PATH : EYE_OPEN_PATH).attr("transform", `translate(${eyeX}, ${eyeY})`).attr("fill", "none").attr("stroke", palette.textMuted).attr("stroke-width", 1.2).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
9563
9547
  }
9564
9548
  if (isActive) {
9565
- const eyeShift = fixedWidth != null ? LEGEND_EYE_SIZE4 + LEGEND_EYE_GAP4 : 0;
9549
+ const eyeShift = fixedWidth != null ? LEGEND_EYE_SIZE + LEGEND_EYE_GAP : 0;
9566
9550
  let entryX = pillXOff + pillW + 4 + eyeShift;
9567
9551
  for (const entry of group.entries) {
9568
9552
  const entryG = legendG.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
9569
- entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R4).attr("cy", LEGEND_HEIGHT4 / 2).attr("r", LEGEND_DOT_R4).attr("fill", entry.color);
9570
- const textX = entryX + LEGEND_DOT_R4 * 2 + LEGEND_ENTRY_DOT_GAP4;
9571
- entryG.append("text").attr("x", textX).attr("y", LEGEND_HEIGHT4 / 2 + LEGEND_ENTRY_FONT_SIZE2 / 2 - 1).attr("font-size", LEGEND_ENTRY_FONT_SIZE2).attr("fill", palette.textMuted).text(entry.value);
9572
- entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W4 + LEGEND_ENTRY_TRAIL4;
9553
+ entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
9554
+ const textX = entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
9555
+ entryG.append("text").attr("x", textX).attr("y", LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1).attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).text(entry.value);
9556
+ entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
9573
9557
  }
9574
9558
  }
9575
9559
  }
@@ -9615,12 +9599,13 @@ async function renderSitemapForExport(content, theme, palette) {
9615
9599
  const brandColor = theme === "transparent" ? "#888" : effectivePalette.textMuted;
9616
9600
  return injectBranding2(svgHtml, brandColor);
9617
9601
  }
9618
- var DIAGRAM_PADDING2, MAX_SCALE2, TITLE_HEIGHT2, TITLE_FONT_SIZE2, LABEL_FONT_SIZE2, META_FONT_SIZE2, META_LINE_HEIGHT4, HEADER_HEIGHT4, SEPARATOR_GAP4, EDGE_STROKE_WIDTH2, NODE_STROKE_WIDTH2, CARD_RADIUS2, CONTAINER_RADIUS2, CONTAINER_LABEL_FONT_SIZE2, CONTAINER_META_FONT_SIZE2, CONTAINER_META_LINE_HEIGHT4, CONTAINER_HEADER_HEIGHT2, ARROWHEAD_W, ARROWHEAD_H, EDGE_LABEL_FONT_SIZE, COLLAPSE_BAR_HEIGHT2, LEGEND_HEIGHT4, LEGEND_FIXED_GAP2, LEGEND_PILL_PAD4, LEGEND_PILL_FONT_SIZE2, LEGEND_PILL_FONT_W4, LEGEND_CAPSULE_PAD4, LEGEND_DOT_R4, LEGEND_ENTRY_FONT_SIZE2, LEGEND_ENTRY_FONT_W4, LEGEND_ENTRY_DOT_GAP4, LEGEND_ENTRY_TRAIL4, LEGEND_GROUP_GAP4, LEGEND_EYE_SIZE4, LEGEND_EYE_GAP4, lineGenerator, EYE_OPEN_PATH2, EYE_CLOSED_PATH2;
9602
+ var DIAGRAM_PADDING2, MAX_SCALE2, TITLE_HEIGHT2, TITLE_FONT_SIZE2, LABEL_FONT_SIZE2, META_FONT_SIZE2, META_LINE_HEIGHT4, HEADER_HEIGHT4, SEPARATOR_GAP4, EDGE_STROKE_WIDTH2, NODE_STROKE_WIDTH2, CARD_RADIUS2, CONTAINER_RADIUS2, CONTAINER_LABEL_FONT_SIZE2, CONTAINER_META_FONT_SIZE2, CONTAINER_META_LINE_HEIGHT4, CONTAINER_HEADER_HEIGHT2, ARROWHEAD_W, ARROWHEAD_H, EDGE_LABEL_FONT_SIZE, COLLAPSE_BAR_HEIGHT2, LEGEND_FIXED_GAP2, lineGenerator;
9619
9603
  var init_renderer2 = __esm({
9620
9604
  "src/sitemap/renderer.ts"() {
9621
9605
  "use strict";
9622
9606
  init_fonts();
9623
9607
  init_color_utils();
9608
+ init_legend_constants();
9624
9609
  DIAGRAM_PADDING2 = 20;
9625
9610
  MAX_SCALE2 = 3;
9626
9611
  TITLE_HEIGHT2 = 30;
@@ -9642,23 +9627,8 @@ var init_renderer2 = __esm({
9642
9627
  ARROWHEAD_H = 7;
9643
9628
  EDGE_LABEL_FONT_SIZE = 11;
9644
9629
  COLLAPSE_BAR_HEIGHT2 = 6;
9645
- LEGEND_HEIGHT4 = 28;
9646
9630
  LEGEND_FIXED_GAP2 = 8;
9647
- LEGEND_PILL_PAD4 = 16;
9648
- LEGEND_PILL_FONT_SIZE2 = 11;
9649
- LEGEND_PILL_FONT_W4 = LEGEND_PILL_FONT_SIZE2 * 0.6;
9650
- LEGEND_CAPSULE_PAD4 = 4;
9651
- LEGEND_DOT_R4 = 4;
9652
- LEGEND_ENTRY_FONT_SIZE2 = 10;
9653
- LEGEND_ENTRY_FONT_W4 = LEGEND_ENTRY_FONT_SIZE2 * 0.6;
9654
- LEGEND_ENTRY_DOT_GAP4 = 4;
9655
- LEGEND_ENTRY_TRAIL4 = 8;
9656
- LEGEND_GROUP_GAP4 = 12;
9657
- LEGEND_EYE_SIZE4 = 14;
9658
- LEGEND_EYE_GAP4 = 6;
9659
9631
  lineGenerator = d3Shape.line().x((d) => d.x).y((d) => d.y).curve(d3Shape.curveBasis);
9660
- EYE_OPEN_PATH2 = "M1 7s2.5-5 6-5 6 5 6 5-2.5 5-6 5-6-5-6-5z M7 9.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z";
9661
- EYE_CLOSED_PATH2 = "M2.5 2.5l9 9 M1.5 7s2.2-4 5.5-4c1.2 0 2.2.5 3 1.1 M12.5 7s-2.2 4-5.5 4c-1.2 0-2.2-.5-3-1.1";
9662
9632
  }
9663
9633
  });
9664
9634
 
@@ -9861,8 +9831,7 @@ function resolveCardTagColor(card, tagGroups, activeTagGroup) {
9861
9831
  return entry?.color;
9862
9832
  }
9863
9833
  function computeLayout(parsed, _palette) {
9864
- const hasHeader = !!parsed.title || parsed.tagGroups.length > 0;
9865
- const headerHeight = hasHeader ? Math.max(TITLE_HEIGHT3, LEGEND_HEIGHT5) + 8 : 0;
9834
+ const headerHeight = parsed.title ? TITLE_HEIGHT3 + 8 : 0;
9866
9835
  const startY = DIAGRAM_PADDING3 + headerHeight;
9867
9836
  const charWidth = CARD_TITLE_FONT_SIZE * 0.6;
9868
9837
  const columnLayouts = [];
@@ -9919,7 +9888,8 @@ function computeLayout(parsed, _palette) {
9919
9888
  currentX += cl.width + COLUMN_GAP;
9920
9889
  }
9921
9890
  const totalWidth = currentX - COLUMN_GAP + DIAGRAM_PADDING3;
9922
- const totalHeight = startY + maxColumnHeight + DIAGRAM_PADDING3;
9891
+ const legendSpace = parsed.tagGroups.length > 0 ? LEGEND_HEIGHT : 0;
9892
+ const totalHeight = startY + maxColumnHeight + DIAGRAM_PADDING3 + legendSpace;
9923
9893
  return { columns: columnLayouts, totalWidth, totalHeight };
9924
9894
  }
9925
9895
  function renderKanban(container, parsed, palette, isDark, _onNavigateToLine, exportDims, activeTagGroup) {
@@ -9932,42 +9902,45 @@ function renderKanban(container, parsed, palette, isDark, _onNavigateToLine, exp
9932
9902
  svg.append("text").attr("class", "chart-title").attr("data-line-number", parsed.titleLineNumber ?? 0).attr("x", DIAGRAM_PADDING3).attr("y", DIAGRAM_PADDING3 + TITLE_FONT_SIZE3).attr("font-size", TITLE_FONT_SIZE3).attr("font-weight", "bold").attr("fill", palette.text).text(parsed.title);
9933
9903
  }
9934
9904
  if (parsed.tagGroups.length > 0) {
9935
- const legendY = DIAGRAM_PADDING3;
9936
- const titleTextWidth = parsed.title ? parsed.title.length * TITLE_FONT_SIZE3 * 0.6 + 16 : 0;
9937
- let legendX = DIAGRAM_PADDING3 + titleTextWidth;
9905
+ const legendY = height - LEGEND_HEIGHT;
9906
+ let legendX = DIAGRAM_PADDING3;
9938
9907
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
9939
- const capsulePad = 4;
9908
+ const capsulePad = LEGEND_CAPSULE_PAD;
9909
+ const legendContainer = svg.append("g").attr("class", "kanban-legend");
9910
+ if (activeTagGroup) {
9911
+ legendContainer.attr("data-legend-active", activeTagGroup.toLowerCase());
9912
+ }
9940
9913
  for (const group of parsed.tagGroups) {
9941
9914
  const isActive = activeTagGroup?.toLowerCase() === group.name.toLowerCase();
9942
9915
  if (activeTagGroup != null && !isActive) continue;
9943
- const pillTextWidth = group.name.length * LEGEND_FONT_SIZE * 0.6;
9916
+ const pillTextWidth = group.name.length * LEGEND_PILL_FONT_SIZE * 0.6;
9944
9917
  const pillWidth = pillTextWidth + 16;
9945
9918
  let capsuleContentWidth = pillWidth;
9946
9919
  if (isActive) {
9947
9920
  capsuleContentWidth += 4;
9948
9921
  for (const entry of group.entries) {
9949
- capsuleContentWidth += LEGEND_DOT_R5 * 2 + 4 + entry.value.length * LEGEND_ENTRY_FONT_SIZE3 * 0.6 + 8;
9922
+ capsuleContentWidth += LEGEND_DOT_R * 2 + 4 + entry.value.length * LEGEND_ENTRY_FONT_SIZE * 0.6 + 8;
9950
9923
  }
9951
9924
  }
9952
9925
  const capsuleWidth = capsuleContentWidth + capsulePad * 2;
9953
9926
  if (isActive) {
9954
- svg.append("rect").attr("x", legendX).attr("y", legendY).attr("width", capsuleWidth).attr("height", LEGEND_HEIGHT5).attr("rx", LEGEND_HEIGHT5 / 2).attr("fill", groupBg);
9927
+ legendContainer.append("rect").attr("x", legendX).attr("y", legendY).attr("width", capsuleWidth).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
9955
9928
  }
9956
9929
  const pillX = legendX + (isActive ? capsulePad : 0);
9957
9930
  const pillBg = isActive ? palette.bg : groupBg;
9958
- svg.append("rect").attr("x", pillX).attr("y", legendY + (isActive ? capsulePad : 0)).attr("width", pillWidth).attr("height", LEGEND_HEIGHT5 - (isActive ? capsulePad * 2 : 0)).attr("rx", (LEGEND_HEIGHT5 - (isActive ? capsulePad * 2 : 0)) / 2).attr("fill", pillBg).attr("class", "kanban-legend-group").attr("data-legend-group", group.name.toLowerCase());
9931
+ legendContainer.append("rect").attr("x", pillX).attr("y", legendY + (isActive ? capsulePad : 0)).attr("width", pillWidth).attr("height", LEGEND_HEIGHT - (isActive ? capsulePad * 2 : 0)).attr("rx", (LEGEND_HEIGHT - (isActive ? capsulePad * 2 : 0)) / 2).attr("fill", pillBg).attr("class", "kanban-legend-group").attr("data-legend-group", group.name.toLowerCase());
9959
9932
  if (isActive) {
9960
- svg.append("rect").attr("x", pillX).attr("y", legendY + capsulePad).attr("width", pillWidth).attr("height", LEGEND_HEIGHT5 - capsulePad * 2).attr("rx", (LEGEND_HEIGHT5 - capsulePad * 2) / 2).attr("fill", "none").attr("stroke", mix(palette.textMuted, palette.bg, 50)).attr("stroke-width", 0.75);
9933
+ legendContainer.append("rect").attr("x", pillX).attr("y", legendY + capsulePad).attr("width", pillWidth).attr("height", LEGEND_HEIGHT - capsulePad * 2).attr("rx", (LEGEND_HEIGHT - capsulePad * 2) / 2).attr("fill", "none").attr("stroke", mix(palette.textMuted, palette.bg, 50)).attr("stroke-width", 0.75);
9961
9934
  }
9962
- svg.append("text").attr("x", pillX + pillWidth / 2).attr("y", legendY + LEGEND_HEIGHT5 / 2 + LEGEND_FONT_SIZE / 2 - 2).attr("font-size", LEGEND_FONT_SIZE).attr("font-weight", "500").attr("fill", isActive ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(group.name);
9935
+ legendContainer.append("text").attr("x", pillX + pillWidth / 2).attr("y", legendY + LEGEND_HEIGHT / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", "500").attr("fill", isActive ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(group.name);
9963
9936
  if (isActive) {
9964
9937
  let entryX = pillX + pillWidth + 4;
9965
9938
  for (const entry of group.entries) {
9966
- const entryG = svg.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
9967
- entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R5).attr("cy", legendY + LEGEND_HEIGHT5 / 2).attr("r", LEGEND_DOT_R5).attr("fill", entry.color);
9968
- const entryTextX = entryX + LEGEND_DOT_R5 * 2 + 4;
9969
- entryG.append("text").attr("x", entryTextX).attr("y", legendY + LEGEND_HEIGHT5 / 2 + LEGEND_ENTRY_FONT_SIZE3 / 2 - 1).attr("font-size", LEGEND_ENTRY_FONT_SIZE3).attr("fill", palette.textMuted).text(entry.value);
9970
- entryX = entryTextX + entry.value.length * LEGEND_ENTRY_FONT_SIZE3 * 0.6 + 8;
9939
+ const entryG = legendContainer.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
9940
+ entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R).attr("cy", legendY + LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
9941
+ const entryTextX = entryX + LEGEND_DOT_R * 2 + 4;
9942
+ entryG.append("text").attr("x", entryTextX).attr("y", legendY + LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1).attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).text(entry.value);
9943
+ entryX = entryTextX + entry.value.length * LEGEND_ENTRY_FONT_SIZE * 0.6 + 8;
9971
9944
  }
9972
9945
  legendX += capsuleWidth + 12;
9973
9946
  } else {
@@ -10054,7 +10027,7 @@ function renderKanbanForExport(content, theme, palette) {
10054
10027
  const svgEl = container.querySelector("svg");
10055
10028
  return svgEl?.outerHTML ?? "";
10056
10029
  }
10057
- var DIAGRAM_PADDING3, COLUMN_GAP, COLUMN_HEADER_HEIGHT, COLUMN_PADDING, COLUMN_MIN_WIDTH, CARD_HEADER_HEIGHT, CARD_META_LINE_HEIGHT, CARD_SEPARATOR_GAP, CARD_GAP, CARD_RADIUS3, CARD_PADDING_X, CARD_PADDING_Y, CARD_STROKE_WIDTH, TITLE_HEIGHT3, TITLE_FONT_SIZE3, COLUMN_HEADER_FONT_SIZE, CARD_TITLE_FONT_SIZE, CARD_META_FONT_SIZE, WIP_FONT_SIZE, COLUMN_RADIUS, COLUMN_HEADER_RADIUS, LEGEND_HEIGHT5, LEGEND_FONT_SIZE, LEGEND_DOT_R5, LEGEND_ENTRY_FONT_SIZE3;
10030
+ var DIAGRAM_PADDING3, COLUMN_GAP, COLUMN_HEADER_HEIGHT, COLUMN_PADDING, COLUMN_MIN_WIDTH, CARD_HEADER_HEIGHT, CARD_META_LINE_HEIGHT, CARD_SEPARATOR_GAP, CARD_GAP, CARD_RADIUS3, CARD_PADDING_X, CARD_PADDING_Y, CARD_STROKE_WIDTH, TITLE_HEIGHT3, TITLE_FONT_SIZE3, COLUMN_HEADER_FONT_SIZE, CARD_TITLE_FONT_SIZE, CARD_META_FONT_SIZE, WIP_FONT_SIZE, COLUMN_RADIUS, COLUMN_HEADER_RADIUS;
10058
10031
  var init_renderer3 = __esm({
10059
10032
  "src/kanban/renderer.ts"() {
10060
10033
  "use strict";
@@ -10063,6 +10036,7 @@ var init_renderer3 = __esm({
10063
10036
  init_inline_markdown();
10064
10037
  init_parser5();
10065
10038
  init_mutations();
10039
+ init_legend_constants();
10066
10040
  DIAGRAM_PADDING3 = 20;
10067
10041
  COLUMN_GAP = 16;
10068
10042
  COLUMN_HEADER_HEIGHT = 36;
@@ -10084,10 +10058,6 @@ var init_renderer3 = __esm({
10084
10058
  WIP_FONT_SIZE = 10;
10085
10059
  COLUMN_RADIUS = 8;
10086
10060
  COLUMN_HEADER_RADIUS = 8;
10087
- LEGEND_HEIGHT5 = 28;
10088
- LEGEND_FONT_SIZE = 11;
10089
- LEGEND_DOT_R5 = 4;
10090
- LEGEND_ENTRY_FONT_SIZE3 = 10;
10091
10061
  }
10092
10062
  });
10093
10063
 
@@ -10803,32 +10773,31 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10803
10773
  }
10804
10774
  }
10805
10775
  if (parsed.tagGroups.length > 0) {
10806
- const LEGEND_Y_PAD = 16;
10807
- const LEGEND_PILL_H = 22;
10808
- const LEGEND_PILL_RX = 11;
10809
- const LEGEND_PILL_PAD9 = 10;
10776
+ const LEGEND_PILL_H = LEGEND_HEIGHT - 6;
10777
+ const LEGEND_PILL_RX = Math.floor(LEGEND_PILL_H / 2);
10810
10778
  const LEGEND_GAP2 = 8;
10811
- const LEGEND_FONT_SIZE2 = 11;
10812
- const LEGEND_GROUP_GAP7 = 16;
10813
10779
  const legendG = svg.append("g").attr("class", "er-tag-legend");
10780
+ if (activeTagGroup) {
10781
+ legendG.attr("data-legend-active", activeTagGroup.toLowerCase());
10782
+ }
10814
10783
  let legendX = DIAGRAM_PADDING5;
10815
10784
  let legendY = height - DIAGRAM_PADDING5;
10816
10785
  for (const group of parsed.tagGroups) {
10817
10786
  const groupG = legendG.append("g").attr("data-legend-group", group.name.toLowerCase());
10818
- const labelText = groupG.append("text").attr("x", legendX).attr("y", legendY + LEGEND_PILL_H / 2).attr("dominant-baseline", "central").attr("fill", palette.textMuted).attr("font-size", LEGEND_FONT_SIZE2).attr("font-family", FONT_FAMILY).text(`${group.name}:`);
10787
+ const labelText = groupG.append("text").attr("x", legendX).attr("y", legendY + LEGEND_PILL_H / 2).attr("dominant-baseline", "central").attr("fill", palette.textMuted).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-family", FONT_FAMILY).text(`${group.name}:`);
10819
10788
  const labelWidth = (labelText.node()?.getComputedTextLength?.() ?? group.name.length * 7) + 6;
10820
10789
  legendX += labelWidth;
10821
10790
  for (const entry of group.entries) {
10822
10791
  const pillG = groupG.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
10823
- const tmpText = legendG.append("text").attr("font-size", LEGEND_FONT_SIZE2).attr("font-family", FONT_FAMILY).text(entry.value);
10792
+ const tmpText = legendG.append("text").attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-family", FONT_FAMILY).text(entry.value);
10824
10793
  const textW = tmpText.node()?.getComputedTextLength?.() ?? entry.value.length * 7;
10825
10794
  tmpText.remove();
10826
- const pillW = textW + LEGEND_PILL_PAD9 * 2;
10795
+ const pillW = textW + LEGEND_PILL_PAD * 2;
10827
10796
  pillG.append("rect").attr("x", legendX).attr("y", legendY).attr("width", pillW).attr("height", LEGEND_PILL_H).attr("rx", LEGEND_PILL_RX).attr("ry", LEGEND_PILL_RX).attr("fill", mix(entry.color, isDark ? palette.surface : palette.bg, 25)).attr("stroke", entry.color).attr("stroke-width", 1);
10828
- pillG.append("text").attr("x", legendX + pillW / 2).attr("y", legendY + LEGEND_PILL_H / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", palette.text).attr("font-size", LEGEND_FONT_SIZE2).attr("font-family", FONT_FAMILY).text(entry.value);
10797
+ pillG.append("text").attr("x", legendX + pillW / 2).attr("y", legendY + LEGEND_PILL_H / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", palette.text).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-family", FONT_FAMILY).text(entry.value);
10829
10798
  legendX += pillW + LEGEND_GAP2;
10830
10799
  }
10831
- legendX += LEGEND_GROUP_GAP7;
10800
+ legendX += LEGEND_GROUP_GAP;
10832
10801
  }
10833
10802
  }
10834
10803
  }
@@ -10875,6 +10844,7 @@ var init_renderer5 = __esm({
10875
10844
  init_color_utils();
10876
10845
  init_palettes();
10877
10846
  init_tag_groups();
10847
+ init_legend_constants();
10878
10848
  init_parser3();
10879
10849
  init_layout4();
10880
10850
  DIAGRAM_PADDING5 = 20;
@@ -11034,12 +11004,14 @@ function layoutInitiativeStatus(parsed, collapseResult) {
11034
11004
  }
11035
11005
  } else if (isYDisplaced) {
11036
11006
  const exitY = tgt.y > src.y + NODESEP ? src.y + src.height / 2 : src.y - src.height / 2;
11037
- const midX = Math.max(src.x + 1, (src.x + enterX) / 2);
11038
- const midY = (exitY + tgt.y) / 2;
11007
+ const spreadExitX = src.x + yOffset;
11008
+ const spreadEntryY = tgt.y + yOffset;
11009
+ const midX = (spreadExitX + enterX) / 2;
11010
+ const midY = (exitY + spreadEntryY) / 2;
11039
11011
  points = [
11040
- { x: src.x, y: exitY },
11012
+ { x: spreadExitX, y: exitY },
11041
11013
  { x: midX, y: midY },
11042
- { x: enterX, y: tgt.y }
11014
+ { x: enterX, y: spreadEntryY }
11043
11015
  ];
11044
11016
  } else if (tgt.x > src.x && !hasIntermediateRank) {
11045
11017
  points = [
@@ -11974,31 +11946,27 @@ function computeC4NodeDimensions(el, options) {
11974
11946
  height += CARD_V_PAD3;
11975
11947
  return { width, height };
11976
11948
  }
11977
- function computeLegendGroups3(tagGroups, usedValuesByGroup) {
11949
+ function computeLegendGroups3(tagGroups) {
11978
11950
  const result = [];
11979
11951
  for (const group of tagGroups) {
11980
11952
  const entries = [];
11981
11953
  for (const entry of group.entries) {
11982
- if (usedValuesByGroup) {
11983
- const used = usedValuesByGroup.get(group.name.toLowerCase());
11984
- if (!used?.has(entry.value.toLowerCase())) continue;
11985
- }
11986
11954
  entries.push({ value: entry.value, color: entry.color });
11987
11955
  }
11988
11956
  if (entries.length === 0) continue;
11989
- const nameW = group.name.length * LEGEND_PILL_FONT_W5 + LEGEND_PILL_PAD5 * 2;
11990
- let capsuleW = LEGEND_CAPSULE_PAD5;
11957
+ const nameW = group.name.length * LEGEND_PILL_FONT_W4 + LEGEND_PILL_PAD4 * 2;
11958
+ let capsuleW = LEGEND_CAPSULE_PAD4;
11991
11959
  for (const e of entries) {
11992
- capsuleW += LEGEND_DOT_R6 * 2 + LEGEND_ENTRY_DOT_GAP5 + e.value.length * LEGEND_ENTRY_FONT_W5 + LEGEND_ENTRY_TRAIL5;
11960
+ capsuleW += LEGEND_DOT_R4 * 2 + LEGEND_ENTRY_DOT_GAP4 + e.value.length * LEGEND_ENTRY_FONT_W4 + LEGEND_ENTRY_TRAIL4;
11993
11961
  }
11994
- capsuleW += LEGEND_CAPSULE_PAD5;
11962
+ capsuleW += LEGEND_CAPSULE_PAD4;
11995
11963
  result.push({
11996
11964
  name: group.name,
11997
11965
  entries,
11998
11966
  x: 0,
11999
11967
  y: 0,
12000
11968
  width: nameW + capsuleW,
12001
- height: LEGEND_HEIGHT6
11969
+ height: LEGEND_HEIGHT4
12002
11970
  });
12003
11971
  }
12004
11972
  return result;
@@ -12118,18 +12086,7 @@ function layoutC4Context(parsed, activeTagGroup) {
12118
12086
  }
12119
12087
  let totalWidth = nodes.length > 0 ? maxX - minX + MARGIN3 * 2 : 0;
12120
12088
  let totalHeight = nodes.length > 0 ? maxY - minY + MARGIN3 * 2 : 0;
12121
- const usedValuesByGroup = /* @__PURE__ */ new Map();
12122
- for (const el of contextElements) {
12123
- for (const group of parsed.tagGroups) {
12124
- const key = group.name.toLowerCase();
12125
- const val = el.metadata[key];
12126
- if (val) {
12127
- if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, /* @__PURE__ */ new Set());
12128
- usedValuesByGroup.get(key).add(val.toLowerCase());
12129
- }
12130
- }
12131
- }
12132
- const legendGroups = computeLegendGroups3(parsed.tagGroups, usedValuesByGroup);
12089
+ const legendGroups = computeLegendGroups3(parsed.tagGroups);
12133
12090
  if (legendGroups.length > 0) {
12134
12091
  const legendY = totalHeight + MARGIN3;
12135
12092
  let legendX = MARGIN3;
@@ -12139,7 +12096,7 @@ function layoutC4Context(parsed, activeTagGroup) {
12139
12096
  legendX += lg.width + 12;
12140
12097
  }
12141
12098
  const legendRight = legendX;
12142
- const legendBottom = legendY + LEGEND_HEIGHT6;
12099
+ const legendBottom = legendY + LEGEND_HEIGHT4;
12143
12100
  if (legendRight > totalWidth) totalWidth = legendRight;
12144
12101
  if (legendBottom > totalHeight) totalHeight = legendBottom;
12145
12102
  }
@@ -12445,18 +12402,7 @@ function layoutC4Containers(parsed, systemName, activeTagGroup) {
12445
12402
  }
12446
12403
  let totalWidth = maxX - minX + MARGIN3 * 2;
12447
12404
  let totalHeight = maxY - minY + MARGIN3 * 2;
12448
- const usedValuesByGroup = /* @__PURE__ */ new Map();
12449
- for (const el of [...containers, ...externals]) {
12450
- for (const group of parsed.tagGroups) {
12451
- const key = group.name.toLowerCase();
12452
- const val = el.metadata[key];
12453
- if (val) {
12454
- if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, /* @__PURE__ */ new Set());
12455
- usedValuesByGroup.get(key).add(val.toLowerCase());
12456
- }
12457
- }
12458
- }
12459
- const legendGroups = computeLegendGroups3(parsed.tagGroups, usedValuesByGroup);
12405
+ const legendGroups = computeLegendGroups3(parsed.tagGroups);
12460
12406
  if (legendGroups.length > 0) {
12461
12407
  const legendY = totalHeight + MARGIN3;
12462
12408
  let legendX = MARGIN3;
@@ -12466,7 +12412,7 @@ function layoutC4Containers(parsed, systemName, activeTagGroup) {
12466
12412
  legendX += lg.width + 12;
12467
12413
  }
12468
12414
  const legendRight = legendX;
12469
- const legendBottom = legendY + LEGEND_HEIGHT6;
12415
+ const legendBottom = legendY + LEGEND_HEIGHT4;
12470
12416
  if (legendRight > totalWidth) totalWidth = legendRight;
12471
12417
  if (legendBottom > totalHeight) totalHeight = legendBottom;
12472
12418
  }
@@ -12821,21 +12767,7 @@ function layoutC4Components(parsed, systemName, containerName, activeTagGroup) {
12821
12767
  }
12822
12768
  let totalWidth = maxX - minX + MARGIN3 * 2;
12823
12769
  let totalHeight = maxY - minY + MARGIN3 * 2;
12824
- const usedValuesByGroup = /* @__PURE__ */ new Map();
12825
- for (const el of [...components, ...externals]) {
12826
- for (const group of parsed.tagGroups) {
12827
- const key = group.name.toLowerCase();
12828
- let val = el.metadata[key];
12829
- if (!val && components.includes(el)) {
12830
- val = targetContainer.metadata[key] ?? system.metadata[key];
12831
- }
12832
- if (val) {
12833
- if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, /* @__PURE__ */ new Set());
12834
- usedValuesByGroup.get(key).add(val.toLowerCase());
12835
- }
12836
- }
12837
- }
12838
- const legendGroups = computeLegendGroups3(parsed.tagGroups, usedValuesByGroup);
12770
+ const legendGroups = computeLegendGroups3(parsed.tagGroups);
12839
12771
  if (legendGroups.length > 0) {
12840
12772
  const legendY = totalHeight + MARGIN3;
12841
12773
  let legendX = MARGIN3;
@@ -12845,7 +12777,7 @@ function layoutC4Components(parsed, systemName, containerName, activeTagGroup) {
12845
12777
  legendX += lg.width + 12;
12846
12778
  }
12847
12779
  const legendRight = legendX;
12848
- const legendBottom = legendY + LEGEND_HEIGHT6;
12780
+ const legendBottom = legendY + LEGEND_HEIGHT4;
12849
12781
  if (legendRight > totalWidth) totalWidth = legendRight;
12850
12782
  if (legendBottom > totalHeight) totalHeight = legendBottom;
12851
12783
  }
@@ -13090,18 +13022,7 @@ function layoutC4Deployment(parsed, activeTagGroup) {
13090
13022
  }
13091
13023
  let totalWidth = maxX - minX + MARGIN3 * 2;
13092
13024
  let totalHeight = maxY - minY + MARGIN3 * 2;
13093
- const usedValuesByGroup = /* @__PURE__ */ new Map();
13094
- for (const r of refEntries) {
13095
- for (const group of parsed.tagGroups) {
13096
- const key = group.name.toLowerCase();
13097
- const val = r.element.metadata[key];
13098
- if (val) {
13099
- if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, /* @__PURE__ */ new Set());
13100
- usedValuesByGroup.get(key).add(val.toLowerCase());
13101
- }
13102
- }
13103
- }
13104
- const legendGroups = computeLegendGroups3(parsed.tagGroups, usedValuesByGroup);
13025
+ const legendGroups = computeLegendGroups3(parsed.tagGroups);
13105
13026
  if (legendGroups.length > 0) {
13106
13027
  const legendY = totalHeight + MARGIN3;
13107
13028
  let legendX = MARGIN3;
@@ -13111,13 +13032,13 @@ function layoutC4Deployment(parsed, activeTagGroup) {
13111
13032
  legendX += lg.width + 12;
13112
13033
  }
13113
13034
  const legendRight = legendX;
13114
- const legendBottom = legendY + LEGEND_HEIGHT6;
13035
+ const legendBottom = legendY + LEGEND_HEIGHT4;
13115
13036
  if (legendRight > totalWidth) totalWidth = legendRight;
13116
13037
  if (legendBottom > totalHeight) totalHeight = legendBottom;
13117
13038
  }
13118
13039
  return { nodes, edges, legend: legendGroups, groupBoundaries, width: totalWidth, height: totalHeight };
13119
13040
  }
13120
- var CHAR_WIDTH5, MIN_NODE_WIDTH, MAX_NODE_WIDTH, TYPE_LABEL_HEIGHT, DIVIDER_GAP, NAME_HEIGHT, DESC_LINE_HEIGHT, DESC_CHAR_WIDTH, CARD_V_PAD3, CARD_H_PAD3, META_LINE_HEIGHT5, META_CHAR_WIDTH, MARGIN3, BOUNDARY_PAD, GROUP_BOUNDARY_PAD, LEGEND_HEIGHT6, LEGEND_PILL_FONT_SIZE3, LEGEND_PILL_FONT_W5, LEGEND_PILL_PAD5, LEGEND_DOT_R6, LEGEND_ENTRY_FONT_SIZE4, LEGEND_ENTRY_FONT_W5, LEGEND_ENTRY_DOT_GAP5, LEGEND_ENTRY_TRAIL5, LEGEND_CAPSULE_PAD5, META_EXCLUDE_KEYS;
13041
+ var CHAR_WIDTH5, MIN_NODE_WIDTH, MAX_NODE_WIDTH, TYPE_LABEL_HEIGHT, DIVIDER_GAP, NAME_HEIGHT, DESC_LINE_HEIGHT, DESC_CHAR_WIDTH, CARD_V_PAD3, CARD_H_PAD3, META_LINE_HEIGHT5, META_CHAR_WIDTH, MARGIN3, BOUNDARY_PAD, GROUP_BOUNDARY_PAD, LEGEND_HEIGHT4, LEGEND_PILL_FONT_SIZE2, LEGEND_PILL_FONT_W4, LEGEND_PILL_PAD4, LEGEND_DOT_R4, LEGEND_ENTRY_FONT_SIZE2, LEGEND_ENTRY_FONT_W4, LEGEND_ENTRY_DOT_GAP4, LEGEND_ENTRY_TRAIL4, LEGEND_CAPSULE_PAD4, META_EXCLUDE_KEYS;
13121
13042
  var init_layout6 = __esm({
13122
13043
  "src/c4/layout.ts"() {
13123
13044
  "use strict";
@@ -13136,16 +13057,16 @@ var init_layout6 = __esm({
13136
13057
  MARGIN3 = 40;
13137
13058
  BOUNDARY_PAD = 40;
13138
13059
  GROUP_BOUNDARY_PAD = 24;
13139
- LEGEND_HEIGHT6 = 28;
13140
- LEGEND_PILL_FONT_SIZE3 = 11;
13141
- LEGEND_PILL_FONT_W5 = LEGEND_PILL_FONT_SIZE3 * 0.6;
13142
- LEGEND_PILL_PAD5 = 16;
13143
- LEGEND_DOT_R6 = 4;
13144
- LEGEND_ENTRY_FONT_SIZE4 = 10;
13145
- LEGEND_ENTRY_FONT_W5 = LEGEND_ENTRY_FONT_SIZE4 * 0.6;
13146
- LEGEND_ENTRY_DOT_GAP5 = 4;
13147
- LEGEND_ENTRY_TRAIL5 = 8;
13148
- LEGEND_CAPSULE_PAD5 = 4;
13060
+ LEGEND_HEIGHT4 = 28;
13061
+ LEGEND_PILL_FONT_SIZE2 = 11;
13062
+ LEGEND_PILL_FONT_W4 = LEGEND_PILL_FONT_SIZE2 * 0.6;
13063
+ LEGEND_PILL_PAD4 = 16;
13064
+ LEGEND_DOT_R4 = 4;
13065
+ LEGEND_ENTRY_FONT_SIZE2 = 10;
13066
+ LEGEND_ENTRY_FONT_W4 = LEGEND_ENTRY_FONT_SIZE2 * 0.6;
13067
+ LEGEND_ENTRY_DOT_GAP4 = 4;
13068
+ LEGEND_ENTRY_TRAIL4 = 8;
13069
+ LEGEND_CAPSULE_PAD4 = 4;
13149
13070
  META_EXCLUDE_KEYS = /* @__PURE__ */ new Set(["description", "tech", "technology", "is a"]);
13150
13071
  }
13151
13072
  });
@@ -13223,8 +13144,14 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
13223
13144
  if (width <= 0 || height <= 0) return;
13224
13145
  const titleHeight = parsed.title ? TITLE_HEIGHT4 + 10 : 0;
13225
13146
  const diagramW = layout.width;
13226
- const diagramH = layout.height;
13227
- const availH = height - titleHeight;
13147
+ const hasLegend = layout.legend.length > 0;
13148
+ const C4_LAYOUT_MARGIN = 40;
13149
+ const LEGEND_FIXED_GAP4 = 8;
13150
+ const fixedLegend = !exportDims && hasLegend;
13151
+ const legendLayoutSpace = C4_LAYOUT_MARGIN + LEGEND_HEIGHT;
13152
+ const legendReserveH = fixedLegend ? LEGEND_HEIGHT + LEGEND_FIXED_GAP4 : 0;
13153
+ const diagramH = fixedLegend ? layout.height - legendLayoutSpace : layout.height;
13154
+ const availH = height - titleHeight - legendReserveH;
13228
13155
  const scaleX = (width - DIAGRAM_PADDING7 * 2) / diagramW;
13229
13156
  const scaleY = (availH - DIAGRAM_PADDING7 * 2) / diagramH;
13230
13157
  const scale = Math.min(MAX_SCALE6, scaleX, scaleY);
@@ -13296,6 +13223,20 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
13296
13223
  }
13297
13224
  for (const node of layout.nodes) {
13298
13225
  const nodeG = contentG.append("g").attr("transform", `translate(${node.x}, ${node.y})`).attr("class", "c4-card").attr("data-line-number", String(node.lineNumber)).attr("data-node-id", node.id);
13226
+ if (activeTagGroup) {
13227
+ const tagKey = activeTagGroup.toLowerCase();
13228
+ const tagValue = node.metadata[tagKey];
13229
+ if (tagValue) {
13230
+ nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
13231
+ } else {
13232
+ const tagGroup = parsed.tagGroups.find(
13233
+ (g) => g.name.toLowerCase() === tagKey || g.alias?.toLowerCase() === tagKey
13234
+ );
13235
+ if (tagGroup?.defaultValue) {
13236
+ nodeG.attr(`data-tag-${tagKey}`, tagGroup.defaultValue.toLowerCase());
13237
+ }
13238
+ }
13239
+ }
13299
13240
  if (node.importPath) {
13300
13241
  nodeG.attr("data-import-path", node.importPath);
13301
13242
  }
@@ -13348,36 +13289,12 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
13348
13289
  nodeG.append("rect").attr("x", -w / 2).attr("y", h / 2 - DRILL_BAR_HEIGHT).attr("width", w).attr("height", DRILL_BAR_HEIGHT).attr("fill", stroke2).attr("clip-path", `url(#${clipId})`).attr("class", "c4-drill-bar");
13349
13290
  }
13350
13291
  }
13351
- if (!exportDims) {
13352
- for (const group of layout.legend) {
13353
- const isActive = activeTagGroup != null && group.name.toLowerCase() === (activeTagGroup ?? "").toLowerCase();
13354
- if (activeTagGroup != null && !isActive) continue;
13355
- const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
13356
- const pillLabel = group.name;
13357
- const pillWidth = pillLabel.length * LEGEND_PILL_FONT_W6 + LEGEND_PILL_PAD6;
13358
- const gEl = contentG.append("g").attr("transform", `translate(${group.x}, ${group.y})`).attr("class", "c4-legend-group").attr("data-legend-group", group.name.toLowerCase()).style("cursor", "pointer");
13359
- if (isActive) {
13360
- gEl.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT7).attr("rx", LEGEND_HEIGHT7 / 2).attr("fill", groupBg);
13361
- }
13362
- const pillX = isActive ? LEGEND_CAPSULE_PAD6 : 0;
13363
- const pillY = isActive ? LEGEND_CAPSULE_PAD6 : 0;
13364
- const pillH = LEGEND_HEIGHT7 - (isActive ? LEGEND_CAPSULE_PAD6 * 2 : 0);
13365
- gEl.append("rect").attr("x", pillX).attr("y", pillY).attr("width", pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", isActive ? palette.bg : groupBg);
13366
- if (isActive) {
13367
- gEl.append("rect").attr("x", pillX).attr("y", pillY).attr("width", pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", "none").attr("stroke", mix(palette.textMuted, palette.bg, 50)).attr("stroke-width", 0.75);
13368
- }
13369
- gEl.append("text").attr("x", pillX + pillWidth / 2).attr("y", LEGEND_HEIGHT7 / 2 + LEGEND_PILL_FONT_SIZE4 / 2 - 2).attr("font-size", LEGEND_PILL_FONT_SIZE4).attr("font-weight", "500").attr("fill", isActive ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(pillLabel);
13370
- if (isActive) {
13371
- let entryX = pillX + pillWidth + 4;
13372
- for (const entry of group.entries) {
13373
- const entryG = gEl.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
13374
- entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R7).attr("cy", LEGEND_HEIGHT7 / 2).attr("r", LEGEND_DOT_R7).attr("fill", entry.color);
13375
- const textX = entryX + LEGEND_DOT_R7 * 2 + LEGEND_ENTRY_DOT_GAP6;
13376
- entryG.append("text").attr("x", textX).attr("y", LEGEND_HEIGHT7 / 2 + LEGEND_ENTRY_FONT_SIZE5 / 2 - 1).attr("font-size", LEGEND_ENTRY_FONT_SIZE5).attr("fill", palette.textMuted).text(entry.value);
13377
- entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W6 + LEGEND_ENTRY_TRAIL6;
13378
- }
13379
- }
13292
+ if (hasLegend) {
13293
+ const legendParent = fixedLegend ? svg.append("g").attr("class", "c4-legend-fixed").attr("transform", `translate(0, ${height - DIAGRAM_PADDING7 - LEGEND_HEIGHT})`) : contentG.append("g").attr("class", "c4-legend");
13294
+ if (activeTagGroup) {
13295
+ legendParent.attr("data-legend-active", activeTagGroup.toLowerCase());
13380
13296
  }
13297
+ renderLegend2(legendParent, layout, palette, isDark, activeTagGroup, fixedLegend ? width : null);
13381
13298
  }
13382
13299
  }
13383
13300
  function renderC4ContextForExport(content, theme, palette) {
@@ -13665,33 +13582,47 @@ function placeEdgeLabels(labels, edges, obstacleRects) {
13665
13582
  placedRects.push({ x: lbl.x, y: lbl.y, w: lbl.bgW, h: lbl.bgH });
13666
13583
  }
13667
13584
  }
13668
- function renderLegend2(contentG, layout, palette, isDark, activeTagGroup) {
13669
- for (const group of layout.legend) {
13585
+ function renderLegend2(parent, layout, palette, isDark, activeTagGroup, fixedWidth) {
13586
+ const visibleGroups = activeTagGroup != null ? layout.legend.filter((g) => g.name.toLowerCase() === (activeTagGroup ?? "").toLowerCase()) : layout.legend;
13587
+ const pillWidthOf = (g) => g.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
13588
+ const effectiveW = (g) => activeTagGroup != null ? g.width : pillWidthOf(g);
13589
+ let fixedPositions = null;
13590
+ if (fixedWidth != null && visibleGroups.length > 0) {
13591
+ fixedPositions = /* @__PURE__ */ new Map();
13592
+ const totalW = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP;
13593
+ let cx = Math.max(DIAGRAM_PADDING7, (fixedWidth - totalW) / 2);
13594
+ for (const g of visibleGroups) {
13595
+ fixedPositions.set(g.name, cx);
13596
+ cx += effectiveW(g) + LEGEND_GROUP_GAP;
13597
+ }
13598
+ }
13599
+ for (const group of visibleGroups) {
13670
13600
  const isActive = activeTagGroup != null && group.name.toLowerCase() === (activeTagGroup ?? "").toLowerCase();
13671
- if (activeTagGroup != null && !isActive) continue;
13672
13601
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
13673
13602
  const pillLabel = group.name;
13674
- const pillWidth = pillLabel.length * LEGEND_PILL_FONT_W6 + LEGEND_PILL_PAD6;
13675
- const gEl = contentG.append("g").attr("transform", `translate(${group.x}, ${group.y})`).attr("class", "c4-legend-group").attr("data-legend-group", group.name.toLowerCase()).style("cursor", "pointer");
13603
+ const pillWidth = pillWidthOf(group);
13604
+ const gX = fixedPositions?.get(group.name) ?? group.x;
13605
+ const gY = fixedPositions != null ? 0 : group.y;
13606
+ const gEl = parent.append("g").attr("transform", `translate(${gX}, ${gY})`).attr("class", "c4-legend-group").attr("data-legend-group", group.name.toLowerCase()).style("cursor", "pointer");
13676
13607
  if (isActive) {
13677
- gEl.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT7).attr("rx", LEGEND_HEIGHT7 / 2).attr("fill", groupBg);
13608
+ gEl.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
13678
13609
  }
13679
- const pillX = isActive ? LEGEND_CAPSULE_PAD6 : 0;
13680
- const pillY = isActive ? LEGEND_CAPSULE_PAD6 : 0;
13681
- const pillH = LEGEND_HEIGHT7 - (isActive ? LEGEND_CAPSULE_PAD6 * 2 : 0);
13610
+ const pillX = isActive ? LEGEND_CAPSULE_PAD : 0;
13611
+ const pillY = isActive ? LEGEND_CAPSULE_PAD : 0;
13612
+ const pillH = LEGEND_HEIGHT - (isActive ? LEGEND_CAPSULE_PAD * 2 : 0);
13682
13613
  gEl.append("rect").attr("x", pillX).attr("y", pillY).attr("width", pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", isActive ? palette.bg : groupBg);
13683
13614
  if (isActive) {
13684
13615
  gEl.append("rect").attr("x", pillX).attr("y", pillY).attr("width", pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", "none").attr("stroke", mix(palette.textMuted, palette.bg, 50)).attr("stroke-width", 0.75);
13685
13616
  }
13686
- gEl.append("text").attr("x", pillX + pillWidth / 2).attr("y", LEGEND_HEIGHT7 / 2 + LEGEND_PILL_FONT_SIZE4 / 2 - 2).attr("font-size", LEGEND_PILL_FONT_SIZE4).attr("font-weight", "500").attr("fill", isActive ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(pillLabel);
13617
+ gEl.append("text").attr("x", pillX + pillWidth / 2).attr("y", LEGEND_HEIGHT / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", "500").attr("fill", isActive ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(pillLabel);
13687
13618
  if (isActive) {
13688
13619
  let entryX = pillX + pillWidth + 4;
13689
13620
  for (const entry of group.entries) {
13690
13621
  const entryG = gEl.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
13691
- entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R7).attr("cy", LEGEND_HEIGHT7 / 2).attr("r", LEGEND_DOT_R7).attr("fill", entry.color);
13692
- const textX = entryX + LEGEND_DOT_R7 * 2 + LEGEND_ENTRY_DOT_GAP6;
13693
- entryG.append("text").attr("x", textX).attr("y", LEGEND_HEIGHT7 / 2 + LEGEND_ENTRY_FONT_SIZE5 / 2 - 1).attr("font-size", LEGEND_ENTRY_FONT_SIZE5).attr("fill", palette.textMuted).text(entry.value);
13694
- entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W6 + LEGEND_ENTRY_TRAIL6;
13622
+ entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
13623
+ const textX = entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
13624
+ entryG.append("text").attr("x", textX).attr("y", LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1).attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).text(entry.value);
13625
+ entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
13695
13626
  }
13696
13627
  }
13697
13628
  }
@@ -13703,8 +13634,14 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
13703
13634
  if (width <= 0 || height <= 0) return;
13704
13635
  const titleHeight = parsed.title ? TITLE_HEIGHT4 + 10 : 0;
13705
13636
  const diagramW = layout.width;
13706
- const diagramH = layout.height;
13707
- const availH = height - titleHeight;
13637
+ const hasLegend = layout.legend.length > 0;
13638
+ const C4_LAYOUT_MARGIN = 40;
13639
+ const LEGEND_FIXED_GAP4 = 8;
13640
+ const fixedLegend = !exportDims && hasLegend;
13641
+ const legendLayoutSpace = C4_LAYOUT_MARGIN + LEGEND_HEIGHT;
13642
+ const legendReserveH = fixedLegend ? LEGEND_HEIGHT + LEGEND_FIXED_GAP4 : 0;
13643
+ const diagramH = fixedLegend ? layout.height - legendLayoutSpace : layout.height;
13644
+ const availH = height - titleHeight - legendReserveH;
13708
13645
  const scaleX = (width - DIAGRAM_PADDING7 * 2) / diagramW;
13709
13646
  const scaleY = (availH - DIAGRAM_PADDING7 * 2) / diagramH;
13710
13647
  const scale = Math.min(MAX_SCALE6, scaleX, scaleY);
@@ -13775,6 +13712,20 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
13775
13712
  renderEdges(contentG, layout.edges, palette, onClickItem, boundaryLabelObstacles);
13776
13713
  for (const node of layout.nodes) {
13777
13714
  const nodeG = contentG.append("g").attr("transform", `translate(${node.x}, ${node.y})`).attr("class", "c4-card").attr("data-line-number", String(node.lineNumber)).attr("data-node-id", node.id);
13715
+ if (activeTagGroup) {
13716
+ const tagKey = activeTagGroup.toLowerCase();
13717
+ const tagValue = node.metadata[tagKey];
13718
+ if (tagValue) {
13719
+ nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
13720
+ } else {
13721
+ const tagGroup = parsed.tagGroups.find(
13722
+ (g) => g.name.toLowerCase() === tagKey || g.alias?.toLowerCase() === tagKey
13723
+ );
13724
+ if (tagGroup?.defaultValue) {
13725
+ nodeG.attr(`data-tag-${tagKey}`, tagGroup.defaultValue.toLowerCase());
13726
+ }
13727
+ }
13728
+ }
13778
13729
  if (node.shape) {
13779
13730
  nodeG.attr("data-shape", node.shape);
13780
13731
  }
@@ -13860,8 +13811,12 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
13860
13811
  nodeG.append("rect").attr("x", -w / 2).attr("y", h / 2 - DRILL_BAR_HEIGHT).attr("width", w).attr("height", DRILL_BAR_HEIGHT).attr("fill", stroke2).attr("clip-path", `url(#${clipId})`).attr("class", "c4-drill-bar");
13861
13812
  }
13862
13813
  }
13863
- if (!exportDims) {
13864
- renderLegend2(contentG, layout, palette, isDark, activeTagGroup);
13814
+ if (hasLegend) {
13815
+ const legendParent = fixedLegend ? svg.append("g").attr("class", "c4-legend-fixed").attr("transform", `translate(0, ${height - DIAGRAM_PADDING7 - LEGEND_HEIGHT})`) : contentG.append("g").attr("class", "c4-legend");
13816
+ if (activeTagGroup) {
13817
+ legendParent.attr("data-legend-active", activeTagGroup.toLowerCase());
13818
+ }
13819
+ renderLegend2(legendParent, layout, palette, isDark, activeTagGroup, fixedLegend ? width : null);
13865
13820
  }
13866
13821
  }
13867
13822
  function renderC4ContainersForExport(content, systemName, theme, palette) {
@@ -13963,7 +13918,7 @@ function renderC4DeploymentForExport(content, theme, palette) {
13963
13918
  document.body.removeChild(el);
13964
13919
  }
13965
13920
  }
13966
- var DIAGRAM_PADDING7, MAX_SCALE6, TITLE_HEIGHT4, TITLE_FONT_SIZE4, TYPE_FONT_SIZE, NAME_FONT_SIZE, DESC_FONT_SIZE, DESC_LINE_HEIGHT2, DESC_CHAR_WIDTH2, EDGE_LABEL_FONT_SIZE5, TECH_FONT_SIZE, EDGE_STROKE_WIDTH6, NODE_STROKE_WIDTH6, CARD_RADIUS4, CARD_H_PAD4, CARD_V_PAD4, TYPE_LABEL_HEIGHT2, DIVIDER_GAP2, NAME_HEIGHT2, META_FONT_SIZE3, META_CHAR_WIDTH2, META_LINE_HEIGHT6, BOUNDARY_LABEL_FONT_SIZE, BOUNDARY_STROKE_WIDTH, BOUNDARY_RADIUS, DRILL_BAR_HEIGHT, CYLINDER_RY, PERSON_HEAD_R, PERSON_ARM_SPAN, PERSON_LEG_SPAN, PERSON_ICON_W, PERSON_SW, LEGEND_HEIGHT7, LEGEND_PILL_FONT_SIZE4, LEGEND_PILL_FONT_W6, LEGEND_PILL_PAD6, LEGEND_DOT_R7, LEGEND_ENTRY_FONT_SIZE5, LEGEND_ENTRY_FONT_W6, LEGEND_ENTRY_DOT_GAP6, LEGEND_ENTRY_TRAIL6, LEGEND_CAPSULE_PAD6, lineGenerator5;
13921
+ var DIAGRAM_PADDING7, MAX_SCALE6, TITLE_HEIGHT4, TITLE_FONT_SIZE4, TYPE_FONT_SIZE, NAME_FONT_SIZE, DESC_FONT_SIZE, DESC_LINE_HEIGHT2, DESC_CHAR_WIDTH2, EDGE_LABEL_FONT_SIZE5, TECH_FONT_SIZE, EDGE_STROKE_WIDTH6, NODE_STROKE_WIDTH6, CARD_RADIUS4, CARD_H_PAD4, CARD_V_PAD4, TYPE_LABEL_HEIGHT2, DIVIDER_GAP2, NAME_HEIGHT2, META_FONT_SIZE3, META_CHAR_WIDTH2, META_LINE_HEIGHT6, BOUNDARY_LABEL_FONT_SIZE, BOUNDARY_STROKE_WIDTH, BOUNDARY_RADIUS, DRILL_BAR_HEIGHT, CYLINDER_RY, PERSON_HEAD_R, PERSON_ARM_SPAN, PERSON_LEG_SPAN, PERSON_ICON_W, PERSON_SW, lineGenerator5;
13967
13922
  var init_renderer7 = __esm({
13968
13923
  "src/c4/renderer.ts"() {
13969
13924
  "use strict";
@@ -13972,6 +13927,7 @@ var init_renderer7 = __esm({
13972
13927
  init_inline_markdown();
13973
13928
  init_parser6();
13974
13929
  init_layout6();
13930
+ init_legend_constants();
13975
13931
  DIAGRAM_PADDING7 = 20;
13976
13932
  MAX_SCALE6 = 3;
13977
13933
  TITLE_HEIGHT4 = 30;
@@ -14004,16 +13960,6 @@ var init_renderer7 = __esm({
14004
13960
  PERSON_LEG_SPAN = 7;
14005
13961
  PERSON_ICON_W = PERSON_ARM_SPAN * 2;
14006
13962
  PERSON_SW = 1.5;
14007
- LEGEND_HEIGHT7 = 28;
14008
- LEGEND_PILL_FONT_SIZE4 = 11;
14009
- LEGEND_PILL_FONT_W6 = LEGEND_PILL_FONT_SIZE4 * 0.6;
14010
- LEGEND_PILL_PAD6 = 16;
14011
- LEGEND_DOT_R7 = 4;
14012
- LEGEND_ENTRY_FONT_SIZE5 = 10;
14013
- LEGEND_ENTRY_FONT_W6 = LEGEND_ENTRY_FONT_SIZE5 * 0.6;
14014
- LEGEND_ENTRY_DOT_GAP6 = 4;
14015
- LEGEND_ENTRY_TRAIL6 = 8;
14016
- LEGEND_CAPSULE_PAD6 = 4;
14017
13963
  lineGenerator5 = d3Shape5.line().x((d) => d.x).y((d) => d.y).curve(d3Shape5.curveBasis);
14018
13964
  }
14019
13965
  });
@@ -14869,23 +14815,6 @@ function computeInfra(parsed, params = {}) {
14869
14815
  const defaultLatencyMs = parseFloat(parsed.options["default-latency-ms"] ?? "") || 0;
14870
14816
  const defaultUptime = parseFloat(parsed.options["default-uptime"] ?? "") || 100;
14871
14817
  let effectiveNodes = parsed.nodes;
14872
- if (params.scenario) {
14873
- const overrides = params.scenario.overrides;
14874
- effectiveNodes = parsed.nodes.map((node) => {
14875
- const nodeOverrides = overrides[node.id];
14876
- if (!nodeOverrides) return node;
14877
- const props = node.properties.map((p) => {
14878
- const ov = nodeOverrides[p.key];
14879
- return ov != null ? { ...p, value: ov } : p;
14880
- });
14881
- for (const [key, val] of Object.entries(nodeOverrides)) {
14882
- if (!props.some((p) => p.key === key)) {
14883
- props.push({ key, value: val, lineNumber: node.lineNumber });
14884
- }
14885
- }
14886
- return { ...node, properties: props };
14887
- });
14888
- }
14889
14818
  if (params.propertyOverrides) {
14890
14819
  const propOv = params.propertyOverrides;
14891
14820
  effectiveNodes = effectiveNodes.map((node) => {
@@ -16484,16 +16413,16 @@ function computeInfraLegendGroups(nodes, tagGroups, palette, edges) {
16484
16413
  color: r.color,
16485
16414
  key: r.name.toLowerCase().replace(/\s+/g, "-")
16486
16415
  }));
16487
- const pillWidth = "Capabilities".length * LEGEND_PILL_FONT_W7 + LEGEND_PILL_PAD7;
16416
+ const pillWidth = "Capabilities".length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
16488
16417
  let entriesWidth = 0;
16489
16418
  for (const e of entries) {
16490
- entriesWidth += LEGEND_DOT_R8 * 2 + LEGEND_ENTRY_DOT_GAP7 + e.value.length * LEGEND_ENTRY_FONT_W7 + LEGEND_ENTRY_TRAIL7;
16419
+ entriesWidth += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + e.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
16491
16420
  }
16492
16421
  groups.push({
16493
16422
  name: "Capabilities",
16494
16423
  type: "role",
16495
16424
  entries,
16496
- width: LEGEND_CAPSULE_PAD7 * 2 + pillWidth + 4 + entriesWidth,
16425
+ width: LEGEND_CAPSULE_PAD * 2 + pillWidth + 4 + entriesWidth,
16497
16426
  minifiedWidth: pillWidth
16498
16427
  });
16499
16428
  }
@@ -16509,123 +16438,72 @@ function computeInfraLegendGroups(nodes, tagGroups, palette, edges) {
16509
16438
  }
16510
16439
  }
16511
16440
  if (entries.length === 0) continue;
16512
- const pillWidth = tg.name.length * LEGEND_PILL_FONT_W7 + LEGEND_PILL_PAD7;
16441
+ const pillWidth = tg.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
16513
16442
  let entriesWidth = 0;
16514
16443
  for (const e of entries) {
16515
- entriesWidth += LEGEND_DOT_R8 * 2 + LEGEND_ENTRY_DOT_GAP7 + e.value.length * LEGEND_ENTRY_FONT_W7 + LEGEND_ENTRY_TRAIL7;
16444
+ entriesWidth += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + e.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
16516
16445
  }
16517
16446
  groups.push({
16518
16447
  name: tg.name,
16519
16448
  type: "tag",
16520
16449
  tagKey: (tg.alias ?? tg.name).toLowerCase(),
16521
16450
  entries,
16522
- width: LEGEND_CAPSULE_PAD7 * 2 + pillWidth + 4 + entriesWidth,
16451
+ width: LEGEND_CAPSULE_PAD * 2 + pillWidth + 4 + entriesWidth,
16523
16452
  minifiedWidth: pillWidth
16524
16453
  });
16525
16454
  }
16526
16455
  return groups;
16527
16456
  }
16528
- function computePlaybackWidth(playback) {
16529
- if (!playback) return 0;
16530
- const pillWidth = "Playback".length * LEGEND_PILL_FONT_W7 + LEGEND_PILL_PAD7;
16531
- if (!playback.expanded) return pillWidth;
16532
- let entriesW = 8;
16533
- entriesW += LEGEND_PILL_FONT_SIZE5 * 0.8 + 6;
16534
- for (const s of playback.speedOptions) {
16535
- entriesW += `${s}x`.length * LEGEND_ENTRY_FONT_W7 + SPEED_BADGE_H_PAD * 2 + SPEED_BADGE_GAP;
16536
- }
16537
- return LEGEND_CAPSULE_PAD7 * 2 + pillWidth + entriesW;
16538
- }
16539
- function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup, playback) {
16540
- if (legendGroups.length === 0 && !playback) return;
16457
+ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup) {
16458
+ if (legendGroups.length === 0) return;
16541
16459
  const legendG = rootSvg.append("g").attr("transform", `translate(0, ${legendY})`);
16460
+ if (activeGroup) {
16461
+ legendG.attr("data-legend-active", activeGroup.toLowerCase());
16462
+ }
16542
16463
  const effectiveW = (g) => activeGroup != null && g.name.toLowerCase() === activeGroup.toLowerCase() ? g.width : g.minifiedWidth;
16543
- const playbackW = computePlaybackWidth(playback);
16544
- const trailingGaps = legendGroups.length > 0 && playbackW > 0 ? LEGEND_GROUP_GAP5 : 0;
16545
- const totalLegendW = legendGroups.reduce((s, g) => s + effectiveW(g), 0) + (legendGroups.length - 1) * LEGEND_GROUP_GAP5 + trailingGaps + playbackW;
16464
+ const totalLegendW = legendGroups.reduce((s, g) => s + effectiveW(g), 0) + (legendGroups.length - 1) * LEGEND_GROUP_GAP;
16546
16465
  let cursorX = (totalWidth - totalLegendW) / 2;
16547
16466
  for (const group of legendGroups) {
16548
16467
  const isActive = activeGroup != null && group.name.toLowerCase() === activeGroup.toLowerCase();
16549
16468
  const groupBg = isDark ? mix(palette.bg, palette.text, 85) : mix(palette.bg, palette.text, 92);
16550
16469
  const pillLabel = group.name;
16551
- const pillWidth = pillLabel.length * LEGEND_PILL_FONT_W7 + LEGEND_PILL_PAD7;
16552
- const gEl = legendG.append("g").attr("transform", `translate(${cursorX}, 0)`).attr("class", "infra-legend-group").attr("data-legend-group", group.name.toLowerCase()).attr("data-legend-type", group.type).style("cursor", "pointer");
16470
+ const pillWidth = pillLabel.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
16471
+ const gEl = legendG.append("g").attr("transform", `translate(${cursorX}, 0)`).attr("class", "infra-legend-group").attr("data-legend-group", group.name.toLowerCase()).style("cursor", "pointer");
16553
16472
  if (isActive) {
16554
- gEl.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT8).attr("rx", LEGEND_HEIGHT8 / 2).attr("fill", groupBg);
16473
+ gEl.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
16555
16474
  }
16556
- const pillXOff = isActive ? LEGEND_CAPSULE_PAD7 : 0;
16557
- const pillYOff = isActive ? LEGEND_CAPSULE_PAD7 : 0;
16558
- const pillH = LEGEND_HEIGHT8 - (isActive ? LEGEND_CAPSULE_PAD7 * 2 : 0);
16475
+ const pillXOff = isActive ? LEGEND_CAPSULE_PAD : 0;
16476
+ const pillYOff = isActive ? LEGEND_CAPSULE_PAD : 0;
16477
+ const pillH = LEGEND_HEIGHT - (isActive ? LEGEND_CAPSULE_PAD * 2 : 0);
16559
16478
  gEl.append("rect").attr("x", pillXOff).attr("y", pillYOff).attr("width", pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", isActive ? palette.bg : groupBg);
16560
16479
  if (isActive) {
16561
- gEl.append("rect").attr("x", pillXOff).attr("y", pillYOff).attr("width", pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", "none").attr("stroke", isDark ? mix(palette.textMuted, palette.bg, 50) : mix(palette.textMuted, palette.bg, 50)).attr("stroke-width", 0.75);
16480
+ gEl.append("rect").attr("x", pillXOff).attr("y", pillYOff).attr("width", pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", "none").attr("stroke", mix(palette.textMuted, palette.bg, 50)).attr("stroke-width", 0.75);
16562
16481
  }
16563
- gEl.append("text").attr("x", pillXOff + pillWidth / 2).attr("y", LEGEND_HEIGHT8 / 2 + LEGEND_PILL_FONT_SIZE5 / 2 - 2).attr("font-family", FONT_FAMILY).attr("font-size", LEGEND_PILL_FONT_SIZE5).attr("font-weight", "500").attr("fill", isActive ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(pillLabel);
16482
+ gEl.append("text").attr("x", pillXOff + pillWidth / 2).attr("y", LEGEND_HEIGHT / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2).attr("font-family", FONT_FAMILY).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", "500").attr("fill", isActive ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(pillLabel);
16564
16483
  if (isActive) {
16565
16484
  let entryX = pillXOff + pillWidth + 4;
16566
16485
  for (const entry of group.entries) {
16567
- const entryG = gEl.append("g").attr("class", "infra-legend-entry").attr("data-legend-entry", entry.key).attr("data-legend-type", group.type).attr("data-legend-color", entry.color).style("cursor", "pointer");
16568
- if (group.type === "tag" && group.tagKey) {
16569
- entryG.attr("data-legend-tag-group", group.tagKey);
16570
- }
16571
- entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R8).attr("cy", LEGEND_HEIGHT8 / 2).attr("r", LEGEND_DOT_R8).attr("fill", entry.color);
16572
- const textX = entryX + LEGEND_DOT_R8 * 2 + LEGEND_ENTRY_DOT_GAP7;
16573
- entryG.append("text").attr("x", textX).attr("y", LEGEND_HEIGHT8 / 2 + LEGEND_ENTRY_FONT_SIZE6 / 2 - 1).attr("font-family", FONT_FAMILY).attr("font-size", LEGEND_ENTRY_FONT_SIZE6).attr("fill", palette.textMuted).text(entry.value);
16574
- entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W7 + LEGEND_ENTRY_TRAIL7;
16486
+ const entryG = gEl.append("g").attr("class", "infra-legend-entry").attr("data-legend-entry", entry.key.toLowerCase()).attr("data-legend-color", entry.color).attr("data-legend-type", group.type).attr("data-legend-tag-group", group.type === "tag" ? group.tagKey ?? "" : null).style("cursor", "pointer");
16487
+ entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
16488
+ const textX = entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
16489
+ entryG.append("text").attr("x", textX).attr("y", LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1).attr("font-family", FONT_FAMILY).attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).text(entry.value);
16490
+ entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
16575
16491
  }
16576
16492
  }
16577
- cursorX += effectiveW(group) + LEGEND_GROUP_GAP5;
16493
+ cursorX += effectiveW(group) + LEGEND_GROUP_GAP;
16578
16494
  }
16579
- if (playback) {
16580
- const isExpanded = playback.expanded;
16581
- const groupBg = isDark ? mix(palette.bg, palette.text, 85) : mix(palette.bg, palette.text, 92);
16582
- const pillLabel = "Playback";
16583
- const pillWidth = pillLabel.length * LEGEND_PILL_FONT_W7 + LEGEND_PILL_PAD7;
16584
- const fullW = computePlaybackWidth(playback);
16585
- const pbG = legendG.append("g").attr("transform", `translate(${cursorX}, 0)`).attr("class", "infra-legend-group infra-playback-pill").style("cursor", "pointer");
16586
- if (isExpanded) {
16587
- pbG.append("rect").attr("width", fullW).attr("height", LEGEND_HEIGHT8).attr("rx", LEGEND_HEIGHT8 / 2).attr("fill", groupBg);
16588
- }
16589
- const pillXOff = isExpanded ? LEGEND_CAPSULE_PAD7 : 0;
16590
- const pillYOff = isExpanded ? LEGEND_CAPSULE_PAD7 : 0;
16591
- const pillH = LEGEND_HEIGHT8 - (isExpanded ? LEGEND_CAPSULE_PAD7 * 2 : 0);
16592
- pbG.append("rect").attr("x", pillXOff).attr("y", pillYOff).attr("width", pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", isExpanded ? palette.bg : groupBg);
16593
- if (isExpanded) {
16594
- pbG.append("rect").attr("x", pillXOff).attr("y", pillYOff).attr("width", pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", "none").attr("stroke", mix(palette.textMuted, palette.bg, 50)).attr("stroke-width", 0.75);
16595
- }
16596
- pbG.append("text").attr("x", pillXOff + pillWidth / 2).attr("y", LEGEND_HEIGHT8 / 2 + LEGEND_PILL_FONT_SIZE5 / 2 - 2).attr("font-family", FONT_FAMILY).attr("font-size", LEGEND_PILL_FONT_SIZE5).attr("font-weight", "500").attr("fill", isExpanded ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(pillLabel);
16597
- if (isExpanded) {
16598
- let entryX = pillXOff + pillWidth + 8;
16599
- const entryY = LEGEND_HEIGHT8 / 2 + LEGEND_ENTRY_FONT_SIZE6 / 2 - 1;
16600
- const ppLabel = playback.paused ? "\u25B6" : "\u23F8";
16601
- pbG.append("text").attr("x", entryX).attr("y", entryY).attr("font-family", FONT_FAMILY).attr("font-size", LEGEND_PILL_FONT_SIZE5).attr("fill", palette.textMuted).attr("data-playback-action", "toggle-pause").style("cursor", "pointer").text(ppLabel);
16602
- entryX += LEGEND_PILL_FONT_SIZE5 * 0.8 + 6;
16603
- for (const s of playback.speedOptions) {
16604
- const label = `${s}x`;
16605
- const isActive = playback.speed === s;
16606
- const slotW = label.length * LEGEND_ENTRY_FONT_W7 + SPEED_BADGE_H_PAD * 2;
16607
- const badgeH = LEGEND_ENTRY_FONT_SIZE6 + SPEED_BADGE_V_PAD * 2;
16608
- const badgeY = (LEGEND_HEIGHT8 - badgeH) / 2;
16609
- const speedG = pbG.append("g").attr("data-playback-action", "set-speed").attr("data-playback-value", String(s)).style("cursor", "pointer");
16610
- speedG.append("rect").attr("x", entryX).attr("y", badgeY).attr("width", slotW).attr("height", badgeH).attr("rx", badgeH / 2).attr("fill", isActive ? palette.primary : "transparent");
16611
- speedG.append("text").attr("x", entryX + slotW / 2).attr("y", entryY).attr("font-family", FONT_FAMILY).attr("font-size", LEGEND_ENTRY_FONT_SIZE6).attr("font-weight", isActive ? "600" : "400").attr("fill", isActive ? palette.bg : palette.textMuted).attr("text-anchor", "middle").text(label);
16612
- entryX += slotW + SPEED_BADGE_GAP;
16613
- }
16614
- }
16615
- cursorX += fullW + LEGEND_GROUP_GAP5;
16616
- }
16617
- }
16618
- function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, playback, expandedNodeIds, exportMode, collapsedNodes) {
16495
+ }
16496
+ function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, _playback, expandedNodeIds, exportMode, collapsedNodes) {
16619
16497
  d3Selection9.select(container).selectAll(":not([data-d3-tooltip])").remove();
16620
16498
  const legendGroups = computeInfraLegendGroups(layout.nodes, tagGroups ?? [], palette, layout.edges);
16621
- const hasLegend = legendGroups.length > 0 || !!playback;
16499
+ const hasLegend = legendGroups.length > 0;
16622
16500
  const fixedLegend = !exportMode && hasLegend;
16623
- const legendOffset = hasLegend && !fixedLegend ? LEGEND_HEIGHT8 : 0;
16501
+ const legendOffset = hasLegend && !fixedLegend ? LEGEND_HEIGHT : 0;
16624
16502
  const titleOffset = title ? 40 : 0;
16625
16503
  const totalWidth = layout.width;
16626
16504
  const totalHeight = layout.height + titleOffset + legendOffset;
16627
16505
  const shouldAnimate = animate !== false;
16628
- const rootSvg = d3Selection9.select(container).append("svg").attr("xmlns", "http://www.w3.org/2000/svg").attr("width", "100%").attr("height", fixedLegend ? `calc(100% - ${LEGEND_HEIGHT8 + LEGEND_FIXED_GAP3}px)` : "100%").attr("viewBox", `0 0 ${totalWidth} ${totalHeight}`).attr("preserveAspectRatio", "xMidYMid meet");
16506
+ const rootSvg = d3Selection9.select(container).append("svg").attr("xmlns", "http://www.w3.org/2000/svg").attr("width", "100%").attr("height", fixedLegend ? `calc(100% - ${LEGEND_HEIGHT + LEGEND_FIXED_GAP3}px)` : "100%").attr("viewBox", `0 0 ${totalWidth} ${totalHeight}`).attr("preserveAspectRatio", "xMidYMid meet");
16629
16507
  if (shouldAnimate) {
16630
16508
  rootSvg.append("style").text(`
16631
16509
  @keyframes infra-pulse-warning {
@@ -16689,10 +16567,10 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
16689
16567
  if (hasLegend) {
16690
16568
  if (fixedLegend) {
16691
16569
  const containerWidth = container.clientWidth || totalWidth;
16692
- const legendSvg = d3Selection9.select(container).append("svg").attr("class", "infra-legend-fixed").attr("width", "100%").attr("height", LEGEND_HEIGHT8 + LEGEND_FIXED_GAP3).attr("viewBox", `0 0 ${containerWidth} ${LEGEND_HEIGHT8 + LEGEND_FIXED_GAP3}`).attr("preserveAspectRatio", "xMidYMid meet").style("display", "block");
16693
- renderLegend3(legendSvg, legendGroups, containerWidth, LEGEND_FIXED_GAP3 / 2, palette, isDark, activeGroup ?? null, playback ?? void 0);
16570
+ const legendSvg = d3Selection9.select(container).append("svg").attr("class", "infra-legend-fixed").attr("width", "100%").attr("height", LEGEND_HEIGHT + LEGEND_FIXED_GAP3).attr("viewBox", `0 0 ${containerWidth} ${LEGEND_HEIGHT + LEGEND_FIXED_GAP3}`).attr("preserveAspectRatio", "xMidYMid meet").style("display", "block");
16571
+ renderLegend3(legendSvg, legendGroups, containerWidth, LEGEND_FIXED_GAP3 / 2, palette, isDark, activeGroup ?? null);
16694
16572
  } else {
16695
- renderLegend3(rootSvg, legendGroups, totalWidth, titleOffset + layout.height + 4, palette, isDark, activeGroup ?? null, playback ?? void 0);
16573
+ renderLegend3(rootSvg, legendGroups, totalWidth, titleOffset + layout.height + 4, palette, isDark, activeGroup ?? null);
16696
16574
  }
16697
16575
  }
16698
16576
  }
@@ -16703,7 +16581,7 @@ function parseAndLayoutInfra(content) {
16703
16581
  const layout = layoutInfra(computed);
16704
16582
  return { parsed, computed, layout };
16705
16583
  }
16706
- var NODE_FONT_SIZE4, META_FONT_SIZE4, META_LINE_HEIGHT8, EDGE_LABEL_FONT_SIZE7, GROUP_LABEL_FONT_SIZE2, NODE_BORDER_RADIUS, EDGE_STROKE_WIDTH8, NODE_STROKE_WIDTH8, OVERLOAD_STROKE_WIDTH, ROLE_DOT_RADIUS, NODE_HEADER_HEIGHT2, NODE_SEPARATOR_GAP2, NODE_PAD_BOTTOM2, COLLAPSE_BAR_HEIGHT5, COLLAPSE_BAR_INSET2, LEGEND_HEIGHT8, LEGEND_PILL_PAD7, LEGEND_PILL_FONT_SIZE5, LEGEND_PILL_FONT_W7, LEGEND_CAPSULE_PAD7, LEGEND_DOT_R8, LEGEND_ENTRY_FONT_SIZE6, LEGEND_ENTRY_FONT_W7, LEGEND_ENTRY_DOT_GAP7, LEGEND_ENTRY_TRAIL7, LEGEND_GROUP_GAP5, LEGEND_FIXED_GAP3, SPEED_BADGE_H_PAD, SPEED_BADGE_V_PAD, SPEED_BADGE_GAP, COLOR_HEALTHY, COLOR_WARNING, COLOR_OVERLOADED, FLOW_SPEED_MIN, FLOW_SPEED_MAX, PARTICLE_R, PARTICLE_COUNT_MIN, PARTICLE_COUNT_MAX, NODE_PULSE_SPEED, NODE_PULSE_OVERLOAD, REJECT_PARTICLE_R, REJECT_DROP_DISTANCE, REJECT_DURATION_MIN, REJECT_DURATION_MAX, REJECT_COUNT_MIN, REJECT_COUNT_MAX, lineGenerator7, PROP_DISPLAY, DESC_MAX_CHARS, RPS_FORMAT_KEYS, MS_FORMAT_KEYS, PCT_FORMAT_KEYS;
16584
+ var NODE_FONT_SIZE4, META_FONT_SIZE4, META_LINE_HEIGHT8, EDGE_LABEL_FONT_SIZE7, GROUP_LABEL_FONT_SIZE2, NODE_BORDER_RADIUS, EDGE_STROKE_WIDTH8, NODE_STROKE_WIDTH8, OVERLOAD_STROKE_WIDTH, ROLE_DOT_RADIUS, NODE_HEADER_HEIGHT2, NODE_SEPARATOR_GAP2, NODE_PAD_BOTTOM2, COLLAPSE_BAR_HEIGHT5, COLLAPSE_BAR_INSET2, LEGEND_FIXED_GAP3, COLOR_HEALTHY, COLOR_WARNING, COLOR_OVERLOADED, FLOW_SPEED_MIN, FLOW_SPEED_MAX, PARTICLE_R, PARTICLE_COUNT_MIN, PARTICLE_COUNT_MAX, NODE_PULSE_SPEED, NODE_PULSE_OVERLOAD, REJECT_PARTICLE_R, REJECT_DROP_DISTANCE, REJECT_DURATION_MIN, REJECT_DURATION_MAX, REJECT_COUNT_MIN, REJECT_COUNT_MAX, lineGenerator7, PROP_DISPLAY, DESC_MAX_CHARS, RPS_FORMAT_KEYS, MS_FORMAT_KEYS, PCT_FORMAT_KEYS;
16707
16585
  var init_renderer8 = __esm({
16708
16586
  "src/infra/renderer.ts"() {
16709
16587
  "use strict";
@@ -16714,6 +16592,7 @@ var init_renderer8 = __esm({
16714
16592
  init_parser9();
16715
16593
  init_compute();
16716
16594
  init_layout8();
16595
+ init_legend_constants();
16717
16596
  NODE_FONT_SIZE4 = 13;
16718
16597
  META_FONT_SIZE4 = 10;
16719
16598
  META_LINE_HEIGHT8 = 14;
@@ -16729,21 +16608,7 @@ var init_renderer8 = __esm({
16729
16608
  NODE_PAD_BOTTOM2 = 10;
16730
16609
  COLLAPSE_BAR_HEIGHT5 = 6;
16731
16610
  COLLAPSE_BAR_INSET2 = 0;
16732
- LEGEND_HEIGHT8 = 28;
16733
- LEGEND_PILL_PAD7 = 16;
16734
- LEGEND_PILL_FONT_SIZE5 = 11;
16735
- LEGEND_PILL_FONT_W7 = LEGEND_PILL_FONT_SIZE5 * 0.6;
16736
- LEGEND_CAPSULE_PAD7 = 4;
16737
- LEGEND_DOT_R8 = 4;
16738
- LEGEND_ENTRY_FONT_SIZE6 = 10;
16739
- LEGEND_ENTRY_FONT_W7 = LEGEND_ENTRY_FONT_SIZE6 * 0.6;
16740
- LEGEND_ENTRY_DOT_GAP7 = 4;
16741
- LEGEND_ENTRY_TRAIL7 = 8;
16742
- LEGEND_GROUP_GAP5 = 12;
16743
16611
  LEGEND_FIXED_GAP3 = 16;
16744
- SPEED_BADGE_H_PAD = 5;
16745
- SPEED_BADGE_V_PAD = 3;
16746
- SPEED_BADGE_GAP = 6;
16747
16612
  COLOR_HEALTHY = "#22c55e";
16748
16613
  COLOR_WARNING = "#eab308";
16749
16614
  COLOR_OVERLOADED = "#ef4444";
@@ -17764,9 +17629,8 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
17764
17629
  const GROUP_PADDING_BOTTOM = 8;
17765
17630
  const GROUP_LABEL_SIZE = 11;
17766
17631
  const titleOffset = title ? TITLE_HEIGHT5 : 0;
17767
- const legendOffset = parsed.tagGroups.length > 0 ? LEGEND_HEIGHT9 + LEGEND_BOTTOM_GAP : 0;
17768
17632
  const groupOffset = groups.length > 0 ? GROUP_PADDING_TOP + GROUP_LABEL_SIZE : 0;
17769
- const participantStartY = TOP_MARGIN + titleOffset + legendOffset + PARTICIPANT_Y_OFFSET + groupOffset;
17633
+ const participantStartY = TOP_MARGIN + titleOffset + PARTICIPANT_Y_OFFSET + groupOffset;
17770
17634
  const lifelineStartY0 = participantStartY + PARTICIPANT_BOX_HEIGHT;
17771
17635
  const hasActors = participants.some((p) => p.type === "actor");
17772
17636
  const messageStartOffset = MESSAGE_START_OFFSET + (hasActors ? 20 : 0);
@@ -17848,7 +17712,9 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
17848
17712
  participants.length * PARTICIPANT_GAP,
17849
17713
  PARTICIPANT_BOX_WIDTH + 40
17850
17714
  );
17851
- const totalHeight = participantStartY + PARTICIPANT_BOX_HEIGHT + Math.max(lifelineLength, 40) + 40;
17715
+ const contentHeight = participantStartY + PARTICIPANT_BOX_HEIGHT + Math.max(lifelineLength, 40) + 40;
17716
+ const legendSpace = parsed.tagGroups.length > 0 ? LEGEND_HEIGHT : 0;
17717
+ const totalHeight = contentHeight + legendSpace;
17852
17718
  const containerWidth = options?.exportWidth ?? container.getBoundingClientRect().width;
17853
17719
  const svgWidth = Math.max(totalWidth, containerWidth);
17854
17720
  const diagramWidth = participants.length * PARTICIPANT_GAP;
@@ -17906,13 +17772,13 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
17906
17772
  }
17907
17773
  }
17908
17774
  if (parsed.tagGroups.length > 0) {
17909
- const legendY = TOP_MARGIN + titleOffset;
17775
+ const legendY = contentHeight;
17910
17776
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
17911
17777
  const legendItems = [];
17912
17778
  for (const tg of parsed.tagGroups) {
17913
17779
  if (tg.entries.length === 0) continue;
17914
17780
  const isActive = !!activeTagGroup && tg.name.toLowerCase() === activeTagGroup.toLowerCase();
17915
- const pillWidth = tg.name.length * LEGEND_PILL_FONT_W8 + LEGEND_PILL_PAD8;
17781
+ const pillWidth = tg.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
17916
17782
  const entries = tg.entries.map((e) => ({
17917
17783
  value: e.value,
17918
17784
  color: resolveColor(e.color)
@@ -17921,38 +17787,42 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
17921
17787
  if (isActive) {
17922
17788
  let entriesWidth = 0;
17923
17789
  for (const entry of entries) {
17924
- entriesWidth += LEGEND_DOT_R9 * 2 + LEGEND_ENTRY_DOT_GAP8 + entry.value.length * LEGEND_ENTRY_FONT_W8 + LEGEND_ENTRY_TRAIL8;
17790
+ entriesWidth += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
17925
17791
  }
17926
- totalWidth2 = LEGEND_CAPSULE_PAD8 * 2 + pillWidth + 4 + entriesWidth;
17792
+ totalWidth2 = LEGEND_CAPSULE_PAD * 2 + pillWidth + 4 + entriesWidth;
17927
17793
  }
17928
17794
  legendItems.push({ group: tg, isActive, pillWidth, totalWidth: totalWidth2, entries });
17929
17795
  }
17930
- const totalLegendWidth = legendItems.reduce((s, item) => s + item.totalWidth, 0) + (legendItems.length - 1) * LEGEND_GROUP_GAP6;
17796
+ const totalLegendWidth = legendItems.reduce((s, item) => s + item.totalWidth, 0) + (legendItems.length - 1) * LEGEND_GROUP_GAP;
17931
17797
  let legendX = (svgWidth - totalLegendWidth) / 2;
17798
+ const legendContainer = svg.append("g").attr("class", "sequence-legend");
17799
+ if (activeTagGroup) {
17800
+ legendContainer.attr("data-legend-active", activeTagGroup.toLowerCase());
17801
+ }
17932
17802
  for (const item of legendItems) {
17933
- const gEl = svg.append("g").attr("transform", `translate(${legendX}, ${legendY})`).attr("class", "sequence-legend-group").attr("data-legend-group", item.group.name.toLowerCase()).style("cursor", "pointer");
17803
+ const gEl = legendContainer.append("g").attr("transform", `translate(${legendX}, ${legendY})`).attr("class", "sequence-legend-group").attr("data-legend-group", item.group.name.toLowerCase()).style("cursor", "pointer");
17934
17804
  if (item.isActive) {
17935
- gEl.append("rect").attr("width", item.totalWidth).attr("height", LEGEND_HEIGHT9).attr("rx", LEGEND_HEIGHT9 / 2).attr("fill", groupBg);
17805
+ gEl.append("rect").attr("width", item.totalWidth).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
17936
17806
  }
17937
- const pillXOff = item.isActive ? LEGEND_CAPSULE_PAD8 : 0;
17938
- const pillYOff = item.isActive ? LEGEND_CAPSULE_PAD8 : 0;
17939
- const pillH = LEGEND_HEIGHT9 - (item.isActive ? LEGEND_CAPSULE_PAD8 * 2 : 0);
17807
+ const pillXOff = item.isActive ? LEGEND_CAPSULE_PAD : 0;
17808
+ const pillYOff = item.isActive ? LEGEND_CAPSULE_PAD : 0;
17809
+ const pillH = LEGEND_HEIGHT - (item.isActive ? LEGEND_CAPSULE_PAD * 2 : 0);
17940
17810
  gEl.append("rect").attr("x", pillXOff).attr("y", pillYOff).attr("width", item.pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", item.isActive ? palette.bg : groupBg);
17941
17811
  if (item.isActive) {
17942
17812
  gEl.append("rect").attr("x", pillXOff).attr("y", pillYOff).attr("width", item.pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", "none").attr("stroke", mix(palette.textMuted, palette.bg, 50)).attr("stroke-width", 0.75);
17943
17813
  }
17944
- gEl.append("text").attr("x", pillXOff + item.pillWidth / 2).attr("y", LEGEND_HEIGHT9 / 2 + LEGEND_PILL_FONT_SIZE6 / 2 - 2).attr("font-size", LEGEND_PILL_FONT_SIZE6).attr("font-weight", "500").attr("fill", item.isActive ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(item.group.name);
17814
+ gEl.append("text").attr("x", pillXOff + item.pillWidth / 2).attr("y", LEGEND_HEIGHT / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", "500").attr("fill", item.isActive ? palette.text : palette.textMuted).attr("text-anchor", "middle").text(item.group.name);
17945
17815
  if (item.isActive) {
17946
17816
  let entryX = pillXOff + item.pillWidth + 4;
17947
17817
  for (const entry of item.entries) {
17948
17818
  const entryG = gEl.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
17949
- entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R9).attr("cy", LEGEND_HEIGHT9 / 2).attr("r", LEGEND_DOT_R9).attr("fill", entry.color);
17950
- const textX = entryX + LEGEND_DOT_R9 * 2 + LEGEND_ENTRY_DOT_GAP8;
17951
- entryG.append("text").attr("x", textX).attr("y", LEGEND_HEIGHT9 / 2 + LEGEND_ENTRY_FONT_SIZE7 / 2 - 1).attr("font-size", LEGEND_ENTRY_FONT_SIZE7).attr("fill", palette.textMuted).text(entry.value);
17952
- entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W8 + LEGEND_ENTRY_TRAIL8;
17819
+ entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
17820
+ const textX = entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
17821
+ entryG.append("text").attr("x", textX).attr("y", LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1).attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).text(entry.value);
17822
+ entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
17953
17823
  }
17954
17824
  }
17955
- legendX += item.totalWidth + LEGEND_GROUP_GAP6;
17825
+ legendX += item.totalWidth + LEGEND_GROUP_GAP;
17956
17826
  }
17957
17827
  }
17958
17828
  for (const group of groups) {
@@ -18473,7 +18343,7 @@ function renderParticipant(svg, participant, cx, cy, palette, isDark, color, tag
18473
18343
  });
18474
18344
  }
18475
18345
  }
18476
- var PARTICIPANT_GAP, PARTICIPANT_BOX_WIDTH, PARTICIPANT_BOX_HEIGHT, TOP_MARGIN, TITLE_HEIGHT5, PARTICIPANT_Y_OFFSET, SERVICE_BORDER_RADIUS, MESSAGE_START_OFFSET, LIFELINE_TAIL, ARROWHEAD_SIZE, NOTE_MAX_W, NOTE_FOLD, NOTE_PAD_H, NOTE_PAD_V, NOTE_FONT_SIZE, NOTE_LINE_H, NOTE_GAP, NOTE_CHAR_W, NOTE_CHARS_PER_LINE, COLLAPSED_NOTE_H, COLLAPSED_NOTE_W, LEGEND_HEIGHT9, LEGEND_PILL_PAD8, LEGEND_PILL_FONT_SIZE6, LEGEND_PILL_FONT_W8, LEGEND_CAPSULE_PAD8, LEGEND_DOT_R9, LEGEND_ENTRY_FONT_SIZE7, LEGEND_ENTRY_FONT_W8, LEGEND_ENTRY_DOT_GAP8, LEGEND_ENTRY_TRAIL8, LEGEND_GROUP_GAP6, LEGEND_BOTTOM_GAP, LABEL_CHAR_WIDTH, LABEL_MAX_CHARS, fill, stroke, SW, W, H;
18346
+ var PARTICIPANT_GAP, PARTICIPANT_BOX_WIDTH, PARTICIPANT_BOX_HEIGHT, TOP_MARGIN, TITLE_HEIGHT5, PARTICIPANT_Y_OFFSET, SERVICE_BORDER_RADIUS, MESSAGE_START_OFFSET, LIFELINE_TAIL, ARROWHEAD_SIZE, NOTE_MAX_W, NOTE_FOLD, NOTE_PAD_H, NOTE_PAD_V, NOTE_FONT_SIZE, NOTE_LINE_H, NOTE_GAP, NOTE_CHAR_W, NOTE_CHARS_PER_LINE, COLLAPSED_NOTE_H, COLLAPSED_NOTE_W, LABEL_CHAR_WIDTH, LABEL_MAX_CHARS, fill, stroke, SW, W, H;
18477
18347
  var init_renderer9 = __esm({
18478
18348
  "src/sequence/renderer.ts"() {
18479
18349
  "use strict";
@@ -18483,6 +18353,7 @@ var init_renderer9 = __esm({
18483
18353
  init_colors();
18484
18354
  init_parser();
18485
18355
  init_tag_resolution();
18356
+ init_legend_constants();
18486
18357
  PARTICIPANT_GAP = 160;
18487
18358
  PARTICIPANT_BOX_WIDTH = 120;
18488
18359
  PARTICIPANT_BOX_HEIGHT = 50;
@@ -18504,18 +18375,6 @@ var init_renderer9 = __esm({
18504
18375
  NOTE_CHARS_PER_LINE = Math.floor((NOTE_MAX_W - NOTE_PAD_H * 2 - NOTE_FOLD) / NOTE_CHAR_W);
18505
18376
  COLLAPSED_NOTE_H = 20;
18506
18377
  COLLAPSED_NOTE_W = 40;
18507
- LEGEND_HEIGHT9 = 28;
18508
- LEGEND_PILL_PAD8 = 16;
18509
- LEGEND_PILL_FONT_SIZE6 = 11;
18510
- LEGEND_PILL_FONT_W8 = LEGEND_PILL_FONT_SIZE6 * 0.6;
18511
- LEGEND_CAPSULE_PAD8 = 4;
18512
- LEGEND_DOT_R9 = 4;
18513
- LEGEND_ENTRY_FONT_SIZE7 = 10;
18514
- LEGEND_ENTRY_FONT_W8 = LEGEND_ENTRY_FONT_SIZE7 * 0.6;
18515
- LEGEND_ENTRY_DOT_GAP8 = 4;
18516
- LEGEND_ENTRY_TRAIL8 = 8;
18517
- LEGEND_GROUP_GAP6 = 12;
18518
- LEGEND_BOTTOM_GAP = 8;
18519
18378
  LABEL_CHAR_WIDTH = 7.5;
18520
18379
  LABEL_MAX_CHARS = Math.floor((PARTICIPANT_BOX_WIDTH - 10) / LABEL_CHAR_WIDTH);
18521
18380
  fill = (palette, isDark, color) => color ? mix(color, isDark ? palette.surface : palette.bg, isDark ? 30 : 40) : isDark ? mix(palette.overlay, palette.surface, 50) : mix(palette.bg, palette.surface, 50);
@@ -20128,9 +19987,9 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20128
19987
  const scaleMargin = timelineScale ? 40 : 0;
20129
19988
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
20130
19989
  const margin = {
20131
- top: 104 + markerMargin + tagLegendReserve,
19990
+ top: 104 + markerMargin,
20132
19991
  right: 40 + scaleMargin,
20133
- bottom: 40,
19992
+ bottom: 40 + tagLegendReserve,
20134
19993
  left: 60 + scaleMargin
20135
19994
  };
20136
19995
  const innerWidth = width - margin.left - margin.right;
@@ -20237,9 +20096,9 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20237
20096
  const scaleMargin = timelineScale ? 40 : 0;
20238
20097
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
20239
20098
  const margin = {
20240
- top: 104 + markerMargin + tagLegendReserve,
20099
+ top: 104 + markerMargin,
20241
20100
  right: 200,
20242
- bottom: 40,
20101
+ bottom: 40 + tagLegendReserve,
20243
20102
  left: 60 + scaleMargin
20244
20103
  };
20245
20104
  const innerWidth = width - margin.left - margin.right;
@@ -20376,9 +20235,9 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20376
20235
  const dynamicLeftMargin = Math.max(120, maxGroupNameLen * 7 + 30);
20377
20236
  const baseTopMargin = title ? 50 : 20;
20378
20237
  const margin = {
20379
- top: baseTopMargin + (timelineScale ? 40 : 0) + markerMargin + tagLegendReserve,
20238
+ top: baseTopMargin + (timelineScale ? 40 : 0) + markerMargin,
20380
20239
  right: 40,
20381
- bottom: 40 + scaleMargin,
20240
+ bottom: 40 + scaleMargin + tagLegendReserve,
20382
20241
  left: dynamicLeftMargin
20383
20242
  };
20384
20243
  const innerWidth = width - margin.left - margin.right;
@@ -20522,9 +20381,9 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20522
20381
  const scaleMargin = timelineScale ? 24 : 0;
20523
20382
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
20524
20383
  const margin = {
20525
- top: 104 + (timelineScale ? 40 : 0) + markerMargin + tagLegendReserve,
20384
+ top: 104 + (timelineScale ? 40 : 0) + markerMargin,
20526
20385
  right: 40,
20527
- bottom: 40 + scaleMargin,
20386
+ bottom: 40 + scaleMargin + tagLegendReserve,
20528
20387
  left: 60
20529
20388
  };
20530
20389
  const innerWidth = width - margin.left - margin.right;
@@ -20655,17 +20514,17 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20655
20514
  });
20656
20515
  }
20657
20516
  if (parsed.timelineTagGroups.length > 0) {
20658
- const LG_HEIGHT = 28;
20659
- const LG_PILL_PAD = 16;
20660
- const LG_PILL_FONT_SIZE = 11;
20661
- const LG_PILL_FONT_W = LG_PILL_FONT_SIZE * 0.6;
20662
- const LG_CAPSULE_PAD = 4;
20663
- const LG_DOT_R = 4;
20664
- const LG_ENTRY_FONT_SIZE = 10;
20665
- const LG_ENTRY_FONT_W = LG_ENTRY_FONT_SIZE * 0.6;
20666
- const LG_ENTRY_DOT_GAP = 4;
20667
- const LG_ENTRY_TRAIL = 8;
20668
- const LG_GROUP_GAP = 12;
20517
+ const LG_HEIGHT = LEGEND_HEIGHT;
20518
+ const LG_PILL_PAD = LEGEND_PILL_PAD;
20519
+ const LG_PILL_FONT_SIZE = LEGEND_PILL_FONT_SIZE;
20520
+ const LG_PILL_FONT_W = LEGEND_PILL_FONT_W;
20521
+ const LG_CAPSULE_PAD = LEGEND_CAPSULE_PAD;
20522
+ const LG_DOT_R = LEGEND_DOT_R;
20523
+ const LG_ENTRY_FONT_SIZE = LEGEND_ENTRY_FONT_SIZE;
20524
+ const LG_ENTRY_FONT_W = LEGEND_ENTRY_FONT_W;
20525
+ const LG_ENTRY_DOT_GAP = LEGEND_ENTRY_DOT_GAP;
20526
+ const LG_ENTRY_TRAIL = LEGEND_ENTRY_TRAIL;
20527
+ const LG_GROUP_GAP = LEGEND_GROUP_GAP;
20669
20528
  const LG_ICON_W = 20;
20670
20529
  const mainSvg = d3Selection12.select(container).select("svg");
20671
20530
  const mainG = mainSvg.select("g");
@@ -20698,6 +20557,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20698
20557
  );
20699
20558
  }, drawLegend2 = function() {
20700
20559
  mainSvg.selectAll(".tl-tag-legend-group").remove();
20560
+ mainSvg.selectAll(".tl-tag-legend-container").remove();
20701
20561
  const effectiveColorKey = (currentActiveGroup ?? currentSwimlaneGroup)?.toLowerCase() ?? null;
20702
20562
  const visibleGroups = viewMode ? legendGroups.filter(
20703
20563
  (lg) => effectiveColorKey != null && lg.group.name.toLowerCase() === effectiveColorKey
@@ -20708,13 +20568,17 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20708
20568
  return s + (isActive ? lg.expandedWidth : lg.minifiedWidth);
20709
20569
  }, 0) + (visibleGroups.length - 1) * LG_GROUP_GAP;
20710
20570
  let cx = (width - totalW) / 2;
20571
+ const legendContainer = mainSvg.append("g").attr("class", "tl-tag-legend-container");
20572
+ if (currentActiveGroup) {
20573
+ legendContainer.attr("data-legend-active", currentActiveGroup.toLowerCase());
20574
+ }
20711
20575
  for (const lg of visibleGroups) {
20712
20576
  const groupKey = lg.group.name.toLowerCase();
20713
20577
  const isActive = viewMode || currentActiveGroup != null && currentActiveGroup.toLowerCase() === groupKey;
20714
20578
  const isSwimActive = currentSwimlaneGroup != null && currentSwimlaneGroup.toLowerCase() === groupKey;
20715
20579
  const pillLabel = lg.group.name;
20716
20580
  const pillWidth = pillLabel.length * LG_PILL_FONT_W + LG_PILL_PAD;
20717
- const gEl = mainSvg.append("g").attr("transform", `translate(${cx}, ${legendY})`).attr("class", "tl-tag-legend-group tl-tag-legend-entry").attr("data-legend-group", groupKey).attr("data-tag-group", groupKey).attr("data-legend-entry", "__group__");
20581
+ const gEl = legendContainer.append("g").attr("transform", `translate(${cx}, ${legendY})`).attr("class", "tl-tag-legend-group tl-tag-legend-entry").attr("data-legend-group", groupKey).attr("data-tag-group", groupKey).attr("data-legend-entry", "__group__");
20718
20582
  if (!viewMode) {
20719
20583
  gEl.style("cursor", "pointer").on("click", () => {
20720
20584
  currentActiveGroup = currentActiveGroup === groupKey ? null : groupKey;
@@ -20804,7 +20668,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20804
20668
  });
20805
20669
  };
20806
20670
  var drawSwimlaneIcon = drawSwimlaneIcon2, relayout = relayout2, drawLegend = drawLegend2, recolorEvents = recolorEvents2;
20807
- const legendY = title ? 50 : 10;
20671
+ const legendY = height - LG_HEIGHT - 4;
20808
20672
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
20809
20673
  const legendGroups = parsed.timelineTagGroups.map((g) => {
20810
20674
  const pillW = g.name.length * LG_PILL_FONT_W + LG_PILL_PAD;
@@ -21575,7 +21439,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21575
21439
  const orgParsed = parseOrg2(content, effectivePalette2);
21576
21440
  if (orgParsed.error) return "";
21577
21441
  const collapsedNodes = orgExportState?.collapsedNodes;
21578
- const activeTagGroup = orgExportState?.activeTagGroup ?? null;
21442
+ const activeTagGroup = orgExportState?.activeTagGroup ?? options?.tagGroup ?? null;
21579
21443
  const hiddenAttributes = orgExportState?.hiddenAttributes;
21580
21444
  const { parsed: effectiveParsed, hiddenCounts } = collapsedNodes && collapsedNodes.size > 0 ? collapseOrgTree2(orgParsed, collapsedNodes) : { parsed: orgParsed, hiddenCounts: /* @__PURE__ */ new Map() };
21581
21445
  const orgLayout = layoutOrg2(
@@ -21604,7 +21468,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21604
21468
  const sitemapParsed = parseSitemap2(content, effectivePalette2);
21605
21469
  if (sitemapParsed.error || sitemapParsed.roots.length === 0) return "";
21606
21470
  const collapsedNodes = orgExportState?.collapsedNodes;
21607
- const activeTagGroup = orgExportState?.activeTagGroup ?? null;
21471
+ const activeTagGroup = orgExportState?.activeTagGroup ?? options?.tagGroup ?? null;
21608
21472
  const hiddenAttributes = orgExportState?.hiddenAttributes;
21609
21473
  const { parsed: effectiveParsed, hiddenCounts } = collapsedNodes && collapsedNodes.size > 0 ? collapseSitemapTree2(sitemapParsed, collapsedNodes) : { parsed: sitemapParsed, hiddenCounts: /* @__PURE__ */ new Map() };
21610
21474
  const sitemapLayout = layoutSitemap2(
@@ -21632,7 +21496,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21632
21496
  container2.style.position = "absolute";
21633
21497
  container2.style.left = "-9999px";
21634
21498
  document.body.appendChild(container2);
21635
- renderKanban2(container2, kanbanParsed, effectivePalette2, theme === "dark");
21499
+ renderKanban2(container2, kanbanParsed, effectivePalette2, theme === "dark", void 0, void 0, options?.tagGroup);
21636
21500
  return finalizeSvgExport(container2, theme, effectivePalette2, options);
21637
21501
  }
21638
21502
  if (detectedType === "class") {
@@ -21664,7 +21528,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21664
21528
  const exportWidth = erLayout.width + PADDING * 2;
21665
21529
  const exportHeight = erLayout.height + PADDING * 2 + titleOffset;
21666
21530
  const container2 = createExportContainer(exportWidth, exportHeight);
21667
- renderERDiagram2(container2, erParsed, erLayout, effectivePalette2, theme === "dark", void 0, { width: exportWidth, height: exportHeight });
21531
+ renderERDiagram2(container2, erParsed, erLayout, effectivePalette2, theme === "dark", void 0, { width: exportWidth, height: exportHeight }, options?.tagGroup);
21668
21532
  return finalizeSvgExport(container2, theme, effectivePalette2, options);
21669
21533
  }
21670
21534
  if (detectedType === "initiative-status") {
@@ -21701,7 +21565,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21701
21565
  const exportHeight = c4Layout.height + PADDING * 2 + titleOffset;
21702
21566
  const container2 = createExportContainer(exportWidth, exportHeight);
21703
21567
  const renderFn = c4Level === "deployment" || c4Level === "components" && c4System && c4Container || c4Level === "containers" && c4System ? renderC4Containers2 : renderC4Context2;
21704
- renderFn(container2, c4Parsed, c4Layout, effectivePalette2, theme === "dark", void 0, { width: exportWidth, height: exportHeight });
21568
+ renderFn(container2, c4Parsed, c4Layout, effectivePalette2, theme === "dark", void 0, { width: exportWidth, height: exportHeight }, options?.tagGroup);
21705
21569
  return finalizeSvgExport(container2, theme, effectivePalette2, options);
21706
21570
  }
21707
21571
  if (detectedType === "flowchart") {
@@ -21724,16 +21588,16 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21724
21588
  const effectivePalette2 = await resolveExportPalette(theme, palette);
21725
21589
  const infraParsed = parseInfra2(content);
21726
21590
  if (infraParsed.error || infraParsed.nodes.length === 0) return "";
21727
- const selectedScenario = options?.scenario ? infraParsed.scenarios.find((s) => s.name.toLowerCase() === options.scenario.toLowerCase()) ?? null : null;
21728
- const infraComputed = computeInfra2(infraParsed, selectedScenario ? { scenario: selectedScenario } : {});
21591
+ const infraComputed = computeInfra2(infraParsed);
21729
21592
  const infraLayout = layoutInfra2(infraComputed);
21593
+ const activeTagGroup = options?.tagGroup ?? null;
21730
21594
  const titleOffset = infraParsed.title ? 40 : 0;
21731
21595
  const legendGroups = computeInfraLegendGroups2(infraLayout.nodes, infraParsed.tagGroups, effectivePalette2);
21732
21596
  const legendOffset = legendGroups.length > 0 ? 28 : 0;
21733
21597
  const exportWidth = infraLayout.width;
21734
21598
  const exportHeight = infraLayout.height + titleOffset + legendOffset;
21735
21599
  const container2 = createExportContainer(exportWidth, exportHeight);
21736
- renderInfra2(container2, infraLayout, effectivePalette2, theme === "dark", infraParsed.title, infraParsed.titleLineNumber, infraParsed.tagGroups, null, false, null, null, true);
21600
+ renderInfra2(container2, infraLayout, effectivePalette2, theme === "dark", infraParsed.title, infraParsed.titleLineNumber, infraParsed.tagGroups, activeTagGroup, false, null, null, true);
21737
21601
  const infraSvg = container2.querySelector("svg");
21738
21602
  if (infraSvg) {
21739
21603
  infraSvg.setAttribute("width", String(exportWidth));
@@ -21777,7 +21641,8 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21777
21641
  const seqParsed = parseSequenceDgmo2(content);
21778
21642
  if (seqParsed.error || seqParsed.participants.length === 0) return "";
21779
21643
  renderSequenceDiagram2(container, seqParsed, effectivePalette, isDark, void 0, {
21780
- exportWidth: EXPORT_WIDTH
21644
+ exportWidth: EXPORT_WIDTH,
21645
+ activeTagGroup: options?.tagGroup
21781
21646
  });
21782
21647
  } else if (parsed.type === "wordcloud") {
21783
21648
  await renderWordCloudAsync(container, parsed, effectivePalette, isDark, dims);
@@ -21791,7 +21656,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21791
21656
  isDark,
21792
21657
  void 0,
21793
21658
  dims,
21794
- orgExportState?.activeTagGroup,
21659
+ orgExportState?.activeTagGroup ?? options?.tagGroup,
21795
21660
  orgExportState?.swimlaneTagGroup
21796
21661
  );
21797
21662
  } else if (parsed.type === "venn") {
@@ -21815,6 +21680,7 @@ var init_d3 = __esm({
21815
21680
  init_diagnostics();
21816
21681
  init_parsing();
21817
21682
  init_tag_groups();
21683
+ init_legend_constants();
21818
21684
  DEFAULT_CLOUD_OPTIONS = {
21819
21685
  rotate: "none",
21820
21686
  max: 0,
@@ -21990,7 +21856,7 @@ async function render(content, options) {
21990
21856
  c4Level: options?.c4Level,
21991
21857
  c4System: options?.c4System,
21992
21858
  c4Container: options?.c4Container,
21993
- scenario: options?.scenario
21859
+ tagGroup: options?.tagGroup
21994
21860
  });
21995
21861
  }
21996
21862