/*
 * Capper Tracker — application styles.
 *
 * Dark theme is the default; a light theme is applied when the <html> element
 * carries data-theme="light". Colors are driven by custom properties so the
 * whole UI re-skins from the token block below — charts.js reads the same
 * properties via getComputedStyle, so a token is the ONLY place a colour is
 * allowed to live. A hardcoded hex in a template is invisible to the light
 * theme and to the chart layer.
 *
 * Layout tiers (the card switch sits at 900px, not 760px: the plays and
 * leaderboard tables carry 8-11 columns and are already cramped at iPad
 * portrait, so 768-899 gets cards and only landscape gets real tables):
 *   base          phone: card lists, bottom tab bar, filters in a sheet
 *   >= 640px      2-up stat grids
 *   >= 900px      tables, top nav links + search, inline filter bar, no tab bar
 *   >= 1180px     container max-width cap
 */

/* ---- Theme tokens ------------------------------------------------------- */
:root,
:root[data-theme="dark"] {
  --bg: #0d1117;
  --bg-elev: #161b22;
  --bg-elev2: #1c2330;
  --bg-inset: #0b0f15;
  --border: #2a323d;
  --border-soft: #222933;
  --text: #e6edf3;
  --text-dim: #9aa7b4;
  --text-mute: #6b7785;
  --accent: #4a9eff;
  --accent-2: #7c5cff;
  --accent-contrast: #ffffff;
  --pos: #3fb950;
  --neg: #f85149;
  --warn: #d29922;
  --push: #8b949e;
  --hot: #ff7b35;
  --cold: #58a6ff;
  --fade: #c07cff;
  --shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
  --radius: 10px;
  --radius-sm: 6px;

  /* Layout / touch metrics. --tap is the 44px minimum interactive size; the
     tab bar reserves the notch inset so the last row of a list is reachable. */
  --nav-h: 52px;
  --tabbar-h: calc(56px + env(safe-area-inset-bottom));
  --tap: 44px;
  /* 12px floor: anything smaller is unreadable on a phone and several labels
     had drifted to 0.68rem. */
  --font-min: 0.75rem;

  /* Status-pill colours. These replace hardcoded hexes that were inline in
     home.html and therefore identical (and wrong) in the light theme. */
  --tag-text: #ffffff;
  --tag-confirmed: #1f7a3f;
  --tag-max: #b03030;
  --tag-big: #8a5a00;
  --tag-live: #7a1f5a;
  --tag-final: #3a3a3a;
  --tag-final-text: #dddddd;
  --tag-await: #5a4a00;
  --tag-await-text: #ffd479;
  --tag-postponed: #6a4a00;
  --tag-soon: #1f4a7a;
}

:root[data-theme="light"] {
  --bg: #f4f6f8;
  --bg-elev: #ffffff;
  --bg-elev2: #eef1f5;
  --bg-inset: #f0f2f5;
  --border: #d5dbe2;
  --border-soft: #e5e9ee;
  --text: #1c2128;
  --text-dim: #55606c;
  --text-mute: #7b8794;
  --accent: #1f6feb;
  --accent-2: #6f42c1;
  --accent-contrast: #ffffff;
  --pos: #1a7f37;
  --neg: #cf222e;
  --warn: #9a6700;
  --push: #6b7785;
  --hot: #d9480f;
  --cold: #1f6feb;
  --fade: #8250df;
  --shadow: 0 6px 20px rgba(31, 41, 55, 0.12);

  --tag-text: #ffffff;
  --tag-confirmed: #1a7f37;
  --tag-max: #cf222e;
  --tag-big: #9a6700;
  --tag-live: #a0326e;
  --tag-final: #57606a;
  --tag-final-text: #ffffff;
  --tag-await: #7d5a00;
  --tag-await-text: #ffffff;
  --tag-postponed: #8a6100;
  --tag-soon: #1f6feb;
}

/* ---- Base --------------------------------------------------------------- */
* {
  box-sizing: border-box;
}

html {
  background: var(--bg);
  /* A grey flash on every tap reads as a rendering bug on touch. */
  -webkit-tap-highlight-color: transparent;
}

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 15px;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  /* No page may scroll the body sideways. Wide content (tables, charts) gets
     its own overflow container instead — a horizontally shifting page makes the
     bottom tab bar and sticky filter bar drift off screen. */
  overflow-x: hidden;
  /* Safety net for unbreakable strings we do not control: raw JSON alert payloads,
     Discord channel ids, slugs and URLs. break-word only splits a word that would
     otherwise overflow, so ordinary prose is untouched and .nowrap cells still win. */
  overflow-wrap: break-word;
}

/* Kill the 300ms tap delay on every control. */
a, button, input, select, label, .segmented button {
  touch-action: manipulation;
}

