/* ============================================================================
   outstation.dev

   The page is laid out on a character cell, not on a pixel grid. --col is one
   advance width of the terminal font and --cell is one line; every frame,
   gutter and rule is a whole number of those. That is the whole trick: it is
   why the box-drawing in here lines up with the type instead of merely sitting
   near it, and why the thing reads as a TUI rather than as a screenshot of one.

   Fira Code advances 1200 units on a 1950 em, so one column is exactly
   0.6154em. If the font ever changes, that number changes with it.
   ========================================================================= */

@font-face {
  font-family: "Fira Code NF";
  src: url("/assets/type/firacode-nf-regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Fira Code NF";
  src: url("/assets/type/firacode-nf-bold.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}

/* ── tokens ───────────────────────────────────────────────────────────── */

:root {
  --fs: 15px;
  --lh: 1.6;
  --col: calc(var(--fs) * 0.6154);     /* one character wide: 1200/1950 upem */
  --cell: calc(var(--fs) * var(--lh)); /* one line tall       */
  --status-h: calc(var(--cell) * 1.75);

  --mono: "Fira Code NF", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Outstation Dark — the surfaces and the non-green ink are
     app/Sources/TerminalCore/TerminalTheme.swift verbatim. **The green is not**,
     since 2026-08-16: see the primary block below. */
  --ground:   #0E0E10;
  --raised:   #141417;
  --chrome:   #1C202C;
  --text:     #E4E4E7;
  --dim:      #9A9AA3;
  /* was #63636B — 3.24:1 here, and it carries real copy (.note, .fine, captions,
     pane titles), not just decoration. 4.94:1 on --ground, 4.71:1 on --raised,
     and still a clear third step below --dim at 6.91:1. */
  --faint:    #80808C;
  --line:     #26262C;
  --line-2:   #34343C;
  --warn:     #E9C46A;
  --alert:    #FF9F0A;
  --blue:     #5AA9FF;
  --cursor:   #ECEFF4;

  /* The brand green. It is a pair, not a value: a mark colour has to stay a
     readable *shape*, and the bright green is decoration on a light ground.
     --brand-green resolves to one of the two and is the only one the *mark*
     should reference.
       #5FBB70 on #0E0E10   8.11:1      #1A7F37 on #FBFBFD  4.91:1
       #5FBB70 on #141417   7.73:1      #1A7F37 on #F4F4F7  4.63:1
       #5FBB70 on #1C202C   6.83:1      #1A7F37 on #E9E9EF  4.20:1
       #5FBB70 on #FBFBFD   2.30:1   — why the pair exists
     --green-bright was #7EE787, then #68D172 (2026-08-15), now #5FBB70
     (2026-08-16). The last move was not about contrast either: at OKLCH L 0.775
     #68D172 sat *above* Supabase's #3ECF8E (L 0.762) at nearly the same chroma,
     so 13.7° of hue could not keep the two apart and the mark read as a
     dev-tool green anyone has seen twice this week. #5FBB70 is L 0.716 — clearly
     below that band — and 17.8° from Supabase, 18.8° from Neon's #00E599.
     --green-deep moved with it. It was #1A7F37, chosen to sit 2.3° from the
     *previous* bright green; against #5FBB70 it had drifted to 6.45° and read
     cooler — a second green rather than the same one darkened. #1A7F37 is the
     same lightness (OKLCH L 0.524), 0.03° of hue away, and slightly higher
     contrast on all three light surfaces. A pair has to be re-derived whenever
     either half moves. */
  --green-bright: #5FBB70;
  --green-deep:   #1A7F37;
  --brand-green:  var(--green-bright);

  /* ── the primary ────────────────────────────────────────────────────────
     **One green on the page, and it is the brand's.** Until 2026-08-16 there
     were two: the mark drew --brand-green and everything else drew --accent
     #5CD98A, taken verbatim from TerminalTheme.swift and declared "not part of
     the brand decision". That is a defensible position about a *palette* and a
     wrong one about a *page*: the hero puts the mark and a button in the same
     viewport, so the split was visible in the one place a visitor looks first.
     --accent / --accent-b are gone; nothing here is the terminal theme's green
     any more, including the drawings of the terminal (see .term).

     Three tokens, and the scale is a lightness ramp of the same colour — OKLCH
     hue and chroma held, L stepped ±0.065, so the shades are the primary
     lighter/darker rather than a second hue. Measured drift ≤0.05° of hue.

       --primary          the brand green as the theme resolves it
       --primary-strong   one step toward emphasis — *lighter* on dark, *darker*
                          on light. Two jobs, one value: the hover/active fill,
                          and green ink that has to sit on a tint of itself
                          (code chips) or on --chrome, where plain --primary
                          does not clear 4.5:1 on light.
       --on-primary       the ink that sits *on* a primary fill

     Tints stay inline color-mix() off --primary, as they always were.

       dark   --primary #5FBB70  --primary-strong #74D083 (L 0.716 → 0.780)
       light  --primary #1A7F37  --primary-strong #006C24 (L 0.524 → 0.460)  */
  --primary:        var(--brand-green);
  --primary-strong: #74D083;
  --on-primary:     #0B120D;
}

/* Outstation Light — the same hues darkened for contrast on white.
   Ratios below are measured against --ground #FBFBFD, then --raised #F4F4F7,
   then --chrome #E9E9EF (the status bar), which is the awkward one.

     --text           16.46  15.50  14.07
     --dim             7.15   6.73   6.11
     --faint           5.49   5.17   4.69
     --primary         4.95   4.66   4.23   ← 4.23 is the known soft spot
     --primary-strong  6.51   6.13   5.57
     --alert           5.61   5.28   4.79
     --warn            4.89   4.61   4.18
     --blue            5.90   5.55   5.04
     --on-primary on --primary 5.02, on --primary-strong 6.60

   **--primary's 4.23 on --chrome is below 4.5 and stays that way, because the
   only thing painting it there is the powerline separator .st-cap, which is the
   pointed end of the .st-sess-t block and therefore has to be the same value as
   the block — a filled shape, not text, and 4.23 clears the 3:1 that a shape
   needs.** Green *text* on the bar takes --primary-strong instead; the one case
   is .st-wins a.is-on::after. Anything new that puts green copy on --chrome has
   to do the same, not inherit this number.

   --faint, --line and --line-2 were all set for a dark ground and simply carried
   over: at #8A8A93 / #DEDEE5 / #C9C9D2 they measured 3.31 / 1.30 / 1.59 here, so
   every caption failed 4.5:1 and the pane borders and diagram wires disappeared
   into the page. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --ground:   #FBFBFD;
    --raised:   #F4F4F7;
    --chrome:   #E9E9EF;
    --text:     #1C1C1E;
    --dim:      #55555D;
    --faint:    #666670;
    --line:     #BDBDCA;
    --line-2:   #9C9CAC;
    --warn:     #8A6A12;
    --alert:    #9C520A;
    --blue:     #1E5FBF;
    --cursor:   #1C1C1E;
    --brand-green:    var(--green-deep);
    --primary-strong: #006C24;
    --on-primary:     #F6FFF9;
  }
}
:root[data-theme="light"] {
  --ground:#FBFBFD; --raised:#F4F4F7; --chrome:#E9E9EF; --text:#1C1C1E;
  --dim:#55555D; --faint:#666670; --line:#BDBDCA; --line-2:#9C9CAC;
  --warn:#8A6A12; --alert:#9C520A; --blue:#1E5FBF; --cursor:#1C1C1E;
  --brand-green: var(--green-deep); --primary-strong:#006C24; --on-primary:#F6FFF9;
}

/* ── reset ────────────────────────────────────────────────────────────── */

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

html {
  font-size: clamp(13.5px, 0.28vw + 12.6px, 16px);
  background: var(--ground);
  -webkit-text-size-adjust: 100%;
}

body {
  font-family: var(--mono);
  font-size: 1rem;
  line-height: var(--cell);
  color: var(--text);
  background: var(--ground);
  font-variant-ligatures: none;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, p, ul, ol, dl, dd, figure, table { margin: 0; }
ul, ol { padding: 0; list-style: none; }
a { color: inherit; }
code, kbd, pre, input, button, table { font: inherit; }

:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

/* **Programmatic focus does not get a ring.** `tabindex="-1"` means "focusable
   by script, never by Tab", so anything matching this was focused *for* the
   user, not *by* them, and a ring around it is noise rather than orientation.
   Nothing a user can Tab to is affected — that requires tabindex 0 or a natural
   control, and every one of those keeps its :focus-visible ring above.

   Two places rely on it, for the same reason. `session.js` puts tabindex="-1"
   on the incoming window's heading and focuses it so a screen reader announces
   the change instead of the content swapping silently — that behaviour is
   load-bearing and must not be removed; only its ring was wrong, and on a page
   driven by `C-b <n>` it was drawing a green box around a heading most of the
   time. The key-table dialog does the same to land the keyboard inside itself.
   This replaces the `.ov-box:focus` one-off that only fixed the second. */
[tabindex="-1"]:focus { outline: none; }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 90;
  background: var(--primary); color: var(--on-primary);
  padding: 0 calc(var(--col) * 2); line-height: var(--status-h);
}
.skip:focus { left: 0; }

/* ── session shell ────────────────────────────────────────────────────── */

.session {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}

.viewport {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
}

/* Without JS every window is simply a section, stacked. The page is complete
   in the markup; the script only adds the session behaviour on top. */
.win { padding: calc(var(--cell) * 1.5) calc(var(--col) * 3); }
html:not(.js) .win + .win { border-top: 1px solid var(--line); }

/* Inactive windows are display:none, not merely invisible. A hidden-but-laid-out
   window still contributes to the document's scroll width, and one wide grid in
   a window you cannot see is enough to give the whole page a horizontal
   scrollbar on a phone. */
html.js .viewport { overflow: hidden; }
html.js .win { display: none; position: absolute; inset: 0; }
html.js .win.is-on { display: block; }

/* ── panes ────────────────────────────────────────────────────────────── */

/* One grid, 1px gaps, the gap colour showing through as the pane border —
   which is exactly how tmux draws it: a single shared line, never two. */
.panes {
  display: grid;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  height: 100%;
  min-height: 0;
}
.panes--h { grid-template-columns: 1fr 1fr; }
.panes--v { grid-template-rows: auto 1fr; }
.panes--hero { grid-template-columns: 1.12fr .88fr; }

.pane {
  position: relative;
  background: var(--ground);
  min-width: 0; min-height: 0;
  display: flex; flex-direction: column;
}
/* the focused pane, as tmux marks it */
.win .pane:first-child { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary) 34%, transparent); }

.pane-title {
  position: absolute; top: 0; left: calc(var(--col) * 3);
  transform: translateY(-50%);
  background: var(--ground);
  padding: 0 var(--col);
  font-size: .78rem; font-weight: 400;
  line-height: 1;
  color: var(--faint);
  letter-spacing: .06em;
  white-space: nowrap;
}
.pane-title::before { content: "─ "; color: var(--line-2); }

.pane-body {
  padding: calc(var(--cell) * 1.25) calc(var(--col) * 4);
  overflow-y: auto;
  overscroll-behavior: contain;
  flex: 1 1 auto;
  min-height: 0;
}
.pane-body > * + * { margin-top: var(--cell); }

/* ── type ─────────────────────────────────────────────────────────────── */

h2 {
  font-size: 1.5rem;
  line-height: calc(var(--cell) * 1.5);
  font-weight: 700;
  letter-spacing: -.02em;
  max-width: 30ch;
  text-wrap: balance;
}
h4 { font-size: 1rem; font-weight: 700; letter-spacing: -.01em; }

p { max-width: 64ch; color: var(--dim); }
p strong, p em { color: var(--text); font-style: normal; }
p em { color: var(--primary); font-style: normal; }

/* --primary-strong, not --primary: the chip sits on a 10% tint of its own hue,
   which costs about 0.6 of the ratio, and plain --primary on that tint measures
   4.34:1 on light — under the floor. The strong step clears it at 5.72:1 light /
   8.90:1 dark. Dark would pass either way (7.11:1); it takes the strong step too
   so a code chip is one colour across the two themes. */
code {
  color: var(--primary-strong);
  background: color-mix(in srgb, var(--primary) 10%, transparent);
  padding: 0 .3ch;
  border-radius: 2px;
}

.note {
  border-left: 2px solid var(--line-2);
  padding-left: calc(var(--col) * 2);
  color: var(--faint);
  font-size: .92rem;
}
.note--warn { border-left-color: var(--alert); }

.caption { font-size: .86rem; color: var(--faint); max-width: 56ch; }

/* ── window 0 · hero ──────────────────────────────────────────────────── */

.pane--hero .pane-body {
  display: flex; flex-direction: column; justify-content: center;
  padding-inline: clamp(calc(var(--col) * 4), 7vw, calc(var(--col) * 14));
}

/* **Baseline-aligned, not centre-aligned, and that is the point.** The chevron
   is a shell prompt, so the wordmark is set *inline with it* and the lockup
   reads `> outstation`; the pole then runs above and below the line of type,
   which is what a flagpole does and what standing the mark on the baseline
   never allowed. Until 2026-08-16 this centred the mark's box against the
   wordmark's, which put the pole's middle on the text and the chevron up in the
   ascenders.

   Baseline alignment does it without depending on line-height, half-leading or
   the block cursor's height: the mark is placed off one number, its own height.
   Centre alignment would have to be recalibrated every time --cell or .cur
   changed, because --cell is a fixed px value while the type scales.

   **The .lockup-flag wrapper is load-bearing and not decoration.** An SVG *as a
   flex item* has no baseline, so the flex algorithm synthesises one from its
   border box and ignores the margin that is doing the positioning — measured in
   Chromium, and it puts the chevron up in the ascenders. A block flex item that
   *contains* an inline replaced element does have a real line box, and that line
   box's baseline is the SVG's bottom margin edge, which is exactly the rule this
   needs. So the mark is wrapped, and the wrapper is what the flex line aligns. */
.lockup {
  display: flex; align-items: baseline; gap: calc(var(--col) * 2.5);
  flex-wrap: wrap;
  --mark-h: clamp(3.8rem, 11.2vw, 6.5rem);
}
/* `height` here is what keeps the overhang honest and needs no magic number:
   the SVG's negative margin puts its own border-box bottom exactly --mark-h
   below this box's top, so the wrapper box and the drawing coincide. The flex
   line therefore reserves the pole's full extent below the baseline, the lede
   never collides with it, and — the case that actually broke — when the lockup
   *wraps* (it does between 901 and 1159 px, where the hero column is one of two
   panes and holds only 397 px) the mark is alone on its line and takes its own
   height instead of the wordmark climbing into the pole. */
.lockup-flag { flex: none; line-height: 0; height: var(--mark-h); }
/* The mark is taller than it is wide (viewBox 496×756), so it is driven by
   height — a width clamp would set the one dimension that does not matter and
   let the mark tower over the wordmark. The three numbers are the wordmark's own
   clamp (2.1rem, 6.2vw, 3.6rem) multiplied by 1.806, which is the mark-to-word
   ratio the OG card uses. Scaling the whole clamp rather than picking a height
   keeps that ratio constant at every width instead of only at the one checked.
   Neither the smaller chevron (2026-08) nor the tighter gap (2026-08-16) moved
   it: both took width off the mark and nothing off its height, which is exactly
   the dimension a height-driven clamp does not care about. Measured back at
   390 / 1024 / 1440 the ratio is still 1.806, and only the rendered width
   changed — 84 → 74 → 68 px at 1440.

   −0.5426 is where the chevron's ink centre lands on the axis Fira Code sets
   its own `>` on. Derived, not tuned: the mark's chevron centre sits 0.7209 of
   the box height above the box's bottom, the font's `>` ink centre sits
   628/1950 em above the baseline, and the mark box is 1.8056 × the wordmark's
   font-size, so the offset is 0.322051/1.8056 − 0.7209 = −0.5426 of the mark's
   height. Change the 1.806 ratio and this number changes with it. */
.lockup-mark {
  height: var(--mark-h); width: auto;
  color: var(--brand-green);
  vertical-align: baseline;
  margin-bottom: calc(var(--mark-h) * -0.5426);
}
/* The mark is monochrome: pole and chevron are both currentColor, so there is
   no per-element rule here. The pennant had one — its pole was the ink colour
   and the split is what got it replaced. .lockup-pole / .lockup-chevron stay in
   the markup so either element can be reached without re-cutting the SVG. */

.lockup-word {
  font-size: clamp(2.1rem, 6.2vw, 3.6rem);
  line-height: calc(var(--cell) * 2);
  font-weight: 700;
  letter-spacing: -.055em;
  display: inline-flex; align-items: baseline;
}
.cur {
  display: inline-block;
  width: .58em; height: 1.02em;
  background: var(--cursor);
  margin-left: .16em;
  transform: translateY(.1em);
  animation: blink 1.06s steps(1) infinite;
}

.lede {
  font-size: clamp(1.15rem, 2.3vw, 1.6rem);
  line-height: 1.4;
  color: var(--text);
  max-width: 34ch;
  text-wrap: balance;
  margin-top: calc(var(--cell) * 1.25) !important;
}
.lede--sm { font-size: clamp(1rem, 1.6vw, 1.2rem); max-width: 46ch; }
.lede em { color: var(--primary); font-style: normal; }

.sub { max-width: 52ch; }

.cta-row {
  display: flex; align-items: center; gap: calc(var(--col) * 3);
  flex-wrap: wrap;
  margin-top: calc(var(--cell) * 1.5) !important;
}

.btn {
  display: inline-block;
  text-decoration: none;
  font-weight: 700;
  color: var(--on-primary);
  background: var(--primary);
  border: 1px solid var(--primary);
  padding: 0 calc(var(--col) * 2.5);
  line-height: calc(var(--cell) * 1.6);
  transition: background .12s, color .12s;
}
/* The hover shade is --primary one OKLCH lightness step toward emphasis, so it
   is the same green brighter (dark) or deeper (light) rather than a second one.
   The label stays --on-primary and gets *more* room either way: 7.99 → 10.03:1
   dark, 5.02 → 6.60:1 light. */
.btn:hover { background: var(--primary-strong); border-color: var(--primary-strong); }
.btn--go { cursor: pointer; }

.tag { color: var(--faint); font-size: .86rem; display: inline-flex; align-items: center; gap: 1ch; }

.dot {
  display: inline-block; width: .5em; height: .5em; border-radius: 50%;
  background: var(--faint); flex: none;
}
.dot--live { background: var(--primary); }
.dot--warn { background: var(--alert); }
.tag .dot { background: var(--alert); }

.hint { font-size: .82rem; color: var(--faint); margin-top: calc(var(--cell) * 2) !important; }
kbd {
  border: 1px solid var(--line-2); border-bottom-width: 2px;
  padding: 0 .5ch; border-radius: 3px; font-size: .88em; color: var(--text);
}

/* ── the terminal, drawn ──────────────────────────────────────────────── */

/* Deliberately a drawing and not a screenshot: it is built from the same
   primitives as the rest of the page, so nobody can mistake it for a photo of
   an interface that does not exist yet.

   **And that is why it draws --primary and not the app's terminal green.** The
   case for keeping #5CD98A here is real — this is a picture of the app, and the
   app's theme does use it. It loses to two things. First, the sentence above:
   the block's whole premise is that it is made of the page's primitives, and a
   primitive it holds out of the page's palette is not one. Second, geometry —
   window 0 puts the mark and this drawing in the same viewport, side by side at
   1440px, so a second green here is not a subtle fidelity gain, it is precisely
   the mismatch the page was rebranded to remove, in the first thing anyone sees.
   Nobody visiting can compare the drawing to an app they have not installed, so
   the fidelity is unverifiable; the mismatch is not.

   The app is unaffected: TerminalTheme.swift still ships #5CD98A, and this is a
   statement about the website's palette, not about the product's. If the two
   ever need to agree, the app moves — not this. There is no terminal-colour
   exception anywhere on the page, which is what keeps it looking intentional:
   a single hold-out would just read as one thing that got missed. */
.pane--term .pane-body { display: flex; flex-direction: column; justify-content: center; }

.term {
  border: 1px solid var(--line-2);
  background: var(--raised);
  font-size: clamp(.68rem, 1.05vw, .84rem);
  line-height: 1.75;
  max-width: 56ch;
  width: 100%;
  border-radius: 4px;
  overflow: hidden;
}
.term-rail {
  display: flex; align-items: center; gap: 1ch;
  padding: 0 1.5ch;
  color: var(--faint);
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--chrome) 55%, var(--raised));
}
.term-rail .dot { width: .45em; height: .45em; }
.term-body { padding: 1ch 1.5ch; }
.term-body p { max-width: none; color: var(--dim); white-space: pre-wrap; }
.term-body .g { color: var(--primary); font-weight: 700; }
.term-body .ok { color: var(--primary); }
.term-body .w { color: var(--alert); font-weight: 700; margin-top: .4em; }
.term-body .q { color: var(--text); }
.term-status {
  display: flex; gap: 1.5ch;
  border-top: 1px solid var(--line);
  color: var(--faint);
  background: color-mix(in srgb, var(--chrome) 55%, var(--raised));
  padding-right: 1.5ch;
}
.term-status .ts-sess {
  background: var(--primary); color: var(--on-primary); font-weight: 700;
  padding: 0 1ch;
}
.term-status .ts-on { color: var(--text); }
.cur--sm {
  width: .6em; height: 1.05em; margin-left: 0;
  transform: translateY(.16em);
}

