/* ===========================================================================
   Section 08's figure: what `proteus chat` actually does, in four beats.

   Owned by web-ui, loaded after site.css and hero-figure.css - AFTER, because
   the four `fig-*` keyframes it animates with live in hero-figure.css and a
   keyframe has to be defined for the rule that names it to resolve. Read that
   file's head comment for the `--d` / `--len` / `--march` conventions; they are
   the same here and there is deliberately only one copy of them.

   WHY THIS EXISTS
     The user asked for it: section 08 had "tons of space" - a 611 x 490px
     rectangle of nothing beside the heading at 1920 - and asked for "a section
     that has something people want to look at not skip over".

     It is not decoration filling a hole. Section 08's argument is the strongest
     technical claim on the page and the prose carried all of it alone: the AI
     edits the MODEL, never the data. The figure states that in four beats, each
     of which the prose already makes - plain language in; the model may only
     call a fixed structured operation; what changes is the estate; the
     telemetry is produced afterwards by the deterministic renderer.

   WHY THE VOID IS THERE, AND WHAT NOT TO DO ABOUT IT
     `.section-head` is capped at 62ch because that is a readable measure. That
     is correct typography and must not change. The fix is to put something in
     the empty half, never to widen the text.

   TWO PLACES THE BRIEF'S SKETCH WAS WRONG, AND THEY ARE BOTH IN THE FIGURE
     1. `add_fault`'s duration parameter is `duration_minutes`, not `minutes`.
        Checked against `tool_schemas()` in `proteus/ai/_chat_tools.py`. The
        required parameters are `kind` and `target`; `severity` is 0.0-1.0.
     2. The sketch showed `orders-db` shared by `web`/`orders` and `checkout`.
        It is not. In `northwind-rx`, `checkout` does not depend on `orders-db`
        at all, and `orders-db` is a SERVICE (archetype `rds-postgres`) rather
        than the shared thing. The shared thing is the infra node `rds-orders`,
        an `rds-instance`, and it is used by THREE services - `orders`,
        `claims-batch` and `orders-db` itself. Measured:

          .venv/bin/python -c "
          from proteus.model import presets
          m = presets.build('northwind-rx','us-west-2')
          for i in m.infra:
              u=[s.name for s in m.services if i.id in (s.uses_infra or [])]
              if len(u)>1: print(i.id, i.kind.value, u)"

        This matters beyond pedantry: faults spread through shared
        infrastructure and NOT along `depends_on` - the gate is
        `fault.target in service.uses_infra` - so a figure aiming the fault at
        the service `orders-db` would draw the wrong mechanism while looking
        right. Three services on one instance is also a better picture than two.

   ANIMATION
     Only `opacity`, `transform` and `stroke-dashoffset`. Fixed `viewBox` with
     `width: 100%; height: auto`, so the box is final on first paint and CLS
     stays 0. Base styles are the finished figure and the keyframes travel from
     hidden to them, so `animation: none` under `reduce` is by itself a correct
     static end state with all four beats present.
   =========================================================================== */

/* --- The two-column row -------------------------------------------------- */

/* A grid holding the section head and the figure, rather than a change to
   `.section-head` itself. The rest of section 08 - the code block, the nine
   functions, the panel - stays full width below this row, untouched. */
.cf-row { display: grid; gap: clamp(24px, 3vw, 44px); }

/* 1280px, not the 992px that `.hero__grid` uses, and this is measured. The
   right column has to be wide enough that 13-unit SVG text renders at 13px or
   better, which needs about 520px of column:

     viewport 1920  container 1280  head 625  gap 44  ->  611px   1.17x
     viewport 1400  container 1312  head 625  gap 44  ->  555px   1.07x   <- floor
     viewport 1200  container 1112  head 625  gap 40  ->  447px   0.86x   too small

   Below 1280px the figure drops BELOW the head instead of beside it, where it
   gets the container's full width and reads better than it would squeezed. It is
   not hidden until 44rem - unlike the hero figure, this one has a fallback
   position, so there is no reason to throw it away at tablet sizes. */
