/* FFL-BOSS — "Sunday night broadcast"
 *
 * Dark charcoal base, field-green accent, condensed caps, tabular numerals.
 * Two rules govern everything here:
 *   1. Team colors are computed for contrast in core/colors.py, never hardcoded.
 *   2. The draft board is read under a pick clock, so legibility and hit-area beat
 *      decoration. No animation on the critical path.
 */

:root {
  /* surfaces */
  --bg: #0d1215;
  --surface: #12171a;
  --surface-2: #181f23;
  --surface-3: #202a2f;
  --line: #2a353b;
  --line-bright: #3d4c54;

  /* ink */
  --ink: #e8eef0;
  --ink-dim: #9fb0b8;
  --ink-faint: #6b7d86;

  /* brand */
  --field: #1f9e57;
  --field-bright: #2ecc71;
  --chalk: #f2f6f5;
  --flag: #e8c53b;
  --penalty: #e2483d;

  /* positions — functional, colorblind-safe, mirrors core/colors.py */
  --pos-qb: #e8743b;
  --pos-rb: #3ba7e8;
  --pos-wr: #48c78e;
  --pos-te: #c084e8;
  --pos-k: #e8c53b;
  --pos-dst: #8b9bb4;

  --radius: 6px;
  --mono: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;
  --sans: "Barlow Condensed", "Roboto Condensed", "Arial Narrow", system-ui, sans-serif;
  --body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--body);
  font-size: 17px;
  /* Numbers must align in columns everywhere they appear. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ---------- yard-line motif -------------------------------------------------
 * Confined to header strips at low opacity. It must never sit behind the pick
 * list, where it would cost legibility for nothing. */
.gridiron {
  background-image:
    linear-gradient(90deg, rgba(242, 246, 245, 0.07) 1px, transparent 1px);
  background-size: 28px 100%;
}

/* ---------- layout ---------------------------------------------------------- */
header.topbar {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 10px 18px;
  background: linear-gradient(180deg, var(--surface-2), var(--surface));
  border-bottom: 2px solid var(--field);
  position: sticky;
  top: 0;
  z-index: 50;
}

/* Brand and team name stack on the left. Whose board this is matters more than the app's
   name, so the team sits directly beneath it rather than off in the corner. */
.brandblock { display: flex; flex-direction: column; gap: 1px; }
.my-team {
  font-family: var(--sans);
  font-size: 20px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--field-bright);
  line-height: 1.1;
}

.brand {
  font-family: var(--sans);
  font-weight: 700;
  font-size: 28px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--chalk);
  white-space: nowrap;
  text-decoration: none;
}
.brand span { color: var(--field-bright); }

nav.tabs { display: flex; align-items: stretch; gap: 4px; margin-left: 8px; }
nav.tabs a {
  font-family: var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-size: 17px;
  color: var(--ink-dim);
  text-decoration: none;
  padding: 6px 13px;
  border-radius: var(--radius);
  /* Centred inside its own hit area. The uppercase tracking adds a trailing space after the
     last letter, so text laid out from the left sits visibly off-centre in the highlighted
     block once a tab is active. */
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}
nav.tabs a:hover { background: var(--surface-3); color: var(--ink); }
nav.tabs a.active { background: var(--field); color: #06180e; font-weight: 600; }

/* Hamburger. Hidden on desktop, where the tabs sit inline; the checkbox stays in the
   layout but off-screen so it remains keyboard-focusable. */
.nav-toggle {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  pointer-events: none;
}
.burger { display: none; }
.burger span {
  display: block;
  width: 22px;
  height: 2px;
  background: var(--chalk);
  border-radius: 2px;
}
.nav-toggle:focus-visible + .burger {
  outline: 2px solid var(--field-bright);
  outline-offset: 2px;
}

/* Data ages and the refresh control in the header, beside the nav. Two tight lines so it
   occupies the height the nav already uses rather than adding any. */
.hdr-fresh {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-left: 14px;
  border-left: 1px solid var(--line);
  min-width: 0;
}
.hdr-ages { display: flex; flex-wrap: wrap; gap: 2px 10px; }
.hdr-fresh .fresh-item {
  font-family: var(--sans);
  font-size: 13px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-faint);
  white-space: nowrap;
}
.hdr-fresh .fresh-item strong { color: var(--ink-dim); font-weight: 600; }
.hdr-fresh .fresh-item.is-stale, .hdr-fresh .fresh-item.is-stale strong { color: var(--flag); }
.hdr-refresh { display: flex; align-items: center; gap: 7px; }
.hdr-refresh .btn-sm { font-size: 13px; padding: 2px 9px; }
/* Amber only when something is actually stale -- a permanently coloured control stops
   meaning anything. */
.hdr-fresh.is-stale .hdr-refresh .btn-sm { border-color: var(--flag); color: var(--flag); }
.hdr-lastrun { font-size: 12px; }

main { padding: 16px; max-width: 1900px; margin: 0 auto; }

/* Roster and recommendations on the LEFT, the pool of players on the right. The order the
   questions get asked: what do I have, who should I take, who is left. The board used to
   hold the left column and push the roster below the fold, which put the answer checked most
   often furthest from where the eye already was. */
.draft-layout {
  display: grid;
  grid-template-columns: 370px minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}
@media (max-width: 1200px) {
  .draft-layout { grid-template-columns: minmax(0, 1fr); }
}

.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.panel > h2 {
  margin: 0;
  padding: 9px 14px;
  font-family: var(--sans);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink-dim);
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
}
.panel .body { padding: 12px 14px; }
.stack > * + * { margin-top: 14px; }

