/* Visual language follows the Claude design system rather than the PandaScore
   dark theme the FACEIT dashboard uses: warm off-white surfaces, flat 0.5px
   hairlines, 8px control / 12px card radius, sentence case, two font weights
   (400 and 500), and role colours — blue for accent, red for discrepancies.
   Every colour is a variable so the dark block below is the only override. */

:root {
  --surface-0: #faf9f5;
  --surface-1: #f1efe8;
  --surface-2: #ffffff;

  --text-primary: #1f1e1c;
  --text-secondary: #5f5e5a;
  --text-muted: #888780;

  --border: rgba(31, 30, 28, 0.12);
  --border-strong: rgba(31, 30, 28, 0.22);

  --text-accent: #185fa5;
  --bg-accent: #e6f1fb;
  --border-accent: #85b7eb;

  --text-danger: #a32d2d;
  --bg-danger: #fcebeb;
  /* A notification count should not invert with the theme: --text-danger is a
     deep red in light and a light pink in dark, and a pale pink disc on a dark
     UI reads as a sticker rather than as "there is something here". This stays
     a deep red in both, only lifting slightly in dark so it holds against a
     near-black ground. */
  --badge-bg: #a32d2d;
  --border-danger: #f09595;

  --text-success: #3b6d11;
  --bg-success: #eaf3de;

  --text-warning: #854f0b;
  --bg-warning: #faeeda;

  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text", "Segoe UI",
    system-ui, Roboto, Helvetica, Arial, sans-serif;
  --radius: 8px;
  --radius-card: 12px;
  --rail-width: 252px;
  --topbar-height: 53px;
}

html[data-theme="dark"] {
  --surface-0: #171614;
  --surface-1: #1f1e1c;
  --surface-2: #262523;

  --text-primary: #f5f4ef;
  --text-secondary: #b4b2a9;
  --text-muted: #888780;

  --border: rgba(245, 244, 239, 0.14);
  --border-strong: rgba(245, 244, 239, 0.26);

  --text-accent: #85b7eb;
  --bg-accent: #0c447c;
  --border-accent: #378add;

  --text-danger: #f09595;
  --bg-danger: #501313;
  --badge-bg: #c0392f;
  --border-danger: #a32d2d;

  --text-success: #97c459;
  --bg-success: #27500a;

  --text-warning: #fac775;
  --bg-warning: #633806;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--surface-0);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { font-weight: 500; margin: 0; }
a { color: var(--text-accent); text-decoration: none; }
a:hover { text-decoration: underline; }
.hidden { display: none !important; }
.muted { color: var(--text-secondary); }
.topbar-spacer { flex: 1 1 auto; }

/* ---------------------------------- topbar -------------------------------- */

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 10px 18px;
  background: var(--surface-2);
  border-bottom: 0.5px solid var(--border);
  flex-wrap: wrap;
}

.topbar-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 15px;
  font-weight: 500;
  white-space: nowrap;
}

.dot-logo { width: 7px; height: 7px; border-radius: 50%; background: var(--text-accent); }

/* Fifteen games do not fit beside the header controls, and letting them wrap
   would make a sticky header three rows tall on a narrow window. One row of its
   own that scrolls sideways instead: always the same height, nothing hidden
   behind a menu. */
.game-tabs {
  display: flex;
  gap: 6px;
  flex: 1 0 100%;
  order: 10;
  flex-wrap: nowrap;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
  padding-bottom: 2px;
}
.game-tab { flex: none; }

.game-tab {
  font-family: inherit;
  font-size: 13px;
  padding: 5px 13px;
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-primary);
  cursor: pointer;
}
.game-tab:hover { background: var(--surface-1); }
.game-tab.active {
  background: var(--bg-accent);
  border-color: var(--border-accent);
  color: var(--text-accent);
}

.status-text {
  font-size: 13px;
  color: var(--text-secondary);
  max-width: 420px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.status-text.error { color: var(--text-danger); }

.btn {
  font-family: inherit;
  font-size: 13px;
  padding: 5px 12px;
  background: transparent;
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--text-primary);
  cursor: pointer;
}
.btn:hover { background: var(--surface-1); }
.btn:active { transform: scale(0.98); }
.btn[disabled] { opacity: 0.45; cursor: default; }
.btn.primary { background: var(--bg-accent); border-color: var(--border-accent); color: var(--text-accent); }
/* A toggle button that is currently on. Same treatment as an active segment, so
   "this panel is open" reads the same way everywhere. */
.btn.active { background: var(--bg-accent); border-color: var(--border-accent); color: var(--text-accent); }
.btn.tiny { font-size: 12px; padding: 3px 8px; }

input[type="text"], input[type="search"] {
  font-family: inherit;
  font-size: 13px;
  width: 100%;
  height: 32px;
  padding: 0 10px;
  background: var(--surface-2);
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--text-primary);
}
input:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--bg-accent); border-color: var(--border-accent); }
label.check { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-secondary); cursor: pointer; }

