:root {
  --bg: #0d0f12;
  --fg: #e8eaed;
  --dim: #9aa0a6;
  --line: #23262b;
  --pass: #3ea76a;
  --fail: #d1495b;
  --slate: #0b3d91; /* the blue. it is a decision, not an error. */
}
* { box-sizing: border-box; }
/*
 * Monospace, on every surface, and it is an argument rather than a style.
 *
 * The register this project belongs to is the one it keeps naming: public
 * access television, a patch panel, the FCC in 1934 and 1972. That is the
 * typography of an instrument, and a system typeface says software-as-a-service
 * instead. Both technical reference points here set their whole body in mono
 * for the same reason.
 *
 * Applied to `body` rather than to the site alone, deliberately. One product
 * has one voice, and the operator surfaces are the ones that most look like
 * equipment: a director reading fates and a publisher reading a ruling are
 * looking at instrument output, not at a web page.
 *
 * The line height goes UP with the change. Monospace sets wider per character
 * and reads worse at prose measure, so the standard page (fifteen clauses of
 * running text) is the surface that pays for this, and `main` gains a max-width
 * for the same reason: a mono line of unbounded length is the version of this
 * that would have to be reverted.
 */
body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 15px/1.6 ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}
header {
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
  display: flex;
  gap: 16px;
  align-items: center;
}
header h1 { font-size: 15px; margin: 0; letter-spacing: .06em; text-transform: uppercase; }
header .sub { color: var(--dim); font-size: 13px; }
main { padding: 16px; }
button {
  font: inherit; padding: 10px 16px; border-radius: 8px; border: 1px solid var(--line);
  background: #1b1e23; color: var(--fg); cursor: pointer;
}
button:disabled { opacity: .45; cursor: default; }
input {
  font: inherit; padding: 10px 12px; border-radius: 8px;
  border: 1px solid var(--line); background: #15181c; color: var(--fg); width: 100%;
}
label { display: block; margin: 12px 0 4px; color: var(--dim); font-size: 13px; }

/* --- viewer ------------------------------------------------------------- */
.stage { position: relative; width: 100%; aspect-ratio: 16/9; background: #000; overflow: hidden; }
.stage video { width: 100%; height: 100%; object-fit: contain; display: block; }
/*
 * Four states, not two. `live` is the only one that shows the BROADCAST, and it
 * is now the only one the page can reach by having seen a frame. Wave 5, D1.
 *
 * Scoped to `#air` on 2026-08-29. As `.stage video` it also hid the publisher's
 * own camera, so anyone publishing while the slate was up lost the one picture
 * they can actually act on: their own framing, which is what the whole
 * two-picture layout exists to show them.
 *
 * Scoped to `video#air` on 2026-08-30, because this stylesheet serves TWO
 * origins and the site is the other one. The public site reuses this file and
 * `profile-page.ts`'s shell, and its hero carries the same id on a DIV:
 * `<div class="site-slate" id="air">`. A site page has no `data-state`, so this
 * selector matched every time and the home page and `/watch` rendered their blue
 * slate at `display: none`. Measured on the deployed site: computed `display`
 * none, height 0, on both pages. The one element that carries this project's
 * entire argument was invisible, on the site whose first heading is "The slate
 * is the product", because a rule written for the viewer reached a surface it
 * was never about.
 *
 * The type selector is the fix rather than a new id: `#air` is a `<video>` here
 * (`web/index.html`) and a `<div>` there, so naming the element says what the
 * rule is for and cannot reach the other surface however either page is edited.
 * A shared stylesheet with an unscoped id selector is a second copy of a
 * decision, and this repository has been bitten by that shape before.
 */
body:not([data-state="live"]) video#air { display: none; }
#slate {
  position: absolute; inset: 0; display: none;
  background: var(--slate); color: #fff;
  flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 24px;
}
body[data-state="slate"] #slate { display: flex; }
body[data-state="wait"] #slate { display: flex; }