/* ---------- scoreboard ----------------------------------------------------- */
.scoreboard {
  display: flex;
  align-items: stretch;
  gap: 0;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow-x: auto;
}
.sb-cell {
  padding: 8px 18px;
  border-right: 1px solid var(--line);
  min-width: 92px;
  flex: 0 0 auto;
  /* Label and value share a centre line. Left-aligned, a short value under a long label
     ("1" under "OVERALL") reads as though it belongs to the cell beside it. */
  text-align: center;
}
/* Two cells are lists rather than a label over a number, and centring them would be wrong:
   the ticker scrolls from its left edge, and the controls are a row of buttons. */
.sb-ticker, .sb-controls { text-align: left; }
.sb-cell:last-child { border-right: 0; }
.sb-label {
  font-family: var(--sans);
  font-size: 14px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-faint);
  white-space: nowrap;
}
.sb-value {
  font-family: var(--sans);
  font-size: 32px;
  font-weight: 700;
  line-height: 1.05;
  color: var(--chalk);
  white-space: nowrap;
}
.sb-value.on-clock { color: var(--field-bright); }
.sb-value.small { font-size: 20px; padding-top: 5px; }

/* Controls take only the room the three buttons need; the ticker takes the rest. The
   scoreboard was a row of six narrow cells and a wide empty gap. */
.sb-controls { flex: 0 0 auto; }
.sb-controls .sb-value { display: flex; gap: 5px; }

/* Recent picks as a horizontal strip. Not animated: a ticker that slides is harder to read
   than one that sits still, and this is read under a pick clock. It scrolls by hand and
   refreshes with the rest of the scoreboard on every poll. */
.sb-ticker {
  flex: 1 1 260px;
  min-width: 0;
  border-right: 0;
}
.ticker {
  display: flex;
  gap: 0;
  overflow-x: auto;
  padding-top: 3px;
  scrollbar-width: thin;
}
.tick {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: 0 0 auto;
  padding: 3px 11px;
  border-right: 1px solid var(--line);
  white-space: nowrap;
  font-size: 15px;
}
.tick:last-child { border-right: 0; }
.tick-at {
  font-family: var(--sans);
  font-size: 13px;
  color: var(--ink-faint);
  letter-spacing: 0.04em;
}
.tick-name { color: var(--ink-dim); }
.tick-by { font-size: 13px; color: var(--ink-faint); }
/* Your own picks stand out, because "did that get recorded as mine" is the question the
   strip is most often being scanned for. */
.tick-mine .tick-name { color: var(--chalk); font-weight: 600; }
.tick-mine .tick-by { color: var(--field-bright); font-weight: 600; }

/* ---------- search --------------------------------------------------------- */
.searchbar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
input[type="search"], input[type="text"], input[type="number"], select, textarea {
  background: var(--surface-3);
  border: 1px solid var(--line-bright);
  color: var(--ink);
  border-radius: var(--radius);
  padding: 9px 11px;
  font: inherit;
  font-family: var(--body);
}
input[type="search"] { flex: 1 1 260px; font-size: 18px; }
input:focus-visible, select:focus-visible, textarea:focus-visible, button:focus-visible {
  outline: 2px solid var(--field-bright);
  outline-offset: 1px;
}
::placeholder { color: var(--ink-faint); }

.filters { display: flex; gap: 4px; flex-wrap: wrap; }
.chip {
  font-family: var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 16px;
  padding: 6px 11px;
  border-radius: 999px;
  border: 1px solid var(--line-bright);
  background: var(--surface-3);
  color: var(--ink-dim);
  cursor: pointer;
}
.chip:hover { color: var(--ink); border-color: var(--ink-faint); }
.chip.on { background: var(--chalk); color: #0d1215; border-color: var(--chalk); font-weight: 700; }

/* ---------- buttons -------------------------------------------------------- */
button, .btn {
  font-family: var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-size: 17px;
  font-weight: 600;
  padding: 8px 14px;
  border-radius: var(--radius);
  border: 1px solid var(--line-bright);
  background: var(--surface-3);
  color: var(--ink);
  cursor: pointer;
}
button:hover, .btn:hover { background: var(--line); }
.btn-primary { background: var(--field); border-color: var(--field); color: #06180e; }
.btn-primary:hover { background: var(--field-bright); }
/* Amber: not an error, but a state worth noticing. Used for sync being off, where the
   consequence is silent -- picks simply stop arriving and the board looks merely quiet. */
.btn-warn { background: transparent; border-color: var(--flag); color: var(--flag); font-weight: 700; }
.btn-warn:hover { background: rgba(232, 197, 59, 0.16); }
.btn-danger { background: transparent; border-color: var(--penalty); color: var(--penalty); }
.btn-danger:hover { background: rgba(226, 72, 61, 0.16); }
.btn-sm { padding: 4px 9px; font-size: 15px; }

/* Search, position filters and Hide drafted on one line. They are all the same act --
   narrowing the list -- and stacking them cost three rows above a table that is read under
   a pick clock. Wraps rather than overflowing on a narrow window. */
.controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
}
/* Filters lead and take only the room they need; search follows and no longer claims the
   widest slot on the row. One click narrows to a position, which is the common case; typing
   a name is the rarer one. */
.controls .filters { flex: 0 0 auto; order: 1; }
.controls .searchbar { flex: 0 1 220px; min-width: 130px; order: 2; }
.controls .spinner { order: 3; }
.controls input[type="search"] { flex: 1 1 auto; font-size: 16px; }

/* ---------- player table --------------------------------------------------- */
.board-scroll { max-height: calc(100vh - 268px); overflow-y: auto; overflow-x: auto; }
table.board { width: 100%; border-collapse: collapse; font-size: 16px; }
table.board thead th {
  position: sticky;
  top: 0;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line-bright);
  font-family: var(--sans);
  font-size: 14px;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink-faint);
  text-align: center;
  padding: 7px 9px;
  white-space: nowrap;
  z-index: 2;
}
table.board thead th.left { text-align: left; }
/* Centred, not right-aligned. Right alignment pulls every value to the far edge of its
   column while the header sits above the middle of it, so the eye pairs numbers with the
   wrong heading -- especially in narrow columns like Bye and Tier. */