/* ----------------------------------- shell -------------------------------- */

.shell { display: flex; align-items: flex-start; }

.rail {
  width: var(--rail-width);
  flex: 0 0 var(--rail-width);
  position: sticky;
  top: var(--topbar-height);
  max-height: calc(100vh - var(--topbar-height));
  display: flex;
  flex-direction: column;
  background: var(--surface-1);
  border-right: 0.5px solid var(--border);
}

.rail-head { padding: 12px; display: flex; flex-direction: column; gap: 8px; border-bottom: 0.5px solid var(--border); }
.rail-count { font-size: 12px; color: var(--text-muted); }
/* A text button that reads as part of the sentence around it. Used for the
   rail's "N finished tournaments are still linked · show" toggle, which must
   stay quiet — it is a note, not a control worth a button's weight. */
.linkish {
  font: inherit;
  color: var(--text-accent);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-decoration: underline;
}
.linkish:hover { text-decoration: none; }
#rail-ended-note { margin-top: 4px; }
.rail-list { overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 2px; }

.rail-item {
  font-family: inherit;
  text-align: left;
  background: transparent;
  border: 0.5px solid transparent;
  border-radius: var(--radius);
  color: var(--text-primary);
  padding: 8px 10px;
  cursor: pointer;
  width: 100%;
}
.rail-item:hover { background: var(--surface-2); }
.rail-item.active { background: var(--surface-2); border-color: var(--border-accent); }
.rail-item.unlinked .rail-name { color: var(--text-secondary); }
.segmented { display: flex; gap: 0; }
.seg {
  font-family: inherit;
  font-size: 12px;
  flex: 1 1 0;
  padding: 4px 0;
  background: transparent;
  border: 0.5px solid var(--border-strong);
  border-right-width: 0;
  color: var(--text-secondary);
  cursor: pointer;
}
/* `.seg` divides a fixed width and so carries no horizontal padding — right for
   the rail's filter and the view switch, wrong inside a wrapping actions row,
   where there is no width to divide and the labels collapse against each other
   ("AllChecksProblems"). In that context they size to their own text instead. */
.agenda-actions .segmented { flex: 0 0 auto; }
.agenda-actions .seg { flex: 0 0 auto; padding: 4px 10px; white-space: nowrap; }

.seg:first-child { border-radius: var(--radius) 0 0 var(--radius); }
.seg:last-child { border-radius: 0 var(--radius) var(--radius) 0; border-right-width: 0.5px; }
.seg:hover { background: var(--surface-2); }
.seg.active { background: var(--bg-accent); border-color: var(--border-accent); color: var(--text-accent); }

/* The header's view switch is not a segmented control.
   Boxed, it drew five heavy dividers across the top of the app and competed with
   the game tabs directly beneath it; the labels also wrapped to two lines, which
   pushed each badge onto a line of its own. These are tabs: no box, no dividers,
   the active one marked by colour, weight and a rule beneath it. Labels never
   wrap, and the strip scrolls sideways when it does not fit — the same thing
   `.game-tabs` already does under it. */
.view-switch {
  gap: 2px;
  overflow-x: auto;
  scrollbar-width: none;
}
.view-switch::-webkit-scrollbar { display: none; }
.view-switch .seg {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px 6px;
  border: 0;
  border-radius: 0;
  border-bottom: 2px solid transparent;
  background: none;
  white-space: nowrap;
  font-size: 13px;
}
.view-switch .seg:hover { background: none; color: var(--text-primary); }
.view-switch .seg.active {
  background: none;
  color: var(--text-accent);
  border-bottom-color: var(--text-accent);
  font-weight: 600;
}

/* One badge per linkable source, so an unlinked tournament is visible without
   opening it. "na" is a source that can never be linked for this game. */
.rail-badges { display: flex; gap: 4px; margin-top: 5px; }
.src-badge {
  font-size: 11px;
  line-height: 1.4;
  padding: 0 6px;
  border-radius: 6px;
  border: 0.5px dashed var(--border-strong);
  color: var(--text-muted);
  white-space: nowrap;
}
.src-badge.on {
  border-style: solid;
  border-color: var(--border-accent);
  background: var(--bg-accent);
  color: var(--text-accent);
}

.rail-name { font-size: 13px; line-height: 1.35; }
.rail-league { font-weight: 500; }
/* A linked tournament PandaScore has stopped listing, because it finished. It
   stays in the rail — it is still linked, and until 2026-09-05 it was invisible
   here while still being swept — but it reads as history, not as work. */
.rail-ended {
  margin-left: 6px;
  font-size: 10px;
  font-weight: 600;
  line-height: 1.5;
  padding: 0 5px;
  border-radius: 6px;
  background: var(--text-danger);
  color: var(--surface-0);
  white-space: nowrap;
}
/* Solid red, not red text: the rail already uses red *text* for the flag count
   two lines below, and a filled badge reads as a state rather than as another
   finding. */
.rail-item.ended .rail-name,
.rail-item.ended .rail-league { color: var(--text-secondary); }

