/* ============================================================================
 * styles.css — Surplus draft board
 *
 * Design constraints, all of them functional rather than decorative:
 *   - Dim room, 60-second pick clock. High contrast, dense, no decorative motion.
 *   - Position colour is the same hue in the table, the chips, the roster and the
 *     board grid, so a position RUN reads as a vertical band in the grid without
 *     anyone having to parse a single name.
 *   - Nothing animates for longer than 120 ms anywhere in the pick path (§11).
 *   - Absence is the signal: a healthy player renders NO injury mark at all.
 * ========================================================================== */

/* ─────────────────────────── tokens ─────────────────────────── */
:root {
  /* surface ramp: darkest at the page, lighter as you go "up" toward controls */
  --bg:        #0b0e13;
  --bg-1:      #111721;
  --bg-2:      #172032;
  --bg-3:      #1e2a40;
  --line:      #253347;
  --line-soft: #1b2534;

  --fg:        #e9eff7;
  --fg-dim:    #94a4bb;
  --fg-mute:   #64758c;

  --accent:    #4aa8ff;
  --good:      #35c88b;
  --warn:      #f2b23d;
  --bad:       #ff5f6d;

  /* ── turn state ────────────────────────────────────────────────────────────
   * Green now MEANS "it is your pick" and nothing else. Every surface that says
   * MINE or DO THIS — the clock cell, your column on the board, your roster
   * markers, all 640 draft buttons, the recommendation card — reads --turn, and
   * --turn flips to orange the moment somebody else is on the clock. In a dim room
   * you do not read the header to find out whose turn it is; the whole board
   * changes temperature and you know from across the table.
   *
   * TWO KINDS OF GREEN EXIST HERE AND ONLY ONE OF THEM FLIPS.
   *   --turn  is identity and affordance: mine, yours, press this. It flips.
   *   --good  is one end of a MEASUREMENT ramp: survival hi/md/lo, Δ pick +/−,
   *           SOS easy/hard, the live/cached/offline status dot. It does not flip,
   *           because orange is already taken inside those ramps — a survival
   *           column where "high" and "medium" were both orange would have traded
   *           a legible number for a decorative one.
   * Position hues never flip either, for the reason at the top of this file: RB is
   * green in the table, the chips, the roster and the grid so that a run reads as a
   * vertical band, and a position that changed colour mid-draft would destroy the
   * one signal this palette exists to carry.
   *
   * The orange is hotter and redder than --pos-TE (#f2a03d, a soft gold) on purpose:
   * the two do sit side by side in every row, and they must not be mistaken for each
   * other at a glance.
   */
  --turn-me:   #35c88b;
  --turn-away: #ff8f3f;
  --turn: var(--turn-me);

  /* Position hues. Chosen for separation at low luminance and for deuteranopia
     (RB green vs WR blue vs TE orange never collide on the same axis). */
  --pos-QB:  #f0668c;
  --pos-RB:  #35c88b;
  --pos-WR:  #4aa8ff;
  --pos-TE:  #f2a03d;
  --pos-K:   #b98cff;
  --pos-DST: #7f8ea6;

  --row-h:  26px;
  --hdr-h:  56px;
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  --fast: 90ms;   /* every transition in the pick path is capped here (§11) */
}

/* "near" is your on-deck warning — it earns a badge in the header, not a third hue.
   "done" stays green: with the draft over there is no longer anyone to wait for. */
:root[data-turn="other"],
:root[data-turn="near"] { --turn: var(--turn-away); }

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;                 /* panels scroll, the page never does */
  background: var(--bg);
  color: var(--fg);
  font: 13px/1.35 var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeSpeed;    /* 640 rows; kerning niceties are not worth ms */
}

body { display: flex; flex-direction: column; }

.noscript { padding: 2rem; color: var(--fg); }

button, input, select { font: inherit; color: inherit; }

button {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 4px 9px;
  cursor: pointer;
  transition: background var(--fast) linear, border-color var(--fast) linear;
}
button:hover:not(:disabled) { background: var(--bg-3); }
button:disabled { opacity: .38; cursor: default; }
button.primary { background: var(--turn); border-color: var(--turn); color: #06140d; font-weight: 650; }
button.danger  { border-color: #5d2730; color: var(--bad); }
button.icon    { background: none; border: none; font-size: 15px; color: var(--fg-dim); }

input[type=text], input[type=number], select {
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 5px 8px;
}
input:focus-visible, select:focus-visible, button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.muted     { color: var(--fg-mute); font-weight: 400; }
.fineprint { color: var(--fg-mute); font-size: 11px; line-height: 1.45; margin: 6px 0 0; }
code { font-family: var(--mono); font-size: 11px; color: var(--fg-dim); }
kbd {
  font-family: var(--mono); font-size: 9px; padding: 0 3px; margin-left: 4px;
  border: 1px solid currentColor; border-radius: 3px; opacity: .55; vertical-align: 1px;
}

/* ─────────────────────────── header ─────────────────────────── */
/* The rail is an INSET SHADOW, not a border or an extra element: it repaints in place
   on every turn flip without moving a single pixel of the layout below it, which is the
   whole point of putting the loudest signal in the app on the pick path. */
#hdr {
  flex: 0 0 auto;
  height: var(--hdr-h);
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 0 12px;
  background: var(--bg-1);
  border-bottom: 1px solid var(--line);
  box-shadow: inset 0 3px 0 0 var(--turn);
  transition: box-shadow var(--fast) linear, background var(--fast) linear;   /* §11 */
}
:root[data-turn="me"] #hdr { background: color-mix(in srgb, var(--turn) 7%, var(--bg-1)); }
.hdr-group { display: flex; align-items: center; gap: 10px; min-width: 0; }
#clock-group { flex: 1 1 auto; gap: 18px; overflow: hidden; }
.hdr-actions { margin-left: auto; flex: 0 0 auto; }

.clock-cell { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.clock-cell .lbl {
  font-size: 9px; letter-spacing: .09em; color: var(--fg-mute); font-weight: 600;
}
.clock-cell strong { font-size: 15px; font-weight: 650; white-space: nowrap; }
.clock-onclock strong { font-size: 19px; line-height: 1.05; }
/* The single most important word on the screen when it is your turn. A 9px caption was
   not enough — it sat inside a header full of other 9px captions. Filled, boxed and
   sized like a label instead, so it is legible from the far side of a draft table. */
.clock-onclock.is-me {
  padding: 2px 9px; margin: -2px -3px; border-radius: 6px;
  background: color-mix(in srgb, var(--turn) 16%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--turn) 55%, transparent);
}
.clock-onclock.is-me strong { color: var(--turn); }
.clock-onclock.is-me .lbl { color: var(--turn); opacity: .85; }
#clock-next { display: flex; gap: 6px; font-size: 11px; color: var(--fg-dim); white-space: nowrap; }
#clock-next .nx-me { color: var(--turn); font-weight: 650; }
.clock-mine strong { font-size: 13px; color: var(--fg-dim); }
:root[data-turn="near"] .clock-mine strong { color: var(--turn); font-weight: 700; }

/* ── positional run alert ──
   Deliberately quiet. It reports something the survival column has ALREADY priced in,
   so it must read as a caption, not an emergency — a red flashing banner would push
   people to reach for exactly the position the board just marked down. */
#run-alert {
  flex: 0 0 auto; gap: 7px;
  padding: 4px 10px; border-radius: 999px;
  background: var(--bg-2); border: 1px solid var(--line);
  font-size: 11.5px; color: var(--fg-dim); white-space: nowrap;
  cursor: help;
}
/* .hdr-group's display:flex is an author rule and would beat the UA [hidden] rule. */
#run-alert[hidden] { display: none; }
#run-alert .run-dot {
  width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto;
  background: currentColor;
}
#run-alert strong { font-weight: 700; letter-spacing: .02em; }

#status-chip { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--fg-mute); flex: 0 0 auto; }
.dot.live    { background: var(--good); }
.dot.cached  { background: var(--warn); }
.dot.offline { background: var(--bad); }

/* ─────────────────────────── banners ─────────────────────────── */
#banners { flex: 0 0 auto; }
.banner {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 12px; font-size: 11.5px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--bg-2); color: var(--fg-dim);
}
.banner.warn { background: #2a2312; color: #f2d08a; }
.banner.bad  { background: #2a1418; color: #ffb0b6; }
.banner button { margin-left: auto; padding: 1px 7px; font-size: 11px; }

/* ─────────────────────────── layout ─────────────────────────── */
#grid {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: minmax(620px, 1.5fr) 262px minmax(340px, 1fr);
  gap: 1px;
  background: var(--line-soft);
  min-height: 0;
}
.panel {
  background: var(--bg);
  display: flex; flex-direction: column;
  min-width: 0; min-height: 0;
}
.panel-hd {
  flex: 0 0 auto;
  padding: 7px 10px;
  background: var(--bg-1);
  border-bottom: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 6px;
}
#panel-roster .panel-hd, #panel-draft .panel-hd {
  flex-direction: row; align-items: baseline; gap: 8px;
}
.panel-hd h2 { font-size: 12px; font-weight: 650; letter-spacing: .05em; margin: 0; text-transform: uppercase; }
.panel-body { flex: 1 1 auto; overflow: auto; padding: 8px; min-height: 0; }
.sub {
  font-size: 10px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--fg-dim); margin: 14px 0 5px; font-weight: 650;
}