/* Keyboard focus must stay visible even though :hover is styled away on touch. */
a:focus-visible,
.btn:focus-visible,
.icon-btn:focus-visible,
.segmented button:focus-visible,
.tabbar a:focus-visible,
.tabbar button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

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

h1, h2, h3, h4 {
  margin: 0 0 0.5rem;
  line-height: 1.25;
  font-weight: 650;
}
h1 { font-size: 1.5rem; }
h2 { font-size: 1.2rem; }
h3 { font-size: 1.02rem; }

p { margin: 0 0 0.75rem; }

code, pre {
  font-family: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;
  font-size: 0.86em;
}

hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: 1.25rem 0;
}

.muted { color: var(--text-mute); }
.dim { color: var(--text-dim); }
.small { font-size: 0.82rem; }
.mono { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; }
.nowrap { white-space: nowrap; }
.right { text-align: right; }
.center { text-align: center; }
.pos { color: var(--pos); }
.neg { color: var(--neg); }
.flat { color: var(--text-dim); }
.hidden { display: none !important; }

/* ---- Layout ------------------------------------------------------------- */
.container {
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  padding: 1.25rem 1.1rem 3rem;
}

.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1.1rem;
}
.page-head p { margin: 0.15rem 0 0; }

.row { display: flex; gap: 0.75rem; flex-wrap: wrap; }
.row.between { justify-content: space-between; }
.row.tight { gap: 0.4rem; }
.grow { flex: 1 1 auto; }

/* ---- Top nav ------------------------------------------------------------ */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--bg-elev) 92%, transparent);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--border);
}
.nav-inner {
  max-width: 1180px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.55rem 1.1rem;
  flex-wrap: wrap;
}
.brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 750;
  color: var(--text);
  letter-spacing: -0.01em;
}
.brand:hover { text-decoration: none; }
.brand .dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 60%, transparent);
}
.nav-links {
  display: flex;
  gap: 0.15rem;
  flex-wrap: wrap;
}
.nav-links a {
  color: var(--text-dim);
  padding: 0.35rem 0.6rem;
  border-radius: var(--radius-sm);
  font-weight: 550;
  font-size: 0.92rem;
}
.nav-links a:hover {
  color: var(--text);
  background: var(--bg-elev2);
  text-decoration: none;
}
.nav-links a.active {
  color: var(--text);
  background: var(--bg-elev2);
}
.nav-spacer { flex: 1 1 auto; }

.nav-search {
  display: flex;
  align-items: center;
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.12rem 0.2rem 0.12rem 0.7rem;
}
.nav-search input {
  border: none;
  background: transparent;
  color: var(--text);
  outline: none;
  width: 140px;
  font-size: 0.88rem;
  padding: 0.28rem 0.2rem;
}
.nav-search button {
  border: none;
  background: transparent;
  color: var(--text-mute);
  cursor: pointer;
  padding: 0.2rem 0.45rem;
  font-size: 0.9rem;
}
.nav-search button:hover { color: var(--text); }

.demo-badge {
  font-size: var(--font-min);
  font-weight: 750;
  letter-spacing: 0.06em;
  color: #1c2128;
  background: var(--warn);
  padding: 0.18rem 0.45rem;
  border-radius: 999px;
  text-transform: uppercase;
}

.icon-btn {
  border: 1px solid var(--border);
  background: var(--bg-inset);
  color: var(--text-dim);
  border-radius: var(--radius-sm);
  cursor: pointer;
  padding: 0.32rem 0.5rem;
  font-size: 0.95rem;
  line-height: 1;
}
.icon-btn:hover { color: var(--text); border-color: var(--accent); }

/* ---- Footer ------------------------------------------------------------- */
.footer {
  border-top: 1px solid var(--border);
  color: var(--text-mute);
  font-size: 0.82rem;
  text-align: center;
  padding: 1.4rem 1rem 2rem;
}

/* ---- Cards -------------------------------------------------------------- */
.card {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem 1.05rem;
}
.card + .card { margin-top: 1rem; }
.card-title {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-mute);
  margin-bottom: 0.55rem;
}

.grid {
  display: grid;
  gap: 0.85rem;
}
.grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
.grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid.cols-2 { grid-template-columns: repeat(2, 1fr); }

/* ---- Stat tiles --------------------------------------------------------- */
.stat {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.85rem 0.95rem;
}
.stat .label {
  font-size: var(--font-min);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-mute);
}
.stat .value {
  font-size: 1.7rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  margin-top: 0.15rem;
  font-variant-numeric: tabular-nums;
}
.stat .sub { font-size: 0.78rem; color: var(--text-dim); }

