/* ======================================================================
   AE DECLARATIVE DIAGRAM LAYER  (shared/js/ae-diagram.js)
   Lane-based node layout + curved SVG edges rendered from a nodes/edges
   spec. Reuses the existing themed .diagram-node component for boxes
   (guide-diagrams.css), so nodes are theme-correct in light and dark.
   Edge stroke palette mirrors data-flow-diagram.css. Theme the default
   accent per diagram with --ae-diagram-accent on the .ae-diagram element.
   ====================================================================== */

.ae-diagram {
  --ae-diagram-accent: var(--teal);
  position: relative;
  margin: 2rem auto;
  max-width: 100%;
  /* display:flex + flex-direction:column + align-items:center, NOT block:
     the canvas below is pinned to its content's natural width and can
     legitimately be wider than this element's available space, needing to
     overflow SYMMETRICALLY (centred) so its transform-origin: top centre
     scales it down correctly (R2-I) - margin:auto only centres a NARROWER
     child, it collapses to 0 on an overflowing one and leaves it
     left-aligned. flexbox's align-items:center correctly centres an
     overflowing child on the cross axis. column direction keeps the legend
     (canvas's sibling, appended after it - see ae-diagram.js) stacked BELOW
     the canvas as before, rather than beside it (the default row
     direction). */
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* The JSON spec is a <script> and never renders, but guard anyway. */
.ae-diagram > script.ae-diagram-spec { display: none; }

/* Fit-to-width canvas. The four positioned layers (groups, stage, overlay,
   labels) ride inside this wrapper so a wide diagram can be uniformly scaled
   DOWN to the available width instead of clipping its outer edge lanes. The
   stage centres its lanes and overflows symmetrically, so scaling about the
   top-centre keeps the whole thing centred and fully visible. JS sets the
   transform + a negative margin-bottom (to reclaim the space the scale frees)
   and resets both to natural size for image export. The legend is a sibling
   AFTER the canvas, so it is never shrunk with the diagram. */
.ae-diagram-canvas {
  position: relative;
  transform-origin: top center;
  /* No transform transition: the fit-to-width scale is reset to none at the top
     of each draw() so the natural width can be measured. A transition would
     animate that reset, so the in-flight measurement would read a still-scaled
     (too small) width and skip the fit. The scale is a layout decision, applied
     instantly - not an animation. */
  /* Matches .ae-diagram-stage's width: max-content exactly (R2-I). The
     canvas's three OTHER children - groups/overlay(svg)/labels - are
     position:absolute, sized/positioned in JS purely relative to the
     canvas's own box (left:0, width:100%). For that to align with the
     stage's actual content (which it draws on top of), the canvas's box
     must exactly equal the stage's box - any gap between them would need
     tracking as the viewport changes, which reintroduces a live,
     zoom-varying quantity into the picture (R2-I's whole point is
     eliminating those). Giving the canvas the SAME natural width makes that
     gap structurally zero at all times: stage fills its exactly-matching
     parent (no internal centring needed), and the canvas itself overflows
     ITS OWN (still container-bound) parent .ae-diagram instead - handled by
     align-items: center one level up, above, where it can't perturb the
     overlay/groups/labels alignment at all. */
  width: max-content;
}

/* ---- lane layout ----------------------------------------------------- */
.ae-diagram-stage {
  position: relative;
  z-index: 2;
  display: flex;
  gap: 4.5rem;
  align-items: stretch;
  justify-content: center;
  flex-wrap: nowrap;
  /* The stage itself has no explicit width by default, so even with lanes
     pinned to their natural width (flex: 0 0 auto, above), the stage's OWN
     box still tracks its container's available width and just lets the
     non-shrinking lanes overflow it (justify-content: center splits that
     overflow left/right). Every routing measurement in ae-diagram.js is
     relative to stage.getBoundingClientRect() - so an overflow that grows
     with the container shifts that coordinate origin on every resize/zoom,
     re-perturbing routing even though the lane grid itself never moves
     (R2-I). width: max-content pins the stage's own box to its content's
     natural width too, so that origin is genuinely constant. Reset to auto
     on mobile (below), where the stage stacks as rows and must fill the
     viewport width instead. */
  width: max-content;
}

.ae-diagram-lane {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  /* 0 0 auto, not 0 1 auto: lanes must never flex-shrink below their natural
     content width. A narrower container used to compress lanes toward the
     card min-width, re-wrapping label text and reflowing the whole grid - the
     fit-to-width transform: scale(k) on .ae-diagram-canvas (ae-diagram.js) is
     what fits the diagram to its container; the lane grid itself always lays
     out at one true natural width so zoom/resize scales uniformly instead of
     re-deciding the layout (R2-I). */
  flex: 0 0 auto;
  transition: opacity 0.3s ease, filter 0.3s ease;
}

.ae-diagram-lane-label {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--text-muted);
  text-align: center;
  margin-bottom: 0.35rem;
}

.ae-diagram-lane-nodes {
  display: flex;
  flex-direction: column;
  gap: 2.25rem;
  flex: 1;
  justify-content: center;
  /* rows span the lane width so per-row left/center/right alignment is
     meaningful; a single-card row then centres via the row's justify-content. */
  align-items: stretch;
}

/* A row holds 1-4 cards side by side within a lane. Centred by default; a row
   of 2 then sits centred under a row of 3. Per-row align overrides below. */
