/* ═══════════════════════════════════════════════════════════════════════════
   graph-canvas.css — "LUMINOUS DEPTH"
   Art direction for the Solution Architecture Canvas.

   Extends assets/agent-console.css (.ac scope, --ac-* tokens). Load AFTER it
   and after falaq-2026.css. No new fonts, no external assets, no frameworks.

   The one rule that governs everything below:
     LIGHT IS A STATE VARIABLE, NOT DECORATION.
   A node only emits light once it is confirmed. Unknowns sit deeper in the
   volume, unlit and out of focus. Motion is spent only on things that are
   unresolved — so a finished architecture is bright, sharp and completely
   still. The canvas is calm when the work is done. That is the whole idea.

   Contract with graph-canvas.js:
     - JS owns `transform` on .gc-node and .gc-stage. CSS here NEVER writes
       `transform` on those two. Depth/lift use the individual `scale:` and
       `translate:` properties, which compose with the JS transform.
     - MOVE_MS 520 / ENTER_MS 380 / EXIT_MS 260 / EASE cubic-bezier(.22,.9,.28,1)
       are mirrored as tokens below. Change them in both places or not at all.
     - The shell flips .ac[data-mode] from "centered" to "split", then
       calls canvas.refit() on transitionend.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Tokens ────────────────────────────────────────────────────────────────
   Added to the existing .ac block rather than a new :root, so the canvas
   inherits the console's surface ladder and dies with it.                   */

.ac {
  /* Field — the chamber the graph is suspended in. */
  --gc-void:        #050507;
  --gc-field:       #08090c;

  /* There are exactly two working hues. Ice-blue carries structure; amber
     carries "you have to answer this". A seven-colour node palette would make
     kind loud and state quiet — the wrong way round. */
  --gc-beam:        var(--ac-cyan);            /* #4da3ff  structure light   */
  --gc-deep:        var(--ac-violet);          /* #1a6dff  depth light       */
  --gc-ice:         #cfe9ff;                   /*          rim / specular    */
  --gc-await:       #f2b23c;                   /*          PENDING ONLY      */
  --gc-ready:       var(--ac-mint);            /* #4ade80  chamber warm-up   */

  /* Glass. Dark glass — the body never lightens enough to threaten text
     contrast (brightest body ≈ #1c2129 against #e9e7ec ≈ 11:1). */
  --gc-glass-hi:    rgba(28, 33, 41, .78);
  --gc-glass-lo:    rgba(12, 14, 18, .86);
  --gc-glass-blur:  10px;
  --gc-rim:         rgba(207, 233, 255, .16);
  --gc-rim-hot:     rgba(207, 233, 255, .5);
  --gc-wire:        rgba(233, 231, 236, .13);

  /* Depth ladder. z is expressed as shadow spread + how dark the ground pool
     under the node is. Back = flat and close to the floor, front = lifted. */
  --gc-z-back:      0 4px 12px rgba(0, 0, 0, .42);
  --gc-z-mid:       0 12px 26px rgba(0, 0, 0, .5), 0 2px 6px rgba(0, 0, 0, .4);
  --gc-z-front:     0 22px 50px rgba(0, 0, 0, .58), 0 4px 12px rgba(0, 0, 0, .45);

  /* Emission ladder — how much light a node makes of its own. */
  --gc-em-1:        0 0 26px -10px rgba(77, 163, 255, .55);
  --gc-em-2:        0 0 34px -12px rgba(77, 163, 255, .6);
  --gc-em-3:        0 0 46px -14px rgba(77, 163, 255, .75);

  /* Clock — mirrors the JS constants. */
  --gc-move:        520ms;
  --gc-enter:       380ms;
  --gc-exit:        260ms;
  --gc-ease:        cubic-bezier(.22, .9, .28, 1);
  --gc-ease-out:    cubic-bezier(.16, 1, .3, 1);
}

/* ── The chamber ───────────────────────────────────────────────────────────
   Not a panel with a diagram on it: a volume with light in it. Three layers —
   graded ground, a receding dot grid, and one very slow aurora. The aurora is
   the ONLY ambient loop in the whole canvas: 48s, under 4% amplitude, below
   the perceptual threshold for "something is animating at me".              */

