/* Draft tracker GUI — "The Board" brand: dark nebula, frosted glass,
   violet→red aurora. Aesthetics only; layout behavior unchanged.
   Tokens rebaselined 2026-08-02 from design_handoff_design_system/ds/tokens
   — those files are canonical; keep this :root in sync with them. */

:root {
  /* ---- Base: violet-cast near-blacks (never pure black) ---- */
  --bg-0: oklch(0.14 0.022 300);
  --bg-1: oklch(0.18 0.028 298);
  --bg-2: oklch(0.22 0.032 296);
  --bg-3: oklch(0.27 0.036 295);

  /* ---- Foreground ---- */
  --fg-1: oklch(0.97 0.008 300);
  --fg-2: oklch(0.80 0.025 298);
  --fg-3: oklch(0.68 0.035 296);
  --fg-4: oklch(0.58 0.035 296);

  /* ---- Spectrum accents: shared L/C, varied hue ---- */
  --blue:    oklch(0.66 0.24 262);
  --violet:  oklch(0.66 0.24 295);
  --red:     oklch(0.62 0.24 20);
  --hot:     oklch(0.70 0.23 35); /* gradient ends only, never a fill */

  --blue-bright:   oklch(0.79 0.16 262);
  --violet-bright: oklch(0.79 0.16 295);
  --red-bright:    oklch(0.76 0.17 20);

  --blue-deep:   oklch(0.38 0.15 262);
  --violet-deep: oklch(0.40 0.17 295);
  --red-deep:    oklch(0.34 0.15 20);

  /* ---- Semantic ---- */
  --accent: var(--violet);
  --accent-hover: oklch(0.72 0.24 295);
  --accent-press: oklch(0.60 0.24 295);
  --text-on-accent: oklch(0.985 0.01 300);
  --link: var(--violet-bright);
  --link-hover: oklch(0.87 0.10 295);

  --success: oklch(0.66 0.15 165);
  --warning: oklch(0.75 0.14 80);
  --danger:  oklch(0.62 0.19 15);

  /* ---- Borders & glass ---- */
  --border-1: color-mix(in oklab, white 11%, transparent);
  --border-2: color-mix(in oklab, white 18%, transparent);
  --border-accent: color-mix(in oklab, var(--violet) 45%, transparent);
  --glass-1: color-mix(in oklab, oklch(0.85 0.05 295) 9%, transparent);
  --glass-2: color-mix(in oklab, oklch(0.85 0.05 295) 14%, transparent);

  /* ---- Gradients: one family, violet → red ---- */
  --grad-aurora: linear-gradient(118deg, var(--blue-deep) 0%, var(--violet) 34%, var(--red) 78%, var(--hot) 106%);
  --grad-aurora-deep: linear-gradient(160deg, var(--violet-deep), var(--red-deep) 70%, oklch(0.30 0.12 35));
  /* The aurora as a FILL THAT CARRIES A LABEL (row 40; owner, 2026-10-01 for
     the primary, 2026-10-03 for the other five): a flat 20% of --bg-0 laid
     OVER --grad-aurora, under the text. A white 13px label measured 3.33:1
     against the bare aurora (painted, worst case: its warm end is far lighter
     than its average and the label crosses it); with this it is 4.75:1, over
     AA's 4.5:1. Not a sixth gradient: the same aurora and the page ground,
     composed here once, so a fill that takes it cannot disagree with another.
     Read by `button.primary, a.primary`, the active toggle segment, the
     current menu row, a picked Trades row, the MOCK badge, the on-the-clock
     cell and the native picker's chosen option (UI_GUIDELINES D20); any element that sets a bare `--grad-aurora`
     background under a label is the defect `AuroraFillContractTest` fails. App
     composition of design-system tokens: it is not in ds/tokens. */
  --aurora-fill:
    linear-gradient(color-mix(in oklab, var(--bg-0) 20%, transparent),
                    color-mix(in oklab, var(--bg-0) 20%, transparent)),
    var(--grad-aurora);
  --grad-text: linear-gradient(100deg, var(--violet-bright), var(--red-bright) 58%, oklch(0.80 0.17 30));
  /* Dense data surfaces. The design system says dense views drop off glass
     so glass stays meaningful; this keeps them dark and readable while still
     carrying the house violet -> red, rather than going flat near-black.
     Built from --violet-deep and --red-deep so it stays inside the one
     gradient family. */
  --grad-panel: linear-gradient(160deg,
    color-mix(in oklab, var(--violet-deep) 30%, var(--bg-0)) 0%,
    var(--bg-0) 55%,
    color-mix(in oklab, var(--red-deep) 22%, var(--bg-0)) 100%);
  --grad-nebula:
    radial-gradient(130% 140% at 10% -10%, oklch(0.42 0.19 295 / .55) 0%, transparent 55%),
    radial-gradient(110% 120% at 90% 10%, oklch(0.42 0.19 20 / .42) 0%, transparent 55%),
    radial-gradient(150% 160% at 50% 118%, oklch(0.36 0.14 262 / .38) 0%, transparent 60%);
  --grad-chrome: conic-gradient(from 210deg at 50% 50%,
    oklch(0.70 0.16 295), oklch(0.78 0.14 20), oklch(0.76 0.15 35),
    oklch(0.91 0.04 320), oklch(0.66 0.18 285), oklch(0.70 0.16 295));
  --grad-steel: conic-gradient(from 40deg,#fdfeff 0%,#c7ccdd 5%,#6a7089 13%,#262a3e 21%,#9ba1b8 29%,#f4f6ff 37%,#ffffff 41%,#8a90a8 49%,#383d53 57%,#d4d9ea 65%,#ffffff 71%,#565c74 81%,#2c3044 87%,#cfd4e6 93%,#fdfeff 100%);
  --grain-rings: repeating-radial-gradient(closest-side,rgba(255,255,255,.06) 0 1.2px,rgba(10,12,24,.07) 1.2px 3px,rgba(255,255,255,.025) 3px 4.5px);

  /* ---- Effects ---- */
  --shadow-card:  0 10px 30px -10px oklch(0.05 0.03 295 / .50), 0 2px 8px oklch(0.05 0.03 295 / .30);
  --shadow-plane: 0 24px 48px -12px oklch(0.05 0.03 295 / .55), 0 4px 12px oklch(0.05 0.03 295 / .35);
  --shadow-float: 0 48px 96px -24px oklch(0.04 0.04 295 / .65), 0 12px 28px -8px oklch(0.04 0.04 295 / .45);
  --glow-violet: 0 0 52px -4px oklch(0.66 0.24 295 / .70);
  --glow-red:    0 0 52px -4px oklch(0.62 0.24 20 / .65);
  --glow-blue:   0 0 44px -6px oklch(0.66 0.24 262 / .55);
  --inset-glass: inset 0 1px 0 color-mix(in oklab, white 22%, transparent), inset 0 -1px 0 color-mix(in oklab, white 6%, transparent);
  --blur-glass: 28px;
  --ease-drift: cubic-bezier(.22, .8, .24, 1);
  --dur-quick: 160ms;
  --dur-drift: 480ms;

  /* ---- Spacing & radii ---- */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-8: 32px; --sp-10: 40px;
  --sp-12: 48px; --sp-16: 64px; --sp-20: 80px; --sp-24: 96px;
  --r-sm: 8px; --r-md: 12px; --r-lg: 16px; --r-xl: 24px; --r-pill: 999px;

  /* ---- Controls: one height, one border, one type pair ----
     Every button, segmented toggle, select, search box and field is built
     from these (see "THE CONTROL SYSTEM" below and UI_GUIDELINES.md §6).
     --ctl-h is 34px because that is the row height #board-controls and
     #info-head already reserve, and because a .btn-group lands on it
     exactly: --ctl-h-sm segments + --sp-1 of rail either side + a hairline.
     --ctl-h-sm is the D2 target floor, never lower. */
  --ctl-h: 34px;
  --ctl-h-sm: 24px;
  --ctl-pad-x: var(--sp-3);
  --ctl-radius: var(--r-md);
  /* a control boundary needs 3:1 (C3); --border-1 measures 1.38:1 */
  --ctl-border: var(--border-2);
  --ctl-font:       600 13px/1 var(--font-sans);
  --ctl-font-sm:    600 11px/1 var(--font-sans);
  --ctl-font-field: 400 13px/1 var(--font-sans);
  --ctl-font-num:   500 13px/1 var(--font-mono);
  /* the well, to --inset-glass's raised edge: a field is recessed */
  --inset-well: inset 0 1px 2px color-mix(in oklab, var(--bg-0) 70%, transparent),
                inset 0 -1px 0 color-mix(in oklab, white 6%, transparent);

  /* ---- Brand lockup reserve ----
     #brand-slot is position:fixed at left:4px, 290x108, on EVERY screen, so
     its box ends at x=294. A centred column of width W puts its text at
     (100vw - W)/2 + 16, which only clears 294 above 2*(294-16) + W. Those
     thresholds are written out per shell below; --lockup-clear is what a
     column drops by when it cannot clear the corner sideways. */
  --lockup-w: 290px;
  --lockup-h: 108px;
  --lockup-clear: 124px;   /* --lockup-h + --sp-4 */
  /* B8 (2026-09-29): the asset's ink starts 17.6% into its transparent
     frame (measured: alpha bbox x 0.176..0.824 of 2800), so a frame at
     left:4 put the visible logo ~55px in, off every page's gutter. The
     frame is placed so the INK sits on the page gutter instead -- a ratio
     of the asset, never a fixed offset (E2, E6). */
  --lockup-ink: 0.176;
  --lockup-x: calc(var(--sp-6) - var(--lockup-w) * var(--lockup-ink));

  /* ---- Season-page shell (see "SEASON PAGE LAYOUT" below) ----
     The season pages are fluid, not a centred fixed column: --season-max is
     the ceiling a 1440-1600px screen fills, --season-rail the width of a
     secondary column beside the main one. (--season-scrollport, the cap on
     the activity feed's in-card scroller, went with that scroller in B32:
     season tables grow the page now.) --season-sticky-top is where a
     season table's header sticks as the page scrolls: just under the
     lockup's band while the lockup is fixed (above 1150px), and the top
     of the viewport below that, where the lockup scrolls away with the
     page (see the .table-wrap block). */
  --season-max: 1600px;
  --season-rail: 320px;
  /* B34: at 1200px and up the lockup is the head of the menu pane, not
     over the content column, so a stuck header sticks at the top. Below
     that the compact bar (--nav-bar-h) may be over the top of the page,
     so it sticks under it (the drawer-mode block in NAVIGATION). */
  --season-sticky-top: 0px;

  /* ---- Navigation (B34, "NAVIGATION" below) ----
     --nav-w is the lockup's frame (--lockup-x + --lockup-w = 263px) plus
     the page gutter's overhang, so the lockup fits the pane's head with
     room to spare (contract-tested: at least the lockup's width + 4).
     --nav-row is a menu row's height: a control's, so 34px with a mouse
     and 44px under pointer: coarse (the touch block below re-sets
     --ctl-h). The scrim is a dim, deliberately unblurred (B44). */
  --nav-w: 300px;
  --nav-row: var(--ctl-h);
  --nav-bar-h: 56px;
  /* Menu's width floor: the account icon (B7) sits beside it, so its edge has
     to be known (a media query cannot read it: the phone threshold below is
     16 gutter + 64 + 8 + 44 + 8 + the lockup box's 247 = 387) */
  --nav-menu-w: 64px;
  --scrim: oklch(0.08 0.02 300 / .62);

  /* ---- Type ---- */
  --font-display: "Kanit", "Helvetica Neue", Helvetica, sans-serif; /* always italic 900 */
  --font-sans: "Geist", "Helvetica Neue", Helvetica, sans-serif;
  --font-mono: "Geist Mono", "SF Mono", ui-monospace, monospace;
  --text-hero:  italic 900 64px/1.04 var(--font-display);
  --text-h1:    italic 900 40px/1.1 var(--font-display);
  --text-h2:    italic 800 26px/1.2 var(--font-display);
  --text-h3:    600 17px/1.35 var(--font-sans);
  --text-body:  400 15px/1.55 var(--font-sans);
  --text-small: 400 13px/1.5 var(--font-sans);
  --text-label: 500 11px/1.2 var(--font-sans);   /* + --tracking-label, uppercase */
  --text-data:  500 15px/1.3 var(--font-mono);
  --text-data-lg: 500 28px/1.1 var(--font-mono);
  --tracking-label: 0.14em;
  --tracking-display: 0.01em;
  --display-slant: -8deg;
  --display-stretch: 1.5;

  /* ---- Legacy names the app CSS uses, mapped to the new palette ---- */
  --bg: var(--bg-0);
  --panel: var(--glass-1);
  --panel-2: var(--glass-2);
  --border: var(--border-1);
  --text: var(--fg-1);
  --muted: var(--fg-3);
  --good: var(--success);
  --warn: var(--warning);
  --bad: var(--danger);
  --qb: var(--red-bright); --rb: var(--blue-bright); --wr: var(--violet-bright);
  --te: var(--hot); --k: var(--fg-3); --def: var(--fg-3);
}

* { box-sizing: border-box; }
/* the display rules below (flex on #confirm/#modal) must not defeat [hidden] */
[hidden] { display: none !important; }
html, body { margin: 0; padding: 0; }
/* THE NEBULA ON ITS OWN FIXED LAYER (B44, 27a). It was `background-attachment:
   fixed` on the body, and a fixed canvas background is repainted by the main
   thread on every wheel step (Trades: 6.09 s of main-thread time for 200
   steps, against 0.95 s with the same gradient on a layer of its own). The
   gradient is the same three radials over `--bg-0`, drawn once into a
   viewport-sized layer the compositor holds still; every pixel of it is the
   same (UI_GUIDELINES section 2). The page's ground is `html`'s flat
   `--bg-0`, so an overscroll or a print still shows the brand's dark and
   never white. `z-index: -1` puts the layer above that ground and below
   everything else in the root stacking context (`body` makes no stacking
   context of its own, so keep it that way: a `z-index`, `transform` or
   `opacity` on `body` would bury the layer under `body`'s own background).
   The one visible change: Chromium draws text above a composited layer in
   grayscale rather than LCD sub-pixel anti-aliasing, so ClearType text on
   Windows is a shade lighter (owner's decision, 2026-10-02). */
html { background: var(--bg-0); }
body {
  color: var(--fg-1);
  font: 400 14px/1.45 var(--font-sans);
}
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background: var(--grad-nebula), var(--bg-0);
  will-change: transform;
}
/* iOS WebKit keeps what it had. It is believed not to honour `fixed` (it
   paints that background relative to the document, so the glow scrolls with
   the page), and the owner does not yet know which it is (B44 decision 4:
   "not sure, check on the phone"). Giving the phone a pinned layer would
   change how his phone looks without his say-so, so the old declarations
   stay for iOS alone (only iOS WebKit knows `-webkit-touch-callout`) and the
   layer above is switched off there. Once the phone has been looked at,
   deleting this block makes it match the desktop. */
@supports (-webkit-touch-callout: none) {
  html { background: none; }
  body {
    background: var(--grad-nebula), var(--bg-0);
    background-attachment: fixed;
  }
  body::before { content: none; }
}
/* scrollbars — brand recipe for every scroller, current and future:
   gradient thumb, transparent track inset so the thumb never rides a
   rounded corner (panes with header bands tighten the bounds below) */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb {
  /* quiet violet-grey per the reference — an aurora thumb would compete
     with the comp column and the CTAs (no ad-hoc gradients) */
  background: oklch(0.30 0.04 295);
  border-radius: 99px;
}
::-webkit-scrollbar-track { background: transparent; margin: 8px; }
::-webkit-scrollbar-corner { background: transparent; }

/* ============================================================================
   THE CONTROL SYSTEM — defined once here, used by every screen
   ----------------------------------------------------------------------------
   Owner feedback (2026-09-16): "we need to make sure all buttons, toggles,
   drop down menus, search bars, etc. conform to the new design we gave the
   panes. This did not seem to happen in the last review." The draft topbar
   alone was carrying four generations of button styling. Everything clickable
   or typeable in the app is now one of the families below; a screen adds a
   width or a position and nothing else.

   FAMILIES (written up as rules in UI_GUIDELINES.md §6)
     button           neutral action — the pane recipe put on a control: the
                      --grad-panel wash, a hairline, --inset-glass, no blur.
     button.primary   the thing the owner is meant to click (Owner Rule 2) —
                      the house gradient. One per card, row or screen.
     button.subtle    a quiet secondary standing BESIDE a primary, and never
                      the only action on a card (D1).
     button.danger    destructive or unrecoverable: Quit mock, Rebuild the
                      harness cells, delete a saved mock.
     button:disabled  the brand's disabled state, which until now was
                      specified and implemented nowhere: --fg-4 text, glass
                      held at level 1, no glow, cursor: not-allowed.
     .btn-sm          the same families at the dense inline size used inside
                      table rows, feeds and stamp lines. Still >= 24px (D2).
     .btn-icon        a square glyph-only control (close, the order arrows,
                      the watch star). Always carries an accessible name (G6).
     .btn-group       the segmented toggle — one component for the position
                      filter, the view and tab toggles, the settings tabs, the
                      pane config and the waiver filter. Active segment is the
                      gradient; inactive segments are the pane recipe.

   GEOMETRY
     --ctl-h is the one control height, so a button, a toggle, a select and a
     search box sit on the same baseline in a row. It is 34px because that is
     the row height #board-controls and #info-head already reserve. A
     .btn-group measures exactly --ctl-h as well: --ctl-h-sm segments plus
     --sp-1 of rail either side plus its hairline. Press never shrinks.

   BORDERS
     A control boundary takes --border-2, not --border-1: C3 puts the floor at
     3:1 and --border-1 measures 1.38:1, while white at 18% over --bg-0 clears
     it. --border-1 stays the hairline for panes, where the shadow separates.

   FIELDS ARE WELLS, BUTTONS ARE RAISED
     A field and a button share height, radius, border, type and focus ring.
     They differ by one shadow: a button carries --inset-glass, the lit top
     edge that makes it read as raised; a field carries --inset-well. --bg-1
     is the brand's own "recessed well" background and is what keeps a field
     visible inside a --grad-panel card.

   LABELS
     Every field carries a persistent visible label (D11). The one row that
     cannot hold one is the war room's single-line control strip, where
     #search carries an aria-label plus the persistent `/` hint chip beside
     it — never a placeholder standing in for a label.
   ========================================================================= */

/* ---- neutral: the pane recipe, on a control ---- */
/* `a.primary` rides with the button families deliberately, and is the only
   anchor that does: "Open on this phone" hands the phone a `theboard://`
   deep link, which is a real navigation and not a script action, and Owner
   Rule 2 says the owner clicks buttons. Same recipe, one source. An anchor
   carrying no class is still plain text. */
button, a.primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: var(--ctl-h);
  padding: 0 var(--ctl-pad-x);
  background: var(--grad-panel);
  color: var(--fg-1);
  border: 1px solid var(--ctl-border);
  border-radius: var(--ctl-radius);
  cursor: pointer;
  font: var(--ctl-font);
  white-space: nowrap;
  text-decoration: none;
  box-shadow: var(--inset-glass);
  transition: background var(--dur-quick) var(--ease-drift),
              border-color var(--dur-quick) var(--ease-drift),
              box-shadow var(--dur-quick) var(--ease-drift),
              color var(--dur-quick) var(--ease-drift);
}
/* B21 (2026-09-29): every hover look in this system sits under
   `(hover: hover)`. A touch screen has no hover, but iOS applies :hover on
   a tap and keeps it until the next tap somewhere else, so a tapped
   button went on looking hovered. Each rule is wrapped where it stood, so
   the cascade order (and so every override below it) is unchanged. */
@media (hover: hover) {
  button:hover { background: var(--glass-2);
                 box-shadow: var(--shadow-card), var(--inset-glass); }
}
/* press: no shrink — the element settles back into the plane */
button:active { box-shadow: var(--inset-glass); }

/* tiles and the trades rail are buttons carrying paragraphs, not labels */
.home-tile, #home-resume { white-space: normal; }

/* ---- primary: Owner Rule 2, the thing to click ---- */
/* The label's scrim (row 40, owner 2026-10-01: "Yes, match the site"): the
   aurora paints as `--aurora-fill`, a flat 20% of --bg-0 laid OVER it (the
   token's comment in :root has the numbers), in every state that paints the
   gradient. The gradient and the glow are untouched. The public site copies
   this rule and the token (tools/gen_site_design.py), so a change here is a
   change there once the tool has been run. Rest and hover read the one token,
   so they cannot disagree: a hover that re-set `background:
   var(--grad-aurora)` would drop the scrim. */
button.primary, a.primary {
  background: var(--aurora-fill);
  background-clip: padding-box;
  border-color: transparent;
  color: var(--text-on-accent);
  box-shadow: var(--shadow-card);
}
@media (hover: hover) {
  button.primary:hover, a.primary:hover {
    background: var(--aurora-fill);
    background-clip: padding-box;
    box-shadow: var(--glow-violet), var(--shadow-card);
  }
}
button.primary:active, a.primary:active { box-shadow: var(--shadow-card); }

/* ---- subtle: a secondary standing beside a primary ---- */
button.subtle {
  background: none;
  border-color: transparent;
  box-shadow: none;
  color: var(--fg-3);
}
@media (hover: hover) {
  button.subtle:hover { background: var(--glass-1); color: var(--fg-1);
                        box-shadow: none; }
}

/* ---- danger: destructive, or unrecoverable compute ---- */
button.danger {
  background: color-mix(in oklab, var(--danger) 14%, transparent);
  border-color: color-mix(in oklab, var(--danger) 55%, transparent);
  color: color-mix(in oklab, var(--danger) 30%, white);
}
@media (hover: hover) {
  button.danger:hover {
    background: color-mix(in oklab, var(--danger) 24%, transparent);
    box-shadow: var(--glow-red), var(--inset-glass);
  }
}

/* ---- disabled: --fg-4 text, glass held at level 1, no glow ---- */
button:disabled, button:disabled:hover,
input:disabled, select:disabled, input:disabled:hover {
  background: var(--glass-1);
  background-clip: padding-box;
  border-color: var(--border-1);
  color: var(--fg-4);
  box-shadow: none;
  cursor: not-allowed;
}

/* ---- the dense inline size: feeds, table rows, stamp lines ---- */
.btn-sm {
  min-height: var(--ctl-h-sm);
  min-width: var(--ctl-h-sm);
  padding: 0 var(--sp-2);
  font: var(--ctl-font-sm);
  white-space: nowrap;
}
/* ---- glyph-only square: close, the order arrows, the watch star ---- */
.btn-icon {
  width: var(--ctl-h);
  min-width: var(--ctl-h);
  height: var(--ctl-h);
  padding: 0;
}
.btn-icon.btn-sm {
  width: var(--ctl-h-sm);
  min-width: var(--ctl-h-sm);
  height: var(--ctl-h-sm);
}

/* ---- page heads (the title row) ----
   The Back control that used to lead these is gone (B34, 2026-09-29): the
   menu is how a screen is left (UI_GUIDELINES D33), so a page head is its
   title, and a hint under it takes a full row. */
.season-head, #settings-head, #history-head, .page-head {
  display: flex; flex-wrap: wrap; align-items: center;
  align-content: flex-start; column-gap: var(--sp-4);
}
:is(.season-head, #settings-head, #history-head, .page-head) > h1 {
  min-width: 0; margin: var(--sp-2) 0;
}
:is(.season-head, #settings-head, #history-head, .page-head)
  > :not(h1) { flex-basis: 100%; }
/* the chooser's head is centred over its tiles */
#choice-head {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: center; column-gap: var(--sp-4);
}
#choice-head > h1 { min-width: 0; margin: var(--sp-2) 0; }
#choice-head > :not(h1) { flex-basis: 100%; }

/* ---- segmented toggle ---- */
.btn-group {
  display: inline-flex;
  align-items: center;
  /* no gap: the segments abut inside the rail, which is what keeps the
     war room's control strip inside its pane at 1280 */
  gap: 0;
  min-height: var(--ctl-h);
  padding: var(--sp-1);
  background: var(--grad-panel);
  border: 1px solid var(--border-1);
  border-radius: var(--r-pill);
  box-shadow: var(--inset-glass);
}
.btn-group button {
  min-height: var(--ctl-h-sm);
  padding: 0 var(--sp-2);
  background: none;
  border-color: transparent;
  border-radius: var(--r-pill);
  box-shadow: none;
  color: var(--fg-3);
  white-space: nowrap;
}
@media (hover: hover) {
  .btn-group button:hover { background: var(--glass-1); color: var(--fg-1);
                            box-shadow: none; }
}
.btn-group button.active, .btn-group button.active:hover {
  background: var(--aurora-fill);
  background-clip: padding-box;
  color: var(--text-on-accent);
  box-shadow: var(--glow-violet);
}

/* ---- fields: text, search, number, and the native select ----
   There was no global rule for number or text inputs, so the 136 knob fields
   on the settings panes fell through to the browser's own white,
   square-cornered, Arial box inside a dark glass UI. This is the recipe
   #mock-setup, #espn-setup and .tf-eval had each written out locally; it is
   hoisted here rather than reinvented, and they now set only their widths. */
select,
input[type="search"],
input[type="text"],
input[type="password"],
input[type="number"] {
  min-height: var(--ctl-h);
  padding: 0 var(--ctl-pad-x);
  background: var(--bg-1);
  color: var(--fg-1);
  border: 1px solid var(--ctl-border);
  border-radius: var(--ctl-radius);
  font: var(--ctl-font-field);
  box-shadow: var(--inset-well);
  transition: border-color var(--dur-quick) var(--ease-drift),
              box-shadow var(--dur-quick) var(--ease-drift);
}
/* every number in the product is mono — the system's strongest single rule */
input[type="number"] {
  font: var(--ctl-font-num);
  font-variant-numeric: tabular-nums;
}
/* The chevron is drawn in CSS from --fg-3. The brand is deliberately
   near-iconless: no icon font, no sprite, and no SVG asset for a caret. */
select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: var(--sp-8);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--fg-3) 50%),
    linear-gradient(135deg, var(--fg-3) 50%, transparent 50%);
  background-position: right 18px top 50%, right 13px top 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
select[multiple] {
  background-image: none;
  padding: var(--sp-1);
}
@media (hover: hover) {
  select:hover, input[type="search"]:hover,
  input[type="text"]:hover, input[type="password"]:hover,
  input[type="number"]:hover {
    border-color: var(--border-accent);
  }
}

/* ---- checkbox: a real control, not the browser's 13x13 default ---- */
input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex: none;
  width: var(--sp-6);
  height: var(--sp-6);
  margin: 0;
  background: var(--bg-1);
  border: 1px solid var(--ctl-border);
  border-radius: var(--r-sm);
  box-shadow: var(--inset-well);
  cursor: pointer;
  transition: background var(--dur-quick) var(--ease-drift),
              border-color var(--dur-quick) var(--ease-drift);
}
@media (hover: hover) {
  input[type="checkbox"]:hover { border-color: var(--border-accent); }
}
input[type="checkbox"]:checked {
  background: var(--grad-aurora);
  background-clip: padding-box;
  border-color: transparent;
  box-shadow: var(--shadow-card);
}
/* the tick is two borders on a rotated box — a CSS shape, not an icon */
input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  left: 8px; top: 4px;
  width: 5px; height: 10px;
  border: solid var(--text-on-accent);
  border-width: 0 2px 2px 0;
  transform: rotate(40deg);
}

/* ---- range: the mock speed control ---- */
input[type="range"] {
  appearance: none;
  -webkit-appearance: none;
  height: var(--ctl-h);
  background: none;
  cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
  height: var(--sp-2);
  border-radius: var(--r-pill);
  background: var(--glass-2);
  box-shadow: var(--inset-well);
}
input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: var(--sp-4); height: var(--sp-4);
  margin-top: -4px;
  border-radius: var(--r-pill);
  background: var(--grad-aurora);
  background-clip: padding-box;
  border: 1px solid var(--border-2);
  box-shadow: var(--shadow-card);
}
input[type="range"]::-moz-range-track {
  height: var(--sp-2);
  border-radius: var(--r-pill);
  background: var(--glass-2);
  box-shadow: var(--inset-well);
}
input[type="range"]::-moz-range-thumb {
  width: var(--sp-4); height: var(--sp-4);
  border-radius: var(--r-pill);
  background: var(--grad-aurora);
  background-clip: padding-box;
  border: 1px solid var(--border-2);
  box-shadow: var(--shadow-card);
}

/* ---- focus: one ring for every control ----
   The brand asks for an accent ring plus a violet glow and never a browser
   outline. The old ring was 1px of --border-accent, which measures 1.98:1 and
   fails both the 2px and the 3:1 floors (G2/G3); --violet-bright is 9.54:1 on
   --bg-0 and carries the same violet reading. */
input:focus-visible,
select:focus-visible,
button:focus-visible,
a:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--violet-bright);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}
/* B55 (2026-09-29): a heading that script focused on a screen change
   (focusScreen, keyboard navigation only) is a place to start reading,
   not a control -- it never draws a ring. Rings are for real controls. */
h1[tabindex="-1"]:focus, h1[tabindex="-1"]:focus-visible { outline: none; }
/* the same for <main>, which the skip link focuses (B34): a place to start
   reading, not a control */
main[tabindex="-1"]:focus, main[tabindex="-1"]:focus-visible { outline: none; }
input[type="search"]:focus, select:focus,
input[type="text"]:focus, input[type="password"]:focus,
input[type="number"]:focus {
  border-color: var(--border-accent);
  box-shadow: var(--glow-violet);
}

/* ---- the persistent hint inside a field that cannot carry a label ----
   #search sits in the war room's single-line control strip, which is one
   control high and already scrolls sideways, so there is no room for a
   label above or beside it. The accessible name is the aria-label; the
   keyboard hint the placeholder used to smuggle in ("( / )", and gone the
   moment you typed) is this chip, which is always there. */
/* 150px is the floor that still shows the whole placeholder: 12px of field
   padding + "search players" at --ctl-font-field + the 32px the hint chip
   reserves on the right. Below it the box reads "searcl", which is how the
   1280 render found this. */
.ctl-search {
  position: relative; display: flex; align-items: center;
  flex: 1 1 150px; min-width: 150px; max-width: 240px;
}
.ctl-search input { width: 100%; padding-right: var(--sp-8); }
.ctl-hint {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--ctl-h-sm);
  min-height: var(--ctl-h-sm);
  padding: 0 var(--sp-2);
  border: 1px solid var(--border-1);
  border-radius: var(--r-sm);
  color: var(--fg-3);
  font: var(--ctl-font-num);
  white-space: nowrap;
}
.ctl-search .ctl-hint {
  position: absolute; right: var(--sp-1); pointer-events: none;
}
/* the browser's own clear affordance is unbranded chrome and collides with
   the hint chip; Escape and the filter buttons already clear the search */
input[type="search"]::-webkit-search-cancel-button {
  appearance: none; -webkit-appearance: none;
}
/* ==================== end of the control system ========================== */