table.board tbody td { padding: 6px 9px; border-bottom: 1px solid var(--line); text-align: center; white-space: nowrap; }
table.board tbody td.left { text-align: left; }
table.board tbody tr:hover { background: var(--surface-2); }
table.board tbody tr.drafted { opacity: 0.34; }
table.board tbody tr.drafted .pname { text-decoration: line-through; }
table.board tbody tr.dnd .pname { text-decoration: line-through; color: var(--penalty); }
table.board tbody tr.mine { background: rgba(31, 158, 87, 0.11); }

/* Team color accent bar — the only place raw club color is load-bearing. */
td.accent { padding: 0 !important; width: 4px; }
.accent-bar { display: block; width: 4px; height: 30px; border-radius: 0 2px 2px 0; }

.pname { font-weight: 600; color: var(--ink); cursor: pointer; background: none; border: 0; padding: 0; font-size: 16px; font-family: var(--body); text-align: left; }
.pname:hover { color: var(--field-bright); text-decoration: underline; }
.pmeta { color: var(--ink-faint); font-size: 14px; }

.pos {
  display: inline-block;
  min-width: 34px;
  text-align: center;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 14px;
  letter-spacing: 0.06em;
  padding: 2px 6px;
  border-radius: 3px;
  color: #0b0f0e;
}
.pos-QB { background: var(--pos-qb); }
.pos-RB { background: var(--pos-rb); }
.pos-WR { background: var(--pos-wr); }
.pos-TE { background: var(--pos-te); }
.pos-K  { background: var(--pos-k); }
.pos-DST { background: var(--pos-dst); }

.tier-badge { font-family: var(--sans); font-size: 14px; color: var(--ink-faint); }
/* A tier is only meaningful inside one position, so the badge says which. Muted, because
   it is a qualifier on the tier number rather than a second position column. */
.tier-pos {
  font-size: 12px;
  color: var(--ink-faint);
  margin-left: 3px;
  letter-spacing: 0.05em;
}
.cliff { color: var(--flag); font-weight: 700; }
.vor { font-family: var(--sans); font-size: 18px; font-weight: 700; color: var(--chalk); }
.num { font-family: var(--sans); font-size: 17px; color: var(--ink-dim); }

/* Where a player actually went, in round.pick form. */
.pick-badge {
  font-family: var(--sans);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 1px 6px;
  margin-left: 6px;
  border-radius: 3px;
  background: var(--surface-3);
  border: 1px solid var(--line-bright);
  color: var(--ink-dim);
  white-space: nowrap;
}
.pick-badge.pick-mine {
  background: rgba(31, 158, 87, 0.24);
  border-color: var(--field);
  color: var(--field-bright);
}

/* The round.pick slot an ADP implies, shown beside the raw number. */
.adp-slot {
  font-family: var(--sans);
  font-size: 14px;
  color: var(--ink-faint);
  margin-left: 5px;
}

/* Team abbreviation is a control: it opens the team profile. */
.team-btn {
  font-family: var(--sans);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.05em;
  background: none;
  border: 0;
  border-bottom: 1px dotted var(--ink-faint);
  border-radius: 0;
  padding: 1px 0;
  color: var(--ink-dim);
  cursor: pointer;
  text-transform: none;
}
.team-btn:hover { background: none; color: var(--field-bright); border-bottom-color: var(--field-bright); }

.team-logo-lg {
  width: 66px;
  height: 66px;
  object-fit: contain;
  flex: 0 0 auto;
}
tr.drafted-row { opacity: 0.5; }

.flag-badge {
  font-size: 13px;
  padding: 1px 5px;
  border-radius: 3px;
  border: 1px solid currentColor;
  margin-left: 5px;
  vertical-align: middle;
}
/* An injury tag that opens the player's details. It always looked like a button, so it is
   one -- a badge that appears clickable and is not is a small lie repeated on every row. */
.flag-click {
  font-family: inherit;
  font-size: 13px;
  font-weight: inherit;
  letter-spacing: normal;
  text-transform: none;
  padding: 1px 5px;
  background: none;
  border: 1px solid currentColor;
  cursor: pointer;
  line-height: inherit;
}
.flag-click:hover { background: rgba(226, 72, 61, 0.16); }

.flag-injury { color: var(--penalty); }
.flag-watch { color: var(--flag); }
.flag-note { color: var(--field-bright); }

