/* ==========================================================================
   SYSTEM EXPLORER  ·  the Engine page's band 3
   ==========================================================================
   Layout only. Every colour, line and type size comes from the .pd token
   block in css/demo-system.css — this file must never introduce a hue.

   THE ONE IDEA THIS LAYOUT EXISTS TO CARRY: one selection, seen twice.
   The tree picks a component; Structure shows where it sits, Behavior shows
   what it did in run 4711, and the detail pane says what the engine knows
   about it. Nothing else on this surface changes. If a third view seems
   necessary, the selection is doing too little work, not too much.

   This demo is INTERACTIVE (.pd--interactive), which is the exception the
   demo system documents: every control here is a real <button>, keyboard
   operable, with a visible focus ring. Do not add a control that is not.
   ========================================================================== */

/* The tree carries indented component names, which the 168px default cuts
   in half. This is the one geometry token the band overrides.

   The selector is the panel's own class rather than [data-iax]: that
   attribute moved up to the <section>, because the two rows that drive the
   view sit above the figure and the script's root has to contain them. */
.iax__panel { --pd-side-w: 168px; }

/* The tab strip is chrome, not a call to action. In .pd--tabbed the tabs are
   real buttons and inherit a button's weight, which on a product surface reads
   as marketing furniture sitting inside an application. Pull it back to the
   size the rest of the shell is set at. */
.iax__tab.pd-tab {
  padding: 5px 10px;
  font-size: var(--pd-size-label);
}

/* One column. The detail pane that used to sit on the right is gone — it
   restated in a list what the drawing already shows, and the stage is worth
   more than the restatement. */
.iax__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  min-height: 0;
}

.iax__stage {
  position: relative;
  display: flex;
  min-width: 0;
  padding: var(--pd-pad);
}

.iax__view { width: 100%; }
.iax__view[hidden] { display: none; }

.iax__svg {
  display: block;
  width: 100%;
  max-height: 420px;
  height: auto;
  margin: 0 auto;
}

/* — Narrow. The three panes cannot survive side by side on a phone, so the
     detail drops under the stage and the tree becomes a horizontal rail. — */
@media (max-width: 860px) {
  .iax__body { grid-template-columns: minmax(0, 1fr); }

    }

/* ==========================================================================
   THE DRAWING
   ==========================================================================
   Ported from the twin panels on /platform/engine, which draw the system
   better than the node graph that stood here first: containment is shown by
   nesting rather than asserted by a label, and the weight order between the
   marks IS the containment order — system heaviest, then domains, then units,
   then whatever lives inside a unit.

   WHAT WAS CUT ON THE WAY ACROSS, and why, so nobody restores it:
     · the seven-beat build-up and its level rail — the original reveals one
       level per beat under an engine's clock. Here the reader drives, and a
       drawing that arrives in pieces cannot answer "what does this contain".
       Every level is drawn at once.
     · the unit inspector under the static panel — the detail pane to the right
       is that, and two of them on one surface is one too many.
     · the travelling pulses — they belong to a timeline nothing here runs.
     · partitions, processes and services in the Behavior view — structure the
       reader has just seen, and carrying it across buries the three hops.

   The original styles are scoped to [data-pd-flow="twin-static"] and gated on
   data-ts-level, so they are restated here rather than reused: importing them
   would drag in a state machine this band does not have. The VALUES are the
   originals and should stay in step with them.
   ========================================================================== */

/* 01 · the system */
.iax-sys { fill: none; stroke: var(--pd-line-strong); stroke-width: 1.4; }

.iax-sys-label {
  fill: var(--pd-text-soft);
  font-family: var(--pd-mono);
  font-size: 9px;
  letter-spacing: 0.14em;
}

/* The car mark. The outer frame is the vehicle, and it took a word to say so
   until this was here. Quiet on purpose — it is a legend for the frame, not an
   illustration, so it never takes the accent colour. */
.iax-car__body {
  fill: none;
  stroke: var(--pd-text-dim);
  stroke-width: 1;
  stroke-linejoin: round;
}

.iax-car__glass { fill: var(--pd-text-dim); opacity: 0.45; }

.iax-car__wheel {
  fill: var(--pd-edge);
  stroke: var(--pd-text-dim);
  stroke-width: 1;
}

/* 02 · domains. Dashed, so a domain reads as a boundary rather than a box, and
   lighter than the system frame around it. */
.iax-dom {
  fill: rgba(78, 205, 196, 0.035);
  stroke: var(--pd-line-strong);
  stroke-width: 1;
  stroke-dasharray: 5 4;
}

