/* Crushed Courts base and components.
 *
 * Plain CSS against the tokens in tokens.css. No framework, no build step --
 * a saved file is live on reload.
 *
 * Desktop-first, but the mobile rules are not an afterthought. The three-pane
 * desktop layout becomes something different on a phone rather than merely
 * shrinking, because what makes the desktop layout good is that the chart stays
 * in view while you read the supporting cards, and naive stacking destroys
 * exactly that.
 */

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 400 var(--fs-body)/1.5 var(--font);
  -webkit-font-smoothing: antialiased;
}

/* A safety net, not a fix: a page that scrolls sideways on a phone is bad
 * enough to warrant one, and the causes are always some descendant refusing to
 * shrink. Set on html rather than body because `clip` does NOT propagate from
 * body to the viewport the way `hidden` does -- set there it reads as a fix
 * and does nothing, which is worse than no rule at all.
 *
 * `clip` rather than `hidden`: `hidden` would make this a scroll container and
 * break every `position: sticky` on the page, including the topbar and the
 * chart pinned on a phone. The board's own sideways scrolling is unaffected,
 * since that lives on .board-wrap with its own overflow-x. */
html { overflow-x: clip; }

/* Every number in the product is monospaced and tabular. Misaligned digits are
 * what make a stats table feel approximate. */
.num, td.num, th.num, .fig, .line-val, .price, .pct, .heat {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-hover); }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }

button { font: inherit; color: inherit; }

/* ---- Display type -------------------------------------------------------
 * Headings and the wordmark use the condensed face in caps. Applied by class
 * rather than by element so a heading can opt out where caps would be wrong.
 */
h1, h2, h3, .display {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: var(--track-display);
  font-weight: 700;
}

/* ---- Text utilities ----------------------------------------------------- */
.lab {
  font-size: var(--fs-lab); letter-spacing: var(--track-lab);
  text-transform: uppercase; color: var(--dim); font-weight: 600;
}
.micro { font-size: var(--fs-micro); color: var(--faint); }
.muted { color: var(--ink-2); }

/* ---- App shell ---------------------------------------------------------- */
.app {
  display: grid;
  grid-template-columns: var(--nav-w) 1fr;
  min-height: 100vh;
  /* NO transition on grid-template-columns.
   *
   * It had one, and it meant the Collapse button did nothing at all. With
   * the transition active the computed tracks stay at `232px 1fr` for ever
   * -- not for 150ms, for ever -- because Chrome will not interpolate this
   * track list and never settles on the target. Set `transition: none` on
   * the same element and it snaps to 60px immediately.
   *
   * Confirmed on production as well as locally, so the rail has never
   * collapsed on the deployed site. The sidebar snapping is not worth an
   * animation that costs the feature. */
}
.app[data-nav="collapsed"] { grid-template-columns: var(--nav-w-collapsed) 1fr; }
/* A grid item defaults to min-width:auto, so it refuses to shrink below its
 * content's intrinsic width. With a 1040px-min table inside, that made the
 * whole PAGE scroll sideways instead of the table scrolling in its own
 * container -- the sticky sidebar and topbar slid out of view with it. */
.app > * { min-width: 0; }

/* ---- Sidebar ------------------------------------------------------------ */
.nav {
  background: var(--panel);
  border-right: 1px solid var(--line);
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100vh;
  padding: var(--s3) var(--s2);
  gap: var(--s2);
  overflow: hidden;
}
.brand {
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s2) var(--s4);
  min-width: 0;
}
.brand-mark {
  width: 28px; height: 28px; flex: none;
  border-radius: 8px;
  background: linear-gradient(140deg, var(--accent), var(--accent-deep));
  display: grid; place-items: center;
  color: var(--accent-ink);
}
.brand-word {
  font-family: var(--font-display);
  text-transform: uppercase;
  font-weight: 700; font-size: var(--fs-xl);
  letter-spacing: 0.01em;
  white-space: nowrap;
}
.brand-word .dot { color: var(--accent); }
.nav[data-collapsed] .brand-word { display: none; }