.rail-meta { display: flex; align-items: center; gap: 6px; margin-top: 3px; font-size: 12px; color: var(--text-muted); }

/* Freshness is the thing being scanned for down the list, so it gets a dot that
   reads at a glance and a colour that only ever means one of three things. */
.rail-age { display: inline-flex; align-items: center; gap: 5px; }
.rail-age::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
.rail-age.fresh { color: var(--text-success); }
.rail-age.stale { color: var(--text-warning); }
.rail-age.never { color: var(--text-danger); }
.rail-flags { color: var(--text-danger); }
.rail-clean { color: var(--text-success); }

/* ----------------------------------- board -------------------------------- */

.board { flex: 1 1 auto; min-width: 0; padding: 16px 18px 48px; }
.board-empty { padding: 48px 16px; text-align: center; color: var(--text-secondary); font-size: 13px; }

.board-head { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
.board-head-main { min-width: 0; }
.board-head h2 { font-size: 18px; }
.board-sub { color: var(--text-secondary); font-size: 13px; margin-top: 3px; }
.board-head-actions { margin-left: auto; display: flex; align-items: center; gap: 10px; }

.sources-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-card);
  padding: 10px 12px;
}
.sources-strip { display: flex; gap: 8px; flex-wrap: wrap; flex: 1 1 auto; min-width: 0; }

.source-chip {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 6px 10px;
  background: var(--surface-1);
  border: 0.5px solid transparent;
  border-radius: var(--radius);
  min-width: 132px;
}
.source-chip.linked { border-color: var(--border-accent); }
.source-chip.working { border-color: var(--border-accent); background: var(--bg-accent); }
.source-chip.working .source-chip-name, .source-chip.working .source-chip-meta { color: var(--text-accent); }
.source-chip.stale { border-color: var(--text-warning); }
.source-chip.stale .source-chip-meta { color: var(--text-warning); }
.source-chip.failed { background: var(--bg-danger); border-color: var(--border-danger); }
.source-chip.failed .source-chip-name { color: var(--text-danger); }
.source-chip-name { font-size: 13px; font-weight: 500; }
.source-chip-meta { font-size: 12px; color: var(--text-secondary); }
.source-chip-meta.err { color: var(--text-danger); }