/* ---- Buttons ------------------------------------------------------------ */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  border: 1px solid var(--border);
  background: var(--bg-elev2);
  color: var(--text);
  border-radius: var(--radius-sm);
  padding: 0.42rem 0.8rem;
  font-size: 0.9rem;
  font-weight: 550;
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s;
  font-family: inherit;
}
.btn:hover { border-color: var(--accent); text-decoration: none; }
/* Touch has no hover, so press feedback has to come from :active or a tap looks
   like it did nothing until the network answers. */
.btn:active { filter: brightness(0.92); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}
.btn.primary:hover { filter: brightness(1.08); }
.btn.danger { border-color: var(--neg); color: var(--neg); background: transparent; }
.btn.danger:hover { background: color-mix(in srgb, var(--neg) 14%, transparent); }
.btn.ghost { background: transparent; }
.btn.sm { padding: 0.28rem 0.55rem; font-size: 0.82rem; }

/* ---- Forms -------------------------------------------------------------- */
label.field {
  display: block;
  margin-bottom: 0.7rem;
}
label.field > span {
  display: block;
  font-size: 0.78rem;
  color: var(--text-dim);
  margin-bottom: 0.22rem;
}
input, select, textarea {
  font-family: inherit;
  font-size: 0.9rem;
  color: var(--text);
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.42rem 0.55rem;
  width: 100%;
  outline: none;
}
input:focus, select:focus, textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 25%, transparent);
}
textarea { resize: vertical; min-height: 90px; }
select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--text-mute) 50%),
    linear-gradient(135deg, var(--text-mute) 50%, transparent 50%);
  background-position: calc(100% - 15px) 55%, calc(100% - 10px) 55%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 1.8rem;
}
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: flex-end;
}
.filters .field { margin-bottom: 0; }
.filters .field { min-width: 130px; }
.filters .search-field { min-width: 180px; }
.inline-field { display: flex; align-items: center; gap: 0.4rem; }
.inline-field input[type="checkbox"] { width: auto; }
/* Bottom-aligns with the inputs beside it without the &nbsp; ghost label and the
   hardcoded 2.05rem height that used to fake the alignment. */
.filters .inline-field { align-self: flex-end; padding-bottom: 0.35rem; }

/* ---- Tables ------------------------------------------------------------- */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elev);
}
table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
}
/* No position:sticky here: the table lives inside .table-wrap{overflow-x:auto},
   which is a scroll container with no vertical scrolling, so a sticky header
   could never stick — and top:0 would ignore the sticky nav's height anyway. */
thead th {
  text-align: left;
  font-size: var(--font-min);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-mute);
  font-weight: 650;
  padding: 0.6rem 0.7rem;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  background: var(--bg-elev);
}
tbody td {
  padding: 0.55rem 0.7rem;
  border-bottom: 1px solid var(--border-soft);
  vertical-align: middle;
  font-variant-numeric: tabular-nums;
}
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--bg-elev2); }
/* Max / high-unit plays pop out of the feed. */
tbody tr.big-play td {
  background: color-mix(in srgb, var(--tag-max) 9%, transparent);
}
tbody tr.big-play td:first-child { border-left: 3px solid var(--tag-max); }
tbody tr.big-play:hover td { background: color-mix(in srgb, var(--tag-max) 16%, transparent); }
th.num, td.num { text-align: right; }
td .capper-link { font-weight: 600; color: var(--text); }
td .capper-link:hover { color: var(--accent); }
/* Board membership beside the capper name: 🔥 hot / 🧊 fade, at a glance. */
.board-badge { font-size: 0.8rem; margin-left: 0.25rem; cursor: default; }

/* Sparkline cell canvas. sparkline() reads clientWidth * devicePixelRatio, so it
   stays crisp at whatever size CSS gives it. */
canvas.spark {
  width: 92px;
  height: 26px;
  display: block;
}

/* ---- Badges ------------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.4rem;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  font-size: var(--font-min);
  font-weight: 700;
  line-height: 1.5;
  border: 1px solid transparent;
}
.badge.win { color: var(--pos); background: color-mix(in srgb, var(--pos) 16%, transparent); }
.badge.loss { color: var(--neg); background: color-mix(in srgb, var(--neg) 16%, transparent); }
.badge.push { color: var(--push); background: color-mix(in srgb, var(--push) 18%, transparent); }
.badge.void { color: var(--text-mute); background: var(--bg-elev2); }
.badge.pending { color: var(--accent); background: color-mix(in srgb, var(--accent) 15%, transparent); }
.badge.warn { color: var(--warn); background: color-mix(in srgb, var(--warn) 16%, transparent); }
.badge.flat { color: var(--text-dim); background: var(--bg-elev2); }

.pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: var(--font-min);
  padding: 0.12rem 0.5rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--text-dim);
  background: var(--bg-inset);
  text-transform: capitalize;
}
.pill.telegram { color: #34a0da; border-color: color-mix(in srgb, #34a0da 40%, var(--border)); }
.pill.x { color: var(--text); }
.pill.discord { color: #7d8cf0; border-color: color-mix(in srgb, #7d8cf0 40%, var(--border)); }
.pill.manual { color: var(--text-dim); }

/* Permalink pills are real navigation. At ~20px they were the smallest tap
   target in the app; the ::after grows the hit area without moving anything. */