.ae-diagram-row {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  justify-content: center;
  /* roomy enough that a connector + its caption between two side-by-side cards
     is visible, not just an arrowhead poking out of the gap */
  gap: 2.4rem;
}
.ae-diagram-row--left  { justify-content: flex-start; }
.ae-diagram-row--right { justify-content: flex-end; }

/* Per-lane vertical alignment within the stretched stage height (default is
   middle, set by .ae-diagram-lane-nodes justify-content: center). */
.ae-diagram-lane--valign-top .ae-diagram-lane-nodes { justify-content: flex-start; }
.ae-diagram-lane--valign-bottom .ae-diagram-lane-nodes { justify-content: flex-end; }

/* When the diagram has group boundary boxes, reserve a top band between the lane
   labels and the first node so a group's straddling label tab clears the lane
   label AND the group box top sits comfortably above the cards (not touching). */
.ae-diagram.has-groups .ae-diagram-lane-nodes { padding-top: 2.5rem; }
/* Nested frames stack their title tabs, so reserve a deeper top band. */
.ae-diagram.has-nested-groups .ae-diagram-lane-nodes { padding-top: 4.1rem; }

/* Component-local sizing of the shared .diagram-node box (icon + label,
   denser than the standalone diagram-flow usage). */
.ae-diagram .diagram-node {
  width: auto;
  min-width: 132px;
  max-width: 210px;
  min-height: 94px;
  padding: 0.95rem 1.1rem;
  gap: 0.45rem;
}

.ae-diagram .diagram-icon {
  width: 34px;
  height: 34px;
}
.ae-diagram .diagram-icon svg {
  width: 26px;
  height: 26px;
  stroke: var(--node-accent);
}

.ae-diagram .diagram-label {
  font-size: 0.92rem;
  font-weight: 650;
  line-height: 1.2;
  color: var(--text-primary);
}

.ae-diagram .diagram-sublabel {
  font-size: 0.72rem;
  color: var(--text-muted);
  line-height: 1.25;
  margin-top: -0.1rem;
}

/* ---- focus interaction (AEDiagram.focusLane / cycleFocus) ------------- */
.ae-diagram-lane.is-dimmed { opacity: 0.28; filter: saturate(0.5); }
.ae-diagram.has-focus .ae-diagram-overlay { opacity: 0.45; }

/* ---- node-centric focus (AEDiagram.focusNode / enableNodeFocus) -------
   Spotlight one node + its directly-wired neighbours; dim everything else.
   Inspired by measure-dependency viewers, applied to the interactive tool. */
.ae-diagram--interactive .diagram-node:not(a) { cursor: pointer; }
.ae-diagram--interactive .diagram-node { transition: opacity 0.25s ease, filter 0.25s ease, box-shadow 0.25s ease, transform 0.2s ease; }
.ae-diagram.has-node-focus .diagram-node.is-dimmed { opacity: 0.22; filter: saturate(0.45); }
.ae-diagram.has-node-focus .diagram-node.is-active {
  box-shadow: 0 0 0 3px var(--node-accent, var(--ae-diagram-accent)),
              0 0 24px -2px var(--node-accent, var(--ae-diagram-accent)),
              0 10px 30px rgba(0, 0, 0, 0.35);
  transform: translateY(-3px) scale(1.035);
  opacity: 1;
}
.ae-diagram.has-node-focus .diagram-node.is-related { opacity: 1; }
.ae-diagram .ae-edge { transition: opacity 0.25s ease; }
.ae-diagram.has-node-focus .ae-edge.is-dimmed { opacity: 0.1; }
.ae-diagram.has-node-focus .ae-edge.is-active { opacity: 1; }
/* Dim what isn't on the spotlighted path: origin/terminal dots and edge-label
   pills. Group frames are structural context - only the ones with NO on-path
   node dim, and only gently (a transitive chain often runs THROUGH a frame, so
   a frame holding an active node stays fully lit). The legend is a reference key
   and is never dimmed - you read it to interpret the highlighted lines. */
.ae-diagram .ae-edge-cap, .ae-diagram .ae-diagram-edge-label,
.ae-diagram .ae-diagram-group { transition: opacity 0.25s ease; }
.ae-diagram.has-node-focus .ae-edge-cap.is-dimmed { opacity: 0.1; }
.ae-diagram.has-node-focus .ae-diagram-edge-label.is-dimmed { opacity: 0.12; }
.ae-diagram.has-node-focus .ae-diagram-group.is-dimmed { opacity: 0.4; }

/* ---- hover-to-trace a single line ----------------------------------------
   In a busy diagram several same-coloured lines bunch together. Hovering any
   line isolates it: that one path, its label, and the two cards it joins stay
   lit while every other line/label dims, so you can follow exactly where it
   goes. A wide transparent hit-path (built per edge) makes the thin line easy
   to land on; the overlay itself stays pointer-events:none so cards stay
   clickable. */
.ae-edge-hit { fill: none; stroke: transparent; stroke-width: 16; pointer-events: stroke; cursor: pointer; }
.ae-diagram.has-edge-focus .ae-edge { opacity: 0.1; }
.ae-diagram.has-edge-focus .ae-edge.is-edge-lit { opacity: 1; }
.ae-diagram.has-edge-focus .ae-edge.is-edge-lit .ae-edge-glow { opacity: 0.34; }
.ae-diagram.has-edge-focus .ae-diagram-edge-label { opacity: 0.12; }
.ae-diagram.has-edge-focus .ae-diagram-edge-label.is-edge-lit { opacity: 1; }
.ae-diagram.has-edge-focus .ae-edge-cap { opacity: 0.12; }
.ae-diagram.has-edge-focus .diagram-node { opacity: 0.4; transition: opacity 0.2s ease; }
.ae-diagram.has-edge-focus .diagram-node.is-edge-lit {
  opacity: 1;
  box-shadow: 0 0 0 2px var(--node-accent, var(--ae-diagram-accent)), 0 8px 24px rgba(0, 0, 0, 0.3);
}
/* keyboard focus ring on operable nodes */
.ae-diagram--interactive .diagram-node:focus-visible {
  outline: 2px solid var(--ae-diagram-accent);
  outline-offset: 3px;
}