/* ─────────────────── left panel: search + chips ─────────────────── */
#best-hd { gap: 6px; }
#search {
  width: 100%; font-size: 14px; padding: 6px 9px;
  background: var(--bg-2); border-color: var(--bg-3);
}
#search::placeholder { color: var(--fg-mute); }

#pos-chips { display: flex; gap: 4px; flex-wrap: wrap; }
.chip {
  padding: 2px 8px; border-radius: 999px; font-size: 11px; font-weight: 600;
  border: 1px solid var(--line); background: transparent; color: var(--fg-dim);
}
.chip.is-on { background: var(--bg-3); color: var(--fg); border-color: currentColor; }
.chip.pos-QB.is-on  { color: var(--pos-QB); }
.chip.pos-RB.is-on  { color: var(--pos-RB); }
.chip.pos-WR.is-on  { color: var(--pos-WR); }
.chip.pos-TE.is-on  { color: var(--pos-TE); }
.chip.pos-K.is-on   { color: var(--pos-K); }
.chip.pos-DST.is-on { color: var(--pos-DST); }

.hd-row2 { display: flex; align-items: center; gap: 6px; font-size: 11px; }
.toggle { padding: 2px 8px; font-size: 11px; border-radius: 999px; color: var(--fg-mute); }
.toggle.is-on { color: var(--warn); border-color: #4a3a17; background: #241d0e; }
#ba-count { margin-left: auto; font-size: 11px; }

/* ─────────────────── best-available table ─────────────────── */
.tbl-wrap { flex: 1 1 auto; overflow: auto; min-height: 0; position: relative; }
/* Sized so the whole row fits a 1280px viewport without horizontal scrolling.
   At 1280 the three panels resolve to 620 / 262 / 396, and the panel body's 8px
   padding leaves 604px here — SURV% and INJ are the two most decision-relevant
   columns on the board, so they must never be the ones pushed off the edge. */
#ba-table { min-width: 654px; }

/* One column template, shared by the header and every row. Changing a width here
   moves the header and the body together — there is no second source of truth. */
.ba-row {
  display: grid;
  grid-template-columns:
    32px           /* #      */
    minmax(0, 1fr) /* name   */
    34px           /* team   */
    38px           /* pos    */
    30px           /* bye    */
    46px           /* proj   */
    48px           /* vob    */
    34px           /* tier   */
    44px           /* adp    */
    46px           /* delta  */
    44px           /* surv   */
    40px           /* inj    */
    58px;          /* draft  */
  align-items: center;
  column-gap: 4px;
  padding: 0 8px;
}

/* Narrow viewports keep the draft buttons and give up a few pixels everywhere else.
   Hiding the action column would be worse: recording picks fast is the whole job, and
   a 1280px laptop is exactly where a cramped row is most likely to be used. */
@media (max-width: 1400px) {
  #ba-table { min-width: 506px; }
  .ba-row {
    grid-template-columns:
      28px minmax(0, 1fr) 30px 34px 26px 42px 44px 30px 40px 42px 40px 34px 52px;
  }
}
.ba-row > * {
  text-align: right; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}
.ba-row > .t-l { text-align: left; }

/* ── inline draft actions ──────────────────────────────────────────────────────
   Always visible, never hover-only: on a fast clock you cannot hunt for a control
   that only exists once the cursor finds it. Clicks here stopPropagation so the
   deep panel does NOT open — recording a pick and researching a player are two
   different intentions and must not share a gesture. */
.row-act { display: flex; gap: 3px; align-items: center; overflow: visible; }
.act-draft, .act-more {
  font: inherit; font-size: 10px; font-weight: 700; line-height: 1;
  padding: 3px 0; border-radius: 4px; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--turn) 40%, transparent);
  background: color-mix(in srgb, var(--turn) 13%, transparent);
  color: var(--turn);
  transition: background 90ms ease, border-color 90ms ease;   /* §11: nothing over 120ms */
}
.act-draft { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.act-more  { flex: 0 0 16px; width: 16px; }
.act-draft:hover, .act-more:hover {
  background: color-mix(in srgb, var(--turn) 26%, transparent);
  border-color: var(--turn);
}
.act-draft:focus-visible, .act-more:focus-visible { outline: 2px solid var(--turn); outline-offset: 1px; }
/* A drafted row keeps the column so the grid never reflows, but shows nothing. */
.row.is-taken .row-act { visibility: hidden; }

.ba-hd {
  position: sticky; top: 0; z-index: 3;
  height: 26px;
  background: var(--bg-1);
  border-bottom: 1px solid var(--line);
  font-size: 10px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--fg-dim); font-weight: 650;
  user-select: none;
}
.ba-hd > * { cursor: pointer; }
.ba-hd > *:hover { color: var(--fg); }
/* Sort indicator is a real glyph, not a background image: it has to be legible in a
   dim room and it has to survive a CSP that allows no external images. */
.ba-hd > .sorted { color: var(--accent); }
.ba-hd > .sorted::after { content: "▾"; margin-left: 2px; }
.ba-hd > .sorted.asc::after { content: "▴"; }

.row {
  height: var(--row-h);
  /* ~640 rows, no virtual scroller (§11 rendering note). content-visibility keeps
     layout + paint proportional to the viewport rather than to the row count, and
     the intrinsic size keeps the scrollbar from jittering as rows are skipped.
     This is exactly why the list is grid rows and not a <table>: containment does
     not apply to internal table boxes, so on a <tr> the property is a no-op. */
  content-visibility: auto;
  contain-intrinsic-size: auto var(--row-h);
  cursor: pointer;
  border-bottom: 1px solid var(--line-soft);
}
.row:hover { background: var(--bg-1); }
.row.is-sel { background: var(--bg-3); box-shadow: inset 3px 0 0 var(--accent); }
.row.is-taken { opacity: .34; }
.row.is-taken .cell-name { text-decoration: line-through; }
.row.is-queued .cell-name::before { content: "★ "; color: var(--warn); }
.row.is-mine  .cell-name::after { content: " ⬤"; color: var(--turn); font-size: 7px; vertical-align: 3px; }

.cell-name { font-weight: 560; }
.cell-rank { color: var(--fg-mute); font-family: var(--mono); font-size: 11px; }
.cell-num  { font-family: var(--mono); font-size: 11.5px; }
.cell-vob  { color: var(--fg); font-weight: 600; }
.pos-tag {
  display: inline-block; min-width: 30px; text-align: center;
  padding: 0 4px; border-radius: 3px; font-size: 10px; font-weight: 700;
  font-family: var(--mono); letter-spacing: .02em;
}
.pos-QB  { color: var(--pos-QB); }
.pos-RB  { color: var(--pos-RB); }
.pos-WR  { color: var(--pos-WR); }
.pos-TE  { color: var(--pos-TE); }
.pos-K   { color: var(--pos-K); }
.pos-DST { color: var(--pos-DST); }
.pos-tag.pos-QB  { background: color-mix(in srgb, var(--pos-QB) 17%, transparent); }
.pos-tag.pos-RB  { background: color-mix(in srgb, var(--pos-RB) 17%, transparent); }
.pos-tag.pos-WR  { background: color-mix(in srgb, var(--pos-WR) 17%, transparent); }
.pos-tag.pos-TE  { background: color-mix(in srgb, var(--pos-TE) 17%, transparent); }
.pos-tag.pos-K   { background: color-mix(in srgb, var(--pos-K) 17%, transparent); }
.pos-tag.pos-DST { background: color-mix(in srgb, var(--pos-DST) 17%, transparent); }

.d-pos { color: var(--good); }        /* he lasts past my pick */
.d-neg { color: var(--bad); }         /* he is gone before my pick */
.surv-hi { color: var(--good); }
.surv-md { color: var(--warn); }
.surv-lo { color: var(--bad); }
.adp-censored { color: var(--fg-mute); }
/* A number the free tier does not include. Dimmed rather than blank so the column keeps
   its shape and reads as withheld rather than as missing data. */
.locked-cell { color: var(--fg-mute); opacity: .55; cursor: help; }
/* A bye week one of MY starters already has. Boxed rather than coloured: the numeric
   columns either side are already carrying colour that means something else, and one
   more hue in that band would be noise.

   SCOPED TO THE TABLE ROW deliberately — `.bye-clash` was already in use on the roster
   panel's slots (see `.slot.bye-clash` below), and a bare rule here would have repainted
   every clashing slot's background as a side effect of naming the class the same thing. */
.ba-row .bye-clash {
  color: var(--warn);
  background: color-mix(in srgb, var(--warn) 14%, transparent);
  border-radius: 3px;
  cursor: help;
}