a.pill {
  position: relative;
  min-height: 26px;
  font-weight: 700;
  text-decoration: none;
}
a.pill::after { content: ""; position: absolute; inset: -9px; }
a.pill:hover { text-decoration: none; border-color: var(--accent); }

/* ---- Status tags (play state) ------------------------------------------- */
/* Every one of these was an inline background:#hex in home.html, which meant the
   light theme rendered white-on-pale and charts.js could not see them. */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: var(--font-min);
  font-weight: 650;
  line-height: 1.5;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  border: 1px solid transparent;
  color: var(--tag-text);
  white-space: nowrap;
}
.tag.confirmed { background: var(--tag-confirmed); }
.tag.max { background: var(--tag-max); font-weight: 750; letter-spacing: 0.04em; }
.tag.big { background: var(--tag-big); }
.tag.live { background: var(--tag-live); }
.tag.final { background: var(--tag-final); color: var(--tag-final-text); }
.tag.await { background: var(--tag-await); color: var(--tag-await-text); }
.tag.postponed { background: var(--tag-postponed); }
.tag.soon { background: var(--tag-soon); }

.chip {
  display: inline-flex;
  gap: 0.3rem;
  align-items: center;
  font-size: 0.78rem;
  font-weight: 650;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  font-variant-numeric: tabular-nums;
}
.chip.hot { color: var(--hot); background: color-mix(in srgb, var(--hot) 15%, transparent); }
.chip.cold { color: var(--cold); background: color-mix(in srgb, var(--cold) 15%, transparent); }
.chip.fade { color: var(--fade); background: color-mix(in srgb, var(--fade) 15%, transparent); }
/* Counts of rows a ratio beside them EXCLUDES (unpriced wins, ungraded plays). Small and
   tappable-for-title rather than loud: they explain a gap, they are not a warning. */
.chip.excluded {
  color: var(--warn);
  background: color-mix(in srgb, var(--warn) 14%, transparent);
  font-size: 0.7rem;
  font-weight: 600;
  cursor: help;
}
.chip.muted { color: var(--text-mute); background: var(--bg-elev2); font-size: 0.7rem; }

/* ---- Segmented control (stat-window switch on the boards) --------------- */
.segmented {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-inset);
}
.segmented button {
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  padding: 0.35rem 0.85rem;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
}
.segmented button.active { background: var(--bg-elev2); color: var(--text); }
.segmented button:hover:not(.active) { color: var(--text); }

.dot-ok { color: var(--pos); }
.dot-err { color: var(--neg); }
.dot-idle { color: var(--text-mute); }

/* ---- Callouts / notices ------------------------------------------------- */
.callout {
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  background: var(--bg-elev);
  border-radius: var(--radius-sm);
  padding: 0.7rem 0.9rem;
  font-size: 0.88rem;
  color: var(--text-dim);
  margin-bottom: 1rem;
}
.callout.warn { border-left-color: var(--warn); }
.callout.fade { border-left-color: var(--fade); }

.empty {
  text-align: center;
  color: var(--text-dim);
  padding: 2.4rem 1rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: var(--bg-elev);
}
.empty h3 { color: var(--text); }
.empty .hint { font-size: 0.88rem; margin-top: 0.3rem; }

.loading {
  color: var(--text-mute);
  padding: 1.6rem;
  text-align: center;
  font-size: 0.9rem;
}

/* ---- Toggle switch ------------------------------------------------------ */
.switch {
  position: relative;
  display: inline-block;
  width: 38px;
  height: 21px;
  flex: none;
}
.switch input { opacity: 0; width: 0; height: 0; }
.switch .slider {
  position: absolute;
  inset: 0;
  background: var(--border);
  border-radius: 999px;
  transition: 0.15s;
  cursor: pointer;
}
.switch .slider::before {
  content: "";
  position: absolute;
  width: 15px;
  height: 15px;
  left: 3px;
  top: 3px;
  background: #fff;
  border-radius: 50%;
  transition: 0.15s;
}
.switch input:checked + .slider { background: var(--pos); }
.switch input:checked + .slider::before { transform: translateX(17px); }

/* ---- Charts ------------------------------------------------------------- */
/* Height tracks the viewport rather than sitting at a fixed 300px, which ate most
   of a phone screen. Chart.js is responsive + maintainAspectRatio:false, so it
   follows the box with no config change. */
