@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.cjs CHANGED
@@ -1467,6 +1467,29 @@ var init_tag_groups = __esm({
1467
1467
  }
1468
1468
  });
1469
1469
 
1470
+ // src/utils/legend-constants.ts
1471
+ 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;
1472
+ var init_legend_constants = __esm({
1473
+ "src/utils/legend-constants.ts"() {
1474
+ "use strict";
1475
+ LEGEND_HEIGHT = 28;
1476
+ LEGEND_PILL_PAD = 16;
1477
+ LEGEND_PILL_FONT_SIZE = 11;
1478
+ LEGEND_PILL_FONT_W = LEGEND_PILL_FONT_SIZE * 0.6;
1479
+ LEGEND_CAPSULE_PAD = 4;
1480
+ LEGEND_DOT_R = 4;
1481
+ LEGEND_ENTRY_FONT_SIZE = 10;
1482
+ LEGEND_ENTRY_FONT_W = LEGEND_ENTRY_FONT_SIZE * 0.6;
1483
+ LEGEND_ENTRY_DOT_GAP = 4;
1484
+ LEGEND_ENTRY_TRAIL = 8;
1485
+ LEGEND_GROUP_GAP = 12;
1486
+ LEGEND_EYE_SIZE = 14;
1487
+ LEGEND_EYE_GAP = 6;
1488
+ 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";
1489
+ 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";
1490
+ }
1491
+ });
1492
+
1470
1493
  // src/sequence/participant-inference.ts