/* ── questions ────────────────────────────────────────────────────────── */

.qa { display: grid; gap: var(--cell); }
.qa dt { font-weight: 700; color: var(--primary); }
.qa dt::before { content: "? "; color: var(--line-2); }
.qa dd { margin: 0 0 0 2ch; color: var(--dim); font-size: .92rem; max-width: 52ch; }

/* ── window 1 · cards ─────────────────────────────────────────────────── */

.ticks li { color: var(--dim); padding-left: 3ch; position: relative; }
.ticks li::before { content: "▸"; position: absolute; left: 0; color: var(--primary); }

.cards { display: grid; gap: var(--cell); grid-template-columns: 1fr; }
.card {
  border: 1px solid var(--line-2);
  padding: var(--cell) calc(var(--col) * 2);
  background: var(--raised);
}
.card--waiting {
  border-color: var(--alert);
  background: color-mix(in srgb, var(--alert) 12%, var(--ground));
}
.card-top { display: flex; align-items: center; gap: 1.5ch; }
.card-name { font-weight: 700; }
.card-idx { margin-left: auto; color: var(--faint); font-size: .82rem; }
.card-q { color: var(--alert) !important; font-weight: 700; margin-top: calc(var(--cell) * .5); }
.card-quote { color: var(--text) !important; font-size: .9rem; }
.card-meta { font-size: .9rem; margin-top: calc(var(--cell) * .5); }
.chips { display: flex; gap: 1ch; margin-top: calc(var(--cell) * .5); flex-wrap: wrap; }
.chip {
  border: 1px solid var(--line-2); border-radius: 999px;
  padding: 0 1.2ch; font-size: .76rem; color: var(--faint);
}

