/* app.css — the UI's styles. Moved verbatim out of the old src/server/page.js template string
   (2026-08-22), with the handful of inline `style="..."` attributes turned into classes so a
   Content-Security-Policy can forbid inline styles outright rather than allowing 'unsafe-inline'.
   See src/server/static.js for how this is served. */
/* ⚠ THE PALETTE, THE DOCUMENT BASE AND THE SITE CHROME MOVED TO /base.css ON 2026-09-07, when
   this file moved under /football-exchange/ and the legal pages could no longer depend on a game’s
   stylesheet. base.css loads FIRST; everything below layers the trading UI on top of it and is
   deliberately about markets, boards, portfolios and trades — nothing else. */



/* ⚠ MUST OUTRANK EVERY `display:` RULE BELOW. The `hidden` attribute is only a UA default of
   `display:none`, so any class that sets its own display — .gate, .cards, .qty, .toolbar — silently
   beats it and the element stays on screen. That is not theoretical: it shipped the sign-in card
   and the join card stacked on top of each other. Every show/hide in app.js sets `.hidden`, so this
   one rule is what makes all of them mean anything. */
[hidden] { display:none !important; }


/* The Fuzzy Sports wordmark is the way BACK OUT of this game to the landing page. It is a real
   link rather than a logo-shaped decoration, because it is the only route from a game to its
   siblings and a user who cannot find it is stuck in whichever game they opened first. */
header h1 .wordmark { color:var(--dim); text-decoration:none; font-weight:700; }
header h1 .wordmark:hover { color:var(--accent); }
.spacer { flex:1; }
.cash { font-variant-numeric:tabular-nums; }
.cash b { color:var(--up); }

select, button, input {
  font:inherit; color:var(--ink); background:var(--field);
  border:1px solid var(--line); border-radius:6px; padding:6px 9px;
}
button { cursor:pointer; }
button:hover:not(:disabled) { border-color:var(--accent); }
button.buy { color:var(--up); }
button.sell { color:var(--down); }
button:disabled { opacity:.35; cursor:default; border-color:var(--line); }

/* The menus (owner, 2026-09-22): Exchange, League, Team. Each button drops a list over the page;
   the menu holding the current page is underlined, and the line under them names the page. */
nav.menus { display:flex; gap:4px; padding:10px 18px 0; flex-wrap:wrap; }
.menu { position:relative; }
.menu-btn {
  background:transparent; border:none; border-bottom:2px solid transparent;
  border-radius:0; padding:8px 12px; color:var(--dim);
}
.menu-btn::after { content:" ▾"; font-size:11px; }
.menu-btn.active { color:var(--ink); border-bottom-color:var(--accent); }
.menu-btn[aria-expanded="true"] { color:var(--ink); }
.menu-list {
  position:absolute; top:100%; left:0; z-index:30; margin:4px 0 0; padding:6px 0;
  list-style:none; min-width:220px; max-width:calc(100vw - 24px);
  background:var(--panel); border:1px solid var(--line); border-radius:10px;
  box-shadow:0 8px 24px var(--shadow);
}
/* ⚠ SCOPED TO THE NAVIGATION (2026-09-23). This keeps the LAST menu in the bar — NFL — from
   opening off the right of the screen. Unscoped it also caught the Theme menu, which is an only
   child and therefore a last child, and right-aligning a list to a button at the left margin puts
   the list off the left edge of the page. */
nav.menus .menu:last-child .menu-list { left:auto; right:0; }
.menu-list ul { list-style:none; margin:0; padding:0; }
.menu-list button, .menu-list a {
  display:block; width:100%; text-align:left; background:none; border:none; border-radius:0;
  padding:9px 14px; color:var(--ink); text-decoration:none; font:inherit;
}
.menu-list button:hover, .menu-list a:hover { background:var(--line); }
.menu-list [aria-current] { color:var(--accent); font-weight:600; }
.menu-label { padding:6px 14px 2px; font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:var(--dim); }
.menu-sep { border-top:1px solid var(--line); margin:6px 0; }
.where { margin:6px 18px 0; font-size:12px; color:var(--dim); }

/* The NFL pages (2026-09-22): a team picker over a plain table; depth charts are one table per
   formation, headed by the formation's name. */
.nfl-bar label { color:var(--dim); font-size:13px; }
.nfl-grp { margin:18px 0 6px; font-size:14px; color:var(--dim); font-weight:600; }
.nfl-table tr.nfl-bye td { color:var(--dim); }

/* The viewer's own league in the power ranking — marked, not coloured in. */
.lb-table tr.lb-mine td { color:var(--ink); font-weight:600; }

/* Removing a league sits apart from the invite tools above it. */
#lpDelete { margin-top:6px; }

/* The public list asks before it seats you (owner, 2026-09-23). */
.join-row { margin-bottom:6px; }
.join-confirm { border:1px solid var(--line); border-radius:8px; padding:10px 12px; margin:6px 0 10px; background:var(--panel); }
.join-confirm p { margin:0 0 8px; font-size:13px; color:var(--dim); }
.join-confirm .join-go { border-color:var(--accent); color:var(--accent); }

main { padding:14px 18px 40px; }
.toolbar { display:flex; gap:10px; align-items:center; margin-bottom:12px; flex-wrap:wrap; }
.search { min-width:180px; }
/* ⚠ THE SHARE CHOICE (2026-09-24): an in-page panel fixed to the bottom of the screen, so the
   table can redraw under it and a phone shows it without anything to allow. Not a modal: the page
   stays usable, and pressing another Buy or Sell simply replaces what it asks. */