/* ---- Collapsed rail -----------------------------------------------------
 * Collapsing used to change ONE thing: the grid column, 232px to 60px. Every
 * item inside kept its padding, its gap and its full label, and `overflow:
 * hidden` on the sidebar sliced them mid-word -- so the rail read "Disc",
 * "Mode", "Coll". The button that restores it was the worst of them, because
 * guessing which sliced word takes you back is the whole problem.
 *
 * So the collapsed state is a real state: labels out, icons centred, one
 * legible control at the bottom. Hovering any icon names its page, via the
 * `title` on each item. */
.nav[data-collapsed] { padding-left: var(--s1); padding-right: var(--s1); }
.nav[data-collapsed] .nav-label,
.nav[data-collapsed] .nav-section > .lab { display: none; }
.nav[data-collapsed] .brand { justify-content: center; padding-left: 0; padding-right: 0; }
.nav[data-collapsed] a.nav-item,
.nav[data-collapsed] .nav-actions .btn {
  justify-content: center;
  gap: 0;
  padding-left: 0; padding-right: 0;
  width: 40px; margin-inline: auto;
}
/* The active page is a 2px inset bar on the left edge, which at 40px wide sits
 * under the icon rather than beside it. A ring reads at any width. */
.nav[data-collapsed] a.nav-item[aria-current="page"] {
  box-shadow: inset 0 0 0 1px var(--accent);
}
.nav[data-collapsed] .nav-chevron { transform: rotate(180deg); }

/* The cross-product links. Expanded they are ordinary buttons; collapsed they
 * are the two-letter mark alone, which is the only part that survives 40px. */
.nav-cross { justify-content: flex-start; }
.cross-mark {
  flex: none;
  width: 20px; height: 20px;
  display: grid; place-items: center;
  border-radius: 5px;
  background: var(--panel-3);
  color: var(--ink-2);
  font-family: var(--font-num);
  font-size: var(--fs-micro); font-weight: 700;
  letter-spacing: 0.02em;
}
.nav-cross:hover .cross-mark { background: var(--accent); color: var(--accent-ink); }

.nav-toggle { color: var(--ink-2); }
.nav-chevron {
  font-size: var(--fs-body); line-height: 1;
  transition: transform var(--t) var(--ease);
}

.nav-section { margin-top: var(--s3); }
.nav-section > .lab { padding: 0 var(--s2) var(--s1); }
.nav a.nav-item {
  display: flex; align-items: center; gap: var(--s3);
  padding: 9px var(--s2);
  border-radius: var(--r-chip);
  color: var(--ink-2); font-size: var(--fs-sm); font-weight: 500;
  white-space: nowrap;
  transition: background var(--t-fast), color var(--t-fast);
}
.nav a.nav-item:hover { background: var(--panel-3); color: var(--ink); }
.nav a.nav-item[aria-current="page"] {
  background: var(--accent-weak);
  color: var(--ink);
  box-shadow: inset 2px 0 0 var(--accent);
}
/* The icon carries the item's own colour; the LABEL stays neutral.
 *
 * Eight items in one dim grey read as texture rather than as eight places.
 * Colouring the text too would make the sidebar a rainbow of competing
 * links -- Crushed Yards colours the mark and leaves the word alone, and
 * that is the half that does the work. The icon keeps its colour whatever
 * state the row is in, including hover and active, because the colour IS
 * the item's identity rather than a state. */