/* injury chips — healthy renders NOTHING (§ fastest possible signal) */
.inj {
  display: inline-block; padding: 0 4px; border-radius: 3px;
  font-size: 9.5px; font-weight: 700; font-family: var(--mono); letter-spacing: .03em;
  cursor: help;
}
.inj-red   { background: #401419; color: #ff8f97; }
.inj-amber { background: #3a2e10; color: #f5c96b; }
.inj-grey  { background: var(--bg-3); color: var(--fg-dim); }
/* §3: an August "Questionable" is a camp tweak, not a game designation — same hue so
   it still reads as a caution, dimmed so it never outranks a real one. */
.inj.is-camp { opacity: .5; }

/* verdict row — take-now-vs-wait, rendered inline under the highlighted row */
.verdict { padding: 4px 12px; background: var(--bg-2); border-bottom: 1px solid var(--line-soft); }
.verdict-line { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; font-size: 12px; }
.verdict-take { color: var(--turn); font-weight: 700; letter-spacing: .04em; }
.verdict-wait { color: var(--warn); font-weight: 700; letter-spacing: .04em; }
/* Off the clock. TARGET takes --turn (orange, because it is not your pick); LIKELY GONE
   takes --bad, which is the one place in this app where red means "no decision to make"
   rather than "bad player" — the tooltip and the sentence beside it say which. */
.verdict-target { color: var(--turn); font-weight: 700; letter-spacing: .04em; }
.verdict-gone   { color: var(--bad); font-weight: 700; letter-spacing: .04em; }
.verdict-risk { font-weight: 600; }
.verdict-sub  { color: var(--fg-mute); font-size: 11px; }

/* ─────────────────── deep panel (expanded row) ─────────────────── */
.deep { background: var(--bg-1); }
.deep-inner {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 10px 16px;
  padding: 10px 12px 12px;
  border-top: 1px solid var(--line);
  border-bottom: 2px solid var(--accent);
}
.deep-sec h4 {
  margin: 0 0 4px; font-size: 9.5px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--accent); font-weight: 700;
}
.kv { display: flex; justify-content: space-between; gap: 8px; font-size: 11.5px; padding: 1px 0; }
.kv span:first-child { color: var(--fg-dim); }
.kv span:last-child  { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.kv.total { border-top: 1px solid var(--line); margin-top: 3px; padding-top: 3px; font-weight: 700; }
.deep-note { font-size: 11px; color: var(--fg-dim); line-height: 1.45; margin: 4px 0 0; }
.deep-blurb {
  font-size: 11px; color: var(--fg-dim); line-height: 1.5; margin: 4px 0 0;
  max-height: 8.4em; overflow: auto;
}
.deep-wide { grid-column: 1 / -1; }

/* ── roster-fit findings ──
   Shared by the deep panel's "if you take him" and the post-draft report, because they
   are the same sentences about the same relations — one before the pick and one after,
   and a reader who learns to read them in one place should not have to learn again. */
.risk-hd {
  margin: 8px 0 3px; font-size: 9.5px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--fg-dim); font-weight: 700;
}
.risk-list { display: flex; flex-direction: column; gap: 2px; margin-top: 4px; }
.risk-row {
  font-size: 11.5px; line-height: 1.4; padding: 2px 7px; border-radius: 4px;
  border-left: 2px solid currentColor;
  background: var(--bg-2);
}
.risk-warn { color: var(--warn); }
.risk-bad  { color: var(--bad); }
.risk-good { color: var(--good); }   /* a stack is genuinely good news, not a caution */

/* ── matchups ──
   Colour only at the ends. A schedule inside ±6% of average is not information, and
   painting it would imply a precision last season's defenses do not have. */
.sos-easy { color: var(--good); }
.sos-hard { color: var(--bad); }
.sos-opps { display: flex; gap: 6px; margin-top: 5px; }
.sos-wk {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; gap: 1px; align-items: center;
  padding: 3px 2px; border-radius: 5px;
  background: var(--bg-2); border: 1px solid var(--line);
}
.sos-wk em { font-style: normal; font-size: 8.5px; letter-spacing: .08em; color: var(--fg-mute); font-weight: 600; }
.sos-wk b { font-family: var(--mono); font-size: 11.5px; font-weight: 650; }
.spark { display: block; width: 100%; height: 46px; }
.spark-lbl { font-size: 10px; color: var(--fg-mute); display: flex; justify-content: space-between; }

/* ─────────────────── team picker popover ─────────────────── */
#team-picker {
  position: absolute; z-index: 20; left: 12px; bottom: 12px;
  background: var(--bg-2); border: 1px solid var(--accent); border-radius: 6px;
  padding: 8px; box-shadow: 0 8px 24px #0009; max-width: 420px;
}
.tp-hd { font-size: 11px; color: var(--fg-dim); margin-bottom: 6px; }
#tp-list { display: flex; flex-wrap: wrap; gap: 4px; }
#tp-list button { font-size: 11px; padding: 3px 8px; }
#tp-list button.is-clock { border-color: var(--accent); }
#tp-list button.is-me    { color: var(--turn); border-color: var(--turn); }

/* ─────────────────── middle: roster + scarcity ─────────────────── */
.slot {
  display: flex; align-items: center; gap: 6px;
  height: 24px; padding: 0 6px; margin-bottom: 2px;
  background: var(--bg-1); border-radius: 4px; font-size: 11.5px;
  border-left: 3px solid var(--line);
}
.slot.filled { background: var(--bg-2); }
.slot.empty  { color: var(--fg-mute); border-left-style: dashed; }
.slot-key {
  width: 42px; flex: 0 0 auto; font-family: var(--mono); font-size: 9.5px;
  font-weight: 700; color: var(--fg-mute); letter-spacing: .04em;
}
.slot-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.slot-pts  { font-family: var(--mono); font-size: 10.5px; color: var(--fg-dim); }
.slot-bye  { font-family: var(--mono); font-size: 9.5px; color: var(--fg-mute); width: 26px; text-align: right; }
.slot.bye-clash .slot-bye { color: var(--warn); font-weight: 700; }
.slot-sep {
  font-size: 9px; letter-spacing: .1em; color: var(--fg-mute);
  margin: 8px 0 3px; text-transform: uppercase; font-weight: 700;
}

.scar-row { display: flex; align-items: center; gap: 6px; height: 22px; font-size: 11.5px; }
.scar-bar { flex: 1 1 auto; height: 5px; background: var(--bg-2); border-radius: 3px; overflow: hidden; }
.scar-fill { height: 100%; border-radius: 3px; }
.scar-n { font-family: var(--mono); font-size: 11px; width: 26px; text-align: right; }
.scar-lbl { font-size: 10px; color: var(--fg-mute); width: 74px; text-align: right; font-variant-numeric: tabular-nums; }
.scar-row.is-cliff .scar-n,
.scar-row.is-cliff .scar-lbl { color: var(--bad); font-weight: 700; }

.q-row {
  display: flex; align-items: center; gap: 6px; height: 22px;
  font-size: 11.5px; cursor: pointer; padding: 0 4px; border-radius: 4px;
}
.q-row:hover { background: var(--bg-1); }
.q-row.gone { opacity: .4; text-decoration: line-through; }
.q-x { margin-left: auto; color: var(--fg-mute); }
.empty-note { font-size: 11px; color: var(--fg-mute); padding: 3px 0; }

/* ─────────────────── right: draft board grid ─────────────────── */
#board-wrap { padding: 0; }
#board-grid { display: grid; gap: 1px; padding: 1px; min-width: max-content; }
.bd-h {
  position: sticky; top: 0; z-index: 2;
  background: var(--bg-1); color: var(--fg-dim);
  font-size: 9.5px; font-weight: 700; text-align: center;
  padding: 3px 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bd-h.is-me { color: var(--turn); }
.bd-r {
  position: sticky; left: 0; z-index: 1;
  background: var(--bg-1); color: var(--fg-mute);
  font-family: var(--mono); font-size: 9.5px; text-align: center;
  display: flex; align-items: center; justify-content: center;
}
.bd-c {
  min-height: 22px; padding: 2px 3px; overflow: hidden;
  background: var(--bg-1); border-radius: 2px;
  font-size: 10px; line-height: 1.15; white-space: nowrap; text-overflow: ellipsis;
}
.bd-c .bd-nm { display: block; overflow: hidden; text-overflow: ellipsis; }
.bd-c .bd-po { font-size: 8.5px; font-family: var(--mono); font-weight: 700; opacity: .85; }
.bd-c.empty { background: var(--bg); }
.bd-c.next  { background: var(--bg-3); outline: 1px solid var(--accent); }
/* The user's own column, outlined end to end so their next slot is findable
   without counting. */
.bd-col-me { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--turn) 55%, transparent); }
.bd-c.bp-QB  { background: color-mix(in srgb, var(--pos-QB) 22%, var(--bg-1)); }
.bd-c.bp-RB  { background: color-mix(in srgb, var(--pos-RB) 22%, var(--bg-1)); }
.bd-c.bp-WR  { background: color-mix(in srgb, var(--pos-WR) 22%, var(--bg-1)); }
.bd-c.bp-TE  { background: color-mix(in srgb, var(--pos-TE) 22%, var(--bg-1)); }
.bd-c.bp-K   { background: color-mix(in srgb, var(--pos-K) 22%, var(--bg-1)); }
.bd-c.bp-DST { background: color-mix(in srgb, var(--pos-DST) 22%, var(--bg-1)); }