1471
1494
  function inferParticipantType(name) {
1472
1495
  for (const rule of PARTICIPANT_RULES) {
@@ -4777,10 +4800,12 @@ function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacit
4777
4800
  if (type === "category" && data && data.length > 0) {
4778
4801
  const maxLabelLen = Math.max(...data.map((l) => l.length));
4779
4802
  const count = data.length;
4780
- if (count > 10 || maxLabelLen > 20) catFontSize = 10;
4781
- else if (count > 5 || maxLabelLen > 14) catFontSize = 11;
4803
+ const step = intervalOverride != null && intervalOverride > 0 ? intervalOverride + 1 : 1;
4804
+ const visibleCount = Math.ceil(count / step);
4805
+ if (visibleCount > 10 || maxLabelLen > 20) catFontSize = 10;
4806
+ else if (visibleCount > 5 || maxLabelLen > 14) catFontSize = 11;
4782
4807
  else if (maxLabelLen > 8) catFontSize = 12;
4783
- if (chartWidthHint && count > 0) {
4808
+ if ((intervalOverride == null || intervalOverride === 0) && chartWidthHint && count > 0) {
4784
4809
  const availPerLabel = Math.floor(chartWidthHint * 0.85 / count);
4785
4810
  catLabelExtras = {
4786
4811
  width: availPerLabel,
@@ -4798,6 +4823,9 @@ function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacit
4798
4823
  fontFamily: FONT_FAMILY,
4799
4824
  ...type === "category" && {
4800
4825
  interval: intervalOverride ?? 0,
4826
+ // Prevent ECharts auto-rotation: it measures raw slot width (chartWidth/N),
4827
+ // which is too narrow when an interval skips most labels, and rotates to 90°.
4828
+ rotate: 0,
4801
4829
  formatter: (value) => value.replace(/([a-z])([A-Z])/g, "$1\n$2"),
4802
4830
  ...catLabelExtras
4803
4831
  }
@@ -4873,21 +4901,13 @@ function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOp
4873
4901
  ]
4874
4902
  };
4875
4903
  }
4876
- function buildIntervalCallback(labels, eras) {
4904
+ function buildIntervalStep(labels) {
4877
4905
  const count = labels.length;
4878
- if (count <= 8) return () => true;
4879
- const snapSteps = [1, 2, 4, 5, 10, 20, 25, 50];
4880
- const raw = Math.ceil(count / 8);
4906
+ if (count <= 6) return 0;
4907
+ const snapSteps = [1, 2, 5, 10, 25, 50, 100];
4908
+ const raw = Math.ceil(count / 5);
4881
4909
  const N = [...snapSteps].reverse().find((s) => s <= raw) ?? 1;
4882
- const pinned = /* @__PURE__ */ new Set();
4883
- for (let i = 0; i < count; i += N) pinned.add(i);
4884
- for (const era of eras) {
4885
- const si = labels.indexOf(era.start);
4886
- const ei = labels.indexOf(era.end);
4887
- if (si >= 0) pinned.add(si);
4888
- if (ei >= 0) pinned.add(ei);
4889
- }
4890
- return (index) => pinned.has(index);
4910
+ return N - 1;
4891
4911
  }
4892
4912
  function buildMarkArea(eras, labels, textColor, defaultColor) {
4893
4913
  if (eras.length === 0) return void 0;
@@ -4922,7 +4942,7 @@ function buildLineOption(parsed, palette, textColor, axisLineColor, splitLineCol
4922
4942
  const labels = parsed.data.map((d) => d.label);
4923
4943
  const values = parsed.data.map((d) => d.value);
4924
4944
  const eras = parsed.eras ?? [];
4925
- const interval = buildIntervalCallback(labels, eras);
4945
+ const interval = buildIntervalStep(labels);
4926
4946
  const markArea = buildMarkArea(eras, labels, textColor, palette.colors.blue);
4927
4947
  return {
4928
4948
  ...CHART_BASE,
@@ -4954,7 +4974,7 @@ function buildMultiLineOption(parsed, palette, textColor, axisLineColor, splitLi
4954
4974
  const seriesNames = parsed.seriesNames ?? [];
4955
4975
  const labels = parsed.data.map((d) => d.label);
4956
4976
  const eras = parsed.eras ?? [];
4957
- const interval = buildIntervalCallback(labels, eras);
4977
+ const interval = buildIntervalStep(labels);
4958
4978
  const markArea = buildMarkArea(eras, labels, textColor, palette.colors.blue);
4959
4979
  const series = seriesNames.map((name, idx) => {
4960
4980
  const color = parsed.seriesNameColors?.[idx] ?? colors[idx % colors.length];
@@ -4998,7 +5018,7 @@ function buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineCol
4998
5018
  const labels = parsed.data.map((d) => d.label);
4999
5019
  const values = parsed.data.map((d) => d.value);
5000
5020
  const eras = parsed.eras ?? [];
5001
- const interval = buildIntervalCallback(labels, eras);
5021
+ const interval = buildIntervalStep(labels);
5002
5022
  const markArea = buildMarkArea(eras, labels, textColor, palette.colors.blue);
5003
5023
  return {
5004
5024
  ...CHART_BASE,
@@ -7012,7 +7032,6 @@ function parseInfra(content) {
7012
7032
  edges: [],
7013
7033
  groups: [],
7014
7034
  tagGroups: [],
7015
- scenarios: [],
7016
7035
  options: {},
7017
7036
  diagnostics: [],
7018
7037
  error: null
@@ -7115,16 +7134,7 @@ function parseInfra(content) {
7115
7134
  continue;
7116
7135
  }
7117
7136
  if (/^scenario\s*:/i.test(trimmed)) {
7118
- finishCurrentNode();
7119
- finishCurrentTagGroup();
7120
- currentGroup = null;
7121
- const scenarioName = trimmed.replace(/^scenario\s*:\s*/i, "").trim();
7122
- const scenario = {
7123
- name: scenarioName,
7124
- overrides: {},
7125
- lineNumber
7126
- };
7127
- let scenarioNodeId = null;
7137
+ console.warn("[dgmo warn] scenario syntax is deprecated and will be ignored");
7128
7138
  let si = i + 1;
7129
7139
  while (si < lines.length) {
7130
7140
  const sLine = lines[si];
@@ -7135,23 +7145,9 @@ function parseInfra(content) {
7135
7145
  }
7136
7146
  const sIndent = sLine.length - sLine.trimStart().length;
7137
7147
  if (sIndent === 0) break;
7138
- if (sIndent <= 2) {
7139
- scenarioNodeId = nodeId2(sTrimmed.replace(/\|.*$/, "").trim());
7140
- if (!scenario.overrides[scenarioNodeId]) {
7141
- scenario.overrides[scenarioNodeId] = {};
7142
- }
7143
- } else if (scenarioNodeId) {
7144
- const pm = sTrimmed.match(PROPERTY_RE);
7145
- if (pm) {
7146
- const key = pm[1].toLowerCase();
7147
- const val = parsePropertyValue(pm[2].trim());
7148
- scenario.overrides[scenarioNodeId][key] = val;
7149
- }
7150
- }
7151
7148
  si++;
7152
7149
  }
7153
7150
  i = si - 1;
7154
- result.scenarios.push(scenario);
7155
7151
  continue;
7156
7152
  }
7157
7153
  const tagMatch = trimmed.match(TAG_GROUP_RE);
@@ -7683,14 +7679,14 @@ function computeLegendGroups(tagGroups, showEyeIcons, usedValuesByGroup) {
7683
7679
  const usedValues = usedValuesByGroup?.get(group.name.toLowerCase());
7684
7680
  const visibleEntries = usedValues ? group.entries.filter((e) => usedValues.has(e.value.toLowerCase())) : group.entries;
7685
7681
  if (visibleEntries.length === 0) continue;
7686
- const pillWidth = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
7682
+ const pillWidth = group.name.length * LEGEND_PILL_FONT_W2 + LEGEND_PILL_PAD2;
7687
7683
  const minPillWidth = pillWidth;
7688
7684
  let entriesWidth = 0;
7689
7685
  for (const entry of visibleEntries) {
7690
- entriesWidth += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
7686
+ entriesWidth += LEGEND_DOT_R2 * 2 + LEGEND_ENTRY_DOT_GAP2 + entry.value.length * LEGEND_ENTRY_FONT_W2 + LEGEND_ENTRY_TRAIL2;
7691
7687
  }
7692
- const eyeSpace = showEyeIcons ? LEGEND_EYE_SIZE + LEGEND_EYE_GAP : 0;
7693
- const capsuleWidth = LEGEND_CAPSULE_PAD * 2 + pillWidth + 4 + eyeSpace + entriesWidth;
7688
+ const eyeSpace = showEyeIcons ? LEGEND_EYE_SIZE2 + LEGEND_EYE_GAP2 : 0;
7689
+ const capsuleWidth = LEGEND_CAPSULE_PAD2 * 2 + pillWidth + 4 + eyeSpace + entriesWidth;
7694
7690
  groups.push({
7695
7691
  name: group.name,
7696
7692
  alias: group.alias,
@@ -7701,9 +7697,9 @@ function computeLegendGroups(tagGroups, showEyeIcons, usedValuesByGroup) {
7701
7697
  x: 0,
7702
7698
  y: 0,
7703
7699
  width: capsuleWidth,
7704
- height: LEGEND_HEIGHT,
7700
+ height: LEGEND_HEIGHT2,
7705
7701
  minifiedWidth: minPillWidth,
7706
- minifiedHeight: LEGEND_HEIGHT
7702
+ minifiedHeight: LEGEND_HEIGHT2
7707
7703
  });
7708
7704
  }
7709
7705
  return groups;
@@ -7733,7 +7729,7 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
7733
7729
  for (const g of legendGroups2) {
7734
7730
  g.x = MARGIN;
7735
7731
  g.y = cy;
7736
- cy += LEGEND_HEIGHT + LEGEND_GROUP_GAP;
7732
+ cy += LEGEND_HEIGHT2 + LEGEND_GROUP_GAP2;
7737
7733
  if (g.width > maxWidth2) maxWidth2 = g.width;
7738
7734
  }
7739
7735
  return {
@@ -7742,7 +7738,7 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
7742
7738
  containers: [],
7743
7739
  legend: legendGroups2,
7744
7740
  width: maxWidth2 + MARGIN * 2,
7745
- height: cy - LEGEND_GROUP_GAP + MARGIN
7741
+ height: cy - LEGEND_GROUP_GAP2 + MARGIN
7746
7742
  };
7747
7743
  }
7748
7744
  injectDefaultMetadata(parsed.roots, parsed.tagGroups);
@@ -8272,7 +8268,7 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
8272
8268
  const effectiveH = (g) => activeTagGroup != null || allExpanded ? g.height : g.minifiedHeight;
8273
8269
  if (visibleGroups.length > 0) {
8274
8270
  if (legendPosition === "bottom") {
8275
- const totalGroupsWidth = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP;
8271
+ const totalGroupsWidth = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP2;
8276
8272
  const neededWidth = totalGroupsWidth + MARGIN * 2;
8277
8273
  if (neededWidth > totalWidth) {
8278
8274
  finalWidth = neededWidth;
@@ -8290,22 +8286,22 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
8290
8286
  for (const g of visibleGroups) {
8291
8287
  g.x = cx;
8292
8288
  g.y = legendY;
8293
- cx += effectiveW(g) + LEGEND_GROUP_GAP;
8289
+ cx += effectiveW(g) + LEGEND_GROUP_GAP2;
8294
8290
  }
8295
- finalHeight = totalHeight + LEGEND_GAP + LEGEND_HEIGHT;
8291
+ finalHeight = totalHeight + LEGEND_GAP + LEGEND_HEIGHT2;
8296
8292
  } else {
8297
- const legendShift = LEGEND_HEIGHT + LEGEND_GROUP_GAP;
8293
+ const legendShift = LEGEND_HEIGHT2 + LEGEND_GROUP_GAP2;
8298
8294
  for (const n of layoutNodes) n.y += legendShift;
8299
8295
  for (const c of containers) c.y += legendShift;
8300
8296
  for (const e of layoutEdges) {
8301
8297
  for (const p of e.points) p.y += legendShift;
8302
8298
  }
8303
- const totalGroupsWidth = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP;
8299
+ const totalGroupsWidth = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP2;
8304
8300
  let cx = MARGIN;
8305
8301
  for (const g of visibleGroups) {
8306
8302
  g.x = cx;
8307
8303
  g.y = MARGIN;
8308
- cx += effectiveW(g) + LEGEND_GROUP_GAP;
8304
+ cx += effectiveW(g) + LEGEND_GROUP_GAP2;
8309
8305
  }
8310
8306
  finalHeight += legendShift;
8311
8307
  const neededWidth = totalGroupsWidth + MARGIN * 2;
@@ -8323,7 +8319,7 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
8323
8319
  height: finalHeight
8324
8320
  };
8325
8321
  }
8326
- var import_d3_hierarchy, 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;
8322
+ var import_d3_hierarchy, 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;
8327
8323
  var init_layout = __esm({
8328
8324
  "src/org/layout.ts"() {
8329
8325
  "use strict";
@@ -8345,17 +8341,17 @@ var init_layout = __esm({
8345
8341
  CONTAINER_META_LINE_HEIGHT = 16;
8346
8342
  STACK_V_GAP = 20;
8347
8343
  LEGEND_GAP = 30;
8348
- LEGEND_HEIGHT = 28;
8349
- LEGEND_PILL_PAD = 16;
8350
- LEGEND_PILL_FONT_W = 11 * 0.6;
8351
- LEGEND_CAPSULE_PAD = 4;
8352
- LEGEND_DOT_R = 4;
8353
- LEGEND_ENTRY_FONT_W = 10 * 0.6;
8354
- LEGEND_ENTRY_DOT_GAP = 4;
8355
- LEGEND_ENTRY_TRAIL = 8;
8356
- LEGEND_GROUP_GAP = 12;
8357
- LEGEND_EYE_SIZE = 14;
8358
- LEGEND_EYE_GAP = 6;
8344
+ LEGEND_HEIGHT2 = 28;
8345
+ LEGEND_PILL_PAD2 = 16;
8346
+ LEGEND_PILL_FONT_W2 = 11 * 0.6;
8347
+ LEGEND_CAPSULE_PAD2 = 4;
8348
+ LEGEND_DOT_R2 = 4;
8349
+ LEGEND_ENTRY_FONT_W2 = 10 * 0.6;
8350
+ LEGEND_ENTRY_DOT_GAP2 = 4;
8351
+ LEGEND_ENTRY_TRAIL2 = 8;
8352
+ LEGEND_GROUP_GAP2 = 12;
8353
+ LEGEND_EYE_SIZE2 = 14;
8354
+ LEGEND_EYE_GAP2 = 6;
8359
8355
  }
8360
8356
  });
8361
8357
 
@@ -8452,11 +8448,11 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
8452
8448
  if (width <= 0 || height <= 0) return;
8453
8449
  const titleOffset = parsed.title ? TITLE_HEIGHT : 0;
8454
8450
  const legendOnly = layout.nodes.length === 0;
8455
- const legendPosition = parsed.options?.["legend-position"] ?? "top";
8451
+ const legendPosition = parsed.options?.["legend-position"] ?? "bottom";
8456
8452
  const hasLegend = layout.legend.length > 0;
8457
- const layoutLegendShift = LEGEND_HEIGHT2 + LEGEND_GROUP_GAP2;
8453
+ const layoutLegendShift = LEGEND_HEIGHT + LEGEND_GROUP_GAP;
8458
8454
  const fixedLegend = !exportDims && hasLegend && !legendOnly;
8459
- const legendReserve = fixedLegend ? LEGEND_HEIGHT2 + LEGEND_FIXED_GAP : 0;
8455
+ const legendReserve = fixedLegend ? LEGEND_HEIGHT + LEGEND_FIXED_GAP : 0;
8460
8456
  const fixedTitle = !exportDims && !!parsed.title;
8461
8457
  const titleReserve = fixedTitle ? TITLE_HEIGHT : 0;
8462
8458
  const diagramW = layout.width;
@@ -8611,56 +8607,60 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
8611
8607
  if (fixedLegend && visibleGroups.length > 0) {
8612
8608
  fixedPositions = /* @__PURE__ */ new Map();
8613
8609
  const effectiveW = (g) => activeTagGroup != null ? g.width : g.minifiedWidth;
8614
- const totalW = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP2;
8610
+ const totalW = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP;
8615
8611
  let cx = (width - totalW) / 2;
8616
8612
  for (const g of visibleGroups) {
8617
8613
  fixedPositions.set(g.name, cx);
8618
- cx += effectiveW(g) + LEGEND_GROUP_GAP2;
8614
+ cx += effectiveW(g) + LEGEND_GROUP_GAP;
8619
8615
  }
8620
8616
  }
8621
- const legendParent = fixedLegend ? svg.append("g").attr("class", "org-legend-fixed").attr(
8617
+ const legendParentBase = fixedLegend ? svg.append("g").attr("class", "org-legend-fixed").attr(
8622
8618
  "transform",
8623
- legendPosition === "bottom" ? `translate(0, ${height - DIAGRAM_PADDING - LEGEND_HEIGHT2})` : `translate(0, ${DIAGRAM_PADDING + titleReserve})`
8619
+ legendPosition === "bottom" ? `translate(0, ${height - DIAGRAM_PADDING - LEGEND_HEIGHT})` : `translate(0, ${DIAGRAM_PADDING + titleReserve})`
8624
8620
  ) : contentG;
8621
+ const legendParent = legendParentBase;
8622
+ if (fixedLegend && activeTagGroup) {
8623
+ legendParentBase.attr("data-legend-active", activeTagGroup.toLowerCase());
8624
+ }
8625
8625
  for (const group of visibleGroups) {
8626
8626
  const isActive = legendOnly || activeTagGroup != null && group.name.toLowerCase() === activeTagGroup.toLowerCase();
8627
8627
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
8628
8628
  const pillLabel = group.name;
8629
- const pillWidth = pillLabel.length * LEGEND_PILL_FONT_W2 + LEGEND_PILL_PAD2;
8629
+ const pillWidth = pillLabel.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
8630
8630
  const gX = fixedPositions?.get(group.name) ?? group.x;
8631
8631
  const gY = fixedPositions ? 0 : group.y;
8632
8632
  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");
8633
8633
  if (isActive) {
8634
- gEl.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT2).attr("rx", LEGEND_HEIGHT2 / 2).attr("fill", groupBg);
8634
+ gEl.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
8635
8635
  }
8636
- const pillXOff = isActive ? LEGEND_CAPSULE_PAD2 : 0;
8637
- const pillYOff = isActive ? LEGEND_CAPSULE_PAD2 : 0;
8638
- const pillH = LEGEND_HEIGHT2 - (isActive ? LEGEND_CAPSULE_PAD2 * 2 : 0);
8636
+ const pillXOff = isActive ? LEGEND_CAPSULE_PAD : 0;
8637
+ const pillYOff = isActive ? LEGEND_CAPSULE_PAD : 0;
8638
+ const pillH = LEGEND_HEIGHT - (isActive ? LEGEND_CAPSULE_PAD * 2 : 0);
8639
8639
  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);
8640
8640
  if (isActive) {
8641
8641
  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);
8642
8642
  }
8643
- 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);
8643
+ 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);
8644
8644
  if (isActive && fixedLegend) {
8645
8645
  const groupKey = group.name.toLowerCase();
8646
8646
  const isHidden = hiddenAttributes?.has(groupKey) ?? false;
8647
- const eyeX = pillXOff + pillWidth + LEGEND_EYE_GAP2;
8648
- const eyeY = (LEGEND_HEIGHT2 - LEGEND_EYE_SIZE2) / 2;
8647
+ const eyeX = pillXOff + pillWidth + LEGEND_EYE_GAP;
8648
+ const eyeY = (LEGEND_HEIGHT - LEGEND_EYE_SIZE) / 2;
8649
8649
  const hitPad = 6;
8650
8650
  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);
8651
- 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");
8651
+ 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");
8652
8652
  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");
8653
8653
  }
8654
8654
  if (isActive) {
8655
- const eyeShift = fixedLegend ? LEGEND_EYE_SIZE2 + LEGEND_EYE_GAP2 : 0;
8655
+ const eyeShift = fixedLegend ? LEGEND_EYE_SIZE + LEGEND_EYE_GAP : 0;
8656
8656
  let entryX = pillXOff + pillWidth + 4 + eyeShift;
8657
8657
  for (const entry of group.entries) {
8658
8658
  const entryG = gEl.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
8659
- entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R2).attr("cy", LEGEND_HEIGHT2 / 2).attr("r", LEGEND_DOT_R2).attr("fill", entry.color);
8660
- const textX = entryX + LEGEND_DOT_R2 * 2 + LEGEND_ENTRY_DOT_GAP2;
8659
+ entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
8660
+ const textX = entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
8661
8661
  const entryLabel = entry.value;
8662
- 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);
8663
- entryX = textX + entryLabel.length * LEGEND_ENTRY_FONT_W2 + LEGEND_ENTRY_TRAIL2;
8662
+ 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);
8663
+ entryX = textX + entryLabel.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
8664
8664
  }
8665
8665
  }
8666
8666
  }
@@ -8699,7 +8699,7 @@ function renderOrgForExport(content, theme, palette) {
8699
8699
  document.body.removeChild(container);
8700
8700
  }
8701
8701
  }
8702
- var d3Selection, 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;
8702
+ var d3Selection, 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;
8703
8703
  var init_renderer = __esm({
8704
8704
  "src/org/renderer.ts"() {
8705
8705
  "use strict";
@@ -8708,6 +8708,7 @@ var init_renderer = __esm({
8708
8708
  init_color_utils();
8709
8709
  init_parser4();
8710
8710
  init_layout();
8711
+ init_legend_constants();
8711
8712
  DIAGRAM_PADDING = 20;
8712
8713
  MAX_SCALE = 3;
8713
8714
  TITLE_HEIGHT = 30;
@@ -8727,22 +8728,7 @@ var init_renderer = __esm({
8727
8728
  CONTAINER_HEADER_HEIGHT = 28;
8728
8729
  COLLAPSE_BAR_HEIGHT = 6;
8729
8730
  COLLAPSE_BAR_INSET = 0;
8730
- LEGEND_HEIGHT2 = 28;
8731
- LEGEND_PILL_PAD2 = 16;
8732
- LEGEND_PILL_FONT_SIZE = 11;
8733
- LEGEND_PILL_FONT_W2 = LEGEND_PILL_FONT_SIZE * 0.6;
8734
- LEGEND_CAPSULE_PAD2 = 4;
8735
- LEGEND_DOT_R2 = 4;
8736
- LEGEND_ENTRY_FONT_SIZE = 10;
8737
- LEGEND_ENTRY_FONT_W2 = LEGEND_ENTRY_FONT_SIZE * 0.6;
8738
- LEGEND_ENTRY_DOT_GAP2 = 4;
8739
- LEGEND_ENTRY_TRAIL2 = 8;
8740
- LEGEND_GROUP_GAP2 = 12;
8741
- LEGEND_EYE_SIZE2 = 14;
8742
- LEGEND_EYE_GAP2 = 6;
8743
8731
  LEGEND_FIXED_GAP = 8;
8744
- 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";
8745
- 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";
8746
8732
  }
8747
8733
  });
8748
8734
 
@@ -9166,23 +9152,17 @@ function layoutSitemap(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, e
9166
9152
  const effectiveW = (g2) => activeTagGroup != null || allExpanded ? g2.width : g2.minifiedWidth;
9167
9153
  if (visibleGroups.length > 0) {
9168
9154
  const legendShift = LEGEND_HEIGHT3 + LEGEND_GROUP_GAP3;
9169
- for (const n of layoutNodes) n.y += legendShift;
9170
- for (const c of layoutContainers) c.y += legendShift;
9171
- for (const e of layoutEdges) {
9172
- for (const p of e.points) p.y += legendShift;
9173
- }
9174
9155
  const totalGroupsWidth = visibleGroups.reduce((s, g2) => s + effectiveW(g2), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP3;
9175
- let cx = MARGIN2;
9156
+ const neededWidth = totalGroupsWidth + MARGIN2 * 2;
9157
+ if (neededWidth > totalWidth) totalWidth = neededWidth;
9158
+ let cx = (totalWidth - totalGroupsWidth) / 2;
9159
+ const legendY = totalHeight + LEGEND_GROUP_GAP3;
9176
9160
  for (const g2 of visibleGroups) {
9177
9161
  g2.x = cx;
9178
- g2.y = MARGIN2;
9162
+ g2.y = legendY;
9179
9163
  cx += effectiveW(g2) + LEGEND_GROUP_GAP3;
9180
9164
  }
9181
9165
  totalHeight += legendShift;
9182
- const neededWidth = totalGroupsWidth + MARGIN2 * 2;
9183
- if (neededWidth > totalWidth) {
9184
- totalWidth = neededWidth;
9185
- }
9186
9166
  }
9187
9167
  return {
9188
9168
  nodes: layoutNodes,
@@ -9376,25 +9356,26 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
9376
9356
  const height = exportDims?.height ?? container.clientHeight;
9377
9357
  if (width <= 0 || height <= 0) return;
9378
9358
  const hasLegend = layout.legend.length > 0;
9379
- const layoutLegendShift = LEGEND_HEIGHT4 + LEGEND_GROUP_GAP4;
9359
+ const layoutLegendShift = LEGEND_HEIGHT + LEGEND_GROUP_GAP;
9380
9360
  const fixedLegend = !exportDims && hasLegend;
9381
9361
  const fixedTitle = fixedLegend && !!parsed.title;
9382
9362
  const fixedTitleH = fixedTitle ? TITLE_HEIGHT2 : 0;
9383
- const legendReserveH = fixedLegend ? LEGEND_HEIGHT4 + LEGEND_FIXED_GAP2 : 0;
9384
- const fixedReserve = fixedTitleH + legendReserveH;
9363
+ const legendReserveH = fixedLegend ? LEGEND_HEIGHT + LEGEND_FIXED_GAP2 : 0;
9364
+ const fixedReserveTop = fixedTitleH;
9365
+ const fixedReserveBottom = legendReserveH;
9385
9366
  const titleOffset = !fixedTitle && parsed.title ? TITLE_HEIGHT2 : 0;
9386
9367
  const diagramW = layout.width;
9387
9368
  let diagramH = layout.height + titleOffset;
9388
9369
  if (fixedLegend) {
9389
9370
  diagramH -= layoutLegendShift;
9390
9371
  }
9391
- const availH = height - DIAGRAM_PADDING2 * 2 - fixedReserve;
9372
+ const availH = height - DIAGRAM_PADDING2 * 2 - fixedReserveTop - fixedReserveBottom;
9392
9373
  const scaleX = (width - DIAGRAM_PADDING2 * 2) / diagramW;
9393
9374
  const scaleY = availH / diagramH;
9394
9375
  const scale = Math.min(MAX_SCALE2, scaleX, scaleY);
9395
9376
  const scaledW = diagramW * scale;
9396
9377
  const offsetX = (width - scaledW) / 2;
9397
- const offsetY = DIAGRAM_PADDING2 + fixedReserve;
9378
+ const offsetY = DIAGRAM_PADDING2 + fixedReserveTop;
9398
9379
  const svg = d3Selection2.select(container).append("svg").attr("width", width).attr("height", height).style("font-family", FONT_FAMILY);
9399
9380
  const defs = svg.append("defs");
9400
9381
  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);
@@ -9535,7 +9516,10 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
9535
9516
  titleEl.text(parsed.title);
9536
9517
  }
9537
9518
  if (fixedLegend) {
9538
- const legendParent = svg.append("g").attr("class", "sitemap-legend-fixed").attr("transform", `translate(0, ${DIAGRAM_PADDING2 + fixedTitleH})`);
9519
+ const legendParent = svg.append("g").attr("class", "sitemap-legend-fixed").attr("transform", `translate(0, ${height - DIAGRAM_PADDING2 - LEGEND_HEIGHT})`);
9520
+ if (activeTagGroup) {
9521
+ legendParent.attr("data-legend-active", activeTagGroup.toLowerCase());
9522
+ }
9539
9523
  renderLegend(legendParent, layout.legend, palette, isDark, activeTagGroup, width, hiddenAttributes);
9540
9524
  }
9541
9525
  }
@@ -9547,49 +9531,49 @@ function renderLegend(parent, legendGroups, palette, isDark, activeTagGroup, fix
9547
9531
  if (fixedWidth != null && visibleGroups.length > 0) {
9548
9532
  fixedPositions = /* @__PURE__ */ new Map();
9549
9533
  const effectiveW = (g) => activeTagGroup != null ? g.width : g.minifiedWidth;
9550
- const totalW = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP4;
9534
+ const totalW = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP;
9551
9535
  let cx = (fixedWidth - totalW) / 2;
9552
9536
  for (const g of visibleGroups) {
9553
9537
  fixedPositions.set(g.name, cx);
9554
- cx += effectiveW(g) + LEGEND_GROUP_GAP4;
9538
+ cx += effectiveW(g) + LEGEND_GROUP_GAP;
9555
9539
  }
9556
9540
  }
9557
9541
  for (const group of visibleGroups) {
9558
9542
  const isActive = activeTagGroup != null;
9559
- const pillW = group.name.length * LEGEND_PILL_FONT_W4 + LEGEND_PILL_PAD4;
9543
+ const pillW = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
9560
9544
  const gX = fixedPositions?.get(group.name) ?? group.x;
9561
9545
  const gY = fixedPositions ? 0 : group.y;
9562
9546
  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");
9563
9547
  if (isActive) {
9564
- legendG.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT4).attr("rx", LEGEND_HEIGHT4 / 2).attr("fill", groupBg);
9548
+ legendG.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
9565
9549
  }
9566
- const pillXOff = isActive ? LEGEND_CAPSULE_PAD4 : 0;
9567
- const pillYOff = isActive ? LEGEND_CAPSULE_PAD4 : 0;
9568
- const pillH = LEGEND_HEIGHT4 - (isActive ? LEGEND_CAPSULE_PAD4 * 2 : 0);
9550
+ const pillXOff = isActive ? LEGEND_CAPSULE_PAD : 0;
9551
+ const pillYOff = isActive ? LEGEND_CAPSULE_PAD : 0;
9552
+ const pillH = LEGEND_HEIGHT - (isActive ? LEGEND_CAPSULE_PAD * 2 : 0);
9569
9553
  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);
9570
9554
  if (isActive) {
9571
9555
  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);
9572
9556
  }
9573
- 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);
9557
+ 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);
9574
9558
  if (isActive && fixedWidth != null) {
9575
9559
  const groupKey = group.name.toLowerCase();
9576
9560
  const isHidden = hiddenAttributes?.has(groupKey) ?? false;
9577
- const eyeX = pillXOff + pillW + LEGEND_EYE_GAP4;
9578
- const eyeY = (LEGEND_HEIGHT4 - LEGEND_EYE_SIZE4) / 2;
9561
+ const eyeX = pillXOff + pillW + LEGEND_EYE_GAP;
9562
+ const eyeY = (LEGEND_HEIGHT - LEGEND_EYE_SIZE) / 2;
9579
9563
  const hitPad = 6;
9580
9564
  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);
9581
- 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");
9582
- 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");
9565
+ 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");
9566
+ 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");
9583
9567
  }
9584
9568
  if (isActive) {
9585
- const eyeShift = fixedWidth != null ? LEGEND_EYE_SIZE4 + LEGEND_EYE_GAP4 : 0;
9569
+ const eyeShift = fixedWidth != null ? LEGEND_EYE_SIZE + LEGEND_EYE_GAP : 0;
9586
9570
  let entryX = pillXOff + pillW + 4 + eyeShift;
9587
9571
  for (const entry of group.entries) {
9588
9572
  const entryG = legendG.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
9589
- entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R4).attr("cy", LEGEND_HEIGHT4 / 2).attr("r", LEGEND_DOT_R4).attr("fill", entry.color);
9590
- const textX = entryX + LEGEND_DOT_R4 * 2 + LEGEND_ENTRY_DOT_GAP4;
9591
- 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);
9592
- entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W4 + LEGEND_ENTRY_TRAIL4;
9573
+ entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
9574
+ const textX = entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
9575
+ 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);
9576
+ entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
9593
9577
  }
9594
9578
  }
9595
9579
  }
@@ -9635,7 +9619,7 @@ async function renderSitemapForExport(content, theme, palette) {
9635
9619
  const brandColor = theme === "transparent" ? "#888" : effectivePalette.textMuted;
9636
9620
  return injectBranding2(svgHtml, brandColor);
9637
9621
  }
9638
- var d3Selection2, d3Shape, 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;
9622
+ var d3Selection2, d3Shape, 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;
9639
9623
  var init_renderer2 = __esm({
9640
9624
  "src/sitemap/renderer.ts"() {
9641
9625
  "use strict";
@@ -9643,6 +9627,7 @@ var init_renderer2 = __esm({
9643
9627
  d3Shape = __toESM(require("d3-shape"), 1);
9644
9628
  init_fonts();
9645
9629
  init_color_utils();
9630
+ init_legend_constants();
9646
9631
  DIAGRAM_PADDING2 = 20;
9647
9632
  MAX_SCALE2 = 3;
9648
9633
  TITLE_HEIGHT2 = 30;
@@ -9664,23 +9649,8 @@ var init_renderer2 = __esm({
9664
9649
  ARROWHEAD_H = 7;
9665
9650
  EDGE_LABEL_FONT_SIZE = 11;
9666
9651
  COLLAPSE_BAR_HEIGHT2 = 6;
9667
- LEGEND_HEIGHT4 = 28;
9668
9652
  LEGEND_FIXED_GAP2 = 8;
9669
- LEGEND_PILL_PAD4 = 16;
9670
- LEGEND_PILL_FONT_SIZE2 = 11;
9671
- LEGEND_PILL_FONT_W4 = LEGEND_PILL_FONT_SIZE2 * 0.6;
9672
- LEGEND_CAPSULE_PAD4 = 4;
9673
- LEGEND_DOT_R4 = 4;
9674
- LEGEND_ENTRY_FONT_SIZE2 = 10;
9675
- LEGEND_ENTRY_FONT_W4 = LEGEND_ENTRY_FONT_SIZE2 * 0.6;
9676
- LEGEND_ENTRY_DOT_GAP4 = 4;
9677
- LEGEND_ENTRY_TRAIL4 = 8;
9678
- LEGEND_GROUP_GAP4 = 12;
9679
- LEGEND_EYE_SIZE4 = 14;
9680
- LEGEND_EYE_GAP4 = 6;
9681
9653
  lineGenerator = d3Shape.line().x((d) => d.x).y((d) => d.y).curve(d3Shape.curveBasis);
9682
- 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";
9683
- 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";
9684
9654
  }
9685
9655
  });
9686
9656
 
@@ -9882,8 +9852,7 @@ function resolveCardTagColor(card, tagGroups, activeTagGroup) {
9882
9852
  return entry?.color;
9883
9853
  }
9884
9854
  function computeLayout(parsed, _palette) {
9885
- const hasHeader = !!parsed.title || parsed.tagGroups.length > 0;
9886
- const headerHeight = hasHeader ? Math.max(TITLE_HEIGHT3, LEGEND_HEIGHT5) + 8 : 0;
9855
+ const headerHeight = parsed.title ? TITLE_HEIGHT3 + 8 : 0;
9887
9856
  const startY = DIAGRAM_PADDING3 + headerHeight;
9888
9857
  const charWidth = CARD_TITLE_FONT_SIZE * 0.6;
9889
9858
  const columnLayouts = [];
@@ -9940,7 +9909,8 @@ function computeLayout(parsed, _palette) {
9940
9909
  currentX += cl.width + COLUMN_GAP;
9941
9910
  }
9942
9911
  const totalWidth = currentX - COLUMN_GAP + DIAGRAM_PADDING3;
9943
- const totalHeight = startY + maxColumnHeight + DIAGRAM_PADDING3;
9912
+ const legendSpace = parsed.tagGroups.length > 0 ? LEGEND_HEIGHT : 0;
9913
+ const totalHeight = startY + maxColumnHeight + DIAGRAM_PADDING3 + legendSpace;
9944
9914
  return { columns: columnLayouts, totalWidth, totalHeight };
9945
9915
  }
9946
9916
  function renderKanban(container, parsed, palette, isDark, _onNavigateToLine, exportDims, activeTagGroup) {
@@ -9953,42 +9923,45 @@ function renderKanban(container, parsed, palette, isDark, _onNavigateToLine, exp
9953
9923
  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);
9954
9924
  }
9955
9925
  if (parsed.tagGroups.length > 0) {
9956
- const legendY = DIAGRAM_PADDING3;
9957
- const titleTextWidth = parsed.title ? parsed.title.length * TITLE_FONT_SIZE3 * 0.6 + 16 : 0;
9958
- let legendX = DIAGRAM_PADDING3 + titleTextWidth;
9926
+ const legendY = height - LEGEND_HEIGHT;
9927
+ let legendX = DIAGRAM_PADDING3;
9959
9928
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
9960
- const capsulePad = 4;
9929
+ const capsulePad = LEGEND_CAPSULE_PAD;
9930
+ const legendContainer = svg.append("g").attr("class", "kanban-legend");
9931
+ if (activeTagGroup) {
9932
+ legendContainer.attr("data-legend-active", activeTagGroup.toLowerCase());
9933
+ }
9961
9934
  for (const group of parsed.tagGroups) {
9962
9935
  const isActive = activeTagGroup?.toLowerCase() === group.name.toLowerCase();
9963
9936
  if (activeTagGroup != null && !isActive) continue;
9964
- const pillTextWidth = group.name.length * LEGEND_FONT_SIZE * 0.6;
9937
+ const pillTextWidth = group.name.length * LEGEND_PILL_FONT_SIZE * 0.6;
9965
9938
  const pillWidth = pillTextWidth + 16;
9966
9939
  let capsuleContentWidth = pillWidth;
9967
9940
  if (isActive) {
9968
9941
  capsuleContentWidth += 4;
9969
9942
  for (const entry of group.entries) {
9970
- capsuleContentWidth += LEGEND_DOT_R5 * 2 + 4 + entry.value.length * LEGEND_ENTRY_FONT_SIZE3 * 0.6 + 8;
9943
+ capsuleContentWidth += LEGEND_DOT_R * 2 + 4 + entry.value.length * LEGEND_ENTRY_FONT_SIZE * 0.6 + 8;
9971
9944
  }
9972
9945
  }
9973
9946
  const capsuleWidth = capsuleContentWidth + capsulePad * 2;
9974
9947
  if (isActive) {
9975
- svg.append("rect").attr("x", legendX).attr("y", legendY).attr("width", capsuleWidth).attr("height", LEGEND_HEIGHT5).attr("rx", LEGEND_HEIGHT5 / 2).attr("fill", groupBg);
9948
+ legendContainer.append("rect").attr("x", legendX).attr("y", legendY).attr("width", capsuleWidth).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
9976
9949
  }
9977
9950
  const pillX = legendX + (isActive ? capsulePad : 0);
9978
9951
  const pillBg = isActive ? palette.bg : groupBg;
9979
- 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());
9952
+ 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());
9980
9953
  if (isActive) {
9981
- 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);
9954
+ 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);
9982
9955
  }
9983
- 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);
9956
+ 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);
9984
9957
  if (isActive) {
9985
9958
  let entryX = pillX + pillWidth + 4;
9986
9959
  for (const entry of group.entries) {
9987
- const entryG = svg.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
9988
- entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R5).attr("cy", legendY + LEGEND_HEIGHT5 / 2).attr("r", LEGEND_DOT_R5).attr("fill", entry.color);
9989
- const entryTextX = entryX + LEGEND_DOT_R5 * 2 + 4;
9990
- 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);
9991
- entryX = entryTextX + entry.value.length * LEGEND_ENTRY_FONT_SIZE3 * 0.6 + 8;
9960
+ const entryG = legendContainer.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
9961
+ entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R).attr("cy", legendY + LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
9962
+ const entryTextX = entryX + LEGEND_DOT_R * 2 + 4;
9963
+ 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);
9964
+ entryX = entryTextX + entry.value.length * LEGEND_ENTRY_FONT_SIZE * 0.6 + 8;
9992
9965
  }