.tradepick {
  position:fixed; left:50%; bottom:16px; transform:translateX(-50%); z-index:50;
  width:min(420px, calc(100vw - 32px)); padding:14px 16px;
  background:var(--panel); border:1px solid var(--accent); border-radius:10px;
  box-shadow:0 8px 28px rgba(0,0,0,.35);
}
.tradepick p { margin:0 0 4px; }
.tradepick-sizes { display:flex; gap:8px; margin:10px 0 8px; flex-wrap:wrap; }
.tradepick-sizes button { flex:1 1 0; min-width:64px; padding:10px 0; font-size:15px; font-variant-numeric:tabular-nums; }
/* The slot a size would go in, when the sizes do not all go to the same one. */
.tradepick-sizes button small { display:block; font-size:11px; color:var(--dim); margin-top:2px; }
/* The kickoff clock (owner, 2026-09-24): the top of League Home and Team Home. */
.kickoff-clock {
  display:flex; align-items:baseline; gap:12px; flex-wrap:wrap;
  margin:0 0 14px; padding:10px 14px; border-radius:8px;
  border:1px solid var(--line); border-left:4px solid var(--accent); background:var(--panel);
}
.kickoff-clock .kc-label { font-size:13px; color:var(--dim); }
.kickoff-clock .kc-time { font-size:22px; font-weight:700; font-variant-numeric:tabular-nums; }
.kickoff-clock .kc-time small { font-size:12px; font-weight:500; color:var(--dim); margin-left:1px; }
.kickoff-clock .kc-when { font-size:13px; color:var(--dim); }
.kickoff-clock.is-record { border-left-color:var(--dim); }
.kickoff-clock.is-record .kc-when { flex-basis:100%; }
/* Transaction Cost under a buy size (2026-09-24): the money, so in the ink colour, not dimmed. */
.tradepick-sizes button small.tp-cost { color:var(--ink); font-size:12px; }
.tradepick-price { margin:6px 0 0 !important; font-size:13px; }
/* Every Buy/Sell pair, on all three pages. */
td.acts { white-space:nowrap; }
td.acts button + button { margin-left:6px; }

.count { color:var(--dim); font-size:12px; margin:0 0 8px; }

/* A wide table must scroll INSIDE its own box; the page body never scrolls sideways. */
.tablewrap { overflow-x:auto; -webkit-overflow-scrolling:touch; }
table { width:100%; border-collapse:collapse; font-variant-numeric:tabular-nums; }
th,td { text-align:right; padding:7px 10px; border-bottom:1px solid var(--line); white-space:nowrap; }
th:first-child, td:first-child, .l { text-align:left; }
th { color:var(--dim); font-weight:550; font-size:12px; position:sticky; top:0; background:var(--bg); }
tbody tr:hover { background:var(--hover); }

/* Sortable headers are real buttons — keyboard reachable, and announced as controls. */
.sortbtn {
  background:none; border:none; padding:0; color:inherit; font:inherit;
  cursor:pointer; border-radius:3px;
}
.sortbtn:hover { color:var(--ink); }
.sortbtn::after { content:''; opacity:.45; margin-left:5px; }
th[aria-sort=ascending]  .sortbtn::after { content:'▲'; opacity:1; }
th[aria-sort=descending] .sortbtn::after { content:'▼'; opacity:1; }
th[aria-sort=ascending]  .sortbtn, th[aria-sort=descending] .sortbtn { color:var(--accent); }

.pos { color:var(--dim); font-size:12px; }
/* Team Home's Slot: the part that is the player's own position in full ink, the rest dim. */
td.slot b { color:var(--ink); font-weight:650; }

/* ⚠ THE AVAILABILITY SUPERSCRIPT — the one thing on the board that says a listing is not a player
   you can field this week. The owner's ruling (2026-09-15) is specific about its form: smaller
   than the name, RED, and to the right of the NAME rather than beside the club. About 10% of
   listings carry one (52 of 502 non-defence rows, measured 2026-09-19), so it has to read at a
   glance without shouting over the 450 ordinary rows it sits among — hence a superscript and not
   a bordered badge, and nothing at all for an active player.
   ⚠ ONE COLOUR, NOT THREE. An earlier draft graded it — amber for "may yet play", red for
   "released", faint grey for "not checked". The ruling collapses all of that: a man on no roster
   reads FA in the TEAM column with no mark at all, and "not checked" shows his preseason state
   rather than a marker of its own. What is left is a single class. */
.avail {
  margin-left:3px; font-size:9px; font-weight:700; letter-spacing:.3px;
  color:var(--down); white-space:nowrap;
}
.me { color:var(--accent); }
/* Gain/loss. ⚠ The sign is written into the TEXT as well (see app.js): colour alone fails anyone
   with a colour vision deficiency, and red/green is the single worst pair for exactly that. */
.up { color:var(--up); }
.down { color:var(--down); }

.cards { display:flex; gap:14px; flex-wrap:wrap; margin-bottom:16px; }
.card {
  background:var(--panel); border:1px solid var(--line);
  border-radius:10px; padding:12px 16px; min-width:150px; flex:1 1 150px;
}
.card .k { font-size:11px; text-transform:uppercase; letter-spacing:.5px; color:var(--dim); }
.card .v { font-size:20px; font-variant-numeric:tabular-nums; margin-top:3px; }

.note { color:var(--dim); font-size:13px; padding:20px 0; }
.subhead { font-size:13px; font-weight:600; color:var(--dim); margin:26px 0 10px; }