/* ============================================================================
   NAVIGATION (B34, 2026-09-29; UI_GUIDELINES D33, E9)
   ----------------------------------------------------------------------------
   One <nav id="nav"> with two presentations, written mobile-first: the BASE
   rules make it a drawer that is closed until Menu opens it, and the block
   under `min-width: 1200px` turns it into the pane down the left edge of
   every screen but the war room. The war room never shows the pane (its
   three panes are already tight at 1280): there Menu leads #top-actions and
   the drawer opens at every width, which is why the pane is written
   `body:not(.draft-locked, .boot-failed)` and not just a media query.

   The pane is transparent over the nebula with a hairline on its right
   edge: no blur, no animation and no per-route re-render (B44).
   The drawer is a floating surface, so it takes the modal glass tier; its
   scrim is dimmed and NOT blurred, because one blur is cheaper than two on
   WebKit. Only the current row's gradient is the "quiet aurora" (no glow,
   like #pane-config's active button): glow stays with the one focal
   element, and a filled row is a shape change as well as a colour change
   (C5). Layers: menu pane 19, Menu button 20, lockup 21, compact bar 22,
   drawer and scrim 55 (the #modal tier: a moment never paints over an
   overlay the owner opened, so the drawer is above the card flight at 54).
   Rows are navigation, not actions, so like `.tf-team` they are an Owner
   Rule 2 / D1 exception; Add League is a primary button.
   ========================================================================= */

/* Skip to Content: a primary link, visible only while it has focus */
#skip-link:not(:focus) {
  position: absolute; width: 1px; height: 1px; min-height: 0; padding: 0;
  overflow: hidden; clip-path: inset(50%); border: 0; white-space: nowrap;
}
#skip-link:focus { position: fixed; top: var(--sp-2); left: var(--sp-2); z-index: 60; }

/* ---- the drawer (base) ---- */
#nav {
  display: none;
  position: fixed; top: 0; left: 0; bottom: 0; z-index: 55;
  width: min(360px, 100vw - 56px);
  height: 100vh; height: 100dvh;
  flex-direction: column; gap: var(--sp-4);
  padding: var(--sp-4);
  overflow-y: auto; overscroll-behavior: contain;
  background: var(--glass-2);
  border-right: 1px solid var(--border-2);
  box-shadow: var(--shadow-float), var(--inset-glass);
}
/* the modal glass tier, present only while the drawer is open */
#nav.nav-open {
  display: flex;
  backdrop-filter: blur(var(--blur-glass));
  animation: nav-drawer-in var(--dur-drift) var(--ease-drift) backwards;
}
#nav.nav-closing {
  animation: nav-drawer-in var(--dur-quick) var(--ease-drift) reverse forwards;
}
/* Not slide-in-from-offscreen (the motion brand): the drawer is already in
   place and fades in while it drifts 16px */
@keyframes nav-drawer-in {
  from { opacity: 0; transform: translateX(calc(-1 * var(--sp-4))); }
  to   { opacity: 1; transform: none; }
}
@keyframes nav-fade-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  /* the drawer fades only: cancel the transform, not just the transition */
  #nav.nav-open { animation: nav-fade-in var(--dur-quick) linear backwards; }
  #nav.nav-closing { animation: nav-fade-in var(--dur-quick) linear reverse forwards; }
}
#nav-scrim {
  position: fixed; inset: 0; z-index: 55; background: var(--scrim);
  animation: nav-fade-in var(--dur-quick) var(--ease-drift) backwards;
}
body.nav-locked, body.draft-locked.nav-locked { overflow: hidden; }

#nav-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3);
}
#nav-head-title, .nav-label {
  font: var(--text-label); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-3);
}
.nav-label { margin: 0; padding: 0 var(--sp-3); }
.nav-group { display: flex; flex-direction: column; gap: var(--sp-2); }

/* Menu, Close: the neutral family (D20). Menu is a word, not a hamburger:
   the brand is near-iconless, and a text label finds itself (NN/g). */
#nav-toggle, #nav-bar-menu, #nav-close { min-width: 44px; min-height: 44px; }
#nav-toggle { min-width: var(--nav-menu-w); }
/* top right of the page, centred on the lockup's band; it scrolls with the
   page and never goes left of a title */
#nav-toggle {
  position: absolute; z-index: 20;
  top: calc((var(--lockup-h) - 44px) / 2); right: var(--sp-6);
}
@media (max-width: 640px) {
  #nav-toggle { right: var(--sp-4); }
}

/* The account icon (B7, queue row 23; the owner's "placeholder icon in the
   header"): a neutral square control (D20, `.btn-icon`), 44px like Menu.
   Below 1200px it sits in Menu's corner, to Menu's left (and on the compact
   bar it leads Menu). From 1200px Menu is gone and the lockup heads the
   menu pane, so the icon heads it too: fixed, level with the lockup, at the
   pane's right edge, where no page's content can ever be (a corner of the
   page itself would sit on Home's top-right tile: E3, reserve it
   structurally). The person is drawn here, not shipped as an asset: a head
   ring over a shoulder arch, in the control's own text colour (G6: the name
   is the aria-label). The war room has none: its strip is the tightest row
   in the app and Menu -> Account is there. */
.acct-btn { min-width: 44px; min-height: 44px; padding: 0; }
.acct-glyph { position: relative; width: 20px; height: 20px; }
.acct-glyph::before, .acct-glyph::after {
  content: ""; position: absolute; box-sizing: border-box;
  border: 2px solid currentColor;
}
.acct-glyph::before {                       /* the head */
  left: 5px; top: 0; width: 10px; height: 10px; border-radius: 50%;
}
.acct-glyph::after {                        /* the shoulders */
  left: 1px; bottom: 0; width: 18px; height: 9px;
  border-bottom: 0; border-radius: 9px 9px 0 0;
}
.acct-btn[aria-current="page"] {
  border-color: var(--border-accent); color: var(--violet-bright);
}
#acct-btn {
  position: absolute; z-index: 20;
  top: calc((var(--lockup-h) - 44px) / 2);
  right: calc(var(--sp-6) + var(--nav-menu-w) + var(--sp-2));
}
@media (max-width: 640px) {
  #acct-btn { right: calc(var(--sp-4) + var(--nav-menu-w) + var(--sp-2)); }
}
/* Under 387px the account icon would touch the lockup's 280px box, so both
   drop into a row of their own under the lockup's band, at the right (this
   was 335px for Menu alone before the icon; it is the sum of the pieces) */
@media (max-width: 386px) {
  #nav-toggle, #acct-btn { top: var(--lockup-h); }
  body:not(.draft-locked, .boot-failed) #content { padding-top: var(--sp-12); }
}
body.draft-locked #nav-toggle, body.boot-failed #nav-toggle,
body.draft-locked #acct-btn, body.boot-failed #acct-btn,
body.draft-locked #nav-bar, body.boot-failed #nav-bar { display: none; }
#nav-bar-actions { display: flex; align-items: center; gap: var(--sp-2); }

/* The compact bar (N3): a slim glass strip with the page name and Menu, once
   the page's own title has scrolled away (IntersectionObserver in app.js).
   The #topbar recipe: --glass-2 at the pane blur. Above the lockup, which
   still scrolls past its own band at 1151-1199. */
#nav-bar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 22;
  height: var(--nav-bar-h);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-4); padding: 0 var(--sp-4);
  background: var(--glass-2);
  backdrop-filter: blur(20px);
  border-bottom: 1px solid var(--border-1);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity var(--dur-quick) var(--ease-drift),
              visibility 0s linear var(--dur-quick);
}
#nav-bar.show {
  opacity: 1; visibility: visible; pointer-events: auto;
  transition-delay: 0s;
}
@media (min-width: 641px) {
  #nav-bar { padding: 0 var(--sp-6); }
}
#nav-bar-title {
  font: var(--text-body); font-weight: 600; color: var(--fg-1);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
@media (prefers-reduced-motion: reduce) {
  #nav-bar { transition: none; }
}

/* ---- the league box (B34a): a disclosure, opening inline ---- */
#nav-league-btn {
  --nav-chev:
    linear-gradient(45deg, transparent 50%, var(--fg-3) 50%)
      right 18px top 50% / 5px 5px no-repeat,
    linear-gradient(135deg, var(--fg-3) 50%, transparent 50%)
      right 13px top 50% / 5px 5px no-repeat;
  display: flex; flex-direction: column; align-items: flex-start;
  justify-content: center; gap: var(--sp-1);
  width: 100%; min-height: calc(var(--nav-row) + var(--sp-4));
  padding: var(--sp-2) var(--sp-8) var(--sp-2) var(--sp-3);
  text-align: left; white-space: normal;
  background: var(--nav-chev), var(--grad-panel);
}
@media (hover: hover) {
  #nav-league-btn:hover { background: var(--nav-chev), var(--glass-2); }
}
#nav-league-btn[aria-expanded="true"] { border-color: var(--border-accent); }
#nav-league-name {
  font: var(--text-body); font-weight: 600; color: var(--fg-1);
  min-width: 0; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#nav-league-meta {
  font: var(--text-label); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-3);
}
#nav-leagues {
  display: flex; flex-direction: column; gap: var(--sp-2);
  margin-top: var(--sp-2); padding: var(--sp-2);
  background: var(--bg-1); border: 1px solid var(--ctl-border);
  border-radius: var(--r-md); box-shadow: var(--inset-well);
}
#nav-league-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 2px;
}
.nav-league-item {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: flex-start; gap: var(--sp-1) var(--sp-2);
  width: 100%; min-height: var(--nav-row);
  padding: var(--sp-2) var(--sp-3);
  background: none; border-color: transparent; box-shadow: none;
  color: var(--fg-2); text-align: left; white-space: normal;
}
@media (hover: hover) {
  .nav-league-item:hover { background: var(--glass-1); color: var(--fg-1);
                           box-shadow: none; }
}
.nav-league-item[aria-current="true"] {
  background: var(--glass-2); border-color: var(--border-accent);
  color: var(--fg-1);
}
.nav-league-item-name {
  flex: 1 1 100%; min-width: 0; font: var(--ctl-font);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nav-league-item-meta {
  font: var(--text-label); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-3);
}
#nav-add-league { width: 100%; }

/* ---- the rails: the D23 segmented recipe turned vertical ---- */
.nav-rail {
  list-style: none; margin: 0; padding: var(--sp-1);
  display: flex; flex-direction: column; gap: 2px;
  background: var(--grad-panel);
  border: 1px solid var(--border-1); border-radius: var(--r-lg);
  box-shadow: var(--inset-glass);
}
.nav-sep {
  height: 1px; margin: var(--sp-1) var(--sp-2); background: var(--border-1);
}
.nav-item {
  position: relative; isolation: isolate;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-2);
  min-height: var(--nav-row); padding: 0 var(--sp-3);
  border-radius: var(--r-md);
  font: var(--ctl-font); color: var(--fg-2); text-decoration: none;
  transition: background var(--dur-quick) var(--ease-drift),
              color var(--dur-quick) var(--ease-drift);
}
.nav-item:focus-visible { border-radius: var(--r-md); }
.nav-name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
}
@media (hover: hover) {
  .nav-item:hover { background: var(--glass-1); color: var(--fg-1); }
}
/* the current page: the gradient sits on its own layer so its opacity can
   fade (a gradient cannot transition), with no glow and no scale (D30) */
.nav-item::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  border-radius: inherit; background: var(--aurora-fill);
  opacity: 0; transition: opacity var(--dur-quick) var(--ease-drift);
}
.nav-item[aria-current="page"] { color: var(--text-on-accent); }
.nav-item[aria-current="page"]::before { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .nav-item, .nav-item::before { transition: none; }
}
/* the "Open" pill: a live or ESPN draft still has picks to make. The 8px
   status dot is the brand's own; the word carries the meaning (C5) */
.nav-pill {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: 2px var(--sp-2); border: 1px solid var(--border-2);
  border-radius: var(--r-pill);
  font: var(--text-label); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-2); white-space: nowrap;
}
.nav-item .nav-pill::before {
  content: ""; width: 8px; height: 8px; border-radius: var(--r-pill);
  background: var(--success);
}
.nav-item[aria-current="page"] .nav-pill { color: var(--text-on-accent); }
/* "In Draft" in the league box (X5 P6): a live draft is open in that league.
   The same pill and the same status dot as "Open"; "Current" beside it has
   no dot, so the dot means a draft and nothing else */
.nav-pill-live::before {
  content: ""; width: 8px; height: 8px; border-radius: var(--r-pill);
  background: var(--success);
}
/* Settings and Account: one horizontal group at the foot of the menu */
#nav-foot { flex-direction: row; margin-top: auto; }
#nav-foot li { flex: 1 1 0; min-width: 0; }
#nav-foot .nav-item { justify-content: center; }

/* ---- the account stub (B34) ---- */
.acct-rows {
  display: grid; grid-template-columns: max-content minmax(0, 1fr);
  gap: var(--sp-2) var(--sp-6); margin: 0;
}
.acct-rows > div { display: contents; }
.acct-rows dt {
  font: var(--text-label); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-3); padding-top: 2px;
}
.acct-rows dd { margin: 0; font: var(--text-small); color: var(--fg-1); min-width: 0; }
/* the Account card's one action when the deployment has a sign-in (B6) */
.acct-actions { margin: var(--sp-4) 0 0; }
.acct-actions.set-actions { margin-bottom: 0; }
/* Signed-In Devices (B7b): one row a device, its name and "This device" badge
   on the first line, its two times under them; the hairline between rows is
   the Advanced tab's own (.set-deploy-row) */
.acct-devices {
  list-style: none; margin: 0 0 var(--sp-4); padding: 0;
  display: flex; flex-direction: column; gap: var(--sp-3);
}
.acct-devices li {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--sp-1) var(--sp-3);
  border-bottom: 1px solid var(--border-1); padding-bottom: var(--sp-3);
}
.acct-device-name { font: 500 13px var(--font-sans); color: var(--fg-1); }
.acct-devices .acct-device-when { flex: 1 0 100%; margin: 0; }
#acct-devices > .warn, #acct-devices > .muted { margin: 0 0 var(--sp-4); }
/* Account's cards stack with a gap no smaller than their padding (A4/A5) */
#account-body { display: flex; flex-direction: column; gap: var(--sp-4); }
/* Change Password (B7): the sign-in page's field and alert recipes, in a
   card; the form is a readable width, not the card's whole 820px */
.acct-pw { display: flex; flex-direction: column; gap: var(--sp-4);
           max-width: 380px; }
.acct-pw .login-field input { width: 100%; }
.acct-pw .set-help { margin: 0; }
.acct-pw .set-actions { margin: 0; }

/* ---- the pane: 1200px and up, every screen but the war room ----
   The lockup stays where it is (fixed, its ink on the gutter) and becomes
   the head of this column; the pane's own content starts under it. The
   content column takes the rest, so no page needs lockup arithmetic here
   (E9). 1200 is where the widest season table still fits the column
   (measured: see findings/B34.md). */
@media (min-width: 1200px) {
  body:not(.draft-locked, .boot-failed) #content { margin-left: var(--nav-w); }
  body:not(.draft-locked, .boot-failed) #nav {
    display: flex; z-index: 19;
    width: var(--nav-w); height: auto; bottom: 0;
    padding: calc(var(--lockup-h) + var(--sp-2)) var(--sp-6) var(--sp-4);
    background: none; box-shadow: none;
    border-right: 1px solid var(--border-1);
  }
  body:not(.draft-locked, .boot-failed) #nav-head,
  body:not(.draft-locked, .boot-failed) #nav-scrim,
  body:not(.draft-locked, .boot-failed) #nav-toggle,
  body:not(.draft-locked, .boot-failed) #nav-bar { display: none; }
  /* the account icon heads the pane beside the lockup (B7): above the
     lockup's transparent right margin (z 21, later in the document), inside
     the pane's own right padding */
  body:not(.draft-locked, .boot-failed) #acct-btn {
    position: fixed; z-index: 21; right: auto;
    left: calc(var(--nav-w) - var(--sp-6) - 44px);
  }
}

/* branded dropdowns: Chromium's customizable select renders the open list
   as glass; browsers without ::picker support keep the native popup */
@supports selector(::picker(select)) {
  /* base-select draws its own ::picker-icon, so the CSS chevron the control
     system paints for every other browser is switched off here rather than
     doubled up */
  select, ::picker(select) { appearance: base-select; }
  /* base-select makes the button an inline-flex whose default `normal`
     alignment stretches the selected option to the full --ctl-h and leaves
     its 13px text stuck against the top edge -- which reads as a second,
     smaller box sitting inside the field (the activity team picker at 1280).
     One control, one centred line. */
  select { background-image: none; padding-right: var(--ctl-pad-x);
           align-items: center; }
  select[multiple] { padding: var(--sp-1); }
  ::picker(select) {
    background: color-mix(in oklab, var(--bg-1) 82%, oklch(0.85 0.05 295));
    backdrop-filter: blur(var(--blur-glass));
    border: 1px solid var(--border-2); border-radius: var(--r-md);
    box-shadow: var(--shadow-plane), var(--inset-glass);
    padding: 4px; margin-top: 4px;
  }
  select option {
    padding: 6px 10px; border-radius: var(--r-sm);
    font: 400 13px/1.3 var(--font-sans); color: var(--fg-2);
  }
  select option:focus { background: var(--glass-2); color: var(--fg-1); }
  @media (hover: hover) {           /* H11, B21 */
    select option:hover { background: var(--glass-2); color: var(--fg-1); }
  }
  /* the picker's chosen option carries a label on the aurora too, so it paints
     the scrimmed fill like the rest (row 40; owner, 2026-10-03: every
     gradient-with-white-text spot). Chromium's base-select draws an option as
     an ordinary box, so the layered background is honoured. */
  select option:checked {
    background: var(--aurora-fill); background-clip: padding-box;
    color: var(--text-on-accent); font-weight: 600;
  }
  select option::checkmark { display: none; }
  select::picker-icon { color: var(--fg-3); }
}
h1 {
  font: italic 900 28px/1.1 var(--font-display);
  letter-spacing: 0.01em;
}
h2 {
  font: 500 11px/1.2 var(--font-sans);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--fg-3);
  margin: 0 0 10px;
}
.muted { color: var(--fg-3); }
.hint { color: var(--fg-3); font-size: 13px; }
.warn { color: var(--danger); }
.num, td, #clock-pick { font-variant-numeric: tabular-nums; }

/* traded picks: "via <original owner>" wherever a pick changed hands */
.via {
  margin-left: 6px; padding: 1px 5px; border-radius: var(--r-pill);
  background: var(--glass-1); color: var(--fg-3);
  font: 600 11px var(--font-mono); white-space: nowrap;
}
.traded-chip {
  display: inline-block; margin: 2px 4px 0 0; padding: 2px 7px;
  border-radius: var(--r-pill); background: var(--glass-1);
  font: 500 11px var(--font-mono); color: var(--fg-2);
}
#setup-order-note { margin: 0 0 10px; font-size: 12px; }
#setup-order-note .order-head { display: block; }
#setup-order-note .order-stamp {
  display: block; margin-top: 3px; font: 400 11px var(--font-mono);
  color: var(--fg-3);
}

/* --- home hub (B27, B42, B11, B12; 2026-09-29) -----------------------------
   The tools, and nothing in front of them: the brand lives in the lockup
   alone (the kicker, wordmark and tagline are gone, B9/B10). Home is no
   longer a centred launcher -- it starts on the lockup clear line at every
   width, so nothing can drift under the wordmark -- and the grid is as
   wide as a season page (the A6 shell: the viewport less a gutter each
   side, up to --season-max), where it used to stop at 820. So the tiles,
   the lockup's ink and every season page share one left edge. */
#home {
  min-height: 100vh; display: flex; flex-direction: column;
  align-items: center; gap: var(--sp-6);
  padding: var(--lockup-clear) var(--sp-6) var(--sp-16);
}
/* B34, E9: at 1200px and up the lockup is the head of the menu pane, beside
   the content column rather than over it, so Home needs no lockup band
   above its tiles: they start where a page title does. */
@media (min-width: 1200px) {
  #home { padding-top: var(--sp-10); }
}
/* B42: every tile the same size, every title on one line across the grid.
   The tiles used to centre their content vertically, so a longer tagline
   pushed its title up. Now `grid-auto-rows: 1fr` makes every row as tall
   as the tallest, each tile stretches to its row, the title is pinned to
   the top, and the tagline is clamped to two lines. */
#home-tiles, #home-draft {
  display: grid; width: min(var(--season-max), 100%);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  gap: var(--sp-4);
}
#home-tiles { grid-auto-rows: 1fr; }
/* the second group (the demoted tools, B12) starts a row of its own, so
   it reads as a set at every column count */
#home-tiles .home-row-start { grid-column-start: 1; }
#home-tiles .home-tile {
  justify-content: flex-start; align-items: stretch; gap: var(--sp-2);
  min-width: 0; min-height: 0; padding: var(--sp-5);
}
#home-tiles .tile-desc {
  display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
}
/* B11: an open draft -- the Resume tile, and End Draft Session beside it,
   on the tiles' own column grid so the Resume tile is exactly one tile.
   It leads Home while it exists, because it is state that wants an
   answer; ending the session takes it away. */
#home-draft { align-items: center; }
#home-end-draft { justify-self: start; }
/* Phones, upright or on their side (§9): a 16px gutter and a two-column
   grid, one column below ~375px where two tiles cannot hold a title. The
   Resume tile takes the full row, End Draft Session the row under it. */
@media (max-width: 640px) {
  #home { padding-left: var(--sp-4); padding-right: var(--sp-4); }
}
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  #home-tiles, #home-draft {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 168px), 1fr));
    gap: var(--sp-3);
  }
  #home-tiles .home-tile { padding: var(--sp-4); }
  #home-resume { grid-column: 1 / -1; }
}
/* boot failure: a full-viewport centred card, since it stands in for the
   whole app until a retry gets past it */
#boot-error {
  min-height: 100vh; display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: 80px 24px 60px;
}
#boot-error-card { max-width: 440px; text-align: center; }
#boot-error-card h1 {
  margin: 0 0 14px; font: italic 800 26px/1.2 var(--font-display);
  color: var(--fg-1);
}
#boot-error-card .warn { margin: 0 0 6px; }
#boot-error-card .muted { margin: 0 0 24px; }
/* gradient-clipped italic spans need overhang room or the B/D edges clip */
.choice-tile .tile-name, #home-resume .tile-name,
.history-grade, .grade-letter {
  padding-left: 0.24em; padding-right: 0.24em;
  margin-left: -0.24em; margin-right: -0.24em;
}
/* press: settle, never shrink (overrides the global button:active) */
.home-tile:active { transform: none;
                    box-shadow: var(--shadow-card), var(--inset-glass); }

/* --- draft chooser (mock vs live) --- */
#draft-choice {
  min-height: 100vh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 32px;
  padding: 80px 24px 60px;
}
/* the head spans the tile grid (2 x 300 + gap) so its Back lines up with
   the grid's left edge, the same top-left place it takes on every page */
#choice-head { text-align: center; width: min(614px, 100%); }
#choice-head h1 { margin: 0 0 8px; }
#choice-tiles {
  display: grid; grid-template-columns: repeat(2, minmax(220px, 300px));
  gap: 14px; justify-content: center; width: 100%;
}
@media (max-width: 560px) { #choice-tiles { grid-template-columns: 1fr; } }
.choice-tile { min-height: 116px; padding: 22px 20px; }
/* a running draft's Resume tile sits alone on the top row */
#choice-resume { grid-column: 1 / -1; min-height: 0; }
.choice-tile .tile-name {
  background: var(--grad-text);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.home-tile {
  position: relative; display: flex; flex-direction: column; gap: 6px;
  align-items: flex-start; text-align: left;
  padding: 18px 18px 16px; min-height: 96px;
  background: var(--grad-panel);
  border: 1px solid var(--border-1); border-radius: var(--r-lg);
  box-shadow: var(--shadow-card), var(--inset-glass);
}
/* Served while a draft (live, ESPN or mock) is still open: one click back
   into the war room, the size of one tool tile and the screen's focal
   element. Hidden whenever nothing is resumable. */
#home-resume {
  min-width: 0;
  display: flex; flex-direction: column; gap: var(--sp-2);
  align-items: flex-start; justify-content: flex-start; text-align: left;
  padding: var(--sp-5);
  background: var(--grad-panel);
  border: 1px solid var(--accent); border-radius: var(--r-lg);
  box-shadow: var(--shadow-card), var(--glow-violet), var(--inset-glass);
}
@media (hover: hover) {
  #home-resume:hover { background: var(--glass-2); }
}
#home-resume .tile-name {
  font: italic 800 18px/1.2 var(--font-display);
  background: var(--grad-text);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
#home-resume .tile-desc { font: 400 12px/1.45 var(--font-sans);
                          color: var(--fg-3); }
/* every pick is in (B11): the tile is the way back to a finished board,
   no longer the next thing to do, so it gives up the focal glow and End
   Draft Session beside it is what the card leads with */
#home-resume.finished {
  border-color: var(--border-2);
  box-shadow: var(--shadow-card), var(--inset-glass);
}
#home-resume.finished .tile-name { background: none; color: var(--fg-1); }
@media (hover: hover) {
  .home-tile:hover {
    background: var(--glass-2);
    box-shadow: var(--shadow-plane), var(--glow-violet), var(--inset-glass);
  }
}
.home-tile .tile-name {
  font: italic 800 18px/1.2 var(--font-display); color: var(--fg-1);
}
.home-tile .tile-desc { font: 400 12px/1.45 var(--font-sans);
                        color: var(--fg-3); }

/* --- toast: bottom-right so it never covers the topbar controls --- */
#toast {
  /* border-accent + glow is the focal budget — spent on the confirm bar,
     not on notifications */
  position: fixed; right: 18px; bottom: 18px; z-index: 60;
  background: var(--glass-2); backdrop-filter: blur(var(--blur-glass));
  border: 1px solid var(--border-2); border-radius: var(--r-lg);
  padding: 10px 16px; max-width: min(420px, 70vw);
  font: 500 13px/1.45 var(--font-sans);
  box-shadow: var(--shadow-float), var(--inset-glass);
}
#toast.error {
  border-color: color-mix(in oklab, var(--danger) 45%, transparent);
  color: color-mix(in oklab, var(--danger) 45%, white);
  box-shadow: var(--shadow-float), var(--glow-red), var(--inset-glass);
}
/* sticky toast: stays until clicked (sync results the user must get to
   read); the data-hint labels what the click does */
#toast.sticky { cursor: pointer; padding-bottom: 22px; }
#toast.sticky::after {
  content: attr(data-hint);
  position: absolute; right: 16px; bottom: 6px;
  font: 500 11px var(--font-mono); text-transform: uppercase;
  letter-spacing: var(--tracking-label); color: var(--fg-3);
}
@media (hover: hover) {           /* H11, B21 */
  #toast.sticky:hover { border-color: var(--fg-3); }
}

/* --- settings --- */
#settings, #account { max-width: 820px; margin: 40px auto 80px; padding: 0 16px; }
#settings-head, #account-head { margin-bottom: 16px; }
#settings-tabs { margin-bottom: 16px; flex-wrap: wrap; }
/* B59, H10: four tabs fill the phone's row, equal and centred. They may
   wrap below ~340px; they never scroll. */
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  #settings-tabs { display: flex; width: 100%; }
  #settings-tabs button { flex: 1 1 auto; min-width: 0; text-align: center; }
}
/* cards stack with a gap no smaller than their own padding (A4/A5) */
#settings-body { display: flex; flex-direction: column; gap: var(--sp-4); }
/* A data surface, not a floating one: the wash instead of glass, and the
   drop shadow the glass recipe always asked for and this rule omitted. */
.set-card {
  background: var(--grad-panel);
  border: 1px solid var(--border-1); border-radius: var(--r-lg);
  box-shadow: var(--shadow-card), var(--inset-glass); padding: var(--sp-5);
}
.set-card h3 {
  margin: var(--sp-5) 0 var(--sp-2); font: 500 11px var(--font-sans);
  text-transform: uppercase; letter-spacing: var(--tracking-label);
  color: var(--fg-3);
}
.set-note { margin: 0 0 var(--sp-3); font-size: 13px; }
.set-field { margin: var(--sp-3) 0; }
.set-field-head { display: flex; align-items: center; gap: 8px; }
.set-field-head label {
  font: 500 13px var(--font-sans); color: var(--fg-1);
}
.set-help { margin: var(--sp-1) 0; font-size: 12px; color: var(--fg-3); overflow-wrap: anywhere; }
.set-field input[type="number"], .set-field input[type="text"] {
  width: 180px; max-width: 100%;
}

.set-league {
  display: flex; align-items: center; gap: 12px;
  border: 1px solid var(--border-1); border-radius: var(--r-md);
  padding: var(--sp-3); margin-bottom: var(--sp-2);
}
.set-league.active { border-color: var(--border-accent); background: var(--glass-2); }
.set-league-main { flex: 1; display: flex; flex-direction: column; gap: 2px; }
.set-league-name { font: 500 14px var(--font-sans); }
.set-league-meta { font: 400 11px var(--font-mono); color: var(--fg-3); }
.set-badge {
  padding: 2px 9px; border-radius: var(--r-pill); background: var(--glass-2);
  border: 1px solid var(--border-accent);
  font: 600 11px var(--font-mono); text-transform: uppercase;
}
/* Settings -> Leagues with more than one league (X5 P6): a row is the name,
   its tags (Current, In Draft) and its actions (Switch, Remove), which wrap
   under the name on a phone instead of squeezing it */