9993
9966
  legendX += capsuleWidth + 12;
9994
9967
  } else {
@@ -10075,7 +10048,7 @@ function renderKanbanForExport(content, theme, palette) {
10075
10048
  const svgEl = container.querySelector("svg");
10076
10049
  return svgEl?.outerHTML ?? "";
10077
10050
  }
10078
- var d3Selection3, 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;
10051
+ var d3Selection3, 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;
10079
10052
  var init_renderer3 = __esm({
10080
10053
  "src/kanban/renderer.ts"() {
10081
10054
  "use strict";
@@ -10085,6 +10058,7 @@ var init_renderer3 = __esm({
10085
10058
  init_inline_markdown();
10086
10059
  init_parser5();
10087
10060
  init_mutations();
10061
+ init_legend_constants();
10088
10062
  DIAGRAM_PADDING3 = 20;
10089
10063
  COLUMN_GAP = 16;
10090
10064
  COLUMN_HEADER_HEIGHT = 36;
@@ -10106,10 +10080,6 @@ var init_renderer3 = __esm({
10106
10080
  WIP_FONT_SIZE = 10;
10107
10081
  COLUMN_RADIUS = 8;
10108
10082
  COLUMN_HEADER_RADIUS = 8;
10109
- LEGEND_HEIGHT5 = 28;
10110
- LEGEND_FONT_SIZE = 11;
10111
- LEGEND_DOT_R5 = 4;
10112
- LEGEND_ENTRY_FONT_SIZE3 = 10;
10113
10083
  }
10114
10084
  });
10115
10085
 
@@ -10823,32 +10793,31 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10823
10793
  }
10824
10794
  }
10825
10795
  if (parsed.tagGroups.length > 0) {
10826
- const LEGEND_Y_PAD = 16;
10827
- const LEGEND_PILL_H = 22;
10828
- const LEGEND_PILL_RX = 11;
10829
- const LEGEND_PILL_PAD9 = 10;
10796
+ const LEGEND_PILL_H = LEGEND_HEIGHT - 6;
10797
+ const LEGEND_PILL_RX = Math.floor(LEGEND_PILL_H / 2);
10830
10798
  const LEGEND_GAP2 = 8;
10831
- const LEGEND_FONT_SIZE2 = 11;
10832
- const LEGEND_GROUP_GAP7 = 16;
10833
10799
  const legendG = svg.append("g").attr("class", "er-tag-legend");
10800
+ if (activeTagGroup) {
10801
+ legendG.attr("data-legend-active", activeTagGroup.toLowerCase());
10802
+ }
10834
10803
  let legendX = DIAGRAM_PADDING5;
10835
10804
  let legendY = height - DIAGRAM_PADDING5;
10836
10805
  for (const group of parsed.tagGroups) {
10837
10806
  const groupG = legendG.append("g").attr("data-legend-group", group.name.toLowerCase());
10838
- 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}:`);
10807
+ 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}:`);
10839
10808
  const labelWidth = (labelText.node()?.getComputedTextLength?.() ?? group.name.length * 7) + 6;
10840
10809
  legendX += labelWidth;
10841
10810
  for (const entry of group.entries) {
10842
10811
  const pillG = groupG.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
10843
- const tmpText = legendG.append("text").attr("font-size", LEGEND_FONT_SIZE2).attr("font-family", FONT_FAMILY).text(entry.value);
10812
+ const tmpText = legendG.append("text").attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-family", FONT_FAMILY).text(entry.value);
10844
10813
  const textW = tmpText.node()?.getComputedTextLength?.() ?? entry.value.length * 7;
10845
10814
  tmpText.remove();
10846
- const pillW = textW + LEGEND_PILL_PAD9 * 2;
10815
+ const pillW = textW + LEGEND_PILL_PAD * 2;
10847
10816
  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);
10848
- 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);
10817
+ 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);
10849
10818
  legendX += pillW + LEGEND_GAP2;
10850
10819
  }