/* Bye tracker: a group is either satisfied or it is not, and that is the whole point of the view. */
.ok-pill, .risk-pill {
  display:inline-block; font-size:11px; padding:1px 7px; border-radius:999px;
  border:1px solid currentColor;
}
.ok-pill { color:var(--up); }
.risk-pill { color:var(--down); }
tr.satisfied td:first-child { color:var(--up); }
.prelaunch-note {
  color:var(--dim); font-size:12.5px; line-height:1.5; background:var(--panel);
  border:1px solid var(--line); border-left:3px solid var(--accent);
  border-radius:8px; padding:10px 14px; margin-bottom:14px;
}
.prelaunch-note b { color:var(--ink); font-weight:600; }

/* --- loading ---------------------------------------------------------------------------------
   A table mid-refresh is dimmed rather than emptied: blanking it makes the page jump and loses
   the reader's place, and for a refresh that takes 80ms a spinner is worse than nothing. */
.loading tbody { opacity:.45; transition:opacity .12s; }
.count.busy::after { content:' · loading…'; }

/* --- error banner ----------------------------------------------------------------------------
   The old client did `(await fetch(p)).json()` — a 500 or a dropped connection threw, the promise
   was unhandled, and the UI simply stopped with no indication that anything had happened. */
.banner {
  display:flex; gap:12px; align-items:center; justify-content:center; flex-wrap:wrap;
  background:var(--alert-bg); border-bottom:1px solid var(--down); color:var(--alert-ink);
  padding:9px 18px; font-size:13px;
}
.banner button { background:transparent; border-color:var(--down); color:var(--alert-ink); }

/* --- sign-in / join gates ------------------------------------------------------------------ */
.gate { display:flex; justify-content:center; padding:64px 18px; }
.gate-card {
  background:var(--panel); border:1px solid var(--line); border-radius:12px;
  padding:26px 28px; width:100%; max-width:380px;
}
.gate-card h2 { margin:0 0 6px; font-size:17px; font-weight:650; }
.gate-card p { margin:0 0 16px; font-size:13px; line-height:1.5; }
.gate-card form { display:flex; gap:8px; flex-wrap:wrap; }
.gate-card input[type=email] { flex:1; min-width:190px; }
.gate-msg { margin:14px 0 0 !important; font-size:13px; color:var(--dim); }
.gate-msg.err { color:var(--down); }
.gate-msg.good { color:var(--up); }
.gate-or { margin:20px 0 10px !important; font-size:12px; }
/* A failed sign-in link: the first thing on the card, in the loss colour, big enough to read first. */
.link-problem {
  margin:0 0 20px; padding:12px 14px; border-radius:8px;
  border:1px solid var(--down); border-left-width:4px;
  background:color-mix(in srgb, var(--down) 10%, var(--panel));
}
.gate-card .link-problem p { margin:0; font-size:14px; }
.gate-card .link-problem .link-problem-head { margin:0 0 6px; font-size:15px; font-weight:650; color:var(--down); }
/* A new discussion topic, above the team list: noticeable, not alarming — the interactive colour. */
.board-news {
  display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap; width:100%;
  margin:0 0 14px; padding:10px 14px; border-radius:8px; text-align:left;
  border:1px solid color-mix(in srgb, var(--accent) 45%, var(--line)); border-left:4px solid var(--accent);
  background:color-mix(in srgb, var(--accent) 8%, var(--panel)); color:var(--ink); font:inherit; cursor:pointer;
}
.board-news:hover { background:color-mix(in srgb, var(--accent) 14%, var(--panel)); }
.board-news b { color:var(--accent); }
.demo-users { display:flex; gap:6px; flex-wrap:wrap; }

.flash {
  position:fixed; bottom:16px; left:50%; transform:translateX(-50%);
  background:var(--panel); border:1px solid var(--line); border-radius:8px;
  padding:9px 16px; opacity:0; transition:opacity .2s; pointer-events:none;
}
.flash.show { opacity:1; }
.flash.err { border-color:var(--down); color:var(--down); }

/* --- narrow screens --------------------------------------------------------------------------
   ⚠ EVERY COLUMN ON EVERY SCREEN (owner, 2026-09-24). Phones used to drop the secondary columns so
   the Buy button would not need a sideways scroll — but the tables scrolled sideways anyway, so a
   phone got less information and the scroll as well. Now each table scrolls inside its own box
   (.tablewrap; the page body never does), and the Player column stays pinned at the left while
   it does, so a row never loses its name. */
@media (max-width: 720px) {
  main { padding:12px 12px 40px; }
  header, nav { padding-left:12px; padding-right:12px; }
  .where { margin-left:12px; margin-right:12px; }
  header { gap:10px; }
  header h1 { font-size:14px; }
  #teamTable th, #teamTable td { padding-left:4px; padding-right:4px; }
  #teamTable td { font-size:13px; }
  th, td { padding:9px 8px; }          /* taller rows: touch targets, not mouse targets */
  /* The Player column pinned while the table scrolls sideways; its own background so the columns
     sliding under it do not show through. */
  #mktTable th:first-child, #mktTable td:first-child,
  #pfTable th:first-child, #pfTable td:first-child,
  #scTable th:first-child, #scTable td:first-child {
    position:sticky; left:0; z-index:1; background:var(--bg);
    box-shadow:1px 0 0 var(--line);
  }
  #mktTable thead th:first-child, #pfTable thead th:first-child, #scTable thead th:first-child { z-index:2; }
  /* Standings: Rank and Team both pinned (owner, 2026-09-24 late) — a row of numbers means nothing
     once the team's name has scrolled away. Rank has a fixed width so Team knows where to sit. */
  .st-table th:nth-child(1), .st-table td:nth-child(1) {
    position:sticky; left:0; z-index:1; background:var(--bg); width:34px; min-width:34px; max-width:34px;
  }
  .st-table th:nth-child(2), .st-table td:nth-child(2) {
    position:sticky; left:34px; z-index:1; background:var(--bg); box-shadow:1px 0 0 var(--line);
    white-space:normal; min-width:110px;
  }
  .st-table thead th:nth-child(1), .st-table thead th:nth-child(2) { z-index:2; }
  .search { min-width:0; flex:1 1 100%; }
  .toolbar { gap:8px; }
  .card { min-width:0; flex:1 1 44%; }
  .gate { padding:28px 12px; }
}
@media (max-width:640px) {
  /* Buy over Sell on a narrow team row, so a long name keeps its width. */
  #teamTable td.acts button { display:block; width:100%; padding:4px 5px; }
  #teamTable td.acts button + button { margin-left:0; margin-top:4px; }
}