.set-league-multi { flex-wrap: wrap; }
.set-league-multi .set-league-main {
  flex: 1 1 12rem; min-width: 0; overflow-wrap: anywhere;
}
.set-league-tags { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.set-league-acts { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* --- Add League: the guided flow (B35) ------------------------------------
   One dialog, five steps. The box is the modal's glass at the modal's blur.
   A step is its count, a heading that takes focus (never ringed: a place to
   start reading, G8), its choices or fields, and a row of actions with one
   primary. A choice is a native radio dressed like the checkbox (the same
   well and border) with a plain accent dot: the aurora fill without a label
   is allowed only on the tick box and the slider thumb. A choice that cannot
   be taken (ESPN, a league already listed) is dimmed and says why in text. */
#addleague {
  position: fixed; inset: 0; background: var(--scrim); z-index: 55;
  backdrop-filter: blur(8px); padding: var(--sp-4);
  display: flex; align-items: center; justify-content: center;
}
body.al-locked { overflow: hidden; }
/* a click on the dialog's text focuses the box, so Escape still reaches it (F69 N1);
   it is a place focus rests, never a control, so it draws no ring */
#addleague-box:focus, #addleague-box:focus-visible { outline: none; }
#addleague-box {
  background: var(--glass-2); backdrop-filter: blur(var(--blur-glass));
  border: 1px solid var(--border-2); border-radius: var(--r-lg);
  box-shadow: var(--shadow-float), var(--inset-glass);
  width: min(560px, 100%); max-height: 90vh;
  display: flex; flex-direction: column;
}
#addleague-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--border-1);
}
#addleague-head h2 { margin: 0; }
@supports (height: 100dvh) {
  #addleague-box { max-height: calc(100dvh - var(--sp-8)); }
}
#addleague-body { padding: var(--sp-4) var(--sp-5) var(--sp-5); overflow: auto; }
.al-count {
  margin: 0 0 var(--sp-1); font: var(--text-label);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--fg-3);
}
.al-heading {
  margin: 0 0 var(--sp-3); min-height: var(--sp-6);   /* D2: a focus target */
  font: var(--text-h3); color: var(--fg-1);
}
.al-heading:focus, .al-heading:focus-visible { outline: none; }
.al-choices { margin: 0 0 var(--sp-4); padding: 0; border: 0; min-width: 0; }
.al-legend { padding: 0; margin: 0 0 var(--sp-2); font: var(--text-small); color: var(--fg-2); }
.al-choice {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  border: 1px solid var(--border-1); border-radius: var(--r-md);
  padding: var(--sp-3); margin-bottom: var(--sp-2); cursor: pointer;
}
.al-choice:has(input:checked) { border-color: var(--border-accent); background: var(--glass-2); }
.al-choice:has(input:focus-visible) {
  outline: 2px solid var(--violet-bright); outline-offset: 2px;
}
.al-choice-off { cursor: default; opacity: .7; }
.al-choice-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.al-choice-name { font: 500 14px var(--font-sans); overflow-wrap: anywhere; }
.al-choice-meta { font: 400 11px var(--font-mono); color: var(--fg-3); overflow-wrap: anywhere; }
.al-choice input[type="radio"] {
  appearance: none; -webkit-appearance: none; position: relative; flex: none;
  width: var(--sp-6); height: var(--sp-6); margin: 0;
  background: var(--bg-1); border: 1px solid var(--ctl-border);
  border-radius: var(--r-pill); box-shadow: var(--inset-well); cursor: inherit;
}
.al-choice input[type="radio"]:checked { border-color: var(--accent); }
.al-choice input[type="radio"]:checked::after {
  content: ""; position: absolute; inset: var(--sp-2);
  border-radius: var(--r-pill); background: var(--accent);
}
.al-facts { margin: 0 0 var(--sp-3); }
.al-facts > div {
  display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3);
  padding: var(--sp-2) 0; border-bottom: 1px solid var(--border-1);
}
.al-facts dt { flex: 0 0 6.5rem; font: var(--text-small); color: var(--fg-3); }
.al-facts dd { flex: 1 1 9rem; margin: 0; min-width: 0; font: var(--text-small); overflow-wrap: anywhere; }
.al-now { margin: 0 0 var(--sp-2); font: var(--text-small); color: var(--fg-1); overflow-wrap: anywhere; }
.al-lines {
  margin: 0 0 var(--sp-3); padding: 0 0 0 var(--sp-4);
  font: 400 11px var(--font-mono); color: var(--fg-3); overflow-wrap: anywhere;
}
#al-form .login-alert { margin: 0 0 var(--sp-3); }
#al-form .login-field { margin-bottom: var(--sp-3); }

/* "The Board on your phone" (Owner Rule 3): the sign-in the button mints,
   shown exactly once because the server keeps only its hash. A data
   surface holding a literal value, so it takes the panel wash, a hairline
   and no blur (UI_GUIDELINES 2b), and the token is --font-mono like every
   other literal on this page (B7). */
.set-phone {
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
  margin-top: 12px; padding: 12px;
  background: var(--grad-panel);
  border: 1px solid var(--border-1); border-radius: var(--r-md);
  box-shadow: var(--shadow-card), var(--inset-glass);
}
.set-phone p { margin: 0; }
.set-phone-lead { font-size: 13px; }
/* content width, not the card's: Copy belongs beside the token it copies,
   not pushed to the far edge of a wide settings card */
.set-phone-token {
  display: flex; align-items: center; gap: 8px; max-width: 100%;
}
/* the token is long and must stay readable in full: it wraps rather than
   truncating, because a clipped secret is a wrong secret (B9) */
.set-phone-token code {
  min-width: 0; word-break: break-all;
  font: 500 13px var(--font-mono); color: var(--fg-2);
}

/* --- the settings pass (2026-09-28): lead line, action row, disclosure ---
   Settings conventions (NN/g, Material, Apple HIG) agree on three things
   the Data pane broke: group related controls under a plain heading, say
   what a control does in one line beside it, and put the long "why"
   behind progressive disclosure rather than in front of the button. */
.set-lead { margin: 0 0 var(--sp-3); font: var(--text-small); color: var(--fg-2); }
.set-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
/* the disclosure: a quiet text control, never a button family, with a real
   24px target (D2), the house focus ring (D27) and a mono glyph for its
   state instead of the browser's marker (the brand is near-iconless) */
.set-more { margin: 0 0 var(--sp-2); }
.set-more > summary {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-height: var(--ctl-h-sm); cursor: pointer; list-style: none;
  font: var(--ctl-font-sm); color: var(--fg-3);
  border-radius: var(--r-sm);
}
.set-more > summary::-webkit-details-marker { display: none; }
.set-more > summary::before {
  /* the glyph is decoration: generated content counts toward the control's
     accessible name, so a screen reader said "plus Suggested Deals"; the
     empty alternative (`/ ""`) keeps it out of the name while the state is
     still exposed as expanded or collapsed (row 43 review). The bare
     `content` before it is the fallback for a browser without alt text. */
  content: "+"; content: "+" / ""; font: var(--ctl-font-num);
  color: var(--violet-bright); width: 1ch;
}
.set-more[open] > summary::before { content: "\2212"; content: "\2212" / ""; }
@media (hover: hover) {           /* H11, B21 */
  .set-more > summary:hover { color: var(--fg-1); }
}
.set-more > summary:focus-visible {
  outline: 2px solid var(--violet-bright); outline-offset: 2px;
}
.set-more > .set-help { max-width: 72ch; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* deploy status: label/value rows, read-only */
.set-deploy { display: flex; flex-direction: column; gap: var(--sp-3); }
.set-deploy-row {
  display: flex; gap: 12px; align-items: baseline;
  border-bottom: 1px solid var(--border-1); padding-bottom: 8px;
}
.set-deploy-row > span:first-child {
  flex: 0 0 150px; font: 500 11px var(--font-sans); color: var(--fg-3);
  text-transform: uppercase; letter-spacing: var(--tracking-label);
}
.set-deploy-row > span:last-child { flex: 1; font-size: 13px; }
.set-deploy-row code { font: 500 12px var(--font-mono); }
/* B63: the deploy card's disclosure -- the checkout path, the fetch note
   and the raw log, one click away (D29) */
.set-deploy-more { margin-top: var(--sp-3); }
.set-deploy-more code { font: 500 12px var(--font-mono); }
/* a status pill is one word-group: at 390 "2 updates on main" broke inside
   its own outline */
.set-deploy-row .set-badge { white-space: nowrap; }
/* on a phone (the PHONE LAYOUT query) a 150px label column leaves the
   value a third of the row: the label goes above its value instead */
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  .set-deploy-row { flex-direction: column; gap: var(--sp-1); }
  .set-deploy-row > span:first-child { flex: none; }
}
/* Model Fits (X5 M1): a row's first cell is a league's NAME, the user's own
   text, so it is never recased (B28: user data is never recased). The deploy
   row's label slot is uppercase, which is for fixed labels; this keeps its
   width and wrapping and drops the transform. Later than the rule above, at
   the same specificity. */
.set-fit-row > span:first-child {
  font: 500 13px var(--font-sans); color: var(--fg-1);
  text-transform: none; letter-spacing: 0;
}
.set-badge-ok { border-color: var(--good); color: var(--good); }
.set-badge-warn { border-color: var(--warn); color: var(--warn); }
.set-badge-bad { border-color: var(--bad); color: var(--bad); }
.set-badge-muted { border-color: var(--border-1); color: var(--fg-3); }
/* the last weekly refresh's step lines (Q16): one per row, a WARNING line
   in the warning colour and a FAILED line in the danger colour. The words
   WARNING / FAILED stay in the text, so colour is never the only signal. */
.set-run-lines {
  margin: 0; padding: 0; list-style: none; max-width: 72ch;
}
.set-run-lines li {
  padding: var(--sp-1) 0; border-bottom: 1px solid var(--border-1);
  font-size: 12px; color: var(--fg-2); overflow-wrap: anywhere;
}
.set-run-lines li.is-warning { color: var(--warning); }
.set-run-lines li.is-error { color: var(--danger); }

#settings-job {
  border: 1px solid var(--border-1); border-radius: var(--r-md);
  background: var(--glass-1); padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-4);
}
#settings-job.job-error { border-color: color-mix(in oklab, var(--danger) 45%, transparent); }
#settings-job.job-done { border-color: color-mix(in oklab, var(--success) 45%, transparent); }
.job-head { font: 500 13px var(--font-sans); }
.job-lines {
  margin-top: 6px; font: 400 11px var(--font-mono); color: var(--fg-3);
  overflow-wrap: anywhere;
}
#settings-job .set-more { margin: var(--sp-2) 0 0; }
.job-spin { color: var(--fg-3); animation: job-pulse 1.2s ease-in-out infinite; }
@keyframes job-pulse { 0%, 100% { opacity: .3; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .job-spin { animation: none; opacity: 1; }
}

/* --- the Advanced tab's lead, Model Health and the feeds list (B59) ---
   Model Health: one fold per weekly check. The summary is the row (name,
   its one plain line, the day it last ran, a status word), the same +/-
   glyph as `.set-more`; its body is the report, which loads when it opens.
   Nothing on the card is a button. */
.set-advanced-lead { margin: 0; }
.set-checks { display: flex; flex-direction: column; }
.set-check { border-top: 1px solid var(--border-1); }
.set-check:first-child { border-top: none; }
.set-check > summary {
  display: grid; grid-template-columns: 1ch minmax(0, 1fr) auto auto;
  align-items: center; column-gap: var(--sp-3);
  min-height: var(--ctl-h); padding: var(--sp-3) 0;
  cursor: pointer; list-style: none; border-radius: var(--r-sm);
}
.set-check > summary::-webkit-details-marker { display: none; }
.set-check > summary::before {        /* the glyph out of the name, as .set-more */
  content: "+"; content: "+" / ""; font: var(--ctl-font-num);
  color: var(--violet-bright);
}
.set-check[open] > summary::before { content: "\2212"; content: "\2212" / ""; }
.set-check > summary:focus-visible {
  outline: 2px solid var(--violet-bright); outline-offset: 2px;
}
@media (hover: hover) {           /* H11, B21 */
  .set-check > summary:hover .set-check-name { color: var(--violet-bright); }
}
.set-check-main { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
.set-check-name { font: 500 14px var(--font-sans); color: var(--fg-1); }
.set-check-what { font: var(--text-small); color: var(--fg-3); }
.set-check-when { font: var(--ctl-font-num); color: var(--fg-2); white-space: nowrap; }
/* the label is generated, so it is read out with the date and is not a title */
.set-check-when::before {
  content: "Last run "; font: var(--text-small); color: var(--fg-3);
}
.set-check > summary .set-badge { white-space: nowrap; }
.set-check-body { padding: 0 0 var(--sp-3) calc(1ch + var(--sp-3)); }
.set-check-body .set-help { max-width: 72ch; }
.set-check-next { margin: var(--sp-3) 0 0; }
/* a report is for reading: it wraps (pre-wrap, anywhere) rather than
   scrolling inside its own box, which would trap the wheel on a desktop
   and is forbidden on a phone (H10, D8) */
.set-check-body .set-report { max-height: none; overflow: visible; }
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  .set-check > summary { grid-template-columns: 1ch minmax(0, 1fr) auto; }
  .set-check-when { grid-column: 2; grid-row: 2; margin-top: var(--sp-1); }
  .set-check > summary .set-badge { grid-column: 3; grid-row: 1 / span 2; }
  .set-check-body { padding-left: 0; }
}

/* the feeds list: a real table at every width, fixed to the card, so its
   three short columns wrap instead of scrolling sideways (H10). The global
   `th` is sticky and right-aligned numerals; neither belongs in a card. */
.set-feeds {
  width: 100%; margin: var(--sp-2) 0 var(--sp-3);
  border-collapse: collapse; table-layout: fixed;
}
.set-feeds th, .set-feeds td {
  position: static; background: none; text-align: left; vertical-align: top;
  padding: var(--sp-2) var(--sp-2) var(--sp-2) 0;
  border-bottom: 1px solid var(--border-1); overflow-wrap: anywhere;
  font: var(--text-small); color: var(--fg-2);
}
.set-feeds thead th {
  font: var(--text-label); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-3);
}
.set-feeds tbody th { text-transform: none; letter-spacing: 0; color: var(--fg-1); }
.set-feeds td.num {
  font: var(--ctl-font-num); color: var(--fg-2); text-align: left;
  white-space: nowrap;
}
.set-feeds th:nth-child(1) { width: 40%; }
.set-feeds th:nth-child(2) { width: 36%; }
/* on a phone each feed is a two-line row, not three cramped columns: its
   name and the day it landed on the first line, the timer under them. The
   header row is hidden (every value names itself), and nothing scrolls. */
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  .set-feeds thead { display: none; }
  .set-feeds, .set-feeds tbody { display: block; }
  .set-feeds tr {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--sp-3); row-gap: var(--sp-1);
    padding: var(--sp-2) 0; border-bottom: 1px solid var(--border-1);
  }
  .set-feeds th, .set-feeds td { width: auto; padding: 0; border: 0; }
  /* the desktop column widths above have higher specificity than the line
     before this one, and left the name 40% of its own grid column: three
     lines of one word each (B59 review, B2) */
  .set-feeds th:nth-child(1), .set-feeds th:nth-child(2) { width: auto; }
  .set-feeds tbody th { grid-column: 1; grid-row: 1; }
  .set-feeds td.num { grid-column: 2; grid-row: 1; text-align: right; }
  .set-feeds td:last-child {
    grid-column: 1 / -1; grid-row: 2; color: var(--fg-3);
  }
  .set-feeds td:last-child::before { content: "Updated by "; }
}
#set-username { width: 100%; max-width: 320px; }

/* the Cleared Settings card (B59 fix round): which page set each saved
   value, then the value beside its name, wrapping rather than scrolling */
.set-cleared-group { margin: var(--sp-3) 0 var(--sp-2); font: var(--text-small); color: var(--fg-1); }
.set-cleared .set-deploy-row code { overflow-wrap: anywhere; }
.set-cleared .set-deploy-row > span:first-child { flex-basis: 14rem; }
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  /* stacked on a phone, so the basis would be a height */
  .set-cleared .set-deploy-row > span:first-child { flex-basis: auto; }
}

/* --- mock history --- */
#mock-history { max-width: 760px; margin: 40px auto; padding: 0 16px; }
#history-head { margin-bottom: 18px; }
#history-list { display: flex; flex-direction: column; gap: 8px; }
.history-card {
  display: flex; align-items: center; gap: 16px;
  background: var(--grad-panel); 
  border: 1px solid var(--border-1); border-radius: var(--r-md);
  box-shadow: var(--inset-glass);
  padding: 12px 14px; cursor: pointer;
}
@media (hover: hover) {           /* H11, B21 */
  .history-card:hover { background: var(--glass-2);
                        border-color: var(--border-accent); }
}
.history-grade {
  flex: none; width: 62px; text-align: center; border-radius: var(--r-md);
  border: 1px solid var(--border-1); padding: 6px 0;
  font: italic 900 24px/1 var(--font-display);
  background: var(--grad-text);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.history-main { flex: 1; display: flex; flex-direction: column; gap: 3px; }
.history-title { font: 500 14px var(--font-sans); color: var(--fg-1); }
.history-meta { font: 400 12px var(--font-mono); color: var(--fg-3); }
.history-stats {
  display: flex; gap: 14px; flex: none;
  font: 400 12px var(--font-mono); color: var(--fg-3);
}
.history-stats b { color: var(--fg-2); font-weight: 600; }
.history-del { flex: none; }
@media (max-width: 640px) {
  .history-card { flex-wrap: wrap; }
  .history-stats { width: 100%; }
}

/* --- setup screen --- */
#setup { max-width: 560px; margin: 40px auto; padding: 0 16px; }
#order-list { list-style: none; margin: 12px 0; padding: 0; }
#order-list li {
  display: flex; align-items: center; gap: 10px;
  background: var(--grad-panel);
  border: 1px solid var(--border-1); border-radius: var(--r-md);
  box-shadow: var(--inset-glass);
  padding: 7px 10px; margin-bottom: 4px; cursor: grab; user-select: none;
}
#order-list li.dragging { opacity: .4; }
#order-list li.me { border-color: var(--border-accent); background: var(--glass-2); }
#order-list li .slot-no {
  width: 26px; text-align: right; color: var(--fg-3);
  font: 500 12px var(--font-mono); font-variant-numeric: tabular-nums;
}
#order-list li .label { flex: 1; min-width: 0; overflow-wrap: anywhere; }
/* B57: the owner's Sleeper username, under the team name where it differs */
#order-list li .label .s-sub { display: block; }
#order-list li .move { display: flex; gap: 4px; }
/* flex-wrap: X10 — every season page's footer (Refresh, stamp)
   is a .setup-row, and at 390px an unwrapped row overflows the page. */
.setup-row {
  display: flex; align-items: center; gap: 16px; margin: 14px 0;
  flex-wrap: wrap;
}

/* --- top bar --- */
#topbar {
  display: flex; align-items: center; gap: 24px;
  /* left padding reserves the corner for the fixed lockup image: its box
     (--lockup-x + 290px, E6) plus --sp-4, the same 16px it had when the
     frame sat at left:4; the bar matches the lockup frame height so the
     asset never clips */
  padding: 14px 24px 14px calc(var(--lockup-x) + var(--lockup-w) + var(--sp-4));
  min-height: 108px;
  background: var(--glass-1); backdrop-filter: blur(20px);
  border-bottom: 1px solid var(--border-1);
  position: sticky; top: 0; z-index: 20;
  /* The bar wraps rather than squeezing the clock (Q13): with mock controls
     at 1280, or the ESPN chip and "Match this player" at 1280-1440, the one
     row had no room, the clock pill was squeezed into a three-line oval
     (bar 178px tall at 1280) and the ESPN row pushed the actions 89px off
     the page. Now the clock keeps its one line and the controls and actions
     take a second row; a live draft's bar still fits one row. */
  flex-wrap: wrap; row-gap: var(--sp-3);
}
#clock { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }
#clock-pick {
  font: 500 22px/1.1 var(--font-mono);
  padding: 0 0.24em; margin: 0 -0.24em;
  background: var(--grad-text);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
#clock-team { font: 600 15px var(--font-sans); color: var(--fg-1); }
#clock-team .oncl { font: 400 13px var(--font-sans); color: var(--fg-3); }
#clock-mine { color: var(--fg-3); font-size: 13px; }
#clock-mine.now { color: var(--success); }
#top-actions { display: flex; align-items: center; gap: var(--sp-2); margin-left: auto; }
/* Order only. The topbar used to re-declare its own button chrome three
   times over (a bespoke #btn-done aurora, an accent-rimmed #btn-sleeper, a
   glass #btn-new, plus a 32px height override); they are `primary`,
   `primary bm-focal` and a neutral button now, from the control system. */
#btn-new { order: 1; }   /* directly left of Done */
#btn-done { order: 2; }  /* Done anchors the far right of the action row */
#saved { color: var(--success); font: 500 12px var(--font-mono); }

/* --- alerts --- */
#alerts { display: flex; gap: 8px; flex-wrap: wrap; padding: 0 16px; }
#alerts:not(:empty) { padding: 8px 16px 0; }
.alert {
  background: color-mix(in oklab, var(--danger) 8%, transparent);
  border: 1px solid color-mix(in oklab, var(--danger) 35%, transparent);
  color: color-mix(in oklab, var(--danger) 40%, white);
  border-radius: var(--r-sm); padding: 3px 10px; font-size: 13px;
}

/* --- viewport-locked draft layout ---
   board left (widest), three separate info panels right; the page never
   scrolls while drafting — each pane scrolls internally */
body.draft-locked { overflow: hidden; height: 100vh; }
body.draft-locked #draft { display: flex; flex-direction: column; height: 100vh; }
body.draft-locked #topbar, body.draft-locked #alerts { flex-shrink: 0; }
#main { display: flex; gap: 12px; padding: 16px 24px; flex: 1; min-height: 0; }
#board-pane { flex: 1.5; min-width: 0; display: flex; flex-direction: column; }
#board-controls { flex-shrink: 0; }
body.draft-locked #board-wrap,
body.draft-locked #grid-wrap { flex: 1; min-height: 0; max-height: none; }
#info-area { flex: 1; min-width: 320px; max-width: 640px;
             display: flex; flex-direction: column; gap: 12px; min-height: 0; }
/* pane-config row height-matches #board-controls so the info panes below
   top-align with the draft board pane */
#info-head { flex-shrink: 0; display: flex; align-items: center;
             min-height: 34px; }
#pane-config button.active { box-shadow: none; } /* quiet aurora, no glow */
#info-cols { display: flex; gap: 12px; flex: 1; min-height: 0; }
#roster-col { flex: 1.1; min-width: 0; display: flex; }
#side-col { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 12px; }
.panel {
  /* A data surface, so the wash rather than glass (UI_GUIDELINES.md 2b).
     Blur is reserved for things that float over content; these are inline. */
  background: var(--grad-panel);
  border: 1px solid var(--border-1); border-radius: var(--r-lg);
  box-shadow: var(--shadow-card), var(--inset-glass);
  padding: 14px; overflow: hidden;
}
.panel h2 { flex-shrink: 0; }
#roster-panel { flex: 1; min-height: 0; display: flex; flex-direction: column; }
#my-roster { flex: 1; min-height: 0; overflow: auto; }
#my-needs { flex-shrink: 0; }
/* scarcity hugs its content; the picks feed takes every spare pixel */
#scarcity-panel { flex: 0 1 auto; min-height: 0; display: flex; flex-direction: column; }
#scarcity-cards { flex: 0 1 auto; min-height: 0; overflow: auto; }
#picks-panel { flex: 1 1 0; min-height: 120px; display: flex; flex-direction: column; }
/* trades: same scroll treatment as its siblings — .panel clips overflow,
   so the cards need their own scrolling box or long text gets cut off */
#trades-panel { flex: 0 1 auto; min-height: 0; display: flex; flex-direction: column; }
#trades-panel h2 { display: flex; align-items: baseline; justify-content: space-between; }
#trade-cards { flex: 0 1 auto; min-height: 0; overflow: auto; }
.trade-card { overflow-wrap: anywhere; }

/* --- brand lockup (reserved #brand-slot) ---
   the official transparent lockup asset (lockup-2800.png, 2800x1040):
   wordmark + mark + orbit + halo in one image. The frame is shown WHOLE
   at exactly bar height (top:0) so nothing ever clips — the solid
   composition spans the middle 75% of the frame and self-centers.
   290px wide keeps the ≥280px brand minimum (BRAND-ASSETS.md). The frame
   hangs off the left edge by its own transparent margin, so the ink lands
   on the page gutter (--lockup-x, B8); nothing visible is cut. */
#brand-slot {
  position: fixed; top: 0; left: var(--lockup-x); z-index: 21;
  width: 290px; height: 108px; cursor: pointer;
}
#brand-slot .brand-img { width: 100%; height: 100%; display: block; }
/* The frame now hangs past the viewport's left edge (E6) and sits flush
   with its top (`top: 0`), so a ring drawn outside the frame would be cut
   off on both of those sides. The ring is drawn round the INK
   instead (x 17.6..82.4%, y 7.3..86% of the frame), a --sp-2 outside it:
   the same D27 ring, where the eye sees the logo. */
#brand-slot:focus-visible { outline: none; }
#brand-slot:focus-visible::after {
  content: ""; position: absolute; pointer-events: none;
  inset: var(--sp-1) calc(var(--lockup-ink) * 100% - var(--sp-2))
         calc(14% - var(--sp-2));
  outline: 2px solid var(--violet-bright); border-radius: var(--r-sm);
}

/* B34: below 1200px there is no pane for the lockup to head, so it scrolls
   away with the page like every small window's -- outside the war room,
   whose own bar keeps it fixed. It keeps the 290x108 frame here: a fixed
   frame at 1151-1199 would sit over the compact bar. 1150 and below has
   the same rule, with the 280px brand minimum, in the block that follows. */
@media (min-width: 1151px) and (max-width: 1199px) {
  body:not(.draft-locked) #brand-slot { position: absolute; }
}

/* small windows: fall back to the stacked flow with normal page scroll */
@media (max-width: 1150px) {
  body.draft-locked { overflow: auto; height: auto; }
  body.draft-locked #draft { display: block; height: auto; }
  #main { flex-direction: column; }
  #info-cols { flex-direction: column; }
  /* board gets a definite height so the row-fit logic has a real budget */
  #board-wrap,
  body.draft-locked #board-wrap { flex: none; height: 70vh; max-height: 70vh; }
  #grid-wrap,
  body.draft-locked #grid-wrap { flex: none; max-height: 70vh; }
  #my-roster, #scarcity-cards, #history, #wait-chart { max-height: 40vh; }
  /* narrow windows: the same lockup asset at the 280px brand minimum,
     whole frame at bar height, ink on the gutter (B8) */
  #brand-slot { position: absolute; top: 0;
                left: calc(var(--sp-6) - 280px * var(--lockup-ink));
                width: 280px; height: 104px; }
  body.draft-locked #brand-slot { position: fixed; }
  /* the corner again: this lockup's box right edge is --sp-6 + 82.4% of
     280px (its left is --sp-6 - 17.6%), then --sp-4 of clear space */
  #topbar { padding: 10px 16px 10px
              calc(var(--sp-6) + 280px * (1 - var(--lockup-ink)) + var(--sp-4));
            min-height: 104px; }
  /* below the three-pane width the topbar's one row (clock, mock
     controls, five actions) is ~1200px long and pushed the whole page
     sideways; the bar wraps (base rule), and each group wraps inside itself */
  #mock-controls, #top-actions { flex-wrap: wrap; }
}
/* a phone's page gutter is --sp-4 (the season shell, Home and the war
   room at this width), so the lockup's ink follows it there (B8) */
@media (max-width: 640px) {
  #brand-slot { left: calc(var(--sp-4) - 280px * var(--lockup-ink)); }
}
/* A phone cannot hold anything beside a 280px lockup, so the bar starts
   under it -- vertical clearance, the same answer the other screens give
   at this width (see the lockup-reserve media queries). */
@media (max-width: 640px) {
  /* overflow-x: clip -- belt and braces for the on-the-clock glow
     (board-moments; it no longer scales, D30) so nothing it paints can
     widen the page's scrollable area at this width */
  #topbar { padding: calc(104px + var(--sp-2)) var(--sp-4) var(--sp-3);
            overflow-x: clip; }
  /* the pill (and its opened label) stays inside the bar */
  #draft #clock { max-width: 88%; }
  #top-actions { margin-left: 0; }
}

/* ============================================================================
   THE WEB SIGN-IN (B6 phase 3, 2026-10-07; UI_GUIDELINES D38)
   ----------------------------------------------------------------------------
   Three small pieces, all drawn from the control system, none with a colour
   or a length of its own:
     the sign-in page    static/login.html, no script, served at /login by
                         ffdraft_gui/web_gate.py. The lockup is #brand-slot
                         exactly as on every screen (E6); the card sits in the
                         area UNDER the lockup's band, so the corner is
                         reserved structurally at every width (E3) and never
                         by a centred column's arithmetic (E4).
     the signed-out card  app.js, when a gated server says 401 mid-use. The
                         same card over an opaque nebula, above every other
                         layer but the lockup, the toast and the skip link
                         (z 59; the lockup is raised over it on purpose).
     Log Out             a <form method="post">, never a link (a GET that
                         signs out would let any page do it): neutral in the
                         menu, primary on the Account card (Owner Rule 2).
   The card is a form on a data surface (2b): --grad-panel, a hairline, the
   card shadow and --inset-glass, and NO blur.
   ========================================================================= */
.login, #signed-out {
  min-height: 100vh; min-height: 100dvh;
  display: grid; place-items: center;
  padding: var(--lockup-clear) var(--sp-4) var(--sp-8);
}
.login-card {
  width: min(380px, 100%);
  display: flex; flex-direction: column; gap: var(--sp-4);
  padding: var(--sp-6);
  background: var(--grad-panel);
  border: 1px solid var(--border-1); border-radius: var(--r-lg);
  box-shadow: var(--shadow-card), var(--inset-glass);
}
.login-card h1 { margin: 0; }
.login-lead { margin: 0; font: var(--text-small); color: var(--fg-2); }
.login-field { display: flex; flex-direction: column; gap: var(--sp-2); }
.login-field label { font: var(--text-small); font-weight: 600; color: var(--fg-1); }
.login-field input, .login-card .primary { width: 100%; }
/* a refusal, in words on a tinted well: the --danger tint the danger family
   uses, the text --fg-1 measured on the card, never the hue alone (C5) */
.login-alert {
  margin: 0; padding: var(--sp-3);
  background: color-mix(in oklab, var(--danger) 14%, transparent);
  border: 1px solid color-mix(in oklab, var(--danger) 55%, transparent);
  border-radius: var(--r-md);
  font: var(--text-small); color: var(--fg-1);
}
/* the password manager's user name: in the page, out of sight, out of the
   Tab order and the reading order (the field rules above would otherwise
   give it a control's height) */
input.login-user {
  position: absolute; width: 1px; height: 1px; min-height: 0;
  padding: 0; margin: -1px; border: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

/* signed out in the middle of a visit: the card over an opaque nebula. The
   page behind is made inert by app.js, and cannot scroll */
#signed-out {
  position: fixed; inset: 0; z-index: 59; overflow-y: auto;
  /* the nebula again, opaque, so the page behind cannot show through. The
     card is itself the viewport (fixed, inset 0), so the gradient lands
     where the layer's does without `background-attachment: fixed`, which
     would cost a main-thread repaint per wheel step (B44, 27a; the row's
     test forbids it anywhere but the iOS block) */
  background: var(--grad-nebula), var(--bg-0);
}
body.signed-out { overflow: hidden; }
body.signed-out #brand-slot { position: fixed; z-index: 60; }

/* the menu's Log Out: the neutral family, the full width of the pane or
   drawer, under the Settings and Account row */
#nav-logout { margin: 0; }
#nav-logout button { width: 100%; }

/* --- board controls --- */
/* single-line controls (scroll sideways rather than wrap) so the row's
   height always matches the pane-config row across the gutter */
#board-controls { display: flex; gap: var(--sp-2); flex-wrap: nowrap;
                  align-items: center; margin-bottom: var(--sp-3);
                  overflow-x: auto; min-height: var(--ctl-h); }
/* #search is sized by its .ctl-search wrapper (the control system) */
/* .btn-group itself lives in the control system above -- this row only says
   how wide the search box may grow. The second copy of the segmented-toggle
   recipe that used to sit here (its own glass, 3px rail, 12px type and two
   dead :first-child/:last-child radius rules) is gone. */