.nav-item .ico {
  width: 18px; flex: none; display: grid; place-items: center;
  color: var(--nav-ink, currentColor);
  font-size: 13px; line-height: 1;
}
.nav-item[data-ink="props"]       { --nav-ink: var(--ink-props); }
.nav-item[data-ink="trend"]       { --nav-ink: var(--ink-trend); }
.nav-item[data-ink="players"]     { --nav-ink: var(--ink-players); }
.nav-item[data-ink="depth"]       { --nav-ink: var(--ink-depth); }
.nav-item[data-ink="value"]       { --nav-ink: var(--ink-value); }
.nav-item[data-ink="opportunity"] { --nav-ink: var(--ink-opportunity); }
.nav-item[data-ink="sim"]         { --nav-ink: var(--ink-sim); }
.nav-item[data-ink="model"]       { --nav-ink: var(--ink-model); }

/* More, and what it opens. Hidden entirely on a desktop, where nothing is
   behind it. */
.nav-more { display: none; }
.nav-more[hidden] { display: none; }
.nav-spacer { flex: 1; }
.nav-actions { display: grid; gap: var(--s2); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  padding: 9px var(--s3);
  border: 1px solid var(--line-2); border-radius: var(--r-chip);
  background: var(--panel-2); color: var(--ink);
  font-size: var(--fs-sm); font-weight: 600; cursor: pointer;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.btn:hover { background: var(--panel-3); border-color: var(--line-2); }
.btn-accent {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}
.btn-accent:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn-ghost { background: transparent; border-color: transparent; color: var(--ink-2); }
.btn-ghost:hover { background: var(--panel-3); color: var(--ink); }
.btn-block { width: 100%; }

/* ---- Top bar ------------------------------------------------------------ */
.topbar {
  position: sticky; top: 0; z-index: 40;
  height: var(--topbar-h);
  display: flex; align-items: center; gap: var(--s3);
  padding: 0 var(--s4);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.search {
  flex: 1; max-width: 460px;
  display: flex; align-items: center; gap: var(--s2);
  padding: 7px var(--s3);
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-chip);
  color: var(--dim); font-size: var(--fs-sm);
  cursor: text;
}
.search:hover { border-color: var(--line-2); }
.search input {
  flex: 1; background: none; border: 0; outline: 0;
  color: var(--ink); font: inherit;
}
.search input::placeholder { color: var(--dim); }
.kbd {
  font-family: var(--font-num); font-size: var(--fs-micro);
  padding: 2px 6px; border: 1px solid var(--line-2); border-radius: 4px;
  color: var(--dim); background: var(--panel-2);
}

/* Live odds freshness. The pulse is the only ambient motion in the product --
 * it says "this is live" without a spinner. */
.live {
  display: inline-flex; align-items: center; gap: var(--s2);
  font-size: var(--fs-sm); color: var(--ink-2); white-space: nowrap;
}
.live .dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--over);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--over) 60%, transparent);
  animation: pulse 2.4s ease-out infinite;
}
.live[data-stale] .dot { background: var(--neutral); animation: none; }
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--over) 55%, transparent); }
  70%  { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

.icon-btn {
  position: relative;
  display: grid; place-items: center;
  width: 34px; height: 34px;
  border: 1px solid transparent; border-radius: var(--r-chip);
  background: transparent; color: var(--ink-2); cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
}
.icon-btn:hover { background: var(--panel-3); color: var(--ink); }
.badge-count {
  position: absolute; top: 1px; right: 1px;
  min-width: 15px; height: 15px; padding: 0 4px;
  border-radius: var(--r-pill);
  background: var(--accent); color: var(--accent-ink);
  font-family: var(--font-num); font-size: 9px; font-weight: 700;
  display: grid; place-items: center;
}
/* The back button, beside the account button.
 *
 * Hidden on a desktop, where the nav is fully written out and the browser's
 * own back button is on screen. A phone has neither: the nav is icons and a
 * home-screen window has no chrome at all. */
.back-btn {
  display: none;
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--panel-3); border: 1px solid var(--line-2);
  place-items: center; cursor: pointer;
  font-size: 15px; color: var(--ink-2); line-height: 1;
}
.back-btn:hover { color: var(--ink); border-color: var(--accent); }
.back-btn:active { background: var(--panel-2); }
@media (max-width: 860px) {
  .back-btn { display: grid; }
  /* The player page writes its own "<- Board" link into the bar. Two back
     controls in one header is one too many, and the new one is the honest
     one -- it returns where you came from rather than always to the board. */
  .topbar .back { display: none; }
}