@media (max-width: 420px) {
  header .phase { display:none; }
}

/* ⚠ THE PHONE HEADER IS TWO FIXED ROWS (GitHub issue #1, 2026-09-25). Left to wrap on its own it
   took two or three rows depending on the league name's length, and Sign out landed wherever it
   fell. Now: row 1 the wordmark with Sign out at the right; row 2 the league name, My leagues, and
   Cash at the right. The ::after is a zero-height, full-width flex item that forces the break —
   flex `order` applies to it like any child. The league name gives way (ellipsis, full name in its
   title) so row 2 never wraps. ⚠ ITS CAP IS A WIDTH, NOT flex-shrink: a wrapping flex line moves an
   item to the next row before it shrinks anything, so shrink alone put Cash on a third row. 230px
   is the rest of row 2 — gutters 24, three gaps 30, My leagues ~70, Cash ~100. The phase pill
   (421–720px only) sits on row 1 so it takes nothing from row 2. The wordmark may wrap inside
   itself on a 320px screen. */
@media (max-width: 720px) {
  header { row-gap:4px; column-gap:10px; }
  header h1 { order:1; flex:1 1 0; min-width:0; }
  header .phase { order:2; }
  header #logout { order:3; }
  header::after { content:''; order:4; flex-basis:100%; height:0; }
  header #leagueLink {
    order:5; min-width:0; max-width:calc(100vw - 230px);
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }
  header #myLeagues { order:6; white-space:nowrap; }
  header .spacer { order:7; }
  header #cashWrap { order:8; white-space:nowrap; }
  header #asLabel, header #user { order:9; }     /* the demo's switcher; production never has it */
}
/* The four menus under it on one line at 320px too: at 12px a side, NFL wrapped to a row of its own. */
@media (max-width: 360px) {
  .menu-btn { padding-left:7px; padding-right:7px; }
}

/* Someone who has asked the OS for less motion should not be given transitions. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration:.01ms !important; animation-duration:.01ms !important; }
}

/* --- multi-league picker ---------------------------------------------------------------------
   The header switcher only appears for a user with two or more seats (app.js hides it otherwise —
   a select with one option is furniture, not a control). */
header #league { max-width:210px; }
.league-list { display:flex; flex-direction:column; gap:8px; margin:4px 0 0; }
.league-list button { text-align:left; padding:9px 12px; }
.league-list button .muted { font-size:12px; }

/* --- create-a-league form -------------------------------------------------------------------- */
.create-form { display:flex; flex-direction:column; gap:8px; margin-top:4px; }
.create-form input[type=text] { width:100%; }
.create-row { display:flex; gap:10px; flex-wrap:wrap; }
.create-row span { display:flex; flex-direction:column; gap:3px; flex:1 1 90px; min-width:0; }
.create-row label { font-size:11px; text-transform:uppercase; letter-spacing:.5px; color:var(--dim); }
.create-row input, .create-row select { width:100%; }

/* ---- screener ------------------------------------------------------------------------------ */
.chips { display:flex; flex-wrap:wrap; gap:6px; list-style:none; padding:0; margin:0 0 8px; }
.chip {
  display:inline-flex; align-items:center; gap:6px; font-size:12px;
  color:var(--ink); background:var(--panel);
  border:1px solid var(--line); border-radius:999px; padding:3px 6px 3px 10px;
}
.chip button { border:0; background:none; color:var(--dim); padding:0 4px; font-size:14px; line-height:1; }
.chip button:hover { color:var(--down); }

/* ⚠ THE UNTESTABLE LINE IS DELIBERATELY NOT `.muted`, AND THAT IS A CORRECTNESS DECISION RATHER
   than a visual one. On a sleeper screen it routinely reports a LARGER number than the result
   table shows — 42 matched against 445 untestable, measured on the real board — because a player
   who did not play last season has no prior-season stat and a null must never read as a zero. Greyed
   out it would be taken for a disclaimer and skipped, and the short list above it would be read as
   "there are no sleepers" rather than "most of the board could not be checked". It gets the accent
   rule and full-strength text for the same reason the count itself is returned at the top level of
   the API response: it is part of the answer, not a note about it. */
#scUntested {
  color:var(--ink); font-size:12px; line-height:1.5;
  border-left:2px solid var(--accent); background:var(--panel);
  padding:8px 10px; margin:0 0 10px; border-radius:0 6px 6px 0;
}
#scPerCond { color:var(--dim); font-size:11px; margin:8px 0 0; white-space:normal; }

/* --- the cross-league leaderboard ------------------------------------------------------------
   ⚠ THE BOARDS ARE VISUALLY SEPARATED ON PURPOSE. Small and main are separate competitions, each
   with its own rank 1; run together as one long table a reader would take the second board's
   leader for a mid-table finisher. Each gets its own heading and its own denominator. */
.lb-board { margin-bottom:28px; }
.lb-board h3 { font-size:14px; font-weight:600; margin:0 0 8px; }
.lb-board h3 .muted { font-weight:400; font-size:12px; }
.lb-table td:first-child { font-variant-numeric:tabular-nums; }