.gc-viewport {
  position: relative;
  z-index: 0;
  display: grid;
  place-content: center;
  isolation: isolate;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  direction: ltr;                 /* geometry only — node text re-mirrors below */
  background:
    radial-gradient(120% 88% at 50% -18%, rgba(26, 109, 255, .16), transparent 62%),
    radial-gradient(88% 70% at 50% 118%, rgba(26, 109, 255, .07), transparent 60%),
    linear-gradient(180deg, var(--gc-field), var(--gc-void) 78%);
  /* Soft edge falloff on the content itself, not just the background, so the
     chamber reads as unbounded rather than as a box with a border. */
  -webkit-mask-image: radial-gradient(126% 118% at 50% 50%, #000 62%, rgba(0, 0, 0, .55) 88%, transparent 100%);
  mask-image: radial-gradient(126% 118% at 50% 50%, #000 62%, rgba(0, 0, 0, .55) 88%, transparent 100%);
  transition: box-shadow 1200ms ease;
}

/* Grid — the floor of the volume. Masked to a pool of light in the middle so
   it recedes toward the walls instead of tiling flatly to the edges. */
.gc-viewport::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: -10%;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(210, 230, 255, .05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(210, 230, 255, .05) 1px, transparent 1px),
    radial-gradient(circle, rgba(210, 230, 255, .13) .7px, transparent .8px);
  background-size: 44px 44px, 44px 44px, 8px 8px;
  -webkit-mask-image: radial-gradient(76% 70% at 50% 46%, #000 8%, rgba(0, 0, 0, .5) 54%, transparent 88%);
  mask-image: radial-gradient(76% 70% at 50% 46%, #000 8%, rgba(0, 0, 0, .5) 54%, transparent 88%);
}

/* Aurora — depth haze. Pure radial gradients, no blur filter, screen-blended. */
.gc-viewport::after {
  content: "";
  position: absolute;
  z-index: -1;
  inset: -20%;
  pointer-events: none;
  opacity: .55;
  background:
    radial-gradient(28% 34% at 26% 30%, rgba(77, 163, 255, .2), transparent 70%),
    radial-gradient(24% 30% at 74% 66%, rgba(26, 109, 255, .16), transparent 72%),
    radial-gradient(30% 26% at 58% 12%, rgba(141, 214, 255, .1), transparent 70%);
  mix-blend-mode: screen;
  animation: gcChamberDrift 48s ease-in-out infinite alternate;
  transition: opacity 1200ms ease;
  will-change: transform;
}

/* The reward: when the architecture is agreed, the room warms up. This is the
   only place --gc-ready is ever used. */
.ac[data-status="ready"] .gc-viewport::after { opacity: .8; }
.ac[data-status="ready"] .gc-viewport { box-shadow: inset 0 0 140px -50px rgba(74, 222, 128, .2); }

@keyframes gcChamberDrift {
  from { transform: translate3d(-1.2%, .8%, 0) scale(1); }
  to   { transform: translate3d(1.4%, -1%, 0) scale(1.04); }
}

/* ── Stage / layers ─────────────────────────────────────────────────────── */

.gc-stage {
  position: relative;
  transform-origin: 50% 50%;      /* JS writes scale() into `transform` */
  opacity: 1;
  scale: 1;
}

.gc-edges {
  position: absolute;
  z-index: 1;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  fill: none;
}

.gc-nodes {
  position: absolute;
  z-index: 2;
  inset: 0;
}

/* ── Connectors — energy travelling between nodes ──────────────────────────
   State is carried by stroke pattern first and colour second: solid beam =
   settled, dashed = provisional, sparse dots crawling = live current, fine
   dotted and static = dead line. Only the unresolved ones move.            */

.gc-edge {
  transition: opacity 320ms ease;
}

.gc-edge-line {
  fill: none;
  stroke: var(--gc-wire);
  stroke-width: 1.4;
  stroke-linecap: round;
  /* The stage is scaled to fit; without this the connective tissue thins out
     of existence on a large graph. Legibility beats strict proportion here. */
  vector-effect: non-scaling-stroke;
  transition: stroke 320ms ease, opacity 320ms ease;
}

/* confirmed — a settled beam. Solid, luminous, perfectly still. */
.gc-edge[data-state="confirmed"] .gc-edge-line {
  stroke: url(#gc-flow);
  stroke-width: 1.8;
  opacity: 1;
  filter: drop-shadow(0 0 4px rgba(77, 163, 255, .55));
}

/* inferred — the architect's guess. Dashed, and drifting almost imperceptibly
   because it has not settled yet. */
.gc-edge[data-state="inferred"] .gc-edge-line {
  stroke: rgba(141, 214, 255, .5);
  stroke-width: 1.5;
  stroke-dasharray: 7 6;
  opacity: .8;
  animation: gcDrift 5.6s linear infinite;
}

/* pending — live current running toward a question you have not answered. */
.gc-edge[data-state="pending"] .gc-edge-line {
  stroke: var(--gc-await);
  stroke-width: 1.7;
  stroke-dasharray: 2 10;
  opacity: .95;
  filter: drop-shadow(0 0 5px rgba(242, 178, 60, .5));
  animation: gcCurrent 1.5s linear infinite;
}

/* placeholder — no current. Cold, hairline, dotted, static. */
.gc-edge[data-state="placeholder"] .gc-edge-line {
  stroke: rgba(233, 231, 236, .2);
  stroke-width: 1.3;
  stroke-dasharray: 1 7;
  opacity: .6;
}

/* Conditional edges are thinner and carry their condition as a label; they are
   NOT given a hue of their own. The words are the signal. */
.gc-edge[data-kind="conditional"] .gc-edge-line { stroke-width: 1.2; opacity: .78; }

.gc-edge.is-exiting { opacity: 0; transition-duration: var(--gc-exit); }

.gc-edge-label {
  fill: var(--ac-text-2);
  stroke: var(--gc-void);
  stroke-width: 3.5;
  paint-order: stroke fill;
  font-family: var(--f-data);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: .06em;
  text-anchor: middle;
  dominant-baseline: middle;
}
html[dir="rtl"] .gc-edge-label {
  font-family: var(--f-body);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0;              /* never letter-space Arabic */
}

@keyframes gcDrift   { to { stroke-dashoffset: -26; } }
@keyframes gcCurrent { to { stroke-dashoffset: -36; } }

/* ── Node — a slab of dark glass lit from within ───────────────────────────
   172 x 78 exactly, per graph-layout.js. Budget:
     9 top + 20 head + 3 + 2 label lines (25.8) + 2 + sub (10.8) + 7 bottom.
   The state badge rides the rim, outside the box, so it never competes with
   the label for those 58 vertical pixels.                                   */

.gc-node {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  display: flex;
  width: 172px;
  height: 78px;
  flex-direction: column;
  gap: 3px;
  padding: 9px 11px 7px;
  overflow: visible;
  appearance: none;
  -webkit-appearance: none;
  border: 1px solid var(--gc-rim);
  border-radius: 13px;
  background:
    linear-gradient(157deg, rgba(255, 255, 255, .055), rgba(255, 255, 255, 0) 42%),
    linear-gradient(180deg, var(--gc-glass-hi), var(--gc-glass-lo));
  color: var(--ac-text);
  text-align: start;
  cursor: pointer;
  scale: var(--gc-scale, 1);
  translate: 0 0;
  filter: blur(0) saturate(1);
  box-shadow: var(--gc-z-mid), inset 0 1px 0 rgba(255, 255, 255, .06);
  transition:
    transform var(--gc-move) var(--gc-ease),
    scale var(--gc-enter) var(--gc-ease),
    translate var(--gc-enter) var(--gc-ease),
    opacity var(--gc-enter) ease,
    filter var(--gc-enter) ease,
    box-shadow 260ms ease,
    border-color 260ms ease,
    background 260ms ease;
  will-change: transform;
}

/* Frost. Edges passing behind a node are blurred by it — that single effect
   does more for depth than any amount of shadow. */
@supports (backdrop-filter: blur(2px)) {
  .gc-node { backdrop-filter: blur(var(--gc-glass-blur)) saturate(125%); }
}

.gc-node:focus-visible { outline: 2px solid var(--gc-ice); outline-offset: 4px; }

/* Arabic: the node mirrors its own text, but the box never moves — the graph
   is left-to-right in both languages, and the ports stay physically pinned. */
html[dir="rtl"] .gc-node { direction: rtl; }

/* Specular sheen. Idle at 0; used on hover, selection, and as the sweep that
   passes over a node the moment it resolves. */
.gc-node::after {
  content: "";
  position: absolute;
  z-index: 3;
  inset: 0;
  border-radius: inherit;
  opacity: 0;
  pointer-events: none;
  background: linear-gradient(118deg, transparent 28%, rgba(207, 233, 255, .12) 46%, rgba(207, 233, 255, .02) 58%, transparent 72%);
  background-size: 260% 100%;
  background-position: 130% 0;
  transition: opacity 260ms ease;
}

/* ── Node anatomy ─────────────────────────────────────────────────────────── */

.gc-node-head {
  display: flex;
  height: 20px;
  flex: none;
  align-items: center;
  gap: 7px;
}

.gc-node-head em {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  color: var(--ac-text-3);
  font-family: var(--f-data);
  font-size: .46rem;
  font-style: normal;
  font-weight: 600;
  letter-spacing: .12em;
  text-overflow: ellipsis;
  text-transform: uppercase;
  white-space: nowrap;
}
html[dir="rtl"] .gc-node-head em {
  font-family: var(--f-body);
  font-size: .56rem;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
}

.gc-node > strong[data-label] {
  display: -webkit-box;
  position: relative;
  z-index: 1;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  color: var(--ac-text);
  font-size: .66rem;
  font-weight: 600;
  letter-spacing: -.01em;
  line-height: 1.2;
  overflow-wrap: anywhere;
}
html[dir="rtl"] .gc-node > strong[data-label] { font-weight: 700; letter-spacing: 0; }

.gc-node > small[data-sub] {
  position: relative;
  z-index: 1;
  margin-top: auto;               /* pins to the floor of the node */
  overflow: hidden;
  color: var(--ac-text-3);
  font-size: .48rem;
  line-height: 1.4;
  text-overflow: ellipsis;
  white-space: nowrap;
}
html[dir="rtl"] .gc-node > small[data-sub] { font-size: .55rem; }
.gc-node > small[hidden] { display: none; }

/* A remark about the step — a volume, a rule, a caveat.

   Facts like "٣٠ مريض أسبوعيًا" used to be added as nodes, because a box was
   the only way to get anything onto the canvas. A quantity is not a step in a
   process and has nothing to point at, so it landed as a stray trigger floating
   beside the flow with no edges. It belongs to the step it describes, and it is
   drawn as a marginal note on that step: quieter than the label, in the one
   colour reserved for "this needs your attention" only at half strength, so it
   reads as annotation rather than as another thing in the flow. */
.gc-node > .gc-note {
  position: relative;
  z-index: 1;
  margin-top: 2px;
  overflow: hidden;
  padding-inline-start: 6px;
  border-inline-start: 2px solid rgba(242, 178, 60, .45);
  color: rgba(233, 231, 236, .62);
  font-size: .46rem;
  line-height: 1.35;
  text-overflow: ellipsis;
  white-space: nowrap;
}
html[dir="rtl"] .gc-node > .gc-note { font-size: .53rem; }
.gc-node > .gc-note[hidden] { display: none; }

/* ── Kind — seven silhouettes, one hue ─────────────────────────────────────
   Kind is carried by the shape of the icon chip and by the word already
   printed next to it. Giving each kind a colour would spend the palette on
   the least urgent axis and leave state fighting for attention.            */

.gc-node-icon {
  position: relative;
  display: grid;
  width: 20px;
  height: 20px;
  flex: none;
  place-items: center;
  border-radius: 7px;
  background:
    radial-gradient(120% 120% at 30% 20%, rgba(141, 214, 255, var(--gc-chip-a, .14)), transparent 70%),
    rgba(141, 214, 255, .045);
  color: var(--gc-ice);
  box-shadow: inset 0 0 0 1px rgba(207, 233, 255, .2), inset 0 1px 0 rgba(255, 255, 255, .1);
  transition: box-shadow 260ms ease, background 260ms ease, color 260ms ease, opacity 260ms ease;
}
.gc-node-icon svg { width: 12px; height: 12px; stroke-width: 1.5; }

/* trigger — chamfered, the origin of the flow, brightest chip */
.gc-node[data-kind="trigger"] .gc-node-icon { --gc-chip-a: .24; border-radius: 3px 9px 3px 9px; }

/* action — plain rounded square, the neutral baseline everything else reads against */
.gc-node[data-kind="action"] .gc-node-icon { --gc-chip-a: .16; border-radius: 7px; }

/* integration — one chip split down the middle: two systems joined */
.gc-node[data-kind="integration"] .gc-node-icon {
  --gc-chip-a: .16;
  border-radius: 7px;
  background:
    linear-gradient(90deg, transparent 49.4%, rgba(207, 233, 255, .3) 49.4% 50.6%, transparent 50.6%),
    radial-gradient(120% 120% at 30% 20%, rgba(141, 214, 255, var(--gc-chip-a)), transparent 70%),
    rgba(141, 214, 255, .045);
}

/* ai_agent — squircle with a halo: the only chip that has something around it */
.gc-node[data-kind="ai_agent"] .gc-node-icon {
  --gc-chip-a: .22;
  border-radius: 9px;
  box-shadow:
    inset 0 0 0 1px rgba(207, 233, 255, .24),
    inset 0 1px 0 rgba(255, 255, 255, .1),
    0 0 0 3px rgba(77, 163, 255, .09);
}

/* decision — a diamond; the icon counter-rotates so it stays upright */
.gc-node[data-kind="decision"] .gc-node-icon {
  --gc-chip-a: .18;
  width: 17px;
  height: 17px;
  margin-inline: 1.5px;
  border-radius: 4px;
  rotate: 45deg;
}
.gc-node[data-kind="decision"] .gc-node-icon svg { width: 11px; height: 11px; rotate: -45deg; }

/* human — the only circle on the canvas. A person in the loop must be findable
   at a glance from across the room. */
.gc-node[data-kind="human"] .gc-node-icon {
  --gc-chip-a: .2;
  border-radius: 50%;
  box-shadow:
    inset 0 0 0 1px rgba(207, 233, 255, .28),
    inset 0 1px 0 rgba(255, 255, 255, .1),
    0 0 0 2px rgba(207, 233, 255, .1);
}

/* data — a barrel, echoing the cylinder in its own glyph */
.gc-node[data-kind="data"] .gc-node-icon { --gc-chip-a: .13; border-radius: 40% / 26%; }

/* ── State ────────────────────────────────────────────────────────────────
   Every state is legible in greyscale:
     confirmed   solid border · front plane · emits · filled chip · "●" badge
     inferred    dashed border · mid plane · hatched body · half badge · dimmer
     placeholder dotted border · back plane · recessed + desaturated · "?" · ring
     pending     solid border · sonar ring pulsing · spinner badge · amber
   Colour is the fourth signal, never the first.                            */

/* confirmed — built. It is the only state that makes light. */
.gc-node[data-state="confirmed"] {
  border-style: solid;
  border-color: rgba(207, 233, 255, .26);
  box-shadow:
    var(--gc-z-front),
    inset 0 1px 0 rgba(255, 255, 255, .09),
    var(--gc-em-1);
}
.gc-node[data-state="confirmed"] .gc-node-icon {
  color: #eaf6ff;
  box-shadow:
    inset 0 0 0 1px rgba(207, 233, 255, .42),
    inset 0 0 10px -2px rgba(141, 214, 255, .6),
    0 0 12px -4px rgba(77, 163, 255, .7);
}

/* inferred — drawn, not built. Provisional hatch across the glass, dashed rim,
   no emission of its own: it only catches light from the room. */
.gc-node[data-state="inferred"] {
  border-style: dashed;
  border-color: rgba(207, 233, 255, .22);
  background:
    repeating-linear-gradient(135deg, rgba(255, 255, 255, .022) 0 3px, transparent 3px 7px),
    linear-gradient(157deg, rgba(255, 255, 255, .035), rgba(255, 255, 255, 0) 42%),
    linear-gradient(180deg, rgba(24, 28, 35, .72), rgba(11, 13, 17, .84));
  opacity: .92;
  box-shadow: var(--gc-z-mid), inset 0 1px 0 rgba(255, 255, 255, .045);
}
.gc-node[data-state="inferred"] > strong[data-label] { color: #d3d0da; }
.gc-node[data-state="inferred"] .gc-node-icon { opacity: .8; }

/* placeholder — a hole in the design. Sits physically further back in the
   volume: smaller, desaturated, pooled in its own shadow, unlit. */
.gc-node[data-state="placeholder"] {
  --gc-scale: .965;
  border-style: dotted;
  border-color: rgba(207, 233, 255, .2);
  background: linear-gradient(180deg, rgba(18, 20, 25, .62), rgba(9, 10, 13, .74));
  opacity: .8;
  filter: blur(0) saturate(.55);
  box-shadow: var(--gc-z-back), inset 0 0 24px -8px rgba(0, 0, 0, .85);
}
.gc-node[data-state="placeholder"] .gc-node-icon {
  background: none;
  color: var(--ac-text-3);
  opacity: .7;
  box-shadow: inset 0 0 0 1px rgba(207, 233, 255, .14);
}
.gc-node[data-state="placeholder"] > strong[data-label],
.gc-node[data-state="placeholder"] > small[data-sub] { padding-inline-end: 30px; }
.gc-node[data-state="placeholder"] .gc-port { opacity: .4; box-shadow: none; filter: saturate(.3); }

/* The "?" is a watermark inside the glass, not a chip on top of it. It is the
   single loudest object on an unfinished canvas, and it disappears the instant
   the question is answered. */
.gc-unknown {
  position: absolute;
  z-index: 0;
  top: 50%;
  inset-inline-end: 8px;
  translate: 0 -46%;
  color: transparent;
  font-family: var(--f-head);
  font-size: 2.3rem;
  font-weight: 700;
  font-style: normal;
  line-height: 1;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(207, 233, 255, .26), rgba(207, 233, 255, .05));
  -webkit-background-clip: text;
  background-clip: text;
  animation: gcSeek 3.8s ease-in-out infinite;
}
@keyframes gcSeek { 0%, 100% { opacity: .55; } 50% { opacity: .95; } }

/* pending — the graph is waiting on the human. A sonar ring leaves the node
   every 2.2s. This is the only element on the canvas allowed to ping. */
.gc-node[data-state="pending"] {
  border-style: solid;
  border-color: rgba(242, 178, 60, .42);
  box-shadow:
    var(--gc-z-mid),
    inset 0 1px 0 rgba(255, 255, 255, .07),
    0 0 24px -10px rgba(242, 178, 60, .5);
}
.gc-node[data-state="pending"] .gc-node-icon {
  color: #ffdca3;
  box-shadow: inset 0 0 0 1px rgba(242, 178, 60, .5), inset 0 0 9px -2px rgba(242, 178, 60, .5);
}
.gc-node[data-state="pending"]::before {
  content: "";
  position: absolute;
  z-index: 0;
  inset: -3px;
  border: 1px solid rgba(242, 178, 60, .55);
  border-radius: 16px;
  pointer-events: none;
  animation: gcAwaitRing 2.2s var(--gc-ease) infinite;
}
.gc-node[data-state="pending"] .gc-port.is-out {
  background: radial-gradient(circle at 50% 50%, #fff3dc 0 34%, var(--gc-await) 38% 72%, rgba(242, 178, 60, .25) 100%);
  box-shadow: 0 0 0 2px rgba(6, 8, 12, .9), 0 0 10px rgba(242, 178, 60, .7);
  animation: gcPortPulse 2.2s var(--gc-ease) infinite;
}
@keyframes gcAwaitRing {
  0%   { opacity: 0;   scale: .985; }
  35%  { opacity: .85; scale: 1; }
  100% { opacity: 0;   scale: 1.05; }
}
@keyframes gcPortPulse {
  0%, 100% { box-shadow: 0 0 0 2px rgba(6, 8, 12, .9), 0 0 8px rgba(242, 178, 60, .5); }
  40%      { box-shadow: 0 0 0 2px rgba(6, 8, 12, .9), 0 0 16px rgba(242, 178, 60, .95); }
}

/* ── State badge ───────────────────────────────────────────────────────────
   Rides the top rim, half outside the box. Carries the word AND a CSS-drawn
   glyph, so the state survives greyscale, colour-blindness, and a 60% zoom-out
   where the word itself is unreadable.                                      */

.gc-state-tag {
  position: absolute;
  z-index: 4;
  top: -8px;
  inset-inline-end: 10px;
  display: block;
  max-width: 116px;
  height: 16px;
  padding-inline: 19px 8px;
  overflow: hidden;
  border: 1px solid var(--gc-rim);
  border-radius: 999px;
  background: linear-gradient(180deg, rgba(20, 24, 30, .97), rgba(10, 12, 16, .97));
  color: var(--ac-text-2);
  font-family: var(--f-data);
  font-size: .5rem;
  font-weight: 600;
  letter-spacing: .1em;
  line-height: 15px;
  text-overflow: ellipsis;
  text-transform: uppercase;
  white-space: nowrap;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .5);
}
html[dir="rtl"] .gc-state-tag {
  height: 17px;
  font-family: var(--f-body);
  font-size: .58rem;
  font-weight: 700;
  letter-spacing: 0;
  line-height: 16px;
}

.gc-state-tag::before {
  content: "";
  position: absolute;
  top: 50%;
  inset-inline-start: 6px;
  width: 7px;
  height: 7px;
  border: 1px solid currentColor;
  border-radius: 50%;
  translate: 0 -50%;
}

/* ● filled — known */
.gc-node[data-state="confirmed"] .gc-state-tag { border-color: rgba(141, 214, 255, .38); color: #cfe9ff; }
.gc-node[data-state="confirmed"] .gc-state-tag::before { background: currentColor; box-shadow: 0 0 6px currentColor; }

/* ◐ half — guessed */
.gc-node[data-state="inferred"] .gc-state-tag { border-color: rgba(207, 233, 255, .22); color: var(--ac-text-2); }
.gc-node[data-state="inferred"] .gc-state-tag::before {
  border-style: dashed;
  background: linear-gradient(90deg, currentColor 0 50%, transparent 50%);
}

/* ○ hollow dotted — unknown */
.gc-node[data-state="placeholder"] .gc-state-tag { border-color: rgba(207, 233, 255, .18); color: var(--ac-text-3); }
.gc-node[data-state="placeholder"] .gc-state-tag::before { border-style: dotted; background: none; }

/* ◜ spinning arc — waiting on you */
.gc-node[data-state="pending"] .gc-state-tag { border-color: rgba(242, 178, 60, .45); color: #ffd68f; }
.gc-node[data-state="pending"] .gc-state-tag::before {
  border-color: currentColor;
  border-top-color: transparent;
  animation: gcSpin 1.1s linear infinite;
}
@keyframes gcSpin { to { rotate: 360deg; } }

/* ── Ports — light arrives, light leaves ───────────────────────────────────
   In-port is a well (dark core, bright ring). Out-port is an emitter (bright
   core, bloom). Direction of flow is readable from a single node.           */

.gc-port {
  position: absolute;
  z-index: 5;
  top: 50%;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  translate: 0 -50%;
  transition: background 260ms ease, box-shadow 260ms ease, opacity 260ms ease;
}
.gc-port.is-in {
  left: -5px;                     /* physical: the graph is always LTR */
  background: radial-gradient(circle at 50% 50%, rgba(4, 6, 10, .95) 0 38%, rgba(141, 214, 255, .75) 42% 100%);
  box-shadow: 0 0 0 2px rgba(6, 8, 12, .9), inset 0 0 3px rgba(0, 0, 0, .8);
}
.gc-port.is-out {
  right: -5px;
  background: radial-gradient(circle at 50% 50%, #eaf6ff 0 34%, var(--gc-beam) 38% 72%, rgba(77, 163, 255, .25) 100%);
  box-shadow: 0 0 0 2px rgba(6, 8, 12, .9), 0 0 10px rgba(77, 163, 255, .65);
}

/* ── Enter / exit — condensing out of the fog, dissolving back into it ───── */

.gc-node.is-entering {
  --gc-scale: .9;
  translate: 0 12px;
  opacity: 0;
  filter: blur(7px) saturate(.7);
}
.gc-node.is-exiting {
  --gc-scale: .94;
  opacity: 0;
  filter: blur(9px) saturate(.6);
  pointer-events: none;
  transition-duration: var(--gc-exit);
}

/* ── Hover / selection — depth of field ─────────────────────────────────── */

.gc-node:hover {
  --gc-scale: 1.02;
  border-color: var(--gc-rim-hot);
  box-shadow: var(--gc-z-front), inset 0 1px 0 rgba(255, 255, 255, .1), var(--gc-em-2);
}
.gc-node:hover::after,
.gc-node.is-selected::after { opacity: 1; }

.gc-node.is-selected {
  --gc-scale: 1.035;
  z-index: 6;
  border-color: rgba(141, 214, 255, .55);
  box-shadow:
    var(--gc-z-front),
    inset 0 0 0 1px rgba(141, 214, 255, .22),
    0 0 0 3px rgba(77, 163, 255, .1),
    var(--gc-em-3);
}

/* Everything else pulls back a plane. Real defocus, not just dimming — this is
   what makes the canvas read as a volume rather than a flat sheet. */
@supports selector(:has(*)) {
  .gc-nodes:has(.is-selected) .gc-node:not(.is-selected) { opacity: .62; filter: blur(.5px) saturate(.8); }
  .gc-stage:has(.is-selected) .gc-edge { opacity: .5; }
}

/* ── just-resolved — the morph ─────────────────────────────────────────────
   A placeholder does not get replaced; it SURFACES. It rises from the back
   plane toward the viewer, comes into focus, ignites at the icon, a sheen
   passes over the new glass, a shockwave leaves the rim, and the answer writes
   itself in. 620ms, inside the 900ms window graph-canvas.js holds the class.
   Nothing here touches `transform` — JS owns it.                           */

.gc-node.just-resolved { animation: gcResolveLift 620ms var(--gc-ease-out) both; }
@keyframes gcResolveLift {
  0%   { scale: .965; filter: blur(1.4px) saturate(.6); box-shadow: var(--gc-z-back); }
  22%  { scale: 1.045; filter: blur(0) saturate(1.25); box-shadow: var(--gc-z-front), 0 0 60px -12px rgba(141, 214, 255, .85); }
  55%  { scale: 1.012; }
  100% { scale: 1; filter: blur(0) saturate(1); box-shadow: var(--gc-z-front), inset 0 1px 0 rgba(255, 255, 255, .09), var(--gc-em-1); }
}

.gc-node.just-resolved::before {
  content: "";
  position: absolute;
  z-index: 0;
  inset: -2px;
  border: 1px solid rgba(207, 233, 255, .8);
  border-radius: 15px;
  pointer-events: none;
  animation: gcShock 620ms var(--gc-ease-out) both;
}
@keyframes gcShock {
  0%   { opacity: .9; scale: .96; }
  100% { opacity: 0;  scale: 1.16; }
}

.gc-node.just-resolved::after { animation: gcSheen 620ms var(--gc-ease-out) 60ms both; }
@keyframes gcSheen {
  0%   { opacity: 0; background-position: 130% 0; }
  22%  { opacity: 1; }
  100% { opacity: 0; background-position: -30% 0; }
}

.gc-node.just-resolved .gc-node-icon { animation: gcIgnite 560ms var(--gc-ease-out) both; }
@keyframes gcIgnite {
  0%   { scale: .7;   box-shadow: inset 0 0 0 1px rgba(207, 233, 255, .14); }
  30%  { scale: 1.18; box-shadow: inset 0 0 0 1px rgba(234, 246, 255, .9), inset 0 0 14px rgba(141, 214, 255, .9), 0 0 26px rgba(77, 163, 255, .9); }
  100% { scale: 1;    box-shadow: inset 0 0 0 1px rgba(207, 233, 255, .42), inset 0 0 10px -2px rgba(141, 214, 255, .6), 0 0 12px -4px rgba(77, 163, 255, .7); }
}

.gc-node.just-resolved > strong[data-label] { animation: gcTextIn 400ms var(--gc-ease-out) 110ms both; }
.gc-node.just-resolved > small[data-sub]    { animation: gcTextIn 400ms var(--gc-ease-out) 210ms both; }
.gc-node.just-resolved .gc-state-tag        { animation: gcTagIn 360ms var(--gc-ease-out) 260ms both; }
@keyframes gcTextIn {
  from { opacity: 0; translate: 0 5px; filter: blur(3px); }
  to   { opacity: 1; translate: 0 0;   filter: blur(0); }
}
@keyframes gcTagIn {
  from { opacity: 0; scale: .8; }
  to   { opacity: 1; scale: 1; }
}

/* ── Centred → split ───────────────────────────────────────────────────────
   The canvas is not a panel that slides in. Space opens, the chamber lights
   up empty for a beat, and only then does the architecture condense into it.
   Shell contract: .ac[data-mode="centered"|"split"] on .ac-workspace, canvas
   first, chat second. RTL mirrors for free — grid follows `direction`, the graph
   inside does not.

   The grid itself is NOT stated here any more. This file used to write
   .ac-workspace's grid-template-columns, agent-console.css wrote a different
   pair of values, and because this file loads second it silently won — a
   440px chat track at every width above 900px regardless of what the shell
   had. agent-stage.css owns the split now and switches it on the shell's own
   inline size. What remains below is the transition's *look*, not its
   geometry. */

.ac-canvas-panel {
  position: relative;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  opacity: 1;
}
.ac[data-mode="split"] .ac-workspace .ac-canvas-panel { opacity: 1; }

/* Focus pull: the chamber arrives slightly over-scaled and settles. */
.ac[data-mode="centered"] .ac-workspace .gc-stage { opacity: 1; scale: 1; }
.ac[data-mode="split"] .ac-workspace .gc-stage {
  opacity: 1;
  scale: 1;
  transition: opacity 420ms ease 120ms, scale 620ms var(--gc-ease) 100ms;
}

/* The seam between the two halves draws itself as a filament of light. */
.ac-chat-panel { position: relative; min-width: 0; }
.ac-chat-panel::before {
  content: "";
  position: absolute;
  z-index: 3;
  inset-block: 0;
  inset-inline-end: -1px;
  width: 1px;
  opacity: 0;
  pointer-events: none;
  background: linear-gradient(180deg, transparent, rgba(141, 214, 255, .42) 18%, rgba(141, 214, 255, .55) 50%, rgba(141, 214, 255, .42) 82%, transparent);
  scale: 1 0;
}
.ac[data-mode="split"] .ac-workspace .ac-chat-panel::before {
  opacity: 1;
  scale: 1 1;
  transition: scale 520ms var(--gc-ease) 140ms, opacity 300ms ease 140ms;
}

/* ── Narrow viewports ─────────────────────────────────────────────────────
   Frost is the first thing to go: backdrop-filter on a dozen nodes is the one
   effect here that can cost frames on a mid-range phone. The design survives
   it — depth is still carried by shadow, scale, saturation and blur-on-enter. */

/* Asked of the shell, not the screen: frost costs frames because of how many
   nodes are on it, and how many nodes fit is a function of the chamber's
   width. A phone in landscape and a narrow console on a desktop have the same
   problem and now get the same answer. The grid rules that used to sit in this
   block have moved to agent-stage.css with the rest of the layout. */
@container agent-shell (max-width: 759.98px) {
  .ac-chat-panel::before,
  .ac[data-mode="split"] .ac-workspace .ac-chat-panel::before { display: none; }
  .gc-node {
    backdrop-filter: none;
    background: linear-gradient(157deg, rgba(255, 255, 255, .05), rgba(255, 255, 255, 0) 42%),
                linear-gradient(180deg, #14171d, #0b0d11);
  }
  .gc-viewport::after { animation: none; }
}

/* ── Reduced motion ───────────────────────────────────────────────────────
   graph-canvas.js already skips enter, exit, draw-on and the resolve class
   when the query matches, so the job here is to stop every ambient loop and
   make sure each state is still fully readable standing still.             */

@media (prefers-reduced-motion: reduce) {
  .gc-viewport::after,
  .gc-unknown,
  .gc-node[data-state="pending"]::before,
  .gc-node[data-state="pending"] .gc-port.is-out,
  .gc-state-tag::before { animation: none; }

  .gc-edge-line { animation: none; }

  /* Pending stops pinging but keeps a static ring, so "waiting on you" is
     still the loudest thing on the canvas. */
  .gc-node[data-state="pending"]::before { opacity: .9; scale: 1; }
  .gc-unknown { opacity: .85; }

  .gc-node,
  .gc-node.is-entering,
  .gc-node.is-exiting,
  .gc-node[data-state="placeholder"] {
    scale: 1;
    translate: 0;
    filter: none;
    transition: opacity 160ms linear, border-color 160ms linear, background 160ms linear, box-shadow 160ms linear;
  }
  .gc-node.is-entering { opacity: 1; }
  .gc-node:hover,
  .gc-node.is-selected { scale: 1; }

  /* Placeholder loses the recess but keeps dotted rim, "?", badge and dimming. */
  .gc-node[data-state="placeholder"] { opacity: .85; }

  .gc-node.just-resolved,
  .gc-node.just-resolved::before,
  .gc-node.just-resolved::after,
  .gc-node.just-resolved .gc-node-icon,
  .gc-node.just-resolved > strong[data-label],
  .gc-node.just-resolved > small[data-sub],
  .gc-node.just-resolved .gc-state-tag { animation: none; }
  /* A resolution still has to be *noticed*: a static ring, no movement. */
  .gc-node.just-resolved { outline: 1px solid rgba(207, 233, 255, .7); outline-offset: 3px; }
  .gc-node.just-resolved::before { display: none; }

  @supports selector(:has(*)) {
    .gc-nodes:has(.is-selected) .gc-node:not(.is-selected) { filter: none; opacity: .68; }
  }

  .ac-workspace,
  .ac-workspace .gc-stage,
  .ac-canvas-panel,
  .ac-chat-panel::before { transition: none; }
  .ac[data-mode="centered"] .ac-workspace .gc-stage { opacity: 1; scale: 1; }
}


/* ══ SHELL ═════════════════════════════════════════════════════════════════
   The chamber above is the product; everything here is the frame around it and
   is deliberately quieter than the graph. The one idea carried over from the
   canvas is that motion is a debt indicator: the agent's presence breathes
   while something is outstanding and goes still when the architecture is
   agreed. Nothing in this section is allowed to glow.                        */

/* ── Centred chat ──────────────────────────────────────────────────────────
   Before the first mutation the canvas column is 0fr, so the chat would run
   the full width of the shell and read as a page rather than a conversation.

   How it used to do that was the single most expensive line in this project:

       .ac-chat-panel { max-width: 1400px; margin-inline: auto; }

   An auto inline margin on a grid item takes it out of `stretch` and sizes it
   fit-content, so the panel measured its own max-content (665px), ignored the
   440px track it was sitting in, and painted 225px of itself across the
   diagram at every width from 901px to 1440px. Worse, "max-content" includes
   whatever the conversation contains, so one pasted URL took the panel to
   1399px and the stream's scrollWidth to 19663px.

   The replacement — a definite `inline-size`, which centres identically, still
   interpolates, and cannot be talked upwards by its own contents — lives in
   agent-stage.css with the rest of the page's geometry. Nothing about the
   chat panel's box is stated in this file any more. */

/* ── Agent presence ────────────────────────────────────────────────────────
   Large and centred while the chat is alone, small once the room opens: the
   avatar yields its prominence to the architecture the moment there is one. */
.ac-chat-head { display: flex; align-items: center; gap: 11px; }
.ac-chat-title { display: grid; gap: 2px; min-width: 0; flex: 1; }

.ac-avatar {
  position: relative;
  display: grid;
  place-items: center;
  flex: none;
  width: 34px;
  height: 34px;
  transition: width var(--gc-move) var(--gc-ease), height var(--gc-move) var(--gc-ease);
}
.ac[data-mode="centered"] .ac-workspace .ac-avatar { width: 46px; height: 46px; }

.ac-avatar-core {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  border: 1px solid var(--gc-rim);
  border-radius: 12px;
  background: linear-gradient(150deg, var(--gc-glass-hi), var(--gc-glass-lo));
  box-shadow: var(--gc-z-mid);
}
.ac-avatar-core img { width: 52%; height: 52%; object-fit: contain; }

/* The ring is a sonar ping, not a throb of the mark itself, so the logo stays
   sharp while it pulses — the same reasoning as the pending node. */
.ac-avatar-ring {
  position: absolute;
  inset: -3px;
  border: 1px solid var(--gc-beam);
  border-radius: 15px;
  opacity: 0;
  pointer-events: none;
}
.ac.is-busy .ac-avatar-ring { animation: acPing 1.8s var(--gc-ease) infinite; }
/* Amber, and only here: the graph is holding a question for the human. */
.ac[data-status="awaiting"] .ac-avatar-ring {
  border-color: var(--gc-await);
  animation: acPing 2.2s var(--gc-ease) infinite;
}
/* Agreed architecture, still room. */
.ac[data-status="ready"] .ac-avatar-ring { animation: none; opacity: 0; }
.ac[data-status="ready"] .ac-avatar-core { border-color: rgba(74, 222, 128, .3); }

@keyframes acPing {
  0%   { opacity: 0;   scale: .94; }
  35%  { opacity: .55; scale: 1;   }
  100% { opacity: 0;   scale: 1.14; }
}

/* ── Status pill ───────────────────────────────────────────────────────────
   Four states, three hues, and the amber rule holds: warm means the graph is
   waiting on a person. */
.ac-status {
  flex: none;
  padding: 4px 9px;
  border: 1px solid var(--gc-rim);
  border-radius: 999px;
  color: var(--ac-text-2);
  font-family: var(--f-data, inherit);
  font-size: .44rem;
  letter-spacing: .06em;
  white-space: nowrap;
  transition: color 420ms ease, border-color 420ms ease, background-color 420ms ease;
}
.ac-status.is-drafting { color: var(--gc-ice); border-color: rgba(207, 233, 255, .3); background: rgba(77, 163, 255, .05); }
.ac-status.is-awaiting { color: #ffd68f; border-color: rgba(242, 178, 60, .42); background: rgba(242, 178, 60, .06); }
.ac-status.is-ready    { color: var(--gc-ready); border-color: rgba(74, 222, 128, .34); background: rgba(74, 222, 128, .06); }
.ac-status.is-review   { color: #ffd68f; border-color: rgba(242, 178, 60, .3); }

/* ── Brief strip ───────────────────────────────────────────────────────────
   Evidence, not a dashboard. An empty field is a hairline ghost; a filled one
   earns a single lit dot. It must never out-shout the graph beneath it.      */
.ac-brief-strip {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 14px;
  border-bottom: 1px solid var(--ac-line);
  background: rgba(255, 255, 255, .012);
  overflow-x: auto;
  scrollbar-width: none;
}
.ac-brief-strip::-webkit-scrollbar { display: none; }

.ac-brief-label {
  flex: none;
  color: var(--ac-text-3);
  font-size: .43rem;
  letter-spacing: .04em;
}
.ac-brief-label b { color: var(--ac-text-2); font-family: var(--f-data, inherit); font-weight: 500; }

.ac-brief-fields { display: flex; gap: 5px; min-width: 0; }

.ac-brief-field {
  display: flex;
  align-items: center;
  gap: 5px;
  flex: none;
  max-width: 132px;
  padding: 4px 8px;
  border: 1px solid var(--ac-line);
  border-radius: 7px;
  background: rgba(255, 255, 255, .014);
  opacity: .5;
  transition: opacity 420ms ease, border-color 420ms ease, background-color 420ms ease;
}
.ac-brief-field > i {
  flex: none;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--ac-text-3);
  transition: background-color 420ms ease, box-shadow 420ms ease;
}
.ac-brief-field small { flex: none; color: var(--ac-text-3); font-size: .4rem; }
.ac-brief-field b {
  overflow: hidden;
  color: var(--ac-text-2);
  font-size: .44rem;
  font-weight: 500;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ac-brief-field.is-filled {
  border-color: rgba(77, 163, 255, .22);
  background: rgba(77, 163, 255, .05);
  opacity: 1;
}
.ac-brief-field.is-filled > i { background: var(--gc-beam); box-shadow: 0 0 6px var(--gc-beam); }
.ac-brief-field.is-filled small { color: var(--ac-text-2); }
.ac-brief-field.is-filled b { color: var(--gc-ice); }

/* One flash, on the turn a field is first learned. Fields already lit must not
   re-flash on every reply or the strip reads as noise instead of progress. */
.ac-brief-field.just-lit,
.ac-brief-field.just-changed { animation: acFieldLit 900ms var(--gc-ease-out) both; }

@keyframes acFieldLit {
  0%   { border-color: var(--gc-rim-hot); background: rgba(77, 163, 255, .16); }
  100% { border-color: rgba(77, 163, 255, .22); background: rgba(77, 163, 255, .05); }
}

/* The graph's prose form. Not display:none — that would remove it from the
   accessibility tree along with the live region, which is the one part of this
   component that has to keep speaking. */
.gc-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  border: 0;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── Panning ───────────────────────────────────────────────────────────────
   The sizer carries the graph's SCALED dimensions, because `transform: scale`
   leaves the layout box at full size and the viewport would otherwise scroll
   far past the diagram. While the graph fits it stays centred; once it outgrows
   the panel the chamber becomes pannable rather than shrinking the nodes below
   readability. */
.gc-sizer { position: relative; flex: none; }
.gc-stage { position: absolute; inset-block-start: 0; inset-inline-start: 0; }

.gc-viewport.is-pannable {
  /* `safe` centring, not `start`: the graph stays centred on whichever axis
     still fits and only falls back to start-aligned on the axis that actually
     overflows. Plain `center` would push the overflowing edge out of reach of
     the scrollbar; plain `start` top-aligns a short graph in a tall chamber and
     leaves a dead half-panel underneath it. */
  place-content: safe center;
  overflow: auto;
  scrollbar-width: thin;
  scrollbar-color: rgba(207, 233, 255, .18) transparent;
  scroll-behavior: smooth;
  /* The mask fades content at the perimeter, which reads as "there is more this
     way" exactly where a scrollable edge needs that hint. */
  cursor: grab;
}
.gc-viewport.is-pannable::-webkit-scrollbar { width: 8px; height: 8px; }
.gc-viewport.is-pannable::-webkit-scrollbar-thumb {
  border: 2px solid transparent;
  border-radius: 99px;
  background: rgba(207, 233, 255, .18);
  background-clip: content-box;
}

@media (prefers-reduced-motion: reduce) {
  .gc-viewport.is-pannable { scroll-behavior: auto; }
}

/* ── Canvas host ───────────────────────────────────────────────────────────
   The panel must be a grid, not a block. The mount inside the host is
   absolutely positioned, so the host contributes no intrinsic height — without
   an explicit row track the chamber collapses to zero and the graph renders
   into nothing while every other measurement still looks correct. */
/* The 620px floor that used to be stated here was the third copy of the
   workspace's height — agent-console.css said 660px, this file said 620px, and
   whichever loaded last decided. It also fought the fixed live region: a floor
   taller than the space available is how a long conversation grew the document
   instead of scrolling. agent-stage.css sizes the region; the chamber takes
   1fr of it and can no longer collapse, because the region has a height of its
   own rather than borrowing the chat's. */

/* The chamber takes the space the head and the strip leave, and it is named
   rather than counted.

   This was `grid-template-rows: auto auto minmax(0, 1fr)` — three rows, with the
   chamber relying on being the third child. A fourth element was later added to
   this column and took the 1fr row, which pushed the chamber into an implicit
   `auto` row. Because .ac-canvas-mount is absolutely positioned, that row had no
   in-flow content to size against and collapsed to zero height: the graph was
   built, mutations were applied, the accessible summary was written — and it was
   all drawn into a box with no height. Nothing errored.

   Naming the areas makes the layout independent of child order and child count.
   An element added here without a place now sits outside the grid instead of
   silently taking the chamber's. */
.ac-canvas-panel {
  display: grid;
  grid-template-rows: [head] auto [strip] auto [chamber] minmax(0, 1fr);
  min-height: 0;
  min-width: 0;
}
.ac-canvas-panel > .ac-panel-head { grid-row: head; }
.ac-canvas-panel > .ac-brief-strip { grid-row: strip; }
.ac-canvas-panel > .ac-canvas-host { grid-row: chamber; }

/* `height: 100%` and not only `min-height: 0`: both children are absolutely
   positioned, so this element has nothing in flow to give it a height of its
   own. In a grid row it is stretched anyway; the explicit height is what keeps
   it correct if this column ever becomes a flex container again. */
.ac-canvas-host { position: relative; height: 100%; min-height: 0; min-width: 0; }
.ac-canvas-mount { position: absolute; inset: 0; }

.ac-canvas-empty {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 10px;
  padding: 24px;
  text-align: center;
  transition: opacity 380ms ease;
}
.ac-canvas-empty svg {
  width: 30px;
  height: 30px;
  fill: none;
  stroke: var(--ac-text-3);
  stroke-width: 1.4;
  opacity: .5;
}
.ac-canvas-empty p { max-width: 30ch; color: var(--ac-text-3); font-size: .5rem; line-height: 1.7; }
.ac-canvas-empty.is-gone { opacity: 0; pointer-events: none; }

/* ── Greeting ──────────────────────────────────────────────────────────────*/
.ac-greeting {
  display: grid;
  place-content: center;
  min-height: 100%;
  padding: 26px 20px;
  text-align: center;
}
.ac-greeting p {
  max-width: 34ch;
  margin-inline: auto;
  color: var(--ac-text-2);
  font-size: .62rem;
  line-height: 1.85;
}

/* A caret while the reply is still arriving from the stream. */
.ac-message.is-streaming p::after {
  content: "";
  display: inline-block;
  width: 2px;
  height: .72em;
  margin-inline-start: 3px;
  border-radius: 1px;
  background: var(--gc-beam);
  vertical-align: -.08em;
  animation: acCaret 1s steps(2, start) infinite;
}
@keyframes acCaret { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }

@container agent-shell (max-width: 759.98px) {
  .ac-canvas-host { min-height: 340px; }
}

/* ── Legibility floor ──────────────────────────────────────────────────────
   The shell text this file owns, brought up to the same 12px / 4.5:1 floor as
   agent-console.css. The brief strip was the worst offender on the page: its
   field labels ran at .4rem (6.4px) in --ac-text-3, which was 3.9:1 before
   that token was corrected. These are chips the visitor is meant to read to
   check what the agent understood — if they are unreadable the strip is
   decoration claiming to be evidence.

   NOTE: the graph nodes' own text (.gc-node-head em at 7.4px, [data-sub] at
   7.7px, .gc-state-tag at 8px) is deliberately NOT raised here. Those live
   inside a 172x78 box whose vertical budget is spelled out at .gc-node and
   comes from NODE_W/NODE_H in graph-layout.js; 12px overruns it by ~6px and
   the fix is a taller node, not a bigger font in the same hole. See the
   handover note — it needs a conversation with a live provider to verify, and
   this branch has none. */
.ac-status { font-size: .75rem; padding: 4px 11px; }
.ac-brief-label { font-size: .75rem; }
.ac-brief-field { max-width: 118px; padding: 5px 9px; }
.ac-brief-field small,
.ac-brief-field b { font-size: .75rem; }
/* Eight fields at a readable size will not fit any panel this page has. The
   strip has always been a horizontal scroller for exactly this reason; the
   only change is that it now scrolls sooner, which is the honest trade for
   labels that can actually be read. */
.ac-brief-field b { max-width: 11ch; }
.ac-canvas-empty p { font-size: .8rem; }
.ac-greeting p { font-size: .85rem; }

@media (prefers-reduced-motion: reduce) {
  .ac-avatar-ring,
  .ac-brief-field.just-lit,
  .ac-brief-field.just-changed,
  .ac-message.is-streaming p::after { animation: none; }
  .ac-avatar-ring { opacity: 0; }
  .ac-chat-panel,
  .ac-avatar,
  .ac-brief-field,
  .ac-canvas-empty { transition: none; }
}

/* ── Layers ────────────────────────────────────────────────────────────────
   The canvas carries two accounts of one business: how it runs today, and how
   it would run with the agent in it. Drawing both at once was the original
   defect — a manual step and the agent meant to replace it read as two live
   branches of the same flow, and the picture described neither the operation
   nor the proposal.

   No new hue. The palette has exactly two working colours and neither of them
   means "proposed"; the layers are told apart by which one you are looking at,
   not by tinting them against each other. The comparison is the toggle.      */

.gc-layers {
  display: flex;
  gap: 2px;
  align-self: flex-start;
  margin: 0 0 10px;
  padding: 3px;
  border: 1px solid var(--gc-rim);
  border-radius: 999px;
  background: var(--gc-glass-lo);
  backdrop-filter: blur(var(--gc-glass-blur));
}

.gc-layers[hidden] { display: none; }

.gc-layer-btn {
  padding: 5px 14px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: rgba(233, 231, 236, .6);
  font: inherit;
  font-size: .76rem;
  cursor: pointer;
  transition: background 180ms ease, color 180ms ease;
}

.gc-layer-btn:hover { color: rgba(233, 231, 236, .85); }

.gc-layer-btn[aria-pressed="true"] {
  background: rgba(77, 163, 255, .14);
  color: var(--gc-ice);
  box-shadow: inset 0 0 0 1px rgba(77, 163, 255, .3);
}

.gc-layer-btn:focus-visible {
  outline: 2px solid var(--gc-beam);
  outline-offset: 2px;
}

/* Which layer is on screen. Nodes are absolutely positioned, so hiding one
   layer costs the other nothing: the surviving nodes keep the coordinates the
   layout gave them and the two views stay registered against each other. That
   is what makes the toggle read as one diagram seen twice, rather than two. */

[data-layer="current"] .gc-node[data-phase="proposed"],
[data-layer="current"] .gc-edge[data-phase="proposed"] {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

/* The proposed view hides the wiring of the old path, not its nodes.
   Superseded steps stay on screen, ghosted.

   They used to be hidden outright, and that tore the diagram apart: the agent
   is wired precisely TO the steps it takes over, so removing them deleted the
   proposed edges that landed on them and left the remaining boxes floating
   unconnected. It also threw away the sentence the view exists to say — the
   agent absorbs THIS — leaving a flow that appeared from nowhere.

   Ghosted rather than dimmed-to-nothing: still legible, clearly no longer the
   live path, and still something an edge can land on. */
/* The retired wiring is ghosted, not hidden — for the same reason the nodes
   are. Hiding it stranded whatever sat at the end of it: a superseded step
   keeps only replaced edges, so removing them left it floating in the middle of
   the proposed view with nothing attached, which is the "falling apart" this
   whole section exists to prevent.

   Ghosting makes the guarantee structural rather than a thing the model has to
   remember: every node keeps every edge it ever had, so nothing can come
   loose. The proposed view ends up saying the useful thing outright — the pale
   path is what you do now, the lit one is what replaces it. */
/* ALL of it fades, not only the part that was explicitly replaced.
   Restricting this to replaced edges was an attempt to keep the untouched parts
   of their operation connected, and it did — but it also left whole stretches
   of the manual path burning at full brightness beside the agent's, so the
   proposed view showed the problem and the solution as one tangled flow. It
   depended on the model marking every superseded step, and in five of eight
   real conversations it had not marked them all.

   Nodes stay solid and only the wiring fades, so nothing comes loose and the
   only path that reads as live is the proposed one. */
[data-layer="proposed"] .gc-edge[data-phase="current"] {
  opacity: .15;
  pointer-events: none;
}

[data-layer="proposed"] .gc-node[data-superseded] {
  opacity: .32;
  filter: grayscale(1);
}

.gc-node[data-phase],
.gc-edge[data-phase] {
  transition: opacity 260ms ease, visibility 0s linear 0s;
}

/* In the current view, the steps about to be replaced are marked but not
   diminished — they are still what the business runs on this morning. A thin
   dashed rim reads as "this one changes" without implying it is broken. */
[data-layer="current"] .gc-node[data-superseded] {
  outline: 1px dashed rgba(207, 233, 255, .34);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .gc-node[data-phase],
  .gc-edge[data-phase],
  .gc-layer-btn { transition: none; }
}