.flag-split { color: #b08cd8; }

/* ---------- still around at my next pick? -----------------------------------
 * The board's most important column, and the one that took the most revisions.
 *
 * It answers the question in the heading's own terms -- "still around at 2.08?" -> Likely --
 * rather than reporting the opposite quantity and expecting the reader to invert it. The
 * earlier GONE/SAFE wording failed twice over: those read as verdicts on the player instead
 * of answers to a question, and SAFE is genuinely ambiguous (safe to take, or safe to wait?).
 *
 * Deliberately NOT styled as a pill or button. Bordered chips read as controls, and the one
 * thing this column must never invite is a click during a live draft.
 */
.wait { white-space: nowrap; }
.stay {
  font-family: var(--sans);
  font-size: 16px;
  letter-spacing: 0.03em;
}
.stay-pct {
  font-size: 13px;
  color: var(--ink-faint);
  margin-left: 5px;
}
/* Colour tracks urgency, which runs opposite to the probability: a LOW chance of him being
   there is what demands action now. */
.stay-unlikely { color: var(--penalty); font-weight: 700; }
.stay-doubtful { color: #e8843b; }
.stay-toss-up  { color: var(--flag); }
.stay-probably { color: #7fb069; }
.stay-likely   { color: var(--ink-dim); }

/* Two-line headings need to wrap, which the board's default nowrap prevents. */
table.board thead th.wrap { white-space: normal; line-height: 1.25; }

/* Sortable headings. The button inherits the heading's type so the column reads the same
   whether or not it happens to be the active sort. */
table.board thead th.sortable { padding: 0; }
table.board thead th.sortable > button {
  width: 100%;
  background: none;
  border: 0;
  padding: 7px 9px;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  cursor: pointer;
  line-height: inherit;
}
table.board thead th.sortable > button:hover { color: var(--ink); background: var(--surface-3); }
table.board thead th.sorted > button { color: var(--field-bright); }
.sort-mark { font-size: 10px; margin-left: 4px; }

/* ADP movement. Rendered only when draft.SHOW_ADP_MOMENTUM is on -- see the comment there;
   the sign convention is unverified and a reversed arrow would mislead. */
/* ADP movement. Green up, red down -- the convention everyone already reads without being
   told, describing the MARKET rather than your urgency.
   The first version inverted this on the theory that a rising player is one you have less
   time to wait on. Two problems with that: the "Still around at my next pick" column already
   states urgency, so momentum duplicating it left nothing reporting which way the market was
   actually moving; and a red up-arrow reads as "warning, he is dropping" -- the opposite of
   what it says. Ambiguity beats convention only with a much stronger reason than that.
   Sits on its own line under the ADP it qualifies -- inline it would push the column wide
   for the ~9% of rows that carry it. */
.adp-move {
  display: block;
  margin-top: 2px;
  font-family: var(--sans);
  font-size: 13px;
  letter-spacing: 0.03em;
  white-space: nowrap;
}
.adp-move-word { font-size: 12px; opacity: 0.85; }
.adp-move.rising  { color: var(--field-bright); }
.adp-move.falling { color: var(--penalty); }

/* Individual analyst ranks in the dossier: eight small chips read faster as a cluster
   than as a sentence, and the visual width of the cluster is itself the disagreement. */
.analyst-row { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 4px; }
.analyst-chip {
  font-family: var(--sans);
  font-size: 14px;
  padding: 2px 7px;
  border-radius: 3px;
  background: var(--surface-3);
  border: 1px solid var(--line-bright);
  color: var(--ink-dim);
}

/* Column legend. Collapsed by default: the board is read under a clock, and anyone who
   already knows the columns should not have to scroll past an explanation of them. */
.legend { margin-top: 10px; }
.legend > summary {
  cursor: pointer;
  font-family: var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 15px;
  color: var(--ink-faint);
  padding: 6px 2px;
}
.legend > summary:hover { color: var(--ink-dim); }
.legend dl {
  margin: 6px 0 0;
  padding: 12px 14px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-size: 15px;
}
.legend dt {
  font-family: var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--field-bright);
  margin-top: 10px;
}
.legend dt:first-child { margin-top: 0; }
.legend dd { margin: 3px 0 0; color: var(--ink-dim); line-height: 1.45; }

/* Data-age strip above the recommendations. */
.freshness-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  padding: 6px 10px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-family: var(--sans);
  font-size: 14px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.fresh-item strong { color: var(--ink-dim); font-weight: 600; }
.fresh-item.is-stale, .fresh-item.is-stale strong { color: var(--flag); }

/* The refresh control lives in the strip, beside the ages it fixes. */
.fresh-refresh { display: inline-flex; align-items: center; gap: 7px; }
.fresh-refresh .btn-sm { font-size: 14px; padding: 3px 9px; }
/* Busy state for any htmx button that swaps its own label.
   htmx adds .htmx-request to the element issuing the request, so the two labels have to be
   INSIDE that element -- a sibling is never a descendant of it and stays hidden forever,
   which is what happened the first time. */
.btn-busy { display: none; }
.htmx-request .btn-idle { display: none; }
.htmx-request .btn-busy { display: inline; }

/* And make the button itself look occupied, not just relabelled. A four-second wait with no
   change of state reads as a dead control, which is the whole problem being fixed. */
button.htmx-request,
button[disabled] {
  border-color: var(--flag);
  color: var(--flag);
  cursor: default;
}
button.htmx-request { animation: busy-pulse 1.1s ease-in-out infinite; }
@keyframes busy-pulse { 50% { opacity: 0.55; } }
/* The no-animation rule covers the draft board, read under a pick clock. This is a
   deliberate click with a real wait behind it, which is where a pulse earns its place --
   but not for anyone who has asked the system for less motion. */
@media (prefers-reduced-motion: reduce) {
  button.htmx-request { animation: none; opacity: 0.7; }
}

/* Still used by any indicator that is a genuine descendant. */
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: inline; }

/* News recency, and ESPN's collapsed writeup. */
.news-age {
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.06em;
  text-transform: none;
  color: var(--ink-faint);
  margin-left: 7px;
}
.news-age.is-stale { color: var(--flag); }
.outlook > summary {
  cursor: pointer;
  font-family: var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-size: 14px;
  color: var(--ink-faint);
  padding: 4px 0;
}
.outlook > summary:hover { color: var(--ink-dim); }
.outlook p { margin: 6px 0 0; line-height: 1.55; color: var(--ink-dim); }

/* Bye severity. Amber is "one starting slot empty", red is "two or more" -- both are
   survivable, which is why neither uses the same red as an injury flag. */
.bye-bad  { color: var(--penalty); font-weight: 700; }
.bye-warn { color: var(--flag); }
.bye-ok   { color: var(--ink-faint); }

/* The rule behind the recommended lineup, stated above the table it produces. */
.rule-note {
  padding: 9px 14px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
  color: var(--ink-dim);
  line-height: 1.55;
}

/* ---------- recommendations ------------------------------------------------ */
/* Recommendations are a disclosure list: name and Details always visible, the meta line and
   reasons behind an arrow. Six cards showing everything filled the column and pushed My
   Roster below the fold; the top pick opens by default because it is the one being decided. */
details.rec > summary {
  display: flex;
  align-items: center;
  gap: 7px;
  cursor: pointer;
  /* No disclosure arrow: the whole row is the click target, so a marker spent width a
     370px column needs for names without telling the reader anything new. */
  list-style: none;
  padding: 2px 0;
}
/* Name on its own line with any flag beneath it. Sharing a line meant the badge ate the
   width a two-word name needed, pushing it onto a second line regardless -- same height,
   worse to read. */
details.rec > summary .rec-id {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 1 1 auto;
  min-width: 0;
}
details.rec > summary .rec-name { min-width: 0; }
.rec-flags { display: flex; flex-wrap: wrap; gap: 4px; }
.rec-flags .flag-badge { margin-left: 0; }
details.rec .rec-details { margin-left: auto; flex: 0 0 auto; }
details.rec > .pmeta { margin-top: 5px; }

.rec {
  border: 1px solid var(--line-bright);
  border-left: 4px solid var(--field);
  border-radius: var(--radius);
  padding: 10px 12px;
  background: var(--surface-2);
}
.rec + .rec { margin-top: 9px; }
.rec.top { border-left-color: var(--field-bright); background: var(--surface-3); }
.rec-head { display: flex; align-items: baseline; gap: 8px; justify-content: space-between; }
/* 22px wrapped two-word names onto a second line in a 370px column, which made each
   card taller than the one line of content it was showing when collapsed. */
.rec-name { font-family: var(--sans); font-size: 17px; font-weight: 700; letter-spacing: 0.02em; }
/* .pname is sized for a board row (16px body type). Inside a recommendation it has to keep
   the heading's type instead, or the name shrinks to a third the size of everything around
   it. The hover stays the board's, so a clickable name looks the same wherever it appears. */
.rec-name .pname {
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  vertical-align: baseline;
}
.rec-score { font-family: var(--sans); font-size: 17px; color: var(--field-bright); font-weight: 700; }
.rec-why { margin: 7px 0 0; padding-left: 17px; color: var(--ink-dim); font-size: 15px; line-height: 1.55; }
.rec-why li + li { margin-top: 2px; }
.rec-actions { margin-top: 9px; display: flex; gap: 6px; }

/* ---------- roster --------------------------------------------------------- */
table.roster { width: 100%; border-collapse: collapse; font-size: 16px; }
table.roster td { padding: 5px 7px; border-bottom: 1px solid var(--line); }
table.roster td.slot {
  font-family: var(--sans);
  font-size: 14px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-faint);
  width: 52px;
}
tr.empty-slot td { color: var(--ink-faint); font-style: italic; }