/* Visually hidden, still announced. For a label that a sighted reader gets
   from layout and a screen reader cannot. */
.sr-text {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* A skeleton that has the SHAPE of the answer: cards, with rows inside them.
   A bare "Loading..." does not say whether the page is working. */
.model-skeleton { display: grid; gap: var(--s3); }
.model-skeleton .card-head { padding: var(--s3) var(--s4); }
.skel-row {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) 0;
}
.skel-row + .skel-row { border-top: 1px solid var(--line); }

.avatar-btn {
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--panel-3); border: 1px solid var(--line-2);
  display: grid; place-items: center; cursor: pointer;
  font-size: var(--fs-micro); font-weight: 700; color: var(--ink-2);
}

/* ---- Cards -------------------------------------------------------------- */
.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  overflow: hidden;
  transition: box-shadow var(--t) var(--ease);
}
/* Soft inner glow on hover rather than a drop shadow: a raised card in a dark
 * UI reads as a lighter edge, not as a cast shadow. */
.card:hover { box-shadow: var(--glow); }
.card-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--line);
}
.card-head h3 { margin: 0; font-size: var(--fs-lg); }
.card-body { padding: var(--s4); }

/* ---- Chips and pills ---------------------------------------------------- */
.chip {
  display: inline-flex; align-items: center; gap: var(--s2);
  padding: 7px var(--s3);
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-chip);
  color: var(--ink-2); font-size: var(--fs-sm); font-weight: 500;
  cursor: pointer; white-space: nowrap;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.chip:hover { background: var(--panel-2); color: var(--ink); border-color: var(--line-2); }
.chip[aria-pressed="true"] {
  background: var(--accent-weak); border-color: var(--accent); color: var(--ink);
}
.chip .count { font-family: var(--font-num); color: var(--dim); }
.chip[aria-pressed="true"] .count { color: var(--accent); }