/* ---- per-node tooltip (hover description) ---------------------------- */
/* A node given a `tooltip` (or `description`) gets a small corner info dot as
   the visual cue, plus a styled bubble that fades in on hover/focus. The node
   is already a containing block (it has a transform), so these anchor to it. */
.diagram-node-tipdot {
  position: absolute;
  top: 7px;
  right: 7px;
  width: 15px;
  height: 15px;
  color: var(--node-accent, var(--ae-diagram-accent, var(--primary)));
  opacity: 0.5;
  transition: opacity 0.2s ease;
  pointer-events: none;
}
.diagram-node-tipdot svg { width: 100%; height: 100%; display: block; }
.diagram-node--has-tip:hover .diagram-node-tipdot,
.diagram-node--has-tip:focus-visible .diagram-node-tipdot,
.diagram-node--has-tip:focus-visible .diagram-node-tipdot { opacity: 1; }

.ae-diagram-node-tip {
  position: absolute;
  bottom: calc(100% + 9px);
  left: 50%;
  transform: translate(-50%, 4px);
  width: max-content;
  max-width: 260px;
  padding: 0.5rem 0.7rem;
  border-radius: 8px;
  background: var(--card-bg, var(--bg-dark));
  border: 1px solid color-mix(in srgb, var(--node-accent, var(--ae-diagram-accent)) 40%, transparent);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.38);
  color: var(--text-secondary);
  font-size: 0.74rem;
  font-weight: 500;
  line-height: 1.45;
  text-align: left;
  white-space: normal;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s;
  z-index: 20;
}
.ae-diagram-node-tip strong { color: var(--text-primary); font-weight: 700; }
.ae-diagram-node-tip em { font-style: italic; }
.ae-diagram-node-tip code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.92em;
  background: color-mix(in srgb, var(--node-accent, var(--ae-diagram-accent)) 16%, transparent);
  padding: 0.02em 0.3em; border-radius: 4px;
}
.ae-diagram-node-tip a { color: var(--node-accent, var(--ae-diagram-accent)); text-decoration: underline; pointer-events: auto; }
.ae-diagram-node-tip::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: var(--card-bg, var(--bg-dark));
}
.diagram-node--has-tip:hover .ae-diagram-node-tip,
.diagram-node--has-tip:focus-visible .ae-diagram-node-tip,
.diagram-node--has-tip:focus-visible .ae-diagram-node-tip {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
}
/* Top-row cards flip the bubble BELOW the card (and the arrow to its top edge)
   so it can't clip off the top of the diagram. */
.diagram-node--toprow .ae-diagram-node-tip {
  bottom: auto;
  top: calc(100% + 9px);
  transform: translate(-50%, -4px);
}
.diagram-node--toprow .ae-diagram-node-tip::after {
  top: auto;
  bottom: 100%;
  border-top-color: transparent;
  border-bottom-color: var(--card-bg, var(--bg-dark));
}
/* A tooltip must never sit behind another layer. Its node lives in the stage
   (z-index 2), but edge labels are a sibling layer at z-index 4, so the bubble's
   own z-index can't beat them from inside the stage. While a has-tip node is
   hovered/focused, lift the whole stage above the label/overlay layers and lift
   the active lane + node above their siblings, so the bubble always paints on top. */
.ae-diagram-stage:has(.diagram-node--has-tip:hover),
.ae-diagram-stage:has(.diagram-node--has-tip:focus-visible) { z-index: 30; }
.ae-diagram-lane:has(.diagram-node--has-tip:hover),
.ae-diagram-lane:has(.diagram-node--has-tip:focus-visible) { z-index: 6; }
.diagram-node--has-tip:hover,
.diagram-node--has-tip:focus-visible { z-index: 6; }

/* ---- SVG edge overlay ------------------------------------------------ */
.ae-diagram-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  overflow: visible;
  pointer-events: none;
  z-index: 1;
  transition: opacity 0.3s ease;
}

.ae-edge-base,
.ae-edge-glow,
.ae-edge-core {
  fill: none;
  stroke-linecap: round;
  opacity: 0;
  transition: opacity 0.45s ease;
}

.ae-edge-base { stroke-width: 2.4; }
.ae-edge-core { stroke-width: 1; }
.ae-edge-glow { stroke-width: 7; filter: blur(3px); }
/* Invisible line, full-weight stroke: only its arrowhead shows. The head sizes
   to this stroke-width (matches the base) and paints above the bright core. */
.ae-edge-headcarrier { fill: none; stroke: transparent; stroke-width: 2.4; }

.ae-diagram-overlay.drawn .ae-edge-base { opacity: 0.9; }
.ae-diagram-overlay.drawn .ae-edge-core { opacity: 0.95; }
.ae-diagram-overlay.drawn .ae-edge-glow { opacity: 0.18; }