/* ── window 2 · options + diagram ─────────────────────────────────────── */

.opts dt {
  font-weight: 700; color: var(--primary);
  margin-top: var(--cell);
}
.opts dt::before { content: "▸ "; color: var(--line-2); }
.opts dd { margin: 0 0 0 3ch; color: var(--dim); max-width: 56ch; }

/* 1.2308 is not a taste decision: Fira Code's block glyphs are drawn from
   -600 to 1800 on a 1950 em, so they tile seamlessly at exactly that line
   height and at no other. It is the cell height a terminal would use. */
.diagram pre {
  font-size: clamp(.68rem, 1.5vw, .92rem);
  line-height: 1.2308;
  color: var(--dim);
  overflow-x: auto;
  margin: 0;
  padding-bottom: calc(var(--cell) * .5);
}
.dg-l { color: var(--text); }
.dg-w { color: var(--line-2); }
.dg-e { color: var(--primary); }
.dg-r { color: var(--faint); }
.diagram figcaption { color: var(--faint); font-size: .86rem; max-width: 52ch; margin-top: var(--cell); }

/* ── window 3 · properties ────────────────────────────────────────────── */

.props { display: grid; gap: var(--cell) calc(var(--col) * 4); grid-template-columns: repeat(auto-fit, minmax(min(30ch, 100%), 1fr)); counter-reset: p; }
.props li { border-top: 1px solid var(--line); padding-top: calc(var(--cell) * .5); counter-increment: p; }
.props h4::before {
  content: counter(p, decimal-leading-zero) " ";
  color: var(--primary); font-weight: 400;
}
.props p { font-size: .92rem; margin-top: calc(var(--cell) * .25); }