10851
- legendX += LEGEND_GROUP_GAP7;
10820
+ legendX += LEGEND_GROUP_GAP;
10852
10821
  }
10853
10822
  }
10854
10823
  }
@@ -10897,6 +10866,7 @@ var init_renderer5 = __esm({
10897
10866
  init_color_utils();
10898
10867
  init_palettes();
10899
10868
  init_tag_groups();
10869
+ init_legend_constants();
10900
10870
  init_parser3();
10901
10871
  init_layout4();
10902
10872
  DIAGRAM_PADDING5 = 20;
@@ -11055,12 +11025,14 @@ function layoutInitiativeStatus(parsed, collapseResult) {
11055
11025
  }
11056
11026
  } else if (isYDisplaced) {
11057
11027
  const exitY = tgt.y > src.y + NODESEP ? src.y + src.height / 2 : src.y - src.height / 2;
11058
- const midX = Math.max(src.x + 1, (src.x + enterX) / 2);
11059
- const midY = (exitY + tgt.y) / 2;
11028
+ const spreadExitX = src.x + yOffset;
11029
+ const spreadEntryY = tgt.y + yOffset;
11030
+ const midX = (spreadExitX + enterX) / 2;
11031
+ const midY = (exitY + spreadEntryY) / 2;
11060
11032
  points = [
11061
- { x: src.x, y: exitY },
11033
+ { x: spreadExitX, y: exitY },
11062
11034
  { x: midX, y: midY },
11063
- { x: enterX, y: tgt.y }
11035
+ { x: enterX, y: spreadEntryY }
11064
11036
  ];
11065
11037
  } else if (tgt.x > src.x && !hasIntermediateRank) {
11066
11038
  points = [
@@ -11995,31 +11967,27 @@ function computeC4NodeDimensions(el, options) {
11995
11967
  height += CARD_V_PAD3;
11996
11968
  return { width, height };
11997
11969
  }
11998
- function computeLegendGroups3(tagGroups, usedValuesByGroup) {
11970
+ function computeLegendGroups3(tagGroups) {
11999
11971
  const result = [];
12000
11972
  for (const group of tagGroups) {
12001
11973
  const entries = [];
12002
11974
  for (const entry of group.entries) {
12003
- if (usedValuesByGroup) {
12004
- const used = usedValuesByGroup.get(group.name.toLowerCase());
12005
- if (!used?.has(entry.value.toLowerCase())) continue;
12006
- }
12007
11975
  entries.push({ value: entry.value, color: entry.color });
12008
11976
  }
12009
11977
  if (entries.length === 0) continue;
12010
- const nameW = group.name.length * LEGEND_PILL_FONT_W5 + LEGEND_PILL_PAD5 * 2;
12011
- let capsuleW = LEGEND_CAPSULE_PAD5;
11978
+ const nameW = group.name.length * LEGEND_PILL_FONT_W4 + LEGEND_PILL_PAD4 * 2;
11979
+ let capsuleW = LEGEND_CAPSULE_PAD4;
12012
11980
  for (const e of entries) {
12013
- capsuleW += LEGEND_DOT_R6 * 2 + LEGEND_ENTRY_DOT_GAP5 + e.value.length * LEGEND_ENTRY_FONT_W5 + LEGEND_ENTRY_TRAIL5;
11981
+ capsuleW += LEGEND_DOT_R4 * 2 + LEGEND_ENTRY_DOT_GAP4 + e.value.length * LEGEND_ENTRY_FONT_W4 + LEGEND_ENTRY_TRAIL4;
12014
11982
  }
12015
- capsuleW += LEGEND_CAPSULE_PAD5;
11983
+ capsuleW += LEGEND_CAPSULE_PAD4;
12016
11984
  result.push({
12017
11985
  name: group.name,
12018
11986
  entries,
12019
11987
  x: 0,
12020
11988
  y: 0,
12021
11989
  width: nameW + capsuleW,
12022
- height: LEGEND_HEIGHT6
11990
+ height: LEGEND_HEIGHT4
12023
11991
  });
12024
11992
  }
12025
11993
  return result;
@@ -12139,18 +12107,7 @@ function layoutC4Context(parsed, activeTagGroup) {
12139
12107
  }
12140
12108
  let totalWidth = nodes.length > 0 ? maxX - minX + MARGIN3 * 2 : 0;
12141
12109
  let totalHeight = nodes.length > 0 ? maxY - minY + MARGIN3 * 2 : 0;
12142
- const usedValuesByGroup = /* @__PURE__ */ new Map();
12143
- for (const el of contextElements) {
12144
- for (const group of parsed.tagGroups) {
12145
- const key = group.name.toLowerCase();
12146
- const val = el.metadata[key];
12147
- if (val) {
12148
- if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, /* @__PURE__ */ new Set());
12149
- usedValuesByGroup.get(key).add(val.toLowerCase());
12150
- }
12151
- }
12152
- }
12153
- const legendGroups = computeLegendGroups3(parsed.tagGroups, usedValuesByGroup);
12110
+ const legendGroups = computeLegendGroups3(parsed.tagGroups);
12154
12111
  if (legendGroups.length > 0) {
12155
12112
  const legendY = totalHeight + MARGIN3;
12156
12113
  let legendX = MARGIN3;
@@ -12160,7 +12117,7 @@ function layoutC4Context(parsed, activeTagGroup) {
12160
12117
  legendX += lg.width + 12;
12161
12118
  }
12162
12119
  const legendRight = legendX;
12163
- const legendBottom = legendY + LEGEND_HEIGHT6;
12120
+ const legendBottom = legendY + LEGEND_HEIGHT4;
12164
12121
  if (legendRight > totalWidth) totalWidth = legendRight;
12165
12122
  if (legendBottom > totalHeight) totalHeight = legendBottom;
12166
12123
  }
@@ -12466,18 +12423,7 @@ function layoutC4Containers(parsed, systemName, activeTagGroup) {
12466
12423
  }
12467
12424
  let totalWidth = maxX - minX + MARGIN3 * 2;
12468
12425
  let totalHeight = maxY - minY + MARGIN3 * 2;
12469
- const usedValuesByGroup = /* @__PURE__ */ new Map();
12470
- for (const el of [...containers, ...externals]) {
12471
- for (const group of parsed.tagGroups) {
12472
- const key = group.name.toLowerCase();
12473
- const val = el.metadata[key];
12474
- if (val) {
12475
- if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, /* @__PURE__ */ new Set());
12476
- usedValuesByGroup.get(key).add(val.toLowerCase());
12477
- }
12478
- }
12479
- }
12480
- const legendGroups = computeLegendGroups3(parsed.tagGroups, usedValuesByGroup);
12426
+ const legendGroups = computeLegendGroups3(parsed.tagGroups);
12481
12427
  if (legendGroups.length > 0) {
12482
12428
  const legendY = totalHeight + MARGIN3;
12483
12429
  let legendX = MARGIN3;
@@ -12487,7 +12433,7 @@ function layoutC4Containers(parsed, systemName, activeTagGroup) {
12487
12433
  legendX += lg.width + 12;
12488
12434
  }
12489
12435
  const legendRight = legendX;
12490
- const legendBottom = legendY + LEGEND_HEIGHT6;
12436
+ const legendBottom = legendY + LEGEND_HEIGHT4;
12491
12437
  if (legendRight > totalWidth) totalWidth = legendRight;
12492
12438
  if (legendBottom > totalHeight) totalHeight = legendBottom;
12493
12439
  }
@@ -12842,21 +12788,7 @@ function layoutC4Components(parsed, systemName, containerName, activeTagGroup) {
12842
12788
  }
12843
12789
  let totalWidth = maxX - minX + MARGIN3 * 2;
12844
12790
  let totalHeight = maxY - minY + MARGIN3 * 2;
12845
- const usedValuesByGroup = /* @__PURE__ */ new Map();
12846
- for (const el of [...components, ...externals]) {
12847
- for (const group of parsed.tagGroups) {
12848
- const key = group.name.toLowerCase();
12849
- let val = el.metadata[key];
12850
- if (!val && components.includes(el)) {
12851
- val = targetContainer.metadata[key] ?? system.metadata[key];
12852
- }
12853
- if (val) {
12854
- if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, /* @__PURE__ */ new Set());
12855
- usedValuesByGroup.get(key).add(val.toLowerCase());
12856
- }
12857
- }
12858
- }
12859
- const legendGroups = computeLegendGroups3(parsed.tagGroups, usedValuesByGroup);
12791
+ const legendGroups = computeLegendGroups3(parsed.tagGroups);
12860
12792
  if (legendGroups.length > 0) {
12861
12793
  const legendY = totalHeight + MARGIN3;
12862
12794
  let legendX = MARGIN3;
@@ -12866,7 +12798,7 @@ function layoutC4Components(parsed, systemName, containerName, activeTagGroup) {
12866
12798
  legendX += lg.width + 12;
12867
12799
  }
12868
12800
  const legendRight = legendX;
12869
- const legendBottom = legendY + LEGEND_HEIGHT6;
12801
+ const legendBottom = legendY + LEGEND_HEIGHT4;
12870
12802
  if (legendRight > totalWidth) totalWidth = legendRight;
12871
12803
  if (legendBottom > totalHeight) totalHeight = legendBottom;
12872
12804
  }
@@ -13111,18 +13043,7 @@ function layoutC4Deployment(parsed, activeTagGroup) {
13111
13043
  }
13112
13044
  let totalWidth = maxX - minX + MARGIN3 * 2;
13113
13045
  let totalHeight = maxY - minY + MARGIN3 * 2;
13114
- const usedValuesByGroup = /* @__PURE__ */ new Map();
13115
- for (const r of refEntries) {
13116
- for (const group of parsed.tagGroups) {
13117
- const key = group.name.toLowerCase();
13118
- const val = r.element.metadata[key];
13119
- if (val) {
13120
- if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, /* @__PURE__ */ new Set());
13121
- usedValuesByGroup.get(key).add(val.toLowerCase());
13122
- }
13123
- }
13124
- }
13125
- const legendGroups = computeLegendGroups3(parsed.tagGroups, usedValuesByGroup);
13046
+ const legendGroups = computeLegendGroups3(parsed.tagGroups);
13126
13047
  if (legendGroups.length > 0) {
13127
13048
  const legendY = totalHeight + MARGIN3;
13128
13049
  let legendX = MARGIN3;
@@ -13132,13 +13053,13 @@ function layoutC4Deployment(parsed, activeTagGroup) {
13132
13053
  legendX += lg.width + 12;
13133
13054
  }
13134
13055
  const legendRight = legendX;
13135
- const legendBottom = legendY + LEGEND_HEIGHT6;
13056
+ const legendBottom = legendY + LEGEND_HEIGHT4;
13136
13057
  if (legendRight > totalWidth) totalWidth = legendRight;
13137
13058
  if (legendBottom > totalHeight) totalHeight = legendBottom;
13138
13059
  }
13139
13060
  return { nodes, edges, legend: legendGroups, groupBoundaries, width: totalWidth, height: totalHeight };
13140
13061
  }