/* The draft page's own roster panel, tightened so more of Recommended fits above the fold.
   Nine starting slots plus bench at the default size pushed the recommendations down by
   about a card and a half. */
table.my-roster { font-size: 13px; line-height: 1.25; }
table.my-roster td { padding: 1px 5px; }
table.my-roster td.slot { font-size: 11px; width: 36px; letter-spacing: 0.06em; }
table.my-roster .team-logo-sm { height: 13px; }
.panel-tight > h2 { padding: 4px 10px; font-size: 14px; letter-spacing: 0.09em; }
.panel-tight .body { padding: 6px 10px; }
/* The bye warning and the "still need" line each carried a 10px gap above them, which on a
   nine-row table is a meaningful share of the panel. */
.panel-tight .body > p { margin-top: 6px !important; font-size: 13px; }
tr.empty-slot td.slot { color: var(--flag); }

/* ---------- banners -------------------------------------------------------- */
.banner {
  padding: 9px 13px;
  border-radius: var(--radius);
  border: 1px solid var(--line-bright);
  background: var(--surface-2);
  font-size: 16px;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.banner.warn { border-color: var(--flag); background: rgba(232, 197, 59, 0.1); }
.banner.bad { border-color: var(--penalty); background: rgba(226, 72, 61, 0.1); }
.banner.good { border-color: var(--field); background: rgba(31, 158, 87, 0.11); }
.dot { width: 9px; height: 9px; border-radius: 50%; background: var(--ink-faint); flex: 0 0 auto; }
.dot.live { background: var(--field-bright); }
.dot.off { background: var(--ink-faint); }
.dot.err { background: var(--penalty); }

/* ---------- dossier drawer ------------------------------------------------- */
/* A small panel for one question, as against the full-height drawer for everything about a
   player. Centred rather than edge-anchored: it is a short answer, and a tall column holding
   four lines looks like something failed to load. */
.popover {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(420px, calc(100vw - 32px));
  max-height: 80vh;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--field);
  border-radius: var(--radius);
  z-index: 101;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
}
.popover-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 14px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
}
/* Smaller than the drawer's: this panel answers one question and a 78px portrait
   would outweigh the sentence it sits beside. */
.headshot-sm { width: 44px; height: 44px; }

