/* ==========================================================================
   stage.css — the landing page.

   Loaded after tokens.css and base.css, which stay as they are: base.css owns
   the grid font, .grid / .screenbox / .sizer, .install and .termshell, and
   tools/build-gridfont.py --check reads its @font-face block. Nothing here
   touches any of that.

   The idiom is Apple's product page, not Apple's palette. What is borrowed is
   the *behaviour of the layout*: one centred column of very large, very tight
   type; enormous vertical rhythm; a thin translucent bar that gets out of the
   way; media in big soft rectangles; alternating full-bleed panels; and copy
   cut down to a line and a half per idea. What is not borrowed is a single
   colour — the palette is still the literal blueprint-dark theme, because the
   captures and the duotoned artwork are rendered against those exact values
   and the whole credibility argument rests on them matching.

   One deliberate departure from the old page: there is no curtain. An Apple
   page never gates its own content; it opens on the thing. The curtain markup
   and .curtain in motion.css are untouched if it should come back.
   ========================================================================== */

:root {
  /* SF Pro first, so an Apple machine renders this page in Apple's own type
     and everything else falls back to what tokens.css already asked for. No
     webfont is loaded: this site ships no third-party requests and that is
     worth more than a typeface. */
  --ui: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
        "Inter", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --pill: 980px;
  --card: clamp(14px, 1.6vw, 22px);
  --media: clamp(14px, 2vw, 28px);
  --bar: 48px;
  --measure: 40ch;
}

body { font-family: var(--ui); background: var(--deep); letter-spacing: -.011em; }

/* base.css sets section padding for the old rhythm; this page is far airier. */
section { padding: clamp(84px, 12vw, 184px) 0; }
.wrap { max-width: 1120px; }
.wide { max-width: 1360px; margin: 0 auto; padding: 0 var(--gutter); width: 100%; }

/* ------------------------------------------------------------------ bar --- */

.bar {
  position: sticky; top: 0; z-index: 50; height: var(--bar);
  display: flex; align-items: center;
  background: color-mix(in srgb, var(--deep) 72%, transparent);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid color-mix(in srgb, var(--rule) 55%, transparent);
}
.bar .wide { display: flex; align-items: center; gap: 26px; }
.bar .home { display: inline-flex; align-items: center; gap: 9px; text-decoration: none; }
.bar .home .mark { height: 19px; }
.bar .home b { font-size: 14px; color: var(--ink); font-weight: 600; letter-spacing: .01em; }
.bar nav { display: flex; gap: 22px; margin-left: auto; align-items: center; }
.bar nav a { font-size: 12.5px; color: var(--muted); text-decoration: none; white-space: nowrap; }
.bar nav a:hover { color: var(--ink); text-decoration: none; }
.bar .pill { padding: 6px 15px; font-size: 12.5px; }
@media (max-width: 720px) { .bar nav a.hide-sm { display: none; } }

/* ---------------------------------------------------------------- pills --- */