13141
- var import_dagre5, 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;
13062
+ var import_dagre5, 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;
13142
13063
  var init_layout6 = __esm({
13143
13064
  "src/c4/layout.ts"() {
13144
13065
  "use strict";
@@ -13158,16 +13079,16 @@ var init_layout6 = __esm({
13158
13079
  MARGIN3 = 40;
13159
13080
  BOUNDARY_PAD = 40;
13160
13081
  GROUP_BOUNDARY_PAD = 24;
13161
- LEGEND_HEIGHT6 = 28;
13162
- LEGEND_PILL_FONT_SIZE3 = 11;
13163
- LEGEND_PILL_FONT_W5 = LEGEND_PILL_FONT_SIZE3 * 0.6;
13164
- LEGEND_PILL_PAD5 = 16;
13165
- LEGEND_DOT_R6 = 4;
13166
- LEGEND_ENTRY_FONT_SIZE4 = 10;
13167
- LEGEND_ENTRY_FONT_W5 = LEGEND_ENTRY_FONT_SIZE4 * 0.6;
13168
- LEGEND_ENTRY_DOT_GAP5 = 4;
13169
- LEGEND_ENTRY_TRAIL5 = 8;
13170
- LEGEND_CAPSULE_PAD5 = 4;
13082
+ LEGEND_HEIGHT4 = 28;
13083
+ LEGEND_PILL_FONT_SIZE2 = 11;
13084
+ LEGEND_PILL_FONT_W4 = LEGEND_PILL_FONT_SIZE2 * 0.6;
13085
+ LEGEND_PILL_PAD4 = 16;
13086
+ LEGEND_DOT_R4 = 4;
13087
+ LEGEND_ENTRY_FONT_SIZE2 = 10;
13088
+ LEGEND_ENTRY_FONT_W4 = LEGEND_ENTRY_FONT_SIZE2 * 0.6;
13089
+ LEGEND_ENTRY_DOT_GAP4 = 4;
13090
+ LEGEND_ENTRY_TRAIL4 = 8;
13091
+ LEGEND_CAPSULE_PAD4 = 4;
13171
13092
  META_EXCLUDE_KEYS = /* @__PURE__ */ new Set(["description", "tech", "technology", "is a"]);
13172
13093
  }
13173
13094
  });
@@ -13243,8 +13164,14 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
13243
13164
  if (width <= 0 || height <= 0) return;
13244
13165
  const titleHeight = parsed.title ? TITLE_HEIGHT4 + 10 : 0;
13245
13166
  const diagramW = layout.width;
13246
- const diagramH = layout.height;
13247
- const availH = height - titleHeight;
13167
+ const hasLegend = layout.legend.length > 0;
13168
+ const C4_LAYOUT_MARGIN = 40;
13169
+ const LEGEND_FIXED_GAP4 = 8;
13170
+ const fixedLegend = !exportDims && hasLegend;
13171
+ const legendLayoutSpace = C4_LAYOUT_MARGIN + LEGEND_HEIGHT;
13172
+ const legendReserveH = fixedLegend ? LEGEND_HEIGHT + LEGEND_FIXED_GAP4 : 0;
13173
+ const diagramH = fixedLegend ? layout.height - legendLayoutSpace : layout.height;
13174
+ const availH = height - titleHeight - legendReserveH;
13248
13175
  const scaleX = (width - DIAGRAM_PADDING7 * 2) / diagramW;
13249
13176
  const scaleY = (availH - DIAGRAM_PADDING7 * 2) / diagramH;
13250
13177
  const scale = Math.min(MAX_SCALE6, scaleX, scaleY);
@@ -13316,6 +13243,20 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
13316
13243
  }
13317
13244
  for (const node of layout.nodes) {
13318
13245
  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);
13246
+ if (activeTagGroup) {
13247
+ const tagKey = activeTagGroup.toLowerCase();
13248
+ const tagValue = node.metadata[tagKey];
13249
+ if (tagValue) {
13250
+ nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
13251
+ } else {
13252
+ const tagGroup = parsed.tagGroups.find(
13253
+ (g) => g.name.toLowerCase() === tagKey || g.alias?.toLowerCase() === tagKey
13254
+ );
13255
+ if (tagGroup?.defaultValue) {
13256
+ nodeG.attr(`data-tag-${tagKey}`, tagGroup.defaultValue.toLowerCase());
13257
+ }
13258
+ }
13259
+ }
13319
13260
  if (node.importPath) {
13320
13261
  nodeG.attr("data-import-path", node.importPath);
13321
13262
  }
@@ -13368,36 +13309,12 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
13368
13309
  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");
13369
13310
  }
13370
13311
  }
13371
- if (!exportDims) {
13372
- for (const group of layout.legend) {
13373
- const isActive = activeTagGroup != null && group.name.toLowerCase() === (activeTagGroup ?? "").toLowerCase();
13374
- if (activeTagGroup != null && !isActive) continue;
13375
- const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
13376
- const pillLabel = group.name;
13377
- const pillWidth = pillLabel.length * LEGEND_PILL_FONT_W6 + LEGEND_PILL_PAD6;
13378
- 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");
13379
- if (isActive) {
13380
- gEl.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT7).attr("rx", LEGEND_HEIGHT7 / 2).attr("fill", groupBg);
13381
- }
13382
- const pillX = isActive ? LEGEND_CAPSULE_PAD6 : 0;
13383
- const pillY = isActive ? LEGEND_CAPSULE_PAD6 : 0;
13384
- const pillH = LEGEND_HEIGHT7 - (isActive ? LEGEND_CAPSULE_PAD6 * 2 : 0);
13385
- 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);
13386
- if (isActive) {
13387
- 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);
13388
- }
13389
- 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);
13390
- if (isActive) {
13391
- let entryX = pillX + pillWidth + 4;
13392
- for (const entry of group.entries) {
13393
- const entryG = gEl.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
13394
- entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R7).attr("cy", LEGEND_HEIGHT7 / 2).attr("r", LEGEND_DOT_R7).attr("fill", entry.color);
13395
- const textX = entryX + LEGEND_DOT_R7 * 2 + LEGEND_ENTRY_DOT_GAP6;
13396
- 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);
13397
- entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W6 + LEGEND_ENTRY_TRAIL6;
13398
- }
13399
- }
13312
+ if (hasLegend) {
13313
+ 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");
13314
+ if (activeTagGroup) {
13315
+ legendParent.attr("data-legend-active", activeTagGroup.toLowerCase());
13400
13316
  }
13317
+ renderLegend2(legendParent, layout, palette, isDark, activeTagGroup, fixedLegend ? width : null);
13401
13318
  }
13402
13319
  }
13403
13320
  function renderC4ContextForExport(content, theme, palette) {
@@ -13685,33 +13602,47 @@ function placeEdgeLabels(labels, edges, obstacleRects) {
13685
13602
  placedRects.push({ x: lbl.x, y: lbl.y, w: lbl.bgW, h: lbl.bgH });
13686
13603
  }
13687
13604
  }
13688
- function renderLegend2(contentG, layout, palette, isDark, activeTagGroup) {
13689
- for (const group of layout.legend) {
13605
+ function renderLegend2(parent, layout, palette, isDark, activeTagGroup, fixedWidth) {
13606
+ const visibleGroups = activeTagGroup != null ? layout.legend.filter((g) => g.name.toLowerCase() === (activeTagGroup ?? "").toLowerCase()) : layout.legend;
13607
+ const pillWidthOf = (g) => g.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
13608
+ const effectiveW = (g) => activeTagGroup != null ? g.width : pillWidthOf(g);
13609
+ let fixedPositions = null;
13610
+ if (fixedWidth != null && visibleGroups.length > 0) {
13611
+ fixedPositions = /* @__PURE__ */ new Map();
13612
+ const totalW = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP;
13613
+ let cx = Math.max(DIAGRAM_PADDING7, (fixedWidth - totalW) / 2);
13614
+ for (const g of visibleGroups) {
13615
+ fixedPositions.set(g.name, cx);
13616
+ cx += effectiveW(g) + LEGEND_GROUP_GAP;
13617
+ }
13618
+ }
13619
+ for (const group of visibleGroups) {
13690
13620
  const isActive = activeTagGroup != null && group.name.toLowerCase() === (activeTagGroup ?? "").toLowerCase();
13691
- if (activeTagGroup != null && !isActive) continue;
13692
13621
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
13693
13622
  const pillLabel = group.name;
13694
- const pillWidth = pillLabel.length * LEGEND_PILL_FONT_W6 + LEGEND_PILL_PAD6;
13695
- 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");
13623
+ const pillWidth = pillWidthOf(group);
13624
+ const gX = fixedPositions?.get(group.name) ?? group.x;
13625
+ const gY = fixedPositions != null ? 0 : group.y;
13626
+ 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");
13696
13627
  if (isActive) {
13697
- gEl.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT7).attr("rx", LEGEND_HEIGHT7 / 2).attr("fill", groupBg);
13628
+ gEl.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
13698
13629
  }
13699
- const pillX = isActive ? LEGEND_CAPSULE_PAD6 : 0;
13700
- const pillY = isActive ? LEGEND_CAPSULE_PAD6 : 0;
13701
- const pillH = LEGEND_HEIGHT7 - (isActive ? LEGEND_CAPSULE_PAD6 * 2 : 0);
13630
+ const pillX = isActive ? LEGEND_CAPSULE_PAD : 0;
13631
+ const pillY = isActive ? LEGEND_CAPSULE_PAD : 0;
13632
+ const pillH = LEGEND_HEIGHT - (isActive ? LEGEND_CAPSULE_PAD * 2 : 0);
13702
13633
  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);
13703
13634
  if (isActive) {
13704
13635
  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);
13705
13636
  }
13706
- 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);
13637
+ 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);
13707
13638
  if (isActive) {
13708
13639
  let entryX = pillX + pillWidth + 4;
13709
13640
  for (const entry of group.entries) {
13710
13641
  const entryG = gEl.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
13711
- entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R7).attr("cy", LEGEND_HEIGHT7 / 2).attr("r", LEGEND_DOT_R7).attr("fill", entry.color);
13712
- const textX = entryX + LEGEND_DOT_R7 * 2 + LEGEND_ENTRY_DOT_GAP6;
13713
- 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);
13714
- entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W6 + LEGEND_ENTRY_TRAIL6;
13642
+ entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
13643
+ const textX = entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
13644
+ 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);
13645
+ entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
13715
13646
  }
13716
13647
  }
13717
13648
  }
@@ -13723,8 +13654,14 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
13723
13654
  if (width <= 0 || height <= 0) return;
13724
13655
  const titleHeight = parsed.title ? TITLE_HEIGHT4 + 10 : 0;
13725
13656
  const diagramW = layout.width;
13726
- const diagramH = layout.height;
13727
- const availH = height - titleHeight;
13657
+ const hasLegend = layout.legend.length > 0;
13658
+ const C4_LAYOUT_MARGIN = 40;
13659
+ const LEGEND_FIXED_GAP4 = 8;
13660
+ const fixedLegend = !exportDims && hasLegend;
13661
+ const legendLayoutSpace = C4_LAYOUT_MARGIN + LEGEND_HEIGHT;
13662
+ const legendReserveH = fixedLegend ? LEGEND_HEIGHT + LEGEND_FIXED_GAP4 : 0;
13663
+ const diagramH = fixedLegend ? layout.height - legendLayoutSpace : layout.height;
13664
+ const availH = height - titleHeight - legendReserveH;
13728
13665
  const scaleX = (width - DIAGRAM_PADDING7 * 2) / diagramW;
13729
13666
  const scaleY = (availH - DIAGRAM_PADDING7 * 2) / diagramH;
13730
13667
  const scale = Math.min(MAX_SCALE6, scaleX, scaleY);
@@ -13795,6 +13732,20 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
13795
13732
  renderEdges(contentG, layout.edges, palette, onClickItem, boundaryLabelObstacles);
13796
13733
  for (const node of layout.nodes) {
13797
13734
  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);
13735
+ if (activeTagGroup) {
13736
+ const tagKey = activeTagGroup.toLowerCase();
13737
+ const tagValue = node.metadata[tagKey];
13738
+ if (tagValue) {
13739
+ nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
13740
+ } else {
13741
+ const tagGroup = parsed.tagGroups.find(
13742
+ (g) => g.name.toLowerCase() === tagKey || g.alias?.toLowerCase() === tagKey
13743
+ );
13744
+ if (tagGroup?.defaultValue) {
13745
+ nodeG.attr(`data-tag-${tagKey}`, tagGroup.defaultValue.toLowerCase());
13746
+ }
13747
+ }
13748
+ }
13798
13749
  if (node.shape) {
13799
13750
  nodeG.attr("data-shape", node.shape);
13800
13751
  }
@@ -13880,8 +13831,12 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
13880
13831
  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");
13881
13832
  }
13882
13833
  }
13883
- if (!exportDims) {
13884
- renderLegend2(contentG, layout, palette, isDark, activeTagGroup);
13834
+ if (hasLegend) {
13835
+ 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");
13836
+ if (activeTagGroup) {
13837
+ legendParent.attr("data-legend-active", activeTagGroup.toLowerCase());
13838
+ }
13839
+ renderLegend2(legendParent, layout, palette, isDark, activeTagGroup, fixedLegend ? width : null);
13885
13840
  }
13886
13841
  }