/* Between the stacked fallback (<=1150px) and ~1320px the four control
   groups plus a legible search box no longer fit on one line, and the
   strip's sideways scroll would hide the Board/Grid toggle off its right
   edge. Here, and only here, the strip wraps: every control keeps
   --ctl-h, nothing scrolls out of view, and #info-head grows by the same
   one row so the info panes still top-align with the board -- which is
   what the height-match above exists for. */
@media (min-width: 1151px) and (max-width: 1320px) {
  #board-controls { flex-wrap: wrap; row-gap: var(--sp-2); overflow-x: visible; }
  #info-head { min-height: calc(var(--ctl-h) * 2 + var(--sp-2));
               align-items: flex-start; }
}

/* --- board table --- */
#board-wrap, #grid-wrap {
  /* a data surface: the wash, and no blur -- blur means "floats over
     content", and the board is the content (UI_GUIDELINES.md 2b) */
  background: var(--grad-panel);
  border: 1px solid var(--border-1); border-radius: var(--r-lg);
  box-shadow: var(--shadow-card), var(--inset-glass);
  overflow: auto;
}
/* default board fits whole rows with no scrollbar; app.js flips .scroll-on
   after "show all" (or while searching) to unlock internal scrolling */
#board-wrap { overflow: hidden; }
#board-wrap.scroll-on { overflow-y: auto; }
/* board/grid panes: paint the header band on the pane itself so it reaches
   both rounded edges (the table's sticky header stops short of the
   scrollbar gutter); the scroll thumb is bounded between the band and the
   bottom curve */
#board-wrap, #grid-wrap {
  --head-band: 35px; /* thead height — keep in sync with th font/padding */
  background:
    linear-gradient(var(--bg-1), var(--bg-1)) top / 100% var(--head-band) no-repeat,
    var(--grad-panel);
}
#board-wrap::-webkit-scrollbar-track,
#grid-wrap::-webkit-scrollbar-track {
  margin-top: var(--head-band); margin-bottom: var(--r-lg);
}

table { border-collapse: collapse; width: 100%; }
#board-table th, #grid-wrap th {
  padding: 10px 14px; text-align: left; white-space: nowrap;
}
#board-table td, #grid-wrap td {
  padding: 9px 14px; text-align: left; white-space: nowrap;
}
/* 11px (B2) on a 14px line: the same 14px line box 10px/1.4 gave, so the
   thead stays exactly --head-band (10 + 14 + 10 padding + 1 border = 35) */
th { color: var(--fg-3); font: 500 11px/14px var(--font-sans); text-transform: uppercase;
     letter-spacing: .1em; border-bottom: 1px solid var(--fg-4);
     position: sticky; top: 0; background: var(--bg-1); }
td { border-bottom: 1px solid var(--border-1); font-variant-numeric: tabular-nums; }
td.num, th.num { text-align: right; }
td.num { font: 500 12px var(--font-mono); font-variant-numeric: tabular-nums; color: var(--fg-2); }
/* reference color ramp: row index fg-4 -> overall rank fg-3 -> stats fg-2 */
#board-table td.num:first-child { color: var(--fg-4); }
#board-table td.num:nth-child(2) { color: var(--fg-3); }
#board-table tbody tr { cursor: pointer;
  transition: background var(--dur-quick) var(--ease-drift); }
#board-table tbody tr:nth-child(odd) { background: oklch(0.85 0.05 295 / .03); }
/* a tapped row must not stay lit on a phone (H11, B21) */
@media (hover: hover) {
  #board-table tbody tr:hover { background: var(--glass-2); }
}
#board-table tbody tr.selected { background: var(--glass-2);
  outline: 1px solid var(--border-accent); outline-offset: -1px; }
/* positions my roster can no longer use (e.g. a second K/DEF) */
#board-table tbody tr.redundant { opacity: 0.45; }
.player-name { font: 600 13px var(--font-sans); color: var(--fg-1); }
/* the ranking score (last column) is the row's focal number */
#board-table tbody td.num:last-child {
  font-weight: 600;
  background: var(--grad-text);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.rookie { color: var(--warning); font-size: 11px; vertical-align: super; margin-left: 2px; }
/* the text is --red-bright, a text token: --danger at 11px measured 4.41:1
   on --grad-panel's lightest stop and 3.93:1 on the Lineup's hole wash,
   where a ruled-out badge always sits (W2-LINEUP review F5); --red-bright
   is 6.46:1 there. The hairline keeps the --danger hue. */
.inj { color: var(--red-bright); font-size: 11px;
       border: 1px solid color-mix(in oklab, var(--danger) 45%, transparent);
       border-radius: 4px; padding: 0 4px; margin-left: 6px; }

/* position tags: colored mono text, no fill */
.pos { display: inline-block; min-width: 38px; text-align: left;
       font: 600 11px/1.4 var(--font-mono); padding: 1px 0; }
.pos-QB { color: var(--qb); } .pos-RB { color: var(--rb); }
.pos-WR { color: var(--wr); } .pos-TE { color: var(--te); }
.pos-K { color: var(--k); }  .pos-DEF { color: var(--def); }

/* stays visible at the pane's bottom edge while the list scrolls;
   heavily frosted so the rows behind it read as texture, not content */
#show-more {
  position: sticky; bottom: 8px; margin: 8px;
  background: oklch(0.28 0.045 296 / .78);
  backdrop-filter: blur(24px) saturate(1.3);
  box-shadow: var(--shadow-card), var(--inset-glass);
}

/* record-unlisted-player escape hatch under the search results */
#unlisted-add { margin: var(--sp-2) var(--sp-3); }

/* drafted search results */
#drafted-matches { border-top: 1px solid var(--border-1); padding: 8px 12px; }
#drafted-matches .drafted-row { color: var(--fg-3); padding: 3px 0; font-size: 13px; }
#drafted-matches .drafted-row .player-name { color: var(--fg-1); }
.drafted-tag { color: var(--danger); font: 700 11px var(--font-mono); margin-left: 8px; }

/* --- grid --- */
#grid-wrap { max-height: 74vh; }
#grid-wrap table { width: max-content; }
#grid-wrap th.me-col, #grid-wrap td.me-col {
  background: color-mix(in oklab, var(--violet) 12%, var(--bg-1));
}
#grid-wrap th:first-child, #grid-wrap td:first-child {
  position: sticky; left: 0; background: var(--bg-1);
  color: var(--fg-3); font: 500 11px var(--font-mono); z-index: 5;
}
#grid-wrap td { font-size: 12px; min-width: 110px; max-width: 150px; overflow: hidden;
                text-overflow: ellipsis; }
/* B57: a seat is named by its team, which runs longer than a username did.
   The head's text takes the body cell's width (150px less the cell's 28px of
   padding) and ellipsises; the full name is the head's title. A cell's own
   max-width does nothing in an auto-layout table, so the cap is on a block
   inside it. Without this a long team name widens its whole column, and the
   grid (a scroller already) grows by the difference twelve times over. */
#grid-wrap th .seat-head {
  display: block; max-width: 122px; overflow: hidden; text-overflow: ellipsis;
}
#grid-wrap td.on-clock {
  background: var(--aurora-fill); background-clip: padding-box;
  box-shadow: var(--glow-violet); color: var(--text-on-accent);
}
/* the position tag in this cell is the full label colour: at 90% alpha it
   measured 4.13:1 on the scrimmed fill (row 40), and a literal oklch here was
   a C10 defect besides */
#grid-wrap td.on-clock .pos { color: var(--text-on-accent); }
#grid-wrap td .pos { min-width: 26px; padding: 0; font-size: 11px; margin-right: 4px; }

/* --- the grid on a phone (B22) ---------------------------------------------
   Twelve teams by fourteen rounds scrolled both ways inside its own box (H10
   forbids that on a phone). One team's picks are listed instead, a row per
   round, under a select of the teams (app.js `renderGridPhone`); the box
   stops being a scroller and the page grows. The head band painted on the
   table's pane has no head to sit behind, so it goes too. The on-the-clock
   row is the table cell's own look (aurora fill, glow). Desktop keeps the
   table: nothing here is outside this query. */
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  #grid-wrap, body.draft-locked #grid-wrap {
    flex: none; max-height: none; overflow: visible;
    background: var(--grad-panel);
    padding: var(--sp-3) var(--sp-4) var(--sp-2);
  }
  #grid-wrap .gp-pick {
    display: flex; align-items: center; gap: var(--sp-2);
    font: var(--text-small); color: var(--fg-2);
  }
  #grid-wrap .gp-team { flex: 1 1 auto; min-width: 0; }
  #grid-wrap .gp-count { margin: var(--sp-2) 0 var(--sp-1); font-size: 13px; }
  #grid-wrap .gp-list { list-style: none; margin: 0; padding: 0; }
  #grid-wrap .gp-row {
    display: flex; align-items: center; gap: var(--sp-3);
    min-height: var(--ctl-h); padding: var(--sp-2) var(--sp-2);
    border-bottom: 1px solid var(--border-1);
  }
  #grid-wrap .gp-row:last-child { border-bottom: 0; }
  #grid-wrap .gp-rd {
    flex: 0 0 32px; color: var(--fg-3); font: 500 11px var(--font-mono);
  }
  #grid-wrap .gp-body { flex: 1 1 auto; min-width: 0; }
  #grid-wrap .gp-pick-line {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1);
    min-width: 0; font-size: 14px;
  }
  /* a traded pick's "via" takes its own line under the name, so the name
     keeps its room; a long team name wraps inside the row (F68 S2) */
  #grid-wrap .gp-pick-line .via {
    order: 9; flex: 0 0 calc(100% - var(--sp-8)); box-sizing: border-box;
    margin: 0 0 0 var(--sp-8); border-radius: var(--r-sm);
    white-space: normal; overflow-wrap: anywhere;
  }
  #grid-wrap .gp-pick-line .pos {
    flex: 0 0 auto; min-width: 28px; padding: 0; font-size: 11px;
  }
  #grid-wrap .gp-pick-line .pname {
    min-width: 0; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap;
  }
  #grid-wrap .gp-no {
    margin-left: auto; padding-left: var(--sp-2); flex: 0 0 auto;
    color: var(--fg-3); font: 500 11px var(--font-mono);
  }
  #grid-wrap .gp-empty { color: var(--fg-3); }
  #grid-wrap .gp-row.on-clock {
    background: var(--aurora-fill); background-clip: padding-box;
    box-shadow: var(--glow-violet); border-radius: var(--r-sm);
    border-bottom-color: transparent;
    color: var(--text-on-accent);
  }
  #grid-wrap .gp-row.on-clock .gp-rd, #grid-wrap .gp-row.on-clock .gp-no,
  #grid-wrap .gp-row.on-clock .pos, #grid-wrap .gp-row.on-clock .via {
    color: var(--text-on-accent);
  }
  #grid-wrap .gp-clock { font-weight: 700; }
}

/* --- sidebar --- */
#my-roster div, #history div { padding: 2px 0; font-variant-numeric: tabular-nums; }
#my-roster .rp { color: var(--fg-3); font-size: 12px; margin-right: 6px; }
#my-needs { margin-top: 10px; display: flex; gap: 6px; flex-wrap: wrap; }
.need-chip { background: var(--glass-2); border: 1px solid var(--border-1);
             border-radius: var(--r-pill); padding: 2px 9px;
             font: 500 11px/16px var(--font-mono); }
.need-chip:not([class*="pos-"]) { color: var(--fg-2); }
.need-chip b { font-weight: 600; }
/* --- scarcity cards (roster-pane design language) --- */
#scarcity-cards { display: flex; flex-direction: column; gap: 5px; }
#scarcity-cards .scar-card {
  border: 1px solid var(--border-1); background: transparent;
  border-radius: var(--r-sm); padding: 6px 9px; font-variant-numeric: tabular-nums;
}
#scarcity-cards .scar-top { display: flex; align-items: center; gap: 8px; }
#scarcity-cards .slot-tag {
  min-width: 42px; text-align: left; font: 600 11px/1.5 var(--font-mono);
  padding: 2px 0;
  text-transform: uppercase; letter-spacing: .03em;
}
/* position color is load-bearing; grey only when there is no pos class */
#scarcity-cards .slot-tag:not([class*="pos-"]) { color: var(--fg-4); }
#scarcity-cards .scar-supply { flex: 1; font-size: 12px; color: var(--fg-2); }
#scarcity-cards .scar-pill {
  font: 500 11px/16px var(--font-mono); color: var(--fg-3);
  border: 1px solid var(--border-1);
  border-radius: var(--r-pill); padding: 1px 8px; white-space: nowrap;
}
#scarcity-cards .scar-best {
  display: flex; align-items: center; gap: 8px; margin-top: 4px;
  padding-left: 50px; font-size: 12px; min-width: 0;
}
#scarcity-cards .scar-best .pname {
  font-weight: 600; font-size: 13px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#scarcity-cards .scar-label { color: var(--fg-4); }
#scarcity-cards .scar-repl {
  margin-left: auto; color: var(--fg-4);
  font: 500 11px var(--font-mono); white-space: nowrap;
}
#scarcity-cards .scar-wait {
  display: flex; align-items: center; gap: 8px; margin-top: 2px;
  padding-left: 50px; font-size: 12px; min-width: 0;
}
#scarcity-cards .wait-cost { font: 500 11px var(--font-mono); color: var(--fg-3); }
#scarcity-cards .scar-wait.costly .wait-cost {
  color: color-mix(in oklab, var(--danger) 60%, var(--fg-2)); font-weight: 700;
}
#scarcity-cards .wait-safe {
  margin-left: auto; color: var(--fg-4); font-size: 11px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#scarcity-cards .scar-card.tight {
  border-color: color-mix(in oklab, var(--danger) 35%, transparent);
  background: color-mix(in oklab, var(--danger) 8%, transparent);
}
#scarcity-cards .scar-card.tight .scar-supply {
  color: color-mix(in oklab, var(--danger) 40%, white);
}
#scarcity-cards .scar-card.tight .scar-pill {
  color: var(--danger);
  border-color: color-mix(in oklab, var(--danger) 45%, transparent);
  font-weight: 600;
}
#scarcity-cards .scar-card.tight .scar-label,
#scarcity-cards .scar-card.tight .scar-repl {
  color: color-mix(in oklab, var(--danger) 30%, var(--fg-3));
}
/* value-in-waiting pane: per-position now-vs-next bars + survival dots */
#wait-chart { flex: 1; min-height: 0; overflow: auto; }
#wait-chart .wait-empty { font-size: 12px; padding: 4px 2px; }
#wait-chart .wait-card {
  border-radius: var(--r-md); padding: 6px 8px; margin-bottom: 4px;
}
#wait-chart .wait-card:nth-child(odd) { background: oklch(0.85 0.05 295 / .03); }
#wait-chart .wait-head { display: flex; align-items: center; gap: 8px; }
#wait-chart .wait-cost {
  margin-left: auto; font: 600 11px var(--font-mono); color: var(--fg-3);
}
#wait-chart .wait-card.costly .wait-cost {
  color: color-mix(in oklab, var(--danger) 60%, var(--fg-2)); font-weight: 700;
}
#wait-chart .wait-bar-row {
  display: flex; align-items: center; gap: 6px; margin-top: 4px;
}
#wait-chart .wait-bar-label {
  width: 28px; font-size: 11px; line-height: 1.3; /* the ~14.5px row 10px gave */
  color: var(--fg-4); text-align: right;
}
#wait-chart .wait-bar-track {
  flex: 1; height: 8px; border-radius: var(--r-pill);
  background: var(--glass-1); overflow: hidden;
}
#wait-chart .wait-bar-fill {
  display: block; height: 100%; border-radius: var(--r-pill);
  background: currentColor; opacity: 0.9;   /* .pos-XX sets currentColor */
  transition: width var(--dur-quick) var(--ease-drift);
}
#wait-chart .wait-bar-fill.next { opacity: 0.35; }
#wait-chart .wait-card.costly .wait-bar-fill.next {
  opacity: 0.35;
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--danger) 45%, transparent);
}
#wait-chart .wait-bar-num {
  width: 34px; font: 500 11px var(--font-mono); color: var(--fg-2);
  text-align: right;
}
#wait-chart .wait-dots {
  display: flex; align-items: center; gap: 5px;
  margin: 6px 0 0 34px;
}
#wait-chart .wait-dot {
  width: 10px; height: 10px; border-radius: 50%; cursor: pointer;
  border-bottom: none;   /* the .pname dotted affordance is for text */
  transition: transform var(--dur-quick) var(--ease-drift);
}
@media (hover: hover) {           /* H11, B21 */
  #wait-chart .wait-dot:hover { transform: scale(1.35); }
}
#wait-chart .wait-dot.safe { background: var(--success); }
#wait-chart .wait-dot.risky { background: var(--warning); }
#wait-chart .wait-dot.gone {
  background: color-mix(in oklab, var(--danger) 70%, transparent);
}
#wait-chart .wait-foot {
  margin: 4px 0 0 34px; font-size: 11px; color: var(--fg-4);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

#history { flex: 1; min-height: 0; overflow: auto; font-size: 13px; }
#history button { margin-left: var(--sp-1); flex-shrink: 0; }

/* pick mini-cards (rolling feed, newest first) */
#history .pick-card {
  display: flex; align-items: center; gap: 7px;
  border: none; background: transparent;
  border-radius: var(--r-sm); padding: 4px 8px; margin-bottom: 2px;
}
#history .pick-card:nth-child(odd) { background: oklch(0.85 0.05 295 / .03); }
#history .pick-card .slot-tag {
  min-width: 34px; text-align: left; font: 500 11px/15px var(--font-mono);
  padding: 1px 0; color: var(--fg-4); flex-shrink: 0;
}
#history .pick-card .slot-tag.pos-QB { color: var(--qb); }
#history .pick-card .slot-tag.pos-RB { color: var(--rb); }
#history .pick-card .slot-tag.pos-WR { color: var(--wr); }
#history .pick-card .slot-tag.pos-TE { color: var(--te); }
#history .pick-card .pick-name {
  font-weight: 500; font-size: 12px; min-width: 0; color: var(--fg-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#history .pick-card .pick-meta {
  margin-left: auto; color: var(--fg-4); font: 500 11px var(--font-mono);
  white-space: nowrap; flex-shrink: 0;
}

/* --- confirm bar --- */
#confirm {
  position: fixed; bottom: 26px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 14px; z-index: 50;
  background: var(--glass-2); backdrop-filter: blur(var(--blur-glass));
  border: 1px solid var(--border-accent); border-radius: var(--r-xl);
  padding: 12px 18px;
  box-shadow: var(--shadow-float), var(--glow-violet), var(--inset-glass);
}
#confirm-player { font-weight: 600; font-size: 14px; }

/* --- mock setup --- */
#mock-setup { max-width: 620px; margin: 40px auto; padding: 0 16px; }
/* the field recipe is the control system's; this sets only the width */
#mock-setup input[type="number"] { width: 76px; }
/* the seed's placeholder ("random") was clipped to "rando" at 76px */
#mock-setup #mock-seed { width: 112px; }
.roster-inputs { display: flex; gap: var(--sp-3); flex-wrap: wrap; }
.roster-inputs label {
  display: flex; flex-direction: column; gap: 2px; font-size: 11px;
  color: var(--fg-3); text-transform: uppercase; letter-spacing: .1em;
}
.roster-inputs input[type="number"] { width: 58px; }

/* --- trades pane: MC verification readout --- */
.trade-mc-out { font: 500 12px var(--font-mono); color: var(--fg-3);
                margin: 0 8px; }
.trade-mc-out.ok { color: var(--violet-bright); }
.trade-mc-out.bad { color: var(--red-bright); }

/* --- ESPN draft setup --- */
#espn-setup { max-width: 620px; margin: 40px auto; padding: 0 16px; }
/* the field recipe and the focus ring are the control system's; this
   screen sets only its widths below */
#espn-league-id { width: 132px; }
#espn-year { width: 76px; }
#espn-league-pick, #espn-my-team { min-width: 250px; }
#espn-setup code {
  font: 400 12px var(--font-mono); color: var(--fg-2);
  background: var(--glass-1); border: 1px solid var(--border-1);
  border-radius: var(--r-sm); padding: 1px 5px;
}
/* the Sync job's progress line, styled like a small glass log card */
#espn-job {
  margin: 12px 0; padding: 8px 12px;
  background: var(--grad-panel);
  border: 1px solid var(--border-1); border-radius: var(--r-md);
  box-shadow: var(--inset-glass);
  font: 400 12px/1.5 var(--font-mono); color: var(--fg-2);
  overflow-wrap: anywhere;
}

/* --- mock mode badge --- */
#mode-badge {
  background: var(--aurora-fill); background-clip: padding-box;
  color: var(--text-on-accent); font: 500 11px/1.9 var(--font-sans);
  border-radius: var(--r-pill); padding: 0 9px; letter-spacing: .06em;
  text-transform: uppercase; box-shadow: var(--glow-violet);
}

/* --- mock report --- */
#report-overlay {
  position: fixed; inset: 0; background: oklch(0.10 0.02 300 / .6);
  backdrop-filter: blur(8px); z-index: 56; padding: 24px;
  display: flex; align-items: center; justify-content: center;
}
/* the report box and the how-it-works explainer are two sibling glass
   panes; the explainer docks to the right and never squeezes the table */
#report-layout {
  display: flex; gap: 16px; align-items: flex-start;
  justify-content: center; width: min(1400px, 96vw);
}
#report-box {
  background: var(--glass-2); backdrop-filter: blur(var(--blur-glass));
  border: 1px solid var(--border-2); border-radius: var(--r-lg);
  box-shadow: var(--shadow-float), var(--inset-glass);
  flex: 1; min-width: 0; max-height: 90vh; display: flex; flex-direction: column;
}
#report-aside {
  /* nested side pane, not a second modal: glass-1, hairline, inset only */
  width: 250px; flex-shrink: 0; max-height: 90vh; overflow: auto;
  background: var(--glass-1); backdrop-filter: blur(var(--blur-glass));
  border: 1px solid var(--border-1); border-radius: var(--r-lg);
  box-shadow: var(--inset-glass);
  padding: 12px 14px;
  font: 400 12px/1.55 var(--font-sans); color: var(--fg-2);
}
#report-aside p { margin: 0 0 8px; }
#report-aside p:last-child { margin-bottom: 0; }
/* on narrow windows the explainer drops below the report instead */
@media (max-width: 1020px) {
  #report-layout { flex-direction: column; align-items: center; overflow: auto;
                   max-height: 92vh; }
  #report-box { max-height: none; width: 100%; }
  #report-aside { width: 100%; max-height: none; }
}
#report-head { display: flex; justify-content: space-between; align-items: center;
               padding: 12px 16px; border-bottom: 1px solid var(--border-1); }
#report-head > span {
  font: italic 900 17px/1.2 var(--font-display); letter-spacing: .02em;
}
#report-body { padding: 14px 16px; overflow: auto; }

.rep-side-title {
  display: block; margin-bottom: 8px;
  font: var(--text-label); text-transform: uppercase;
  letter-spacing: var(--tracking-label); color: var(--fg-3);
}

/* grade pane: glass with the grade's color as a glow, letter in brand type */
.grade-banner {
  display: flex; align-items: center; gap: 18px;
  border-radius: var(--r-lg); padding: 12px 18px; margin-bottom: 12px;
  background: var(--glass-1); border: 1px solid var(--border-1);
  box-shadow: var(--inset-glass);
}
.grade-letter {
  font: italic 900 44px/1 var(--font-display);
  display: inline-block; transform: skewX(var(--display-slant));
  background: var(--grad-text);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.grade-a  { border-color: color-mix(in oklab, var(--success) 45%, transparent);
            box-shadow: 0 0 44px -8px color-mix(in oklab, var(--success) 60%, transparent),
                        var(--inset-glass); }
.grade-b  { border-color: var(--border-accent);
            box-shadow: 0 0 44px -8px oklch(0.66 0.24 295 / .5), var(--inset-glass); }
.grade-c  { border-color: color-mix(in oklab, var(--warning) 45%, transparent);
            box-shadow: 0 0 44px -8px color-mix(in oklab, var(--warning) 55%, transparent),
                        var(--inset-glass); }
.grade-df { border-color: color-mix(in oklab, var(--danger) 45%, transparent);
            box-shadow: 0 0 44px -8px color-mix(in oklab, var(--danger) 60%, transparent),
                        var(--inset-glass); }

/* summary stat tiles inside the grade pane */
.grade-stats { display: flex; gap: 8px; flex: 1; min-width: 0; }
.rep-stat {
  flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px;
  padding: 9px 12px; border-radius: var(--r-md);
  background: var(--glass-1); border: 1px solid var(--border-1);
}
.rep-stat-label {
  font: var(--text-label); text-transform: uppercase;
  letter-spacing: .1em; color: var(--fg-3); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.rep-stat-value {
  font: 500 18px/1.2 var(--font-mono); font-variant-numeric: tabular-nums;
  color: var(--fg-1);
}

.report-warn { color: var(--danger); font-size: 12px; margin: 0 0 8px; }
#report-table-wrap {
  background: var(--glass-1); border: 1px solid var(--border-1);
  border-radius: var(--r-lg); box-shadow: var(--inset-glass);
  overflow: hidden;
}
#report-table th, #report-table td { padding: 6px 8px; }
/* the global th rule leaves headers browser-centered — pin every column so
   headers and cells share an edge; only the delta column is numeric-right */
#report-table th { text-align: left; }
#report-table th.num { text-align: right; }
#report-table td { vertical-align: top; white-space: normal; }
#report-table td.num { font: 500 13px var(--font-mono); }
#report-table td.num .muted { font-size: 11px; }
#report-table td .pos { min-width: 0; margin-right: 6px; }
#report-table td.num:first-child { text-align: left; }
/* long names never wrap: the name column takes what it needs and the
   flexible better-options column gives up the space */
#report-table td:nth-child(2) { white-space: nowrap; }
#report-table tr.flagged td {
  background: color-mix(in oklab, var(--danger) 8%, transparent);
}
#report-table .alt { padding: 2px 0; font-size: 13px; }
#report-table .alt .pos { min-width: 26px; padding: 0; font-size: 11px; }

/* phone (Q12, 2026-09-28). At 390 the four stat tiles shared one row with
   the grade letter and ellipsised to "PI…"/"F…" while "0.00" escaped its
   tile; and the four-column table squeezed "better options still
   available" into a four-line header whose body ran past the clipped
   wrapper. Here the tiles drop below the letter as a 2x2 grid with their
   full labels, and each pick becomes one row card: pick, name and delta on
   the first line, the verdict or better options full width beneath. The
   header row stays in the accessibility tree, visually hidden, and the
   delta carries its own glyph since its column header is gone. */
@media (max-width: 600px) {
  .grade-banner { flex-wrap: wrap; row-gap: var(--sp-3); }
  .grade-stats {
    flex-basis: 100%; display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .rep-stat-label { white-space: normal; overflow: visible; }
  #report-table, #report-table tbody { display: block; }
  #report-table thead {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
  }
  #report-table tr {
    display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
    column-gap: var(--sp-2); border-bottom: 1px solid var(--border-1);
  }
  #report-table tbody tr:last-child { border-bottom: 0; }
  #report-table td { display: block; border-bottom: 0; min-width: 0; }
  #report-table td:nth-child(2) { white-space: normal; overflow-wrap: anywhere; }
  #report-table td:nth-child(3)::before { content: "\0394\00a0"; color: var(--fg-3); }
  #report-table td:nth-child(4) { grid-column: 1 / -1; padding-top: 0; }
}

/* --- modal --- */
#modal {
  position: fixed; inset: 0; background: oklch(0.10 0.02 300 / .6);
  backdrop-filter: blur(8px); z-index: 55;
  display: flex; align-items: center; justify-content: center;
}
#modal-box {
  background: var(--glass-2); backdrop-filter: blur(var(--blur-glass));
  border: 1px solid var(--border-2); border-radius: var(--r-lg);
  box-shadow: var(--shadow-float), var(--inset-glass);
  width: min(720px, 92vw); max-height: 84vh; display: flex; flex-direction: column;
}
#modal-head { display: flex; justify-content: space-between; align-items: center;
              padding: 12px 16px; border-bottom: 1px solid var(--border-1); font-weight: 600; }
/* H3: the title shrinks and wraps, so a long one can never push the close
   button out of the box -- at 360 it sat at x 331-375, past the screen */
#modal-title { min-width: 0; overflow-wrap: anywhere; }
#modal-close { flex-shrink: 0; }
#modal-body { margin: 0; padding: 12px 16px; overflow: auto;
              font: 400 12px/1.5 var(--font-mono); white-space: pre;
              font-variant-numeric: tabular-nums; }

/* the three overlay closes are `.btn-icon` from the control system; the
   28px glass square they each used to declare here is gone */

/* --- sortable board headers --- */
#board-table th[data-key] { cursor: pointer; user-select: none; }
@media (hover: hover) {           /* H11, B21 */
  #board-table th[data-key]:hover { color: var(--fg-1); }
}

/* --- mock runner controls --- */
#mock-controls { display: flex; align-items: center; gap: 10px; }

/* ESPN live-tracking control + status chip (live drafts on espn_ leagues) */
#espn-controls { display: flex; align-items: center; gap: var(--sp-2); }
/* on a phone the chip and the "Match this player" button (Q16) do not fit
   one row beside Track ESPN; unwrapped, the row ran off the screen */
@media (max-width: 640px) {
  #espn-controls { flex-wrap: wrap; max-width: 100%; }
}
.espn-chip {
  font: 500 13px/1 var(--font-sans);
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--r-pill);
  border: 1px solid var(--border-1);
  color: var(--fg-3);
  white-space: nowrap;
}
.espn-chip.ok { color: var(--fg-2); border-color: var(--accent); }
.espn-chip.warn { color: var(--warning); border-color: var(--warning); }
.espn-chip.err { color: var(--red-bright); border-color: var(--red); }
/* the "Match this player" picker (Q16): a small form in #modal's box. The
   list is capped at 8 rows by the script, so it never needs its own
   scroller; the box itself scrolls on a short phone. */
#modal-form { padding: var(--sp-3) var(--sp-4) var(--sp-4); overflow: auto; }
.espn-match-lead { margin: 0 0 var(--sp-3); font-size: 13px; color: var(--fg-2); }
.espn-match-label {
  display: block; margin-bottom: var(--sp-1);
  font: 500 13px var(--font-sans); color: var(--fg-1);
}
#espn-match-q { width: 100%; }
.espn-match-results { margin: var(--sp-2) 0 0; padding: 0; list-style: none; }
.espn-match-results li {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-2); padding: var(--sp-1) 0;
  border-bottom: 1px solid var(--border-1); font-size: 13px;
}
.espn-match-who { min-width: 0; overflow-wrap: anywhere; }
.espn-match-note { margin: var(--sp-3) 0 0; font-size: 12px; }
/* The mock runner is the one thing the owner clicks to make the mock go, so
   it is `primary` (Owner Rule 2) rather than the green-outlined one-off it
   used to be. Running, it drops back to the neutral control: the aurora is
   the invitation to start, and once it is running the invitation is to stop.
   The state is never carried by colour alone — setRunning() also swaps the
   label between "▶ Start" and "❚❚ Pause". `button.run.running` outranks
   the (gated) `button.primary:hover` by source order at equal specificity,
   so it holds on a hovered button too; its own hover is gated (H11). */