/* An unranked row is listed, dimmed, and says why — it is NOT a low-scoring row, and must not be
   mistaken for one. See the note above lbRow() in app.js. */
tr.lb-prov td { color:var(--dim); }
.lb-unranked { color:var(--dim); }
.lb-why { font-size:11px; color:var(--dim); border:1px solid var(--line); border-radius:999px; padding:1px 7px; margin-left:6px; }

/* The break between "the top of the board" and "your league" in a collapsed view. Without it the
   two runs read as one continuous ranking, which is exactly what a collapsed view is not. */
tr.lb-gap td { border-top:2px solid var(--line); }

/* --- leaving a league -------------------------------------------------------------------------
   Deliberately quiet until asked for: an action that liquidates a season should not compete for
   attention with the Sell buttons above it. The confirmation, once open, is not quiet at all. */
.leave-zone { margin:34px 0 8px; padding-top:16px; border-top:1px solid var(--line); }
.leave-open { color:var(--dim); }
.leave-open:hover:not(:disabled) { color:var(--down); border-color:var(--down); }
.leave-confirm {
  background:var(--panel); border:1px solid var(--line); border-left:3px solid var(--down);
  border-radius:8px; padding:14px 16px; max-width:640px;
}
.leave-confirm p { margin:0 0 10px; font-size:13px; line-height:1.55; }
.leave-confirm p.muted { font-size:12.5px; }
.leave-confirm b { color:var(--ink); font-weight:600; }
.leave-go { color:var(--down); border-color:var(--down); margin-right:8px; }

/* The way out of the join gate for somebody who already has a seat. Quiet and top-left, because
   it is a back button rather than one of the gate's own actions. */
#joinBack { float:right; margin:-4px 0 0 12px; color:var(--dim); font-size:12px; padding:4px 9px; }

/* --- initiation: the countdown, and the owner's way to stop it --------------------------------
   Deliberately prominent while a league has not started — it is the only thing on the Market tab
   that anyone can act on before kickoff, and the trade buttons above it are dead. */
.initiation {
  background:var(--panel); border:1px solid var(--line); border-left:3px solid var(--accent);
  border-radius:8px; padding:12px 14px; margin:0 0 12px; max-width:760px;
}
.initiation p { margin:0 0 6px; font-size:13px; line-height:1.5; }
.initiation p.muted { font-size:12px; margin-bottom:10px; }
.initiation b { color:var(--ink); font-weight:600; }
/* An active postponement is a state, not an action — it reads back as "on" so an owner can see at
   a glance that they have already asked to skip. */
button.postpone.on { color:var(--accent); border-color:var(--accent); }

/* ---- the roster: the primary view (owner, 2026-09-13) ----------------------------------------
   ⚠ A ROSTER ROW IS ONE POSITION, NOT A SET OF SLOT BOXES. Three WR "slots" are a 300% pool, so the
   row carries one total (1.75 of 3.0) and the players beneath it. Drawing three empty boxes would
   promise a lineup this game does not have.
   ⚠ THE ROW IS A GRID WITH FIXED COLUMNS so that eight positions read as a column of aligned
   numbers rather than eight sentences of different lengths — the page is scanned, not read. */
.teamhead { display:flex; justify-content:space-between; align-items:flex-start; gap:16px; flex-wrap:wrap; margin:18px 0 14px; }
/* ⚠ BLOCK, NOT FLEX-COLUMN. The Edit control belongs on the same line as the name it edits; in a
   flex column every child becomes its own row and the link drops beneath the heading, reading as a
   separate action rather than as part of it. */
.teamnames { display:block; }
.teamname { font-size:24px; margin:0; display:inline; }
.teamnames p { margin:2px 0 0; font-size:13px; }
.teamtotals { font-size:13px; color:var(--dim); text-align:right; }
.teamtotals .big { font-size:20px; color:var(--ink); font-variant-numeric:tabular-nums; }
/* The same budget line above the Market and Portfolio tables: left, like the rest of the toolbar. */
#budgetMkt, #budgetPf { text-align:left; margin:0 0 8px; }

/* ⚠ AN EDIT AFFORDANCE, NOT A BUTTON. These sit beside a heading and must not compete with it; the
   owner asked for an explicit edit control rather than a permanently writeable field, and a quiet
   link is the smallest thing that still says "this is changeable". */
.linkish {
  background:none; border:none; padding:0 0 0 8px; font-size:12px;
  color:var(--dim); text-decoration:underline; cursor:pointer;
}
.linkish:hover { color:var(--accent); border-color:transparent; }

/* The rename field under the team heading. The input shrinks rather than pushing Save off a phone. */
.nameedit { margin:10px 0 0; max-width:420px; }
.nameedit label { display:block; font-size:12px; color:var(--dim); margin-bottom:4px; }
.nameedit-row { display:flex; gap:8px; align-items:center; }
.nameedit-row input { flex:1 1 auto; min-width:0; }
.nameedit .gate-msg { margin:6px 0 0 !important; }

/* ---- the market's position strip -------------------------------------------------------------
   ⚠ ABBREVIATED ON PURPOSE. Eight positions, a quantity and a spend each, on one line: this is a
   glance while shopping, not a second copy of the roster. It wraps rather than scrolling, because a
   number that has scrolled out of view is a number that is not doing its job. */