.pill {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 7px; border-radius: var(--r-chip);
  font-size: var(--fs-micro); font-weight: 700;
  letter-spacing: 0.04em; text-transform: uppercase;
  border: 1px solid currentColor;
}
.pill-goblin { color: var(--goblin); }
.pill-demon { color: var(--demon); }
.pill-fixed { color: var(--fixed); }
.pill-alt { color: var(--alt); }
.pill-soft { color: var(--over); }
.pill-neutral { color: var(--neutral); }
.pill-tough { color: var(--under); }
.pill-solid { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

/* ---- Heat ----------------------------------------------------------------
 * Colour never carries meaning alone: a hit rate always renders with its
 * sample size, and over/under always carry an arrow as well as a hue.
 */
.heat { font-weight: 700; padding: 3px 7px; border-radius: var(--r-chip); display: inline-block; }
.heat-5 { color: var(--heat-5); background: color-mix(in srgb, var(--heat-5) calc(var(--heat-tint)*100%), transparent); }
.heat-4 { color: var(--heat-4); background: color-mix(in srgb, var(--heat-4) calc(var(--heat-tint)*100%), transparent); }
.heat-3 { color: var(--heat-3); background: color-mix(in srgb, var(--heat-3) calc(var(--heat-tint)*100%), transparent); }
.heat-2 { color: var(--heat-2); background: color-mix(in srgb, var(--heat-2) calc(var(--heat-tint)*100%), transparent); }
.heat-1 { color: var(--heat-1); background: color-mix(in srgb, var(--heat-1) calc(var(--heat-tint)*100%), transparent); }
.heat-n { display: block; font-size: var(--fs-micro); color: var(--faint); font-weight: 500; }

.side-over { color: var(--over); }
.side-under { color: var(--under); }
.nodata { color: var(--faint); font-style: italic; font-size: var(--fs-micro); }

/* Awaiting data: a visible placeholder, never a blank. "No data yet" and
 * "the value is zero" are different facts and must not look the same. */
.pending {
  display: inline-block; min-width: 28px; height: 8px; border-radius: var(--r-pill);
  background: repeating-linear-gradient(90deg, var(--line-2) 0 5px, transparent 5px 9px);
}

/* The model DECLINED, which is not the same as data not having arrived.
 *
 * `.pending` above is a dashed bar meaning "not here yet". This cell is a
 * finished answer: the line sits below the volume the stat was calibrated
 * over, so the model refuses to price it. Drawn as a quiet "n/a" with the
 * dotted underline that means "there is a reason, hover it" -- an empty cell
 * and a deliberately empty cell looked identical, so the restraint read as a
 * failure. Focusable, so the reason is reachable without a mouse. */
.declined {
  font-size: var(--fs-micro); color: var(--faint); cursor: help;
  border-bottom: 1px dotted var(--line-2);
}
.declined:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* A line that just moved pulses once. */
.just-moved { animation: flash var(--t) var(--ease) 2; }
@keyframes flash {
  50% { background: color-mix(in srgb, var(--accent) 22%, transparent); }
}

/* ---- Command palette ---------------------------------------------------- */
.palette-backdrop {
  position: fixed; inset: 0; z-index: 90;
  background: var(--scrim);
  backdrop-filter: blur(2px);
  display: grid; place-items: start center;
  padding-top: 12vh;
}
.palette-backdrop[hidden] { display: none; }
.palette {
  width: min(620px, 92vw);
  background: var(--panel-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-pop);
  overflow: hidden;
}
.palette-input {
  width: 100%; padding: var(--s4);
  background: none; border: 0; border-bottom: 1px solid var(--line);
  color: var(--ink); font: 500 var(--fs-lg)/1.3 var(--font); outline: 0;
}
.palette-input::placeholder { color: var(--dim); }
.palette-list { max-height: 48vh; overflow-y: auto; padding: var(--s2); }
.palette-item {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s2) var(--s3); border-radius: var(--r-chip);
  cursor: pointer; font-size: var(--fs-sm);
}
.palette-item[aria-selected="true"] { background: var(--accent-weak); box-shadow: var(--glow-accent); }
.palette-item .kind {
  margin-left: auto; font-size: var(--fs-micro);
  color: var(--dim); text-transform: uppercase; letter-spacing: var(--track-lab);
}
.palette-empty { padding: var(--s5); text-align: center; color: var(--dim); font-size: var(--fs-sm); }

/* The short nav label exists only for the phone strip; see the mobile block
   below. Hidden by default so the sidebar reads in full names. */
.nav-short { display: none; }

/* ---- Court motif -------------------------------------------------------- */
/* A faint half-court line for empty states and share cards. Pure CSS so there
 * is no image to load. */
.court-motif {
  position: relative; overflow: hidden;
}
.court-motif::before {
  content: ""; position: absolute; inset: 0;
  background:
    radial-gradient(circle at 50% 100%, transparent 78px, var(--line) 78px, var(--line) 79px, transparent 80px),
    linear-gradient(to right, transparent calc(50% - 0.5px), var(--line) calc(50% - 0.5px), var(--line) calc(50% + 0.5px), transparent calc(50% + 0.5px));
  opacity: 0.5; pointer-events: none;
}