/*
 * Deliberately NOT var(--slate). The blue is a broadcast decision and this is
 * an instrument failure; giving them the same colour would merge the two things
 * this page exists to keep apart.
 */
#dark {
  position: absolute; inset: 0; display: none;
  background: #17191d; color: var(--fg);
  flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding: 24px; border: 1px dashed var(--dim);
}
body[data-state="dark"] #dark { display: flex; }
body[data-state="connecting"] #dark { display: flex; }
body[data-state="connecting"] #dark .mark::after { content: ' ...'; }
body[data-state="connecting"] #dark .why { visibility: hidden; }
#dark .mark { font-size: clamp(16px, 3vw, 26px); font-weight: 600; letter-spacing: .03em; color: var(--dim); }
#dark .why { margin-top: 10px; color: var(--dim); font-size: clamp(13px, 1.6vw, 16px); max-width: 34em; }
#slate .mark { font-size: clamp(20px, 4vw, 40px); font-weight: 600; letter-spacing: .04em; }
#slate .why { margin-top: 10px; opacity: .85; font-size: clamp(13px, 1.6vw, 17px); }
#slate .cta {
  margin-top: 28px; padding: 12px 20px; border: 1px solid rgba(255,255,255,.5);
  border-radius: 8px; color: #fff; text-decoration: none;
}
.footer { padding: 10px 16px; color: var(--dim); font-size: 13px; display: flex; gap: 16px; flex-wrap: wrap; }
.footer #delay { margin-left: auto; }

/* --- publisher ---------------------------------------------------------- */
#preview { width: 100%; border-radius: 10px; background: #000; aspect-ratio: 16/9; object-fit: cover; }
.verdict { margin: 14px 0 6px; padding: 12px; border-radius: 8px; font-weight: 600; text-align: center; }
.verdict.pass { background: rgba(62,167,106,.15); color: var(--pass); border: 1px solid var(--pass); }
.verdict.fail { background: rgba(209,73,91,.12); color: var(--fail); border: 1px solid var(--fail); }
/* Not a ruling. The page cannot reach the switchboard, so it has nothing to
   report either way, and must not keep showing the last thing it heard. */