.posstrip {
  display:flex; flex-wrap:wrap; gap:6px; align-items:center;
  margin:10px 0 14px; font-size:12px;
}
.posstrip .ps {
  border:1px solid var(--line); border-radius:999px; padding:5px 12px;
  font-variant-numeric:tabular-nums; white-space:nowrap;
  background:var(--panel); color:var(--ink); font-size:12px; cursor:pointer;
  box-shadow:0 1px 0 var(--shadow);
}
.posstrip .ps:hover { border-color:var(--accent); color:var(--accent); }
.posstrip .ps:active { transform:translateY(1px); box-shadow:none; }
/* The position currently filtering the board. */
.posstrip .ps-on { border-color:var(--accent); color:var(--accent); box-shadow:none; }
.posstrip .ps b { font-weight:600; }
.posstrip .ps-full { border-color:var(--up); color:var(--up); }
.posstrip .ps-full .muted { color:inherit; opacity:.7; }

.groupfilter { font-size:12px; color:var(--dim); }

/* ⚠ THE NAMING GATE REUSES .gate-card BUT NOT .gate, which is what centres the sign-in panel. Both
   names are required before the roster is usable, so this covers the page the same way. */
#nameGate { display:flex; align-items:flex-start; justify-content:center; padding:40px 16px; }
#nameGate[hidden] { display:none; }
#nameGate .gate-card { max-width:420px; width:100%; }
#nameGate label { display:block; margin:12px 0 4px; font-size:13px; color:var(--dim); }
#nameGate input { width:100%; }
#nameGate .small { font-size:11px; margin:6px 0 0; }
#nameGate button[type=submit] { margin-top:16px; }

/* ⚠ THE INVITE PANEL — the link an owner sends. It appears at the moment a league is created, so
   it has to read as the next step rather than as decoration; hence the border and the spacing
   above it, separating it from the create form it follows. */
.invite-panel { margin-top:18px; padding:12px; border:1px solid var(--line); border-radius:6px; }
.invite-panel h3 { margin:0 0 4px; font-size:13px; }
.invite-row { display:flex; gap:8px; margin-top:8px; }
.invite-row input {
  flex:1 1 auto; min-width:0; font-family:ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:11px; padding:6px 8px;
}

/* ⚠ THE OWNER'S MANAGERS TAB. Two plain tables and a one-field form: this is administration, not
   the board, and it should look like a settings page rather than compete with the market. */
.invite-form { display:flex; gap:8px; margin:10px 0; max-width:420px; }
/* The email invitations: addresses, then the optional note, then Send — stacked, one column. */
.invite-form { flex-direction:column; gap:6px; max-width:520px; }
.invite-form label { font-size:12px; }
.invite-form label + textarea { margin-bottom:6px; }
.invite-form textarea { width:100%; box-sizing:border-box; padding:6px 8px; font:inherit; resize:vertical; }
.invite-actions { display:flex; align-items:center; gap:12px; }
.invite-step { margin:14px 0 4px; font-size:13px; }
/* The key under each player list: short heading, what it means. */
.key { display:grid; grid-template-columns:max-content 1fr; gap:3px 12px; margin:12px 0 4px;
       font-size:12px; color:var(--dim); max-width:760px; }
.key dt { font-weight:600; color:var(--ink); }
.key dd { margin:0; }
/* The line above each key (owner, 2026-09-24): says what one row of the table is, so the
   definitions under it have a subject. Without it every key opened on "your share of him". */
.key-head { font-size:12px; color:var(--dim); margin:18px 0 0; max-width:760px; }
.key-head b { color:var(--ink); }
.key-head + .key { margin-top:6px; }
/* Player names: full on wide screens, "J. Allen" at 720px and below (see nameHtml in app.js). */
.nm-short { display:none; }
@media (max-width: 720px) {
  .nm-full { position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden;
             clip:rect(0 0 0 0); white-space:nowrap; border:0; }
  .nm-short { display:inline; white-space:normal; }
}
/* ⚠ THE APPEARANCE SWITCHER, ON THE TEAM PAGE AND NOT IN A MENU (owner, 2026-09-23): one label,
   one dropdown. It started as six swatch buttons painted in their own palettes, which showed the
   choice but spent six lines of the page on a decision most people make once — so it is a single
   select now, and the names carry what the swatches did. */
.themebar { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:14px 0 4px; }
/* The navigation's menu buttons sit in a bar and are borderless; this one sits in the page, so it
   takes the ordinary button's border to read as something you press. */
.themebtn {
  border:1px solid var(--line); border-radius:6px; padding:6px 10px;
  color:var(--ink); background:var(--panel);
}
.themebtn:hover { border-color:var(--accent); }
.themebtn[aria-expanded="true"] { border-color:var(--accent); }
/* ⚠ THE CURRENT PALETTE IS MARKED IN THE LIST, which is the only place it is shown. A tick as well
   as the colour, because colour alone is not a state a reader can rely on. */
#themeList { min-width:260px; }
#themeList button { white-space:nowrap; }
#themeList button[aria-checked="true"] { color:var(--accent); font-weight:600; }
#themeList button::before { content:"\2713 "; visibility:hidden; margin-right:6px; }
#themeList button[aria-checked="true"]::before { visibility:visible; }

/* ⚠ WHAT IS STILL TO FILL, AS ONE LINE OF BUTTONS (owner, 2026-09-23). This replaced eight tall
   position cards, each with a status bar: the bars were most of the page even when there was nothing
   left to decide. A full position drops out of the line, and a full roster drops the line. */
.rosterfill { display:flex; flex-wrap:wrap; gap:6px; align-items:center; margin:12px 0 14px; font-size:12px; }
.fillchip {
  border:1px solid var(--line); border-radius:999px; padding:5px 12px; background:var(--panel);
  color:var(--ink); font:inherit; font-size:12px; cursor:pointer; white-space:nowrap;
  font-variant-numeric:tabular-nums; box-shadow:0 1px 0 var(--shadow);
}
.fillchip:hover { border-color:var(--accent); color:var(--accent); }
.fillchip:active { transform:translateY(1px); box-shadow:none; }
/* ⚠ WHAT THE LEAGUE HOLDS IS NOT SHOWN UNTIL ITS SEASON STARTS (owner, 2026-09-23). The server
   sends these as null (read.js hidesOwnership); this is only so the page does not carry columns of
   dashes. Cap, Sup and P/FV on the board, Own/Field/Teams on the screener — all one number. */