/* ---- Mobile -------------------------------------------------------------- */
@media (max-width: 860px) {
  .app, .app[data-nav="collapsed"] { grid-template-columns: 1fr; }
  .nav {
    position: sticky; top: 0; height: auto; z-index: 50;
    flex-direction: row; align-items: center;
    overflow-x: auto; overflow-y: hidden;
    border-right: 0; border-bottom: 1px solid var(--line);
    padding: var(--s2);
    scrollbar-width: none;
  }
  .nav::-webkit-scrollbar { display: none; }
  /* In a row-direction nav the brand is a flex item like any other, so it was
   * being squeezed -- and brand-word is nowrap, so it overflowed its own box
   * and printed across the nav items beside it. */
  .nav .brand { flex: none; padding: var(--s1) var(--s2); color: var(--ink); }
  .nav-section { margin-top: 0; display: flex; gap: var(--s1); }
  .nav-section > .lab, .nav-spacer, .nav .btn-block { display: none; }
  /* FOUR tabs and a More button, not eight tabs and a scrollbar.
   *
   * Measured before this: 742px of menu in a 390px window. Four of eight
   * visible, no anchor, and on /model or /sim the one item telling you where
   * you were had scrolled off the left edge. Scrolling was not the fix --
   * there were simply too many destinations for the space, and half of them
   * are things Paul reads on a laptop.
   *
   * Nothing scrolls now. The strip holds exactly what it can hold. */
  .nav { overflow-x: visible; overflow-y: visible; position: relative; }
  /* The brand went. It cost about a quarter of the strip to name an app you
     have already opened, with the page title directly beneath it. */
  .nav .brand { display: none; }
  .nav .nav-primary {
    display: flex; gap: 2px; flex: 1; min-width: 0;
    justify-content: space-between;
  }
  .nav .nav-primary .lab { display: none; }
  .nav a.nav-item {
    flex: 0 1 auto; min-width: 0; gap: 5px;
    padding: 7px var(--s2); font-size: var(--fs-micro);
  }
  .nav a.nav-item .nav-label { overflow: hidden; text-overflow: ellipsis; }
  .nav a.nav-item:has(.nav-short) .nav-label { display: none; }
  .nav .nav-short { display: inline; }
  /* Active: a soft panel and the brand colour, not a loud fill. The item's
     own ink stays on its icon, so the row reads as "this one, and it is
     still Trend" rather than losing its identity to the selected state. */
  .nav a.nav-item[aria-current="page"] {
    background: var(--accent-weak); color: var(--ink);
    box-shadow: inset 0 -2px 0 var(--accent);
  }

  .nav-more {
    display: inline-flex; align-items: center; gap: 5px; flex: none;
    padding: 7px var(--s2);
    border: 0; border-radius: var(--r-chip);
    background: transparent; color: var(--ink-2);
    font-family: inherit; font-size: var(--fs-micro); font-weight: 500;
    cursor: pointer;
  }
  .nav-more .ico { font-size: 15px; line-height: 1; }
  .nav-more[aria-expanded="true"] { background: var(--panel-3); color: var(--ink); }
  /* Marked when the page you are ON lives behind More, so the strip still
     tells you where you are even though the item is not on it. */
  .nav-more[data-holds-current] { color: var(--ink); }
  .nav-more[data-holds-current]::after {
    content: ""; position: absolute; bottom: 0; left: var(--s2);
    right: var(--s2); height: 2px; background: var(--accent);
  }
  .nav-more { position: relative; }

  /* The dropdown IS the rest of the nav, not a copy of it. */
  .nav .nav-rest {
    position: absolute; top: 100%; right: var(--s2); z-index: 60;
    min-width: 190px; padding: var(--s2);
    background: var(--panel-2); border: 1px solid var(--line-2);
    border-radius: var(--r-card);
    box-shadow: 0 14px 30px rgb(0 0 0 / 55%);
    display: grid; gap: var(--s1);
  }
  .nav .nav-rest[hidden] { display: none; }
  .nav .nav-rest .nav-section { display: grid; gap: 2px; margin: 0; }
  .nav .nav-rest .lab {
    display: block; padding: var(--s1) var(--s2) 0;
    font-family: var(--font-display); text-transform: uppercase;
    letter-spacing: var(--track-lab); font-size: var(--fs-lab);
    color: var(--dim);
  }
  .nav .nav-rest a.nav-item {
    width: 100%; padding: 10px var(--s2); font-size: var(--fs-sm);
    min-height: 44px;
  }
  .nav .nav-rest a.nav-item .nav-label { display: inline; }
  .nav .nav-rest .nav-short { display: none; }
  .topbar {
    padding: 0 var(--s3); gap: var(--s2);
    /* Its children totalled 416px in a 390px viewport. min-width: 0 lets the
     * search box give way instead of pushing the avatar off the edge. */
    min-width: 0;
  }
  .topbar .live span { display: none; }
  .topbar .search { min-width: 0; }
  /* A command palette prompt is noise without a keyboard to use it with. */
  .topbar .chip { display: none; }
  .search { max-width: none; }
  .search .kbd { display: none; }
}