.links-panel {
  margin-top: 8px;
  padding: 12px;
  background: var(--surface-1);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-card);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 12px;
}
.link-card {
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 12px;
}
.link-card h4 { font-size: 13px; margin-bottom: 6px; }
.link-list { list-style: none; margin: 0 0 8px; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.link-list li { display: flex; align-items: center; gap: 6px; font-size: 12px; }
.link-ref { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.link-form { display: flex; gap: 6px; }
.link-actions { display: flex; gap: 6px; margin-top: 6px; }
.subpage-list { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.subpage-hint { flex: 1 1 100%; font-size: 12px; color: var(--text-secondary); }
.btn.subpage { font-size: 11px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.link-note { font-size: 12px; color: var(--text-warning); margin-top: 6px; }
.link-err { font-size: 12px; color: var(--text-danger); margin-top: 6px; }

.board-filters { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 16px 0 4px; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  padding: 3px 10px;
  border-radius: var(--radius);
  background: var(--surface-1);
  color: var(--text-secondary);
  white-space: nowrap;
}
.chip.bad { background: var(--bg-danger); color: var(--text-danger); }
.chip.warn { background: var(--bg-warning); color: var(--text-warning); }
.chip.good { background: var(--bg-success); color: var(--text-success); }
.chip.state-live { background: var(--bg-danger); color: var(--text-danger); }
.chip.state-upcoming { background: var(--bg-accent); color: var(--text-accent); }

/* ------------------------------- match cards ------------------------------ */

.day-head {
  position: sticky;
  top: var(--topbar-height);
  z-index: 5;
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 10px 2px 6px;
  margin-top: 12px;
  background: var(--surface-0);
  font-size: 13px;
  font-weight: 500;
  color: var(--text-primary);
  border-bottom: 0.5px solid var(--border);
}
.day-head .count { font-weight: 400; color: var(--text-muted); font-size: 12px; }

.cards-group { display: flex; flex-direction: column; gap: 8px; padding-top: 8px; }

.mcard {
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: var(--radius-card);
  padding: 12px 14px;
}
.mcard.flagged { border-color: var(--border-danger); }
.mcard.warn { border-color: var(--text-warning); }
.mcard.extra { border-color: var(--text-warning); border-style: dashed; }

.mcard-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.mcard-time { font-size: 13px; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.mcard-head .verdict { margin-left: auto; font-size: 12px; }
.verdict.bad { color: var(--text-danger); }
.verdict.warn { color: var(--text-warning); }
.verdict.good { color: var(--text-success); }
.verdict.neutral { color: var(--text-muted); }

.mcard-title { font-size: 13px; font-weight: 500; margin-bottom: 8px; }

.mcard-teams { display: flex; align-items: center; gap: 14px; margin-bottom: 12px; }
.team { display: flex; align-items: center; gap: 10px; flex: 1 1 0; min-width: 0; }
.team.right { justify-content: flex-end; }
.team-name { font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vs { font-size: 12px; color: var(--text-muted); flex: 0 0 auto; }

/* Either a crest or an acronym chip, never both. The chip keeps the filled
   circle so a logo-less team still reads as a team; a real logo sits on nothing,
   because a grey disc behind a transparent crest looks like a mistake. */
.logo-wrap {
  width: 30px;
  height: 30px;
  flex: 0 0 30px;
  border-radius: 50%;
  background: var(--surface-1);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-secondary);
}
.logo-wrap.has-logo { background: transparent; border-radius: 0; }
.logo-wrap.placeholder { background: transparent; border: 1px dashed var(--border); }
.logo-wrap img { width: 100%; height: 100%; object-fit: contain; }

/* --panes is set per card from the number of sources this game has (see
   paneSources in app.js); the fallback keeps the grid sane if it's ever absent. */
.mcard-sources { display: grid; grid-template-columns: repeat(var(--panes, 4), minmax(0, 1fr)); gap: 8px; }

.pane {
  background: var(--surface-1);
  border-radius: var(--radius);
  padding: 8px 10px;
  min-width: 0;
}
.pane-label { font-size: 12px; color: var(--text-muted); }
.pane-time { font-size: 16px; font-variant-numeric: tabular-nums; margin-top: 2px; }
.pane-note { font-size: 12px; color: var(--text-secondary); margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pane.ontime .pane-note { color: var(--text-success); }
.pane.bad { background: var(--bg-danger); }
.pane.bad .pane-label, .pane.bad .pane-time, .pane.bad .pane-note { color: var(--text-danger); }
.pane.warn { background: var(--bg-warning); }
.pane.warn .pane-label, .pane.warn .pane-time, .pane.warn .pane-note { color: var(--text-warning); }
.pane.idle .pane-time, .pane.idle .pane-label, .pane.idle .pane-note { color: var(--text-muted); }

.suggestion {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 10px;
  padding: 7px 9px;
  background: var(--bg-warning);
  border-radius: var(--radius);
  font-size: 12px;
  color: var(--text-warning);
}
.suggestion-text { flex: 1 1 auto; min-width: 0; }
.suggestion strong { font-weight: 500; }

.alias-form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; font-size: 13px; color: var(--text-secondary); }
.alias-form select {
  font-family: inherit;
  font-size: 13px;
  padding: 4px 6px;
  background: var(--surface-2);
  border: 0.5px solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--text-primary);
}

/* ---------------------------------- agenda ---------------------------------
   A dense, scannable list rather than the board's cards: the question here is
   "does anything in the next two days need attention", which is answered by
   reading down a column of times, not by studying one match. */

.view-switch { margin-left: 4px; }

.agenda { max-width: 1120px; margin: 0 auto; padding: 20px 20px 8px; }
.agenda-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.agenda-head h2 { font-size: 20px; font-weight: 500; }
.agenda-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.auto-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 14px 0 4px;
  padding: 8px 12px;
  background: var(--surface-1);
  border-radius: var(--radius);
  font-size: 12px;
  min-height: 34px;
}

.agenda-list { margin-top: 8px; }

/* Scoped to the agenda: an unscoped `.day-head` here overrode the board's own
   sticky day headers, which need the topbar's height as their offset rather
   than 0 — they were sliding under it. */
.agenda-list .day-head {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 12px 2px 6px;
  background: var(--surface-0);
  font-size: 13px;
  font-weight: 500;
  border-bottom: 0.5px solid var(--border);
}

/* One card per match. PandaScore on the first line, then a line per linked
   source, all sharing one grid so label / time / teams / format line up in
   columns — a disagreement is then a break in a column rather than something to
   read for. */
.pcard {
  display: grid;
  grid-template-columns: 4px 48px minmax(0, 1fr) 200px;
  align-items: stretch;
  border: 0.5px solid var(--border);
  border-radius: var(--radius-lg, 12px);
  background: var(--surface-0);
  margin: 10px 0;
  overflow: hidden;
  cursor: pointer;
}
.pcard:hover { background: var(--surface-1); }
/* The edge is the only thing carrying severity. Tinting a whole card turns a
   list of a hundred matches into noise. */
.pcard-edge { background: var(--border); }
.pcard.sev-bad .pcard-edge { background: var(--text-danger); }
.pcard.sev-warn .pcard-edge { background: var(--text-warning); }
.pcard.sev-ok .pcard-edge { background: var(--text-success); }

.pcard-game { display: flex; align-items: center; justify-content: center; padding: 12px 0; }
.gmono {
  width: 34px;
  height: 34px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 600;
  color: #fff;
  background: #6b7280;
}
/* PandaScore ships no videogame artwork, so each game gets a monogram rather
   than a borrowed logo. */
.gmono.cs2 { background: #d97706; }
.gmono.valorant { background: #dc2626; }
.gmono.lol { background: #1d4ed8; }
.gmono.dota2 { background: #b91c1c; }
.gmono.ow { background: #ea580c; }
.gmono.r6siege { background: #0f766e; }
.gmono.rl { background: #2563eb; }
.gmono.pubg { background: #ca8a04; }
.gmono.codmw { background: #4b5563; }
.gmono.fifa { background: #15803d; }
.gmono.kog { background: #7c3aed; }
.gmono.wildrift { background: #0369a1; }
.gmono.mlbb { background: #be185d; }
.gmono.sc2 { background: #1e40af; }
.gmono.bw { background: #475569; }

.pcard-rows {
  display: grid;
  /* The notes column is capped rather than `auto`: left to size itself it steals
     width from the teams, which then wrap and destroy the alignment the layout
     exists for. */
  grid-template-columns: 82px 52px minmax(0, 1fr) 46px minmax(0, 190px);
  align-items: center;
  gap: 1px 10px;
  padding: 8px 4px 8px 0;
  min-width: 0;
}
.prow { display: contents; }
.plabel {
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  justify-self: start;
}
.ptime { font-size: 14px; font-variant-numeric: tabular-nums; }
.ptime.anchor { font-weight: 500; }
.ptime.bad { color: var(--text-danger); }

/* One line, always. A truncated name still lines up with the one above it; a
   wrapped one does not. */
.pteams {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  flex-wrap: nowrap;
  overflow: hidden;
  font-size: 14px;
}
.pteams.sub { font-size: 13px; color: var(--text-secondary); }
/* Without a floor, flex shrinks one name to a single character to make room for
   the other. */
.tname { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 6ch; }
.tname.diff { color: var(--text-warning); }
.vs { color: var(--text-muted); font-size: 12px; flex: none; }
.crest { width: 18px; height: 18px; object-fit: contain; flex: none; }
.crest-ph {
  width: 18px;
  height: 18px;
  border-radius: 3px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 8px;
  font-weight: 600;
  background: var(--surface-2);
  color: var(--text-muted);
}

/* Best-of gets a column of its own rather than another chip in the notes: the
   format is a per-source fact like the time, and a column can be read straight
   down. A source that states none says so — a blank would read as agreement. */
.pbo { font-size: 12px; color: var(--text-secondary); justify-self: start; white-space: nowrap; }
.pbo.anchor { color: var(--text-primary); }
.pbo.diff { color: var(--text-warning); font-weight: 500; }
.pbo.none { color: var(--text-muted); }
.pbo.cached { color: var(--text-muted); font-style: italic; }

.pnotes {
  display: flex;
  gap: 5px;
  align-items: center;
  justify-self: end;
  flex-wrap: nowrap;
  overflow: hidden;
}
.ptag {
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 6px;
  background: var(--surface-1);
  color: var(--text-secondary);
  white-space: nowrap;
}
.ptag.bad { background: var(--bg-danger); color: var(--text-danger); }
.ptag.warn { background: var(--bg-warning); color: var(--text-warning); }
.ptag.ok { color: var(--text-success); }
.pmiss {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 13px;
  color: var(--text-warning);
  font-weight: 500;
}

.pcard-meta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
  gap: 3px;
  padding: 10px 14px;
  border-left: 0.5px solid var(--border);
  width: 200px;
  flex: none;
}
.pdate { font-size: 13px; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.pserie { font-size: 11px; color: var(--text-muted); text-align: right; max-width: 190px; }
.pref { font-size: 12px; color: var(--text-accent); text-decoration: none; }
.pref:hover { text-decoration: underline; }

/* Below this the rows and the right-hand meta column cannot both have enough
   width, and the teams lose — which defeats the card. So the meta moves under
   the rows well before the layout is anywhere near mobile. */
@media (max-width: 1040px) {
  .pcard { grid-template-columns: 4px 44px minmax(0, 1fr); }
  .pcard-meta {
    grid-column: 2 / -1;
    flex-direction: row;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    width: auto;
    border-left: none;
    border-top: 0.5px solid var(--border);
    padding: 8px 12px;
  }
  .pserie { text-align: left; max-width: none; }
  .pcard-rows { grid-template-columns: 74px 50px minmax(0, 1fr) 46px minmax(0, 170px); }
}

/* Real mobile: the notes drop to their own line, because at this width nothing
   lines up in columns anyway. */
@media (max-width: 620px) {
  .agenda { padding: 14px 12px 8px; }
  .pcard-rows { grid-template-columns: 70px 46px minmax(0, 1fr) 44px; }
  .pnotes { grid-column: 1 / -1; justify-self: start; flex-wrap: wrap; }
  .view-switch .seg { padding: 5px 8px 6px; font-size: 12px; }
}

/* ------------------------------- what's new -------------------------------
   A flag-level feed rather than a match-level one: the unit of news is "this
   disagreement appeared / moved / went away", and a resolved flag has no source
   data left to render as a card. */

/* A filled red circle, not a rounded pill: these two counts are the only things
   in the header that mean "there is something for you", and at a glance a circle
   reads as a notification while a pill reads as a label. Kept perfectly round for
   one and two digits by matching min-width to height; three digits stretch it to
   a stadium rather than clipping, which is the right failure. */
.seg-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--badge-bg);
  /* Not #fff: --text-danger is a deep red in light mode but a *light* pink in
     dark, where white text on it scores 2.2:1 and is unreadable. --surface-0
     flips with the theme, so the numeral is near-white on dark red and near-black
     on light pink. Same pairing as the rail's "ended" badge. */
  color: #fff;
  font-size: 10px;
  font-weight: 600;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.news-group { margin-top: 18px; }
.news-group h3 {
  font-size: 13px;
  font-weight: 500;
  padding-bottom: 6px;
  border-bottom: 0.5px solid var(--border);
}
.news-group .news-why { font-size: 12px; color: var(--text-muted); font-weight: 400; margin-left: 8px; }

.frow {
  display: grid;
  grid-template-columns: 4px 92px minmax(0, 1fr) minmax(0, 230px);
  gap: 12px;
  align-items: center;
  padding: 9px 10px 9px 0;
  border-bottom: 0.5px solid var(--border);
  cursor: pointer;
}
.frow:hover { background: var(--surface-1); }
.frow-edge { align-self: stretch; background: var(--border); border-radius: 2px; }
.frow.new .frow-edge { background: var(--text-danger); }
.frow.changed .frow-edge { background: var(--text-warning); }
.frow.resolved .frow-edge { background: var(--text-success); }

.frow-when { font-size: 12px; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.frow-teams { font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.frow-sub {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 2px;
  font-size: 12px;
  color: var(--text-secondary);
}
.frow-what { display: flex; gap: 6px; align-items: center; justify-self: end; flex-wrap: wrap; justify-content: flex-end; }
/* A resolved row is history: readable, but it should not compete with the
   things still needing attention. */
.frow.resolved .frow-teams,
.frow.resolved .frow-sub { color: var(--text-muted); }

@media (max-width: 760px) {
  /* Two tracks, and *every* content child has to be told to use the second one —
     naming only some of them left the main column flowing into the 4px edge
     track, which squashed the team names to a single character. */
  .frow { grid-template-columns: 4px minmax(0, 1fr); gap: 4px 10px; }
  .frow-edge { grid-row: 1 / -1; }
  .frow-when,
  .frow-main,
  .frow-what {
    grid-column: 2;
    min-width: 0;
  }
  .frow-what { justify-self: start; justify-content: flex-start; }
}

footer {
  padding: 16px 18px 28px;
  color: var(--text-muted);
  font-size: 12px;
  border-top: 0.5px solid var(--border);
}

.spinner {
  display: inline-block;
  width: 11px;
  height: 11px;
  border: 2px solid var(--border-strong);
  border-top-color: var(--text-accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  vertical-align: -1px;
}
@keyframes spin { to { transform: rotate(360deg); } }


/* --------------------------------- activity -------------------------------- */
/* "Is this thing working?" The health strip carries the verdict, so it is the
   only place in the app where a coloured panel is used for status rather than
   for a schedule disagreement -- red here means the tool is broken, not that a
   start time differs. */

.health {
  border: 0.5px solid var(--border);
  border-radius: 12px;
  background: var(--surface-1);
  padding: 14px 16px;
  margin-bottom: 12px;
}
.health.warn { background: var(--bg-warning); border-color: var(--bg-warning); }
.health.bad { background: var(--bg-danger); border-color: var(--bg-danger); }

.health-verdict { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.health-verdict strong { font-size: 15px; }
.health-detail { font-size: 13px; color: var(--text-secondary); }
.health-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-success);
  align-self: center;
  flex: 0 0 auto;
}
.health.warn .health-dot { background: var(--text-warning); }
.health.bad .health-dot { background: var(--text-danger); }
/* Not colour alone: the dot pulses only while something is actually wrong, and
   the verdict is always spelled out in words beside it. */
.health.bad .health-dot { animation: pulse 1.6s ease-in-out infinite; }
@keyframes pulse { 50% { opacity: 0.35; } }

.stat-row {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 0.5px solid var(--border);
}
.stat-value { font-size: 15px; font-variant-numeric: tabular-nums; }
.stat-label { font-size: 11px; color: var(--text-secondary); margin-top: 1px; }

/* What happens next. */
.qbox {
  border: 0.5px solid var(--border);
  border-radius: 12px;
  margin-bottom: 12px;
  overflow: hidden;
}
.qhead {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  padding: 9px 12px;
  background: var(--surface-1);
  border-bottom: 0.5px solid var(--border);
  font-size: 12px;
  color: var(--text-secondary);
}
.qhead.sub { border-top: 0.5px solid var(--border); }
.qcool { color: var(--text-warning); font-variant-numeric: tabular-nums; }
.qrow {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr) minmax(0, 130px) minmax(0, 210px);
  gap: 12px;
  align-items: center;
  padding: 7px 12px;
  border-bottom: 0.5px solid var(--border);
  font-size: 13px;
}
.qrow:last-child { border-bottom: none; }
.qwhen { font-variant-numeric: tabular-nums; color: var(--text-secondary); }
.qwhen.bad { color: var(--text-danger); }
.qlabel { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.qsources,
.qnote { font-size: 12px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* The feed. Its own grid rather than the news view's: same shape of row, but
   the columns carry different things and tuning one would distort the other. */
.arow {
  display: grid;
  grid-template-columns: 4px 92px minmax(0, 1fr) minmax(0, 340px);
  gap: 12px;
  align-items: center;
  padding: 8px 10px 8px 0;
  border-bottom: 0.5px solid var(--border);
}
.arow-edge { align-self: stretch; background: var(--border); border-radius: 2px; }
.arow.bad .arow-edge { background: var(--text-danger); }
/* A restart is a landmark when reading the feed -- it explains the gap above it. */
.arow.start { background: var(--surface-1); }
.arow.start .arow-edge { background: var(--text-accent); }
.arow.compare .arow-edge { background: transparent; }
.arow-when { font-size: 12px; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.arow-title { display: flex; gap: 8px; align-items: baseline; min-width: 0; }
.arow-kind { font-size: 13px; color: var(--text-secondary); white-space: nowrap; }
.arow-label { font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.arow-error { font-size: 12px; color: var(--text-danger); margin-top: 2px; }
.arow-chips { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.chip.quiet { color: var(--text-muted); }


/* ------------------------------- link missing ------------------------------ */
/* One row per tournament short of a source, across all games. The whole row is
   a click target into the links panel — telling the user what is wrong and
   leaving them to navigate to it is most of the friction this view removes. */

.grow {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 10px 12px;
  border-bottom: 0.5px solid var(--border);
  cursor: pointer;
}
.grow:hover { background: var(--surface-1); }
/* Nothing linked at all is the worse case: the tournament is invisible to every
   other view, because they all work from what has been linked. Marked with an
   edge rather than a red fill — `.chip.bad` already uses --bg-danger as its
   background, so tinting the row made every missing-source chip disappear into
   it. */
.grow.bare { box-shadow: inset 3px 0 0 var(--text-danger); }
.grow-title { font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.grow-chips { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 4px; }
.grow-go { flex: 0 0 auto; }

@media (max-width: 760px) {
  /* The button moves under the title rather than squeezing it. Every child is
     placed explicitly, or one flows into the monogram column. */
  .grow { grid-template-columns: 34px minmax(0, 1fr); row-gap: 8px; }
  .grow .gmono { grid-column: 1; grid-row: 1; }
  .grow-main { grid-column: 2; grid-row: 1; }
  .grow-go { grid-column: 2; grid-row: 2; justify-self: start; }
}

@media (max-width: 900px) {
  .shell { flex-direction: column; }
  .rail {
    width: 100%;
    flex: 1 1 auto;
    position: static;
    max-height: 260px;
    border-right: none;
    border-bottom: 0.5px solid var(--border);
  }
  .mcard-sources { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .day-head { top: 0; }
}

@media (max-width: 760px) {
  /* Four columns do not fit at 375px. Every child must be placed explicitly, or
     one of them flows into the 4px edge track and gets squeezed to a character
     -- the same trap the news rows hit. */
  .arow { grid-template-columns: 4px minmax(0, 1fr); row-gap: 4px; }
  .arow-edge { grid-column: 1; grid-row: 1 / span 3; }
  .arow-when { grid-column: 2; }
  .arow-main { grid-column: 2; }
  .arow-chips { grid-column: 2; justify-content: flex-start; }
  .qrow { grid-template-columns: minmax(0, 1fr); row-gap: 2px; }
  .stat-row { gap: 14px; }
}

/* ---------------------------------- notes ---------------------------------- */
/* One appearance wherever a match appears: the board, the agenda, the feed.
   A note is the only content in the app the user wrote themselves, so it is
   given a left edge in the accent colour rather than a flag colour — it is not
   a finding, it is the answer to one. */

.mcard-foot { margin-top: 10px; display: flex; align-items: flex-start; gap: 8px; flex-wrap: wrap; }
.mcard-foot .note,
.mcard-foot .note-add { flex: 1 1 100%; }

.note {
  border-left: 2px solid var(--border-accent);
  background: var(--surface-1);
  border-radius: 2px;
  padding: 6px 10px;
}
.note-text { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 12px; line-height: 1.45; }
.note-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 4px;
  font-size: 11px;
}
/* Kept quiet until the row is hovered: on a board of 50 cards the notes are
   there to be read, not to offer 50 edit buttons at once. */
.note-meta .btn { opacity: 0; transition: opacity 0.12s; }
.mcard:hover .note-meta .btn,
.pcard:hover .note-meta .btn,
.note:focus-within .note-meta .btn { opacity: 1; }


.note.editing { border-left-color: var(--text-accent); }
.note-input {
  width: 100%;
  box-sizing: border-box;
  font: inherit;
  font-size: 12px;
  line-height: 1.45;
  color: var(--text);
  background: var(--surface-0);
  border: 1px solid var(--border-strong);
  border-radius: 2px;
  padding: 6px 8px;
  resize: vertical;
}
.note-input:focus { outline: none; border-color: var(--border-accent); }
.note-actions { display: flex; align-items: center; gap: 6px; margin-top: 6px; flex-wrap: wrap; }
.note-actions .muted { font-size: 11px; }
.btn.danger { color: var(--text-danger); border-color: var(--border-danger); }

/* The agenda card is a grid of source rows; the note spans the full width under
   them rather than squeezing into a column. */
.pcard-note:not(:empty) { grid-column: 1 / -1; margin: 6px 0 0; }

/* Read-only in the feed — that view is for scanning, and the row already
   navigates to the board where the note can be edited. */
.frow-note {
  margin-top: 4px;
  padding-left: 8px;
  border-left: 2px solid var(--border-accent);
  font-size: 11px;
  line-height: 1.4;
  color: var(--text-muted);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* -------------------------------- notes page ------------------------------- */
/* It renders the agenda's card, so it inherits that layout. This only covers
   what is specific to it. */

/* A note whose match is no longer in any linked tournament. Nothing to click
   through to, so the card says so instead of inviting a dead navigation. */
.pcard.orphaned { cursor: default; opacity: 0.85; }
.pcard.orphaned .pcard-edge { background: var(--border-strong); }

/* The view switch carries six entries. Let it scroll rather than wrap — a header
   that changes height when a badge appears is worse than one that scrolls on a
   narrow window. */
.view-switch { overflow-x: auto; scrollbar-width: none; }
.view-switch::-webkit-scrollbar { display: none; }
.view-switch .seg { white-space: nowrap; }

/* A source's date, shown only when it differs from PandaScore's. Sits under the
   time so the times still line up down the column. */
.pdate-off {
  display: block;
  font-size: 10px;
  line-height: 1.2;
  color: var(--text-danger);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------ the note starter --------------------------- */
/* A + rather than a labelled button, placed in room the card already has: above
   the game monogram on an agenda card, at the end of the head row on a board
   card. The old "add note" button sat in a footer of its own, which added a row
   to every card on the page to offer something most of them never use. */

.note-add {
  display: block;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0.5px solid var(--border);
  border-radius: 999px;
  background: var(--surface-0);
  color: var(--text-muted);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.45;
  transition: opacity 0.12s, color 0.12s, border-color 0.12s;
}
.note-add:hover {
  opacity: 1;
  color: var(--text-accent);
  border-color: var(--border-accent);
}
/* Faint until the card is hovered, so a list of 180 cards reads as data rather
   than as 180 buttons — but never fully hidden, or it cannot be found. */
.mcard:hover .note-add,
.pcard:hover .note-add { opacity: 0.8; }
.note-add:focus-visible { opacity: 1; outline: 1px solid var(--border-accent); outline-offset: 1px; }

/* The game column is a full-height flex column; the + sits above the monogram
   with the pair centred together. */
.pcard-game {
  flex-direction: column;
  gap: 6px;
}

/* On a board card the head is a single row, so the + is pushed to its end. */
.mcard-head .note-add { margin-left: auto; }

/* ------------------------------- sign-in page ------------------------------ */
/* The one page an unauthenticated visitor can reach. Deliberately plain: it
   carries no data, so it should look like a door, not like the app. */

.login-body {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 24px;
  background: var(--surface-1);
}
.login-card {
  width: 100%;
  max-width: 340px;
  padding: 28px 26px 24px;
  border: 0.5px solid var(--border);
  border-radius: var(--radius-lg, 12px);
  background: var(--surface-0);
}
.login-brand { display: flex; align-items: center; gap: 8px; font-size: 15px; font-weight: 500; }
.login-lead { margin: 10px 0 20px; font-size: 12px; line-height: 1.5; color: var(--text-muted); }
.login-label { display: block; font-size: 11px; color: var(--text-secondary); margin-bottom: 4px; }
.login-input {
  width: 100%;
  box-sizing: border-box;
  font: inherit;
  font-size: 13px;
  padding: 8px 10px;
  margin-bottom: 14px;
  color: var(--text);
  background: var(--surface-0);
  border: 1px solid var(--border-strong);
  border-radius: 2px;
}
.login-input:focus { outline: none; border-color: var(--border-accent); }
.login-submit { width: 100%; justify-content: center; padding: 9px; }
.login-error {
  margin-top: 12px;
  padding: 8px 10px;
  border-left: 2px solid var(--border-danger);
  background: var(--bg-danger);
  color: var(--text-danger);
  font-size: 12px;
}

/* Who is signed in, in the header. */
.who {
  font-size: 11px;
  color: var(--text-muted);
  max-width: 140px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