/* PIXELS, NOT `rem`, AND DELIBERATELY - the rest of this project uses `rem`
   breakpoints and this one must not.

   Two reasons, and the first is the one that matters. `.cf`'s rendered text size
   is `figureWidthPx / 520 * 13` and nothing else: SVG text inside a fixed
   `viewBox` does not scale with the root font size at all. So the thing this
   breakpoint is protecting is a number of CSS pixels, and `rem` would express it
   in a unit it has no relationship to.

   The second is that it bit. Written `80rem`, this matched at a 1100px viewport
   in the browser it was verified in, which computes the root font size as 12px -
   making `80rem` 960px rather than 1280px. The figure went into a 339px column
   and its labels rendered at 8.5px. Media-query `rem` is specified to resolve
   against the INITIAL font size, not the root's computed one, so that behaviour
   is arguably a browser bug; it is also not a thing to bet a layout on when the
   correct unit is right there.

   `.hero-figure` in hero-figure.css keeps `62rem` on purpose, for the opposite
   reason: it has to flip at the same moment `.hero__grid` does, and that is
   written `62rem` in site.css. Matching the neighbour's unit is what keeps those
   two locked together. Here there is no neighbour to track. */
@media (min-width: 1280px) {
  .cf-row {
    grid-template-columns: minmax(0, 62ch) minmax(0, 1fr);
    align-items: start;
  }
}

/* Below 700px the beat labels fall under 11px however the figure is placed, the
   four-beat flow becomes a very tall thin column, and the prose beside it already
   makes every claim it makes. Same reasoning as the hero figure. */
@media (max-width: 700px) {
  .cf-figure { display: none; }
}

.cf {
  display: block;
  width: 100%;
  height: auto;
  /* CAPPED, because `width: 100%` cuts both ways. Below 1280px the figure becomes
     a single full-width column, and at a 1100px viewport that is a 997px box - a
     520-unit viewBox stretched to nearly twice its intended scale, rendering the
     13-unit labels at 24.9px. The figure went from unreadably small to comically
     large in one breakpoint.

     620px is 1.19x, which puts the labels at 15.5px: bigger than the 13.1px the
     two-column case gives them, and still clearly a diagram rather than a poster.
     In the two-column case the cap never binds - that column is 523px. */
  max-width: 620px;
}

/* --- Ink ----------------------------------------------------------------- */

/* Every colour is a --p-* token, so both themes come free. Measured against the
   real composited background - the figure sits on `--p-bg` in this section,
   which is a plain `.section`. */
.cf text { font-family: var(--p-mono); }

.cf-beat {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.14em;
  fill: var(--p-accent);
}
/* The beat number, so the four read as a sequence and not as four separate
   pictures. Text, because a reader should not have to infer order from position. */
.cf-step { font-size: 12px; font-weight: 600; letter-spacing: 0.1em; fill: var(--p-muted); }

.cf-quote { font-size: 13px; fill: var(--p-text); }
.cf-code { font-size: 13px; fill: var(--p-text); }
.cf-code-key { fill: var(--p-accent); font-weight: 600; }
.cf-node-label { font-size: 12px; fill: var(--p-text); text-anchor: middle; }
.cf-note { font-size: 12px; fill: var(--p-muted); }
.cf-emph { font-size: 13px; fill: var(--p-accent); font-weight: 600; }