@media (max-width: 560px) {
  .nav .brand-word { display: none; }
}

/* ---- "There is more to the right" ---------------------------------------
 *
 * A phone shows no scrollbar at rest, so anything running off the right edge
 * looks like something that ends there. Paul found this upright on a phone:
 * "you have to scroll to the right a lot, and people may not know that they
 * can even scroll."
 *
 * Here rather than in board.css because it is not about boards: the player
 * page scrolls its prop tabs and its on/off table and does not load that
 * stylesheet at all.
 *
 * The class is set from JS only when there is genuinely something further
 * right, and cleared once you reach the end -- a fade that never clears reads
 * as a broken last column rather than as more to come.
 */
/* No media query.
 *
 * This used to be scoped to phones, and the class is set from JS only when
 * something genuinely overflows -- so between 861px and about 1023px the
 * players table scrolled sideways with the class applied and no fade drawn.
 * The last column sat off the right edge of a 648px main column with nothing
 * saying there was more, which reads as a clipped table rather than a
 * scrollable one. A laptop at 900px is exactly as unable to see the overflow
 * as a phone is. */
.can-scroll-x {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent);
          mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent);
}
.can-scroll-x.at-end-x {
  -webkit-mask-image: none; mask-image: none;
}

/* ---- Segmented controls -------------------------------------------------
 *
 * Here rather than in board.css because the player page uses them too and
 * does not load board.css. See the note left in its place there. */
/* A segmented control rather than a dropdown: the options are few enough to
 * show, and a visible set of choices is faster than a menu that hides them. */
/* min-width: 0 because a flex item defaults to min-width: auto and so refuses
 * to shrink below its content. Without it the mobile rule below sets
 * overflow-x: auto on an element that is already as wide as its buttons, so
 * nothing scrolls and the whole PAGE goes sideways instead -- the same trap
 * `.app > *` carries a note about in app.css. */
.seg { display: flex; flex-wrap: wrap; gap: 3px; min-width: 0; }
.segb {
  padding: 5px var(--s3);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-chip);
  color: var(--ink-2); font-size: var(--fs-sm); font-weight: 500;
  cursor: pointer; white-space: nowrap; text-transform: capitalize;
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast);
}
.segb:hover { background: var(--panel-2); color: var(--ink); }
/* Selected, without shouting.
 *
 * A solid accent fill on every pressed chip meant four or five saturated
 * orange blocks across the filter bar at all times, including on the two
 * groups that are pressed by DEFAULT and therefore say nothing. Paul's eye
 * strain was traced to chip density and saturation rather than the dark
 * background, and this is the densest run of chips in the product.
 *
 * A tint plus an accent border and accent text reads as clearly selected --
 * it is the only chip with any colour in it at all -- at a fraction of the
 * area. The solid fill is kept for :active, so pressing still feels like
 * pressing. */
.segb[aria-pressed="true"] {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-color: var(--accent);
  color: var(--accent);
}
.segb[aria-pressed="true"]:hover {
  background: color-mix(in srgb, var(--accent) 24%, transparent);
}
.segb[aria-pressed="true"]:active {
  background: var(--accent); color: var(--accent-ink);
}
