/* ═══════════════════════════════════════════════════════════════════════════
   DIAGRAM — the control plane, and the only drawing of it on the site.

   It lived in blocks.css, which index.html does not load, so the front door
   drew a different picture of the same system. Both pages load this now.

   The tokens are declared here rather than inherited, because the two pages
   name their colours differently — blocks.css calls the ground --bg and
   landing.css calls it something else — and a figure that changes colour
   depending on which sheet happens to be loaded is not one figure.

   THE BLOCK BELOW IS NOTHING BUT CUSTOM PROPERTIES, and that is load-bearing:
   gate.sh check 1 forbids a raw hex where a colour is applied, and
   tools/css-code.cjs implements it by blanking any block whose declarations
   are ALL custom properties. Mixed into a styling rule these would be paint.
   Declared alone they are named values.
   ═══════════════════════════════════════════════════════════════════════════ */

.diag {
  --card:     #FFFFFF;
  --line:     rgba(18, 24, 32, .14);
  --ink:      #121820;
  --mut:      #5F6670;
  --acc:      #121820;
  --acc-text: #121820;
  --face-m:   ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
}

.diag { position: relative; }
.diag svg { width: 100%; height: auto; overflow: visible; }

/* A DENSE FIGURE CANNOT BE MADE SMALL, IT CAN ONLY BE MADE UNREADABLE.
   The plane is authored at 900 units wide. Fitted to a 375px phone the
   viewBox scales to 0.368, which puts the 11-unit box labels at 4px and the
   8.5-unit side notes at 3.1px on the actual screen. Nothing overflowed and
   nothing looked broken in a desktop screenshot; it was simply illegible on
   the device most people will open it on.

   So below the point where it stops being readable it keeps a floor and
   PANS instead. 760px is the floor because that is the width
   references/diagram is authored at, and it holds the same density of
   strokes and labels legibly. At that floor the box labels land at 9.3px
   and the side notes at 7.2px.

   The scroll lives on .diag, not on the page: the body still measures zero
   horizontal overflow at 375, which is the gate this must not break. */
/* THE FLOOR WAS STILL BELOW THE FIGURE’S OWN SCALE.
   760 against a 900-unit viewBox is 0.844, and the comment above says what
   that costs in as many words: 9.3px labels and 7.2px side notes. Those
   numbers were written down and then left, because a screenshot at 860px
   looks fine and the loss only shows on the device.

   900 is 1:1, and 1:1 was still not enough: the figure is AUTHORED small.
   Its side notes are 8.5px and 9px in viewBox units, so at 1:1 they paint at
   8.5px and 9px, which is under the floor for a tracked label let alone for
   reading.

   Raising the authored sizes is the wrong lever — the plane is hand-laid and
   a bigger glyph would collide with the box it sits in. Raising the FLOOR
   scales every element together and cannot cause an overlap, so 1200: a 1.333
   multiplier that puts the side notes at 11.3px, the keys at 12.7 and the box
   labels at 14.7.

   It pans about three screens on a phone. That is the trade, made knowingly:
   panning a diagram is a thing people know how to do, and squinting at an
   8.5px label is not. The scroll lives on .diag, so the BODY still measures
   zero horizontal overflow — the gate this must not break. */
@media (max-width: 1240px) {
  .diag { overflow-x: auto; overscroll-behavior-x: contain; }
  .diag svg { min-width: 1200px; }
}
.diag__box { fill: var(--card); stroke: var(--line); }
.diag__t { font-family: var(--face-m); font-size: 11px; fill: var(--ink); letter-spacing: .6px; }
.diag__k { font-family: var(--face-m); font-size: 9.5px; fill: var(--mut); letter-spacing: .8px; }
.diag__l { stroke: var(--line); fill: none; }
.diag__l--heavy { stroke: var(--ink); stroke-width: 1.6; }
.diag__node { cursor: pointer; }
.diag__node:hover .diag__box, .diag__node:focus-visible .diag__box { stroke: var(--acc); }

/* ── THE CONTROL PLANE, REBUILT FROM THREE MEASURED REFERENCES ───────────
   It was orthogonal V/H paths between grey rounded rectangles, which is a
   Visio flowchart, and Kevin named it before the teardown did.

   THE STROKE REGISTER is references/diagram, measured off its render:
       structural hairline   1px   43 elements
       emphasis              2px    7 elements
       joins                 bevel  (its dominant join)
       caps                  butt
   Its hairline is #B5B6B8 and its emphasis #6B6C6E — a 2:1 weight ratio
   and roughly a 2:1 darkness ratio. The RATIO is what transfers; the
   values come from this site's tokens, because that sheet's greys are
   somebody else's neutral and this page has its own.

   (The atlas records that reference as having SQUARE caps. Measured, they
   are butt. Small, and it is written down because the atlas is otherwise
   accurate and a wrong number in it will be trusted later.)         */
.diag__l { stroke-linejoin: bevel; stroke-linecap: butt; }
.diag__l--heavy { stroke: var(--ink); stroke-width: 1.6; }

/* the dashed run BELOW the gate. The gap is the argument: a solid line
   through an authority gate would say the opposite of the sentence next
   to it. */
.diag__l--stop { stroke: var(--ink); stroke-width: 1.6; stroke-dasharray: 3 5; opacity: .55; }