/* ── window 4 · specs ─────────────────────────────────────────────────── */

.specs { border-collapse: collapse; width: 100%; max-width: 46ch; }
.specs caption { text-align: left; color: var(--faint); font-size: .82rem; padding-bottom: calc(var(--cell) * .5); }
.specs th, .specs td { text-align: left; padding: calc(var(--cell) * .35) 0; border-bottom: 1px dotted var(--line-2); }
.specs th { font-weight: 400; color: var(--dim); }
.specs td { text-align: right; color: var(--primary); font-weight: 700; white-space: nowrap; }

/* ── window 5 · waitlist ──────────────────────────────────────────────── */

.access { max-width: 60ch; }
.wl { margin-top: calc(var(--cell) * 1.5) !important; }
.wl-line {
  display: flex; align-items: center; gap: 1.5ch;
  border: 1px solid var(--line-2);
  background: var(--raised);
  padding: 0 calc(var(--col) * 2);
  transition: border-color .12s;
}
.wl-line:focus-within { border-color: var(--primary); }
.wl-prompt { color: var(--primary); font-weight: 700; }
.wl-line input {
  flex: 1 1 auto; min-width: 0;
  background: none; border: 0; color: var(--text);
  line-height: calc(var(--cell) * 1.7);
  outline: none;
}
.wl-line input::placeholder { color: var(--faint); }
.wl-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.wl .btn--go { margin-top: var(--cell); }
.wl-out { min-height: var(--cell); font-size: .92rem; margin-top: var(--cell); }
.wl-out.ok, .t-ok { color: var(--primary); }
.wl-out.bad, .t-bad { color: var(--alert); }
.wl.is-done .wl-line, .wl.is-done .btn--go { display: none; }
.fine { font-size: .8rem; color: var(--faint); max-width: 52ch; margin-top: calc(var(--cell) * 1.5) !important; }