/* Inactive / optional paths: dimmed, muted-grey, dashed (dash set in JS). */
.ae-diagram-overlay.drawn .ae-edge-base.is-inactive { opacity: 0.45; stroke: var(--text-muted); }
.ae-edge-glow.is-inactive { display: none; }

/* Animated "data flow" on an edge. */
.ae-edge-base.is-flow { stroke-dasharray: 9 7; animation: ae-edge-flow 0.9s linear infinite; }
/* The march shift is one dash PERIOD; defaults to the 9 7 stream dash (-16) but an
   animated dashed/dotted/inactive line overrides dasharray + --ae-flow-shift inline
   so it marches its OWN pattern instead of being force-converted to the stream dash. */
@keyframes ae-edge-flow { to { stroke-dashoffset: var(--ae-flow-shift, -16px); } }

/* Bi-directional edge: rendered as two half-paths meeting at the label, each
   flowing OUTWARD from the centre toward its end (mirror / federation / sync).
   Dash pattern + matching --ae-flow-shift (one full period) are set inline so
   the loop is seamless for both dashed and dotted styles. */
.ae-edge-base.is-biflow { animation: ae-edge-biflow 0.9s linear infinite; }
@keyframes ae-edge-biflow { to { stroke-dashoffset: var(--ae-flow-shift, -13px); } }

/* Reference (no-copy) edge: a dotted pointer, never a glowing flow - it does
   not move data, so it carries no bloom halo. The dotted dash itself is set
   inline (style:'dotted') and the head is the hollow open arrowhead. */
.ae-edge-glow.is-reference { display: none; }