.popover-body { padding: 13px 14px 16px; }
/* Estimated return. The week sits beside the date because the date alone needs the
   schedule to interpret, and a fantasy decision is made in weeks. */
.injury-back {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: 9px;
  font-size: 16px;
}
.injury-back strong { color: var(--chalk); }
.back-week {
  font-family: var(--sans);
  font-size: 14px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--field-bright);
  border: 1px solid currentColor;
  border-radius: 3px;
  padding: 1px 6px;
}
/* Out for the season is not a return date, and must not look like one. */
.injury-back.is-done strong { color: var(--penalty); }
.injury-section .injury-what { font-size: 18px; }

.injury-what {
  font-family: var(--sans);
  font-size: 20px;
  font-weight: 600;
  color: var(--chalk);
  letter-spacing: 0.02em;
}

.drawer-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(5, 8, 9, 0.62);
  z-index: 100;
}
.drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(560px, 100vw);
  background: var(--surface);
  border-left: 2px solid var(--field);
  z-index: 101;
  overflow-y: auto;
  padding: 0 0 30px;
}

/* Opens immediately to the RIGHT of the roster/recommendations column, not at the far edge
   of the window. Clicking Details on the left and having the answer appear a screen away
   reads as two unrelated events; landing it against the column you clicked in keeps it one.
   Only above 1200px, where that two-column layout actually exists -- below it the columns
   stack and there is no column edge to sit beside. */
@media (min-width: 1201px) {
  .drawer {
    /* main is centred at max-width 1900 with 16px padding, and the side column is 370 wide,
       so its right edge is 386px in -- plus half the leftover on a window wider than 1900.
       max() picks the plain 386 on anything narrower, where the centring contributes nothing. */
    left: max(386px, calc((100vw - 1900px) / 2 + 386px));
    right: auto;
    width: min(560px, calc(100vw - 420px));
    border-left: 2px solid var(--field);
    border-right: 1px solid var(--line-bright);
  }
}
.drawer-head {
  display: flex;
  gap: 14px;
  padding: 15px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  align-items: flex-start;
}
.headshot {
  width: 78px;
  height: 78px;
  border-radius: 50%;
  background: var(--surface-3);
  object-fit: cover;
  flex: 0 0 auto;
  border: 2px solid var(--line-bright);
}
.drawer h3 { font-family: var(--sans); font-size: 28px; margin: 0 0 4px; letter-spacing: 0.02em; }
.drawer .close { margin-left: auto; }
.drawer section { padding: 13px 15px; border-bottom: 1px solid var(--line); }
/* Section headings in the details panel. They were --ink-faint, the dimmest ink token in
   the palette, which left them quieter than the body text they were supposed to be
   introducing -- so a long drawer read as one undifferentiated column. Chalk plus a green
   rule makes each section findable by scanning rather than by reading. */
.drawer section h4 {
  font-family: var(--sans);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--chalk);
  margin: 0 0 10px;
  padding-left: 9px;
  border-left: 3px solid var(--field);
  line-height: 1.25;
}
.kv { display: grid; grid-template-columns: auto 1fr; gap: 5px 14px; font-size: 16px; }
.kv dt { color: var(--ink-faint); }
.kv dd { margin: 0; color: var(--ink); }
.news-item { font-size: 15px; padding: 7px 0; border-bottom: 1px dashed var(--line); }
.news-item:last-child { border-bottom: 0; }
.news-item a { color: var(--ink); text-decoration: none; }
.news-item a:hover { color: var(--field-bright); text-decoration: underline; }
.news-date { color: var(--ink-faint); font-size: 14px; }
textarea.note { width: 100%; min-height: 74px; resize: vertical; }

/* metric grid used in the dossier */
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(88px, 1fr)); gap: 8px; }
.metric { background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius); padding: 7px 9px; }
.metric .m-label { font-family: var(--sans); font-size: 13px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-faint); }
.metric .m-value { font-family: var(--sans); font-size: 23px; font-weight: 700; color: var(--chalk); }

/* ---------- setup form ----------------------------------------------------- */
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 13px; }
.field { display: flex; flex-direction: column; gap: 5px; }
.field label {
  font-family: var(--sans);
  font-size: 15px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-dim);
}
.field .hint { font-size: 14px; color: var(--ink-faint); }
.slots-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(108px, 1fr)); gap: 10px; }
.scoring-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 18px; }
.scoring-group h4 {
  font-family: var(--sans);
  font-size: 15px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--field-bright);
  margin: 0 0 8px;
  border-bottom: 1px solid var(--line);
  padding-bottom: 4px;
}
.scoring-row { display: flex; justify-content: space-between; align-items: center; gap: 9px; padding: 2px 0; }
.scoring-row label { font-size: 15px; color: var(--ink-dim); }
.scoring-row input { width: 74px; text-align: right; padding: 4px 7px; }

.muted { color: var(--ink-faint); }
.small { font-size: 15px; }
.mono { font-family: var(--mono); font-size: 15px; }
.right { text-align: right; }
.spacer { flex: 1; }
.htmx-request .spinner { visibility: visible; }
.spinner { visibility: hidden; color: var(--field-bright); font-size: 15px; }

kbd {
  font-family: var(--mono);
  font-size: 13px;
  background: var(--surface-3);
  border: 1px solid var(--line-bright);
  border-bottom-width: 2px;
  border-radius: 3px;
  padding: 1px 5px;
  color: var(--ink-dim);
}