.pill {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  border-radius: var(--pill); border: 1px solid transparent;
  padding: 13px 26px; font-size: 16px; font-weight: 500; line-height: 1;
  text-decoration: none; cursor: pointer; font-family: inherit;
  transition: background .22s var(--ease), border-color .22s, color .22s, transform .22s var(--ease);
}
.pill:hover { text-decoration: none; transform: translateY(-1px); }
.pill.solid { background: var(--house); color: #17120a; font-weight: 600; }
.pill.solid:hover { background: #eec081; }
.pill.ghost { border-color: var(--rule); color: var(--ink); }
.pill.ghost:hover { border-color: var(--house); color: var(--house); }
.more {
  font-size: 16px; color: var(--accent); text-decoration: none;
  display: inline-flex; align-items: center; gap: 4px;
}
.more::after { content: "\203A"; font-size: 1.18em; line-height: 0; transition: transform .2s var(--ease); }
.more:hover { text-decoration: none; }
.more:hover::after { transform: translateX(3px); }
.actions { display: flex; flex-wrap: wrap; gap: 14px 18px; align-items: center; justify-content: center; margin-top: 30px; }

/* ------------------------------------------------------------- headings --- */

.eyebrow {
  font-family: var(--mono); font-size: 12px; letter-spacing: .2em;
  text-transform: uppercase; color: var(--house); margin-bottom: 16px;
}
.head { text-align: center; max-width: 900px; margin: 0 auto; }
/* base.css caps h2 at 20ch with margin:0 0 20px, which inside a centred block
   left-aligns the box and only centres the text within it — the heading then
   sits visibly off-centre. Both have to be restated here. */
.head h2 {
  font-family: var(--ui); font-size: clamp(30px, 4.6vw, 62px); line-height: 1.06;
  letter-spacing: -.028em; font-weight: 700; color: var(--ink);
  max-width: 20ch; margin: 0 auto;
}
.head .sub {
  font-size: clamp(17px, 1.6vw, 21px); line-height: 1.5; color: var(--muted);
  max-width: 46ch; margin: 18px auto 0; letter-spacing: -.006em;
}
.head .sub b { color: var(--ink); font-weight: 600; }
.head .actions { margin-top: 26px; }

/* ----------------------------------------------------------------- hero --- */

/* The opening is a room, not a header image: it holds most of the first
   screen, with the copy optically centred in it. Capped in px so a very tall
   monitor does not turn it into a poster you have to scroll past. */
.hero {
  text-align: center; min-height: 88vh;
  /* A grid item's automatic minimum is min-content, and the install command is
     one nowrap 464px string -- without these two the hero column is sized by
     that string and the headline runs 74px off a 390px phone. base.css makes
     the same point about min-width:0 where a grid sits in a flex track. */
  display: grid; grid-template-columns: minmax(0, 1fr); align-content: center;
  padding: clamp(72px, 10vh, 132px) 0 clamp(64px, 9vh, 120px);
}
/* Cap it on a tall monitor: 88vh of a 1600px-high screen is a poster, not an
   opening. A media query rather than min(), which not every engine we still
   render on has. */
@media (min-height: 1000px) { .hero { min-height: 880px; } }
.hero .gloss { justify-content: center; display: flex; align-items: baseline; gap: 0; margin-bottom: 20px; }
.hero h1 {
  font-family: var(--ui); margin-bottom: 0;
  /* Sized so each of the two sentences holds its own line at the measure —
     104px looked right in isolation and broke "Nothing stops." in half. */
  font-size: clamp(34px, 5.4vw, 72px); line-height: 1.04; letter-spacing: -.034em;
  font-weight: 700; color: var(--ink); max-width: 100%; margin: 0 auto;
}
.hero h1 .fade { color: var(--muted); }
.hero .sub {
  font-size: clamp(18px, 1.9vw, 24px); line-height: 1.4; color: var(--muted);
  max-width: 44ch; margin: 24px auto 0; letter-spacing: -.012em;
}
.hero .sub b { color: var(--ink); font-weight: 600; }
.hero .fine {
  font-family: var(--mono); font-size: 12px; color: var(--faint);
  margin-top: 22px; letter-spacing: .02em;
}
.hero .fine a { color: var(--faint); }

/* ---------------------------------------------------------- the stage ----
   The print is the hero's ground, full bleed, with one lamp aimed at the
   raised stage -- 50% across, 74% down once `cover` has taken the top and
   bottom off the plate. It does not follow the pointer: a house light is not
   held by the audience, and a fixed lamp is also the version a phone, a
   screenshot and a reader who never moves the mouse all get.

   Three layers, same image: `.dim` is the room, `.lit` is the same frame at
   full brightness showing only through the lamp's mask, `.warm` is the light
   itself. The two <picture>s resolve to one network request.
   -------------------------------------------------------------------------- */

.hero { position: relative; overflow: hidden; }
.hero .wrap { position: relative; z-index: 1; min-width: 0; width: 100%; }

.backdrop {
  position: absolute; inset: 0; pointer-events: none;
  --aim-x: 50%; --aim-y: 74%; --r: clamp(200px, 26vw, 560px);
  /* --vis is 1 while the hero is unscrolled and 0 once it has left, written
     by js/motion.js. Unset -- reduced motion, no JS -- reads as 1. */
  opacity: calc(.42 + .58 * var(--vis, 1));
  transition: opacity .25s linear;
}
.backdrop picture { position: absolute; inset: 0; }
/* Oversized on purpose, and left alone by the rules above: motion.css sizes
   .plate img at 118%/-9% so the parallax has somewhere to travel, and
   restating height here would take that room back. */
.backdrop img { position: absolute; left: 0; width: 100%; object-fit: cover; object-position: 50% 62%; }

/* The plate was held at brightness(.34), which is legible but leaves the
   theatre as a suggestion: at that level the boxes, the lanterns and the crowd
   are all one dark mass. Lifted to .52 so the picture actually reads. */
.backdrop .dim { filter: brightness(.52) saturate(.86) contrast(1.02); }

/* Keep the masked light static so an idle page does not keep compositing. */
.backdrop .lit {
  filter: brightness(1.02) saturate(1.12) contrast(1.02);
  -webkit-mask-image: radial-gradient(circle var(--r) at var(--aim-x) var(--aim-y),
    #000 0%, rgba(0,0,0,.72) 44%, rgba(0,0,0,.24) 64%, transparent 78%);
  mask-image: radial-gradient(circle var(--r) at var(--aim-x) var(--aim-y),
    #000 0%, rgba(0,0,0,.72) 44%, rgba(0,0,0,.24) 64%, transparent 78%);
  opacity: .92;
}
/* A fixed warm pool above the image scrim. */
.backdrop .warm {
  position: absolute; inset: 0; mix-blend-mode: screen;
  z-index: 3;
  background: radial-gradient(circle var(--r) at var(--aim-x) var(--aim-y),
    color-mix(in srgb, var(--house) 24%, transparent) 0%, transparent 72%);
  opacity: .5;
}
/* The scrim is what keeps 62px type legible over a woodblock print: the top
   holds the header bar, the bottom dissolves the plate into the section
   below so there is no visible edge to the image at all. */
.backdrop::after {
  content: ""; position: absolute; inset: 0;
  background:
    radial-gradient(88% 60% at 50% 38%,
      rgba(12, 15, 22, .88) 0%, rgba(12, 15, 22, .62) 52%, rgba(12, 15, 22, .18) 100%),
    linear-gradient(180deg,
      rgba(21, 26, 35, .74) 0%, rgba(21, 26, 35, .34) 26%,
      rgba(21, 26, 35, .70) 68%, var(--ground) 100%);
}

/* ------------------------------------------------------------- on a phone --
   The lamp is sized with clamp(200px, 26vw, 560px) above. At 390px that is
   26vw = 101px, i.e. pinned to the 200px floor -- a pool the size of a coin in
   a hero that is nearly a whole screen tall, which is why the print read dark
   and the light barely read as light at all on a phone.

   Here the pool is sized against the viewport instead, the aim moves up to
   where `cover` actually puts the raised stage on a portrait crop, the plate
   is lifted again to carry the smaller amount of light falling on it, with a
   fixed warm pool. */
@media (max-width: 760px) {
  .backdrop {
    --r: clamp(230px, 74vw, 460px);
    --aim-y: 58%;
  }
  .backdrop .dim { filter: brightness(.60) saturate(.90) contrast(1.02); }
  /* Also warmer at the core than the desktop pool. On a wide screen the light
     has a whole theatre to fall across and 24% house is plenty; in a portrait
     crop it is a small pool inside a tall frame, and at 24% it read as a
     smudge rather than as a lamp aimed at something. */
  .backdrop .warm {
    background: radial-gradient(circle var(--r) at var(--aim-x) var(--aim-y),
      color-mix(in srgb, var(--house) 38%, transparent) 0%,
      color-mix(in srgb, var(--house) 14%, transparent) 46%,
      transparent 74%);
  }
}


.hero .credit {
  font-family: var(--mono); font-size: 11.5px; line-height: 1.6; color: var(--faint);
  max-width: 62ch; margin: 34px auto 0; letter-spacing: .01em;
}
.hero .credit i { color: var(--muted); font-style: italic; }
.hero .credit a { color: var(--faint); }

/* ---------------------------------------------------------------- doors ---
   Numerals, not cards. The right-hand column states what the choice costs —
   how many sections it removes — because hiding four sections silently was
   the one serious objection to this control. */

.doors { max-width: 900px; margin: clamp(30px, 4vw, 48px) auto 0; }
.door {
  display: grid; grid-template-columns: 62px minmax(0, 1fr) auto; gap: 20px; align-items: start;
  width: 100%; appearance: none; background: none; border: 0; border-top: 1px solid var(--rule);
  text-align: left; cursor: pointer; font: inherit; color: inherit; padding: 22px 4px;
  transition: padding-left .22s var(--ease), background .22s;
}
.doors .door:last-of-type { border-bottom: 1px solid var(--rule); }
/* No background on hover. There used to be a #171e29 rectangle here, the full
   width of the row, and it is the reason the warm light css/house.css draws
   over it read as a box that got warmer rather than as light. The row slides
   4px instead -- house.css holds that number, beside the light it belongs
   with. */
.door:hover { padding-left: 4px; }
.door .n {
  font-family: var(--ui); font-size: 42px; line-height: .84; font-weight: 200;
  color: var(--rule); letter-spacing: -.04em; transition: color .22s;
}
.door:hover .n, .door[aria-pressed="true"] .n { color: var(--house); }
.door h3 { font-size: 18px; line-height: 1.28; letter-spacing: -.016em; font-weight: 600; margin: 0; color: var(--ink); }
.door[aria-pressed="true"] h3 { color: var(--house); }
.door p { font-size: 14.5px; line-height: 1.55; color: var(--muted); margin: 7px 0 0; }
.door .cost {
  font-family: var(--mono); font-size: 11.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); white-space: nowrap; padding-top: .8em;
}
.door[aria-pressed="true"] .cost { color: var(--house); }
@media (max-width: 700px) {
  .door { grid-template-columns: 44px minmax(0, 1fr); gap: 14px; }
  .door .n { font-size: 30px; }
  .door .cost { grid-column: 2; padding-top: 8px; }
}

/* THE CHOICE CHIP IS GONE, and it was 20 lines of sticky bar here.

   It read "READING AS several, across machines" under the header for the
   whole rest of the document -- a caption for a decision three inches below
   it that the doors were already showing, in --house, with `aria-pressed`.
   The receipt is now where the decision was made: the two questions you did
   not answer fold away and the one you did carries a line under it. That
   block is in css/house.css beside the doors it belongs to, and the
   behaviour is in js/stage.js. */

/* -------------------------------------------------------------- panels --- */

.panel { background: var(--ground); }
.panel + .panel { border-top: 1px solid color-mix(in srgb, var(--rule) 45%, transparent); }
section[hidden] { display: none; }

.media { border-radius: var(--media); overflow: hidden; margin-top: clamp(34px, 5vw, 64px); }
.media.lit {
  box-shadow: 0 -1px 0 color-mix(in srgb, var(--house) 42%, transparent),
              0 40px 90px -50px color-mix(in srgb, var(--house) 55%, transparent);
}
/* base.css gives .termshell its own radius; match the softer one here. */
.media .termshell { border-radius: var(--media); }
.media .screenbox { border-radius: 0 0 var(--media) var(--media); }
figure { margin: 0; }
figcaption {
  text-align: center; font-size: 13.5px; line-height: 1.6; color: var(--faint);
  max-width: 64ch; margin: 18px auto 0;
}
.framenote { text-align: center; font-size: 13.5px; color: var(--faint); max-width: 66ch; margin: 18px auto 0; min-height: 2.6em; }
.framenote b { color: var(--muted); font-weight: 500; }
.scrollhint { text-align: center; }

/* the four rails, drawn as one frame -------------------------------------
   The section's claim is that four regions belong to a single frame and never
   move. Four separate boxes argued against the sentence; this draws the frame
   once, with the regions annotated where they actually sit. Geometry is the
   real workbench's: agents upper left, processes beneath them, the stage in
   the middle at full height, changes down the right. */

.aframe {
  max-width: 1000px; margin: clamp(34px, 5vw, 60px) auto 0;
  border: 1px solid var(--rule); border-radius: var(--media); overflow: hidden; background: var(--sunken);
}
.aframe .bar { display: flex; align-items: center; gap: 7px; padding: 9px 13px; background: #171e2a; border-bottom: 1px solid var(--rule); }
.aframe .bar i { width: 9px; height: 9px; border-radius: 50%; background: var(--rule); flex: none; }
.aframe .bar span { font-family: var(--mono); font-size: 11.5px; color: var(--faint); margin-left: 8px; }
.aframe .body { display: grid; grid-template-columns: 212px minmax(0, 1fr) 212px; }
.aframe .z { padding: 18px; transition: background .22s; }
.aframe .z:hover { background: #1a2230; }
.aframe .z.ul  { grid-column: 1; grid-row: 1; border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }
.aframe .z.ll  { grid-column: 1; grid-row: 2; border-right: 1px solid var(--rule); }
.aframe .z.mid { grid-column: 2; grid-row: 1 / span 2; border-right: 1px solid var(--rule); }
.aframe .z.rt  { grid-column: 3; grid-row: 1 / span 2; }
.aframe .z .where { font-family: var(--mono); font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase; color: var(--faint); }
.aframe .z h3 { font-size: 16px; line-height: 1.24; letter-spacing: -.014em; font-weight: 600; color: var(--ink); margin: 9px 0 0; }
.aframe .z.mid h3 { font-size: 21px; }
.aframe .z p { font-size: 13.5px; line-height: 1.54; color: var(--muted); margin: 8px 0 0; }
.aframe .z.mid p { font-size: 15px; }
.aframe .z .state { font-family: var(--mono); font-size: 11.5px; color: var(--faint); margin-top: 12px; line-height: 1.7; }
.aframe .z .state b { font-weight: 400; }
.aframe .z .state .w { color: var(--house); } .aframe .z .state .r { color: var(--danger); }
.aframe .z .state .g { color: var(--ok); }    .aframe .z .state .b { color: var(--accent); }
@media (max-width: 860px) {
  .aframe .body { grid-template-columns: minmax(0, 1fr); }
  .aframe .z { grid-column: 1 !important; grid-row: auto !important;
    border-right: 0 !important; border-bottom: 1px solid var(--rule); }
  .aframe .z.rt { border-bottom: 0; }
}

/* keyed rows: for a list whose left-hand item is a name rather than a point.
   A coloured dash on a route implies something is wrong with it. */
.keyed { max-width: 68ch; margin: clamp(28px, 4vw, 44px) auto 0; }
.keyed > div { display: grid; grid-template-columns: 168px minmax(0, 1fr); gap: 20px; padding: 15px 0; border-top: 1px solid var(--rule); }
.keyed > div:last-child { border-bottom: 1px solid var(--rule); }
.keyed .k { font-family: var(--mono); font-size: 12px; letter-spacing: .06em; color: var(--house); padding-top: .16em; }
.keyed p { font-size: 15.5px; line-height: 1.6; color: var(--muted); margin: 0; }
.keyed p b { color: var(--ink); font-weight: 600; }
@media (max-width: 620px) { .keyed > div { grid-template-columns: 1fr; gap: 6px; } }

/* the losses, as a plain centred list ------------------------------------- */

.losses { max-width: 62ch; margin: clamp(30px, 4vw, 52px) auto 0; display: grid; gap: 16px; }
.losses li { list-style: none; font-size: 16px; line-height: 1.58; color: var(--muted); padding-left: 22px; position: relative; }
.losses li::before { content: ""; position: absolute; left: 0; top: .66em; width: 10px; height: 1px; background: var(--danger); }
.losses li b { color: var(--ink); font-weight: 600; }
.losses.plain li::before { background: var(--house); }

/* The concession is the most important sentence on the page. A box made it
   look like an aside, so it gets a band of its own and no enclosure. */
.note {
  max-width: 58ch; margin: clamp(44px, 6vw, 78px) auto 0; text-align: center;
  font-size: clamp(17px, 1.7vw, 20px); line-height: 1.56; color: var(--muted);
}
.note b { color: var(--ink); font-weight: 600; }

/* ------------------------------------------------------------- closing --- */

.closing { position: relative; overflow: hidden; text-align: center; }
.closing .plate { position: absolute; inset: 0; }
.closing .plate img { width: 100%; height: 100%; object-fit: cover; filter: brightness(.42) saturate(.85); }
.closing .plate::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, var(--deep), color-mix(in srgb, var(--deep) 52%, transparent) 42%, var(--deep));
}
.closing .wrap { position: relative; z-index: 1; }
.closing .install { justify-content: center; }

footer { border-top: 1px solid var(--rule); padding: 28px 0; }
.footrow { display: flex; flex-wrap: wrap; gap: 16px 30px; align-items: center; justify-content: space-between; }
.foot-identity { display: flex; align-items: center; gap: 18px; }
.footrow .brand { display: inline-flex; align-items: center; gap: 9px; text-decoration: none; }
.footrow .brand .mark { height: 18px; }
.footrow .brand b { font-size: 14px; color: var(--ink); }
.footrow p { margin: 0; }
.footrow p, .footrow a, .footrow .links { font-size: 13px; line-height: 1.5; color: var(--faint); }
.footrow .links { display: flex; align-items: center; gap: 4px 20px; flex-wrap: wrap; }
.footrow .links a, .footrow .consent-settings {
  display: inline-flex; align-items: center; min-height: 36px; padding: 0;
  font: inherit; color: inherit; text-decoration: none; white-space: nowrap;
}
.footrow .links a:hover, .footrow .consent-settings:hover { color: var(--house); text-decoration: underline; }
@media (max-width: 600px) {
  .footrow { align-items: flex-start; gap: 12px; }
  .footrow .links { width: 100%; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 20px; }
  .footrow .links a, .footrow .consent-settings { min-height: 40px; }
}

/* base.css centres .install as an inline-flex row; keep it from stretching
   the centred columns it now lives in. */
.install { max-width: 100%; }
.install code { min-width: 0; }

/* ==========================================================================
   The figures. Drawn by js/figures.js, styled here so every colour still
   comes from tokens.css and a theme change moves the drawings with it.
   ========================================================================== */

svg.fig { display: block; width: 100%; height: auto; font-family: var(--mono); }
svg.fig text { font-family: var(--mono); }

/* The matrix is the one figure carrying its own reading, not just a shape --
   seven row labels and five column headers, all real words. `width: 100%`
   alone means those shrink with the viewBox on every viewport, so a phone
   scaled the whole 1000-unit drawing to its own width and rendered 11.5px
   labels are under 5px. Same fix as a terminal capture running out of room:
   a floor, and the excess scrolls -- `#fig-matrix` is the scroller so the
   caption below stays put while the table itself pans. */
#fig-matrix { overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: thin; scrollbar-color: var(--rule) transparent; }
#fig-matrix svg.mx { min-width: 640px; }
.mx-hint { display: none; font-family: var(--mono); font-size: 11px; color: var(--faint); text-align: center; margin: 8px 0 0; }
@media (max-width: 720px) { .mx-hint { display: block; } }
.f-box     { fill: var(--sunken); stroke: var(--rule); stroke-width: 1.4; }
.f-box.bad { stroke: var(--danger); stroke-opacity: .55; }
.f-chipbox { fill: var(--ground); stroke: var(--rule); stroke-width: 1; }
.f-plate, .f-gate { fill: color-mix(in srgb, var(--house) 8%, var(--sunken)); stroke: var(--house); stroke-width: 1.5; }
.f-hair    { stroke: var(--rule); stroke-width: 1; opacity: .6; }
.f-arrow   { stroke: var(--rule); stroke-width: 1.2; fill: none; }
.f-head    { fill: var(--rule); }
.f-eyebrow { font-size: 10.5px; letter-spacing: .2em; fill: var(--faint); }
.f-eyebrow.hot { fill: var(--house); }
.f-lab     { fill: var(--muted); font-size: 13px; }
.f-key     { fill: var(--house); font-size: 13px; letter-spacing: .1em; }
.f-small   { fill: var(--faint); font-size: 11.5px; }
.f-small.ink { fill: var(--ink); }
.f-small.mid { fill: var(--muted); }
.f-warn    { fill: var(--danger); font-size: 12px; }
.f-tgt     { fill: var(--danger); }
.f-count   { font-size: 12px; fill: var(--faint); font-variant-numeric: tabular-nums; }
.f-count.ok  { fill: var(--ok); }
.f-count.bad { fill: var(--danger); }
.f-tally   { font-size: 12px; }
.f-tally .sep  { fill: var(--rule); }
.f-tally .good { fill: var(--ok); }
.f-tally .bad  { fill: var(--danger); }
.f-rib { opacity: .34; }
.f-rib.r0 { fill: var(--accent); } .f-rib.r1 { fill: var(--muted); }
.f-rib.r2 { fill: var(--ok); }     .f-rib.out { fill: var(--house); opacity: .26; }
.ghost .f-box { fill: var(--sunken); }
.ghost text   { fill: var(--faint); }
.pkt { opacity: .9; }
.pkt.p0 { fill: var(--accent); } .pkt.p1 { fill: var(--muted); }
.pkt.p2 { fill: var(--ok); }     .pkt.p3 { fill: var(--house); }

.mx-col  { fill: var(--faint); font-size: 11.5px; letter-spacing: .1em; }
.mx-col.self { fill: var(--house); }
.mx-row  { fill: var(--muted); font-size: 12.5px; }
.mx-row.loss { fill: var(--danger); }
.mx-o    { fill: none; stroke: var(--rule); stroke-width: 1.4; }
.mx-o.self { stroke: var(--house); } .mx-o.them { stroke: var(--muted); }
.mx-o.loss { stroke: var(--danger); }
.mx-f    { fill: var(--muted); }
.mx-f.self { fill: var(--house); } .mx-f.loss { fill: var(--danger); }
.mx-spine { stroke: var(--house); opacity: .4; }
.mx-cell { transition: opacity .3s; }
.mx-cell.dim { opacity: .2 !important; }
.mx-read { fill: var(--faint); font-size: 12px; }
.mx-read.on   { fill: var(--muted); }
.mx-read.loss { fill: var(--danger); }
.mx-note { fill: var(--rule); font-size: 11px; }

/* the agents rail ---------------------------------------------------------
   A call sheet, not a row of chips. The daemon's own rail is a list -- names
   ragged left, states flush right in one column, a `>` on the row you are on
   (public/data/frames.json, `c_agents`) -- so this figure draws that object
   rather than a webpage component wearing its name. Two consequences worth
   keeping: the state changes at the x your eye is already on, and no row ever
   moves, which is the sentence the rest of the page is built on.

   The state column is set in the grid font because `WAIT`, `done`, `idle` and
   the braille spinner are the daemon's words, and most system monos have no
   braille at all -- the fallback would tofu or come back 22% too wide. */

.rail-sheet {
  font-family: "butai-grid", var(--mono); font-size: 13px;
  border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  padding: 5px 0;
}
.rail-row {
  display: grid; grid-template-columns: 14px minmax(0, auto) 1fr auto;
  align-items: center; gap: 10px; padding: 7px 10px; border-radius: 2px;
  color: var(--muted); transition: background .3s var(--ease), color .3s;
}
.rail-row .gut { color: var(--accent); }
.rail-row b { color: var(--ink); font-weight: 500; }
.rail-row .ldr {
  border-bottom: 1px dotted color-mix(in srgb, var(--rule) 75%, transparent);
  align-self: end; margin-bottom: 6px; min-width: 12px;
}
.rail-row u {
  text-decoration: none; text-align: right; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.rail-row.sel { background: var(--surface); }
.rail-row.work u { color: var(--house); }
.rail-row.done u { color: var(--cyan); }
.rail-row.ok u   { color: var(--faint); }
.rail-row.ok  b  { color: var(--muted); font-weight: 400; }
.rail-row.wait u { color: var(--danger); }
.rail-row.wait   { background: color-mix(in srgb, var(--danger) 10%, transparent); }
.rail-cap { font-family: var(--mono); font-size: 12px; color: var(--faint); margin: 16px 0 0; text-align: center; }
.rail-cap b { color: var(--house); font-weight: 400; }