/* ---- edge colour palette (mirrors data-flow-diagram.css) ------------- */
.ae-edge-base--teal,   .ae-edge-glow--teal,   .ae-edge-core--teal   { stroke: #00B8B8; }
.ae-edge-base--blue,   .ae-edge-glow--blue,   .ae-edge-core--blue   { stroke: #3FA7D6; }
.ae-edge-base--orange, .ae-edge-glow--orange, .ae-edge-core--orange { stroke: #FF8C42; }
.ae-edge-base--purple, .ae-edge-glow--purple, .ae-edge-core--purple { stroke: #9C8FE0; }
.ae-edge-base--gold,   .ae-edge-glow--gold,   .ae-edge-core--gold   { stroke: #F4D03F; }
.ae-edge-base--bronze, .ae-edge-glow--bronze, .ae-edge-core--bronze { stroke: #CD7F32; }
.ae-edge-base--silver,   .ae-edge-glow--silver,   .ae-edge-core--silver   { stroke: #AAB7C7; }
.ae-edge-base--platinum, .ae-edge-glow--platinum, .ae-edge-core--platinum { stroke: #CDD6E2; }
.ae-edge-base--green,    .ae-edge-glow--green,    .ae-edge-core--green    { stroke: #33C06C; }
.ae-edge-base--indigo,   .ae-edge-glow--indigo,   .ae-edge-core--indigo   { stroke: #6E78E0; }
.ae-edge-base--rose,     .ae-edge-glow--rose,     .ae-edge-core--rose     { stroke: #F472A0; }
.ae-edge-base--danger, .ae-edge-glow--danger, .ae-edge-core--danger { stroke: #FF6B6B; }
.ae-edge-base--white,  .ae-edge-glow--white,  .ae-edge-core--white  { stroke: #FFFFFF; }
/* Muted line for inactive/optional paths (head + any solid lead-in carrying it). */
.ae-edge-base--muted, .ae-edge-glow--muted { stroke: var(--text-muted); }

/* ---- edge palette as custom properties -------------------------------
   The SAME 12 colours as the class rules above, also exposed as var(--<name>)
   custom properties. Two independent things read a diagram colour by name:
   (1) every per-feature class rule above/below (.ae-edge-base--X,
   .ae-diagram-edge-label--X, .ae-diagram-legend-swatch--X, .ae-diagram-group--X,
   .ae-edge-head[-open]--X) - these are the PRIMARY, robust mechanism and need
   no entry here; and (2) a handful of narrow JS call sites that build a
   var(--<name>) reference from a spec string at runtime (spec.accent ->
   --ae-diagram-accent) where there is no already-rendered element to read a
   computed colour from instead. Bronze/indigo/platinum/rose/silver had NO
   custom property here at all (teal/blue/orange/purple/gold/green/danger
   only, inherited from the site-wide brand palette in variables.css, which
   uses DIFFERENT shades than this file's own edge palette above) - undefined
   in CSS custom-property terms, so var(--bronze) etc. was invalid with no
   fallback and silently resolved to black. Kept ONE color per line, matching
   the stroke values immediately above, so the two stay trivially diffable.
   >>> Adding a 13th palette colour above? Add its var(--name) here too. <<< */
:root {
  --bronze: #CD7F32;
  --silver: #AAB7C7;
  --platinum: #CDD6E2;
  --indigo: #6E78E0;
  --rose: #F472A0;
}

.ae-edge-core--teal   { stroke: #7FF0F0; }
.ae-edge-core--blue   { stroke: #9AD2F0; }
.ae-edge-core--orange { stroke: #FFC79E; }
.ae-edge-core--purple { stroke: #D6CFF5; }
.ae-edge-core--gold   { stroke: #FBE7A0; }
.ae-edge-core--bronze { stroke: #E8B989; }
.ae-edge-core--silver   { stroke: #DCE3EC; }
.ae-edge-core--platinum { stroke: #EEF2F7; }
.ae-edge-core--green    { stroke: #86E0AA; }
.ae-edge-core--indigo   { stroke: #AEB4F0; }
.ae-edge-core--rose     { stroke: #FBB9D2; }
.ae-edge-core--danger { stroke: #FFB3B3; }
.ae-edge-core--white  { stroke: #FFFFFF; }

.ae-edge-head { stroke: none; }
/* Terminal dot for arrows:none "covers/applies-to" attachments (fill comes from
   the matching .ae-edge-head--<colour> rule below). */
.ae-edge-cap { stroke: none; opacity: 0.85; }
.ae-edge-head--teal   { fill: #00B8B8; }
.ae-edge-head--blue   { fill: #3FA7D6; }
.ae-edge-head--orange { fill: #FF8C42; }
.ae-edge-head--purple { fill: #9C8FE0; }
.ae-edge-head--gold   { fill: #F4D03F; }
.ae-edge-head--bronze { fill: #CD7F32; }
.ae-edge-head--silver   { fill: #AAB7C7; }
.ae-edge-head--platinum { fill: #CDD6E2; }
.ae-edge-head--green    { fill: #33C06C; }
.ae-edge-head--indigo   { fill: #6E78E0; }
.ae-edge-head--rose     { fill: #F472A0; }
.ae-edge-head--danger { fill: #FF6B6B; }
.ae-edge-head--white  { fill: #FFFFFF; }
/* Muted head for an inactive/optional path: matches the dimmed grey line so the
   arrowhead never shows full colour on a faded connector. */
.ae-edge-head--muted  { fill: var(--text-muted); opacity: 0.55; }

/* Hollow arrowhead for reference (no-copy) edges: stroke-only chevron in the
   line colour, so a pointer reads differently from a filled flow arrowhead. */
.ae-edge-head--open { fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.ae-edge-head-open--teal   { stroke: #00B8B8; }
.ae-edge-head-open--blue   { stroke: #3FA7D6; }
.ae-edge-head-open--orange { stroke: #FF8C42; }
.ae-edge-head-open--purple { stroke: #9C8FE0; }
.ae-edge-head-open--gold   { stroke: #F4D03F; }
.ae-edge-head-open--bronze { stroke: #CD7F32; }
.ae-edge-head-open--silver   { stroke: #AAB7C7; }
.ae-edge-head-open--platinum { stroke: #CDD6E2; }
.ae-edge-head-open--green    { stroke: #33C06C; }
.ae-edge-head-open--indigo   { stroke: #6E78E0; }
.ae-edge-head-open--rose     { stroke: #F472A0; }
.ae-edge-head-open--danger { stroke: #FF6B6B; }
.ae-edge-head-open--white  { stroke: #FFFFFF; }
.ae-edge-head-open--muted  { stroke: var(--text-muted); opacity: 0.55; }

/* Light mode: deepen the line/arrowhead colours for contrast on the light
   gradient, and fade the pale "core" highlight (which would otherwise wash
   the line out to near-invisible on white). NOTE: only the arrowhead (head)
   gets a fill — the base/core/glow are open paths and must stay fill:none,
   otherwise the elbow path fills into a solid wedge. */
[data-theme="light"] .ae-edge-base--teal   { stroke: #007A7A; }
[data-theme="light"] .ae-edge-base--blue   { stroke: #1F5F80; }
[data-theme="light"] .ae-edge-base--orange { stroke: #D9712A; }
[data-theme="light"] .ae-edge-base--purple { stroke: #6B5FB0; }
[data-theme="light"] .ae-edge-base--gold   { stroke: #B8901F; }
[data-theme="light"] .ae-edge-base--bronze { stroke: #A0662A; }
[data-theme="light"] .ae-edge-base--silver   { stroke: #6E7B8C; }
[data-theme="light"] .ae-edge-base--platinum { stroke: #8893A2; }
[data-theme="light"] .ae-edge-base--green    { stroke: #1E8E4E; }
[data-theme="light"] .ae-edge-base--indigo   { stroke: #4A52B8; }
[data-theme="light"] .ae-edge-base--rose     { stroke: #C84B7C; }
[data-theme="light"] .ae-edge-base--danger { stroke: #C53D3D; }
[data-theme="light"] .ae-edge-base--white  { stroke: #2B3440; }
[data-theme="light"] {
  --bronze: #A0662A;
  --silver: #6E7B8C;
  --platinum: #8893A2;
  --indigo: #4A52B8;
  --rose: #C84B7C;
}

[data-theme="light"] .ae-edge-head--teal   { fill: #007A7A; }
[data-theme="light"] .ae-edge-head--blue   { fill: #1F5F80; }
[data-theme="light"] .ae-edge-head--orange { fill: #D9712A; }
[data-theme="light"] .ae-edge-head--purple { fill: #6B5FB0; }
[data-theme="light"] .ae-edge-head--gold   { fill: #B8901F; }
[data-theme="light"] .ae-edge-head--bronze { fill: #A0662A; }
[data-theme="light"] .ae-edge-head--silver   { fill: #6E7B8C; }
[data-theme="light"] .ae-edge-head--platinum { fill: #8893A2; }
[data-theme="light"] .ae-edge-head--green    { fill: #1E8E4E; }
[data-theme="light"] .ae-edge-head--indigo   { fill: #4A52B8; }
[data-theme="light"] .ae-edge-head--rose     { fill: #C84B7C; }
[data-theme="light"] .ae-edge-head--danger { fill: #C53D3D; }
[data-theme="light"] .ae-edge-head--white  { fill: #2B3440; }

[data-theme="light"] .ae-edge-head-open--teal   { stroke: #007A7A; }
[data-theme="light"] .ae-edge-head-open--blue   { stroke: #1F5F80; }
[data-theme="light"] .ae-edge-head-open--orange { stroke: #D9712A; }
[data-theme="light"] .ae-edge-head-open--purple { stroke: #6B5FB0; }
[data-theme="light"] .ae-edge-head-open--gold   { stroke: #B8901F; }
[data-theme="light"] .ae-edge-head-open--bronze { stroke: #A0662A; }
[data-theme="light"] .ae-edge-head-open--silver   { stroke: #6E7B8C; }
[data-theme="light"] .ae-edge-head-open--platinum { stroke: #8893A2; }
[data-theme="light"] .ae-edge-head-open--green    { stroke: #1E8E4E; }
[data-theme="light"] .ae-edge-head-open--indigo   { stroke: #4A52B8; }
[data-theme="light"] .ae-edge-head-open--rose     { stroke: #C84B7C; }
[data-theme="light"] .ae-edge-head-open--danger { stroke: #C53D3D; }
[data-theme="light"] .ae-edge-head-open--white  { stroke: #2B3440; }

[data-theme="light"] .ae-diagram-overlay.drawn .ae-edge-base { opacity: 1; }
[data-theme="light"] .ae-diagram-overlay.drawn .ae-edge-core { opacity: 0.22; }
[data-theme="light"] .ae-diagram-overlay.drawn .ae-edge-glow { opacity: 0.1; }

/* Light-mode card occlusion. The SVG edge layer renders BEHIND the node cards.
   In dark mode the coloured .diagram-node variants are opaque, so an edge that
   routes behind a card is hidden. In light mode (guide-diagrams.css) those same
   variants are translucent tints (~6% alpha), so a line shows THROUGH the card
   it passes behind - visible only in light theme. Stack the identical tint over
   an opaque card surface here so the cards occlude edges in BOTH themes. Scoped
   to .ae-diagram so standalone guide .diagram-node usage is untouched. */
[data-theme="light"] .ae-diagram .diagram-node--teal   { background: linear-gradient(rgba(0,153,153,0.06), rgba(0,153,153,0.06)), var(--card-bg); }
[data-theme="light"] .ae-diagram .diagram-node--orange { background: linear-gradient(rgba(255,140,66,0.06), rgba(255,140,66,0.06)), var(--card-bg); }
[data-theme="light"] .ae-diagram .diagram-node--blue   { background: linear-gradient(rgba(0,88,124,0.06), rgba(0,88,124,0.06)), var(--card-bg); }
[data-theme="light"] .ae-diagram .diagram-node--gold   { background: linear-gradient(rgba(244,208,63,0.06), rgba(244,208,63,0.06)), var(--card-bg); }
[data-theme="light"] .ae-diagram .diagram-node--bronze { background: linear-gradient(rgba(205,127,50,0.06), rgba(205,127,50,0.06)), var(--card-bg); }
[data-theme="light"] .ae-diagram .diagram-node--purple { background: linear-gradient(rgba(132,116,178,0.06), rgba(132,116,178,0.06)), var(--card-bg); }
[data-theme="light"] .ae-diagram .diagram-node--silver   { background: linear-gradient(rgba(110,123,140,0.07), rgba(110,123,140,0.07)), var(--card-bg); }
[data-theme="light"] .ae-diagram .diagram-node--platinum { background: linear-gradient(rgba(136,147,162,0.07), rgba(136,147,162,0.07)), var(--card-bg); }
[data-theme="light"] .ae-diagram .diagram-node--green    { background: linear-gradient(rgba(51,192,108,0.06), rgba(51,192,108,0.06)), var(--card-bg); }
[data-theme="light"] .ae-diagram .diagram-node--indigo   { background: linear-gradient(rgba(110,120,224,0.06), rgba(110,120,224,0.06)), var(--card-bg); }
[data-theme="light"] .ae-diagram .diagram-node--rose     { background: linear-gradient(rgba(244,114,160,0.06), rgba(244,114,160,0.06)), var(--card-bg); }
[data-theme="light"] .ae-diagram .diagram-node--white    { background: linear-gradient(rgba(43,52,64,0.07), rgba(43,52,64,0.07)), var(--card-bg); }

/* ---- responsive: stack lanes as rows, wrap nodes -------------------- */
@media (max-width: 680px) {
  .ae-diagram-canvas { width: auto; }
  .ae-diagram-stage { flex-direction: column; gap: 1.85rem; align-items: stretch; width: auto; }
  .ae-diagram-lane-nodes { flex-direction: column; gap: 1.1rem; }
  .ae-diagram-row { flex-wrap: wrap; gap: 0.85rem; }
  .ae-diagram .diagram-node { min-width: 92px; max-width: none; min-height: 72px; padding: 0.7rem 0.8rem; }
  .ae-diagram .diagram-icon { width: 26px; height: 26px; }
  .ae-diagram .diagram-icon svg { width: 22px; height: 22px; }
  .ae-diagram .diagram-label { font-size: 0.82rem; }
}

@media (prefers-reduced-motion: reduce) {
  .ae-edge-base, .ae-edge-glow, .ae-edge-core { transition: opacity 0.2s ease; }
  .ae-edge-base.is-flow { animation: none; stroke-dasharray: none; }
  .ae-edge-base.is-biflow { animation: none; }
  .ae-diagram-legend-swatch--flow { animation: none; }
}

/* ---- node links ----------------------------------------------------- */
.ae-diagram a.diagram-node { text-decoration: none; cursor: pointer; color: inherit; }

/* ---- group boundary boxes (behind nodes) ---------------------------- */
.ae-diagram-groups {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  pointer-events: none;
  z-index: 0;
}
.ae-diagram-group {
  position: absolute;
  --g: var(--ae-diagram-accent);
  border: 1.5px dashed color-mix(in srgb, var(--g) 55%, transparent);
  border-radius: 16px;
  background: color-mix(in srgb, var(--g) 5%, transparent);
}
.ae-diagram-group--teal   { --g: #00B8B8; }
.ae-diagram-group--blue   { --g: #3FA7D6; }
.ae-diagram-group--orange { --g: #FF8C42; }
.ae-diagram-group--purple { --g: #9C8FE0; }
.ae-diagram-group--gold   { --g: #F4D03F; }
.ae-diagram-group--bronze { --g: #CD7F32; }
.ae-diagram-group--silver   { --g: #AAB7C7; }
.ae-diagram-group--platinum { --g: #CDD6E2; }
.ae-diagram-group--green    { --g: #33C06C; }
.ae-diagram-group--indigo   { --g: #6E78E0; }
.ae-diagram-group--rose     { --g: #F472A0; }
.ae-diagram-group--danger { --g: #FF6B6B; }
.ae-diagram-group--white  { --g: #FFFFFF; }
[data-theme="light"] .ae-diagram-group--white { --g: #2B3440; }
/* Outline style overrides (default is dashed, set on .ae-diagram-group). */
.ae-diagram-group--solid  { border-style: solid; }
.ae-diagram-group--dotted { border-style: dotted; }
.ae-diagram-group-label {
  position: absolute;
  top: -0.72rem;
  left: 0.9rem;
  padding: 0.12rem 0.55rem;
  border-radius: 6px;
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  background: var(--bg-dark);
  color: var(--g);
  border: 1px solid color-mix(in srgb, var(--g) 40%, transparent);
}
/* Title-tab seat along the top edge (default left). */
.ae-diagram-group-label--center {
  left: 50%;
  transform: translateX(-50%);
}
.ae-diagram-group-label--right {
  left: auto;
  right: 0.9rem;
}
/* When a non-member card sits directly above the frame, the straddling tab would
   be clipped by it - seat the tab just inside the top border instead. */
.ae-diagram-group-label--inside {
  top: 0.28rem;
}
/* Seat the title tab on the bottom edge instead of the top (composes with the
   left/center/right horizontal seats). */
.ae-diagram-group-label--bottom {
  top: auto;
  bottom: -0.72rem;
}
/* GR-F2: pill seat relative to the border LINE (screen-space semantics, same
   family as edge captions): above = fully outside a top edge / inside a bottom
   edge; below = the mirror. No seat class keeps the straddling default. */
.ae-diagram-group-label--seat-above { top: -1.55rem; }
.ae-diagram-group-label--seat-below { top: 0.28rem; }
.ae-diagram-group-label--bottom.ae-diagram-group-label--seat-above { top: auto; bottom: 0.28rem; }
.ae-diagram-group-label--bottom.ae-diagram-group-label--seat-below { top: auto; bottom: -1.55rem; }

/* ---- edge labels (above edges) -------------------------------------- */
.ae-diagram-labels {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  pointer-events: none;
  z-index: 4;
}
.ae-diagram-edge-label {
  position: absolute;
  transform: translate(-50%, -50%);
  padding: 0.1rem 0.45rem;
  border-radius: 6px;
  font-size: 0.68rem;
  font-weight: 600;
  white-space: nowrap;
  /* --ae-label-bg-alpha (1 = solid, 0 = fully transparent) is inherited from the
     diagram container or set inline per-label from the spec's labelBg; the var()
     fallback (1) is the default, and is NOT declared on this rule so an inherited
     value can win. The border fades with it so a transparent label is just
     floating colour text, not an empty outlined pill. */
  background: color-mix(in srgb, var(--bg-dark) calc(var(--ae-label-bg-alpha, 1) * 100%), transparent);
  border: 1px solid color-mix(in srgb, var(--el) calc(35% * var(--ae-label-bg-alpha, 1)), transparent);
  color: var(--el);
  --el: var(--text-primary);
}
.ae-diagram-edge-label--teal   { --el: #14b8b8; }
.ae-diagram-edge-label--blue   { --el: #3FA7D6; }
.ae-diagram-edge-label--orange { --el: #FF8C42; }
.ae-diagram-edge-label--purple { --el: #9C8FE0; }
.ae-diagram-edge-label--gold   { --el: #d4b021; }
.ae-diagram-edge-label--bronze { --el: #CD7F32; }
.ae-diagram-edge-label--silver   { --el: #9aa7b8; }
.ae-diagram-edge-label--platinum { --el: #b6c0ce; }
.ae-diagram-edge-label--green    { --el: #33C06C; }
.ae-diagram-edge-label--indigo   { --el: #8088E8; }
.ae-diagram-edge-label--rose     { --el: #F472A0; }
.ae-diagram-edge-label--danger { --el: #FF6B6B; }
.ae-diagram-edge-label--white  { --el: #FFFFFF; }
[data-theme="light"] .ae-diagram-edge-label--teal { --el: #007A7A; }
[data-theme="light"] .ae-diagram-edge-label--gold { --el: #B8901F; }
[data-theme="light"] .ae-diagram-edge-label--silver   { --el: #5e6b7c; }
[data-theme="light"] .ae-diagram-edge-label--platinum { --el: #6b7686; }
[data-theme="light"] .ae-diagram-edge-label--green    { --el: #1E8E4E; }
[data-theme="light"] .ae-diagram-edge-label--indigo   { --el: #4A52B8; }
[data-theme="light"] .ae-diagram-edge-label--rose     { --el: #C84B7C; }
[data-theme="light"] .ae-diagram-edge-label--white    { --el: #2B3440; }

/* R3-D: an inactive/"planned" edge's LINE renders desaturated to --text-muted
   (.ae-edge-base.is-inactive above), not its nominal e.color - a not-yet-built
   connection is meant to read as de-emphasised. The label chip must match what's
   actually visible, or a vividly-coloured chip beside a barely-visible muted line
   reads as orphaned ("purple label, but where's the purple line?"). Placed after
   every --<color> variant so it wins the cascade regardless of which colour class
   also applies (same specificity, source order decides). */
.ae-diagram-edge-label.is-inactive { --el: var(--text-muted); }

/* ---- legend --------------------------------------------------------- */
.ae-diagram-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.25rem;
  justify-content: center;
  /* Matches the diagram's 2rem top margin so the legend sits the same distance
     below the diagram as the diagram sits below whatever precedes it (the fit-
     to-width pass reclaims the scale slack exactly, so this gap is honoured at
     any scale). */
  margin-top: 2rem;
  font-size: 0.78rem;
  color: var(--text-muted);
}
/* Two-part key: a labelled "Layers" cluster and a "Connections" cluster, with a
   wider gap between the two groups than within them. */
.ae-diagram-legend--grouped { gap: 0.5rem 2.4rem; }
.ae-diagram-legend-group { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 0.5rem 1rem; }
.ae-diagram-legend-title {
  font-size: 0.64rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-weight: 700;
  color: var(--text-muted);
  opacity: 0.85;
}
.ae-diagram-legend-item { display: inline-flex; align-items: center; gap: 0.45rem; }
.ae-diagram-legend-swatch { width: 16px; height: 3px; border-radius: 2px; }
.ae-diagram-legend-swatch--teal   { background: #00B8B8; }
.ae-diagram-legend-swatch--blue   { background: #3FA7D6; }
.ae-diagram-legend-swatch--orange { background: #FF8C42; }
.ae-diagram-legend-swatch--purple { background: #9C8FE0; }
.ae-diagram-legend-swatch--gold   { background: #F4D03F; }
.ae-diagram-legend-swatch--bronze { background: #CD7F32; }
.ae-diagram-legend-swatch--silver   { background: #AAB7C7; }
.ae-diagram-legend-swatch--platinum { background: #CDD6E2; }
.ae-diagram-legend-swatch--green    { background: #33C06C; }
.ae-diagram-legend-swatch--indigo   { background: #6E78E0; }
.ae-diagram-legend-swatch--rose     { background: #F472A0; }
.ae-diagram-legend-swatch--danger { background: #FF6B6B; }
.ae-diagram-legend-swatch--white  { background: #FFFFFF; }
[data-theme="light"] .ae-diagram-legend-swatch--white { background: #2B3440; }
/* line-style swatches: mask the coloured bar into dashes/dots so the legend
   entry mirrors the edge style it explains (e.g. an optional/secondary path). */
.ae-diagram-legend-swatch--dashed,
.ae-diagram-legend-swatch--inactive {
  -webkit-mask: repeating-linear-gradient(to right, #000 0 5px, transparent 5px 8px);
          mask: repeating-linear-gradient(to right, #000 0 5px, transparent 5px 8px);
}
.ae-diagram-legend-swatch--dotted {
  -webkit-mask: repeating-linear-gradient(to right, #000 0 2px, transparent 2px 5px);
          mask: repeating-linear-gradient(to right, #000 0 2px, transparent 2px 5px);
}
.ae-diagram-legend-swatch--inactive { background: var(--text-muted); opacity: 0.75; }
/* "colour varies" swatch: when distinguishLines auto-tints a kind's lines into
   several colours, its connection-key swatch goes neutral grey so the legend
   never claims a single colour for that kind. Texture (the mask) still shows. */
.ae-diagram-legend-swatch--varies { background: var(--text-muted); }
/* kind swatches: stream = a moving dash (animated mask), reference = a static
   dotted pointer. Batch needs no rule - it is the plain solid colour bar. */
.ae-diagram-legend-swatch--flow {
  -webkit-mask: linear-gradient(to right, #000 0 5px, transparent 5px 8px);
          mask: linear-gradient(to right, #000 0 5px, transparent 5px 8px);
  -webkit-mask-size: 8px 100%; mask-size: 8px 100%;
  -webkit-mask-repeat: repeat-x; mask-repeat: repeat-x;
  animation: ae-legend-flow 0.9s linear infinite;
}
@keyframes ae-legend-flow { to { -webkit-mask-position: 8px 0; mask-position: 8px 0; } }
.ae-diagram-legend-swatch--reference {
  -webkit-mask: repeating-linear-gradient(to right, #000 0 2px, transparent 2px 5px);
          mask: repeating-linear-gradient(to right, #000 0 2px, transparent 2px 5px);
}