/* Inline prose links. The site had none until /security: index.html links only
   through .btn and the status bar, and both style themselves — so `a { color:
   inherit }` above left an un-styled link indistinguishable from its sentence.
   Scoped to .pane-body and excluding .btn so it cannot reach either of those.
   The underline is the affordance and the colour is the emphasis; the rule is
   thin and offset so a line of prose does not turn into a fence. */
.pane-body a:not(.btn) {
  color: var(--primary);
  text-decoration: underline;
  text-underline-offset: .22em;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, var(--primary) 45%, transparent);
}
.pane-body a:not(.btn):hover { text-decoration-color: var(--primary); }

/* ── status bar ───────────────────────────────────────────────────────── */

/* box-sizing is content-box here on purpose, against the global border-box reset.
   The bar is --status-h tall *in its content box* and the home-indicator inset is
   added below that. Under border-box, `height` + `padding-bottom: env(...)` means
   the inset is subtracted from the content instead: on a device reporting 34px,
   a 42px bar collapsed to 7px of content (42 − 34 − 1px border) and every segment
   collapsed with it while the labels, laid out for a 42px line, spilled straight
   out the bottom. Headless Chrome reports the inset as 0px, which is why it only
   ever showed up on real hardware. The --chrome background still paints through
   the inset, which is what a bar sitting against the home indicator should do. */