/* Respect reduced-motion, and keep the draft path animation-free regardless. */
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ---------- who am I ------------------------------------------------------- */
/* Every recommendation is for one team; this says which. */
.whoami {
  display: flex;
  align-items: center;
  gap: 7px;
  padding-left: 14px;
  border-left: 1px solid var(--line);
}
.whoami select {
  font-family: var(--sans);
  font-size: 17px;
  font-weight: 600;
  padding: 5px 9px;
  background: var(--surface-3);
  color: var(--field-bright);
}
td.actions { white-space: nowrap; }
td.actions form + form { margin-left: 4px; }

/* ---------- matchup context ------------------------------------------------
 * Deliberately understated. The backtest says this must not look like a number
 * you can act on directly -- it is context, not an adjustment. */
.matchup {
  font-family: var(--sans);
  font-size: 14px;
  padding: 1px 6px;
  border-radius: 3px;
  border: 1px solid var(--line-bright);
  color: var(--ink-dim);
  white-space: nowrap;
}
.matchup-tough { border-color: var(--penalty); color: var(--penalty); }
.matchup-soft { border-color: var(--field); color: var(--field-bright); }
.matchup-neutral { opacity: 0.6; }

/* ---------- lineup table extras -------------------------------------------- */
.team-logo-sm {
  width: 20px;
  height: 20px;
  object-fit: contain;
  vertical-align: -4px;
  margin-right: 6px;
}
table.roster tr.head td {
  font-family: var(--sans);
  font-size: 13px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border-bottom: 1px solid var(--line-bright);
}

/* Positional rank: colour carries the same message as the number, so a weak start
 * is visible before the text is read. */
.posrank {
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 700;
  padding: 1px 7px;
  border-radius: 3px;
  border: 1px solid currentColor;
  white-space: nowrap;
}
.rank-elite { color: var(--field-bright); }
.rank-good  { color: var(--ink); }
.rank-ok    { color: var(--ink-dim); }
.rank-thin  { color: var(--flag); }

/* ---------- matchup headline ------------------------------------------------
 * Leads the scoreboard: who you are playing frames every other number on the page. */
.matchup-cell { min-width: 280px; }
.matchup-teams {
  font-size: 21px !important;
  line-height: 1.25;
  white-space: normal !important;
}
.matchup-teams .me { color: var(--field-bright); }
.matchup-teams .vs {
  color: var(--ink-faint);
  font-size: 15px;
  padding: 0 3px;
}
.matchup-teams .them { color: var(--chalk); }

/* ---------- lineup metric columns ------------------------------------------
 * Everything except the player name is centred: these are short values compared
 * down a column, and centring makes them scan as a block. */
table.lineup-table td.c { text-align: center; }
table.lineup-table td.slot { text-align: left; }

/* Opponent-defence rank. The old red-on-black failed at this size; these carry the
 * same meaning through a tinted chip with a light label, which stays legible. */
.matchup {
  display: inline-block;
  min-width: 62px;
  font-family: var(--sans);
  font-size: 15px;
  padding: 2px 8px;
  border-radius: 4px;
  border: 1px solid transparent;
  white-space: nowrap;
  color: var(--ink);
}
.matchup strong { font-weight: 700; }
.matchup-tough {
  background: rgba(226, 72, 61, 0.20);
  border-color: rgba(226, 72, 61, 0.55);
  color: #ffb3ad;                 /* light salmon: ~7:1 on the tinted ground */
}
.matchup-soft {
  background: rgba(31, 158, 87, 0.20);
  border-color: rgba(31, 158, 87, 0.55);
  color: #86e5b0;
}
.matchup-neutral {
  background: var(--surface-3);
  border-color: var(--line-bright);
  color: var(--ink-dim);
}

/* Week-by-week magnitude bar. A tinted background behind the number, so the shape of
 * a season is visible without the number ever being replaced by a picture. */
.ptbar {
  display: inline-block;
  min-width: 46px;
  padding: 1px 6px;
  border-radius: 3px;
  background: linear-gradient(
    to right,
    rgba(31, 158, 87, 0.45) var(--w, 0%),
    transparent var(--w, 0%)
  );
}

/* Projection disagreement: flagged only when the sources genuinely differ. */
.disagree {
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 700;
  color: var(--flag);
}

/* ---------- freshness disclosure ------------------------------------------- */
.freshness-summary {
  padding: 9px 14px;
  font-family: var(--sans);
  font-size: 16px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-dim);
  background: var(--surface-2);
  cursor: pointer;
  list-style: none;
}
.freshness-summary::-webkit-details-marker { display: none; }
.freshness-summary::before { content: "▸  "; color: var(--ink-faint); }
details[open] > .freshness-summary::before { content: "▾  "; }
.freshness-summary:hover { color: var(--ink); }

/* ---------- phone layout ----------------------------------------------------
 * Sunday-morning lineup decisions get made on a phone. Below 820px the wide
 * metric tables stop being tables and become stacked cards, each cell labelled
 * by its column header via data-label. CSS only -- no build step, no JS.
 *
 * The draft board is deliberately excluded. It is read under a pick clock on a
 * real screen, and compromising it for a phone would be the wrong trade. */