.iax-dom-label {
  fill: var(--pd-text-soft);
  font-family: var(--pd-mono);
  font-size: 8px;
  letter-spacing: 0.12em;
}

/* 03 · control units — the only marks the tree selects */
.iax-unit {
  fill: var(--pd-raised);
  stroke: var(--pd-line-strong);
  stroke-width: 1;
  transition: fill var(--pd-t) var(--pd-ease), stroke var(--pd-t) var(--pd-ease);
}

.iax-unit.is-on { fill: var(--pd-data-soft); stroke: var(--pd-data); }

.iax-unit-label {
  fill: var(--pd-text-soft);
  font-family: var(--pd-font);
  font-size: 9px;
  font-weight: 500;
  transition: fill var(--pd-t) var(--pd-ease);
}

.iax-unit-label.is-on { fill: var(--pd-text); }

/* 04 · partitions */
.iax-part { fill: var(--pd-sunken); stroke: var(--pd-line); stroke-width: 0.8; }

/* 05 · processes and 06 · services. There are deliberately many: a real unit
   is not four boxes, and at this depth the reader is meant to see granularity
   rather than read individual items. Both dim when another unit is selected,
   so the selected unit's interior is the densest thing on the stage. */
.iax-proc {
  fill: var(--pd-data);
  opacity: 0.4;
  transition: opacity var(--pd-t) var(--pd-ease);
}

.iax-proc.is-on { opacity: 0.95; }

.iax-svc {
  fill: none;
  stroke: var(--pd-data);
  stroke-opacity: 0.28;
  stroke-width: 1;
  transition: stroke-opacity var(--pd-t) var(--pd-ease);
}

.iax-svc.is-on { stroke-opacity: 0.7; }

/* 07 · interfaces — the only marks that leave a unit */
.iax-iface {
  fill: none;
  stroke: var(--pd-data);
  stroke-opacity: 0.35;
  stroke-width: 1.2;
  transition: stroke-opacity var(--pd-t) var(--pd-ease);
}

.iax-iface.is-on { stroke-opacity: 1; stroke-width: 1.6; }

/* — Behavior view — */
.iax-hop {
  fill: none;
  stroke: var(--pd-data);
  stroke-width: 1.4;
  stroke-opacity: 0.35;
  transition: stroke-opacity var(--pd-t) var(--pd-ease);
}

.iax-hop.is-on { stroke-opacity: 1; stroke-width: 1.8; }

.iax-fork { fill: var(--pd-data); opacity: 0.5; }
.iax-fork.is-on { opacity: 1; }

.iax-seq-rule { stroke: var(--pd-line); stroke-width: 1; }

.iax-seq-title {
  fill: var(--pd-text-dim);
  font-family: var(--pd-mono);
  font-size: 8px;
  letter-spacing: 0.12em;
}

.iax-life {
  fill: none;
  stroke: var(--pd-line);
  stroke-width: 1;
  stroke-dasharray: 3 4;
}

.iax-life.is-on { stroke: var(--pd-data); stroke-opacity: 0.7; }

.iax-life-name {
  fill: var(--pd-text-dim);
  font-family: var(--pd-font);
  font-size: 8px;
  transition: fill var(--pd-t) var(--pd-ease);
}

.iax-life-name.is-on { fill: var(--pd-text); }

.iax-msg {
  fill: none;
  stroke: var(--pd-data);
  stroke-width: 1.2;
  stroke-opacity: 0.35;
  transition: stroke-opacity var(--pd-t) var(--pd-ease);
}

.iax-msg.is-on { stroke-opacity: 1; }

.iax-msg-label,
.iax-msg-time {
  fill: var(--pd-text-dim);
  font-family: var(--pd-font);
  font-size: 8px;
  transition: fill var(--pd-t) var(--pd-ease);
}

.iax-msg-time { font-family: var(--pd-mono); }
.iax-msg-label.is-on, .iax-msg-time.is-on { fill: var(--pd-text); }

/* — Arrowheads. A marker cannot inherit the stroke of the path that carries
     it, so the state has to pick the marker rather than recolour one. — */
.iax__defs { position: absolute; width: 0; height: 0; }