.status {
  flex: none;
  box-sizing: content-box;
  display: flex; align-items: stretch;
  height: var(--status-h);
  background: var(--chrome);
  border-top: 1px solid var(--line);
  font-size: .84rem;
  /* Not line-height: var(--status-h). A line box exactly as tall as its container
     leaves no room for descenders, and it only ever centred the labels by
     accident of the two numbers matching. Each segment centres its own now. */
  line-height: 1;
  padding-bottom: env(safe-area-inset-bottom, 0px);
  user-select: none;
}

.st-sess { display: flex; align-items: stretch; flex: none; }
.st-sess-t {
  display: flex; align-items: center;
  background: var(--primary); color: var(--on-primary);
  font-weight: 700; padding: 0 1.4ch;
}
/* --primary and not --primary-strong, even though this is the one green that
   sits directly on --chrome and measures only 4.23:1 there on light: the
   separator is the pointed end of the .st-sess-t block, so it has to be the
   block's exact value or a seam appears between the two. It is a filled shape,
   not text — 3:1 is the bar, and 4.23 clears it. */
.st-cap { display: flex; align-items: center; color: var(--primary); }
.st-cap::after { content: "\e0b0"; font-size: 1.1em; line-height: 1; }

.st-wins {
  display: flex; align-items: stretch; gap: 0;
  overflow-x: auto; scrollbar-width: none;
  flex: 1 1 auto; min-width: 0;
  -webkit-overflow-scrolling: touch;
}
.st-wins::-webkit-scrollbar { display: none; }
/* --dim, not --faint: this is the page's navigation. --faint measured 2.83:1 on
   the light bar and 2.73:1 on the dark one — the window list was the least
   readable text on the page in both themes. --dim is 6.11:1 light, 5.82:1 dark,
   and the active window is still unmistakable (--text plus a tinted block). */