/* ─────────────────── overlays ─────────────────── */
.overlay {
  position: fixed; inset: 0; z-index: 100;
  background: #060911d8;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.overlay[hidden] { display: none; }
.sheet {
  background: var(--bg-1); border: 1px solid var(--line); border-radius: 8px;
  padding: 16px 18px; width: min(980px, 96vw); max-height: 92vh; overflow: auto;
  box-shadow: 0 20px 60px #000a;
}
.sheet-sm { width: min(560px, 96vw); }
.sheet-hd { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }

/* Save receipt. `margin-left: auto` puts it beside the close button rather than in the
   layout flow, so it can appear and vanish without shifting anything. */
.save-pill {
  margin-left: auto; margin-right: 10px;
  padding: 2px 9px; border-radius: 999px;
  font-size: 11px; font-weight: 600; white-space: nowrap;
  color: var(--turn);
  background: color-mix(in srgb, var(--turn) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--turn) 38%, transparent);
  animation: pill-in 120ms ease-out;      /* §11: nothing over 120ms */
}
.save-pill[hidden] { display: none; }
@keyframes pill-in { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: none; } }
/* Respect a reduced-motion preference — the pill still appears, it just does not move. */
@media (prefers-reduced-motion: reduce) { .save-pill { animation: none; } }
.sheet h2 { font-size: 15px; margin: 0 0 6px; }
.sheet h3 {
  font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--accent); margin: 16px 0 6px; font-weight: 700;
}
.sheet h3:first-child { margin-top: 0; }
.sheet-actions { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
.setup-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.setup-col label { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 5px; font-size: 12px; }
.setup-col label input, .setup-col label select { width: 168px; }
.grid-editor { display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; }
.grid-editor label { flex-direction: column; align-items: stretch; gap: 2px; font-size: 10px; color: var(--fg-dim); }
.grid-editor input { width: 100% !important; text-align: right; }
/* A checkbox is not a 168px field. Overriding the width here rather than widening the
   generic rule keeps every other row in this column aligned on the same edge. */
.chk-row input[type=checkbox] { width: auto !important; flex: 0 0 auto; accent-color: var(--turn); }
.slider-row { gap: 10px; }
.slider-row input { flex: 1 1 auto; width: auto !important; }
.slider-row output { font-family: var(--mono); width: 34px; text-align: right; }
.slider-ends { display: flex; justify-content: space-between; font-size: 10px; color: var(--fg-mute); }
.preset-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.preset-chips .chip.is-on { color: var(--accent); }
.scoring-editor { display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 12px; margin-top: 8px; }
.scoring-editor label { display: flex; justify-content: space-between; gap: 6px; font-size: 11px; align-items: center; }
.scoring-editor input { width: 68px; text-align: right; padding: 2px 5px; }
.scoring-grp { grid-column: 1 / -1; font-size: 9.5px; letter-spacing: .08em; color: var(--fg-mute); text-transform: uppercase; margin-top: 6px; font-weight: 700; }
.row-actions { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
#saved-leagues { display: flex; flex-direction: column; gap: 3px; }
.lg-row { display: flex; align-items: center; gap: 6px; font-size: 11.5px; }
.lg-row .lg-nm { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lg-row.is-on .lg-nm { color: var(--turn); font-weight: 650; }
.resume-roster { display: flex; flex-wrap: wrap; gap: 4px; margin: 8px 0; }
.resume-roster .pos-tag { font-size: 10px; }

#help-list { display: grid; grid-template-columns: auto 1fr; gap: 3px 14px; margin: 0; font-size: 12px; }
#help-list dt { font-family: var(--mono); color: var(--accent); white-space: nowrap; }
#help-list dd { margin: 0; color: var(--fg-dim); }

#diag-body { font-family: var(--mono); font-size: 11.5px; }
.diag-row { display: flex; justify-content: space-between; gap: 12px; padding: 1px 0; }
.diag-row.over  { color: var(--bad); }
.diag-row.under { color: var(--good); }
.diag-h { color: var(--accent); margin-top: 8px; font-weight: 700; }

/* ─────────────────────────── the tour ───────────────────────────
   Above the overlays (z 100), because it explains them. Every position here is written
   through the CSSOM by app.js — the CSP has no 'unsafe-inline', so there is no style
   attribute anywhere in this app and there is not one here either. */
.tour { position: fixed; inset: 0; z-index: 200; }

/* The scrim IS this element's shadow. One box with a 9999px spread beats four framing
   divs: no seams at the corners, one thing to move, and the cutout is exact. */
.tour-hole {
  position: fixed; border-radius: 7px;
  box-shadow: 0 0 0 9999px #04070cc4, 0 0 0 1.5px var(--accent) inset;
  transition: left 140ms ease, top 140ms ease, width 140ms ease, height 140ms ease;
  pointer-events: none;
}
/* A step with nothing to point at still needs the room darkened, so the hole moves off
   screen rather than being hidden — hiding it would take the scrim with it. */
.tour-hole.is-off { left: 50%; top: -20px; width: 0; height: 0; box-shadow: 0 0 0 9999px #04070cd8; }

.tour-card {
  position: fixed; width: min(360px, calc(100vw - 24px));
  background: var(--bg-1); border: 1px solid var(--line); border-radius: 9px;
  padding: 14px 16px 12px; box-shadow: 0 18px 50px #000a;
}
.tour-step {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--fg-mute); font-weight: 600;
}
.tour-card h2 { font-size: 16px; margin: 5px 0 6px; letter-spacing: -.01em; text-wrap: balance; }
.tour-card p { margin: 0; font-size: 12.5px; line-height: 1.55; color: var(--fg-dim); }
.tour-foot { display: flex; align-items: center; gap: 8px; margin-top: 13px; }
.tour-nav { margin-left: auto; display: flex; gap: 6px; }
.tour-skip { background: none; border: none; color: var(--fg-mute); padding: 4px 2px; }
.tour-skip:hover { background: none; color: var(--fg-dim); text-decoration: underline; }

@media (prefers-reduced-motion: reduce) { .tour-hole { transition: none; } }

.empty { padding: 20px; text-align: center; color: var(--fg-mute); font-size: 12px; }

/* ─────────────────── responsive ───────────────────
   Laptop first. Below ~1180px the draft board drops under the other two (it is
   reference, not the pick path); below ~880px everything stacks. Nothing is
   removed at any width — a tablet still drafts. */
@media (max-width: 1180px) {
  #grid {
    grid-template-columns: minmax(0, 1fr) 240px;
    grid-template-rows: minmax(0, 1.35fr) minmax(0, .65fr);
  }
  #panel-best   { grid-column: 1; grid-row: 1; }
  #panel-roster { grid-column: 2; grid-row: 1 / span 2; }
  #panel-draft  { grid-column: 1; grid-row: 2; }
}
@media (max-width: 880px) {
  html, body { overflow: auto; }
  #grid { grid-template-columns: 1fr; grid-template-rows: none; }
  #panel-best, #panel-roster, #panel-draft { grid-column: 1; grid-row: auto; min-height: 320px; }
  #hdr { height: auto; flex-wrap: wrap; padding: 6px 8px; gap: 8px; }
  #clock-group { gap: 12px; }
  .setup-cols { grid-template-columns: 1fr; }
}

/* ── phone ──────────────────────────────────────────────────────────────────
   Below 560px the 13-column board row cannot fit, and the thing that gets pushed
   off the right edge is the DRAFT BUTTON — the one control the whole app exists
   to serve. So the row stops being a row and becomes a two-line card built from
   exactly the same DOM, laid out by grid areas. Nothing is removed that a pick
   depends on: name, position, VOB, ADP, survival, injury and the draft button are
   all on screen at 390px. Tier, projected points, Δ pick and bye move to the deep
   panel, which is one tap away.

   Touch targets go to 40px, which is Apple's and Google's shared minimum and about
   the width of an adult fingertip. On a laptop these controls are 21–28px and that
   is correct there; a phone is a different instrument. */
@media (max-width: 560px) {
  /* Two lines of content need two lines of height. Redefining the token rather than
     overriding .row keeps `height` and `contain-intrinsic-size` in agreement — they
     are the same number for a reason, and a content-visibility placeholder that
     disagrees with the real height makes the scrollbar jitter as rows skip. */
  :root { --row-h: 52px; }

  /* Two lines instead of thirteen columns. Column 1 is the rank gutter, spanning
     both lines; column 6 is the draft button, likewise — so the tap target is the
     full height of the card rather than a 20px sliver. */
  #ba-table { min-width: 0; }
  /* Scoped to .row, not .ba-row: the header carries .ba-row too, and it becomes a
     flex strip of sort chips below rather than a card of its own. */
  .row {
    grid-template-columns: 24px 34px 34px minmax(0, 1fr) 44px 46px 60px;
    /* The draft column occupies BOTH rows. That is not cosmetic: a grid area spanning
       one 22px track is a 22px tap target, and this is the control the app exists for. */
    grid-template-areas:
      "rk nm nm nm nm vb dr"
      "rk ps tm ij ad sv dr";
    /* Explicit halves, not `auto`. The draft button spans both rows, and auto-sizing
       hands a spanning item's height to the LAST track it touches — which collapsed
       row 1 to 5px and spilled the player's name out of the top of the card. */
    grid-template-rows: 1fr 1fr;
    padding: 3px 8px;
    align-items: center;
  }
  .row > :nth-child(1)  { grid-area: rk; }
  .row > :nth-child(2)  { grid-area: nm; font-size: 13.5px; }
  .row > :nth-child(3)  { grid-area: tm; }
  .row > :nth-child(4)  { grid-area: ps; }
  .row > :nth-child(7)  { grid-area: vb; font-size: 13px; }
  .row > :nth-child(9)  { grid-area: ad; }
  .row > :nth-child(11) { grid-area: sv; }
  .row > :nth-child(12) { grid-area: ij; }
  .row > :nth-child(13) { grid-area: dr; }
  /* bye, projected points, tier and Δ pick. Present in the DOM (the row cache and
     its signature are untouched), just not on a 390px screen. */
  .row > :nth-child(5),
  .row > :nth-child(6),
  .row > :nth-child(8),
  .row > :nth-child(10) { display: none; }

  /* The button stretches across both tracks instead of declaring its own 40px. An
     fr track takes an automatic MINIMUM from its content, so a min-height here would
     pin row 2 at 40 and starve row 1 — the same collapse, by a different route.
     Spanning 46px of card gives a target taller than 40 for free. */
  .row .row-act { align-self: stretch; height: 100%; }
  .row .act-draft, .row .act-more { height: 100%; font-size: 11px; }

  /* The header is the ONLY sort control, so it cannot become a card of labels and
     it certainly cannot be hidden — sorting is a feature, not chrome. It becomes a
     wrapped strip of sort chips instead, same elements, same click handler. */
  .ba-hd {
    display: flex; flex-wrap: wrap; gap: 3px;
    height: auto; padding: 5px 8px;
  }
  .ba-hd > * {
    width: auto; text-align: left; font-size: 9.5px;
    padding: 0 8px; min-height: 30px; display: flex; align-items: center;
    border: 1px solid var(--line); border-radius: 999px;
  }
  /* Exactly the four keys whose columns this layout drops. A chip that reorders the
     board by something you cannot see is a puzzle, so the rule is simply: hidden
     column, hidden chip. The other nine sorts all still work. */
  .ba-hd > [data-sort="proj"], .ba-hd > [data-sort="bye"],
  .ba-hd > [data-sort="tier"], .ba-hd > [data-sort="delta"] { display: none; }

  /* Header: let the clock cells wrap instead of colliding. #clock-group clips its
     overflow at wider widths, which at 390px silently ate MY NEXT PICK. */
  #clock-group { flex-wrap: wrap; overflow: visible; gap: 10px 14px; }
  #hdr { gap: 6px; }
  .hdr-group { flex-wrap: wrap; }
  /* Its own line. margin-left:auto pins it right on a laptop; at 390px seven 40px
     buttons cannot share a line with anything, and the group was sizing to
     max-content and pushing `?` off the edge. */
  .hdr-actions { flex: 1 1 100%; margin-left: 0; }

  /* The list is the app. Everything above it — banner, plan strip, search, six
     position chips at touch size, the sort strip — was leaving one visible row. */
  #panel-best { min-height: 62vh; }
  #panel-roster, #panel-draft { min-height: 300px; }
  #hdr button, .banner button { min-height: 40px; padding: 6px 11px; }
  #run-alert { min-height: 40px; }
  /* Four upcoming teams is planning-ahead detail, and it is the cell that pushes
     MY NEXT PICK off the line. The board itself still shows the full order. */
  #clock-next, .clock-cell:has(#clock-next) { display: none; }

  #search { font-size: 16px; min-height: 44px; }   /* 16px: anything less zooms iOS */
  .chip, .toggle { min-height: 40px; padding: 0 12px; display: inline-flex; align-items: center; font-size: 12.5px; }
  #plan-toggle { min-height: 40px; }
  .plan-cell { min-width: 150px; scroll-snap-align: start; }
  #plan-cells { scroll-snap-type: x proximity; }

  /* auto-fit at minmax(190px,…) already collapses to one column here, but say it
     outright rather than depending on a minimum that a future edit might raise. */
  .deep-inner { grid-template-columns: 1fr; }
  .setup-cols, .scoring-editor { grid-template-columns: 1fr; }
  .grid-editor { grid-template-columns: repeat(2, 1fr); }
}

/* A tablet keeps the full 13-column row — it has the width for it — but it is still
   a finger, and the draft button measures 18px there. This raises the row and the
   button without touching the layout, so a coarse pointer between 561px and full
   desktop width gets a target it can actually hit. Mice are unaffected: the query
   is about the input device, not the viewport. */
@media (pointer: coarse) and (min-width: 561px) {
  :root { --row-h: 38px; }
  .act-draft, .act-more { min-height: 30px; }
  .chip, .toggle { min-height: 32px; display: inline-flex; align-items: center; }
  #hdr button { min-height: 34px; }
}

/* Respect the OS setting, and note that nothing here animates for longer than
   90 ms anyway (§11). */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* Movement badge, rendered inside the name cell (see buildRow — no 14th grid column). */
.trend { font-size: 10px; line-height: 1; }
.trend.up   { filter: saturate(1.2); }
.trend.down { opacity: .85; }

/* ─────────────────────── the pick (recommendation strip) ───────────────────────
   One band, full width, directly under the banners — above the plan strip because it
   answers the shorter question. Everything in it is --turn accented, so the same
   glance that reads WHO tells you WHETHER: green and "TAKE" means act, orange and
   "TARGET" means read.

   Collapsible and persisted like the plan strip, and for the same reason: vertical
   space on a 1280×800 laptop is the scarcest resource in this layout. Unlike the plan
   it is CHEAP to compute (it reads the pool derive() already ranked), so collapsing it
   is purely about pixels. */
#rec-strip {
  flex: 0 0 auto;
  display: flex; align-items: stretch; gap: 0;
  background: var(--bg-1);
  border-bottom: 1px solid var(--line);
  border-left: 3px solid var(--turn);
  transition: border-color var(--fast) linear;
}
#rec-strip[hidden] { display: none; }
#rec-strip.is-closed .rec-main,
#rec-strip.is-closed .rec-act { display: none; }

/* Turn block. Fixed width so the recommendation beside it never reflows as the
   countdown ticks from "3 away" to "2 away". */
.rec-turn {
  flex: 0 0 auto; width: 132px;
  display: flex; flex-direction: column; justify-content: center; gap: 1px;
  padding: 5px 10px;
  border-right: 1px solid var(--line-soft);
  background: color-mix(in srgb, var(--turn) 9%, transparent);
}
.rec-turn-lbl {
  font-size: 11px; font-weight: 800; letter-spacing: .1em;
  color: var(--turn); white-space: nowrap;
}
.rec-turn-sub { font-size: 10.5px; color: var(--fg-dim); font-family: var(--mono); white-space: nowrap; }

/* The badge only exists when it is actually your pick, so a pulse here can never be
   ambient noise — it is on screen for exactly as long as the thing it is announcing.
   2s is deliberately OUTSIDE the §11 90ms cap, which governs the pick path: this is
   not on the pick path, it IS the pick path's start gun. Killed entirely under
   prefers-reduced-motion by the global rule above. */
:root[data-turn="me"] .rec-turn-lbl { animation: turn-pulse 2s ease-in-out infinite; }
@keyframes turn-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

.rec-main {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; justify-content: center; gap: 2px;
  padding: 5px 12px;
}
.rec-hd { display: flex; align-items: baseline; gap: 7px; min-width: 0; flex-wrap: wrap; }
.rec-kicker {
  font-size: 9.5px; font-weight: 800; letter-spacing: .1em;
  color: var(--turn); flex: 0 0 auto;
}
/* A button, so the name is reachable by keyboard and announced as the control it is —
   but it must not LOOK like one sitting inside a headline. */
.rec-name {
  background: none; border: none; border-radius: 0; padding: 0;
  font: inherit; font-size: 15px; font-weight: 700; color: var(--fg);
  cursor: pointer; text-align: left;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.rec-name:hover { background: none; color: var(--turn); }
.rec-meta { font-size: 10.5px; color: var(--fg-mute); font-family: var(--mono); white-space: nowrap; }
.rec-why {
  display: flex; gap: 4px 14px; flex-wrap: wrap;
  font-size: 11px; color: var(--fg-dim); min-width: 0;
}
/* Nowrap per chip, wrap between them: a reason that breaks across two lines mid-number
   is slower to read than one that moves to the next row whole. */
.rec-chip { white-space: nowrap; }
.rec-why b { color: var(--fg); font-weight: 650; font-variant-numeric: tabular-nums; }
.rec-why .rec-warn { color: var(--warn); }
.rec-why .rec-good { color: var(--good); }
.rec-hd .pos-tag { font-size: 10px; padding: 1px 5px; border-radius: 3px; }
/* Alternates read as a footnote, never as a second recommendation. */
.rec-alts { font-size: 10.5px; color: var(--fg-mute); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rec-alts button {
  background: none; border: none; padding: 0 2px; font: inherit;
  color: var(--fg-dim); text-decoration: underline dotted; cursor: pointer;
}
.rec-alts button:hover { color: var(--fg); background: none; }

.rec-act { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; padding: 5px 10px; }
#rec-draft {
  font-weight: 700; padding: 6px 12px;
  border: 1px solid var(--turn);
  background: color-mix(in srgb, var(--turn) 16%, transparent);
  color: var(--turn);
}
#rec-draft:hover:not(:disabled) { background: color-mix(in srgb, var(--turn) 30%, transparent); }
#rec-draft:focus-visible { outline: 2px solid var(--turn); outline-offset: 1px; }
.rec-empty { padding: 8px 12px; font-size: 11.5px; color: var(--fg-mute); }
#rec-toggle {
  flex: 0 0 auto; align-self: stretch;
  background: none; border: none; border-radius: 0; border-left: 1px solid var(--line-soft);
  padding: 0 8px; color: var(--fg-mute); font-size: 9px;
}
#rec-toggle:hover { background: var(--bg-2); }
#rec-strip.is-closed #rec-toggle .pl-caret { transform: rotate(-90deg); }

/* 880 is where the layout already goes single-column, and this strip has the same
   problem the header does at that width: a 132px gutter plus a name plus a button do
   not fit on one line. Reusing the existing breakpoint rather than inventing a fourth
   keeps the ladder to three rungs. */
@media (max-width: 880px) {
  #rec-strip { flex-wrap: wrap; }
  .rec-turn {
    width: auto; flex: 1 1 100%;
    flex-direction: row; align-items: baseline; gap: 8px;
    border-right: none; border-bottom: 1px solid var(--line-soft);
  }
  .rec-main { flex: 1 1 200px; }
  .rec-act { flex: 0 0 auto; }
}
@media (max-width: 560px) {
  .rec-act { flex: 1 1 100%; }
  #rec-draft { width: 100%; min-height: 40px; }   /* the phone touch-target floor */
  /* Both of these are duplicated within a thumb's reach: team/bye/ADP are columns in
     the row immediately below, and picking an alternate is what scrolling is for.
     Dropping them takes the strip from ~193px to ~140px on a 390px screen, which is
     the difference between the board being visible under it and not. */
  .rec-meta, .rec-alts { display: none; }
}

/* ─────────────────────── draft plan strip ─────────────────────── */
/* Full-width, between #banners and #grid. flex:0 0 auto keeps it out of the panels'
   vertical budget arithmetic, and it needs no grid changes at any breakpoint. */
#plan-strip {
  flex: 0 0 auto;
  background: var(--bg-1);
  border-bottom: 1px solid var(--line);
}
#plan-toggle {
  display: flex; align-items: center; gap: 8px;
  width: 100%; background: none; border: none; border-radius: 0;
  padding: 4px 12px; text-align: left;
  font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--accent);
}
#plan-toggle:hover { background: var(--bg-2); }
.pl-caret { transition: transform var(--fast) linear; font-size: 9px; }
#plan-strip.is-closed .pl-caret { transform: rotate(-90deg); }
#plan-strip.is-closed #plan-cells { display: none; }
#plan-meta { text-transform: none; letter-spacing: 0; font-weight: 400; font-size: 11px; }