button.run.running {
  background: var(--grad-panel);
  border-color: var(--ctl-border);
  color: var(--fg-1);
  box-shadow: var(--inset-glass);
}
@media (hover: hover) {
  button.run.running:hover { background: var(--glass-2);
                             box-shadow: var(--shadow-card), var(--inset-glass); }
}
#mock-speed-label {
  display: flex; align-items: center; gap: var(--sp-2);
  color: var(--fg-3); font-size: 13px;
}
#mock-speed { width: 110px; }
#mock-speed-val { min-width: 34px; font: var(--ctl-font-num);
                  font-variant-numeric: tabular-nums; }

/* --- clickable player names + card overlay --- */
/* dotted underline = the resting "openable" affordance (near-iconless UI) */
.pname { cursor: pointer; border-bottom: 1px dotted var(--fg-4); }
@media (hover: hover) {           /* H11, B21 */
  .pname:hover { color: var(--violet-bright);
                 border-bottom-color: var(--violet-bright);
                 text-decoration: none; }
}
#player-overlay {
  position: fixed; inset: 0; background: oklch(0.10 0.02 300 / .6);
  backdrop-filter: blur(8px); z-index: 57;
  display: flex; align-items: center; justify-content: center;
}
#player-box {
  position: relative;
  background: var(--glass-2); backdrop-filter: blur(var(--blur-glass));
  border: 1px solid var(--border-2); border-radius: var(--r-xl);
  box-shadow: var(--shadow-float), var(--inset-glass);
  width: min(720px, 94vw); max-height: 88vh; display: flex; flex-direction: column;
}
/* the hero band carries the identity; the head bar reduces to a floating × */
#player-head { position: absolute; top: 22px; right: 24px; z-index: 5;
               padding: 0; border: none; background: none; }
#player-title { display: none; }
/* no zoom: scaled type breaks every token'd size inside the card */
#player-body { padding: 0; overflow: auto; border-radius: var(--r-xl); }
/* overlay scrollers: keep the thumb clear of the boxes' deeper curves */
#player-body::-webkit-scrollbar-track { margin: var(--r-xl) 0; }
#modal-body::-webkit-scrollbar-track,
#report-body::-webkit-scrollbar-track { margin: var(--r-lg) 0; }

/* hero band: photo + identity with the team logo as a faded watermark */
.card-hero { position: relative; padding: 22px 24px 18px; overflow: hidden;
             border-bottom: 1px solid var(--border-1); }
.card-hero::before {
  content: ""; position: absolute; right: -40px; top: -60px;
  width: 260px; height: 160px; border-radius: 50%; pointer-events: none;
  background: var(--grad-aurora); filter: blur(60px); opacity: .35;
}
.card-logo {
  /* quiet watermark: text must always win — low opacity, hard left
     fade, and .card-hero-main reserves the right edge so bio lines
     never run into the logo's solid zone (readability fix 2026-08-17) */
  position: absolute; right: 34px; top: 50%; transform: translateY(-50%);
  width: 170px; opacity: .3; pointer-events: none;
  -webkit-mask-image: linear-gradient(to left, black 30%, transparent 78%);
  mask-image: linear-gradient(to left, black 30%, transparent 78%);
  filter: drop-shadow(0 0 28px oklch(0.66 0.24 295 / .45)) saturate(1.1);
}
.card-hero-main { position: relative; display: flex; gap: 16px;
                  align-items: center; padding-right: 128px; }
.card-photo-wrap {
  position: relative; flex-shrink: 0; display: block;
  width: 84px; height: 84px; border-radius: var(--r-lg);
  border: 1px solid var(--border-2); overflow: hidden;
  background: var(--grad-aurora-deep);
  box-shadow: var(--shadow-card), var(--inset-glass);
}
.card-photo-wrap::before {
  content: ""; position: absolute; left: 50%; bottom: -20%;
  width: 120%; height: 70%; transform: translateX(-50%); border-radius: 50%;
  background: var(--grad-aurora); filter: blur(18px); opacity: .55;
}
.card-photo-wrap::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  border-radius: inherit;
  box-shadow: inset 0 -18px 24px -12px oklch(0.14 0.022 300 / .8),
              inset 0 1px 0 color-mix(in oklab, white 16%, transparent);
}
.card-photo {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: top center; border: none; display: block;
  -webkit-mask-image: radial-gradient(120% 110% at 50% 45%, black 55%, transparent 98%);
  mask-image: radial-gradient(120% 110% at 50% 45%, black 55%, transparent 98%);
}
.card-initials {
  position: absolute; inset: 0; display: flex;
  align-items: center; justify-content: center;
  font: italic 900 28px var(--font-display);
  background: var(--grad-text);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.card-id { min-width: 0; }
/* B50 (g): the name wraps at the hero's own right reserve (below), like the
   bio lines under it. It was `nowrap`, so a long name ("Christopher
   Montgomery-Vandenberg III") ran out past the reserve toward the close
   button and was clipped by the hero's overflow at the far end.
   A word wider than the column breaks only when it has to (`break-word`,
   review F5; `anywhere` also shrinks the box's min-content). An inline-block
   sizes itself to its longest word, so the box is capped at the column plus
   `--sp-3`: the reserve on the right is the close button plus `--sp-5`, and
   a word may use the 12px of that gap the italic's overhang and skew do not
   need, no more. Measured at 390 on a touch phone (44px button): uncapped,
   a 62-character name's first word ("Bartholomew", 188px in a 153px column)
   ran under the button; capped at the column itself, "Christopher" (152px)
   split as "Christophe / r"; at column + 12px it stays whole and the
   longest word that has to break is one wider than about 165px. */
.card-name {
  margin: 0 -0.24em; display: inline-block; overflow-wrap: break-word;
  max-width: calc(100% + var(--sp-3));
  font: italic 900 26px/1.15 var(--font-display);
  transform: skewX(var(--display-slant)); transform-origin: left center;
  background: var(--grad-text);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  padding: 0 0.24em;   /* overhang room for the italic first/last glyphs */
}
.card-hero .card-bio { margin: 4px 0 0; font-size: 12px; }
.card-bio { margin: 0 0 10px; color: var(--fg-3); }

/* stats zone: sections in the card language */
.card-zone { padding: 16px 24px 20px; display: flex; flex-direction: column;
             gap: 10px; }
.card-section { border: 1px solid var(--border-1); background: var(--glass-1);
                border-radius: var(--r-md); padding: 10px 12px;
                box-shadow: var(--inset-glass); }
.card-section-title {
  margin: 0 0 6px; font: var(--text-label); text-transform: uppercase;
  letter-spacing: .1em; color: var(--fg-3);
}
.card-section-body { margin: 0; font-size: 12px; color: var(--fg-2); }
/* the algorithm's read: exec headline + driver bullets, above the
   projection-build fine print */
.card-read-headline { margin: 0 0 6px; font: 600 13px/1.45 var(--font-sans);
                      color: var(--fg-1); }
.card-read-bullets { margin: 0; padding-left: 16px; font-size: 12px;
                     line-height: 1.5; color: var(--fg-2); }
.card-read-bullets li { margin: 0 0 2px; }
.card-chips { display: flex; gap: 8px; margin-bottom: 2px; }
.card-chips .chip {
  flex: 1; display: flex; flex-direction: column; gap: 3px;
  background: var(--glass-1); border: 1px solid var(--border-1);
  border-radius: var(--r-md); padding: 9px 12px;
  font: 500 17px/1.2 var(--font-mono); font-variant-numeric: tabular-nums;
  color: var(--fg-1);
}
.card-chips .chip b {
  font: var(--text-label); text-transform: uppercase;
  letter-spacing: .1em; color: var(--fg-3); margin: 0;
}
.card-build { font-size: 13px; margin: 0 0 10px; color: var(--fg-2); }
/* B49: the in-season card's two lead groups (our projection, the season so
   far): a label, a row of chips, and a sentence under them that says what
   the numbers are */
.card-lead .card-section-title { margin-bottom: var(--sp-2); }
.card-lead .card-section-body { margin-top: var(--sp-2); }
/* Season History (B50 g, owner 2026-09-28: "clips out of the card on the
   right", cutting FINISH). Thirteen columns at 8px a side measured 710px
   against the card's 668px; 4px a side is 606px, so the table fits the
   720px card with room for a five-digit yardage. It sits in a `.table-wrap`
   (`fitTableWraps` gives it a sideways scroller of its own if a value ever
   does overflow, H1) and on a phone it folds (`.st-table`, H10). Spacing is
   on the token scale (A1): the 7px and 6px paddings it had were off it. */
.card-table th { position: static; background: var(--glass-2);
                 font-size: 11px; padding: var(--sp-2) var(--sp-1); }
.card-table td { font: 500 12px var(--font-mono); color: var(--fg-2);
                 padding: var(--sp-2) var(--sp-1);
                 font-variant-numeric: tabular-nums; }
.card-table td.num { white-space: nowrap; }
.card-table tbody tr:nth-child(odd) { background: oklch(0.85 0.05 295 / .03); }
/* the phone fold's toggle (B19): hidden above the phone breakpoint */
.card-table .st-toggle { display: none; }
/* the shared tap help (B20) floats at z 50, under every overlay; over the
   open card it would open out of sight, so it rides above it */
body:has(#player-overlay:not([hidden])) #help-pop { z-index: 58; }

/* --- roster panel slot cards ---
   id-qualified so these win over the generic `#my-roster div` padding rule */
#my-roster .slot-divider {
  color: var(--fg-4); font: 500 11px/14px var(--font-sans);
  text-transform: uppercase;
  letter-spacing: .1em; padding: 0; margin: 8px 0 4px;
}
#my-roster .slot-divider:first-child { margin-top: 0; }
#my-roster .slot-card {
  display: flex; align-items: center; gap: 8px;
  border: 1px dashed var(--border-1); border-radius: var(--r-sm);
  padding: 5px 9px; margin-bottom: 5px;
}
#my-roster .slot-card .slot-tag {
  min-width: 42px; text-align: left; font: 500 11px/1.5 var(--font-mono);
  padding: 1px 0;
  text-transform: uppercase; letter-spacing: .03em;
}
#my-roster .slot-card .slot-tag:not([class*="pos-"]) { color: var(--fg-4); }
#my-roster .slot-card .slot-hint { font-size: 12px; color: var(--fg-4); }
#my-roster .slot-card.filled {
  border-style: solid; border-color: var(--border-1); background: var(--glass-1);
}
#my-roster .slot-card.filled .slot-tag { font-weight: 600; }
#my-roster .slot-card.filled .slot-name {
  flex: 1; font-weight: 600; font-size: 13px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#my-roster .slot-card.filled .slot-round {
  color: var(--fg-3); font: 500 11px var(--font-mono);
  font-variant-numeric: tabular-nums;
}
#my-roster .slot-card.bn { border-color: color-mix(in oklab, white 6%, transparent); }
#my-roster .slot-card.bn .slot-tag { color: var(--fg-4); }
#my-roster .slot-card.bn .slot-hint { color: var(--fg-4); }
#my-roster .slot-card.bn.filled { border-color: var(--border-1); }
#my-roster .slot-card.just-filled { animation: slot-flash 1.2s var(--ease-drift); }
@keyframes slot-flash {
  0%   { background: color-mix(in oklab, var(--violet) 18%, transparent);
         border-color: var(--border-accent);
         box-shadow: var(--glow-violet); }
  100% { background: var(--glass-1); border-color: var(--border-1);
         box-shadow: none; }
}

/* Trades panel: advisory suggestion cards */
.trade-card { border: 1px solid rgba(255,255,255,0.12); border-radius: 6px;
              padding: 8px 10px; margin-bottom: 8px; font-size: 13px; }
.trade-head { margin-bottom: 2px; }
.trade-apply { margin-top: var(--sp-2); }
.mover { font-size: 11px; margin-left: 4px; }
.mover-up { color: var(--success); }
.mover-down { color: var(--danger); }
.newsflag { font-size: 11px; margin-left: 4px; color: var(--warning);
            font-weight: 700; }

/* --- season pages: standings / rosters / waivers / my team / matchup ---
   Cards reuse .set-card; motion comes entirely from bm-route-in,
   .bm-stagger, and the bm-interactive hover contract.

   The shared shell used to live here as `max-width: 820px; margin: 40px auto
   80px`, a copy of the settings column. It is now fluid and lives in the
   "SEASON PAGE LAYOUT" section at the end of this file, with the rest of
   these pages' layout; only the page-specific bits are left here. */
.season-stamp { font: 11px var(--font-mono); color: var(--fg-3); flex: 1; }
#wv-filters { margin-bottom: 12px; }

/* B17: the free-agent card opens on one short status line -- "$56 left ·
   Waivers run Tuesday · Open in Sleeper" -- with the "How bids and
   columns work" disclosure at its end. Closed, the disclosure sits on the
   status line's right; open, it takes its own full-width line under it
   and its summary stays on the right, so the toggle does not jump across
   the card. Prose, so the body face: B7 makes numbers mono, not
   sentences (QC 2026-09-28, Q7 P5). */
.wv-list { display: flex; flex-direction: column; gap: var(--sp-3); }
/* a flex column stretches its children: "Show All 124" came out as an
   846px bar across the card (review M3). A button is as wide as its label. */
.wv-list > #wv-more { align-self: flex-start; }
.wv-status {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-1) var(--sp-4);
}
.wv-status-line {
  margin: 0; font: var(--text-small); color: var(--fg-2);
  flex: 1 1 auto; min-width: 0;
}
.wv-dot { color: var(--fg-3); }
/* a real 24px target (D2): the bare inline link measured 16px tall */
.wv-status-line a { color: var(--violet-bright); display: inline-flex;
                    align-items: center; min-height: var(--ctl-h-sm); }
.wv-about { margin: 0 0 0 auto; }
.wv-about[open] { flex: 1 1 100%; margin: 0; }
.wv-about[open] > summary { display: flex; width: max-content;
                            margin-left: auto; }
.wv-about > .set-help { margin-top: var(--sp-2); max-width: 72ch; }
.wv-about .set-help p { margin: 0 0 var(--sp-2); }
.wv-glossary {
  display: grid; grid-template-columns: max-content minmax(0, 1fr);
  gap: var(--sp-1) var(--sp-3); margin: 0 0 var(--sp-2);
}
.wv-glossary > div { display: contents; }
.wv-glossary dt { font: var(--text-label); letter-spacing: var(--tracking-label);
                  text-transform: uppercase; color: var(--fg-3);
                  padding-top: 2px; }
.wv-glossary dd { margin: 0; }
.wv-no-upgrade { margin: 0; }
.wv-drop-note {
  font: 11px var(--font-mono); color: var(--fg-3);
}
/* the waiver player cell is two lines -- who, then the drop and the row's
   "why" -- and neither wraps: "WR52 / Deshaun Vasquez" and "drop Amari /
   Kalani" each broke across lines at 1280, so no two rows were the same
   height. A table that still does not fit scrolls sideways (B32). */
.wv-who, .wv-sub { display: flex; align-items: center; gap: var(--sp-1);
                   min-width: 0; white-space: nowrap; }
.wv-sub { gap: var(--sp-2); }
.wv-who > *, .wv-sub > * { flex-shrink: 0; }
.wv-who > .pname, .wv-sub > .wv-drop-note {
  flex-shrink: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
/* Shell 1232-1391px (a 1280-1439 window before the menu pane, B34): the
   table shares the row with the streaming rail, and the
   unwrapped player column pushed it past its card, Copy claim and all.
   Truncating the names to fit read "Caleb Kow..." on every row, so the
   cells give up half their side padding instead (--sp-2 between numbers
   is still a clear gutter in mono). */
@container season (min-width: 1232px) and (max-width: 1391px) {
  .season-table.wv-table th, .season-table.wv-table td {
    padding-left: var(--sp-1); padding-right: var(--sp-1);
  }
}
.filler-tag {
  font: 600 11px var(--font-mono); text-transform: uppercase;
  color: var(--fg-3); margin-left: 5px;
}

/* W6: the streaming card beside the waiver table — my current DEF/K
   starter vs the recommended stream, per position, with its horizon.
   B47 (owner, 2026-09-28): "Positions need to be more prominent, it's hard
   to know what I'm looking at." Each position is its own block under a
   real heading ("DEF Defense", --text-h3, the code in its position
   colour) instead of an 11px grey code in the gutter, and each horizon
   week is a labelled pair -- "Wk 4" over "7.9 vs ARI" -- not a mono run. */
.wv-streaming h3:first-child { margin-top: 0; }
.wv-stream-block {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--sp-2); padding: var(--sp-4) 0 0; margin-top: var(--sp-4);
  border-top: 1px solid var(--border-1);
}
.wv-streaming h3 + .wv-stream-block {
  border-top: none; padding-top: 0; margin-top: var(--sp-3);
}
.wv-stream-pos {
  display: flex; align-items: baseline; gap: var(--sp-2); margin: 0;
  font: var(--text-h3); color: var(--fg-1);
}
.wv-stream-pos .pos { min-width: 0; }
.wv-stream-side { display: flex; flex-direction: column; gap: var(--sp-1);
                  min-width: 0; }
.wv-stream-who {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 0 var(--sp-2); margin: 0; font: var(--text-small); color: var(--fg-1);
}
.wv-stream-tag {
  font: var(--text-label); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-3);
}
.wv-stream-gain { font-family: var(--font-mono); color: var(--success); }
.wv-hz { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5);
         margin: 0; }
.wv-hz dt {
  font: var(--text-label); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-3);
}
.wv-hz dd {
  margin: 0; font: var(--text-small); color: var(--fg-2); white-space: nowrap;
}
.wv-hz-pts { font: 500 13px var(--font-mono); color: var(--fg-1);
             font-variant-numeric: tabular-nums; }

/* tables (standings, waivers): mono numbers, my row lifted on glass */
/* B32 (owner, 2026-09-28): "I can't scroll on desktop while my mouse is
   inside the info pane, only on the edges." Every season table used to sit
   in a 70vh box of its own (overflow:auto + overscroll-behavior:contain) so
   its sticky header had a scrollport (D8), and that box caught the mouse
   wheel: with 2,600px of waiver rows in a 560px box the page only scrolled
   with the pointer outside it. The page is the one scroller now. The box is
   not a scroll container, so the sticky <th> sticks to the viewport as the
   page scrolls past the table -- D8 still holds, because its scrollport
   (the page) really scrolls.

   A table wider than its card (a narrow window, the full week grid) still
   must not push the page sideways: `fitTableWraps` (app.js) marks that box
   `.tw-wide` and it scrolls sideways only. It has no height cap, so it never
   has a vertical range and the wheel always reaches the page. Its header
   then rides with the page instead of sticking: a sideways scroller and a
   page-sticky header cannot share a box in CSS, and the wheel is what the
   owner asked for. On a phone nothing scrolls sideways at all (B19). */
.table-wrap { overflow: visible; }
.table-wrap.tw-wide { overflow-x: auto; overflow-y: hidden; }
.table-wrap:focus-visible { outline: 2px solid var(--violet-bright);
                            outline-offset: 2px; }
/* stuck to the page, a header must paint over the rows scrolling under it,
   positioned cells (.ol-cell) included; 5 is the in-component layer the
   war room's sticky grid column already uses, under the topbar (20) and
   the lockup (21).
   It sticks just under the lockup's band, not at the viewport's top
   (review M1, 2026-09-29): above 1150px the lockup is fixed over the
   page's top-left 290x108, and a header stuck at y = 0 slid under it for
   the whole scroll -- "RANK PLAYER" under the logo, the rank help and the
   standings' sort buttons unreachable. Rows still pass through that strip
   as they scroll, like every other page's content. Where the lockup
   scrolls away with the page (1150px and below) the header sticks at the
   top again. The lockup itself is W2-HOME's and is not moved here. */
.table-wrap > .season-table th {
  z-index: 5; top: var(--season-sticky-top);
}
/* drawer mode: the compact bar is fixed over the top of the page once the
   title has scrolled away, so a stuck header sticks under it (B34) */
@media (max-width: 1199px) {
  :root { --season-sticky-top: var(--nav-bar-h); }
}
.season-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.season-table th {
  font: 11px var(--font-mono); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-3); text-align: left;
  padding: 4px 8px; border-bottom: 1px solid var(--border-1);
}
.season-table td { padding: 5px 8px; border-bottom: 1px solid var(--border-1); }
/* B34: beside the menu pane the content column is 300px narrower than the
   window, and the two widest tables (Standings' eighteen columns, Outlook's
   week grid) no longer fit it at 1280-1440. Under a 1232px shell the cells
   give up half their side padding -- --sp-2 between mono numbers is still a
   clear gutter, and the waivers table already does this a band up -- which
   is what lets Outlook fit at 1280 and Standings at 1440. A phone (which
   folds its rows, H10) keeps its own padding. */
@container season (max-width: 1231px) {
  @media (min-width: 601px) and (not ((pointer: coarse) and (max-height: 500px))) {
    .season-table th, .season-table td {
      padding-left: var(--sp-1); padding-right: var(--sp-1);
    }
  }
}
.season-table th.num, .season-table td.num { text-align: right; }
/* a number never breaks: "13-20" wrapped at its hyphen read as two values
   ("13-" over "20") in the standings at 1280. A table that no longer fits
   its card scrolls sideways instead (.tw-wide, above). */
.season-table td.num { white-space: nowrap; }
.season-table tr.me-row td {
  background: var(--glass-2);
  border-top: 1px solid var(--border-accent);
  border-bottom: 1px solid var(--border-accent);
}
/* A team's identity: avatar, then username and fantasy team name. The
   flex box is a div INSIDE the cell -- `display:flex` on the <td> itself
   took it out of the table model, which is why the me-row's accent border
   drew short of the team cell. On a phone the two names stack as two
   single lines (the PHONE LAYOUT section at the end of this file). */
/* The two names stack as two single lines at every width, each ellipsised
   with its full text in `title` (B9): side by side, a long team name wrapped
   two or three lines deep at 390 AND at 1280 ("Brad Mitzvah & / ChosenGuns"),
   and every row came out a different height. The cap is what makes the
   ellipsis bite inside an auto-width table column. */
.s-id { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.s-names {
  display: flex; flex-direction: column; min-width: 0; max-width: 9rem;
}
/* Shell 1232-1391px (a 1280-1439 window before the menu pane, B34): the
   standings table's nineteen columns (the rail moves under it below a
   1600px shell) leave the names ~110px before the table scrolls */
@container season (min-width: 1232px) and (max-width: 1391px) {
  .s-names { max-width: 6.75rem; }
}
/* and the same below it, beside the menu pane at 1200-1400 (B34) */
@container season (max-width: 1231px) {
  @media (min-width: 601px) and (not ((pointer: coarse) and (max-height: 500px))) {
    .s-names { max-width: 6.75rem; }
  }
}
.s-names > * {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.s-label { color: var(--fg-1); }
.s-sub { color: var(--fg-3); font-size: 11px; }
/* the phone-only fold toggle (B19); above 600px every column shows, so
   the toggle and the waiver detail row's copy of the numbers are hidden */
.season-table .st-toggle, .wv-more, .tc-toggle { display: none; }

/* owner avatars, with an initials fallback when none is set */
.savatar {
  width: 26px; height: 26px; border-radius: 50%; object-fit: cover;
  flex-shrink: 0; border: 1px solid var(--border-1);
}
.savatar-fb {
  display: inline-flex; align-items: center; justify-content: center;
  font: 700 11px var(--font-mono); color: var(--fg-2);
  background: var(--glass-1);
}

/* roster cards (Rosters page grid; My roster reuses the row parts) */
/* auto-fill against a 300px floor: two cards inside the old 820px column,
   four across the fluid shell at 1440+. H3: the track is minmax(0, ...) at
   its shrinking end and every card carries min-width:0, so a long team name
   cannot push the grid wider than its container. */
.team-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--sp-4);
}
.team-card { min-width: 0; }
.team-card h2 { display: flex; align-items: center; gap: 8px; }
/* any card heading that leads with an avatar keeps it off the text: My
   roster's "J JLKNICKREHM" had the chip touching the name */
.set-card h2:has(> .savatar) {
  display: flex; align-items: center; gap: var(--sp-2);
}
.team-card.me-card { border-color: var(--border-accent); }
.tslot {
  display: flex; align-items: center; gap: 8px; padding: 3px 0;
  font-size: 13px; border-bottom: 1px solid
    oklch(0.85 0.05 295 / .05);
}
.tslot-slot {
  font: 700 11px var(--font-mono); letter-spacing: var(--tracking-label);
  color: var(--fg-3); width: 52px; flex-shrink: 0; text-transform: uppercase;
}
.tslot-slot.bn { color: var(--fg-3); }
/* `overflow:hidden; white-space:nowrap` with no `text-overflow` hard-clips
   mid-word: at 390 "Cooper Santiago" rendered as "Coop" in the row that says
   who to bench. A truncation that invents a different plausible player is a
   correctness bug, not a cosmetic one -- so it ellipsises, and the renderer
   carries the full name in `title`. */
.tslot-main { flex: 1; min-width: 0; display: flex; align-items: center;
              gap: 5px; overflow: hidden; white-space: nowrap; }
.tslot-main > * { min-width: 0; overflow: hidden; text-overflow: ellipsis;
                  white-space: nowrap; }
.tslot-proj { font-variant-numeric: tabular-nums; color: var(--fg-2);
              flex-shrink: 0; }
/* a position badge is two to five characters that must never ellipsise:
   "WR..." is not a position (M2, 390) */
.tslot-main > .pos { flex-shrink: 0; min-width: auto; overflow: visible; }
/* B51: the lineup verdict and the swaps that close the gap, at the top of
   the My Team card. Each change is a sentence that WRAPS ("Start Puka
   Nacua over Isaiah Williams at FLEX"), never an ellipsised row: in the
   old third-of-a-card column both names truncated at 1280 and could not
   fit at 390 (B9). The gain keeps its own column on the right, tinted
   with the same success wash the bench row it brings in carries. */
.mt-verdict { margin: 0 0 var(--sp-2); font-size: 13px; color: var(--fg-1); }
.mt-verdict b { font-variant-numeric: tabular-nums; }
/* a verdict with no swap list under it ("set", "locked") still needs air
   before the starters list */
.mt-verdict + .mt-lineup { margin-top: var(--sp-4); }
.mt-swaps {
  list-style: none; margin: 0 0 var(--sp-4); padding: 0;
  display: grid; gap: var(--sp-1);
}
.mt-swap-line {
  display: flex; align-items: baseline; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-sm);
  background: color-mix(in oklab, var(--success) 8%, transparent);
  font-size: 13px; color: var(--fg-1);
}
.mt-swap-text { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
/* a spot the bench cannot fill (review F2): the warning wash, not the
   success one — there is no change to make here, only a pickup to find */
.mt-holes .mt-swap-line {
  background: color-mix(in oklab, var(--warn) 8%, transparent);
}
.mt-swap-gain { flex-shrink: 0; font-weight: 600;
                font-variant-numeric: tabular-nums; }
.mt-swap-p { flex-shrink: 0; font-variant-numeric: tabular-nums; }
.tslot-divider {
  font: 11px var(--font-mono); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-4); margin: 8px 0 2px;
}
.bye-flag { font: 700 11px var(--font-mono); color: var(--warn);
            margin-left: 4px; text-transform: uppercase; }
.lock-flag { font: 700 11px var(--font-mono); color: var(--fg-3);
             margin-left: 4px; text-transform: uppercase; }
.lock-flag.lock-today { color: var(--warn); }
.unavail-flag { font: 700 11px var(--font-mono); color: var(--fg-3);
                margin-left: 4px; text-transform: uppercase; }
/* Scoped to the season pages. Unscoped this was a second `.need-chip` at
   equal specificity to the war room's (style.css:864), so being later it won
   everywhere -- overriding `.pos-*` (:780-782) and painting every position
   chip --violet-bright, which is WR's colour. Position colour is a brand
   non-negotiable and load-bearing: the signal was not missing, it was wrong.
   The 9px size also undercut the 11px floor. */
#standings .need-chip, #rosters .need-chip, #waivers .need-chip,
#my-team .need-chip, #matchup .need-chip, #activity .need-chip,
#outlook .need-chip {
  font: 700 11px var(--font-mono); text-transform: uppercase;
  border: 1px solid var(--border-accent);
  border-radius: var(--r-pill); padding: 1px 6px;
}
/* only chips that carry no position class take the flat accent colour */
#standings .need-chip:not([class*="pos-"]),
#rosters .need-chip:not([class*="pos-"]),
#waivers .need-chip:not([class*="pos-"]),
#my-team .need-chip:not([class*="pos-"]),
#matchup .need-chip:not([class*="pos-"]),
#activity .need-chip:not([class*="pos-"]),
#outlook .need-chip:not([class*="pos-"]) { color: var(--violet-bright); }

/* W8: the watchlist star toggle beside a name on the waivers table and
   other teams' Rosters cards — subtle, beside the row's own action, never
   the page's main one. */
.watch-star { flex-shrink: 0; }
.watch-star[data-watched="true"] { color: var(--warn); }
.watch-star[data-watched="false"] { color: var(--fg-4); }

/* Lineup and Gamecast: the team head (B57: the team name leads, the
   Sleeper username under it when it differs) and the Lineup's two totals
   under the owner's names for them (B51 e). The head is not the 11px
   label every other card h2 is: a team name is who this card is about.
   Card h2s elsewhere keep the label style. */
.lu-head {
  display: flex; align-items: center; gap: var(--sp-3); min-width: 0;
  margin: 0 0 var(--sp-3);
}
.lu-id { display: flex; flex-direction: column; min-width: 0; }
/* a team name is a heading, so it wraps rather than ellipsises */
.lu-team {
  margin: 0; font: var(--text-h3); color: var(--fg-1);
  text-transform: none; letter-spacing: normal; overflow-wrap: anywhere;
}
.lu-meta { font: var(--text-small); color: var(--fg-3);
            overflow-wrap: anywhere; }
/* the two totals side by side at every width, so they compare at a
   glance: on a phone the columns share the card and the longer label
   wraps, and align-items: end keeps both numbers on one line */
.lu-totals {
  display: grid; grid-template-columns: repeat(2, max-content) minmax(0, 1fr);
  align-items: end; gap: var(--sp-2) var(--sp-8); margin: 0 0 var(--sp-4);
}
.lu-total { display: flex; flex-direction: column; gap: var(--sp-1);
            min-width: 0; }
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  .lu-totals { grid-template-columns: 1fr 1fr; column-gap: var(--sp-4); }
  .lu-basis { grid-column: 1 / -1; }
}
.lu-total-label {
  font: var(--text-label); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-3);
}
.lu-total-num { font: var(--text-h2); color: var(--fg-1);
                font-variant-numeric: tabular-nums; }