.st-wins a {
  display: inline-flex; align-items: center;
  padding: 0 1.4ch;
  color: var(--dim);
  text-decoration: none;
  white-space: nowrap;
  transition: color .12s, background .12s;
}
.st-wins a:hover { color: var(--text); }
.st-wins a.is-on { color: var(--text); background: color-mix(in srgb, var(--primary) 15%, transparent); }
/* The one green *character* on the status bar, and it takes --primary-strong.
   Plain --primary on the 15% tint over light --chrome is 3.53:1; the strong step
   is 4.63:1 light / 6.59:1 dark — the least headroom of any green on the page,
   so the 15% and --primary-strong's lightness are both pinned by it. The tinted block behind it carries --text at
   11.72:1 light / 9.88:1 dark. */
.st-wins a.is-on::after { content: "*"; color: var(--primary-strong); }

/* stretch, so each control is a full-height segment the way tmux draws them;
   :not([hidden]) so this never overrides the UA's display:none on #st-prefix. */
.st-right { display: flex; align-items: stretch; gap: 1.2ch; padding: 0 1.4ch; flex: none; color: var(--dim); }
.st-right > *:not([hidden]) { display: flex; align-items: center; }
.st-btn {
  background: none; border: 0; color: var(--dim); cursor: pointer;
  padding: 0 .6ch; line-height: 1; font-size: 1rem;
}
.st-btn:hover { color: var(--text); }
.st-clock { font-variant-numeric: tabular-nums; }
/* --on-primary on --warn, not on the primary: this and .copy-badge borrow it as
   the page's one "ink on a filled block" value rather than carry a second
   near-black. Measured on --warn too — 11.36:1 dark, 4.96:1 light. */
.st-prefix { color: var(--on-primary); background: var(--warn); padding: 0 1ch; font-weight: 700; }

/* ── theme control ────────────────────────────────────────────────────── */

/* The site has had a complete light theme and a working toggle all along; the
   owner asked for light/dark because a lone ◐ in --faint, sitting next to a lone
   ?, does not read as a control at all. So it becomes a labelled status segment —
   which is the authentic tmux read as well as the discoverable one — and the word
   names the mode you get, so the control also says that a second mode exists.
   The label is generated content: the button's aria-label is its accessible name
   and overrides descendant content, so this adds no screen-reader noise, and the
   element is still a <button> with its own focus ring. */