13887
13842
  function renderC4ContainersForExport(content, systemName, theme, palette) {
@@ -13983,7 +13938,7 @@ function renderC4DeploymentForExport(content, theme, palette) {
13983
13938
  document.body.removeChild(el);
13984
13939
  }
13985
13940
  }
13986
- var d3Selection7, d3Shape5, 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;
13941
+ var d3Selection7, d3Shape5, 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;
13987
13942
  var init_renderer7 = __esm({
13988
13943
  "src/c4/renderer.ts"() {
13989
13944
  "use strict";
@@ -13994,6 +13949,7 @@ var init_renderer7 = __esm({
13994
13949
  init_inline_markdown();
13995
13950
  init_parser6();
13996
13951
  init_layout6();
13952
+ init_legend_constants();
13997
13953
  DIAGRAM_PADDING7 = 20;
13998
13954
  MAX_SCALE6 = 3;
13999
13955
  TITLE_HEIGHT4 = 30;
@@ -14026,16 +13982,6 @@ var init_renderer7 = __esm({
14026
13982
  PERSON_LEG_SPAN = 7;
14027
13983
  PERSON_ICON_W = PERSON_ARM_SPAN * 2;
14028
13984
  PERSON_SW = 1.5;
14029
- LEGEND_HEIGHT7 = 28;
14030
- LEGEND_PILL_FONT_SIZE4 = 11;
14031
- LEGEND_PILL_FONT_W6 = LEGEND_PILL_FONT_SIZE4 * 0.6;
14032
- LEGEND_PILL_PAD6 = 16;
14033
- LEGEND_DOT_R7 = 4;
14034
- LEGEND_ENTRY_FONT_SIZE5 = 10;
14035
- LEGEND_ENTRY_FONT_W6 = LEGEND_ENTRY_FONT_SIZE5 * 0.6;
14036
- LEGEND_ENTRY_DOT_GAP6 = 4;
14037
- LEGEND_ENTRY_TRAIL6 = 8;
14038
- LEGEND_CAPSULE_PAD6 = 4;
14039
13985
  lineGenerator5 = d3Shape5.line().x((d) => d.x).y((d) => d.y).curve(d3Shape5.curveBasis);
14040
13986
  }
14041
13987
  });
@@ -14891,23 +14837,6 @@ function computeInfra(parsed, params = {}) {
14891
14837
  const defaultLatencyMs = parseFloat(parsed.options["default-latency-ms"] ?? "") || 0;
14892
14838
  const defaultUptime = parseFloat(parsed.options["default-uptime"] ?? "") || 100;
14893
14839
  let effectiveNodes = parsed.nodes;
14894
- if (params.scenario) {
14895
- const overrides = params.scenario.overrides;
14896
- effectiveNodes = parsed.nodes.map((node) => {
14897
- const nodeOverrides = overrides[node.id];
14898
- if (!nodeOverrides) return node;
14899
- const props = node.properties.map((p) => {
14900
- const ov = nodeOverrides[p.key];
14901
- return ov != null ? { ...p, value: ov } : p;
14902
- });
14903
- for (const [key, val] of Object.entries(nodeOverrides)) {
14904
- if (!props.some((p) => p.key === key)) {
14905
- props.push({ key, value: val, lineNumber: node.lineNumber });
14906
- }
14907
- }
14908
- return { ...node, properties: props };
14909
- });
14910
- }
14911
14840
  if (params.propertyOverrides) {
14912
14841
  const propOv = params.propertyOverrides;
14913
14842
  effectiveNodes = effectiveNodes.map((node) => {
@@ -16504,16 +16433,16 @@ function computeInfraLegendGroups(nodes, tagGroups, palette, edges) {
16504
16433
  color: r.color,
16505
16434
  key: r.name.toLowerCase().replace(/\s+/g, "-")
16506
16435
  }));
16507
- const pillWidth = "Capabilities".length * LEGEND_PILL_FONT_W7 + LEGEND_PILL_PAD7;
16436
+ const pillWidth = "Capabilities".length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
16508
16437
  let entriesWidth = 0;
16509
16438
  for (const e of entries) {
16510
- entriesWidth += LEGEND_DOT_R8 * 2 + LEGEND_ENTRY_DOT_GAP7 + e.value.length * LEGEND_ENTRY_FONT_W7 + LEGEND_ENTRY_TRAIL7;
16439
+ entriesWidth += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + e.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
16511
16440
  }
16512
16441
  groups.push({
16513
16442
  name: "Capabilities",
16514
16443
  type: "role",
16515
16444
  entries,
16516
- width: LEGEND_CAPSULE_PAD7 * 2 + pillWidth + 4 + entriesWidth,
16445
+ width: LEGEND_CAPSULE_PAD * 2 + pillWidth + 4 + entriesWidth,
16517
16446
  minifiedWidth: pillWidth
16518
16447
  });
16519
16448
  }
@@ -16529,123 +16458,72 @@ function computeInfraLegendGroups(nodes, tagGroups, palette, edges) {
16529
16458
  }
16530
16459
  }
16531
16460
  if (entries.length === 0) continue;
16532
- const pillWidth = tg.name.length * LEGEND_PILL_FONT_W7 + LEGEND_PILL_PAD7;
16461
+ const pillWidth = tg.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
16533
16462
  let entriesWidth = 0;
16534
16463
  for (const e of entries) {
16535
- entriesWidth += LEGEND_DOT_R8 * 2 + LEGEND_ENTRY_DOT_GAP7 + e.value.length * LEGEND_ENTRY_FONT_W7 + LEGEND_ENTRY_TRAIL7;
16464
+ entriesWidth += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + e.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
16536
16465
  }
16537
16466
  groups.push({
16538
16467
  name: tg.name,
16539
16468
  type: "tag",
16540
16469
  tagKey: (tg.alias ?? tg.name).toLowerCase(),
16541
16470
  entries,
16542
- width: LEGEND_CAPSULE_PAD7 * 2 + pillWidth + 4 + entriesWidth,
16471
+ width: LEGEND_CAPSULE_PAD * 2 + pillWidth + 4 + entriesWidth,
16543
16472
  minifiedWidth: pillWidth
16544
16473
  });
16545
16474
  }
16546
16475
  return groups;
16547
16476
  }
16548
- function computePlaybackWidth(playback) {
16549
- if (!playback) return 0;
16550
- const pillWidth = "Playback".length * LEGEND_PILL_FONT_W7 + LEGEND_PILL_PAD7;
16551
- if (!playback.expanded) return pillWidth;
16552
- let entriesW = 8;
16553
- entriesW += LEGEND_PILL_FONT_SIZE5 * 0.8 + 6;
16554
- for (const s of playback.speedOptions) {
16555
- entriesW += `${s}x`.length * LEGEND_ENTRY_FONT_W7 + SPEED_BADGE_H_PAD * 2 + SPEED_BADGE_GAP;
16556
- }
16557
- return LEGEND_CAPSULE_PAD7 * 2 + pillWidth + entriesW;
16558
- }
16559
- function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup, playback) {
16560
- if (legendGroups.length === 0 && !playback) return;
16477
+ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup) {
16478
+ if (legendGroups.length === 0) return;
16561
16479
  const legendG = rootSvg.append("g").attr("transform", `translate(0, ${legendY})`);
16480
+ if (activeGroup) {
16481
+ legendG.attr("data-legend-active", activeGroup.toLowerCase());
16482
+ }
16562
16483
  const effectiveW = (g) => activeGroup != null && g.name.toLowerCase() === activeGroup.toLowerCase() ? g.width : g.minifiedWidth;
16563
- const playbackW = computePlaybackWidth(playback);
16564
- const trailingGaps = legendGroups.length > 0 && playbackW > 0 ? LEGEND_GROUP_GAP5 : 0;
16565
- const totalLegendW = legendGroups.reduce((s, g) => s + effectiveW(g), 0) + (legendGroups.length - 1) * LEGEND_GROUP_GAP5 + trailingGaps + playbackW;
16484
+ const totalLegendW = legendGroups.reduce((s, g) => s + effectiveW(g), 0) + (legendGroups.length - 1) * LEGEND_GROUP_GAP;
16566
16485
  let cursorX = (totalWidth - totalLegendW) / 2;
16567
16486
  for (const group of legendGroups) {
16568
16487
  const isActive = activeGroup != null && group.name.toLowerCase() === activeGroup.toLowerCase();
16569
16488
  const groupBg = isDark ? mix(palette.bg, palette.text, 85) : mix(palette.bg, palette.text, 92);
16570
16489
  const pillLabel = group.name;
16571
- const pillWidth = pillLabel.length * LEGEND_PILL_FONT_W7 + LEGEND_PILL_PAD7;
16572
- 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");
16490
+ const pillWidth = pillLabel.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
16491
+ 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");
16573
16492
  if (isActive) {
16574
- gEl.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT8).attr("rx", LEGEND_HEIGHT8 / 2).attr("fill", groupBg);
16493
+ gEl.append("rect").attr("width", group.width).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
16575
16494
  }
16576
- const pillXOff = isActive ? LEGEND_CAPSULE_PAD7 : 0;
16577
- const pillYOff = isActive ? LEGEND_CAPSULE_PAD7 : 0;
16578
- const pillH = LEGEND_HEIGHT8 - (isActive ? LEGEND_CAPSULE_PAD7 * 2 : 0);
16495
+ const pillXOff = isActive ? LEGEND_CAPSULE_PAD : 0;
16496
+ const pillYOff = isActive ? LEGEND_CAPSULE_PAD : 0;
16497
+ const pillH = LEGEND_HEIGHT - (isActive ? LEGEND_CAPSULE_PAD * 2 : 0);
16579
16498
  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);
16580
16499
  if (isActive) {
16581
- 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);
16500
+ 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);
16582
16501
  }
16583
- 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);
16502
+ 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);
16584
16503
  if (isActive) {
16585
16504
  let entryX = pillXOff + pillWidth + 4;
16586
16505
  for (const entry of group.entries) {
16587
- 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");
16588
- if (group.type === "tag" && group.tagKey) {
16589
- entryG.attr("data-legend-tag-group", group.tagKey);
16590
- }
16591
- entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R8).attr("cy", LEGEND_HEIGHT8 / 2).attr("r", LEGEND_DOT_R8).attr("fill", entry.color);
16592
- const textX = entryX + LEGEND_DOT_R8 * 2 + LEGEND_ENTRY_DOT_GAP7;
16593
- 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);
16594
- entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W7 + LEGEND_ENTRY_TRAIL7;
16506
+ 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");
16507
+ entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
16508
+ const textX = entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
16509
+ 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);
16510
+ entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
16595
16511
  }
16596
16512
  }
16597
- cursorX += effectiveW(group) + LEGEND_GROUP_GAP5;
16513
+ cursorX += effectiveW(group) + LEGEND_GROUP_GAP;
16598
16514
  }