@media (max-width: 820px) {
  html, body { font-size: 16px; }
  main { padding: 10px; }

  /* The header was what made the page wide. It is a non-wrapping flex row -- brand, seven
     nav links, the league description, the profile control -- and none of it could shrink,
     so it set the document width and every other page pans sideways to match.
     The tabs move into a drop-down panel and the rest is allowed to shrink or wrap. */
  header.topbar {
    flex-wrap: wrap;
    gap: 10px;
    padding: 8px 12px;
    position: relative;   /* anchor for the drop-down panel */
  }
  .brand { font-size: 22px; }
  .brandblock { min-width: 0; flex: 1 1 auto; }
  /* Its own row, no left rule: the header is a wrap container here, so a vertical
     divider would sit across the middle of nothing. */
  .hdr-fresh {
    order: 97;
    flex: 1 1 100%;
    padding-left: 0;
    border-left: 0;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
  }
  .my-team { font-size: 17px; overflow-wrap: anywhere; }

  .burger {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    padding: 9px 10px;
    margin-left: auto;    /* push to the right edge, opposite the brand */
    border: 1px solid var(--line-bright);
    border-radius: var(--radius);
    cursor: pointer;
  }
  .nav-toggle:checked + .burger { background: var(--surface-3); }

  nav.tabs { display: none; }
  .nav-toggle:checked ~ nav.tabs {
    display: flex;
    flex-direction: column;
    gap: 2px;
    /* Full-width panel below the header rather than an inline row, so link targets are
       full-width and a mis-tap is unlikely. */
    order: 99;
    flex: 1 0 100%;
    margin: 4px 0 0;
    padding-top: 8px;
    border-top: 1px solid var(--line);
  }
  .nav-toggle:checked ~ nav.tabs a {
    padding: 12px 10px;
    border-radius: var(--radius);
  }

  /* Long scoring description; let it shrink rather than push the row wide. */
  .spacer { display: none; }
  .league-meta {
    flex: 1 1 100%;
    order: 100;
    min-width: 0;
  }
  /* Its own row. A <select> is sized by its widest option, and the ESPN-derived names run
     to ~55 characters ("Connor Davey (ESPNFAN8466215585) (Connor's Competitive Team)"),
     so left to itself it sets the width of the whole header. An explicit width plus
     min-width:0 on the flex parent is what actually lets it shrink -- flex items refuse to
     go below their content width otherwise. */
  .whoami {
    order: 98;
    flex: 1 1 100%;
    min-width: 0;
    padding-left: 0;
    border-left: 0;
  }
  /* flex-basis 0 rather than width:100%: the label shares this row, so a 100% basis
     overflows by the label's width and only shrinks back afterwards. Basis 0 grows into
     whatever is left instead. min-width:0 is the part that actually permits shrinking --
     a flex item will not go below its content width without it, and the content here is
     the widest <option>, not the selected one. */
  .whoami select {
    flex: 1 1 0;
    width: auto;
    min-width: 0;
    max-width: 100%;
    text-overflow: ellipsis;
  }
  .whoami .sb-label { flex: 0 0 auto; }
  .whoami-name { white-space: normal; overflow-wrap: anywhere; }

  .draft-layout { grid-template-columns: minmax(0, 1fr); }

  /* Scoreboard cells wrap instead of forcing a horizontal scroll. */
  .scoreboard { flex-wrap: wrap; }
  .sb-cell {
    flex: 1 1 44%;
    min-width: 0;
    border-bottom: 1px solid var(--line);
  }
  .sb-value { font-size: 26px; }
  .matchup-cell { flex: 1 1 100%; }
  .matchup-teams { font-size: 19px !important; }

  /* Metric tables become cards. */
  table.lineup-table,
  table.lineup-table tbody,
  table.lineup-table tr,
  table.lineup-table td {
    display: block;
    width: 100%;
  }
  table.lineup-table tr.head { display: none; }
  table.lineup-table tr {
    border: 1px solid var(--line-bright);
    border-radius: var(--radius);
    margin-bottom: 10px;
    padding: 8px 10px;
    background: var(--surface-2);
  }
  table.lineup-table td {
    border: 0;
    padding: 3px 0;
    text-align: left !important;
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
  }
  /* The label comes from the column this cell belongs to. */
  table.lineup-table td[data-label]::before {
    content: attr(data-label);
    font-family: var(--sans);
    font-size: 13px;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink-faint);
    flex: 0 0 auto;
  }
  /* Player name and slot lead the card rather than sitting in a column. */
  table.lineup-table td.slot {
    font-size: 15px;
    color: var(--field-bright);
    border-bottom: 1px solid var(--line);
    padding-bottom: 5px;
    margin-bottom: 4px;
  }
  table.lineup-table td.player-cell {
    font-size: 18px;
    display: block;
  }
  table.lineup-table td.player-cell::before { content: none; }

  /* Bigger touch targets; a mis-tap on a phone is worse than a wasted pixel. */
  .pname { font-size: 18px; padding: 6px 0; }
  button, .btn { padding: 10px 16px; }
  .btn-sm { padding: 7px 12px; }

  .drawer { width: 100vw; }
  .metrics { grid-template-columns: repeat(auto-fit, minmax(76px, 1fr)); }

  /* Plain .roster tables (the scorecard's) do not collapse into cards, because their rows
     are comparisons that only make sense read across. Let them scroll inside their own
     container instead -- otherwise they widen the document and the whole page pans
     sideways, which loses the nav as well as the table. */
  .table-scroll {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .table-scroll > table.roster { min-width: 560px; }

  /* An invite link is ~70 characters. Side by side with its label it forces the card wide,
     so give it its own line. */
  table.lineup-table td[data-label="Link"] {
    display: block;
  }
  table.lineup-table td[data-label="Link"]::before { display: block; margin-bottom: 3px; }
  .invite-link { min-width: 0; }
}

/* ---------- invites ---------------------------------------------------------- */
.whoami-name {
  font-family: var(--sans);
  font-size: 18px;
  font-weight: 600;
  color: var(--field-bright);
  white-space: nowrap;
}
.invite-link {
  width: 100%;
  min-width: 260px;
  font-size: 13px;
  padding: 5px 8px;
  background: var(--surface-3);
  border: 1px solid var(--line-bright);
  color: var(--ink-dim);
  border-radius: var(--radius);
}
.invite-link:focus { color: var(--ink); border-color: var(--field-bright); }