.lu-basis { font: var(--text-small); color: var(--fg-3); }
.lineup-note { margin: 0 0 var(--sp-2); font-size: 13px; }

/* L5: alerts strip atop My Team — starter status, empty slot, and
   bye-starter certainties; each row links out to the Sleeper lineup. */
.alerts-strip { display: flex; flex-direction: column; gap: var(--sp-2); }
.alert-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  padding: 8px 10px; border-radius: var(--r-md); text-decoration: none;
  color: inherit; border: 1px solid var(--border-1); background: var(--glass-1);
}
@media (hover: hover) {           /* H11, B21 */
  .alert-row:hover { border-color: var(--border-2); }
}
.alert-row.alert-high { border-left: 3px solid var(--danger); }
.alert-row.alert-medium { border-left: 3px solid var(--warn); }
.alert-who { flex-shrink: 0; }
.alert-detail { color: var(--fg-2); font-size: 13px; }
.alert-action { margin-left: auto; font-size: 11px; color: var(--fg-3);
                white-space: nowrap; }

/* My matchup: the scoreboard */
.vs-cols { display: grid; grid-template-columns: 1fr auto 1fr; gap: 14px; }
.vs-div {
  align-self: center; font: italic 900 16px var(--font-display);
  color: var(--fg-4); text-transform: uppercase;
}
.vs-side .lu-head { margin-bottom: var(--sp-1); }
.vs-total { font: italic 900 40px/1.1 var(--font-display);
            color: var(--fg-1); }
.vs-mine .vs-total {
  background: var(--grad-text); -webkit-background-clip: text;
  background-clip: text; color: transparent; padding: 0 0.1em;
}
.vs-proj { font-size: 11px; margin-bottom: 8px; }
@media (max-width: 640px) {
  .vs-cols { grid-template-columns: 1fr; }
  .vs-div { justify-self: center; }
}

/* --- Outlook: rows my roster, columns the remaining weeks (L4/R7) ----
   B29 (owner, 2026-09-28, desktop ~1800px): "Lines need to line up. Info
   is presented very small. Two panes are very close together."
   (a) Every cell is a real table cell on one shared baseline, so the row
       rules run straight across and a name sits level with its numbers
       (the name cell used to be `display:flex`, out of the table model).
   (b) Numbers are --text-data and the slot under each one --text-label:
       the data/label pair the standings rail uses, not 11px bold mono.
   (c) The holes card and the grid sit a --sp-6 apart (A4).
   (d) The table is as wide as its content, not the card: at width:100%
       the player column took every spare pixel and pushed the weeks to
       the far edge. A grid wider than the card scrolls sideways (B32).
   Muted cells (bye, IR, out) are --fg-3, not --fg-4: a number the owner
   reads is text, and --fg-4 is a decoration token (C1). */
#outlook-body { display: flex; flex-direction: column; gap: var(--sp-6); }
.ol-table { width: auto; }
.ol-table td { vertical-align: baseline; }
.ol-table th {
  font: var(--text-label); letter-spacing: var(--tracking-label);
  white-space: nowrap;
}
.ol-table th.ol-playoff, .ol-table td.ol-playoff {
  background: color-mix(in oklab, var(--violet) 10%, transparent);
}
.ol-who { display: flex; align-items: baseline; gap: var(--sp-2); }
/* one line: a grid too wide for the card scrolls sideways anyway, and a
   name wrapped two deep made that row taller than its neighbours */
.ol-pname { font: 500 15px/1.3 var(--font-sans); color: var(--fg-1);
            white-space: nowrap; }
.ol-who .pos { min-width: 0; }
/* under the name line, each note on a line of its own, then the IR
   estimate control -- the flex cell used to space these apart */
.ol-name > .s-sub { display: block; margin-top: var(--sp-1); }
.ol-name > .ol-ir-set { display: flex; margin-top: var(--sp-1); }
/* L12/L8: the parsed or manager-entered return note, and the inline
   set/clear control for the manager's own estimate */
.ol-return { color: var(--fg-3); }
.ol-ir-set { display: inline-flex; align-items: center; gap: 4px; }
.ol-ir-week { width: 64px; padding: 0 var(--sp-2); }
/* `.ol-table td.ol-cell`, not `.ol-cell`: the war room's global `td.num`
   (500 12px mono) outranks a lone class, and held these at 12px. No
   colour here -- `td.num` gives --fg-2, and the state rules below
   (two classes) must still outrank that. */
.ol-table td.ol-cell {
  position: relative; min-width: 48px; white-space: nowrap;
  font: var(--text-data); font-variant-numeric: tabular-nums;
}
/* B34: at exactly 1200px the 15-week grid needed 43px more than the column
   beside the menu pane has (853px against 810). Under a 1091px shell
   (1200-1438, and the drawer-mode windows below ~1140) a week cell gives up
   half of its remaining side padding, 4px to 2px: thirteen cells at 4px
   fewer each is the 52px that fits the grid at 1200 with no sideways
   scroll. A phone lays its weeks out five to a line (below) and keeps its
   own sizes. */
@container season (max-width: 1091px) {
  @media (min-width: 601px) and (not ((pointer: coarse) and (max-height: 500px))) {
    #outlook .ol-table td.ol-cell { padding-left: 2px; padding-right: 2px; }
  }
}
.ol-slot {
  display: block; font: var(--text-label); text-transform: uppercase;
  color: var(--fg-3); letter-spacing: var(--tracking-label);
}
/* row 10b (F73 S9): the opponent on a line of its own under the slot, in
   the label face and no wider than the labels above it, so a cell does not
   widen and B34's 1200px fit holds (the row grows a line instead). Not
   upper-cased: "vs" and "@" are not labels. The market's posted line, in
   the decision week's cell only, is the label "Market" over its number,
   two short lines: on one line it is 77px, wider than a week's cell. */
.ol-opp, .ol-mkt {
  display: block; font: var(--text-label); color: var(--fg-3);
  letter-spacing: 0;   /* tracked, "vs WAS" is 2px wider than "BENCH" and
                          cost the 1200px fit (measured 2026-10-16) */
}
.ol-mkt { color: var(--fg-2); margin-top: var(--sp-1); }
.ol-mkt > span { display: block; }
.ol-note { margin: 0 0 var(--sp-3); }
.ol-cell.ol-start { color: var(--fg-1); }
.ol-cell.ol-start .ol-slot { color: var(--violet-bright); }
.ol-cell.ol-bye { color: var(--fg-3); }
.ol-cell.ol-bye .ol-slot { color: var(--warn); }
.ol-cell.ol-ir { color: var(--fg-3); }
.ol-cell.ol-ir .ol-slot { color: var(--fg-3); }
/* ruled out by a live injury status (review M2): the 0.0 says why */
.ol-cell.ol-out { color: var(--fg-3); }
.ol-cell.ol-out .ol-slot { color: var(--danger); }

/* the copy-ready hole lines above the grid, each pointing at the waiver
   page — always a plan, never just a flagged problem */
.ol-holes { display: flex; flex-direction: column; gap: var(--sp-3); }
.ol-hole {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--sp-2) var(--sp-4); font: var(--text-small); color: var(--fg-2);
}
.ol-hole-actions { display: flex; gap: var(--sp-2); flex-shrink: 0; }

/* B30: which weeks the problem-weeks view hides, and why, in one row
   with the toggle that shows them (it wraps under the note on a phone) */
.ol-view { justify-content: space-between; margin-bottom: var(--sp-3); }
.ol-view-note { margin: 0; flex: 1 1 280px; }
@media (max-width: 640px) {
  .ol-hole { flex-direction: column; align-items: flex-start; }
}

/* settings: the projection harness report, verbatim */
.set-report { white-space: pre-wrap; overflow-wrap: anywhere; font: 12px var(--font-mono); max-height: 60vh; overflow: auto; margin: var(--sp-2) 0 var(--sp-4); }
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  .set-report { max-height: none; overflow: visible; }   /* H10 */
}

/* ===== trades: one builder screen (B58) =====================================
   A team strip, two full rosters of tappable rows (You Give, You Get) and a
   third column that holds the suggested deals and the live verdict. It takes
   the fluid season shell like every season page, and follows ITS width, not
   the window's: three columns while the shell is 900px or more, two rosters
   with the trade under them below that, and on a phone the H10 form (a
   You Give / You Get switch, one roster at a time, the verdict in a bar
   pinned to the bottom).

   Selection rows are a documented D1 exception, like the rail rows before
   them: .tf-team, .tf-row and .tf-deal choose what to price, they do not
   run anything, so they take the quiet aurora when picked (the menu's
   current-row recipe: the gradient, no glow) instead of `primary`. The one
   primary on the screen is Copy Offer.
   ========================================================================== */
#trades { --tf-row-h: 42px; }

/* the deadline sits small beside the title, and wraps under it when narrow */
#trades .season-head > #trades-deadline {
  flex: 1 1 auto; margin: 0; font: var(--text-small); color: var(--fg-3);
}

.tf-page { display: grid; gap: var(--sp-4); }
.tf-label {
  margin: 0 0 var(--sp-2);
  font: var(--text-label); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-3);
}
.tf-k { color: var(--fg-3); font: var(--text-label);
        letter-spacing: var(--tracking-label); text-transform: uppercase; }
/* every number is Geist Mono (the system's strongest rule); the small ones
   are the 11px label size in mono */
.tf-n, .tf-count, .tf-meta, .tf-deal-nums, .tf-wd {
  font: var(--text-label); font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.tf-ppg { font: var(--ctl-font-num); font-variant-numeric: tabular-nums; }
.tf-side-num, .tf-num {
  font: var(--text-data); font-variant-numeric: tabular-nums;
}

/* --- the team strip (T2, T3) --------------------------------------------- */
.tf-strip { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.tf-team {
  max-width: 100%; border-radius: var(--r-pill);
  white-space: nowrap;
}
.tf-team-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.tf-count { color: var(--fg-3); }
/* the "Trading With" box is the phone's; the desktop shows the strip */
.tf-with { display: none; }

/* the chosen team, the picked rows and the loaded deal: quiet aurora, no
   glow (glow is the one focal element's), and beaten by nothing hover does */
.tf-team.tf-on, .tf-row.tf-on, .tf-deal.tf-on {
  background: var(--aurora-fill); background-clip: padding-box;
  border-color: transparent; color: var(--text-on-accent);
  box-shadow: none;
}
.tf-on .tf-count, .tf-on .tf-meta, .tf-on .tf-k { color: var(--text-on-accent); }
.tf-on .pos { color: inherit; }
@media (hover: hover) {           /* H11, B21 */
  .tf-team.tf-on:hover, .tf-row.tf-on:hover, .tf-deal.tf-on:hover {
    background: var(--aurora-fill); background-clip: padding-box;
    box-shadow: none;
  }
}

.tf-needs { margin: 0; font: var(--text-small); color: var(--fg-2); }
/* "+N more" is a real button (keyboard and screen reader) dressed as text */
.tf-more {
  display: inline; min-height: var(--ctl-h-sm); padding: 0;
  background: none; border: 0; box-shadow: none; border-radius: var(--r-sm);
  color: var(--violet-bright); font: inherit; cursor: help;
  text-decoration: underline dotted;
}
@media (hover: hover) {           /* H11, B21 */
  .tf-more:hover { background: none; box-shadow: none; color: var(--link-hover); }
}

/* --- the builder: give | get | the trade ---------------------------------- */
.tf-builder {
  display: grid; gap: var(--sp-6); align-items: start;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.tf-builder > * { min-width: 0; }
.tf-switch { display: none; }
.tf-col h2, .tf-deals-card h2, .tf-verdict-card h2 {
  margin: 0 0 var(--sp-3); display: flex; flex-wrap: wrap;
  align-items: baseline; column-gap: var(--sp-2);
}
.tf-of { font: var(--text-small); font-style: normal; font-weight: 400;
         color: var(--fg-3); min-width: 0; overflow: hidden;
         text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }

/* every player shows at once, best first inside each position; there is no
   scroll box inside the card (H10 on a phone, and the page scrolls on a
   desktop) */
.tf-group + .tf-group {
  margin-top: var(--sp-2); padding-top: var(--sp-2);
  border-top: 1px solid var(--border-1);
}
.tf-row {
  display: grid; width: 100%; min-height: var(--tf-row-h);
  grid-template-columns: var(--sp-4) 2.25rem minmax(0, 1fr) auto;
  align-items: center; column-gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2);
  background: none; border: 1px solid transparent; box-shadow: none;
  border-radius: var(--r-md); text-align: left; white-space: normal;
  font: var(--text-small); color: var(--fg-1);
}
@media (hover: hover) {
  .tf-row:not(.tf-on):hover { background: var(--glass-1); box-shadow: none; }
}
.tf-mark { font: var(--ctl-font-num); text-align: center; }
.tf-row .pos { min-width: 0; font: var(--text-label); }
.tf-who { display: grid; min-width: 0; }
.tf-name-line { display: flex; align-items: center; gap: var(--sp-1);
                min-width: 0; }
.tf-name { min-width: 0; overflow: hidden; text-overflow: ellipsis;
           white-space: nowrap; font-weight: 600; }
.tf-meta-line { display: flex; align-items: center; gap: var(--sp-2);
                min-width: 0; min-height: var(--sp-4); }
.tf-meta { color: var(--fg-3); overflow: hidden; text-overflow: ellipsis;
           white-space: nowrap; }
.tf-ppg { color: var(--fg-1); }
.tf-tag {
  flex-shrink: 0; padding: 0 var(--sp-1); border-radius: var(--r-sm);
  border: 1px solid var(--border-2); color: var(--fg-2);
  font: var(--text-label); text-transform: none; letter-spacing: 0;
}
.tf-on .tf-tag { border-color: var(--text-on-accent); color: var(--text-on-accent); }

/* --- the third column: sticky, unblurred, two data cards ------------------
   It is inline, so it takes the data-surface wash (2b), never glass. When a
   trade is picked the suggested deals fold up to keep it inside the screen. */
.tf-trade {
  display: grid; gap: var(--sp-4); align-content: start;
  position: sticky; top: calc(var(--season-sticky-top) + var(--sp-6));
}
.tf-deal-list { display: grid; gap: var(--sp-2); margin-top: var(--sp-2); }
/* Queue row 46: a reopened list scrolls its summary into view (`nearest`),
   and this margin keeps the summary's 2px ring, offset 2px, off the window's
   top edge. It is the sticky column's own offset above, so under the compact
   bar (drawer mode, below 1200px: --season-sticky-top is the bar's 56px) the
   summary lands below the bar, not behind it (row 46 review, S1). */
.tf-deals > summary {
  scroll-margin-top: calc(var(--season-sticky-top) + var(--sp-6));
}
/* Under a capped list (queue row 32): the finder sends up to five deals and
   three show, so the column stays shorter than the rosters and has room to
   stick. A neutral button (D20: it takes the control system's glass and adds
   only a width and a gap, like the war room's "Show All N"), never primary --
   Copy Offer is the screen's one -- and a count, not a height: nothing in
   the builder scrolls in a box (H10). */
.tf-deals-more { width: 100%; margin-top: var(--sp-2); }
.tf-deal {
  display: grid; gap: var(--sp-1); justify-items: start; width: 100%;
  padding: var(--sp-2) var(--sp-3); text-align: left; white-space: normal;
  font: var(--text-small);
}
.tf-deal-line { min-width: 0; overflow-wrap: anywhere; }
.tf-deal-nums { display: flex; flex-wrap: wrap; align-items: center;
                gap: var(--sp-2); color: var(--fg-3); }
.tf-on .tf-deal-nums { color: var(--text-on-accent); }

/* --- the verdict ----------------------------------------------------------- */
.tf-offer p { margin: 0 0 var(--sp-1); font: var(--text-small); }
.tf-offer .tf-k { display: inline-block; min-width: 2.5rem; }
.tf-headline { margin: var(--sp-3) 0 var(--sp-2);
               font: var(--text-h3); color: var(--fg-1); }
.tf-side {
  display: flex; align-items: baseline; gap: var(--sp-2);
  padding: var(--sp-1) 0; font: var(--text-small);
}
.tf-side-word { flex: 1 1 auto; min-width: 0; font-weight: 600; }
.tf-glyph { font: var(--ctl-font-num); }
.tf-good .tf-glyph, .tf-good .tf-side-num { color: var(--success); }
.tf-bad .tf-glyph, .tf-bad .tf-side-num { color: var(--red-bright); }
.tf-even .tf-glyph, .tf-even .tf-side-num { color: var(--fg-2); }
.tf-odds, .tf-line { margin: var(--sp-2) 0 0; font: var(--text-small); }
.tf-odds .tf-num { margin-left: var(--sp-2); }
.tf-est { margin-left: var(--sp-2); font: var(--text-label); color: var(--fg-3);
          text-transform: uppercase; letter-spacing: var(--tracking-label); }
.tf-caveat { color: var(--fg-2); }
.tf-empty { margin: 0; }

.tf-faab { margin: var(--sp-3) 0 0; }
/* Add FAAB (owner 2026-10-18): a neutral button (D20), never primary --
   Copy Offer is the card's one -- and never subtle, which paints no box and
   read as text, the fold's own fault. A tap shows the fields and hides it. */
.tf-faab-add { margin: 0; }
/* the split under a side's number when dollars count in it */
.tf-split { margin: 0 0 var(--sp-1); color: var(--fg-2); }
.tf-faab-row { display: flex; flex-wrap: wrap; gap: 0 var(--sp-4);
               margin-top: var(--sp-2); }
.tf-faab-row .set-field { flex: 1 1 8rem; min-width: 0; }
.tf-faab input[type="number"] { width: 100%; }
.tf-holds { display: block; margin-top: var(--sp-1); }
.tf-actions { display: flex; flex-wrap: wrap; align-items: center;
              gap: var(--sp-2); margin-top: var(--sp-4); }
.tf-details { margin-top: var(--sp-3); }
.tf-details .tf-line:first-of-type { margin-top: var(--sp-1); }
/* the weeks wrap into rows of small cells; nothing scrolls sideways */
.tf-weeks { display: grid; gap: var(--sp-1);
            grid-template-columns: repeat(auto-fill, minmax(3.25rem, 1fr));
            margin-top: var(--sp-1); }
.tf-week { display: grid; justify-items: center; padding: var(--sp-1);
           border: 1px solid var(--border-1); border-radius: var(--r-sm); }
.tf-wk { font: var(--text-label); color: var(--fg-3); }
/* a quiet text control, with a real 24px target (D2) and the house ring */
.tf-how {
  margin-top: var(--sp-3); min-height: var(--ctl-h-sm); padding: 0;
  background: none; border: 0; box-shadow: none; color: var(--violet-bright);
  font: var(--ctl-font-sm); text-decoration: underline dotted;
}
@media (hover: hover) {
  .tf-how:hover { background: none; box-shadow: none; color: var(--link-hover); }
}

/* --- the shell narrows: two rosters, the trade under them ------------------ */
@container season (max-width: 899px) {
  .tf-builder { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tf-trade {
    grid-column: 1 / -1; position: static;
    grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start;
  }
}

/* --- the phone (H10; landscape phones too) ---------------------------------
   The team list folds behind a "Trading With" box, one roster shows at a
   time behind a You Give / You Get switch, and the verdict rides in a bar
   pinned to the bottom. Rows are the touch height and the page is the only
   thing that scrolls. */
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  .tf-with {
    display: flex; width: 100%; justify-content: space-between;
    min-height: calc(var(--ctl-h) + var(--sp-4)); padding: var(--sp-2) var(--sp-3);
    text-align: left;
  }
  .tf-with-label { display: block; flex: 0 0 100%; font: var(--text-label);
                   letter-spacing: var(--tracking-label);
                   text-transform: uppercase; color: var(--fg-3); }
  .tf-with { flex-wrap: wrap; row-gap: var(--sp-1); }
  .tf-with-value { display: flex; align-items: center; gap: var(--sp-2);
                   min-width: 0; flex: 1 1 auto; font-weight: 600; }
  .tf-chev { color: var(--fg-3); }
  .tf-teams > .tf-label { display: none; }
  .tf-strip { display: none; flex-direction: column; gap: var(--sp-1);
              margin-top: var(--sp-2); }
  .tf-strip.tf-open { display: flex; }
  .tf-team { width: 100%; justify-content: space-between; border-radius: var(--r-md);
             min-height: var(--tf-row-h); }
  .tf-builder { display: flex; flex-direction: column; align-items: stretch;
                gap: var(--sp-4); }
  .tf-trade { display: contents; }
  .tf-deals-card { order: 1; }
  .tf-switch { display: inline-flex; order: 2; align-self: stretch; }
  .tf-switch button { flex: 1 1 50%; min-height: var(--ctl-h); }
  .tf-col { order: 3; }
  .tf-verdict-card { order: 4; }
  .tf-builder[data-side="give"] .tf-get,
  .tf-builder[data-side="get"] .tf-give { display: none; }
  .tf-col h2 { display: none; }
  #trades.tf-has-bar { padding-bottom: calc(var(--sp-16) + var(--sp-8)); }
  /* shown on the Trades screen alone (see #tf-bar below) */
  body:has(#trades:not([hidden])) #tf-bar:not([hidden]) { display: flex; }
  /* the toast stacks above the bar while it is up */
  body:has(#trades:not([hidden])):has(#tf-bar:not([hidden])) #toast {
    bottom: calc(max(var(--sp-4), env(safe-area-inset-bottom)) + 6rem);
  }
}
/* B1: between a phone and the three-column desktop the shell is under 900px,
   the third column drops under the rosters as a plain block, and the verdict
   would sit screens below the rows being tapped. `body.tf-stacked` (set from
   the column's own computed position, `tfSyncStacked`) gives that layout the
   phone's pinned bar and its room at the foot of the page. */
body.tf-stacked #trades.tf-has-bar { padding-bottom: calc(var(--sp-16) + var(--sp-8)); }
body.tf-stacked:has(#trades:not([hidden])) #tf-bar:not([hidden]) { display: flex; }
body.tf-stacked:has(#trades:not([hidden])):has(#tf-bar:not([hidden])) #toast {
  bottom: calc(max(var(--sp-4), env(safe-area-inset-bottom)) + 6rem);
}
/* beside the menu pane (1200px and up) the bar sits over the content column,
   not over the pane */
@media (min-width: 1200px) {
  body.tf-stacked #tf-bar { left: calc(var(--nav-w) + var(--sp-4)); }
}
.tf-bar-text { flex: 1 1 12rem; min-width: 0; font: var(--text-small); }
/* the phone's verdict bar: the war room's confirm-bar recipe, glass with
   the modal blur and the focal glow, because the verdict is this screen's
   one focal element. It floats over content (2b), so it may blur. Hidden
   everywhere but a phone on the Trades screen (rule in the phone block). */
#tf-bar {
  display: none; position: fixed; z-index: 50; align-items: center;
  gap: var(--sp-3); flex-wrap: wrap;
  left: var(--sp-4); right: var(--sp-4);
  bottom: max(var(--sp-4), env(safe-area-inset-bottom));
  background: var(--glass-2); backdrop-filter: blur(var(--blur-glass));
  border: 1px solid var(--border-accent); border-radius: var(--r-xl);
  padding: var(--sp-3) var(--sp-4);
  box-shadow: var(--shadow-float), var(--glow-violet), var(--inset-glass);
}
/* touch: the rows and the team buttons are 48px, fields are 16px so iOS
   does not zoom in (the control system's touch block does the fields) */
@media (pointer: coarse) {
  #trades { --tf-row-h: 48px; }
}
@media (prefers-reduced-motion: reduce) {
  .tf-team, .tf-row, .tf-deal { transition: none; }
}

/* the link that actually sends the proposal, so it has to read as a
   control rather than as stripped-underline text: the same glass chrome
   the bare `button` element carries (owner rule 2). It sits beside the
   card's two primaries, so it takes the secondary look, not the aurora. */
.tf-deep-link {
  display: inline-flex; align-items: center; justify-content: center;
  margin-top: var(--sp-3); min-height: var(--ctl-h);
  padding: 0 var(--ctl-pad-x);
  background: var(--grad-panel);
  color: var(--fg-1);
  border: 1px solid var(--ctl-border);
  border-radius: var(--ctl-radius);
  box-shadow: var(--inset-glass);
  font: var(--ctl-font);
  text-decoration: none;
}
@media (hover: hover) {           /* H11, B21 */
  .tf-deep-link:hover { background: var(--glass-2); }
}

/* --- brand lockup clearance (guidelines E3/E4) -----------------------------
   These screens are centred fixed-width columns. They used to clear the fixed
   lockup only by accident of viewport width -- the comment at the season
   shell claimed 820px "keeps the h1 clear of the fixed brand lockup", but
   that only holds above 1376px, so on any 1280 or 1366 laptop the title ran
   under the wordmark, and at 390 it was illegible underneath it. It was
   invisible at 1440, which is how it survived.

   A centred column cannot be made to clear a fixed corner at every width, so
   below its own threshold each column clears the lockup VERTICALLY instead.
   Above it, nothing changes and the title still sits beside the wordmark.
   Thresholds are 2*(294 - 16) + column width. */

/* B34, E9: at 1200px and up these columns sit in the content column beside
   the menu pane, so the lockup never reaches them and the two widest
   thresholds (they were 1375 and 1315) end at 1199. Below it the lockup
   scrolls with the page and these columns still drop under its band. */
@media (max-width: 1199px) {     /* 820px columns */
  /* the season pages left this list when they went fluid: they reserve the
     corner in `.season-head` instead, and drop below the lockup at their own
     breakpoint. See "SEASON PAGE LAYOUT" at the end of this file. */
  #settings, #account { margin-top: var(--lockup-clear); }
}
@media (max-width: 1199px) {     /* 760px column */
  #mock-history { margin-top: var(--lockup-clear); }
}
@media (max-width: 1175px) {     /* 620px columns */
  #mock-setup, #espn-setup { margin-top: var(--lockup-clear); }
}
@media (max-width: 1115px) {     /* 560px column */
  #setup { margin-top: var(--lockup-clear); }
}
/* The boot card and the chooser are not fixed columns -- each is a
   full-height centred flex stack, so at phone width its vertically-centred
   content lands inside the lockup band. Start the stack below the lockup
   instead of centring it; `justify-content` has to change too, because with
   `min-height:100vh` and centred content extra padding only moves the stack
   by half of what is added. (Home starts on the clear line at every width,
   B27, so it needs no rule here.) */
@media (max-width: 640px) {
  #boot-error, #draft-choice {
    justify-content: flex-start;
    padding-top: var(--lockup-clear);
  }
}

/* ==========================================================================
   SEASON PAGE LAYOUT (2026-09-16)
   --------------------------------------------------------------------------
   Owner: "the panes ... take up very little horizontal room"; "the FAAB
   ledger and individual moves panes should make more use of horizontal
   space ... you should not have to scroll down to see either pane".

   Every season page was an 820px centred column of stacked cards, a copy of
   the settings shell. That width was never a reading measure -- it was the
   number at which a CENTRED column happened to clear the fixed 290x108 brand
   lockup, which is exactly what UI_GUIDELINES.md E4 forbids relying on, and
   it threw away 57% of a 1440px screen.

   Both problems get one answer: the shell goes fluid, and the corner is
   reserved STRUCTURALLY by the page head (E3/E4) rather than by the width of
   a column. The plan and the per-page decisions are in
   verification/SEASON_LAYOUT_PLAN.md.
   ========================================================================== */

/* --- 1. the shell --------------------------------------------------------
   `100%` here is the body's width, so the gutter is measured against the
   real content area rather than 100vw (which includes a classic scrollbar).
   At <=1648px the shell is the viewport minus a --sp-6 gutter each side;
   above that it centres at --season-max. */
#standings, #rosters, #waivers, #my-team, #matchup, #activity, #outlook,
#injury-report, #trades {
  width: min(var(--season-max), 100% - 2 * var(--sp-6));
  margin: var(--sp-10) auto var(--sp-20);
  padding: 0;
  /* B34: the content column is the viewport less the menu pane from 1200px,
     so a page's layout follows ITS width, not the window's: the splits
     below ask the shell (`@container season`), not `@media`. The widths
     are the old viewport breakpoints less the shell's two gutters (1280 ->
     1232, 1680 -> 1600 at the cap). */
  container: season / inline-size;
}

/* The lockup reserve. The shell's left edge is now ~24px at every width, so
   it never clears the lockup sideways and does not pretend to: the HEAD
   reserves the corner, by arithmetic on the real geometry rather than by a
   threshold. `(100vw - 100%) / 2` is this shell's own left offset, so the
   padding always resolves to "whatever is still needed to reach x = 287"
   -- the lockup box (--lockup-x + 290 = 263 since B8 put its ink on the
   gutter) plus a --sp-6 of clear space. 100vw over-reads by the scrollbar
   width, which makes the reserve up to ~8px short of 287; the --sp-6 of
   clear space absorbs that and the text still lands past 263.

   B34, E9: this is drawer mode's rule (below 1200px). From 1200px the
   lockup heads the menu pane, so the head reserves nothing: the title sits
   at the column's left edge, beside the lockup's centre.

   min-height is the VERTICAL half of the same reserve: the head's own box
   carries the page down past the lockup band, so nothing below it needs a
   width-dependent margin. --lockup-clear (124) minus the shell's top margin
   (40) is exactly the height that puts the head's bottom edge on the lockup
   clear line. h1's top margin is zeroed so that arithmetic is not undone by
   margin collapsing. */
.season-head { margin-bottom: var(--sp-4); }
@media (max-width: 1199px) {
  .season-head {
    padding-left: max(0px, calc(var(--lockup-x) + var(--lockup-w) + var(--sp-6)
                                - (100vw - 100%) / 2));
    min-height: calc(var(--lockup-clear) - var(--sp-10));
  }
}
.season-head h1 { margin-top: 0; }

/* Below this there is no room for a title BESIDE the lockup -- a 294px
   reserve would leave a 72px column at phone width -- so the head drops
   under it instead, the one case where vertical clearance is the answer. */
@media (max-width: 1023px) {
  #standings, #rosters, #waivers, #my-team, #matchup, #activity, #outlook,
  #injury-report, #trades { margin-top: var(--lockup-clear); }
  .season-head { padding-left: 0; min-height: 0; }
}

/* A phone cannot spare 24px a side. The gutter drops to --sp-4, which is
   what these pages had before they went fluid, so no row is narrower at 390
   than it was -- a player line that fits today must still fit. */
@media (max-width: 640px) {
  #standings, #rosters, #waivers, #my-team, #matchup, #activity, #outlook,
  #injury-report, #trades { width: min(var(--season-max), 100% - 2 * var(--sp-4)); }
}

/* --- 2. the shared split: a fluid main column and a secondary rail --------
   H3 throughout: shrinking tracks are minmax(0, 1fr) and every child carries
   min-width: 0, so a long player name shrinks its column instead of pushing
   the grid past the viewport. Below 1280 the rail cannot hold its 280px
   floor beside a usable main column, so both stack. */
.season-split {
  display: grid; gap: var(--sp-6); align-items: start;
  grid-template-columns: minmax(0, 1fr) minmax(280px, var(--season-rail));
}
.season-split > * { min-width: 0; }
/* A grid item's automatic minimum size is its MIN-CONTENT width, so a card
   whose rows cannot shrink (a nowrap action label, a long player line) grows
   past a capped track and paints outside the page. min-width:0 on the rail's
   own cards is what holds them inside it -- H3, the same rule as the tracks. */
.season-split > * > * { min-width: 0; }
@container season (max-width: 1231px) {
  .season-split { grid-template-columns: minmax(0, 1fr); }
}

/* --- 3. standings: the table beside my own row, read out ------------------
   The table keeps every column (they are how the league is compared); the
   rail is a read-out of MY row -- seed, record, points, playoff odds -- so
   "where you sit" does not need a scan across sixteen columns. */
.st-mine { display: grid; gap: var(--sp-3); }
.st-mine-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-4);
}
.st-mine-row dt {
  font: var(--text-label); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-3); min-width: 0;
}
.st-mine-row dd {
  margin: 0; font: var(--text-data); color: var(--fg-1);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.st-mine-row dd.st-big {
  font: var(--text-data-lg);
  background: var(--grad-text); -webkit-background-clip: text;
  background-clip: text; color: transparent; padding: 0 0.1em;
}
/* words, not numbers (B37/B38): the playoff spot and the division name
   take the sans face; every number beside them stays mono (B7) */
.st-mine-row dd.st-text {
  font: var(--text-small); font-weight: 500; min-width: 0;
  overflow: hidden; text-overflow: ellipsis;
}
#standings-odds-note { margin: var(--sp-3) 0 0; font-size: 12px; }
/* B36 (a): the rail's "How this is calculated" — D29's lead line, then
   the plain-language account one click away. Paragraphs, not one run-on
   block: each answers one question (where XW starts, how a week is
   played, seeding, all-play). */
.st-how-lead { margin: var(--sp-4) 0 var(--sp-1); }
.st-how .set-help p { margin: 0 0 var(--sp-2); }
.st-how .set-help p:last-child { margin-bottom: 0; }

/* B40 (owner 2026-09-28): "All columns in the standings pane should be
   sortable." Each header IS its sort control -- the owner's own words were
   "click a column header" -- so the button keeps the header's own type and
   colour rather than a button family: nineteen gradient chips across one
   header row would be nineteen primaries on one card (D20). What says
   "clickable" is the pointer, the hover lift to --fg-1, the arrow on the
   sorted column and the shared focus ring (D27). Height is D2's target
   floor, so the header row grows to hold it. */
.season-table th .th-sort {
  display: flex; align-items: center; justify-content: flex-start;
  gap: var(--sp-1);
  /* 24px: D2's target floor, on both axes, at every pointer size */
  width: 100%; min-width: 24px; min-height: 24px; padding: 0;
  background: none; border: 0; border-radius: var(--r-sm); box-shadow: none;
  font: inherit; letter-spacing: inherit; text-transform: inherit;
  color: inherit; text-align: inherit;
}
.season-table th.num .th-sort { justify-content: flex-end; }
.season-table th .th-sort:active { background: none; box-shadow: none; }
/* hover only where there is one (B21): on an iPhone a :hover sticks after
   the tap; this also undoes the button family's own hover wash, which is
   gated the same way */
@media (hover: hover) {
  .season-table th .th-sort:hover {
    background: none; box-shadow: none; color: var(--fg-1);
  }
}
.season-table th[aria-sort] .th-sort { color: var(--fg-1); }
.th-arrow { color: var(--violet-bright); }
.th-arrow:empty { display: none; }
/* B38: what the seed means, in words beside the number -- never hue
   alone (C5): the words carry it, the colour only ranks it */
.st-seed-tag {
  display: inline-block; margin-left: var(--sp-1);
  font: var(--text-label); white-space: nowrap; color: var(--fg-2);
}
.st-seed-division_leader { color: var(--violet-bright); }
.st-seed-out { color: var(--fg-3); }
/* B37: a division's own name, one line, the full name in the cell title */
.st-div-name {
  display: block; max-width: 8rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* B40 on a phone: the Sort by list and its reverse control. Above the
   phone breakpoint every header is on screen and sorts on its own. */
.st-sortbar { display: none; }
/* the sort line: what the rows are ordered by and what that column means,
   at every width -- a finger that sorts by tapping a header reads the
   column's help here, not in a popover (review M4) */
.st-sort-note { margin: 0 0 var(--sp-3); font: var(--text-small); }
/* Nineteen columns (B37-B40, ROADMAP §8 item 10) measure ~1160px, and
   beside the 320px rail they ran off the card at 1280 AND 1440 -- the
   seed and schedule-left columns the owner asked about were the ones
   scrolled out of sight. Below a 1600px shell -- the cap, the first width
   that leaves the table 1216px beside the rail, room for the longest names
   the cells allow -- the table takes the whole shell and the rail moves
   under it, as it already does below 1232px, laying its read-out across
   the card instead of down it. (Written as a 1680px window until B34, when
   the menu pane made the shell narrower than the window.) */
@container season (max-width: 1599px) {
  #standings .season-split { grid-template-columns: minmax(0, 1fr); }
  #standings .st-mine {
    grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
    column-gap: var(--sp-10);
  }
  #standings .st-how-lead, #standings-odds-note { max-width: 72ch; }
}

/* --- tap help (B20): the shared toggletip `initHelpPopover` opens -------
   A floating surface (2b): the toast's glass recipe at the quiet level,
   never the focal --border-accent + glow. Sits at the confirm bar's tier,
   over the page and under every overlay. */
#help-pop {
  position: fixed; z-index: 50;
  max-width: min(18rem, calc(100vw - 2 * var(--sp-4)));
  padding: var(--sp-2) var(--sp-3);
  background: var(--glass-2); backdrop-filter: blur(var(--blur-glass));
  border: 1px solid var(--border-2); border-radius: var(--r-md);
  box-shadow: var(--shadow-float), var(--inset-glass);
  font: var(--text-small); color: var(--fg-1);
  text-transform: none; letter-spacing: normal; white-space: normal;
  animation: help-pop-in var(--dur-quick) var(--ease-drift);
}
@keyframes help-pop-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  #help-pop { animation: none; }
}