.verdict.unknown { background: rgba(154,160,166,.12); color: var(--dim); border: 1px dashed var(--dim); }
.offline { background: rgba(154,160,166,.12); color: var(--dim); border-bottom: 1px dashed var(--dim); padding: 8px 12px; text-align: center; }
body[data-onair="true"] .verdict { background: var(--fail); color: #fff; }
/* The camera picker and the line that says which one is live. D10. */
.camera-row { display: flex; align-items: center; gap: 10px; margin: 12px 0 4px; }
.camera-row label { margin: 0; white-space: nowrap; }
.camera-row select {
  font: inherit; flex: 1; min-width: 0; padding: 8px 10px; border-radius: 8px;
  border: 1px solid var(--line); background: #15181c; color: var(--fg);
}
#camera-active { color: var(--dim); font-size: 13px; }

#reasons { margin: 0; padding-left: 20px; color: var(--dim); font-size: 13px; }
#status { color: var(--dim); font-size: 13px; margin-top: 10px; }

/* --- the three artifacts a publisher receives ----------------------------- */
/*
 * Different LEFT RULE, different label colour, different weight. The acceptance
 * criterion is that ruling and coaching are separable "without reading them",
 * so the separation has to survive being skimmed: a coach's suggestion set in
 * the same weight as a rubric ruling teaches that advice is binding and rulings
 * are negotiable, and both lessons are wrong.
 */
#feedback { margin-top: 18px; border-top: 1px solid var(--line); padding-top: 12px; }
.fb-title { font-size: 13px; color: var(--dim); text-transform: uppercase; letter-spacing: .08em; margin: 0 0 10px; }
.artifact { border-left: 3px solid var(--line); padding: 8px 0 8px 12px; margin: 0 0 14px; }
.artifact h3 { margin: 0 0 6px; font-size: 14px; }
.artifact ul { margin: 0; padding-left: 0; list-style: none; font-size: 13px; }
.artifact li { display: flex; gap: 8px; flex-wrap: wrap; padding: 2px 0; }

.artifact.ruling { border-left-color: var(--fail); }
.artifact.ruling h3 { color: var(--fg); }
.gate-name { color: var(--dim); min-width: 84px; text-transform: uppercase; font-size: 11px; letter-spacing: .06em; padding-top: 2px; }
.gate-line { flex: 1; min-width: 180px; }
.gate-ok .gate-line { color: var(--pass); }
.gate-no .gate-line { color: var(--fail); }
.gate-by { color: var(--dim); font-size: 11px; }
.appeal { color: var(--dim); font-size: 12px; margin: 8px 0 0; }

.artifact.fate { border-left-color: var(--slate); }
.fate-line { margin: 0; font-size: 13px; }
.fate-context { color: var(--dim); font-size: 12px; margin: 6px 0 0; }

/* Deliberately the quietest of the three. It is advice, not a ruling. */
.artifact.coaching { border-left-color: var(--line); }
.artifact.coaching h3 { color: var(--dim); font-size: 12px; text-transform: uppercase; letter-spacing: .06em; }
.coach-quiet { color: var(--dim); font-size: 12px; font-style: italic; margin: 0; }
.coach-act, .coach-hold { margin: 0; font-size: 13px; color: var(--fg); }
.coach-about { color: var(--dim); font-size: 11px; }

/* --- the director's per-tile record and editorial calls ------------------- */
/*
 * `good` and `bad` are the editor's own judgment and are deliberately quiet:
 * they are not the Take button, they commit nothing to air, and there is no
 * control anywhere on this page that accepts the machine's proposal in bulk.
 * Proposal 11.3 keeps the editorial agent off the commit path, and a UI control
 * is a commit path, so the absence is the feature.
 */
.tile-controls { display: flex; gap: 6px; align-items: center; }
.tile-controls button { flex: 0 0 auto; }
.tile-controls button:first-child { flex: 1; }
.label-good, .label-bad { font-size: 12px; padding: 6px 10px; opacity: .8; }
.label-good:hover { border-color: var(--pass); color: var(--pass); opacity: 1; }
.label-bad:hover { border-color: var(--fail); color: var(--fail); opacity: 1; }
.fate { color: var(--dim); font-size: 11px; font-style: italic; }

label.inline { display: flex; align-items: center; gap: 8px; margin: 12px 0 4px; }
label.inline input { width: auto; }

/* --- director ----------------------------------------------------------- */
.onair { padding: 10px 16px; font-weight: 600; letter-spacing: .04em; }
.onair.live { background: var(--fail); color: #fff; }
.onair.slate { background: var(--slate); color: #fff; }
.controls { display: flex; gap: 14px; align-items: center; padding: 12px 16px; border-bottom: 1px solid var(--line); }
#grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 14px; }
.tile { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: #14171b; }
.tile.live { border-color: var(--fail); box-shadow: 0 0 0 2px rgba(209,73,91,.35); }
.tile.failing { opacity: .6; }
.tile video { width: 100%; aspect-ratio: 16/9; background: #000; object-fit: cover; display: block; }
.tile .meta { padding: 10px; display: grid; gap: 3px; font-size: 13px; }
.tile .op, .tile .bits { color: var(--dim); }
.tile .num { font-weight: 700; }
.tile .fails { color: var(--fail); font-size: 12px; }
.tile button { width: calc(100% - 20px); margin: 0 10px 10px; }

/* --- audience rating ----------------------------------------------------- */
.rate { display: flex; gap: 10px; align-items: center; padding: 12px 16px; flex-wrap: wrap; }
.rate .ask { color: var(--dim); font-size: 13px; }
.rate button { padding: 8px 18px; }
.rate button.chosen { border-color: var(--fg); }
.rate .tally { color: var(--dim); font-size: 12px; margin-left: auto; }
/* Nothing to rate unless a picture is actually on this screen. */
body:not([data-state="live"]) .rate { display: none; }

/* --- delay audit (director only) ----------------------------------------- */
.audit { padding: 8px 16px; font-size: 13px; border-bottom: 1px solid var(--line); }
.audit.ok { color: var(--dim); }
.audit.bad { background: var(--fail); color: #fff; font-weight: 600; }
.tile .gates { color: var(--dim); font-size: 11px; font-family: ui-monospace, monospace; }

/* --- publisher feedback --------------------------------------------------- */
#reasons { list-style: none; padding: 0; margin: 12px 0 0; }
#reasons li { padding: 10px 12px; border-radius: 8px; background: #15181c; margin-bottom: 8px; }
#reasons li.ok { background: rgba(62,167,106,.12); color: var(--pass); }
#reasons .why { display: block; color: var(--fail); font-size: 13px; }
#reasons .fix { display: block; color: var(--fg); font-size: 14px; margin-top: 4px; }
#reasons li.observed { background: #14181f; border-left: 3px solid var(--dim); }
#reasons li.observed .why { color: var(--dim); }

/* --- the merged public page (wave 5, call 2) --------------------------------
 *
 * Full bleed. Nothing here reserves a band of the screen except the video, and
 * every control floats over it inside the safe area, because the publisher's
 * form put its primary action seventh in a vertical stack and that action fell
 * below the fold on any window under 600px.
 *
 * Measured before this replaced it, at 375 wide: Go live sat at 635px, the
 * "locking the phone ends your feed" warning at 664px, and a real phone loses
 * 110-150px of an 812px screen to browser chrome.
 */
body[data-state] .stage {
  position: fixed; inset: 0; width: 100%; height: 100dvh;
  aspect-ratio: auto; background: #000; overflow: hidden;
}
/*
 * The swap, which is the whole point of two pictures on one screen.
 *
 * This was a placeholder comment in an empty rule until 2026-08-29: `data-main`
 * flipped, `mainView()` returned the right string, all 25 of its assertions
 * passed, and NOTHING MOVED. The decision was tested and the rendering was not,
 * which is issue 010 exactly. Measured in a browser: both rects identical
 * before and after the state changed.
 *
 * Driven by class, not by DOM position: the two videos are siblings and stay
 * put. Reparenting a <video> can interrupt playback, and a swap that costs a
 * frame is one nobody uses twice.
 */
/*
 * The swap trades two PANES, not two videos.
 *
 * Trading only the <video> elements shipped on 2026-08-29 and looked correct in
 * a computed-style check: `#air` and `#me` did exchange sizes. What it missed is
 * that the slate is not an overlay on the page, it is what the broadcast
 * currently IS. It stayed at `inset: 0; z-index: 1` and painted straight over a
 * publisher's own camera whenever the broadcast was slated, which is the state a
 * publisher spends most of their time in.
 *
 * So the broadcast's picture and both of its stand-ins live in one pane and move
 * together. Reported by an operator looking at a blue screen where their camera
 * should have been, after a test of mine said the swap worked.
 */
.pane { position: absolute; display: block; background: #000; overflow: hidden; }
#broadcast > video, #broadcast > #slate, #broadcast > #dark {
  position: absolute; inset: 0; width: 100%; height: 100%;
}
#air, #me { object-fit: cover; }

/* The large pane, whichever it currently is. */
body[data-main="broadcast"] #broadcast,
body[data-main="camera"] #me {
  inset: 0; width: 100%; height: 100%; border-radius: 0; z-index: 0; box-shadow: none;
}

/* The small one, in the thumb-reachable corner. */
body[data-main="broadcast"] #me,
body[data-main="camera"] #broadcast {
  left: auto; top: auto;
  right: calc(12px + env(safe-area-inset-right));
  bottom: calc(184px + env(safe-area-inset-bottom));
  width: 108px; height: 152px; border-radius: 14px; z-index: 4;
  box-shadow: 0 6px 24px rgba(0,0,0,.55);
}
/* The slate's type has to shrink with its pane, or it overflows the tile. */
body[data-main="camera"] #slate .mark, body[data-main="camera"] #dark .mark { font-size: 11px; }
body[data-main="camera"] #slate .why, body[data-main="camera"] #dark .why { display: none; }

/* Not publishing: there is no second pane, so the broadcast is simply the page. */
body[data-publish="watching"] #me { display: none; }
body[data-publish="watching"] #broadcast {
  inset: 0; width: 100%; height: 100%; border-radius: 0; z-index: 0; box-shadow: none;
}

/* The tap target, exactly over the small picture. */
#tile {
  position: absolute;
  right: calc(12px + env(safe-area-inset-right));
  bottom: calc(184px + env(safe-area-inset-bottom));
  width: 108px; height: 152px; border-radius: 14px; overflow: hidden;
  z-index: 5; cursor: pointer;
}

.tile-swap {
  position: absolute; inset: auto 0 0 0; border: 0; border-radius: 0;
  background: rgba(0,0,0,.45); color: #fff; font-size: 11px; padding: 4px 0;
}

/*
 * Scrims, not glass. §7.1 correction 1: a live backdrop-filter over video on a
 * phone that is simultaneously encoding WebRTC degrades fps and thermalHint,
 * which are gate 0 metrics, so the interface would be the reason the feed was
 * refused. Gate G6.
 */
.scrim { position: absolute; left: 0; right: 0; pointer-events: none; z-index: 2; }
.scrim-top { top: 0; height: 140px; background: linear-gradient(to bottom, rgba(0,0,0,.62), transparent); }
.scrim-bottom { bottom: 0; height: 300px; background: linear-gradient(to top, rgba(0,0,0,.72), transparent); }

.pill {
  position: absolute; z-index: 3;
  top: calc(12px + env(safe-area-inset-top)); left: calc(14px + env(safe-area-inset-left));
  font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: #fff;
}
.credit {
  position: absolute; z-index: 3;
  left: calc(14px + env(safe-area-inset-left)); right: 92px;
  bottom: calc(96px + env(safe-area-inset-bottom)); color: #fff;
}
#credit-line { font-size: 16px; font-weight: 600; }
.credit-sub { font-size: 13px; color: rgba(255,255,255,.72); margin-top: 2px; }
.chevron {
  margin-top: 8px; background: none; border: 0; padding: 0;
  color: rgba(255,255,255,.72); font-size: 13px; text-decoration: underline;
}

/*
 * The thumb arc. Roughly 75% of phone interactions are single-thumbed and the
 * top 40% of the screen is a dead zone, so every control anyone uses is here.
 */
.rail {
  position: absolute; z-index: 3;
  right: calc(10px + env(safe-area-inset-right));
  bottom: calc(184px + env(safe-area-inset-bottom));
  display: none; flex-direction: column; gap: 14px; align-items: center;
}
body[data-publish="watching"] .rail { display: flex; }
/*
 * §7 rule 1: every persistent element is a video, an action available NOW, or
 * the identity of what you are watching. A rating control on the slate is none
 * of those: there is no picture to rate, and a permanently disabled button is
 * clutter that teaches people the control is broken.
 */
body:not([data-state="live"]) #rate-good,
body:not([data-state="live"]) #rate-bad { display: none; }
.rail-btn {
  background: none; border: 0; padding: 0; color: #fff;
  display: flex; flex-direction: column; align-items: center; gap: 2px; min-width: 56px;
}
.rail-btn:disabled { opacity: .35; }
.rail-btn .glyph { font-size: 22px; line-height: 1; }
.rail-label { font-size: 11px; letter-spacing: .04em; }
.rail-btn.chosen { color: #fff; text-shadow: 0 0 10px rgba(255,255,255,.9); }

/* Exactly one high-contrast control per state. Everything else is a ghost. */
.primary {
  position: absolute; z-index: 3;
  left: calc(16px + env(safe-area-inset-left)); right: calc(16px + env(safe-area-inset-right));
  bottom: calc(28px + env(safe-area-inset-bottom));
  padding: 16px; border: 0; border-radius: 999px;
  background: #fff; color: #101215; font-size: 16px; font-weight: 600;
}
.primary[data-kind="stop"] { background: var(--fail); color: #fff; }
.note {
  position: absolute; z-index: 3;
  left: calc(16px + env(safe-area-inset-left)); right: calc(16px + env(safe-area-inset-right));
  bottom: calc(4px + env(safe-area-inset-bottom));
  color: rgba(255,255,255,.72); font-size: 12px; text-align: center; min-height: 16px;
}

/* Slate and dark fill the whole stage now, not a 16:9 letterbox in a page. */
#slate, #dark { z-index: 1; }
#slate .mark { font-size: clamp(28px, 9vw, 64px); }

/*
 * The sheet: a partial-height overlay on a broadcast that keeps playing, which
 * is the comments-sheet idiom rather than the feed swipe. Scrolling away from a
 * live stream means leaving the broadcast.
 */
.sheet {
  position: fixed; z-index: 5; left: 0; right: 0; bottom: 0;
  max-height: 62dvh; overflow-y: auto;
  background: #0f1114; border-top: 1px solid var(--line);
  border-radius: 16px 16px 0 0; padding: 16px 16px calc(24px + env(safe-area-inset-bottom));
}
.sheet-close { float: right; background: none; border: 0; color: var(--dim); font-size: 13px; }
.sheet-title { font-size: 12px; text-transform: uppercase; letter-spacing: .08em; color: var(--dim); margin: 0 0 8px; }
.sheet-line { margin: 0 0 6px; font-size: 14px; }
/* The account links in every header. Pushed right so the page's own title keeps
   the position a reader looks at first. */
header nav { margin-left: auto; display: flex; gap: 14px; align-items: center; font-size: 13px; }
header nav a { color: var(--dim); text-decoration: none; border-bottom: 1px solid transparent; }
header nav a:hover { color: var(--fg); border-bottom-color: var(--dim); }
header nav .nav-you { color: var(--dim); }

/* The account page: a form, which nothing else here is. */
/* `label`, `label.inline` and the base `input` rule are all defined above and
   already correct: a second copy of a rule is how two files disagreed about one
   number three times in this repository. The account form reuses them. */
label.inline { margin-top: 14px; color: var(--fg); }
.row { display: flex; gap: 10px; margin-top: 16px; }
button.secondary { background: transparent; border: 1px solid var(--line); color: var(--dim); }
button.secondary:hover { color: var(--fg); }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 13px; }
th { text-align: left; color: var(--dim); font-weight: 500; padding: 6px 8px 6px 0; }
td { padding: 6px 8px 6px 0; border-top: 1px solid var(--line); }

/* The account form. Narrow because a full-width login on a monitor reads as a
   page that lost its content. */
.card.narrow { max-width: 460px; }
.card h2 { margin: 0 0 6px; font-size: 16px; }
.small { font-size: 12px; }
button.wide { width: 100%; margin-top: 16px; padding: 10px; }
.switch { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--line); font-size: 13px; }
.switch a { color: var(--fg); }
.msg { min-height: 18px; margin: 10px 0 0; font-size: 13px; color: var(--dim); }
.msg.bad { color: var(--fail); }

/* The account link on the public page. Mirrors the state pill on the other
   side, inside the same safe area, and reserves no band of its own. */
.account-corner {
  position: absolute; z-index: 3;
  top: calc(12px + env(safe-area-inset-top)); right: calc(14px + env(safe-area-inset-right));
  display: flex; gap: 12px; align-items: center; font-size: 12px;
}
.account-corner a { color: rgba(255,255,255,.72); text-decoration: none; }
.account-corner a:hover { color: #fff; }
.account-corner .nav-you { color: rgba(255,255,255,.55); }

/* --- the account nav, at phone width -----------------------------------------
   Written desktop-first and it showed: at 375px the header nav wrapped the
   handle onto three lines, clipped "settings" and pushed "sign out" off the
   screen entirely, and on the broadcast page it collided with the state pill.

   A phone is the publisher's context, so this is the case that matters most.
   The handle is the first thing to go: it is identity rather than navigation,
   and the person holding the phone already knows who they are. */
@media (max-width: 620px) {
  header { flex-wrap: wrap; row-gap: 4px; }
  header nav { gap: 10px; font-size: 12px; }
  header nav .nav-handle { display: none; }
}

/* On the broadcast page the nav shares the top edge with the state pill, which
   is the one label that must never be crowded: it is how a viewer knows whether
   anything is on air. Below 620px the account links collapse to one word. */
@media (max-width: 620px) {
  .account-corner { gap: 10px; font-size: 11px; }
  .account-corner .nav-handle, .account-corner .nav-you { display: none; }
}

/* Links inside a card. Without this they render as the browser default, which
   on a dark page is an unreadable blue: "View your profile" on the account form
   was the one link in the product that looked broken. */
main a { color: var(--fg); text-decoration: none; border-bottom: 1px solid var(--dim); }
main a:hover { border-bottom-color: var(--fg); }

/* An input with a control inside it, for show/hide password. */
.with-affix { position: relative; display: flex; align-items: center; }
.with-affix input { padding-right: 72px; }
.affix {
  position: absolute; right: 6px; padding: 6px 10px; font-size: 12px;
  background: transparent; border: 1px solid var(--line); color: var(--dim);
  border-radius: 6px; cursor: pointer;
}
.affix:hover { color: var(--fg); }

/* --- the public site's slate (the public-site plan §9.2) --------------------
 *
 * The point of `/` is that it renders a REAL slate rather than describing one,
 * and the first build rendered `slateCopy()`'s words with no rule behind them,
 * which is a description. It uses `--slate` and the same type scale as the
 * broadcast's, because a slate that does not look like the slate is a picture
 * of one.
 */
.site-slate {
  background: var(--slate);
  color: #fff;
  border-radius: 10px;
  padding: 48px 24px;
  margin: 0 0 28px;
  text-align: center;
}
/*
 * A live stream is not a slate. The blue IS the argument this page makes, so it
 * must not sit behind "SOMEONE IS STREAMING NOW": added by `evidence.js` only
 * when a node says a feed is on air, and never by the static build, which
 * cannot know.
 */
.site-slate.live { background: var(--pass); color: #06210f; }
.site-slate .mark { font-size: clamp(22px, 5vw, 44px); font-weight: 600; letter-spacing: .04em; }
.site-slate .why { margin-top: 10px; opacity: .85; font-size: clamp(13px, 1.6vw, 17px); }

/*
 * The link from a refusal to the rule behind it, on the two surfaces that
 * refuse: the slate and the publisher's ruling. Quiet on purpose. It is a way
 * out for somebody who wants one, not a call to action.
 */
.slate-standard { display: inline-block; margin-top: 12px; color: #fff; opacity: .85; font-size: 14px; }
.ruling-standard { display: inline-block; margin-top: 8px; font-size: 14px; }
.appeal { margin-top: 10px; font-size: 14px; color: var(--dim); }
 * The answer capsule, which is the first thing in `<main>` on every site page.
 *
 * It is styled as the lead rather than as a note: it is the paragraph most
 * likely to be quoted somewhere this page has no control over, so a reader who
 * reads one thing should read this one. The rule is deliberately quiet
 * otherwise, because a capsule that looks like a callout box reads as marketing
 * and this one is meant to read as the answer.
 */
.capsule {
  font-size: clamp(16px, 2vw, 19px);
  line-height: 1.55;
  max-width: 62ch;
  margin: 0 0 24px;
  padding-left: 14px;
  border-left: 2px solid var(--dim);
}

/* --- the public site --------------------------------------------------------
 *
 * Everything below is the site's, and the site is the only surface that carries
 * running prose. `body` sets monospace for every surface; these rules give the
 * long-form pages a measure a mono line can be read at.
 */

/*
 * A measure, because monospace without one is the version that gets reverted.
 * `ch` rather than `em`: the unit is the character advance, which is exactly
 * what a monospace face makes constant, so this is ~74 characters on every
 * screen and every zoom level rather than a width that drifts with the font.
 *
 * Scoped to `main.prose` so it cannot reach the three live surfaces, whose
 * layouts are full-bleed video and control panels. Same discipline as the
 * `video#air` fix: one stylesheet, two origins, and a rule that names its
 * subject cannot wander into the other one.
 */
main.prose { max-width: 74ch; margin: 0 auto; }
main.prose h1 { font-size: clamp(26px, 4.5vw, 38px); letter-spacing: -.01em; margin: 28px 0 12px; }
main.prose h2 { font-size: clamp(17px, 2.4vw, 21px); margin: 34px 0 10px; letter-spacing: .01em; }
main.prose h3 { font-size: 15px; margin: 0 0 8px; letter-spacing: .04em; text-transform: uppercase; }

/*
 * The reviewer's own words, set apart from the page's.
 *
 * This is the one place on the site where something a model said is reproduced
 * verbatim, so it is marked as a quotation rather than blended into the prose:
 * a reader has to be able to see where our account stops and the instrument's
 * begins. The rule is on the slate blue, which is the colour every ruling here
 * ultimately resolves to.
 */
main.prose blockquote {
  margin: 14px 0; padding: 12px 16px;
  border-left: 3px solid var(--slate);
  background: #15181c; color: var(--fg);
}

/*
 * The receipts: measurements taken on a date, never live state.
 *
 * A `<dl>` because that is what this is, a list of values and what each one
 * means, and the value is the term. `tabular-nums` so the figures line up as a
 * column of measurements rather than as decoration.
 */
.receipts { margin: 14px 0 0; padding: 0; }
.receipts > div {
  display: grid; grid-template-columns: minmax(7.5em, auto) 1fr; gap: 4px 18px;
  padding: 12px 0; border-top: 1px solid var(--line); align-items: baseline;
}
.receipts dt {
  font-size: clamp(19px, 2.6vw, 24px); font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--fg);
}
.receipts dd { margin: 0; color: var(--dim); font-size: 13px; line-height: 1.55; }
@media (max-width: 33em) { .receipts > div { grid-template-columns: 1fr; } }

/*
 * The three doors. One column on a phone, because a door that has to be scrolled
 * past sideways is a door nobody opens.
 */
.doors { display: grid; gap: 18px; margin-top: 14px; }
.doors section { border: 1px solid var(--line); border-radius: 10px; padding: 16px 18px; }
.doors h3 { color: var(--dim); }
.doors p { margin: 0 0 10px; font-size: 14px; }
.doors p:last-child { margin-bottom: 0; }
@media (min-width: 62em) { .doors { grid-template-columns: repeat(3, 1fr); } }

/*
 * The one primary action on a page, which until 2026-08-30 was a bare underlined
 * link indistinguishable from the six references around it.
 *
 * Deliberately NOT a `<button>`: it navigates, so it is a link, and giving it a
 * button's element to get a button's appearance is the kind of thing that reads
 * fine and breaks a screen reader's expectation of what will happen. Styled from
 * the slate, because the venue is the thing the slate is about.
 */
.cta {
  display: inline-block; margin: 4px 0 2px;
  padding: 11px 20px; border-radius: 8px;
  background: var(--slate); color: #fff; text-decoration: none;
  border: 1px solid #1a56b8; font-weight: 700; letter-spacing: .02em;
}
.cta:hover { background: #0d47ab; }
.cta:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