.iax__arrow { marker-end: url(#iaxArrow); }
.iax__arrow.is-on { marker-end: url(#iaxArrowOn); }
.iax__arrow.is-flagged { marker-end: url(#iaxArrowFlag); }

/* A lifeline is not a message. Undashed, the five of them turn the sequence
   into a grid and the reader stops seeing time running downwards. */
.iax__life {
  stroke-dasharray: 3 5;
  opacity: 0.65;
}

/* ==========================================================================
   THE RAILS
   ==========================================================================
   The band's only violet. The demo system reserves that hue for controls that
   really act on the surface the reader is looking at, and these do: a rung
   changes the drawing immediately, in place, every time.
   ========================================================================== */

.iax__rail {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 10px var(--pd-pad);
  border-bottom: 1px solid var(--pd-line);
  background: var(--pd-edge);
}

.iax__rail[hidden] { display: none; }

.iax__rung {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--pd-line-strong);
  border-radius: var(--pd-radius-pill, 999px);
  background: transparent;
  color: var(--pd-text-dim);
  font-family: var(--pd-font);
  font-size: var(--pd-size-meta);
  cursor: pointer;
  transition: border-color var(--pd-t-fast) var(--pd-ease),
    color var(--pd-t-fast) var(--pd-ease), background var(--pd-t-fast) var(--pd-ease);
}

.iax__rung b {
  font-family: var(--pd-mono);
  font-size: var(--pd-size-label);
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--pd-live);
}

.iax__rung:hover { border-color: var(--pd-live-line); color: var(--pd-text-soft); }

.iax__rung:focus-visible {
  outline: 2px solid var(--pd-live);
  outline-offset: 2px;
}

/* Rungs already passed stay legible: the ladder is cumulative, and greying out
   what is still on screen would say it had been put away. */
.iax__rung.is-done { color: var(--pd-text-soft); border-color: var(--pd-line-strong); }

.iax__rung.is-current {
  border-color: var(--pd-live);
  background: var(--pd-live-soft);
  color: var(--pd-text);
}

/* --------------------------------------------------------------------------
   WHAT A RUNG DOES TO THE DRAWING
   A level is absent until the ladder reaches it, present once passed, and
   picked out while it is the current rung. Nothing translates and nothing
   scales: the reader is meant to notice the inside getting finer while the
   outside holds still, and motion anywhere else competes with that.
   -------------------------------------------------------------------------- */

/* EVERY LEVEL IS DRAWN BY DEFAULT. The stepping only exists once the script
   has said so by setting data-iax-stepped, because a reader without
   JavaScript would otherwise be handed an empty frame — and an empty frame is
   a worse failure than a drawing that simply does not build. The same gate
   covers the hops below. */
.iax-lv { transition: opacity var(--pd-t) var(--pd-ease); }

[data-iax-stepped] .iax-lv { opacity: 0; }
[data-iax-stepped] .iax-lv.is-shown { opacity: 1; }

/* The current level is lifted rather than recoloured. Recolouring it would
   collide with the tree's selection, which owns teal on this stage — two
   highlights in one hue and the reader cannot tell which question is being
   answered. */
.iax-lv.is-current .iax-part { stroke: var(--pd-data); stroke-opacity: 0.8; }
.iax-lv.is-current .iax-proc { opacity: 1; }
.iax-lv.is-current .iax-svc { stroke-opacity: 0.85; }
.iax-lv.is-current .iax-iface { stroke-opacity: 1; stroke-width: 1.6; }
.iax-lv.is-current .iax-dom { stroke: var(--pd-data); stroke-opacity: 0.7; }
.iax-lv.is-current .iax-sys { stroke: var(--pd-data); }
.iax-lv.is-current .iax-unit { stroke: var(--pd-data); }

/* --------------------------------------------------------------------------
   THE BEHAVIOR HOPS
   A hop is drawn on rather than faded in: the line arriving end to end is the
   signal travelling, and it is the one piece of motion on this stage that
   carries information rather than decorating it.
   -------------------------------------------------------------------------- */

.iax-hop,
.iax-msg {
  transition: stroke-dashoffset var(--pd-t-slow) var(--pd-ease),
    stroke-opacity var(--pd-t) var(--pd-ease);
}

[data-iax-stepped] .iax-hop,
[data-iax-stepped] .iax-msg {
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
}

[data-iax-stepped] .iax-hop.is-drawn,
[data-iax-stepped] .iax-msg.is-drawn { stroke-dashoffset: 0; }

.iax-fork { transition: opacity var(--pd-t) var(--pd-ease); }
[data-iax-stepped] .iax-fork { opacity: 0; }
[data-iax-stepped] .iax-fork.is-drawn { opacity: 0.5; }
[data-iax-stepped] .iax-fork.is-drawn.is-on { opacity: 1; }

.iax-msg-label,
.iax-msg-time { transition: opacity var(--pd-t) var(--pd-ease), fill var(--pd-t) var(--pd-ease); }
[data-iax-stepped] .iax-msg-label,
[data-iax-stepped] .iax-msg-time { opacity: 0; }
[data-iax-stepped] .iax-msg-label.is-drawn,
[data-iax-stepped] .iax-msg-time.is-drawn { opacity: 1; }

/* The rung's own hop, picked out against the ones already drawn. */
.iax-hop.is-current, .iax-msg.is-current { stroke-opacity: 1; stroke-width: 2; }
.iax-msg-label.is-current, .iax-msg-time.is-current { fill: var(--pd-text); }

@media (prefers-reduced-motion: reduce) {
  .iax-lv,
  .iax-hop,
  .iax-msg,
  .iax-fork,
  .iax-msg-label,
  .iax-msg-time { transition: none; }
}

@media (max-width: 760px) {
  .iax__rung { font-size: var(--pd-size-label); }
}

/* A marker is not part of the stroke, so dashing a path to nothing still
   leaves its arrowhead sitting on the stage — a hop that has not happened
   yet, pointing at a unit that has not received anything. */
[data-iax-stepped] .iax-hop:not(.is-drawn),
[data-iax-stepped] .iax-msg:not(.is-drawn) { marker-end: none; }

/* ==========================================================================
   THE TWO ROWS
   ==========================================================================
   Taken from the engine page's own layout, in light rather than dark. The row
   IS the view selector: pressing Static Twin draws the structure, pressing
   Behavioral Twin draws the run. That is why the panel has no Structure /
   Behavior tab strip any more — one control for one choice, where two were a
   second place to change the same thing.

   ONLY A NAME, A HEADING AND A DESCRIPTION. The original carried an aphorism
   above each heading in the accent colour and a tag line beneath it in grey.
   Four pieces of copy per row left the reader deciding which one was the
   point. Do not put them back.
   ========================================================================== */

/* Rows left, panel right, the way /platform/engine composes the same pair.
   The left column is the narrower of the two on purpose: it is the reading
   column, and the drawing is the thing being read about. */
.iax__tx {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.6fr);
  gap: clamp(20px, 2.6vw, 34px);
  align-items: start;
  margin-top: clamp(20px, 2.6vw, 34px);
}

.iax__fig { margin: 0; }

.iax__rows {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

.iax__row {
  border: 1px solid var(--line, var(--border));
  border-radius: var(--radius, 12px);
  background: var(--bg-card, #fff);
  transition: border-color 200ms cubic-bezier(0.22, 0.8, 0.28, 1);
}

.iax__row.is-open { border-color: var(--color-teal-mid, var(--accent)); }

.iax__rowbtn {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  margin: 0;
  padding: 16px 18px;
  border: 0;
  border-radius: var(--radius, 12px);
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.iax__rowbtn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.iax__caret {
  flex: none;
  width: 0;
  height: 0;
  border-left: 6px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  color: var(--dim, var(--muted));
  transition: transform 200ms cubic-bezier(0.22, 0.8, 0.28, 1), color 200ms ease;
}

.iax__row.is-open .iax__caret {
  transform: rotate(90deg);
  color: var(--color-teal-ink, var(--accent));
}

.iax__rowname {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text);
}

.iax__rowbtn:hover .iax__rowname { color: var(--color-teal-ink, var(--accent)); }

/* THE COLLAPSE IS A GRID ROW GOING FROM 0fr TO 1fr, which is the trick the
   engine page uses and the reason there is no magic number here: the summary
   stays in the DOM and in the accessibility tree while being given no room,
   which display:none cannot do and max-height cannot do without a height that
   is wrong at some width.

   Without JavaScript nothing has .is-ready, so both summaries are open and a
   reader gets the whole explanation as ordinary text. */
.iax__summary {
  display: grid;
  grid-template-rows: 0fr;
  overflow: hidden;
  transition: grid-template-rows 300ms cubic-bezier(0.22, 0.8, 0.28, 1);
}

.iax__row.is-open .iax__summary,
.iax__rows:not(.is-ready) .iax__summary { grid-template-rows: 1fr; }

.iax__summary-inner {
  min-height: 0;
  padding: 0 18px 18px;
  opacity: 0;
  transition: opacity 240ms ease;
}

.iax__row.is-open .iax__summary-inner,
.iax__rows:not(.is-ready) .iax__summary-inner { opacity: 1; }

.iax__rowhead {
  margin: 0 0 10px;
  font-size: 1.04rem;
  font-weight: 600;
  line-height: 1.35;
  color: var(--text);
}

.iax__rowbody {
  margin: 0;
  font-size: 0.92rem;
  line-height: 1.6;
  color: var(--muted);
}

@media (prefers-reduced-motion: reduce) {
  .iax__summary, .iax__summary-inner, .iax__caret { transition: none; }
}

@media (max-width: 900px) {
  /* The reading column goes above the drawing rather than beside it. */
  .iax__tx { grid-template-columns: minmax(0, 1fr); }
}

/* --------------------------------------------------------------------------
   THE SIGNAL, RUNNING
   The hops draw on to say the exchange happened; the pulse says it is still
   happening. Ported from the engine page, geometry and timing both.

   THE PATHS SHARE THEIR STEM EXACTLY, coordinate for coordinate, so the two
   points sit on top of each other until the Gateway and separate only there.
   The 58.6% in the first keyframe is not a guess: the short branch is 303
   units of the long branch's 517, so that is where it arrives, and it holds
   at the end rather than restarting on its own clock.

   The pulse runs only when all three hops are drawn. Pinned to one hop the
   reader is inspecting a single message, and a signal running past it would
   be describing a different fact than the one on screen.
   -------------------------------------------------------------------------- */

.iax-pulse {
  fill: var(--pd-data);
  opacity: 0;
  filter: drop-shadow(0 0 5px var(--pd-data-glow));
}

.iax-pulse--a {
  offset-path: path("M 300 260 L 300 268 L 180 268 L 180 227 L 135 227 L 135 194 L 135 159 L 135 138");
}

.iax-pulse--b {
  offset-path: path("M 300 260 L 300 268 L 180 268 L 180 227 L 135 227 L 135 194 L 135 159 L 349 159 L 349 138");
}

.iax-pulse--a.is-on { animation: iax-travel-a 3.6s linear infinite; }
.iax-pulse--b.is-on { animation: iax-travel-b 3.6s linear infinite; }

/* WHAT MAKES THIS READ AS A SIGNAL RATHER THAN A DOT ON A LOOP.

   IT ARRIVES AND IS GONE. The first version parked each point on its
   destination and held it there — for pulse A that was four tenths of every
   cycle spent as a bright dot sitting on the Comfort ECU, which reads as
   something stuck rather than something delivered. Both now fade out on
   arrival, and a message that has been delivered is no longer in flight.

   IT DOES NOT POP INTO EXISTENCE. Three percent of fade at the start, so the
   point appears at the Charge Controller rather than materialising mid-air.

   THERE IS A BEAT BETWEEN SENDS. The travel occupies 70% of the cycle and
   the rest is empty. Back to back, the restart landed on the previous
   arrival and the whole thing strobed; the gap is what makes it read as a
   message sent again rather than a texture.

   THE 41% IS ARITHMETIC. A's route is 303 units of B's 517, and both move at
   one speed because they are the same signal — so A arrives at 303/517 of
   B's 70%. Change a coordinate and this has to be recomputed, or the two
   points will travel their shared stem at different speeds and stop reading
   as one. */
@keyframes iax-travel-a {
  0%   { offset-distance: 0%;   opacity: 0; }
  3%   { opacity: 1; }
  41%  { offset-distance: 100%; opacity: 1; }
  48%  { offset-distance: 100%; opacity: 0; }
  100% { offset-distance: 100%; opacity: 0; }
}

@keyframes iax-travel-b {
  0%   { offset-distance: 0%;   opacity: 0; }
  3%   { opacity: 1; }
  70%  { offset-distance: 100%; opacity: 1; }
  77%  { offset-distance: 100%; opacity: 0; }
  100% { offset-distance: 100%; opacity: 0; }
}

/* A point travelling on a loop is the one thing on this band that cannot be
   made still and still mean anything, so it is removed rather than frozen. */
@media (prefers-reduced-motion: reduce) {
  .iax-pulse { display: none; }
}

/* ── the panel title follows the open model ─────────────────────────────
   Driven off the section root's own data-iax-view, which the markup
   authors and js/page/ia-engine-explorer.js keeps in sync, so the header
   is right before the script runs and after it. */
[data-iax-title] { display: none; }
[data-iax-view="structure"] [data-iax-title="structure"],
[data-iax-view="behavior"]  [data-iax-title="behavior"]  { display: inline; }