/* --- 4. waivers: the streaming card as a rail, without touching its JS ----
   `renderWaiverList` and `renderStreamingCard` are owned elsewhere this
   week, so the split is done on #waivers-body's existing children: every
   child sits in column 1, and .wv-streaming alone is pinned to column 2.
   Vertical rhythm moves from the children's own margins onto the grid's gap
   (A5), which is why the margins are zeroed here. The row gap is a
   --sp-6, a step above the cards' own padding (A4). */
#waivers-body {
  display: grid; gap: var(--sp-6); align-items: start;
  grid-template-columns: minmax(0, 1fr) minmax(280px, var(--season-rail));
}
#waivers-body > * { grid-column: 1; min-width: 0; margin-top: 0;
                    margin-bottom: 0; }
/* The rail spans the column-1 stack instead of sharing its first row --
   otherwise row 1 is as tall as the streaming card and the table starts
   below it, with 200px of nothing beside it. Three rows is what this body
   ever holds in column 1 (format note, the free-agent card with its status
   line since B17, the "not rated" card); a short page just leaves an
   empty implicit row, which costs one row-gap. */
#waivers-body > .wv-streaming { grid-column: 2; grid-row: 1 / span 3; }
/* a league with nothing to stream renders no rail at all */
#waivers-body:not(:has(> .wv-streaming)) {
  grid-template-columns: minmax(0, 1fr);
}

@container season (max-width: 1231px) {
  #waivers-body { grid-template-columns: minmax(0, 1fr); }
  #waivers-body > .wv-streaming { grid-column: 1; grid-row: auto; }
}

/* --- 5. my roster: two lists across one card -----------------------------
   starters | bench (with IR). auto-fit drops to one column as the card
   narrows, with no media query of its own, so the phone keeps the same
   reading order. The column gap is a step up from the card's own padding
   (A4) so the lists read as separate lists.

   The swaps ("start instead") used to be a third column here; B51 moved
   them above both lists as wrapping sentences (.mt-swaps), after a 320px
   rail and then a third column had both truncated names to "Garrett A...
   o... Cooper S...", which guidelines B9 calls a correctness bug. */
.mt-lineup {
  display: grid; gap: var(--sp-4) var(--sp-8); align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.mt-lineup > * { min-width: 0; }

/* --- 6. my matchup: two full columns, each with its bench ----------------
   Capped below the shell's own maximum: past ~1280 a starter row becomes a
   name and a number with 300px of nothing between them, which is worse to
   read, not better. The owner asked for the bench to be present, not for
   the starters to be thrown apart.

   The reserves are deliberately secondary -- --fg-3 throughout, their own
   group label, under a dashed rule (.lu-reserve, below) -- so a bench
   player can never be mistaken for someone who is playing. */
.vs-board { max-width: 1280px; margin-inline: auto; }

/* --- 6b. LINEUP TABLES: Lineup and Gamecast rosters (B51 a, B53 a) --------
   Owner, 2026-09-28: "player names not lining up is ugly -- give position
   rank and names their own columns." A real table (G7): slot | position
   rank | player | tag | one number. The player cell takes whatever the
   other columns leave (width 100% + max-width 0, the table form of B9's
   min-width: 0) and ellipsises the name with the full value in its title;
   the rest size to their content. Beside a 100%-wide cell a table squeezes
   every other column to its min-content, so a `width` on the slot or rank
   cell does nothing: their floors live in their content instead (the slot
   text's own min-width, the .pos badge's 38px), which is what makes the
   Lineup's starters and bench tables -- stacked on a phone -- and the two
   Gamecast sides start their names at the same x. Scoped by class, not by
   section id. */
.lu-table { width: 100%; border-collapse: collapse; font-size: 13px; }
/* the global th is a sticky --bg-1 band for scrolling tables; these rows
   never scroll inside anything, so it is reset here */
.lu-table th, .lu-table td {
  position: static; background: none; text-align: left;
  vertical-align: middle; white-space: nowrap;
  padding: var(--sp-1) var(--sp-2);
  border-bottom: 1px solid color-mix(in oklab, var(--border-1) 50%, transparent);
}
.lu-table thead th, .lu-group {
  font: var(--text-label); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-3);
}
.lu-table thead th { border-bottom-color: var(--border-1); padding-top: 0; }
.lu-group-row th { padding-top: var(--sp-4); }
.lu-table .lu-slot {
  font: 700 11px var(--font-mono);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--fg-3);
}
/* 5ch holds "FLEX" and its tracking; a longer Sleeper key (SUPER_FLEX)
   widens its own table's column rather than clipping */
.lu-slot > span { display: inline-block; min-width: 5ch; }
.lu-table .lu-player { width: 100%; max-width: 0; }
/* The name comes first and keeps its line; the flags after it (Q, !,
   LOCKS TODAY, BYE) wrap under it when they do not fit beside it (review
   F1). They used to hold their full width on one line while only the name
   shrank, so on a Sunday morning at 390 -- "Locks today" on every row --
   names fell to 39px, 11px, even 0px, and B9 calls a clipped name a
   correctness bug. A flex line breaks before anything shrinks, so the
   name is squeezed only when it alone is wider than the whole cell, and
   then it ellipsises with its title. The flags' own margin-left gives way
   to the gap, so a wrapped flag starts flush under the name. */
.lu-who {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 0 var(--sp-1); min-width: 0;
}
.lu-who > * { flex-shrink: 0; margin-left: 0; }
.lu-who > .pname {
  flex-shrink: 1; min-width: 0; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 52a: the open "why" under a Lineup row is sentences: its cell opts out of
   the table's nowrap so the text wraps inside the card, not past it */
.lu-table .wv-why-row > td { white-space: normal; }
.lu-table .lu-tag { width: 1%; padding-left: 0; }
.lu-table .lu-tag:empty { padding: 0; }
.lu-table .lu-num { width: 1%; text-align: right; }
.lu-val, .lu-sub { display: block; }
.lu-val { font: 500 13px/1.3 var(--font-mono); color: var(--fg-1);
          font-variant-numeric: tabular-nums; }
.lu-sub { font: 500 11px/1.2 var(--font-mono); color: var(--fg-3);
          font-variant-numeric: tabular-nums; }
/* the column head names both lines of the number cell, in label type */
.lu-table thead .lu-val, .lu-table thead .lu-sub { font: inherit; color: inherit; }

/* the row washes: each is the colour of the sentence at the top of the
   card that names the row (review F2) -- red for a starter a swap
   benches, the success green for a bench player it starts, the warning
   wash of "Your bench can't fill this spot" for a hole */
.lu-table tr.lu-out > * { background: color-mix(in oklab, var(--red) 8%, transparent); }
.lu-table tr.lu-in > * { background: color-mix(in oklab, var(--success) 8%, transparent); }
.lu-table tr.lu-hole > * { background: color-mix(in oklab, var(--warn) 8%, transparent); }

/* B51 f: SIT is the red end of the brand's one gradient family -- the
   same angle as --grad-aurora, from --red-deep to --red, because the
   aurora's own red tail (--red -> --hot) holds white text at only
   3.1-3.9:1; across the pill's text this measures 4.6:1 or better.
   START is the design system's success green, deepened into the ground
   the same way (--success 55% into --bg-0, 7.6:1). Both are pills (the
   brand's shape for badges), in text-on-accent, and never the only
   signal: the word is the tag. */
.lu-pill {
  display: inline-flex; align-items: center; height: 18px;
  padding: 0 var(--sp-2); border-radius: var(--r-pill);
  font: 600 11px/1 var(--font-sans); letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-on-accent);
}
.lu-sit { background: linear-gradient(118deg, var(--red-deep), var(--red)); }
.lu-start { background: color-mix(in oklab, var(--success) 55%, var(--bg-0)); }

/* Gamecast's bench, IR and taxi: secondary, under one dashed rule */
.lu-table tbody:not(.lu-reserve) + .lu-reserve .lu-group-row th {
  border-top: 1px dashed var(--border-1);
}
.lu-reserve th, .lu-reserve td, .lu-reserve .lu-val { color: var(--fg-3); }

/* a phone: every cell gives up half its side padding, so a bench name
   keeps ~130px beside a START tag at 390 */
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  .lu-table th, .lu-table td { padding-left: var(--sp-1); padding-right: var(--sp-1); }
}

/* --- 7. activity: the moves, then the money (B56) --------------------------
   Owner, 2026-09-28: the FAAB ledger led the page and the moves, its point,
   came second; the explanation was a grey paragraph at the bottom. Now the
   Moves card comes first (one group per week, the newest open, older weeks
   folded to a one-line summary), then the FAAB card with a small glass
   "How to Read This" pane beside it, under it when the shell is narrow.
   Design: docs/qc_2026-09-28/findings/B56_activity_proposal.md; the phone
   form is in the PHONE LAYOUT block below (H10). */

/* -- the filter row: Team, and Type (a segmented control, D23; a select on a
   phone, where four segments do not fit beside the team select) -- */
.act-filters {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--sp-4);
  margin-bottom: var(--sp-4);
}
/* `display:flex` above outranks the UA's [hidden] rule */
.act-filters[hidden] { display: none; }
.act-field {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--sp-1); font: var(--text-small); color: var(--fg-3);
}
.act-type-select { display: none; }

/* -- the Moves card -- */
.act-moves > h2 { margin-bottom: var(--sp-1); }
.act-note { margin: 0; font: var(--text-small); color: var(--fg-3); }
/* a week: its heading is a disclosure (the house `.set-more` look: a mono
   +/- glyph, no button family) with the week's one-line summary beside it */
.act-week { border-top: 1px solid var(--border-1); }
.act-week:first-of-type { border-top: 0; }
.act-week > summary {
  display: flex; flex-wrap: wrap; align-items: baseline;
  column-gap: var(--sp-3); row-gap: var(--sp-1);
  min-height: var(--ctl-h); padding: var(--sp-3) 0;
  cursor: pointer; list-style: none; border-radius: var(--r-sm);
}
.act-week > summary::-webkit-details-marker { display: none; }
.act-week > summary::before {         /* the glyph out of the name, as .set-more */
  content: "+"; content: "+" / ""; font: var(--ctl-font-num);
  color: var(--violet-bright); width: 1ch;
}
.act-week[open] > summary::before { content: "\2212"; content: "\2212" / ""; }
.act-week > summary:focus-visible {
  outline: 2px solid var(--violet-bright); outline-offset: 2px;
}
@media (hover: hover) {
  .act-week > summary:hover .act-wk-sum { color: var(--fg-2); }
}
.act-wk-name { font: var(--text-h3); color: var(--fg-1); }
.act-wk-sum { min-width: 0; font: var(--text-small); color: var(--fg-3); }

/* a move: tag | the move | the money (its bid, and the time under it).
   The tag column is as wide as "COMMISSIONER" in its pill, so the moves'
   bodies line up whatever the kind. Your own moves carry the violet edge
   (`--violet`, not `--border-accent`: an indicator must clear 3:1, C3) and
   the word "You" -- the word is what says it (C5). */
.act-list { list-style: none; margin: 0; padding: 0; }
.act-move {
  --act-tag-w: 7.75rem;
  display: grid; align-items: start; column-gap: var(--sp-4);
  grid-template-columns: var(--act-tag-w) minmax(0, 1fr) minmax(6.5rem, auto);
  padding: var(--sp-3);
  border-top: 1px solid var(--border-1);
}
.act-mine { background: var(--glass-1); box-shadow: inset 3px 0 0 var(--violet); }
.act-tag {
  justify-self: start; white-space: nowrap;
  padding: var(--sp-1) var(--sp-2);
  font: var(--text-label); letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--fg-2); border: 1px solid var(--border-2);
  border-radius: var(--r-sm);
}
.act-k-waiver .act-tag { color: var(--violet-bright); }
.act-k-trade .act-tag { color: var(--blue-bright); }
.act-k-commissioner .act-tag { color: var(--warning); }
.act-k-failed_claim .act-tag { color: var(--red-bright); }
.act-body { min-width: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.act-team {
  font: var(--text-body); font-weight: 600; color: var(--fg-1);
  overflow-wrap: anywhere;
}
.act-k-failed_claim .act-team { color: var(--fg-2); }
.act-you { color: var(--violet-bright); font-weight: 600; white-space: nowrap; }
.act-players {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--sp-1);
}
/* a player: the sign, the position in its colour, the name and NFL team,
   and under them, for a pickup, how he did for the team that added him */
.act-pl {
  display: grid; column-gap: var(--sp-2); align-items: baseline;
  grid-template-columns: 1.25ch 2rem minmax(0, 1fr);
  font: var(--text-small); color: var(--fg-1);
}
.act-pl-plain { grid-template-columns: 2rem minmax(0, 1fr); }
.act-pl .pos { min-width: 0; }
.act-sign { font: var(--ctl-font-num); color: var(--fg-3); }
.act-plus { color: var(--success); }
.act-pl-main { min-width: 0; overflow-wrap: anywhere; }
.act-nfl { font: var(--text-label); font-family: var(--font-mono); color: var(--fg-3); }
.act-since {
  display: block; margin-top: var(--sp-1);
  font: var(--text-label); font-family: var(--font-mono); color: var(--fg-3);
}
/* a trade's picks and FAAB sit in the players' column, under their names */
.act-item { display: block; padding-left: calc(2rem + var(--sp-2)); color: var(--fg-2); }
.act-none { color: var(--fg-3); }
.act-more { margin: 0; }
.act-more > summary { min-height: var(--ctl-h-sm); }
.act-sides { display: flex; flex-direction: column; gap: var(--sp-2); }
.act-side {
  display: grid; align-items: baseline; column-gap: var(--sp-3);
  grid-template-columns: minmax(8rem, 11rem) minmax(0, 1fr);
}
.act-gets { font: var(--text-small); color: var(--fg-3); overflow-wrap: anywhere; }
.act-money {
  min-width: 0; display: flex; flex-direction: column; align-items: flex-end;
  gap: var(--sp-1); text-align: right;
}
.act-amt {
  font: var(--text-data); color: var(--fg-1);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* a failed claim's bid cost nothing: quiet, and no different in weight from
   the time under it */
.act-amt-void { color: var(--fg-3); }
.act-amt-prio { font: var(--text-small); font-family: var(--font-mono); color: var(--fg-2); }
.act-time {
  font: var(--text-label); font-family: var(--font-mono);
  color: var(--fg-3); white-space: nowrap;
}

/* -- below the moves: the FAAB card, and its info pane -- */
.act-lower { margin-top: var(--sp-6); }
/* A7: the pane BESIDE the FAAB table. The shared `.season-split` stacks under
   a 1232px shell, which is right for a wide table but was never measured for
   this one: the ledger's six columns are ~573px at their natural width, its
   card about 615px, so the card, a 280px rail and the 24px gap fit from a
   ~920px shell -- a 1200px window beside the menu pane is 852 and stacks, a
   1280 window is 932 and splits. The rail is a fixed 280px down here (the
   shared 320px maximum would leave the card 588px and the table a sideways
   scroller); the team name is what gives, ellipsised (`.act-who`), so no
   name however long widens the table. From 1232 the shared split's own
   320px rail applies. `tests.test_ui_activity` measures where the pane is. */
@container season (min-width: 920px) and (max-width: 1231px) {
  .act-lower.season-split { grid-template-columns: minmax(0, 1fr) 280px; }
}
.act-lead {
  margin: 0 0 var(--sp-3); max-width: 72ch;
  font: var(--text-small); color: var(--fg-2);
}
.act-ledger th { white-space: nowrap; }
.act-id { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
/* The team cell takes whatever the numbers leave it and never asks for more
   (`width: 100%; max-width: 0`, the table-cell ellipsis idiom): a long fantasy
   team name plus its ADJUSTED pill and "· You" cannot push the table past its
   card. The name is what is cut (the pill and the word keep their size), and
   its full text is in `title` and `data-help` (a tap shows it, D32). */
.act-who { width: 100%; max-width: 0; }
.act-name {
  min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; color: var(--fg-1);
}
.act-id .act-you { flex-shrink: 0; font: var(--text-label); font-weight: 600; }
.act-badge {
  flex-shrink: 0; padding: 0 var(--sp-1);
  font: var(--text-label); letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--warning); border: 1px solid var(--border-2);
  border-radius: var(--r-sm);
}
/* Left leads: the amount, and under it how much of its start the team still
   has. The fill is measured against its track (C3, tests/test_ui_contract);
   the number is always printed, so the bar is decoration. */
.act-ledger .act-amt { font: var(--text-data); color: var(--fg-1); }
.act-bar {
  display: block; width: 4rem; height: var(--sp-1); margin: var(--sp-1) 0 0 auto;
  overflow: hidden; border-radius: var(--r-pill); background: var(--bg-3);
}
.act-bar-fill {
  display: block; height: 100%; border-radius: var(--r-pill);
  background: var(--violet-bright);
}
.act-ledger td.act-minor, .act-ledger th.act-minor { color: var(--fg-3); }
/* your row carries the same violet edge as your moves (D34; --violet, not the
   shared me-row's --border-accent hairlines, which measure 1.98:1) */
.act-ledger tr.me-row td:first-child { box-shadow: inset 3px 0 0 var(--violet); }
/* the adjustment is its own folded number on a phone, and shown in the
   Started column and the team's pill everywhere else */
.act-adjcell { display: none; }

/* A7: the info pane. Prose, not data -- so it is glass even though it sits
   inline, the one written exception to UI_GUIDELINES 2b besides the red SIT
   tag (`tests.test_ui_contract` measures its text on the worst backdrop the
   nebula can put behind it). Its text is --fg-2 and its terms --fg-1: --fg-3
   measured 3.6:1 on glass over the nebula in the first audit. */
.act-pane {
  min-width: 0; padding: var(--sp-5);
  background: var(--glass-1); backdrop-filter: blur(20px);
  border: 1px solid var(--border-1); border-radius: var(--r-lg);
  box-shadow: var(--shadow-card), var(--inset-glass); color: var(--fg-2);
}
.act-pane h2 { color: var(--fg-2); }
/* one column in the rail, a row of four when the pane has the width */
.act-defs {
  margin: 0 0 var(--sp-2); display: grid; gap: var(--sp-3) var(--sp-6);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}
.act-defs dt { font: var(--text-small); font-weight: 600; color: var(--fg-1); }
.act-defs dd { margin: 0; font: var(--text-small); color: var(--fg-2); }
.act-pane .set-more { margin: 0; }
.act-pane .set-more > summary { color: var(--fg-2); }
.act-pane .set-help { margin: var(--sp-1) 0 0; font: var(--text-small); color: var(--fg-2); }

/* the moves' stacked form: a shell under 720px -- every phone, a small
   tablet, a drawer-mode window before the menu pane -- has no room for
   tag | body | money side by side. The tag and the money share the first
   line and the body takes the full width under them. */
@container season (max-width: 719px) {
  .act-move {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "tag money" "body body"; row-gap: var(--sp-2);
  }
  .act-tag { grid-area: tag; }
  .act-body { grid-area: body; }
  .act-money {
    grid-area: money; flex-direction: row; align-items: baseline;
    gap: var(--sp-3);
  }
  .act-side { grid-template-columns: minmax(0, 1fr); row-gap: var(--sp-1); }
}

/* --- 10. injury report: who is hurt, and what it costs (B48, D36) ------------
   One page, one table, sorted by the server: starters first, then the worst
   status first. A switch (D23) picks My Roster or League; a summary line says
   how many and whether a starter has nobody to cover him; the table's rows
   open for the detail. Dates are never invented: the "first reported" column
   is ESPN's own note date with its source named, and a dash where no note
   names the injury (J5). A data surface (2b): the plain card recipe, no glass.
   The phone form (H10) is the shared fold: the player and the return on the
   row, the rest behind a tap with its own label. */
.inj-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4); margin-bottom: var(--sp-4); }
#injury-report-body { display: flex; flex-direction: column; gap: var(--sp-4); }

/* the summary: a word and a number, and (a word and a mark, C5) a warning
   when a starter has no healthy replacement */
.inj-summary { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2) var(--sp-6); }
.inj-line { margin: 0; font: var(--text-body); color: var(--fg-1); }
.inj-line-none { color: var(--fg-2); }
.inj-warn { margin: 0; font: var(--text-small); font-weight: 600; color: var(--warning); }
.inj-warn > [aria-hidden] { font-family: var(--font-mono); margin-right: var(--sp-1); }

/* the table */
/* a header may wrap ("first / reported") so a date column is as wide as its
   date, not as its label: the table has to fit a 932px shell (1280 beside
   the menu pane) without scrolling sideways */
.inj-table th { white-space: normal; vertical-align: bottom; }
.inj-table td { vertical-align: top; }
.inj-table td.inj-c-who { min-width: 9.5rem; }
.inj-table .inj-c-ret { padding-left: var(--sp-3); }
.inj-table tbody tr.inj-row { cursor: pointer; }
/* a starter carries the violet edge AND the word "Starter" under his name (C5;
   --violet, not --border-accent: an indicator must clear 3:1, C3) */
.inj-table tr.inj-starter td:first-child { box-shadow: inset 3px 0 0 var(--violet); }
.inj-who { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-1) var(--sp-2); min-width: 0; }
.inj-name { min-width: 0; font-weight: 600; color: var(--fg-1); }
/* a name opens his card, so it is a target: 24px tall at least (D2) */
.inj-name .pname { display: inline-block; padding: var(--sp-1) 0; }
.inj-meta {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-1) var(--sp-2);
  margin-top: var(--sp-1); font: var(--text-label); color: var(--fg-3);
}
.inj-meta .pos { min-width: 0; }
.inj-nfl, .inj-where { font-family: var(--font-mono); color: var(--fg-3); }
.inj-status { display: block; font-weight: 600; color: var(--fg-1); }
.inj-sub { display: block; font: var(--text-label); font-family: var(--font-mono); color: var(--fg-3); }
.inj-date { display: block; font: var(--text-data); font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--fg-1); }
.inj-dash, .inj-quiet { color: var(--fg-3); }
.inj-c-missed { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.inj-ret-text { color: var(--fg-1); }
.inj-sub-name { display: block; color: var(--fg-1); }
.inj-delta { display: block; font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: var(--fg-2); }
/* league mode: whose player he is, under him; "You" is a word (C5) */
.inj-team { margin-top: var(--sp-1); font: var(--text-small); color: var(--fg-2); overflow-wrap: anywhere; }
.inj-you { margin-left: var(--sp-1); color: var(--violet-bright); font: var(--text-label); font-weight: 600; }
.inj-you::before { content: "\00b7\00a0"; }
/* the toggle is on the desktop table too: a click or Enter opens the detail */
.inj-table .st-toggle { display: table-cell; width: 40px; padding: 0 var(--sp-1); text-align: right; }
.inj-table .st-toggle button { width: 32px; height: 32px; min-width: 32px; color: var(--fg-2); }

/* the opened row */
.inj-table tr.inj-detail > td { padding: var(--sp-4); background: var(--glass-1); }
.inj-facts, .inj-defs {
  margin: 0; display: grid; gap: var(--sp-3) var(--sp-6);
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
}
.inj-facts dt, .inj-defs dt {
  font: var(--text-label); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--fg-3);
}
.inj-facts dd, .inj-defs dd { margin: var(--sp-1) 0 0; font: var(--text-small); color: var(--fg-1); }
.inj-defs { margin-top: var(--sp-2); grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
.inj-defs dd { color: var(--fg-2); }
.inj-returns, .inj-chips { margin: 0; padding: 0; list-style: none; }
.inj-returns { display: grid; gap: var(--sp-1); }
.inj-sub-inline { display: block; font: var(--text-label); color: var(--fg-3); }
.inj-chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.inj-chip {
  padding: var(--sp-1) var(--sp-2); font: var(--text-label);
  font-family: var(--font-mono); color: var(--fg-2);
  border: 1px solid var(--border-2); border-radius: var(--r-sm);
}
.inj-also { margin: 0; font: var(--text-small); }
.inj-about { margin: 0; }

/* the phone fold (H10): the player and his status code on the row, the
   return on the line under it, a chevron at the end of the first line, and
   every other column folded under the row with its own label. The header
   row is gone, so nothing is named by it. */
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  .inj-bar .btn-group { display: flex; width: 100%; }
  .inj-bar .btn-group button { flex: 1 1 50%; min-height: var(--ctl-h); }
  .inj-table.st-table thead { display: none; }
  .inj-facts .inj-f-dup { display: none; }
  .inj-table tr.inj-row > td.inj-c-who { flex: 1 1 calc(100% - 40px); min-width: 0; }
  .inj-table tr.inj-row > td.st-toggle { order: 0; }
  .inj-table tr.inj-row > td.inj-c-ret {
    order: 1; flex: 0 0 100%; padding: 0 var(--sp-1) var(--sp-2);
  }
  .inj-table tr.inj-row.fold-open td[data-label] { flex-basis: 50%; align-self: stretch; }
  .inj-table tr.inj-detail { display: block; }
  .inj-table tr.inj-detail[hidden] { display: none; }
  .inj-table tr.inj-detail > td { display: block; }
  .inj-facts { grid-template-columns: minmax(0, 1fr); }
}