.no-own .col-cap, .no-own .col-supply, .no-own .col-pb, .no-own .col-own { display:none; }

/* The team list's Buy/Sell pair (2026-09-24; replaces the position cards). */
#teamTable td.acts { white-space:nowrap; }
#teamTable td.acts button { font-size:11px; padding:3px 8px; }
#teamTable td.acts button + button { margin-left:6px; }

/* The three-week thumbnail. The line takes its colour from the cell, which is how one rule
   serves both directions, and the cell is tight so the picture itself sets the column width. */
.spark-cell { padding-top:2px; padding-bottom:0; line-height:0; }
.spark { display:inline-block; vertical-align:middle; }
.spark polyline { fill:none; stroke:currentColor; stroke-width:1.25; stroke-linejoin:round; stroke-linecap:round; }
.spark.up { color:var(--up); }
.spark.down { color:var(--down); }
.spark.flat { color:var(--dim); }


/* One line per address after a batch send. */
#rosterMsg { white-space:pre-line; }
.roster-table { width:100%; border-collapse:collapse; margin:6px 0 14px; }
.roster-table th, .roster-table td { padding:7px 8px; border-bottom:1px solid var(--line); text-align:right; }
.roster-table th.l, .roster-table td.l { text-align:left; }
.roster-table th { color:var(--dim); font-weight:600; font-size:11px; }
/* ⚠ A DESTRUCTIVE BUTTON THAT ARMS BEFORE IT FIRES. `.armed` is the second state — the click that
   follows actually removes somebody — so it has to look different enough that nobody reaches it by
   accident, without a modal dialog, which this app forbids for its own reasons. */