.chart-box {
  position: relative;
  height: clamp(220px, 55vw, 320px);
  width: 100%;
}

/* ---- Toasts ------------------------------------------------------------- */
#toast-host {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-width: min(90vw, 360px);
}
.toast {
  background: var(--bg-elev2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  color: var(--text);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.8rem;
  font-size: 0.87rem;
  box-shadow: var(--shadow);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.25s, transform 0.25s;
}
.toast--show { opacity: 1; transform: translateY(0); }
.toast--error { border-left-color: var(--neg); }
.toast--success { border-left-color: var(--pos); }
.toast--info { border-left-color: var(--accent); }

/* ---- Identity header (capper detail) ------------------------------------ */
.identity {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  flex-wrap: wrap;
}
.avatar {
  width: 46px;
  height: 46px;
  border-radius: 12px;
  display: grid;
  place-items: center;
  font-weight: 750;
  font-size: 1.2rem;
  color: var(--accent-contrast);
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  flex: none;
}
.alias-tag {
  display: inline-block;
  font-size: var(--font-min);
  color: var(--text-dim);
  background: var(--bg-inset);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.08rem 0.45rem;
  /* "0 plays" wrapping between the number and the word reads as two badges. */
  white-space: nowrap;
}

.list-reset { list-style: none; margin: 0; padding: 0; }
.attr-item {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.45rem 0;
  border-bottom: 1px solid var(--border-soft);
}
.attr-item:last-child { border-bottom: none; }

.section { margin-top: 1.4rem; }
.section > h2 { margin-bottom: 0.7rem; }

/* ---- Alert cards -------------------------------------------------------- */
.alert-card { margin-bottom: 0.7rem; }
.alert-card.is-dismissed { opacity: 0.55; }
.alert-body { display: flex; align-items: flex-start; gap: 0.75rem; }
.alert-meta { align-items: center; margin-bottom: 0.3rem; }
.alert-time { margin-left: auto; }
.alert-dismiss { flex: none; }

/* ---- Bottom tab bar (mobile primary navigation) ------------------------- */
/* Hidden by default and switched on below 900px: on a pointer device the top nav
   is already one click away, and a fixed bar would only steal vertical space. */
.tabbar { display: none; }
.tabbar svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  flex: none;
}

/* ---- Bottom sheet (mobile filters / More menu) -------------------------- */
/* Toggled by a class rather than x-show so the slide transition can run; when
   closed it must be visibility:hidden or an invisible panel keeps eating taps
   over the content behind it. */
.sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 60;
  max-height: 82dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 16px 16px 0 0;
  box-shadow: var(--shadow);
  padding: 0.9rem 1rem calc(1rem + env(safe-area-inset-bottom));
  transform: translateY(100%);
  visibility: hidden;
  transition: transform 0.22s ease, visibility 0.22s;
}
.sheet.open { transform: none; visibility: visible; }
.sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.7rem;
}
.sheet-head strong { font-size: 1rem; }
.sheet-links { display: grid; gap: 0.35rem; }
.sheet-links a {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-height: var(--tap);
  padding: 0 0.6rem;
  border-radius: var(--radius-sm);
  color: var(--text);
  font-weight: 550;
}
.sheet-links a:hover { background: var(--bg-elev2); text-decoration: none; }
.sheet-links a:active { background: var(--bg-elev2); }
.sheet-apply { display: none; }
.sheet-search { margin-bottom: 0.7rem; flex-wrap: nowrap; }
/* Inputs are width:100%, which as a flex-basis pushed the Search button onto its
   own line. */
.sheet-search input { width: auto; flex: 1 1 6rem; min-width: 0; }
.sheet-search button { flex: none; }
.scrim {
  position: fixed;
  inset: 0;
  z-index: 55;
  background: rgba(0, 0, 0, 0.5);
}

/* ---- Sticky filter strip (mobile) --------------------------------------- */
.filter-strip { display: none; }
.chip-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  flex: none;
  min-height: 40px;
  padding: 0 0.85rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-elev);
  color: var(--text);
  font: inherit;
  font-size: 0.86rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.chip-btn.on { border-color: var(--accent); color: var(--accent); }
.chip-btn:active { background: var(--bg-elev2); }

/* "Show all metrics" on a condensed board card. Meaningless on a real table. */
.card-toggle { display: none; }

/* An error message is the one thing you opened this page to read — never clip it
   to a fixed width. Two lines, then it expands on tap. */
.err-text {
  color: var(--text-mute);
  font-size: 0.82rem;
  overflow-wrap: anywhere;
}
.err-text.clamp {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  cursor: pointer;
}

/* Nested scroll region (admin capper/source lists). overscroll-behavior stops a
   flick inside the list from scrolling the page once it hits the end. */