/* --- 9. the page footer row ----------------------------------------------
   Refresh / stamp (the page Back is gone, B34). A non-wrapping flex row here was one of the two
   breakages ROADMAP X10 names at phone width. */
.setup-row { flex-wrap: wrap; }
/* ===== waiver "why", Sleeper's own column, and the card's waiver block ===
   Added 2026-09-16 with the waiver-engine audit
   (verification/WAIVER_ENGINE_AUDIT.md). The owner's report was that the
   engine disagreed with Sleeper and the page gave him no way to see why;
   everything in this section exists to show the arithmetic that was
   already being done, never to add a number of its own. */

/* The row's own "why" toggle, beside the name. 11px and a 24px target on
   purpose: the UI audit harness counts anything under those as tiny text
   and a small target, and its budgets are ratchets (CLAUDE.md, UI
   Standing Rule) — a new control does not get to raise one. `subtle`, not
   `primary`: a disclosure that runs nothing, D1's documented exception
   (owner, 2026-10-15). */
.wv-why-toggle {
  margin-left: var(--sp-2); flex-shrink: 0;
  min-height: 24px; padding: 0 var(--sp-2);
  font: 600 11px var(--font-mono); text-transform: lowercase;
}
.wv-why-toggle.wv-why-open { color: var(--fg-1); }

/* The waiver table gained a "sleeper" column, and twelve columns at 1440
   squeezed the player column until names broke mid-name -- "Malik /
   Moreau" over two lines, which is the same defect `pname`'s own title
   attribute exists to prevent ("Coop" is not a shortened name, it is a
   different plausible player). A table that then does not fit its card
   scrolls sideways (`.tw-wide`, B32), and UI_GUIDELINES.md section 10 says
   an `overflow-x: auto` scroller is not a defect, so the name gets a floor
   and the scroller does its job. Keyed to the column's class, not its
   position: the Sleeper column comes and goes (B16). */
.wv-table .wv-c-player { min-width: 156px; }
#waivers-body .season-table .pname { white-space: nowrap; }

/* the expandable detail row under a waiver row. It spans the table, so it
   sets its own quiet background rather than inheriting the striping of a
   row it is not one of. */
.wv-why-row > td {
  padding: var(--sp-2) var(--sp-3);
  background: var(--glass-1);
  border-top: none;
}
/* sentences, so the body face on a desktop too, as on a phone (M2-6):
   11px mono made the "why" the smallest text on the page (B7 makes
   numbers mono, not prose) */
.wv-why {
  margin: 0; padding-left: var(--sp-4);
  font: var(--text-small); color: var(--fg-2);
}
.wv-why li { margin: 2px 0; }

/* Streaming: the keep verdict is a sentence the owner reads, so it is set
   in the body face at body size, not in the mono the numbers beside it
   use. One line since 2026-09-29 (B47): it carries the switch bar itself,
   so the quieter arithmetic line that used to sit under it is gone. */
.wv-stream-verdict {
  margin: 0; font: 13px/1.45 var(--font-sans); color: var(--fg-1);
}

/* the player card's waiver block: the headline numbers, then the same
   sentences the row's "why" shows */
.card-wv-nums {
  margin: 0 0 var(--sp-2); display: flex; flex-wrap: wrap; gap: 4px 18px;
}
.card-wv-num { font: 13px var(--font-mono); color: var(--fg-1); }
.card-wv-num .muted { font-size: 11px; }
.card-wv-why {
  margin: 0; padding-left: 18px;
  font: 11px/1.5 var(--font-mono); color: var(--fg-2);
}
.card-wv-why li { margin: 2px 0; }

/* the reduced card's own note: a player with no ranking gets a card that
   says so, rather than a card with silently empty sections */
.card-reduced { margin: 0 0 var(--sp-2); }

/* "Not rated by the model": free agents outside the ranked pool, listed
   below the valued table with no value of their own (option A, owner
   decision 2026-09-16 — see renderUnrankedFreeAgents). A quieter card
   than the one above it on purpose: this is the section that says what
   the model does NOT know. */
.wv-unranked { margin-top: var(--sp-4); }
.wv-unranked-title { margin: 0 0 var(--sp-1); }
.wv-unranked .season-table td { color: var(--fg-2); }

/* ==========================================================================
   STANDINGS FOLDS BESIDE THE PANE (B34, owner decision 2026-09-30)
   --------------------------------------------------------------------------
   Standings' eighteen columns need ~1028px inside the card. Beside the
   300px menu pane the column is narrower than that at 1200-1438 (and in
   the drawer-mode windows below ~1140), and the owner's rule is that a
   season table does not scroll sideways where it can condense instead
   (B19, B32): so under a 1091px shell -- the first width at which the whole
   table fits -- Standings takes the phone's H10 condensed row: # | team |
   record | pf | +, the rest folding under the row behind a tap (the whole
   row, or its + button for keyboard and screen readers; `foldableRows`),
   with the "Sort by" list for the folded columns. A phone is always inside
   this width, so these rules ARE the phone's Standings layout; the phone
   block below keeps only the rules Waivers shares (`.st-table` is Waivers'
   class too, so the structural rules here are scoped to `#standings`).
   The stuck header the sideways scroller could not keep comes back: off a
   phone, the whole <thead> sticks (a sticky <th> inside a display:block
   <thead> has no room to stick). `STANDINGS_FOLD_MAX` in app.js is this
   1091, so a bare row tap folds exactly where the fold is showing.
   ========================================================================== */
@container season (max-width: 1091px) {
  #standings .table-wrap, #standings .table-wrap.tw-wide { overflow: visible; }
  #standings .st-table, #standings .st-table thead,
  #standings .st-table tbody { display: block; }
  #standings .st-table tr {
    display: flex; flex-wrap: wrap; align-items: center;
    border-bottom: 1px solid var(--border-1);
  }
  #standings .st-table th, #standings .st-table td {
    border-bottom: 0; padding: var(--sp-2) var(--sp-1);
  }
  /* the top rule is an inset shadow, not a border, so my row is exactly
     as tall as every other row */
  #standings .st-table tr.me-row {
    background: var(--glass-2);
    box-shadow: inset 0 1px 0 var(--border-accent);
    border-bottom: 1px solid var(--border-accent);
  }
  #standings .st-table tr.me-row td {
    background: none; border-top: 0; border-bottom: 0;
  }
  /* the whole row is the tap target; the button is its keyboard and
     screen-reader handle, kept quiet so twelve of them do not shout */
  #standings .st-table .st-toggle {
    display: flex; align-items: center; justify-content: flex-end;
    flex: 0 0 40px; order: 1; padding: 0; align-self: stretch;
  }
  #standings .st-table .st-toggle button {
    width: 32px; height: 32px; min-width: 32px; color: var(--fg-2);
  }
  /* folded columns -- every cell carrying a `data-label` -- are hidden
     until the row is opened, then sit three to a line under it, each with
     its own label above the number */
  #standings .st-table td[data-label] { display: none; order: 2; }
  #standings .st-table tr.fold-open td[data-label] {
    display: block; flex: 0 0 33.333%; min-width: 0; text-align: left;
    background: var(--glass-1); padding: var(--sp-2);
  }
  #standings .st-table tr.fold-open td[data-label]::before {
    content: attr(data-label); display: block;
    font: var(--text-label); color: var(--fg-3);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  #standings .st-table tbody tr { cursor: pointer; }
  /* `display:flex` above outranks the UA's [hidden] rule */
  #standings .st-table tr[hidden] { display: none; }
  #standings .st-table .s-names { max-width: none; }

  /* --- standings: # | team | record | pf | + ----------------------------- */
  /* 32px: the # header's 24px sort target (B40) plus the cell padding */
  #standings .st-table :is(th, td):nth-child(1) { flex: 0 0 32px; }
  #standings .st-table :is(th, td):nth-child(2) {
    flex: 1 1 calc(100% - 204px); min-width: 0;
  }
  #standings .st-table :is(th, td):nth-child(3),
  #standings .st-table :is(th, td):nth-child(4) {
    flex: 0 0 auto; min-width: 56px;
  }
  #standings .st-table th:nth-child(n+5):not(.st-toggle) { display: none; }

  /* B40: the order of the folded columns is picked from a list, since
     their headers are folded away; the reverse control sits beside it */
  #standings .st-sortbar {
    display: flex; align-items: center; gap: var(--sp-2);
    margin: 0 0 var(--sp-3);
  }
  #standings .st-sortbar label {
    display: flex; align-items: center; gap: var(--sp-2);
    flex: 1 1 auto; min-width: 0;
    font: var(--text-small); color: var(--fg-2); white-space: nowrap;
  }
  #standings .st-sortbar select { flex: 1 1 auto; min-width: 0; }
  /* sorted by a folded column, that column takes points-for's place on
     the row, header and all, so the order can be read without opening
     every row; points for folds with the rest (`sortStandings` gives its
     cells their fold label while this lasts) */
  /* The slot is a FIXED 88px, and the names' basis gives up the 32px it
     adds over points-for's 56: a wrapping flex row breaks lines on its
     items' basis sizes before anything shrinks, so an `auto` slot holding
     "Frontier Division" pushed the + toggle onto a second line and every
     row grew from 52px to 96px (review MAJ1). 88px holds every number
     and "0 of 2000"; a longer division name ellipsises, whole in the
     fold and on tap. */
  #standings .st-table.st-sort-folded :is(th, td):nth-child(2) {
    flex-basis: calc(100% - 236px);
  }
  #standings .st-table.st-sort-folded th.st-pf { display: none; }
  #standings .st-table.st-sort-folded thead th.st-sorted {
    display: block; flex: 0 0 88px; min-width: 0;
  }
  #standings .st-table td.st-sorted[data-label],
  #standings .st-table tr.fold-open td.st-sorted[data-label] {
    display: block; order: 0; flex: 0 0 88px; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    text-align: right; background: none; padding: var(--sp-2) var(--sp-1);
  }
  #standings .st-table tr.fold-open td.st-sorted[data-label]::before {
    content: none;
  }
  #standings .st-table.st-sort-folded tr.fold-open td.st-pf {
    flex: 0 0 33.333%; text-align: left;
  }
  /* in that slot the seed shows its number alone, which is what the
     order is by; what it means stays one tap away, in its help (B20) */
  #standings .st-table td.st-sorted .st-seed-tag { display: none; }
  /* B37: under an opened row the division's name wraps rather than
     truncating -- the name is the point of the column -- but in the
     sorted slot on the row it stays one line inside the 88px */
  #standings .st-table tr.fold-open .st-div-name {
    max-width: none; white-space: normal; overflow-wrap: anywhere;
  }
  #standings .st-table td.st-sorted .st-div-name,
  #standings .st-table tr.fold-open td.st-sorted .st-div-name {
    max-width: 100%; white-space: nowrap;
  }

  /* Off a phone the header row sticks to the page as a whole, the way every
     other season table's does (D8). The th's own `position: sticky` (the
     global `th` rule) cannot stick inside a display:block <thead>, so the
     <thead> carries it, and its cells line up on the row's top edge. */
  @media (min-width: 601px) and (not ((pointer: coarse) and (max-height: 500px))) {
    #standings .st-table thead {
      position: sticky; top: var(--season-sticky-top); z-index: 5;
      background: var(--bg-1);
    }
    #standings .st-table thead tr { align-items: stretch; }
    #standings .st-table th { position: static; }
  }
}

/* ==========================================================================
   PHONE LAYOUT (2026-09-28, owner reports B18 and B19)
   --------------------------------------------------------------------------
   B18: "very ugly text being pushed into multiple lines" -- the standings
   team cell wrapped a long fantasy team name two or three lines deep.
   B19: "Horizontal and vertical scroll on mobile is atrocious design. Need
   either condensed info or just less."

   The rule on a phone -- <= 600px wide, or a touch screen <= 500px tall
   (the same phone turned sideways, where a 70vh table box was 273px and a
   16-column table still did not fit): no season page scrolls inside a
   card, and nothing scrolls sideways. The page scrolls vertically; a wide table condenses
   into one row per item holding the two or three numbers that matter, and
   the rest folds under the row behind a tap (`foldableRows` in app.js).
   Desktop is untouched: every rule here is inside the media query.
   ========================================================================== */
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  /* the card is the scroll surface's parent, not a scroller of its own:
     never the sideways box a wide desktop table gets (B32), even for the
     moment before `fitTableWraps` has seen the phone width */
  .table-wrap, .table-wrap.tw-wide { overflow: visible; }
  /* 20px of card padding a side is 40px of a 358px shell */
  #standings .set-card, #waivers .set-card, #activity .set-card,
  #rosters .set-card, #my-team .set-card, #matchup .set-card,
  #trades .set-card, #outlook .set-card, #injury-report .set-card { padding: var(--sp-4); }

  /* --- the fold: a table row becomes a wrapping flex line -------------- */
  .st-table, .st-table thead, .st-table tbody { display: block; }
  .st-table tr {
    display: flex; flex-wrap: wrap; align-items: center;
    border-bottom: 1px solid var(--border-1);
  }
  .st-table th, .st-table td {
    border-bottom: 0; padding: var(--sp-2) var(--sp-1);
  }
  /* the top rule is an inset shadow, not a border, so my row is exactly
     as tall as every other row */
  .st-table tr.me-row {
    background: var(--glass-2);
    box-shadow: inset 0 1px 0 var(--border-accent);
    border-bottom: 1px solid var(--border-accent);
  }
  .st-table tr.me-row td { background: none; border-top: 0; border-bottom: 0; }
  /* the whole row is the tap target; the button is its keyboard and
     screen-reader handle, kept quiet so twelve of them do not shout */
  .st-table .st-toggle {
    display: flex; align-items: center; justify-content: flex-end;
    flex: 0 0 40px; order: 1; padding: 0; align-self: stretch;
  }
  .st-table .st-toggle button {
    width: 32px; height: 32px; min-width: 32px; color: var(--fg-2);
  }
  /* folded columns -- every cell carrying a `data-label` -- are hidden
     until the row is opened, then sit three to a line under it, each with
     its own label above the number. Each table hides its own folded <th>s
     below. */
  .st-table td[data-label] { display: none; order: 2; }
  .st-table tr.fold-open td[data-label] {
    display: block; flex: 0 0 33.333%; min-width: 0; text-align: left;
    background: var(--glass-1); padding: var(--sp-2);
  }
  .st-table tr.fold-open td[data-label]::before {
    content: attr(data-label); display: block;
    font: var(--text-label); color: var(--fg-3);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .st-table tbody tr { cursor: pointer; }
  /* `display:flex` above outranks the UA's [hidden] rule */
  .st-table tr[hidden] { display: none; }

  /* the identity cell takes whatever the row leaves it */
  .season-table .s-names { max-width: none; }

  /* --- waivers: two lines a player -------------------------------------
       who               | proj | net
       drop . why        |  Copy claim
     (overall rank and "need" fold away with the rest.)
     A grid, not the flex fold: the player cell's two lines become grid
     items of their own (`display: contents`) so the Copy claim button can
     sit on the second line beside them. Fixed tracks, because every <tr>
     is its own grid and only fixed tracks line the columns up row to row.
     The folded numbers and the "why" open together in the detail row
     under it (`renderWaiverList`, the fold callback). Columns are picked
     by class, not position: the Sleeper column is only there when Sleeper
     has numbers (B16). */
  .wv-table tr { display: grid; column-gap: 0; align-items: center;
                 grid-template-columns: minmax(0, 1fr) 60px 60px; }
  .wv-table thead tr { grid-template-areas: "who proj net"; }
  .wv-table tbody tr:not(.wv-why-row) {
    grid-template-areas: "who proj net" "sub act act";
  }
  /* the overall rank, the need chip and every other number's header fold
     away: the list is already in net-value order */
  .wv-table th:not(.wv-c-player, .wv-c-proj, .wv-c-net),
  .wv-table td.wv-need { display: none; }
  .wv-table th { letter-spacing: normal; }
  .wv-table th.wv-c-player, .wv-who { grid-area: who; }
  .wv-table td.wv-player { display: contents; }
  .wv-table .wv-sub { grid-area: sub; }
  .wv-table .wv-c-proj { grid-area: proj; white-space: normal; }
  .wv-table .wv-c-net { grid-area: net; }
  .wv-table td.wv-act { grid-area: act; justify-self: end; }
  .wv-table td[data-label],
  .wv-table tr.fold-open td[data-label] { display: none; }
  /* the net figure over its stash / one-week tag, not beside it */
  .wv-table td.wv-c-net .filler-tag { display: block; margin: 0; }
  .wv-table .wv-c-player { min-width: 0; }
  .wv-table .wv-who, .wv-table .wv-sub { padding: 0 var(--sp-1); }
  .wv-table .wv-who { padding-top: var(--sp-2); }
  .wv-table .wv-sub { padding-bottom: var(--sp-2); }


  /* the detail row: the folded numbers, then the "why" as prose -- the
     body face at reading size, wrapping, never a sideways scroll */
  .wv-table tr.wv-why-row { display: block; }
  .wv-table tr.wv-why-row[hidden] { display: none; }
  .wv-table tr.wv-why-row > td { display: block; padding: var(--sp-2) var(--sp-3); }
  .wv-why, .card-wv-why {
    font: var(--text-small); color: var(--fg-2); white-space: normal;
    overflow-wrap: anywhere; padding-left: var(--sp-4);
  }
  .wv-more {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--sp-2); margin: 0 0 var(--sp-2);
  }
  .wv-more dt { font: var(--text-label); color: var(--fg-3); }
  .wv-more dd { margin: 0; font: 13px var(--font-mono); color: var(--fg-1);
                font-variant-numeric: tabular-nums; }

  /* not rated by the model: player | sleeper | trend, then bid + claim.
     By class, not position: the Sleeper column is only there when Sleeper
     has numbers (B16). The player cell's basis leaves exactly the number
     columns' width on the first line, which is what pushes bid + claim
     onto the second. */
  .wv-un-table .wv-noproj { display: none; }
  .wv-un-table :is(th, td):nth-child(1) {
    flex: 1 1 calc(100% - 112px); min-width: 0;
  }
  .wv-un-table:not(:has(th.wv-un-num + th.wv-un-num))
    :is(th, td):nth-child(1) { flex-basis: calc(100% - 56px); }
  .wv-un-table :is(th, td).wv-un-num { flex: 0 0 56px; }
  .wv-un-table th.wv-bid, .wv-un-table th.wv-act { display: none; }
  .wv-un-table td.wv-bid { flex: 1 1 auto; text-align: left; }
  .wv-un-table td.wv-act { flex: 0 0 auto; }
  .wv-un-table tbody tr { cursor: auto; }

  /* --- activity ---------------------------------------------------------
     Filters: Team and Type share a line, Type as a select. Moves stack (the
     `@container season` rule in section 7); the FAAB card is the fold
     pattern: team | left | won bids on the row, and Claims, Traded, Started
     (and an adjustment) under it behind a tap. Nothing scrolls sideways or
     inside its own box, and the page just grows (B32). */
  .act-filters { gap: var(--sp-3); }
  .act-filters .act-field { flex: 1 1 0; min-width: 0; }
  .act-filters select { width: 100%; min-width: 0; }
  .act-type-field { display: none; }
  .act-type-select { display: flex; }
  .act-move { padding: var(--sp-3) var(--sp-2); }
  .act-ledger th.act-fold { display: none; }
  /* the name's basis is what the row leaves it, so the folded numbers wrap
     to a line of their own when the row opens instead of squeezing the name
     to nothing (Standings does the same sum with its own widths) */
  .act-ledger tr > :nth-child(1) {
    flex: 1 1 calc(100% - 4rem - 4.75rem - 40px); min-width: 0;
  }
  .act-ledger tr > :nth-child(2) { flex: 0 0 4rem; }
  .act-ledger tr > :nth-child(3) { flex: 0 0 4.75rem; }
  .act-bar { width: 3.25rem; }
  /* the pill and the dot before "You" would squeeze the name: the
     adjustment is a folded number, and the word says who you are */
  .act-badge, .act-id .act-you > [aria-hidden] { display: none; }
  .act-who { width: auto; max-width: none; }

  /* rosters: other teams' cards fold to their heading (renderRosters) */
  .team-card > h2 { cursor: pointer; }
  .team-card .tc-title { flex: 1; min-width: 0; }
  .tc-toggle { display: inline-flex; flex-shrink: 0; width: 32px;
               height: 32px; min-width: 32px; color: var(--fg-2); }
  .team-card:not(.tc-open) .tc-rows { display: none; }
  .team-card:not(.tc-open) > .s-sub { margin-bottom: 0; }
  /* the slot tag column: 52px holds "FLEX" with room to spare */
  .tslot-slot { width: 40px; }
}

/* Below ~380px (small Android, iPhone SE) the avatar chip costs the names
   34px they need -- "kwabena.o..." at 360 -- and the chip only repeats the
   name's first letter, so it steps aside. */
@media (max-width: 380px) {
  .season-table .s-id > .savatar { display: none; }
}

/* A phone on its side has the width for three more standings columns, so
   points against, playoff odds and today's seed come back onto the row
   (the rest still fold). Portrait keeps # / team / record / PF. */
@media (pointer: coarse) and (max-height: 500px) and (min-width: 601px) {
  #standings .st-table :is(th, td):nth-child(2) {
    flex-basis: calc(100% - 204px - 3 * 64px);
  }
  /* sorted by a folded column: the same trade as portrait (MAJ1) */
  #standings .st-table.st-sort-folded :is(th, td):nth-child(2) {
    flex-basis: calc(100% - 236px - 3 * 64px);
  }
  #standings .st-table th.st-wide:not(.st-toggle),
  #standings .st-table td.st-wide,
  #standings .st-table tr.fold-open td.st-wide {
    display: block; order: 0; flex: 0 0 64px; text-align: right;
    padding: var(--sp-2) var(--sp-1);
  }
  #standings .st-table tr.fold-open td.st-wide { background: none; }
  #standings .st-table tr.fold-open td.st-wide::before { content: none; }
}

/* --- the war room on a phone ---------------------------------------------
   The board pane is `overflow: hidden` (it fits whole rows, D8), so at 390
   every column past PROJ was cut off and unreachable, and the position
   filter scrolled sideways with RB..DEF out of sight. On a phone: the strip
   wraps, the running row index goes (the overall rank beside it says the
   same thing), and cells give up most of their 28px of side padding, which
   is what lets the value columns fit on the screen. Vertical padding is
   untouched so the head stays exactly --head-band. */
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  #main { padding: var(--sp-4); }
  /* the war room scrolls as a page on a phone, so the lockup scrolls with
     it: kept `fixed` (the 641-1150 rule, where it rides in the sticky
     topbar's reserved corner) it painted over the roster and scarcity
     panes all the way down. The topbar is ~350px tall here, so it is not
     sticky either -- a third of the screen would be chrome. */
  body.draft-locked #brand-slot { position: absolute; }
  #topbar { position: relative; }
  /* B19 in the war room: the info panes stop being 40vh scroll boxes
     inside a scrolling page and simply grow */
  #my-roster, #scarcity-cards, #history, #wait-chart { max-height: none; }
  #board-controls { flex-wrap: wrap; overflow-x: visible; row-gap: var(--sp-2); }
  #board-controls .ctl-search { flex: 1 1 100%; }
  #board-table th, #board-table td {
    padding-left: var(--sp-1); padding-right: var(--sp-1);
  }
  /* the headers set these columns' widths, and .1em of tracking on "COMP"
     was the last 6px the row did not have */
  #board-table th { letter-spacing: normal; }
  #board-table :is(th, td):first-child { display: none; }
  /* the name gives way last, and only as far as the screen demands: the
     rest of the row (rank, position, four numbers, badges) is ~250px, so
     the name gets whatever is left and ellipsises past it (B9: the full
     name is in its title) */
  #board-table .player-name {
    display: inline-block; vertical-align: bottom;
    max-width: calc(100vw - 15rem);
    overflow: hidden; text-overflow: ellipsis;
  }
  #board-table .pos { min-width: 0; }
  /* ADP is already inside the composite beside it, and the player card
     carries it; at 360 the row cannot hold both and a readable name */
  #board-table th[data-key="adp"], #board-table td.c-adp { display: none; }
}

/* --- the dynamic viewport (100vh on a phone) ------------------------------
   On iOS Safari and Chrome for Android `vh` is the LARGE viewport -- the
   height with the browser's toolbars retracted -- so a `90vh` dialog ran
   its bottom edge (Close, Copy) under the toolbar, and a `100vh` hub or
   locked war room was taller than the screen it was on. `dvh` is the
   height actually visible. Declared after the `vh` rules and inside
   @supports so a browser without it keeps the `vh` value. */
@supports (height: 100dvh) {
  #home, #boot-error, #draft-choice { min-height: 100dvh; }
  body.draft-locked, body.draft-locked #draft { height: 100dvh; }
  #report-box, #report-aside { max-height: 90dvh; }
  #modal-box { max-height: 84dvh; }
  #player-box { max-height: 88dvh; }
}
@supports (height: 100dvh) {
  @media (max-width: 1020px) { #report-layout { max-height: 92dvh; } }
}

/* --- overlays on a phone ---------------------------------------------------
   The player card reserved 128px on the right of its hero for a team-logo
   watermark and kept the name on one nowrap line, so at 390 "Cooper
   Nakamura" ran under the close button. The confirm bar is a single row
   (name, team select, Draft, Cancel) that squeezed the name into three
   lines, and the toast -- also fixed to the bottom -- painted over it. */
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  /* the reserve is the close button plus a gap, not a fixed 48px: on a
     touch screen the button is 44px (`--ctl-h`), and a long name ("Bartholomew
     Christopher ...") ran under it (B50 g); the italic name overhangs its
     box by 0.24em and its skew leans further right at the top of a line */
  .card-hero-main { padding-right: calc(var(--ctl-h) + var(--sp-5)); }
  .card-name { white-space: normal; }
  .card-chips { flex-wrap: wrap; }
  .card-chips .chip { flex: 1 1 calc(50% - var(--sp-2)); }
  /* Season History folds (B50 g, H10): season, games, points, points per
     game and finish stay on the row; the team and every stat column
     (`.cs-fold`, cells carrying `data-label`) open under it on a tap, by
     the shared `.st-table` fold above. The visible cells' bases are
     percentages that add up to exactly 100%, so the opened cells (a third
     of the row each) always start a new line and nothing can push the
     toggle down or the row sideways; a header too long for a narrow phone
     ellipsises, its full text in `title`. */
  .card-table th.cs-fold { display: none; }
  .card-table :is(th, td) { min-width: 0; }
  .card-table thead th {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .card-table :is(th, td):nth-child(1) { flex: 0 0 23%; }
  .card-table :is(th, td):nth-child(3) { flex: 0 0 9%; }
  .card-table :is(th, td):nth-last-child(4),
  .card-table :is(th, td):nth-last-child(3) { flex: 0 0 17%; }
  .card-table :is(th, td):nth-last-child(2) { flex: 0 0 20%; }
  .card-table :is(th, td):nth-last-child(1) { flex: 0 0 14%; }
  #confirm {
    left: var(--sp-4); right: var(--sp-4); transform: none;
    bottom: max(var(--sp-4), env(safe-area-inset-bottom));
    flex-wrap: wrap; gap: var(--sp-2) var(--sp-3);
  }
  #confirm-player { flex: 1 1 100%; }
  #confirm-yes { margin-left: auto; }
  #toast {
    left: var(--sp-4); right: var(--sp-4); max-width: none;
    bottom: max(var(--sp-4), env(safe-area-inset-bottom));
  }
  /* the toast stacks above the confirm bar while it is up (the bar is
     two rows here: the player, then the team select and its buttons) */
  body:has(#confirm:not([hidden])) #toast {
    bottom: calc(max(var(--sp-4), env(safe-area-inset-bottom)) + 7.5rem);
  }
}

/* --- touch: controls a thumb can hit, fields iOS will not zoom into ------
   Coarse pointers only (phones and tablets), so a desktop keeps D22's 34px
   geometry exactly. 44px is Apple's minimum touch target; the segment size
   moves with it so a .btn-group still measures exactly --ctl-h (34 + 2 x
   --sp-1 + the 2px hairline). iOS Safari zooms the page into any field set
   below 16px when it takes focus, and does not zoom back out -- every
   settings field, the war room's search and the trade evaluator did it. */
@media (pointer: coarse) {
  :root { --ctl-h: 44px; --ctl-h-sm: 34px; }
  /* !important: fields take their type from `--ctl-font` and per-screen
     `font:` shorthands with id and class specificity; this one property
     has to beat all of them on a touch screen, and only this one */
  input, select, textarea { font-size: 16px !important; }
  /* a player name is a link: pad its hit area to 32px without moving the
     line (padding on an inline box, cancelled by the margin), and draw the
     dotted affordance as an underline so the padding does not push it */
  .pname {
    padding-block: var(--sp-2); margin-block: calc(-1 * var(--sp-2));
    border-bottom: 0; text-decoration: underline dotted var(--fg-4);
    text-underline-offset: 3px;
  }
  /* the war room's wait-chart dots are 10px marks: paint 10px, hit 26px.
     A transparent pseudo-element carries the hit area -- padding with
     `background-clip` does not survive the `.safe/.risky/.gone` rules,
     whose `background` shorthand resets the clip and painted 26px dots */
  #wait-chart .wait-dot { position: relative; }
  #wait-chart .wait-dot::before {
    content: ""; position: absolute; inset: calc(-1 * var(--sp-2));
  }
  /* the dots' own padding above must not reach them: `.pname` padding is
     for text links */
  #wait-chart .wait-dot.pname { padding: 0; margin: 0; }
}

/* --- outlook on a phone (B19) ---------------------------------------------
   The weeks grid scrolled both ways inside a 70vh box, and every name
   wrapped two lines with its position under it. On a phone each player is
   a block: the name line, then his weeks five to a line, each labelled --
   the page scrolls, nothing scrolls sideways. */
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  .ol-table, .ol-table tbody { display: block; }
  .ol-table thead { display: none; }
  .ol-table tr {
    display: flex; flex-wrap: wrap; padding: var(--sp-2) 0;
    border-bottom: 1px solid var(--border-1);
  }
  .ol-table td { border-bottom: 0; padding: var(--sp-1); }
  .ol-table td.ol-name { flex: 1 1 100%; }
  .ol-table td.ol-cell { flex: 0 0 20%; min-width: 0; text-align: left; }
  .ol-table td.ol-cell::before {
    content: attr(data-week); display: block;
    font: var(--text-label); color: var(--fg-3);
  }
}

/* --- settings tabs on a phone ---------------------------------------------
   Six tabs do not fit one line at 390; the rail wraps (and must not scroll
   sideways, B19), and a pill-radius rail around two rows read as a
   lozenge with "Deploy" stranded in its lower corner. A wrapped rail takes
   the card radius instead. */
@media (max-width: 600px), (pointer: coarse) and (max-height: 500px) {
  #settings-tabs { border-radius: var(--r-lg); }
}