#theme-btn {
  gap: .55ch;
  padding: 0 1.1ch;
  font-size: inherit;
  color: var(--dim);
  background: color-mix(in srgb, var(--text) 7%, transparent);
  transition: color .12s, background .12s;
}
#theme-btn > span { font-size: 1.15em; line-height: 1; }
#theme-btn::after { content: "light"; }
/* Hover fills; focus deliberately does not. The global focus ring is --primary,
   so filling on focus too would draw a green ring 2px off a green block. */
#theme-btn:hover { color: var(--on-primary); background: var(--primary); }

/* Same cascade as the tokens, so the label is right in all three states:
   explicit dark, explicit light, and following the system. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) #theme-btn::after { content: "dark"; }
}
:root[data-theme="light"] #theme-btn::after { content: "dark"; }
:root[data-theme="dark"]  #theme-btn::after { content: "light"; }

@media (max-width: 620px) {
  .st-right .st-clock { display: none; }
  /* The theme segment costs the window list a few characters here, so give the
     right-hand group back everything it can spare. */
  .st-right { gap: .7ch; padding: 0 .9ch; }
  #theme-btn { padding: 0 .8ch; }
  /* The window list scrolls when it will not fit. Fade the cut edge so a
     half-visible "3" reads as more-to-the-right rather than as a bug. */
  .st-wins {
    mask-image: linear-gradient(to right, #000 calc(100% - 3ch), transparent);
  }
}

/* ── overlays ─────────────────────────────────────────────────────────── */

.ov {
  position: fixed; inset: 0; z-index: 60;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--ground) 78%, transparent);
  backdrop-filter: blur(2px);
  padding: var(--cell);
}
.ov[hidden] { display: none; }
/* The dialog takes focus programmatically so the keyboard lands inside it; the
   ring that used to be suppressed here is now suppressed by the general
   `[tabindex="-1"]:focus` rule at the top, which this box matches. */
.ov-box {
  position: relative;
  border: 1px solid var(--line-2);
  background: var(--ground);
  padding: calc(var(--cell) * 1.5) calc(var(--col) * 4) var(--cell);
  max-width: 62ch; width: 100%;
  box-shadow: 0 24px 60px -20px rgb(0 0 0 / .6);
}
.keys { border-collapse: collapse; width: 100%; }
.keys th, .keys td { text-align: left; padding: calc(var(--cell) * .3) 0; vertical-align: baseline; }
.keys th { font-weight: 400; white-space: nowrap; padding-right: 3ch; }
.keys td { color: var(--dim); }
.ov-foot { color: var(--faint); font-size: .82rem; margin-top: var(--cell); }

.detached {
  position: fixed; inset: 0; z-index: 70;
  display: grid; place-items: center;
  background: var(--ground);
  color: var(--dim);
}
.detached[hidden] { display: none; }

/* copy-mode position readout, as tmux draws it */
.pane.is-copy { box-shadow: inset 0 0 0 1px var(--warn) !important; }
.pane .copy-badge {
  position: absolute; top: 0; right: calc(var(--col) * 2);
  transform: translateY(-50%);
  background: var(--warn); color: var(--on-primary);
  padding: 0 1ch; font-size: .76rem; line-height: 1.5;
}

/* ── motion ───────────────────────────────────────────────────────────── */

@keyframes blink { 0%, 50% { opacity: 1 } 50.01%, 100% { opacity: 0 } }
@keyframes paneIn { from { opacity: 0; transform: translateY(6px) } to { opacity: 1; transform: none } }

html.js .win.is-on .pane { animation: paneIn .3s cubic-bezier(.2,.7,.3,1) backwards; }
html.js .win.is-on .pane:nth-child(2) { animation-delay: .07s; }

/* A much older mark had three link cells that pulsed along it (`.lk`, @keyframes
   travel); nothing since has, so those rules went with them. Liveness stays where
   it already was — the cursor after the wordmark. The mark is deliberately not
   animated: a flag whose pole blinks out is not a flag, and the gap between pole
   and chevron is a measured distance, not something to move. */
body.is-away .cur { animation-play-state: paused; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .cur { opacity: 1; }
}

/* ── narrow ───────────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  .panes--h, .panes--v { grid-template-columns: 1fr; grid-template-rows: auto; }
  html.js .win { overflow-y: auto; -webkit-overflow-scrolling: touch; }
  html.js .panes { height: auto; min-height: 100%; }
  .pane-body { overflow: visible; }
  .pane--hero .pane-body { min-height: 62vh; }
}

@media (max-width: 560px) {
  .win { padding: var(--cell) calc(var(--col) * 1.5); }
  .pane-body { padding: calc(var(--cell) * 1.1) calc(var(--col) * 2.5); }
  .lockup { gap: calc(var(--col) * 2); }
}