.roster-table button { font-size:11px; padding:3px 8px; }
.roster-table button.danger { color:var(--down); border-color:color-mix(in srgb, var(--down) 45%, transparent); }
.roster-table button.armed { background:var(--down); color:#fff; border-color:var(--down); }
/* Two buttons in one cell now — Send/Re-send beside Withdraw. ⚠ nowrap so the pair never breaks
   across lines and puts Withdraw directly under the cursor that was aiming at Send. */
.roster-table td.acts { white-space:nowrap; }
.roster-table td.acts button + button { margin-left:6px; }

/* ⚠ THE JOIN CODE, AND IT IS MEANT TO BE READ ALOUD. Monospace with wide tracking so 8 and B, 5 and
   S do not blur at a glance; large, because somebody is copying it onto a phone from across a room.
   The alphabet already excludes I/L/O/U (Crockford base32) — this is the half of that job that
   typography has to do. */
.joincode-panel { margin:8px 0 18px; padding:12px; border:1px solid var(--line); border-radius:6px; }
.joincode-row { display:flex; flex-wrap:wrap; align-items:center; gap:12px; margin-top:8px; }
.joincode {
  font-family:ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:22px; font-weight:700; letter-spacing:3px; color:var(--accent);
  user-select:all;                       /* one click selects the whole code */
}
/* The gate's code box: the first thing on the page, so it gets room. */
.code-form { margin:4px 0 14px; }
.code-row { display:flex; gap:8px; margin-top:6px; }
.code-row input {
  flex:1 1 auto; min-width:0; padding:8px 10px;
  font-family:ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:15px; letter-spacing:2px; text-transform:uppercase;
}
/* ⚠ THE TWO SIGN-IN STEPS MUST NOT LOOK ALIKE (owner, 2026-09-24): step 2 has the accent edge,
   an envelope and its own title, so "I already asked for the email" is visible at a glance. */
.gate-step { margin:0 0 4px !important; font-size:11px !important; text-transform:uppercase; letter-spacing:.08em; color:var(--dim); }
.gate-card.is-check { border-top:4px solid var(--accent); background:color-mix(in srgb, var(--accent) 5%, var(--panel)); }
.check-icon { font-size:34px; line-height:1; margin:0 0 10px; }
.gate-card.is-check h2 { font-size:20px; }
.gate-card .check-note { margin:14px 0 6px; font-size:12px; }
.gate-card .check-links { margin:0; font-size:13px; }
/* "Finish signing in" (2026-09-24): the page the email link opens; one big button, no script. */
.gate-card form.finish-form { display:block; margin:6px 0 0; }
.finish-btn { width:100%; padding:12px; font-size:16px; font-weight:650; border-color:var(--accent); color:var(--accent); }


/* ---- the League page (2026-09-21) ------------------------------------------------------------ */
/* The league's name in the header: a link to the League page, styled as the name rather than as chrome. */
.league-link { color:var(--ink); font-weight:600; text-decoration:none; border-bottom:1px dotted var(--dim); }
.league-link:hover, .league-name-link:hover { color:var(--accent); border-color:var(--accent); }
.league-name-link { color:inherit; text-decoration:none; border-bottom:1px dotted var(--dim); }
#league h3 { font-size:14px; margin:22px 0 8px; }
#lpStatus { margin:2px 0 12px; font-size:13px; }
.lp-teams td { vertical-align:top; }
.lp-teams tr.me-row td:first-child { color:var(--accent); }
#lpOwner { border-top:1px solid var(--line); margin-top:18px; }
/* The board: a list of topics, a thread of posts, and one form for each. */
.board-form { display:flex; flex-direction:column; gap:6px; max-width:560px; margin:12px 0 4px; }
.board-form label { font-size:12px; color:var(--dim); }
.board-form textarea { font:inherit; color:var(--ink); background:var(--field); border:1px solid var(--line); border-radius:6px; padding:8px 9px; resize:vertical; }
.board-form button { align-self:flex-start; }
#lpTopics { display:flex; flex-direction:column; gap:6px; max-width:720px; }
.topic { text-align:left; padding:9px 12px; }
.topic .muted { font-size:12px; }
.post { border-bottom:1px solid var(--line); padding:10px 0; max-width:720px; }
.post-by { margin:0 0 4px; font-size:12.5px; font-weight:600; }
.post-body { margin:0; white-space:pre-wrap; overflow-wrap:anywhere; line-height:1.5; }
.post.deleted p { margin:0; font-style:italic; }
#threadTitle { margin:10px 0 4px; font-size:15px; }
.linkish.danger { color:var(--down); }

/* ---- Team › Limit Orders (owner, 2026-09-24) ------------------------------------------------- */
.orders-intro { max-width:760px; }
.order-form {
  display:flex; flex-direction:column; gap:10px; max-width:760px; margin:10px 0 6px;
  padding:14px; border:1px solid var(--line); border-radius:8px; background:var(--panel);
}
.of-row { display:flex; flex-wrap:wrap; gap:8px; align-items:center; font-size:14px; }
.of-side label, .of-check { display:inline-flex; gap:5px; align-items:center; }
.of-side { display:inline-flex; gap:12px; margin-right:6px; }
.of-player { flex:1 1 240px; min-width:0; }
.of-dollar { display:inline-flex; align-items:center; gap:3px; }
.of-dollar input { width:96px; }
.order-form .of-explain { margin:0; max-width:none; }
.order-form button[type=submit] { align-self:flex-start; border-color:var(--accent); color:var(--accent); }
/* A fill notice: the gain colour, where the discussion banner uses the interactive one. */
.board-news.order-news { border-left-color:var(--up); background:color-mix(in srgb, var(--up) 8%, var(--panel)); }
.board-news.order-news b { color:var(--up); }
.tradepick .tradepick-order { margin-left:14px; }

/* ⚠ STANDINGS' THREE TABS (owner, 2026-09-24 late): the same underline the page menus use for "you
   are here", so a tab reads as a place, not as an action. */
.subtabs { display:flex; gap:4px; margin:4px 0 12px; border-bottom:1px solid var(--line); }
.subtabs [role=tab] { border:0; border-bottom:2px solid transparent; border-radius:0; background:none;
  padding:8px 14px; color:var(--dim); font-weight:600; }
.subtabs [role=tab][aria-selected=true] { color:var(--ink); border-bottom-color:var(--accent); }
.subtabs [role=tab]:hover:not([aria-selected=true]) { color:var(--ink); }
.team-link { color:inherit; text-decoration:underline; text-decoration-color:var(--line); text-underline-offset:3px; }
.team-link:hover { text-decoration-color:var(--accent); }
.st-table td, .st-table th { white-space:nowrap; }

/* ---- column settings (owner, 2026-09-25; docs/COLUMN-SETTINGS.md) ---------------------------
   The bar sits in the SAME place over every table: just above it, at the right. */
.colbar { display:flex; justify-content:flex-end; gap:14px; margin:4px 0 6px; font-size:12px; }
.colbar button:disabled { color:var(--dim); opacity:.5; cursor:default; text-decoration:none; }
.coldlg {
  width:min(640px, calc(100vw - 24px)); max-height:calc(100vh - 32px); padding:16px;
  color:var(--ink); background:var(--panel); border:1px solid var(--line); border-radius:10px;
  box-shadow:0 12px 36px rgba(0,0,0,.4);
}
.coldlg::backdrop { background:rgba(0,0,0,.45); }
.coldlg h2 { font-size:16px; margin:0 0 2px; }
.coldlg #colDlgFor { font-size:12px; margin:0 0 10px; }
/* ⚠ SIDE BY SIDE AT EVERY WIDTH, 320px included (owner's specification): two vertical lists and a
   narrow column of move buttons between them. A long name wraps rather than widening the list. */
.colpanes { display:grid; grid-template-columns:minmax(0,1fr) auto minmax(0,1fr); gap:8px; align-items:start; }
.colpane h3 { font-size:12px; color:var(--dim); margin:0 0 4px; font-weight:600; }
.collist {
  list-style:none; margin:0; padding:2px; height:min(46vh, 340px); overflow-y:auto;
  border:1px solid var(--line); border-radius:6px; background:var(--bg);
}
.collist:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }
.collist li { padding:6px 8px; border-radius:4px; cursor:pointer; font-size:13px; line-height:1.3; overflow-wrap:anywhere; user-select:none; }
.collist li[aria-selected="true"] { background:color-mix(in srgb, var(--accent) 28%, transparent); color:var(--ink); }
.collist li.fixed { cursor:default; color:var(--dim); }
.collist li.empty { cursor:default; }
.collist li[draggable="true"] { cursor:grab; }
.colmove { display:flex; flex-direction:column; gap:8px; padding-top:40px; }
.colmove button { padding:6px 10px; font-size:16px; line-height:1; }
.colorder { display:flex; gap:6px; margin-top:6px; }
.colorder button { flex:1; padding:5px 4px; font-size:12px; }
.coldesc { font-size:12px; color:var(--dim); min-height:3.2em; margin:10px 0 8px; }
.colbtns { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.colbtns .spacer { flex:1; }
.coldlg button.primary { border-color:var(--accent); color:var(--accent); font-weight:650; }
@media (max-width:420px) {
  .coldlg { padding:12px; }
  .colmove { padding-top:36px; }
  .colmove button { padding:6px 7px; }
  .collist li { padding:6px 5px; font-size:12px; }
}