/* BOTH PANELS ARE `--p-surface`, AND THE SECOND ONE WAS NOT.

   Beat 02 was `--s-code-bg` first, to look like the transcript surface
   `.code-block` uses. That is broken in the dark theme and the probe found it:
   `--s-code-bg` IS `--p-forest-950`, and in dark `--p-bg` is `--p-forest-950`
   too - identical values - so the panel's fill was the page's fill and its only
   boundary was a `--s-code-border` hairline measuring 2.00:1. A panel whose edge
   is its sole definition needs that edge to carry 3:1, and it did not.

   So both panels are `--p-surface`, which differs from `--p-bg` in dark
   (#102b22 on #071e17) and is the same near-white card as every `.card` on the
   page in light. The code beat still reads as the tool's own output rather than
   the visitor's words, through the mono face and the accent function name -
   which is a stronger cue than a background colour anyway.

   THE LEFT ACCENT BAR IS WHY THE HAIRLINE NO LONGER MATTERS. `--p-border` is
   1.35:1 on `--p-bg` in light, and in light `--p-surface` is #ffffff against a
   #f4f8f5 page - about 1.06:1 - so neither the fill nor the border defines these
   boxes on their own. The 3-unit `--p-accent` bar does, at 5.03:1, and it is
   lifted straight from `.panel` in site.css, which solves the same problem the
   same way. The hairline stays as the softer part of that pair. */
.cf-say,
.cf-call {
  fill: var(--p-surface);
  stroke: var(--p-border);
  stroke-width: 1;
}
.cf-rule { fill: none; stroke: var(--p-accent); stroke-width: 3; stroke-linecap: round; }

.cf-box { fill: none; stroke: var(--p-accent); stroke-width: 1.4; }
.cf-edge,
.cf-tip {
  fill: none;
  stroke: var(--p-accent);
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* The beat-to-beat arrows are heavier than the graph edges inside beat 3: they
   carry the reading order, which is the figure's spine. */
.cf-flow { stroke-width: 1.8; }

/* THE DEGRADED INFRA NODE. `--p-warning`, matching the hero figure's degraded
   node and the `doctor` transcript's WARN rows - one meaning, one colour, across
   the whole page. The three services that share it get the same amber, because
   that is the claim: a fault on shared infrastructure implicates every service
   using it, whether or not anything is wrong with them locally.

   "degraded" and "no local cause" are words, not just colour: colour may not be
   the only means of conveying information, and the second phrase is the actual
   product behaviour a reader would otherwise miss. */
.cf-warn .cf-box { stroke: var(--p-warning); }
.cf-warn .cf-node-label { fill: var(--p-warning); }
.cf-warn-edge { stroke: var(--p-warning); }
.cf-state { font-size: 12px; font-weight: 600; letter-spacing: 0.04em; fill: var(--p-warning); }

/* --- Timeline ------------------------------------------------------------ */

/* Delays live in the markup as `--d`, same convention as the hero figure. The
   four beats land in order, each about 320ms after the last, so the figure reads
   top to bottom rather than appearing all at once. Everything is done by ~2.6s. */
.cf-beat,
.cf-step,
.cf-quote,
.cf-code,
.cf-note,
.cf-emph,
.cf-state,
.cf-tip {
  animation: fig-fade 0.5s ease-out var(--d, 0s) both;
}

.cf-say,
.cf-call,
.cf-rule,
.cf-node {
  animation: fig-rise 0.55s cubic-bezier(0.22, 0.61, 0.36, 1) var(--d, 0s) both;
}

.cf-edge {
  stroke-dasharray: var(--len);
  stroke-dashoffset: 0;
  animation: fig-draw 0.45s ease-in-out var(--d, 0s) both;
}

/* --- Reduced motion ------------------------------------------------------ */

/* A meaningful static end state: the base styles above ARE the finished figure,
   so switching every animation off renders all four beats, the drawn graph, the
   amber node and every label. Said directly rather than relying on site.css's
   global `animation-duration: 0.001ms !important`, which is a hammer in a file
   this one does not own. */
@media (prefers-reduced-motion: reduce) {
  .cf-beat, .cf-step, .cf-quote, .cf-code, .cf-note, .cf-emph, .cf-state,
  .cf-tip, .cf-say, .cf-call, .cf-rule, .cf-node, .cf-edge {
    animation: none;
  }
}