.diag__gate { stroke: var(--ink); stroke-width: 2; fill: none; stroke-linecap: butt; }

/* ── THE BEAM ────────────────────────────────────────────────────────────
   references/Overhaul/animated-beam, measured out of the component: TWO
   STACKED PATHS on identical geometry — a rail at strokeOpacity 0.2, and
   above it a travelling highlight — with duration 4-7s randomised per
   beam and ease cubic-bezier(0.16, 1, 0.3, 1), which is easeOutExpo — and
   which is ALREADY --e-out on this site, declared in the token block above
   from Part I §7 long before this reference was opened. The two agree to
   the digit, so the easing is not an import at all; it is the curve this
   site already uses, reached independently.

   Both paths are kept and so is the easing. Two values differ and both
   are forced rather than chosen:

     the highlight  its four-stop gradient is moved by framer-motion.
                    This site ships no runtime that can animate a
                    linearGradient's x1/x2, so the band travels on
                    stroke-dashoffset over pathLength="100" instead. The
                    gradient's transparent tails become a round cap.
     the duration   4.6s flat, the midpoint of its 4-7s range. Its
                    randomness comes from Math.random() at mount; a
                    static page has no mount, and five beams on one
                    figure reading the same clock is calmer than five
                    reading none.

   The stagger is nth-child rather than a style attribute: the policy is
   style-src 'self' with no unsafe-inline, and this page has already lost
   one inline value that way. */
/* --line is ALREADY an alpha colour (rgba(18,24,32,.14)); multiplying it by
   the reference strokeOpacity of 0.2 gave 0.028 and the rail disappeared.
   The reference strokes SOLID gray at 0.2. Same intent needs the solid
   token here, not the one that already carries the fade. */
.diag__rail { stroke: var(--ink); stroke-width: 2; fill: none; opacity: .2; }
.diag__beam { stroke: var(--acc); stroke-width: 2; fill: none; stroke-linecap: round; }
.diag__beam path { stroke-dasharray: 14 86; stroke-dashoffset: 100; opacity: 0; }

@media (prefers-reduced-motion: no-preference) {
  .diag__beam path {
    opacity: 1;
    animation: diag-beam var(--d-amb) var(--e-out) infinite;
  }
  /* THE STAGGER IS A FRACTION OF THE CYCLE, not four hand-typed numbers.
     The five beams spread across the first third of one loop, so the fan
     reads as one wave crossing the figure rather than five clocks that
     happen to be near each other. Derived, so retiming --d-amb retimes the
     stagger with it and the two cannot drift apart. */
  .diag__beam path:nth-child(2) { animation-delay: calc(var(--d-amb) / 12); }
  .diag__beam path:nth-child(3) { animation-delay: calc(var(--d-amb) / 6); }
  .diag__beam path:nth-child(4) { animation-delay: calc(var(--d-amb) / 4); }
  .diag__beam path:nth-child(5) { animation-delay: calc(var(--d-amb) / 3); }
}
@keyframes diag-beam { from { stroke-dashoffset: 100; } to { stroke-dashoffset: 0; } }

/* ── THE TWO LANES ───────────────────────────────────────────────────────
   The compute-by-risk claim is made by COUNT, not by caption — one agent
   against four, one verifier against two families. The hot label is the
   only accent on the row so the eye is told which side is expensive
   before it counts anything. */
.diag__lane {
  font-family: var(--face-m); font-size: 9px; letter-spacing: 1px;
  fill: var(--mut);
}
.diag__lane--hot { fill: var(--acc-text); }

.diag__t--sm { font-size: 10px; }
.diag__k--side { font-size: 8.5px; letter-spacing: 1px; fill: var(--mut); }

/* ── THE TWO FAMILIES, DRAWN RATHER THAN LABELLED ────────────────────────
   Two models sharing a training lineage fail together, so a verifier from
   the same family is not verification — it is the same guess twice. The
   brief asks for that difference to be VISIBLE. Family I has a solid edge
   and filled marks; family II a dashed edge and hollow ones. They read as
   different objects in greyscale, at thumbnail size, and to a reader who
   never reaches the caption. */
.diag__box--alt { stroke-dasharray: 5 3; }
.diag__fam { fill: var(--ink); }
.diag__fam--alt { fill: none; stroke: var(--ink); stroke-width: 1; }

/* the effect at the end of the chain is not an actor, and is drawn as the
   only filled box on the figure */
.diag__box--act { fill: var(--card); stroke: var(--ink); stroke-width: 1.6; }

/* ── SAY THAT IT PANS ──────────────────────────────────────────────────────
   At 375px the plane is 311px of a 1200px figure — 3.9 screens of panning,
   and each vertical slice carries a lot of white because a wide diagram cut
   into narrow strips is mostly gaps. Without a word on screen that reads as a
   figure that failed to load, not a figure you drag.

   Shown only where the scroller actually exists. aria-hidden because it
   describes a pointer gesture: a screen reader is already being handed the
   figure's own labels, and a keyboard reaches the same content by tabbing the
   nodes. */
.diag__pan { display: none; }
@media (max-width: 1240px) {
  .diag__pan {
    display: block;
    margin: 8px 0 0;
    font-family: var(--face-ui, inherit);
    font-size: 12px;
    letter-spacing: .04em;
    text-align: center;
    opacity: .6;
  }
}