16599
- if (playback) {
16600
- const isExpanded = playback.expanded;
16601
- const groupBg = isDark ? mix(palette.bg, palette.text, 85) : mix(palette.bg, palette.text, 92);
16602
- const pillLabel = "Playback";
16603
- const pillWidth = pillLabel.length * LEGEND_PILL_FONT_W7 + LEGEND_PILL_PAD7;
16604
- const fullW = computePlaybackWidth(playback);
16605
- const pbG = legendG.append("g").attr("transform", `translate(${cursorX}, 0)`).attr("class", "infra-legend-group infra-playback-pill").style("cursor", "pointer");
16606
- if (isExpanded) {
16607
- pbG.append("rect").attr("width", fullW).attr("height", LEGEND_HEIGHT8).attr("rx", LEGEND_HEIGHT8 / 2).attr("fill", groupBg);
16608
- }
16609
- const pillXOff = isExpanded ? LEGEND_CAPSULE_PAD7 : 0;
16610
- const pillYOff = isExpanded ? LEGEND_CAPSULE_PAD7 : 0;
16611
- const pillH = LEGEND_HEIGHT8 - (isExpanded ? LEGEND_CAPSULE_PAD7 * 2 : 0);
16612
- 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);
16613
- if (isExpanded) {
16614
- 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);
16615
- }
16616
- 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);
16617
- if (isExpanded) {
16618
- let entryX = pillXOff + pillWidth + 8;
16619
- const entryY = LEGEND_HEIGHT8 / 2 + LEGEND_ENTRY_FONT_SIZE6 / 2 - 1;
16620
- const ppLabel = playback.paused ? "\u25B6" : "\u23F8";
16621
- 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);
16622
- entryX += LEGEND_PILL_FONT_SIZE5 * 0.8 + 6;
16623
- for (const s of playback.speedOptions) {
16624
- const label = `${s}x`;
16625
- const isActive = playback.speed === s;
16626
- const slotW = label.length * LEGEND_ENTRY_FONT_W7 + SPEED_BADGE_H_PAD * 2;
16627
- const badgeH = LEGEND_ENTRY_FONT_SIZE6 + SPEED_BADGE_V_PAD * 2;
16628
- const badgeY = (LEGEND_HEIGHT8 - badgeH) / 2;
16629
- const speedG = pbG.append("g").attr("data-playback-action", "set-speed").attr("data-playback-value", String(s)).style("cursor", "pointer");
16630
- 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");
16631
- 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);
16632
- entryX += slotW + SPEED_BADGE_GAP;
16633
- }
16634
- }
16635
- cursorX += fullW + LEGEND_GROUP_GAP5;
16636
- }
16637
- }
16638
- function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, playback, expandedNodeIds, exportMode, collapsedNodes) {
16515
+ }
16516
+ function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, _playback, expandedNodeIds, exportMode, collapsedNodes) {
16639
16517
  d3Selection9.select(container).selectAll(":not([data-d3-tooltip])").remove();
16640
16518
  const legendGroups = computeInfraLegendGroups(layout.nodes, tagGroups ?? [], palette, layout.edges);
16641
- const hasLegend = legendGroups.length > 0 || !!playback;
16519
+ const hasLegend = legendGroups.length > 0;
16642
16520
  const fixedLegend = !exportMode && hasLegend;
16643
- const legendOffset = hasLegend && !fixedLegend ? LEGEND_HEIGHT8 : 0;
16521
+ const legendOffset = hasLegend && !fixedLegend ? LEGEND_HEIGHT : 0;
16644
16522
  const titleOffset = title ? 40 : 0;
16645
16523
  const totalWidth = layout.width;
16646
16524
  const totalHeight = layout.height + titleOffset + legendOffset;
16647
16525
  const shouldAnimate = animate !== false;
16648
- 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");
16526
+ 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");
16649
16527
  if (shouldAnimate) {
16650
16528
  rootSvg.append("style").text(`
16651
16529
  @keyframes infra-pulse-warning {
@@ -16709,10 +16587,10 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
16709
16587
  if (hasLegend) {
16710
16588
  if (fixedLegend) {
16711
16589
  const containerWidth = container.clientWidth || totalWidth;
16712
- 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");
16713
- renderLegend3(legendSvg, legendGroups, containerWidth, LEGEND_FIXED_GAP3 / 2, palette, isDark, activeGroup ?? null, playback ?? void 0);
16590
+ 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");
16591
+ renderLegend3(legendSvg, legendGroups, containerWidth, LEGEND_FIXED_GAP3 / 2, palette, isDark, activeGroup ?? null);
16714
16592
  } else {
16715
- renderLegend3(rootSvg, legendGroups, totalWidth, titleOffset + layout.height + 4, palette, isDark, activeGroup ?? null, playback ?? void 0);
16593
+ renderLegend3(rootSvg, legendGroups, totalWidth, titleOffset + layout.height + 4, palette, isDark, activeGroup ?? null);
16716
16594
  }
16717
16595
  }
16718
16596
  }
@@ -16723,7 +16601,7 @@ function parseAndLayoutInfra(content) {
16723
16601
  const layout = layoutInfra(computed);
16724
16602
  return { parsed, computed, layout };
16725
16603
  }
16726
- var d3Selection9, d3Shape7, 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;
16604
+ var d3Selection9, d3Shape7, 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;
16727
16605
  var init_renderer8 = __esm({
16728
16606
  "src/infra/renderer.ts"() {
16729
16607
  "use strict";
@@ -16736,6 +16614,7 @@ var init_renderer8 = __esm({
16736
16614
  init_parser9();
16737
16615
  init_compute();
16738
16616
  init_layout8();
16617
+ init_legend_constants();
16739
16618
  NODE_FONT_SIZE4 = 13;
16740
16619
  META_FONT_SIZE4 = 10;
16741
16620
  META_LINE_HEIGHT8 = 14;
@@ -16751,21 +16630,7 @@ var init_renderer8 = __esm({
16751
16630
  NODE_PAD_BOTTOM2 = 10;
16752
16631
  COLLAPSE_BAR_HEIGHT5 = 6;
16753
16632
  COLLAPSE_BAR_INSET2 = 0;
16754
- LEGEND_HEIGHT8 = 28;
16755
- LEGEND_PILL_PAD7 = 16;
16756
- LEGEND_PILL_FONT_SIZE5 = 11;
16757
- LEGEND_PILL_FONT_W7 = LEGEND_PILL_FONT_SIZE5 * 0.6;
16758
- LEGEND_CAPSULE_PAD7 = 4;
16759
- LEGEND_DOT_R8 = 4;
16760
- LEGEND_ENTRY_FONT_SIZE6 = 10;
16761
- LEGEND_ENTRY_FONT_W7 = LEGEND_ENTRY_FONT_SIZE6 * 0.6;
16762
- LEGEND_ENTRY_DOT_GAP7 = 4;
16763
- LEGEND_ENTRY_TRAIL7 = 8;
16764
- LEGEND_GROUP_GAP5 = 12;
16765
16633
  LEGEND_FIXED_GAP3 = 16;
16766
- SPEED_BADGE_H_PAD = 5;
16767
- SPEED_BADGE_V_PAD = 3;
16768
- SPEED_BADGE_GAP = 6;
16769
16634
  COLOR_HEALTHY = "#22c55e";
16770
16635
  COLOR_WARNING = "#eab308";
16771
16636
  COLOR_OVERLOADED = "#ef4444";
@@ -17785,9 +17650,8 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
17785
17650
  const GROUP_PADDING_BOTTOM = 8;
17786
17651
  const GROUP_LABEL_SIZE = 11;
17787
17652
  const titleOffset = title ? TITLE_HEIGHT5 : 0;
17788
- const legendOffset = parsed.tagGroups.length > 0 ? LEGEND_HEIGHT9 + LEGEND_BOTTOM_GAP : 0;
17789
17653
  const groupOffset = groups.length > 0 ? GROUP_PADDING_TOP + GROUP_LABEL_SIZE : 0;
17790
- const participantStartY = TOP_MARGIN + titleOffset + legendOffset + PARTICIPANT_Y_OFFSET + groupOffset;
17654
+ const participantStartY = TOP_MARGIN + titleOffset + PARTICIPANT_Y_OFFSET + groupOffset;
17791
17655
  const lifelineStartY0 = participantStartY + PARTICIPANT_BOX_HEIGHT;
17792
17656
  const hasActors = participants.some((p) => p.type === "actor");
17793
17657
  const messageStartOffset = MESSAGE_START_OFFSET + (hasActors ? 20 : 0);
@@ -17869,7 +17733,9 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
17869
17733
  participants.length * PARTICIPANT_GAP,
17870
17734
  PARTICIPANT_BOX_WIDTH + 40
17871
17735
  );
17872
- const totalHeight = participantStartY + PARTICIPANT_BOX_HEIGHT + Math.max(lifelineLength, 40) + 40;
17736
+ const contentHeight = participantStartY + PARTICIPANT_BOX_HEIGHT + Math.max(lifelineLength, 40) + 40;
17737
+ const legendSpace = parsed.tagGroups.length > 0 ? LEGEND_HEIGHT : 0;
17738
+ const totalHeight = contentHeight + legendSpace;
17873
17739
  const containerWidth = options?.exportWidth ?? container.getBoundingClientRect().width;
17874
17740
  const svgWidth = Math.max(totalWidth, containerWidth);
17875
17741
  const diagramWidth = participants.length * PARTICIPANT_GAP;
@@ -17927,13 +17793,13 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
17927
17793
  }
17928
17794
  }
17929
17795
  if (parsed.tagGroups.length > 0) {
17930
- const legendY = TOP_MARGIN + titleOffset;
17796
+ const legendY = contentHeight;
17931
17797
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
17932
17798
  const legendItems = [];
17933
17799
  for (const tg of parsed.tagGroups) {
17934
17800
  if (tg.entries.length === 0) continue;
17935
17801
  const isActive = !!activeTagGroup && tg.name.toLowerCase() === activeTagGroup.toLowerCase();
17936
- const pillWidth = tg.name.length * LEGEND_PILL_FONT_W8 + LEGEND_PILL_PAD8;
17802
+ const pillWidth = tg.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
17937
17803
  const entries = tg.entries.map((e) => ({
17938
17804
  value: e.value,
17939
17805
  color: resolveColor(e.color)
@@ -17942,38 +17808,42 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
17942
17808
  if (isActive) {
17943
17809
  let entriesWidth = 0;
17944
17810
  for (const entry of entries) {
17945
- entriesWidth += LEGEND_DOT_R9 * 2 + LEGEND_ENTRY_DOT_GAP8 + entry.value.length * LEGEND_ENTRY_FONT_W8 + LEGEND_ENTRY_TRAIL8;
17811
+ entriesWidth += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
17946
17812
  }
17947
- totalWidth2 = LEGEND_CAPSULE_PAD8 * 2 + pillWidth + 4 + entriesWidth;
17813
+ totalWidth2 = LEGEND_CAPSULE_PAD * 2 + pillWidth + 4 + entriesWidth;
17948
17814
  }
17949
17815
  legendItems.push({ group: tg, isActive, pillWidth, totalWidth: totalWidth2, entries });
17950
17816
  }
17951
- const totalLegendWidth = legendItems.reduce((s, item) => s + item.totalWidth, 0) + (legendItems.length - 1) * LEGEND_GROUP_GAP6;
17817
+ const totalLegendWidth = legendItems.reduce((s, item) => s + item.totalWidth, 0) + (legendItems.length - 1) * LEGEND_GROUP_GAP;
17952
17818
  let legendX = (svgWidth - totalLegendWidth) / 2;
17819
+ const legendContainer = svg.append("g").attr("class", "sequence-legend");
17820
+ if (activeTagGroup) {
17821
+ legendContainer.attr("data-legend-active", activeTagGroup.toLowerCase());
17822
+ }
17953
17823
  for (const item of legendItems) {
17954
- 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");
17824
+ 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");
17955
17825
  if (item.isActive) {
17956
- gEl.append("rect").attr("width", item.totalWidth).attr("height", LEGEND_HEIGHT9).attr("rx", LEGEND_HEIGHT9 / 2).attr("fill", groupBg);
17826
+ gEl.append("rect").attr("width", item.totalWidth).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
17957
17827
  }
17958
- const pillXOff = item.isActive ? LEGEND_CAPSULE_PAD8 : 0;
17959
- const pillYOff = item.isActive ? LEGEND_CAPSULE_PAD8 : 0;
17960
- const pillH = LEGEND_HEIGHT9 - (item.isActive ? LEGEND_CAPSULE_PAD8 * 2 : 0);
17828
+ const pillXOff = item.isActive ? LEGEND_CAPSULE_PAD : 0;
17829
+ const pillYOff = item.isActive ? LEGEND_CAPSULE_PAD : 0;
17830
+ const pillH = LEGEND_HEIGHT - (item.isActive ? LEGEND_CAPSULE_PAD * 2 : 0);
17961
17831
  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);
17962
17832
  if (item.isActive) {
17963
17833
  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);
17964
17834
  }
17965
- 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);
17835
+ 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);
17966
17836
  if (item.isActive) {
17967
17837
  let entryX = pillXOff + item.pillWidth + 4;
17968
17838
  for (const entry of item.entries) {
17969
17839
  const entryG = gEl.append("g").attr("data-legend-entry", entry.value.toLowerCase()).style("cursor", "pointer");
17970
- entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R9).attr("cy", LEGEND_HEIGHT9 / 2).attr("r", LEGEND_DOT_R9).attr("fill", entry.color);
17971
- const textX = entryX + LEGEND_DOT_R9 * 2 + LEGEND_ENTRY_DOT_GAP8;
17972
- 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);
17973
- entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W8 + LEGEND_ENTRY_TRAIL8;
17840
+ entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
17841
+ const textX = entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
17842
+ 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);
17843
+ entryX = textX + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
17974
17844
  }
17975
17845
  }
17976
- legendX += item.totalWidth + LEGEND_GROUP_GAP6;
17846
+ legendX += item.totalWidth + LEGEND_GROUP_GAP;
17977
17847
  }
17978
17848
  }
17979
17849
  for (const group of groups) {
@@ -18494,7 +18364,7 @@ function renderParticipant(svg, participant, cx, cy, palette, isDark, color, tag
18494
18364
  });
18495
18365
  }
18496
18366
  }
18497
- var d3Selection11, 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;
18367
+ var d3Selection11, 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;
18498
18368
  var init_renderer9 = __esm({
18499
18369
  "src/sequence/renderer.ts"() {
18500
18370
  "use strict";
@@ -18505,6 +18375,7 @@ var init_renderer9 = __esm({
18505
18375
  init_colors();
18506
18376
  init_parser();
18507
18377
  init_tag_resolution();
18378
+ init_legend_constants();
18508
18379
  PARTICIPANT_GAP = 160;
18509
18380
  PARTICIPANT_BOX_WIDTH = 120;
18510
18381
  PARTICIPANT_BOX_HEIGHT = 50;
@@ -18526,18 +18397,6 @@ var init_renderer9 = __esm({
18526
18397
  NOTE_CHARS_PER_LINE = Math.floor((NOTE_MAX_W - NOTE_PAD_H * 2 - NOTE_FOLD) / NOTE_CHAR_W);
18527
18398
  COLLAPSED_NOTE_H = 20;
18528
18399
  COLLAPSED_NOTE_W = 40;
18529
- LEGEND_HEIGHT9 = 28;
18530
- LEGEND_PILL_PAD8 = 16;
18531
- LEGEND_PILL_FONT_SIZE6 = 11;
18532
- LEGEND_PILL_FONT_W8 = LEGEND_PILL_FONT_SIZE6 * 0.6;
18533
- LEGEND_CAPSULE_PAD8 = 4;
18534
- LEGEND_DOT_R9 = 4;
18535
- LEGEND_ENTRY_FONT_SIZE7 = 10;
18536
- LEGEND_ENTRY_FONT_W8 = LEGEND_ENTRY_FONT_SIZE7 * 0.6;
18537
- LEGEND_ENTRY_DOT_GAP8 = 4;
18538
- LEGEND_ENTRY_TRAIL8 = 8;
18539
- LEGEND_GROUP_GAP6 = 12;
18540
- LEGEND_BOTTOM_GAP = 8;
18541
18400
  LABEL_CHAR_WIDTH = 7.5;
18542
18401
  LABEL_MAX_CHARS = Math.floor((PARTICIPANT_BOX_WIDTH - 10) / LABEL_CHAR_WIDTH);
18543
18402
  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);
@@ -20145,9 +20004,9 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20145
20004
  const scaleMargin = timelineScale ? 40 : 0;
20146
20005
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
20147
20006
  const margin = {
20148
- top: 104 + markerMargin + tagLegendReserve,
20007
+ top: 104 + markerMargin,
20149
20008
  right: 40 + scaleMargin,
20150
- bottom: 40,
20009
+ bottom: 40 + tagLegendReserve,
20151
20010
  left: 60 + scaleMargin
20152
20011
  };
20153
20012
  const innerWidth = width - margin.left - margin.right;
@@ -20254,9 +20113,9 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20254
20113
  const scaleMargin = timelineScale ? 40 : 0;
20255
20114
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
20256
20115
  const margin = {
20257
- top: 104 + markerMargin + tagLegendReserve,
20116
+ top: 104 + markerMargin,
20258
20117
  right: 200,
20259
- bottom: 40,
20118
+ bottom: 40 + tagLegendReserve,
20260
20119
  left: 60 + scaleMargin
20261
20120
  };
20262
20121
  const innerWidth = width - margin.left - margin.right;
@@ -20393,9 +20252,9 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20393
20252
  const dynamicLeftMargin = Math.max(120, maxGroupNameLen * 7 + 30);
20394
20253
  const baseTopMargin = title ? 50 : 20;
20395
20254
  const margin = {
20396
- top: baseTopMargin + (timelineScale ? 40 : 0) + markerMargin + tagLegendReserve,
20255
+ top: baseTopMargin + (timelineScale ? 40 : 0) + markerMargin,
20397
20256
  right: 40,
20398
- bottom: 40 + scaleMargin,
20257
+ bottom: 40 + scaleMargin + tagLegendReserve,
20399
20258
  left: dynamicLeftMargin
20400
20259
  };
20401
20260
  const innerWidth = width - margin.left - margin.right;
@@ -20539,9 +20398,9 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20539
20398
  const scaleMargin = timelineScale ? 24 : 0;
20540
20399
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
20541
20400
  const margin = {
20542
- top: 104 + (timelineScale ? 40 : 0) + markerMargin + tagLegendReserve,
20401
+ top: 104 + (timelineScale ? 40 : 0) + markerMargin,
20543
20402
  right: 40,
20544
- bottom: 40 + scaleMargin,
20403
+ bottom: 40 + scaleMargin + tagLegendReserve,
20545
20404
  left: 60
20546
20405
  };
20547
20406
  const innerWidth = width - margin.left - margin.right;
@@ -20672,17 +20531,17 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20672
20531
  });
20673
20532
  }
20674
20533
  if (parsed.timelineTagGroups.length > 0) {
20675
- const LG_HEIGHT = 28;
20676
- const LG_PILL_PAD = 16;
20677
- const LG_PILL_FONT_SIZE = 11;
20678
- const LG_PILL_FONT_W = LG_PILL_FONT_SIZE * 0.6;
20679
- const LG_CAPSULE_PAD = 4;
20680
- const LG_DOT_R = 4;
20681
- const LG_ENTRY_FONT_SIZE = 10;
20682
- const LG_ENTRY_FONT_W = LG_ENTRY_FONT_SIZE * 0.6;
20683
- const LG_ENTRY_DOT_GAP = 4;
20684
- const LG_ENTRY_TRAIL = 8;
20685
- const LG_GROUP_GAP = 12;
20534
+ const LG_HEIGHT = LEGEND_HEIGHT;
20535
+ const LG_PILL_PAD = LEGEND_PILL_PAD;
20536
+ const LG_PILL_FONT_SIZE = LEGEND_PILL_FONT_SIZE;
20537
+ const LG_PILL_FONT_W = LEGEND_PILL_FONT_W;
20538
+ const LG_CAPSULE_PAD = LEGEND_CAPSULE_PAD;
20539
+ const LG_DOT_R = LEGEND_DOT_R;
20540
+ const LG_ENTRY_FONT_SIZE = LEGEND_ENTRY_FONT_SIZE;
20541
+ const LG_ENTRY_FONT_W = LEGEND_ENTRY_FONT_W;
20542
+ const LG_ENTRY_DOT_GAP = LEGEND_ENTRY_DOT_GAP;
20543
+ const LG_ENTRY_TRAIL = LEGEND_ENTRY_TRAIL;
20544
+ const LG_GROUP_GAP = LEGEND_GROUP_GAP;
20686
20545
  const LG_ICON_W = 20;
20687
20546
  const mainSvg = d3Selection12.select(container).select("svg");
20688
20547
  const mainG = mainSvg.select("g");
@@ -20715,6 +20574,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20715
20574
  );
20716
20575
  }, drawLegend2 = function() {
20717
20576
  mainSvg.selectAll(".tl-tag-legend-group").remove();
20577
+ mainSvg.selectAll(".tl-tag-legend-container").remove();
20718
20578
  const effectiveColorKey = (currentActiveGroup ?? currentSwimlaneGroup)?.toLowerCase() ?? null;
20719
20579
  const visibleGroups = viewMode ? legendGroups.filter(
20720
20580
  (lg) => effectiveColorKey != null && lg.group.name.toLowerCase() === effectiveColorKey
@@ -20725,13 +20585,17 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20725
20585
  return s + (isActive ? lg.expandedWidth : lg.minifiedWidth);
20726
20586
  }, 0) + (visibleGroups.length - 1) * LG_GROUP_GAP;
20727
20587
  let cx = (width - totalW) / 2;
20588
+ const legendContainer = mainSvg.append("g").attr("class", "tl-tag-legend-container");
20589
+ if (currentActiveGroup) {
20590
+ legendContainer.attr("data-legend-active", currentActiveGroup.toLowerCase());
20591
+ }
20728
20592
  for (const lg of visibleGroups) {
20729
20593
  const groupKey = lg.group.name.toLowerCase();
20730
20594
  const isActive = viewMode || currentActiveGroup != null && currentActiveGroup.toLowerCase() === groupKey;
20731
20595
  const isSwimActive = currentSwimlaneGroup != null && currentSwimlaneGroup.toLowerCase() === groupKey;
20732
20596
  const pillLabel = lg.group.name;
20733
20597
  const pillWidth = pillLabel.length * LG_PILL_FONT_W + LG_PILL_PAD;
20734
- 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__");
20598
+ 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__");
20735
20599
  if (!viewMode) {
20736
20600
  gEl.style("cursor", "pointer").on("click", () => {
20737
20601
  currentActiveGroup = currentActiveGroup === groupKey ? null : groupKey;
@@ -20821,7 +20685,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20821
20685
  });
20822
20686
  };
20823
20687
  var drawSwimlaneIcon = drawSwimlaneIcon2, relayout = relayout2, drawLegend = drawLegend2, recolorEvents = recolorEvents2;
20824
- const legendY = title ? 50 : 10;
20688
+ const legendY = height - LG_HEIGHT - 4;
20825
20689
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
20826
20690
  const legendGroups = parsed.timelineTagGroups.map((g) => {
20827
20691
  const pillW = g.name.length * LG_PILL_FONT_W + LG_PILL_PAD;
@@ -21592,7 +21456,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21592
21456
  const orgParsed = parseOrg2(content, effectivePalette2);
21593
21457
  if (orgParsed.error) return "";
21594
21458
  const collapsedNodes = orgExportState?.collapsedNodes;
21595
- const activeTagGroup = orgExportState?.activeTagGroup ?? null;
21459
+ const activeTagGroup = orgExportState?.activeTagGroup ?? options?.tagGroup ?? null;
21596
21460
  const hiddenAttributes = orgExportState?.hiddenAttributes;
21597
21461
  const { parsed: effectiveParsed, hiddenCounts } = collapsedNodes && collapsedNodes.size > 0 ? collapseOrgTree2(orgParsed, collapsedNodes) : { parsed: orgParsed, hiddenCounts: /* @__PURE__ */ new Map() };
21598
21462
  const orgLayout = layoutOrg2(
@@ -21621,7 +21485,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21621
21485
  const sitemapParsed = parseSitemap2(content, effectivePalette2);
21622
21486
  if (sitemapParsed.error || sitemapParsed.roots.length === 0) return "";
21623
21487
  const collapsedNodes = orgExportState?.collapsedNodes;
21624
- const activeTagGroup = orgExportState?.activeTagGroup ?? null;
21488
+ const activeTagGroup = orgExportState?.activeTagGroup ?? options?.tagGroup ?? null;
21625
21489
  const hiddenAttributes = orgExportState?.hiddenAttributes;
21626
21490
  const { parsed: effectiveParsed, hiddenCounts } = collapsedNodes && collapsedNodes.size > 0 ? collapseSitemapTree2(sitemapParsed, collapsedNodes) : { parsed: sitemapParsed, hiddenCounts: /* @__PURE__ */ new Map() };
21627
21491
  const sitemapLayout = layoutSitemap2(
@@ -21649,7 +21513,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21649
21513
  container2.style.position = "absolute";
21650
21514
  container2.style.left = "-9999px";
21651
21515
  document.body.appendChild(container2);
21652
- renderKanban2(container2, kanbanParsed, effectivePalette2, theme === "dark");
21516
+ renderKanban2(container2, kanbanParsed, effectivePalette2, theme === "dark", void 0, void 0, options?.tagGroup);
21653
21517
  return finalizeSvgExport(container2, theme, effectivePalette2, options);
21654
21518
  }
21655
21519
  if (detectedType === "class") {
@@ -21681,7 +21545,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21681
21545
  const exportWidth = erLayout.width + PADDING * 2;
21682
21546
  const exportHeight = erLayout.height + PADDING * 2 + titleOffset;
21683
21547
  const container2 = createExportContainer(exportWidth, exportHeight);
21684
- renderERDiagram2(container2, erParsed, erLayout, effectivePalette2, theme === "dark", void 0, { width: exportWidth, height: exportHeight });
21548
+ renderERDiagram2(container2, erParsed, erLayout, effectivePalette2, theme === "dark", void 0, { width: exportWidth, height: exportHeight }, options?.tagGroup);
21685
21549
  return finalizeSvgExport(container2, theme, effectivePalette2, options);
21686
21550
  }
21687
21551
  if (detectedType === "initiative-status") {
@@ -21718,7 +21582,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21718
21582
  const exportHeight = c4Layout.height + PADDING * 2 + titleOffset;
21719
21583
  const container2 = createExportContainer(exportWidth, exportHeight);
21720
21584
  const renderFn = c4Level === "deployment" || c4Level === "components" && c4System && c4Container || c4Level === "containers" && c4System ? renderC4Containers2 : renderC4Context2;
21721
- renderFn(container2, c4Parsed, c4Layout, effectivePalette2, theme === "dark", void 0, { width: exportWidth, height: exportHeight });
21585
+ renderFn(container2, c4Parsed, c4Layout, effectivePalette2, theme === "dark", void 0, { width: exportWidth, height: exportHeight }, options?.tagGroup);
21722
21586
  return finalizeSvgExport(container2, theme, effectivePalette2, options);
21723
21587
  }
21724
21588
  if (detectedType === "flowchart") {
@@ -21741,16 +21605,16 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21741
21605
  const effectivePalette2 = await resolveExportPalette(theme, palette);
21742
21606
  const infraParsed = parseInfra2(content);
21743
21607
  if (infraParsed.error || infraParsed.nodes.length === 0) return "";
21744
- const selectedScenario = options?.scenario ? infraParsed.scenarios.find((s) => s.name.toLowerCase() === options.scenario.toLowerCase()) ?? null : null;
21745
- const infraComputed = computeInfra2(infraParsed, selectedScenario ? { scenario: selectedScenario } : {});
21608
+ const infraComputed = computeInfra2(infraParsed);
21746
21609
  const infraLayout = layoutInfra2(infraComputed);
21610
+ const activeTagGroup = options?.tagGroup ?? null;
21747
21611
  const titleOffset = infraParsed.title ? 40 : 0;
21748
21612
  const legendGroups = computeInfraLegendGroups2(infraLayout.nodes, infraParsed.tagGroups, effectivePalette2);
21749
21613
  const legendOffset = legendGroups.length > 0 ? 28 : 0;
21750
21614
  const exportWidth = infraLayout.width;
21751
21615
  const exportHeight = infraLayout.height + titleOffset + legendOffset;
21752
21616
  const container2 = createExportContainer(exportWidth, exportHeight);
21753
- renderInfra2(container2, infraLayout, effectivePalette2, theme === "dark", infraParsed.title, infraParsed.titleLineNumber, infraParsed.tagGroups, null, false, null, null, true);
21617
+ renderInfra2(container2, infraLayout, effectivePalette2, theme === "dark", infraParsed.title, infraParsed.titleLineNumber, infraParsed.tagGroups, activeTagGroup, false, null, null, true);
21754
21618
  const infraSvg = container2.querySelector("svg");
21755
21619
  if (infraSvg) {
21756
21620
  infraSvg.setAttribute("width", String(exportWidth));
@@ -21794,7 +21658,8 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21794
21658
  const seqParsed = parseSequenceDgmo2(content);
21795
21659
  if (seqParsed.error || seqParsed.participants.length === 0) return "";
21796
21660
  renderSequenceDiagram2(container, seqParsed, effectivePalette, isDark, void 0, {
21797
- exportWidth: EXPORT_WIDTH
21661
+ exportWidth: EXPORT_WIDTH,
21662
+ activeTagGroup: options?.tagGroup
21798
21663
  });
21799
21664
  } else if (parsed.type === "wordcloud") {
21800
21665
  await renderWordCloudAsync(container, parsed, effectivePalette, isDark, dims);
@@ -21808,7 +21673,7 @@ async function renderForExport(content, theme, palette, orgExportState, options)
21808
21673
  isDark,
21809
21674
  void 0,
21810
21675
  dims,
21811
- orgExportState?.activeTagGroup,
21676
+ orgExportState?.activeTagGroup ?? options?.tagGroup,
21812
21677
  orgExportState?.swimlaneTagGroup
21813
21678
  );
21814
21679
  } else if (parsed.type === "venn") {
@@ -21837,6 +21702,7 @@ var init_d3 = __esm({
21837
21702
  init_diagnostics();
21838
21703
  init_parsing();
21839
21704
  init_tag_groups();
21705
+ init_legend_constants();
21840
21706
  DEFAULT_CLOUD_OPTIONS = {
21841
21707
  rotate: "none",
21842
21708
  max: 0,
@@ -22154,7 +22020,7 @@ async function render(content, options) {
22154
22020
  c4Level: options?.c4Level,
22155
22021
  c4System: options?.c4System,
22156
22022
  c4Container: options?.c4Container,
22157
- scenario: options?.scenario
22023
+ tagGroup: options?.tagGroup
22158
22024
  });
22159
22025
  }
22160
22026