.scroller {
  max-height: 26rem;
  overflow: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

/* ---- <= 899px: cards, tab bar, sheets, touch sizing --------------------- */
@media (max-width: 899px) {
  /* Content must clear the fixed tab bar, and so must the toast stack.
     The clearance lives on the FOOTER, not on body: body's padding-bottom sits
     BELOW the footer, so on a short page the disclaimer rendered underneath the
     tab bar and was unreadable. The footer is the last element on every page
     (base.html), so padding it is what actually buys the gap. */
  .footer {
    padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 0.75rem);
  }
  #toast-host { bottom: calc(var(--tabbar-h) + 0.75rem); left: 1rem; right: 1rem; max-width: none; }

  .container { padding: 1rem 0.85rem 1.5rem; }
  .grid.cols-4 { grid-template-columns: repeat(2, 1fr); }
  .grid.cols-2 { grid-template-columns: 1fr; }

  /* The top bar keeps only brand + demo badge + theme toggle; the 7 links and the
     search box move to the tab bar and the More sheet. Left in place they wrapped
     into 2-3 rows and permanently stole ~110px of viewport. */
  .nav-links, .nav-search { display: none; }
  .nav-inner { flex-wrap: nowrap; min-height: var(--nav-h); }

  .tabbar {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 50;
    height: var(--tabbar-h);
    padding-bottom: env(safe-area-inset-bottom);
    background: color-mix(in srgb, var(--bg-elev) 96%, transparent);
    backdrop-filter: blur(8px);
    border-top: 1px solid var(--border);
  }
  .tabbar a, .tabbar button {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-height: 56px;
    border: 0;
    background: transparent;
    color: var(--text-dim);
    font: inherit;
    font-size: var(--font-min);
    font-weight: 600;
    cursor: pointer;
  }
  .tabbar a:hover { text-decoration: none; }
  .tabbar a:active, .tabbar button:active { background: var(--bg-elev2); }
  .tabbar .active { color: var(--accent); }

  .filter-strip {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    position: sticky;
    /* top:0 — the top nav's links are hidden here, so the strip is the only
       sticky chrome the list scrolls under. */
    top: 0;
    z-index: 40;
    padding: 0.5rem 0;
    margin-bottom: 0.6rem;
    overflow-x: auto;
    scrollbar-width: none;
    background: color-mix(in srgb, var(--bg) 92%, transparent);
    backdrop-filter: blur(8px);
  }
  .filter-strip::-webkit-scrollbar { display: none; }

  /* The inline filter card becomes the sheet — same fields, same bindings. */
  .filter-panel {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: 60;
    max-height: 82dvh;
    overflow-y: auto;
    overscroll-behavior: contain;
    border-radius: 16px 16px 0 0;
    box-shadow: var(--shadow);
    padding: 0.9rem 1rem calc(1rem + env(safe-area-inset-bottom));
    margin: 0 !important;
    transform: translateY(100%);
    visibility: hidden;
    transition: transform 0.22s ease, visibility 0.22s;
  }
  .filter-panel.open { transform: none; visibility: visible; }
  /* Two fields per row instead of one 130px-min field per row: the leaderboard's
     control card was ~590px tall before a single capper was on screen. */
  .filters .field { min-width: 0; flex: 1 1 45%; }
  .filters .search-field { flex: 1 1 100%; }
  .filter-panel .filters { flex-direction: column; align-items: stretch; }
  .filter-panel .filters .field { min-width: 0; width: 100%; }
  .filter-panel .quick-range { display: none; }
  .filter-panel .sheet-hide { display: none; }
  .sheet-apply { display: inline-flex; width: 100%; justify-content: center; min-height: 48px; }

  /* ---- Touch targets (44px minimum) ---- */
  .btn { min-height: var(--tap); padding: 0.55rem 1rem; }
  .btn.sm { min-height: 40px; padding: 0.4rem 0.75rem; font-size: 0.85rem; }
  .icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--tap);
    min-height: var(--tap);
  }
  /* 16px exactly: anything smaller and iOS Safari zooms the page on focus. */
  input, select, textarea { font-size: 16px; min-height: var(--tap); }
  textarea { min-height: 96px; }
  .switch { width: 48px; height: 28px; }
  .switch .slider::before { width: 22px; height: 22px; left: 3px; top: 3px; }
  .switch input:checked + .slider::before { transform: translateX(20px); }
  .inline-field { min-height: var(--tap); }
  .inline-field input[type="checkbox"] { width: 22px; height: 22px; }
  .segmented button { min-height: 40px; padding: 0.4rem 0.9rem; }
  .card-toggle {
    display: inline-flex;
    align-items: center;
    min-height: 32px;
    padding: 0 0.5rem;
    margin-left: 0.4rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: transparent;
    color: var(--text-dim);
    font: inherit;
    font-size: var(--font-min);
    font-weight: 650;
    cursor: pointer;
  }

  h1 { font-size: 1.3rem; }
  .stat .value { font-size: 1.4rem; }
  /* Admin/capper-detail list rows: name on the left and a button cluster on the
     right does not survive at 340px — the 44px buttons wrapped INTO the label text.
     Stack them so the actions get a full-width row of their own. */
  .attr-item { flex-direction: column; align-items: stretch; gap: 0.5rem; padding: 0.6rem 0; }
  .attr-item .row.tight { justify-content: flex-end; flex-wrap: wrap; }
  /* A 28px Dismiss button pinned to the top-right corner of a wrapping card is a
     miss waiting to happen; give it the full width under the text instead. */
  .alert-body { flex-direction: column; align-items: stretch; }
  .alert-dismiss { min-height: var(--tap); justify-content: center; }
  .alert-time { margin-left: 0; flex-basis: 100%; }
  .scroller { max-height: 20rem; }
  canvas.spark { width: 100%; max-width: 200px; height: 32px; }

  /* ---- Generic table -> label/value cards ---- */
  /* The wrapper loses its border and scroller: the rows ARE the cards now, and a
     bordered box around them read as a card inside a card. */
  .table-wrap {
    overflow-x: visible;
    border: none;
    background: transparent;
    border-radius: 0;
  }
  table.responsive thead { display: none; }
  table.responsive,
  table.responsive tbody,
  table.responsive tr,
  table.responsive td {
    display: block;
    width: 100%;
  }
  table.responsive tr {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    margin: 0 0 0.6rem;
    padding: 0.35rem 0.75rem;
    background: var(--bg-elev);
  }
  table.responsive tbody tr:hover { background: var(--bg-elev); }
  table.responsive tbody tr:active { background: var(--bg-elev2); }
  table.responsive td {
    border-bottom: 1px solid var(--border-soft);
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    text-align: right;
    padding: 0.45rem 0.05rem;
  }
  table.responsive td:last-child { border-bottom: none; }
  /* A card is only ~340px wide, and unbreakable values (Discord channel ids,
     Telegram -100… ids, slugs) ran straight off the right edge where the body's
     overflow-x:hidden silently cut them off. */
  table.responsive td { overflow-wrap: anywhere; }
  table.responsive td > * { min-width: 0; }
  table.responsive td::before {
    content: attr(data-label);
    font-size: var(--font-min);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-mute);
    font-weight: 650;
    text-align: left;
    flex: none;
  }
  table.responsive td.num { text-align: right; }

  /* Once tds are blocks, a border-left on td:first-child decorates one sub-cell
     instead of the row — and in the plays card that cell is the DATE, which sits at
     the RIGHT edge, so the stripe appeared as a stray red tick mid-card. The
     highlight moves to the card itself and the old edge must be switched off. */
  table.responsive tbody tr.big-play {
    border-left: 3px solid var(--tag-max);
    background: color-mix(in srgb, var(--tag-max) 9%, transparent);
  }
  table.responsive tbody tr.big-play td:first-child { border-left: none; }
  table.responsive tbody tr.big-play td,
  table.responsive tbody tr.big-play:hover td { background: transparent; }

  /* ---- Plays cards (home + capper detail) ---- */
  /* Purpose-built, still driven entirely by the data-label attributes already in
     the markup: line 1 capper + platform pills, line 2 the pick with its status
     tags, then a labelled meta footer. Every Alpine binding is untouched. */
  table.cards-play tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    padding: 0.7rem 0.85rem 0.15rem;
  }
  table.cards-play td {
    display: block;
    border-bottom: none;
    padding: 0;
    text-align: left;
    min-width: 0;
    /* MUST override the generic card rule's width:100%. As a flex item with
       flex-basis:auto that width becomes the basis, so every cell claimed a whole
       line and the "card" was just the old one-row-per-field stack again. */
    width: auto;
  }
  table.cards-play td.num { text-align: left; }
  table.cards-play td::before { display: none; }

  table.cards-play td[data-label="Capper"] { order: 1; flex: 1 1 60%; font-weight: 600; }
  table.cards-play td[data-label="Date"] {
    order: 2;
    flex: 0 0 auto;
    margin-left: auto;
    font-size: var(--font-min);
    color: var(--text-mute);
  }
  /* This table has no Capper column, so Date is the whole first line and must
     not be shoved to the right edge on its own. */
  table.cards-play.no-capper td[data-label="Date"] { order: 1; margin-left: 0; }
  table.cards-play td[data-label="Pick"] {
    order: 3;
    flex: 1 1 100%;
    margin: 0.4rem 0 0;
    padding-bottom: 0.55rem;
    border-bottom: 1px solid var(--border-soft);
  }
  table.cards-play td[data-label="Sport"],
  table.cards-play td[data-label="Odds"],
  table.cards-play td[data-label="Units"],
  table.cards-play td[data-label="Result"],
  table.cards-play td[data-label="P/L"],
  table.cards-play td[data-label="CLV"] {
    order: 4;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: baseline;
    justify-content: flex-start;
    gap: 0.3rem;
    padding: 0.5rem 0.85rem 0.5rem 0;
    font-size: 0.85rem;
  }
  /* Labels only where the value is ambiguous on its own — a sport pill and a
     W/L badge need no caption, "-110" and "+0.91u" do. */
  table.cards-play td[data-label="Odds"]::before,
  table.cards-play td[data-label="Units"]::before,
  table.cards-play td[data-label="P/L"]::before,
  table.cards-play td[data-label="CLV"]::before {
    display: inline;
    content: attr(data-label);
    font-size: var(--font-min);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-mute);
    font-weight: 650;
  }
  table.cards-play tbody tr { cursor: pointer; }

  /* ---- Source cards ---- */
  /* Long values (channel ids, stack traces) squeezed into the right half of a
     label/value row are unreadable; these two stack under their caption instead. */
  table.cards-src td[data-label="Label"],
  table.cards-src td[data-label="Last error"] {
    display: block;
    text-align: left;
  }
  table.cards-src td[data-label="Label"]::before,
  table.cards-src td[data-label="Last error"]::before {
    display: block;
    margin-bottom: 0.15rem;
  }

  /* ---- Board cards (leaderboard + streaks) ---- */
  /* 11 columns became an 11-line stack per capper. Condensed card: rank + name,
     then the metrics that decide the board; the rest behind one toggle. */
  /* A 6-column grid rather than flex wrapping: the identity line must own its whole
     row (rank + name + toggle) and the metrics must line up in tidy thirds. With
     flex, the first metric squeezed onto the name's line whenever it happened to
     fit, so the header row changed shape from capper to capper. */
  table.cards-rank tr {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    align-items: start;
    column-gap: 0.5rem;
    row-gap: 0.55rem;
    padding: 0.75rem 0.85rem;
    cursor: pointer;
  }
  table.cards-rank td {
    display: block;
    border-bottom: none;
    padding: 0;
    text-align: left;
    min-width: 0;
    /* See cards-play: the generic width:100% would become each item's flex-basis. */
    width: auto;
  }
  table.cards-rank td.num { text-align: left; }
  /* The W-L-P cell is .nowrap so "16-2-0" never splits in a real table row. In a
     ~100px card cell that also pinned the "8 unpriced" chip beside the record,
     shoving it across the Units column. */
  table.cards-rank td.nowrap { white-space: normal; }
  table.cards-rank td::before {
    content: attr(data-label);
    display: block;
    font-size: var(--font-min);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-mute);
    font-weight: 650;
    text-align: left;
    flex: none;
  }
  /* Metrics run three to a row; the rank is folded into the card header beside the
     name rather than wasting a whole row of its own. */
  table.cards-rank td { grid-column: span 2; }
  table.cards-rank td[data-label="#"] {
    grid-column: span 1;
    justify-self: end;
    font-weight: 750;
    color: var(--text-dim);
  }
  table.cards-rank td[data-label="#"]::before { display: none; }
  table.cards-rank td[data-label="#"]::after { content: "·"; margin-left: 0.3rem; }
  table.cards-rank td[data-label="Capper"] { grid-column: span 5; }
  table.cards-rank td[data-label="Capper"]::before { display: none; }
  table.cards-rank td[data-label="L10"] { grid-column: 1 / -1; }
  /* Collapsed by default; .is-open comes from the per-row Alpine toggle. */
  table.cards-rank tr:not(.is-open) td.extra { display: none; }
}

/* ---- <= 639px: one-column stat grids ------------------------------------ */
@media (max-width: 639px) {
  /* cols-4 stays 2x2 — a full-width stat tile wastes a whole screen row — but
     cols-3 holds content-heavy cards that do not survive at ~110px wide. */
  .grid.cols-3 { grid-template-columns: 1fr; }
  /* Stacked one-per-row, the default tile padding pushed the first play ~150px
     further down the page. */
  .grid.cols-3 .stat { padding: 0.6rem 0.8rem; }
  .grid.cols-3 .stat .value { font-size: 1.3rem; margin-top: 0; }
  .grid { gap: 0.6rem; }
}

/* ---- >= 900px: real tables, inline filters, no tab bar ------------------ */
@media (min-width: 900px) {
  /* The filter panel is just a card here; its sheet chrome must not render. */
  .filter-panel .sheet-head { display: none; }
}