#plan-cells {
  display: flex; gap: 6px; overflow-x: auto; overflow-y: hidden;
  padding: 0 12px 7px;
}
.plan-cell {
  flex: 0 0 auto; min-width: 128px;
  padding: 5px 8px; border-radius: 5px;
  background: var(--bg-2); border: 1px solid var(--line);
  border-left: 3px solid var(--line);
}
.plan-cell .pl-hd { font-size: 9.5px; color: var(--fg-mute); font-family: var(--mono); }
.plan-cell .pl-pos { font-size: 13px; font-weight: 700; }
.plan-cell .pl-alt { font-size: 10px; color: var(--fg-mute); }
.plan-cell .pl-who { font-size: 10.5px; color: var(--fg-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The next pick disagreeing with the live board is the one thing worth interrupting for. */
.plan-cell.is-now { background: var(--bg-3); }
.plan-cell.disagree { border-left-color: var(--warn); }
.plan-cell.disagree .pl-pos::after { content: " ⚠"; color: var(--warn); font-size: 10px; }
.plan-empty { padding: 2px 12px 7px; font-size: 11px; color: var(--fg-mute); }

/* ─────────────────────── post-draft report ─────────────────────── */
#report-body { max-height: 68vh; overflow: auto; padding-right: 4px; }

/* The verdict. First thing in the sheet and the only part that leaves this screen —
   a rank alone does not travel, so the share of the leader sits beside it at the size
   that makes the pair readable in a screenshot. */
.rep-verdict {
  display: flex; align-items: center; gap: 14px;
  padding: 10px 12px; margin-bottom: 6px;
  background: var(--bg-2); border-radius: 7px;
  border-left: 3px solid currentColor;
}
/* Terciles, so an 8-team league reads the same way as a 14. Deliberately NOT --turn:
   the draft being over resolves that to green, and a team finishing last should not be
   told so in the colour the board uses for good news. */
.rep-verdict.rv-t1 { color: var(--good); }
.rep-verdict.rv-t2 { color: var(--accent); }
.rep-verdict.rv-t3 { color: var(--warn); }
/* The tier colour lives on .rep-verdict and reaches the rank by inheritance, so the rail
   and the numeral cannot drift apart. Everything else re-asserts a normal text colour —
   only the rank is being graded. */
/* The grade is the screenshot. Everything else in this block is the caption under it,
   which is why it gets the size and the rest does not. */
.rv-grade {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  min-width: 62px; height: 62px; border-radius: 9px;
  background: color-mix(in srgb, currentColor 13%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 34%, transparent);
  cursor: help;
}
.rv-grade strong { font-size: 30px; font-weight: 800; letter-spacing: -.03em; line-height: 1; }
.rv-body { min-width: 0; color: var(--fg); }
.rv-share { font-size: 15px; font-weight: 640; }
.rv-sub { font-size: 11.5px; margin-top: 3px; }
.rv-good { color: var(--good); }
.rv-bad  { color: var(--warn); }
.rep-row {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 2px 0; font-size: 12px; border-bottom: 1px solid var(--line-soft);
}
.rep-row.is-warn span:last-child { color: var(--warn); }
.rep-row.is-total { font-weight: 700; border-bottom: none; margin-top: 4px; }
.rep-ok { font-size: 12px; color: var(--turn); }

/* ── wins and blunders ──
   One shape for both, because they are the same measurement seen from two sides: what
   a pick did for the lineup versus what the best available fit would have done. Giving
   them separate layouts would imply the report judged them differently. Only the rail
   colour and the sign of the number change. */
.rep-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 20px; align-items: start; }
.pk-list { display: flex; flex-direction: column; gap: 5px; }
.pk {
  padding: 6px 9px; border-radius: 5px;
  background: var(--bg-2); border-left: 2px solid currentColor;
}
.pk-win     { color: var(--good); }
.pk-blunder { color: var(--warn); }
.pk-hd { display: flex; align-items: baseline; gap: 7px; }
.pk-slot { font: 600 9.5px/1 var(--mono); letter-spacing: .06em; color: var(--fg-mute); flex: 0 0 auto; }
.pk-name { font-size: 12.5px; font-weight: 650; color: var(--fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pk-metric { margin-left: auto; font: 700 12.5px/1 var(--mono); flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.pk-why { font-size: 11px; line-height: 1.45; color: var(--fg-dim); margin-top: 3px; }
.pk-why strong { color: var(--fg); font-weight: 650; }

/* ── the room ──
   A table, not cards: twelve teams is where a card grid stops being scannable and the
   only thing anybody does here is compare one column down the page. */
.room-wrap { overflow-x: auto; }
table.room { border-collapse: collapse; width: 100%; font-variant-numeric: tabular-nums; }
table.room th {
  font: 600 9px/1 var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--fg-mute); text-align: right; padding: 0 5px 6px; white-space: nowrap;
}
table.room th.t-l, table.room td.t-l { text-align: left; }
table.room td { padding: 4px 5px; border-top: 1px solid var(--line-soft); text-align: right; white-space: nowrap; font-size: 11.5px; }
table.room tr.is-me td { background: color-mix(in srgb, var(--turn) 9%, transparent); }
table.room tr.is-me .rm-tm { color: var(--fg); font-weight: 700; }
.rm-rk { font: 700 12px/1 var(--sans); color: var(--fg-dim); width: 22px; }
.rm-gr {
  display: inline-block; min-width: 26px; padding: 2px 4px; border-radius: 3px;
  font: 700 10.5px/1 var(--mono); text-align: center;
  background: var(--bg-3); color: var(--fg-dim);
}
tr.is-me .rm-gr { background: color-mix(in srgb, var(--turn) 22%, transparent); color: var(--turn); }
.rm-tm { color: var(--fg-dim); max-width: 150px; overflow: hidden; text-overflow: ellipsis; }
.rm-lv, .rm-sp { font-family: var(--mono); width: 44px; }
.rm-sp { color: var(--warn); }
/* .rb-track is authored for the flex .rep-bar rows, where `flex: 1 1 auto` gives it a
   size. Inside a table cell that declaration does nothing and the span stays inline, so
   its height collapses and the bar disappears — it has to be told to be a block here. */
.rm-bar { width: 90px; }
.rm-bar .rb-track { display: block; width: 100%; height: 6px; }
tr.is-me .rm-bar .rb-fill { background: var(--turn); }
.rm-w em, .rm-b em { font-style: normal; font-family: var(--mono); font-size: 10.5px; color: var(--fg-mute); }
.rm-w, .rm-b { max-width: 150px; overflow: hidden; text-overflow: ellipsis; color: var(--fg-dim); }
.rm-b { cursor: help; }

/* ── folded reference ──
   The lineup and the full pick list are reference, not the story, so they open on
   demand. <details> rather than a custom disclosure: it is keyboard-operable and
   findable by in-page search even while closed, both of which a div is not. */
.rep-fold { border-top: 1px solid var(--line-soft); margin-top: 10px; }
.rep-fold > summary {
  cursor: pointer; padding: 8px 0 6px; list-style-position: inside;
  font-size: 10px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--fg-dim); font-weight: 650;
}
.rep-fold > summary:hover { color: var(--fg); }
.rep-fold > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rep-lineup { display: flex; flex-direction: column; gap: 1px; padding-bottom: 6px; }
.rl-row { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 2px 0; border-bottom: 1px solid var(--line-soft); }
.rl-slot { font: 700 9.5px/1 var(--mono); color: var(--fg-mute); letter-spacing: .07em; width: 40px; flex: 0 0 auto; }
.rl-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rl-bye { font: 400 10.5px/1 var(--mono); color: var(--fg-mute); flex: 0 0 auto; }
.rl-ev { font: 600 11.5px/1 var(--mono); width: 44px; text-align: right; flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.rl-ev.neg { color: var(--bad); }

@media (max-width: 720px) {
  .rep-cols { grid-template-columns: 1fr; gap: 4px; }
}

/* Lineup-value bars. Widths are set through the CSSOM by h()'s style path, never a
   style attribute — the CSP has no 'unsafe-inline'. */
.rep-bar { display: flex; align-items: center; gap: 8px; padding: 2px 0; font-size: 11.5px; }
.rep-bar.is-me { font-weight: 700; color: var(--turn); }
.rb-name  { flex: 0 0 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rb-track { flex: 1 1 auto; height: 8px; background: var(--bg-2); border-radius: 4px; overflow: hidden; }
.rb-fill  { display: block; height: 100%; background: var(--accent); }
.rep-bar.is-me .rb-fill { background: var(--turn); }
.rb-val   { flex: 0 0 46px; text-align: right; font-family: var(--mono); font-size: 11px; }

/* Column count is set through the CSSOM from league.teams — this is only the fallback. */
.rep-grid { display: grid; grid-template-columns: 44px repeat(12, 1fr) 42px; gap: 2px 1px; font-size: 10.5px; align-items: center; }
.rep-grid .rg-h { font-weight: 700; text-align: center; color: var(--fg-mute); font-size: 9.5px; }
.rep-grid .rg-h.is-me { color: var(--turn); }
.rep-grid .rg-k { font-weight: 700; color: var(--fg-dim); }
.rep-grid .rg-v { text-align: center; font-family: var(--mono); color: var(--fg-dim); }
.rep-grid .rg-v.is-me { color: var(--turn); font-weight: 700; }

/* ═══════════════════════════════ Sleeper sync ══════════════════════════════
   Sits above the manual league fields in the setup sheet. For a Sleeper user it
   replaces everything below it, so it is styled as a distinct card rather than as
   another section heading — the fast path should not look like one more form. */

.sync-box {
  margin: 0 0 18px;
  padding: 14px 16px;
  border: 1px solid var(--line, #232a35);
  border-radius: 10px;
  background: var(--panel-2, #11151c);
}

.sync-hd {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.sync-hd h3 { margin: 0; }

.sync-state {
  font-size: 12px;
  color: var(--muted, #8b94a3);
  /* The longest states are sentences ("Sleeper timed out after 5000ms"). Let them wrap
     rather than blowing out the sheet width. */
  text-align: right;
  max-width: 22em;
}
.sync-state.is-ok   { color: var(--good, #35c88b); }
.sync-state.is-bad  { color: var(--bad, #e5484d); }
.sync-state.is-warn { color: var(--warn, #e8a33d); }
.sync-state.is-busy { color: var(--muted, #8b94a3); }

.sync-row {
  display: flex;
  gap: 8px;
  margin-top: 10px;
  /* Wraps to two lines on a phone instead of shrinking the input to nothing. */
  flex-wrap: wrap;
}

.sync-row input,
.sync-row select {
  flex: 1 1 12em;
  min-width: 0;                 /* without this a flex child refuses to shrink */
}

.sync-row button { flex: 0 0 auto; }

.sync-warnings:empty { display: none; }

.sync-warn {
  margin: 8px 0 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--warn, #e8a33d);
}

/* ═══════════════════════════════ tier gating ═══════════════════════════════
   Pro features are BLURRED, never hidden. A free user who can see there is a number
   they cannot read has a reason to upgrade; one who sees an absence does not know
   anything is missing. See docs/PRODUCT.md §6. */

.kv.is-locked { cursor: help; }

.kv.is-locked .lock-val {
  /* A real blur rather than a placeholder glyph, so the shape of the data shows. */
  filter: blur(4px);
  opacity: 0.65;
  user-select: none;
  letter-spacing: 2px;
}

/* Nothing gated should animate or draw the eye harder than the board itself. */
@media (prefers-reduced-motion: reduce) {
  .kv.is-locked .lock-val { filter: blur(3px); }
}

/* ═══════════════════════════════ account ══════════════════════════════════ */

.acct-box {
  margin: 0 0 18px;
  padding: 14px 16px;
  border: 1px solid var(--line, #232a35);
  border-radius: 10px;
  background: var(--panel-2, #11151c);
}

/* A button that reads as a link — for destructive-but-minor actions like sign out,
   which should be reachable without competing with the buy buttons beside it. */
.linkish {
  background: none;
  border: none;
  color: var(--muted, #8b94a3);
  text-decoration: underline;
  padding: 0 4px;
  cursor: pointer;
}
.linkish:hover { color: var(--fg, #e6e9ef); }

/* ══════════════════════════════════════ the gate ═════════════════════════════
 * The pre-board sign-in screen, and the ONLY decorative surface in this product.
 *
 * Why the austerity rule is suspended for exactly this element: the frame budget
 * governs the PICK PATH. This is the one screen in Surplus where nobody is on a
 * 60-second clock — they are reading one sentence and typing an email. The moment
 * the board mounts, this element is hidden and the product goes back to being plain.
 *
 * The picture is an end-zone camera at field level, at night, lights up. Yard lines
 * recede to a vanishing point at 50%/34%; the sidelines and the two hash rows are
 * rays from that same point. There is no 3D transform — the convergence is baked
 * into hand-placed gradient stops from y = 34 + 62/d with d = 1 + 0.34n, so this is
 * one element, one paint, no transformed geometry, and no animation at all.
 *
 * NOTHING HERE IS GREEN, ON PURPOSE. --turn-me is "it is your pick", the most loaded
 * colour in the app. A green turf field would spend that signal on wallpaper. The
 * field is lit in the app's own chrome instead: a night field under blue floodlight.
 * ========================================================================== */

.gate {
  position: fixed; inset: 0; z-index: 300;   /* over .overlay (100) and .tour (200) */
  overflow: auto;              /* html,body are overflow:hidden — the gate must carry
                                  its own scroll or a tall card clips with no bar */
  background: var(--bg);       /* opaque: the board behind is fully occluded */

  /* Flex + `margin: auto` on the child, NOT `place-items: center`. A centred grid
     item that outgrows its container overflows EQUALLY in both directions, so the
     top of the card ends up above the scroll origin and is unreachable — which is
     any viewport under ~500px tall, i.e. a phone in landscape. Auto margins centre
     when there is room and collapse to the start edge when there is not. */
  display: flex;
  justify-content: center;
  padding: 20px;
}

/* The author `display: flex` above beats the UA [hidden] rule. Without this line
   `gate.hidden = true` silently does nothing and the board is unreachable forever. */
.gate[hidden] { display: none; }

/* ── the field ──────────────────────────────────────────────────────────────
   Seven background layers on one element, first-listed painting on top:
     1 dither   — 140px feTurbulence tile at 5%. NOT texture: it is the anti-banding
                  fix. Every layer under it is a large low-contrast gradient, and
                  8-bit panels band on those in visible steps.
     2 scrim    — the legibility guarantee. Kills the field where the card sits.
     3 bloom    — floodlight spill along the top edge, --accent at 7%.
     4 wash     — paints --bg back over the far field so the horizon dissolves
                  instead of ending on a hard edge, and covers the band around the
                  vanishing point where thin conic wedges alias worst.
     5 rays     — 2 sidelines + 2 hash rows as rays from the vanishing point. A conic
                  gradient is the right primitive: its wedges widen away from the
                  centre, which IS perspective when the centre is the vanishing point.
     6 yards    — hand-placed yard lines, spacing and thickness both scaling as 1/d.
     7 turf     — the field reads as a surface, not sky, by sitting one step up the
                  ramp toward --bg-1 below the horizon.
   ─────────────────────────────────────────────────────────────────────────── */
.gate-bg {
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none;

  /* The two ray angles are the whole geometry and ARE meant to be overridden per
     breakpoint. The vanishing point is not: 34% is hand-baked into every yard-line
     stop below, so a token would let someone move the horizon and silently desync
     the yard lines from the rays, with no error and nothing to catch it. */
  --ray-out:   61deg;      /* sidelines, off the field axis */
  --ray-in:    11.85deg;   /* hash rows: 18'6" apart on a 160' field, to scale */

  /* Line weights are set so the field is legible as a PICTURE at arm's length on a
     1440px display, which is the whole reason this screen exists. Tuned against a
     screenshot, not guessed: at --line 72% the yard lines disappeared into the
     background and the composition read as a flat dark panel with a gradient. */
  --fld-yard:  color-mix(in srgb, var(--line) 100%, transparent);
  --fld-side:  color-mix(in srgb, var(--line) 82%, transparent);
  --fld-hash:  color-mix(in srgb, var(--line-soft) 100%, transparent);
  --fld-turf:  color-mix(in srgb, var(--bg-1) 70%, transparent);
  --fld-bloom: color-mix(in srgb, var(--accent) 7%, transparent);
  --fld-scrim: color-mix(in srgb, var(--bg) 90%, transparent);
  --fld-mid:   color-mix(in srgb, var(--bg) 58%, transparent);

  /* Fourteen lines from y = 34 + 62/d, d = 1 + 0.34n, thickness = 0.30/d, each with
     a 0.05% feather so the far ones antialias into dimness instead of flickering.
     Do NOT tidy this into a repeating-linear-gradient: even spacing is exactly what
     makes a perspective field look like a floor tile instead of a field. */
  --fld-yards: linear-gradient(to bottom,
    transparent 0%,
    transparent 45.36%, var(--fld-yard) 45.41%, var(--fld-yard) 45.47%, transparent 45.52%,
    transparent 46.12%, var(--fld-yard) 46.17%, var(--fld-yard) 46.23%, transparent 46.28%,
    transparent 47.00%, var(--fld-yard) 47.05%, var(--fld-yard) 47.11%, transparent 47.16%,
    transparent 48.01%, var(--fld-yard) 48.06%, var(--fld-yard) 48.12%, transparent 48.17%,
    transparent 49.18%, var(--fld-yard) 49.23%, var(--fld-yard) 49.31%, transparent 49.36%,
    transparent 50.58%, var(--fld-yard) 50.63%, var(--fld-yard) 50.71%, transparent 50.76%,
    transparent 52.25%, var(--fld-yard) 52.30%, var(--fld-yard) 52.38%, transparent 52.43%,
    transparent 54.29%, var(--fld-yard) 54.34%, var(--fld-yard) 54.44%, transparent 54.49%,
    transparent 56.85%, var(--fld-yard) 56.90%, var(--fld-yard) 57.02%, transparent 57.07%,
    transparent 60.16%, var(--fld-yard) 60.21%, var(--fld-yard) 60.33%, transparent 60.38%,
    transparent 64.57%, var(--fld-yard) 64.62%, var(--fld-yard) 64.76%, transparent 64.81%,
    transparent 70.76%, var(--fld-yard) 70.81%, var(--fld-yard) 70.99%, transparent 71.04%,
    transparent 80.11%, var(--fld-yard) 80.16%, var(--fld-yard) 80.38%, transparent 80.43%,
    transparent 95.80%, var(--fld-yard) 95.85%, var(--fld-yard) 96.15%, transparent 96.20%,
    transparent 100%);

  background-color: var(--bg);

  background-image:
    /* 1 — dither. color-interpolation-filters='sRGB' is load-bearing: without it
           feTurbulence and the saturate matrix run in the SVG default linearRGB,
           the grain paints far brighter than the stated 5%, and it differs between
           Chrome, Safari and Firefox. */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.05'/%3E%3C/svg%3E"),

    /* 2 — scrim: the card sits in this hole. Sized to just outrun the 420px card
           rather than to cover the viewport — at 112%/84% it wiped the field across
           most of a desktop screen and there was nothing left to look at. The card
           is opaque, so this is about keeping the field from crowding its edges, not
           about text contrast, which the card guarantees on its own. */
    radial-gradient(64% 58% at 50% 47%,
      var(--fld-scrim) 0%, var(--fld-scrim) 22%,
      var(--fld-mid) 44%, transparent 76%),

    /* 3 — floodlight spill */
    radial-gradient(120% 34% at 50% -2%,
      var(--fld-bloom) 0%, transparent 74%),

    /* 4 — horizon dissolve. Held opaque to 36% rather than 30% so it fully covers
           the vanishing point at 34%, where the 0.10deg conic wedges fall under one
           device pixel and stair-step across the gradient axis. */
    linear-gradient(to bottom,
      var(--bg) 0%, var(--bg) 36%, transparent 64%),

    /* 5 — sidelines + hash rows, as rays from the vanishing point */
    conic-gradient(from 0deg at 50% 34%,
      transparent 0deg,
      transparent     calc(180deg - var(--ray-out) - 0.12deg),
      var(--fld-side) calc(180deg - var(--ray-out) - 0.05deg),
      var(--fld-side) calc(180deg - var(--ray-out) + 0.05deg),
      transparent     calc(180deg - var(--ray-out) + 0.12deg),
      transparent     calc(180deg - var(--ray-in)  - 0.19deg),
      var(--fld-hash) calc(180deg - var(--ray-in)  - 0.09deg),
      var(--fld-hash) calc(180deg - var(--ray-in)  + 0.09deg),
      transparent     calc(180deg - var(--ray-in)  + 0.19deg),
      transparent     calc(180deg + var(--ray-in)  - 0.19deg),
      var(--fld-hash) calc(180deg + var(--ray-in)  - 0.09deg),
      var(--fld-hash) calc(180deg + var(--ray-in)  + 0.09deg),
      transparent     calc(180deg + var(--ray-in)  + 0.19deg),
      transparent     calc(180deg + var(--ray-out) - 0.12deg),
      var(--fld-side) calc(180deg + var(--ray-out) - 0.05deg),
      var(--fld-side) calc(180deg + var(--ray-out) + 0.05deg),
      transparent     calc(180deg + var(--ray-out) + 0.12deg),
      transparent 360deg),

    /* 6 — yard lines */
    var(--fld-yards),

    /* 7 — the field is a surface, the sky is not */
    linear-gradient(to bottom, transparent 34%, var(--fld-turf) 100%);

  background-repeat: repeat, no-repeat, no-repeat, no-repeat, no-repeat, no-repeat, no-repeat;
  background-size: 140px 140px, auto, auto, auto, auto, auto, auto;
}

/* Conic angles are real screen angles, so the field's apparent width follows the
   viewport aspect. On a phone the 61deg sidelines exit the frame entirely and only
   the hash rows carry the convergence, which reads as pinched — so tighten the lens
   and the sidelines come back through the bottom corners.
   The four farthest yard lines are DROPPED here for a separate reason: at 34% of an
   844px screen they land inside about 23 CSS px, roughly 6px apart at half-pixel
   thickness, and alias into a shimmer band rather than reading as distance. */
@media (max-width: 700px) {
  .gate-bg {
    --ray-out: 26deg;
    --ray-in: 7deg;
    --fld-yards: linear-gradient(to bottom,
      transparent 0%,
      transparent 49.18%, var(--fld-yard) 49.23%, var(--fld-yard) 49.31%, transparent 49.36%,
      transparent 50.58%, var(--fld-yard) 50.63%, var(--fld-yard) 50.71%, transparent 50.76%,
      transparent 52.25%, var(--fld-yard) 52.30%, var(--fld-yard) 52.38%, transparent 52.43%,
      transparent 54.29%, var(--fld-yard) 54.34%, var(--fld-yard) 54.44%, transparent 54.49%,
      transparent 56.85%, var(--fld-yard) 56.90%, var(--fld-yard) 57.02%, transparent 57.07%,
      transparent 60.16%, var(--fld-yard) 60.21%, var(--fld-yard) 60.33%, transparent 60.38%,
      transparent 64.57%, var(--fld-yard) 64.62%, var(--fld-yard) 64.76%, transparent 64.81%,
      transparent 70.76%, var(--fld-yard) 70.81%, var(--fld-yard) 70.99%, transparent 71.04%,
      transparent 80.11%, var(--fld-yard) 80.16%, var(--fld-yard) 80.38%, transparent 80.43%,
      transparent 95.80%, var(--fld-yard) 95.85%, var(--fld-yard) 96.15%, transparent 96.20%,
      transparent 100%);
  }
}

/* Someone who asked the OS for more contrast is not asking for a low-contrast
   hairline drawing behind a password field. The whole composition lives inside
   about fifteen 8-bit levels of --bg, so the honest response is to remove it. */
@media (prefers-contrast: more) {
  .gate-bg { background-image: none; }
}

/* Nothing here animates, so the reduced-motion rules elsewhere in this file have
   nothing to switch off and this screen is byte-identical for those users. That is
   deliberate: a background that only reads as finished while it is moving is broken
   for the people who asked it to stop moving. */

/* The card. Opaque, so no field pixel can touch the contrast of any form label:
   --bg-1 #111721 on the brightest adjacent field pixel measures 1.48:1 for the 1px
   --line border, against 1.51:1 on bare --bg — a 2% change. The border carries the
   card's edge, not fill contrast, which is why the scrim only has to be close. */
.gate-card {
  position: relative; z-index: 1;
  margin: auto;                /* see the note on .gate: this is the no-clip centring */
  width: min(420px, 100%);
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 22px 22px 18px;
  box-shadow: 0 20px 60px #000a;
}

.gate-brand {
  font-size: 20px; font-weight: 700; letter-spacing: -.01em;
  margin: 0 0 4px; color: var(--fg);
}
.gate-tag {
  margin: 0 0 18px; font-size: 12px; line-height: 1.5; color: var(--fg-dim);
}
.gate-field { display: block; margin: 0 0 10px; }
.gate-field > span {
  display: block; font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--fg-mute); font-weight: 700; margin: 0 0 4px;
}

/* 16px and 44px are not taste. Under 16px iOS zooms the page on focus, and 44px is
   the platform touch minimum. styles.css already enforces both on #search, but that
   rule is id-scoped to board chrome and does not reach here — and this is the FIRST
   screen of the product, on a phone, with a password field. */
.gate-input {
  width: 100%; box-sizing: border-box;
  font: 500 16px/1.2 var(--sans);
  min-height: 44px; padding: 10px 12px;
  color: var(--fg); background: var(--bg); border: 1px solid var(--line);
  border-radius: 8px;
}
.gate-input:focus { outline: 2px solid var(--accent); outline-offset: -1px; }

.gate-actions { display: flex; gap: 8px; margin: 14px 0 0; }
.gate-actions button { flex: 1 1 0; min-height: 44px; font-size: 13px; }

.gate-alt {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin: 14px 0 0; padding: 12px 0 0; border-top: 1px solid var(--line-soft);
}
.gate-state { min-height: 16px; margin: 10px 0 0; font-size: 11px; }

/* The escape hatch, and it is deliberately a real, plain, obvious control rather
   than fine print. The board is free and works with no account at all; hiding that
   to farm signups would be a lie about the product. */
.gate-skip {
  background: none; border: none; padding: 4px;
  color: var(--fg-dim); font-size: 12px; text-decoration: underline; cursor: pointer;
}
.gate-skip:hover { color: var(--fg); }
