:root {
  --purple: #37003c;
  --pink: #e90052;
  --green: #00ff85;
  --red: #e74c3c;
  --grey: #f4f4f6;
  --text: #2c2c2c;
  --border: #e8e8ec;
  --muted: #8a8a92;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  background: var(--grey);
  color: var(--text);
  margin: 0;
  padding: 0;
  -webkit-font-smoothing: antialiased;
}
/* Animated on .container (the main content area) rather than body. .bottom-nav is a DOM
   child of body too, so animating body's own opacity dragged the fixed bottom nav along
   with it on every single page navigation, making it visibly flicker each time — even
   though it's the same nav bar on both pages and shouldn't be fading at all. Kept short
   and fade-only (no delay before navigating, no fade-out) — just enough to take the harsh
   edge off a fresh page appearing, without adding a perceptible pause first.
 */
.container {
  animation: page-fade-in 0.1s ease;
}
@keyframes page-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

a, button, input[type=submit] {
  transition: color 0.15s ease, background-color 0.15s ease, opacity 0.15s ease, transform 0.1s ease;
}
button:active, .btn:active, .btn-sm:active {
  transform: scale(0.97);
}

nav {
  background: var(--purple);
  padding: 16px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
}

nav .brand {
  display: flex;
  align-items: center;
  gap: 12px;
  color: white;
  font-weight: 700;
  font-size: 1.2rem;
  text-decoration: none;
}
nav .brand span { color: var(--green); }
.brand-logo {
  height: 44px;
  width: auto;
  display: block;
}
.brand-wordmark {
  height: 29px;
  width: auto;
  display: block;
}

nav .links a, nav .links button {
  color: white;
  text-decoration: none;
  margin-left: 18px;
  font-size: 0.95rem;
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  transition: color 0.15s ease;
}
nav .links a:hover, nav .links button:hover { color: var(--green); }

.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 6px;
  /* Anchors the unread notification dot, which is absolutely positioned within. */
  position: relative;
}
/* :not(.notification-dot) so the dot doesn't inherit the 22px white bar styling meant for
   the three hamburger lines — it's a span inside the same button. */
.nav-toggle span:not(.notification-dot) {
  width: 22px;
  height: 2px;
  background: white;
  display: block;
  border-radius: 2px;
  transition: transform 0.25s ease, opacity 0.2s ease;
}
.notification-dot {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--pink);
  display: block;
}
.notice-badge {
  display: inline-block;
  min-width: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--pink);
  color: white;
  font-size: 0.7rem;
  font-weight: 700;
  text-align: center;
  line-height: 16px;
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] span:nth-child(2) {
  opacity: 0;
}
.nav-toggle[aria-expanded="true"] span:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

@media (max-width: 680px) {
  nav { position: relative; }
  .nav-toggle { display: flex; }
  nav .links {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--purple);
    padding: 0 24px;
    z-index: 20;
    box-shadow: 0 6px 12px rgba(0,0,0,0.15);
    max-height: 0;
    opacity: 0;
    overflow: hidden;
    pointer-events: none;
    transition: max-height 0.25s ease, opacity 0.2s ease, padding 0.25s ease;
  }
  nav .links.open {
    /* Raised from 600px: the grouped menu is taller, and at 600 the last entries — Log out
       among them — were cut off with no indication anything was missing. This is a
       transition ceiling, not a layout constraint, so it only needs to exceed the tallest
       the panel can get. */
    max-height: 900px;
    opacity: 1;
    padding: 10px 24px 16px;
    pointer-events: auto;
  }
  nav .links a, nav .links button {
    margin-left: 0;
    padding: 10px 0;
    width: 100%;
    text-align: left;
  }
  /* nav .links .nav-group-label, not .nav-group-label: the default display:none rule is
     added near .nav-support, which sits LATER in the stylesheet than this media block. At
     equal specificity the later rule wins, so a plain class selector here computed to none
     and no headings appeared at any width. */
  nav .links .nav-group-label {
    display: block;
    margin-top: 10px;
    padding-top: 10px;
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.5);
    border-top: 1px solid rgba(255, 255, 255, 0.14);
  }
  /* Slightly tighter than the old 10px, because the panel is now taller — see the
     max-height note below. */
  nav .links a, nav .links button { padding: 8px 0; }
  nav .links form.inline { width: 100%; }
  nav .links form.inline button { width: 100%; text-align: left; }
}

.container {
  max-width: 640px;
  margin: 0 auto;
  padding: 24px 16px 60px;
}

h1 { font-size: 1.4rem; margin: 0 0 4px; line-height: 1.3; }
h2 { font-size: 1.05rem; margin: 28px 0 0; }
.subtitle { color: var(--muted); font-size: 0.88rem; margin: 0 0 20px; line-height: 1.6; }

.card {
  background: white;
  border-radius: 12px;
  padding: 20px;
  margin-bottom: 16px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.06);
}

.flash {
  background: #fff2e0;
  /* Thicker bar and larger text: people were missing these entirely, and on a join attempt
     the message IS the outcome — there's nothing else on screen telling them what happened. */
  border-left: 6px solid var(--pink);
  padding: 14px 18px;
  border-radius: 6px;
  margin-bottom: 20px;
  font-size: 1.05rem;
  font-weight: 600;
  line-height: 1.35;
}
/* Colour carries the outcome at a glance, so it doesn't rely on reading the wording. */
.flash-positive {
  background: #e7f7ed;
  border-left-color: #1a9e5c;
}
.flash-negative {
  background: #fde8e8;
  border-left-color: #c0392b;
}
.error {
  background: #fde8e8;
  border-left: 4px solid #c0392b;
  padding: 10px 16px;
  border-radius: 6px;
  margin-bottom: 16px;
  font-size: 0.9rem;
}

/* ---- Fixture row: two stacked lines, fixed-width columns so everything lines up ---- */
.fixture-row {
  padding: 14px 10px;
  border-bottom: 1px solid var(--border);
  border-left: 4px solid transparent;
  border-radius: 6px;
}
.fixture-row:last-child { border-bottom: none; }

/* Row status: green once a prediction is saved, red if the deadline passed with nothing entered */
.fixture-row.entered {
  border-left-color: var(--green);
  background: rgba(0,255,133,0.05);
}
.fixture-row.missed {
  border-left-color: var(--red);
  background: rgba(231,76,60,0.05);
}

.fixture-main {
  display: flex;
  /* stretch (not flex-end) so both sides are the same height even when one team name wraps
     — see .side's row sizing below, which is what actually top-aligns the names. */
  align-items: stretch;
  justify-content: center;
  gap: 14px;
}

.team {
  flex: 1 1 0;
  min-width: 0;
  font-weight: 600;
  font-size: 1.05rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-transform: uppercase;
  letter-spacing: 0.3px;
}
.team.home { text-align: right; }
.team.away { text-align: left; }

/* Stacked side used on the predict page: team name on top, crest + score box in a row below */
/* Each side is a 2x2 grid: label sits directly above the score box, sharing that box's
   own column — the crest lives in its own adjacent column so it never shifts what the
   label/box pair is centered against. */
:root { --score-box-col: 76px; --crest-size: 53px; }

.side {
  --crest-col: var(--crest-size);
  display: grid;
  grid-template-columns: var(--crest-col) var(--score-box-col);
  /* Label row takes the slack (1fr) and the box row stays its natural height at the bottom,
     so names start at the top while the score boxes still line up across both sides. */
  grid-template-rows: 1fr auto;
  align-items: center;
  column-gap: 8px;
  row-gap: 6px;
  justify-content: center;
  /* 0 0 auto, not 1 1 0: as a half-row-wide flex item the grid was centred inside it, which
     pushed the boxes further from the centre line the wider the screen got. Sized to content,
     the gap between the two boxes is now the same at every width. */
  flex: 0 0 auto;
  min-width: 0;
}
.side.home {
  /* label sits over the crest column only, so the name centres on the badge. It's allowed to
     overflow that column (see .team-label width rules) — nothing else occupies that row, so
     a name wider than the badge simply extends symmetrically either side of it. */
  grid-template-areas:
    "label ."
    "crest box";
}
.side.away {
  grid-template-columns: var(--score-box-col) var(--crest-col);
  grid-template-areas:
    ". label"
    "box crest";
}

.crest {
  grid-area: crest;
  width: var(--crest-size);
  height: var(--crest-size);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: 800;
  font-size: 1.01rem;
  letter-spacing: 0.2px;
  box-shadow: 0 1px 2px rgba(0,0,0,0.15);
}

.team-label {
  grid-area: label;
  justify-self: center;
  /* Top of its row, so a one-line name sits level with the FIRST line of a wrapped one. */
  align-self: start;
  font-weight: 700;
  font-size: 0.83rem;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  text-align: center;
  line-height: 1.2;
  /* max-content so a single long word (NEWCASTLE) stays on one line even though the badge
     column beneath it is only ~53px, with max-width stopping a genuinely long two-word name
     from stretching far enough to collide with the other side's label. */
  width: max-content;
  max-width: 130px;
  /* normal, not break-word — break-word is what let NEWCASTLE split mid-word. This still
     wraps multi-word names between words, it just never breaks inside one. */
  overflow-wrap: normal;
}

.score-sep {
  /* Darker than the old #ccc as well as larger — at this weight and size a very light grey
     read as washed out rather than deliberate. */
  color: var(--muted);
  font-weight: 800;
  font-size: 1.5rem;
  line-height: 1;
  flex: 0 0 auto;
}
/* .fixture-main is align-items:flex-end, so by default the separator's bottom edge lands on
   the score box's bottom edge, leaving the glyph sitting below the box's vertical centre.
   Giving it the box's own height and centring the glyph inside lines the two up exactly.
   Scoped to .fixture-main because the admin result-entry row is a plain centred flex row
   that already aligns correctly and would be broken by this. */
.fixture-main > .score-sep {
  /* Explicit now that the parent is align-items:stretch — this keeps it level with the
     score boxes rather than stretching over the full row height. */
  align-self: flex-end;
  /* Crest height, NOT the score box height: the badge and the box share one grid row, and
     the badge is the taller of the two, so the row's height (and therefore the box's centre
     line) is set by the crest. */
  height: var(--crest-size);
  display: flex;
  align-items: center;
  justify-content: center;
  /* Hides the "-" TEXT. A glyph's ink doesn't sit at the centre of its own line box — with
     line-height:1 it renders low — so no amount of centring the box centres what you
     actually see. The bar below is drawn instead, which is centred geometrically and
     therefore lands exactly on the boxes' centre line. */
  font-size: 0;
}
.fixture-main > .score-sep::before {
  content: "";
  display: block;
  width: 14px;
  height: 4px;
  border-radius: 2px;
  background: var(--muted);
}
.score-cell {
  grid-area: box;
  position: relative;
  /* flex, not inline-flex: as an inline box it sat on a text baseline, which left a few px of
     descender space below it. That made the row taller than the box and pushed the bottom-
     aligned "-" separator below the box's true centre line. */
  display: flex;
  align-items: center;
}
/* Inner edge of its column on each side, so the two boxes sit closer to the middle. */
.side.home .score-cell { justify-self: end; }
.side.away .score-cell { justify-self: start; }

.score-cell input {
  width: 46px;
  height: 42px;
  padding: 0;
  text-align: center;
  border: 3.5px solid var(--border);
  border-radius: 8px;
  font-size: 1.05rem;
  font-variant-numeric: tabular-nums;
  margin-bottom: 0;
}
.score-cell input:focus {
  outline: none;
  border-color: var(--pink);
  box-shadow: 0 0 0 3px rgba(233,0,82,0.12);
}
.score-cell input:disabled {
  background: var(--grey);
  color: var(--muted);
  border-color: var(--border);
}

.score-inputs {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
}
.score-inputs input {
  width: 42px;
  height: 40px;
  padding: 0;
  text-align: center;
  border: 3.5px solid var(--border);
  border-radius: 8px;
  font-size: 1rem;
  font-variant-numeric: tabular-nums;
  margin-bottom: 0;
}
.score-inputs input:focus {
  outline: none;
  border-color: var(--pink);
  box-shadow: 0 0 0 3px rgba(233,0,82,0.12);
}
.score-inputs input:disabled {
  background: var(--grey);
  color: var(--muted);
  border-color: var(--border);
}
/* The admin result-entry separator now uses .score-sep like the predictions page, so it's
   styled from one place — this rule would otherwise override it, coming later in the file. */

.fixture-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 10px;
  font-size: 0.74rem;
  color: var(--muted);
}

.kickoff-time {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.76rem;
  color: #555;
  font-weight: 700;
  white-space: nowrap;
}
.kickoff-time svg { flex: 0 0 auto; opacity: 0.6; }

.deadline-value {
  font-weight: 700;
  color: var(--text);
}

.fixture-result {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
  color: var(--text);
  font-size: 0.8rem;
  white-space: nowrap;
}

.status-pill {
  font-size: 0.66rem;
  font-weight: 800;
  letter-spacing: 0.4px;
  padding: 4px 10px;
  border-radius: 999px;
  text-transform: uppercase;
  white-space: nowrap;
}
.status-pill.to-predict { background: #ffe0e6; color: var(--pink); }
.status-pill.missed-week { background: #fde8e8; color: var(--red); }
.status-pill.postponed { background: #fff3cd; color: #8a6d1a; }
.status-pill.cancelled { background: #f1f1f3; color: var(--muted); text-decoration: line-through; }

.fixture-status-note {
  font-size: 0.76rem;
  color: var(--muted);
  margin: 4px 0 0;
  font-style: italic;
}

.fixture-status-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}

.fixture-manage {
  margin-top: 8px;
}
.fixture-manage summary {
  cursor: pointer;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--purple);
  list-style: none;
}
.fixture-manage summary::-webkit-details-marker { display: none; }
.fixture-manage summary::before { content: '⚙ '; }
.fixture-manage-body {
  margin-top: 10px;
  padding: 12px;
  background: var(--grey);
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.manage-form-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.manage-form-row select,
.manage-form-row input[type=text],
.manage-form-row input[type=datetime-local] {
  flex: 1;
  min-width: 140px;
  margin-bottom: 0;
  padding: 7px 9px;
  font-size: 0.82rem;
}
.btn-danger { background: var(--red); }

.purge-details summary { list-style: none; }
.purge-details summary::-webkit-details-marker { display: none; }
.purge-confirm-form {
  margin-top: 8px;
  padding: 10px;
  border: 1.5px solid var(--red);
  border-radius: 8px;
  background: #fff5f5;
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 200px;
}
.purge-confirm-form input[type=text] { margin-bottom: 0; }

.total-badge {
  background: var(--purple);
  color: white;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  white-space: nowrap;
}

.missed-text {
  color: var(--red);
  font-weight: 700;
}

.history-gw-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 0;
}

/* ---- "N fixtures to predict" banner ---- */
.info-banner {
  background: #fdece0;
  border-left: 4px solid var(--pink);
  padding: 14px 18px;
  border-radius: 8px;
  margin-bottom: 18px;
  font-size: 0.88rem;
}
.info-banner strong { color: var(--pink); }

.page-head { margin-bottom: 18px; }
.page-head h1 { font-size: 1.5rem; }

.gw-heading {
  font-size: 1.15rem;
  margin: 0 0 12px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
}

.save-hint {
  font-size: 0.78rem;
  color: var(--muted);
  margin: 8px 0 0;
}

.deadline-note {
  font-size: 0.84rem;
  color: var(--muted);
  text-align: center;
  margin: 14px 0 18px;
}

/* ---- Scoring rules + row-status legend ---- */
.legend-card h3 {
  font-size: 0.95rem;
  margin: 0 0 14px;
}
.legend-points {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
}
.legend-points .legend-item {
  flex: 1 1 140px;
}
.legend-points .points-badge {
  display: inline-block;
  margin-bottom: 6px;
}
.legend-points p {
  margin: 0;
  font-size: 0.78rem;
  color: var(--muted);
  line-height: 1.4;
}
.legend-divider {
  border-top: 1px solid var(--border);
  margin: 18px 0;
}
.legend-status {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
}
.legend-status .legend-item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.78rem;
  color: var(--muted);
}
.legend-line {
  display: inline-block;
  width: 22px;
  height: 3px;
  border-radius: 2px;
  flex: 0 0 auto;
}
.legend-line.red { background: var(--red); }
.legend-line.green { background: var(--green); }
.legend-line.grey { background: var(--border); }

.points-badge {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 10px;
  font-size: 0.72rem;
  font-weight: 700;
}
.points-3, .points-exact { background: var(--green); color: var(--purple); }
.points-1, .points-correct { background: #ffe08a; color: #6b5100; }
.points-0 { background: var(--grey); color: var(--muted); }

button, .btn {
  background: var(--pink);
  color: white;
  border: none;
  padding: 11px 22px;
  border-radius: 8px;
  font-size: 0.92rem;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
  line-height: 1.2;
}
button:hover, .btn:hover { opacity: 0.9; }
.btn-secondary { background: var(--purple); }
.btn-sm {
  padding: 5px 14px;
  font-size: 0.76rem;
  border-radius: 6px;
}

.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 20px 0 8px;
}
.pagination-status {
  font-size: 0.8rem;
  color: var(--muted);
  font-weight: 600;
}
.pagination-disabled {
  opacity: 0.4;
  cursor: default;
  pointer-events: none;
}

form.inline { display: inline; }

input[type=text], input[type=email], input[type=password], input[type=datetime-local], input[type=number] {
  width: 100%;
  padding: 10px 12px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  font-size: 1rem;
  margin-bottom: 14px;
}
input[type=text]:focus, input[type=email]:focus, input[type=password]:focus,
input[type=datetime-local]:focus, input[type=number]:focus {
  outline: none;
  border-color: var(--pink);
}
label { font-size: 0.82rem; color: #555; font-weight: 600; display: block; margin-bottom: 5px; }

/* Scoped rather than a blanket `select { }` rule — there were no other <select> elements in
   the site when this was added, so keeping it deliberate avoids silently restyling anything
   that gets added later without this same inline, compact context in mind. */
.season-select {
  padding: 8px 32px 8px 12px;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  font-size: 0.88rem;
  font-family: inherit;
  color: inherit;
  background: #fff url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="%23555" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>') no-repeat right 10px center;
  background-size: 14px;
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
}
.season-select:focus {
  outline: none;
  border-color: var(--pink);
}

table { width: 100%; border-collapse: collapse; }
/* Wraps every table in the app — without this, a table wider than the viewport (common on
   mobile with several columns, e.g. the leaderboard's rank/player/exact/correct/points) would
   overflow the whole page horizontally instead of just scrolling within its own bounds. */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; margin-bottom: 10px; }
.table-wrap table { margin-bottom: 0; }
th, td { text-align: left; padding: 11px 8px; border-bottom: 1px solid var(--border); font-size: 0.9rem; }
th { color: var(--muted); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.3px; }
tr.me { background: #f3fef8; box-shadow: inset 4px 0 0 var(--green); }
/* padding-left clears the 4px inset bar on tr.me. Applied to every row so the rank column
   stays aligned — indenting only the highlighted row would swap one misalignment for
   another, and the bar is drawn inside the row so it cannot simply sit outside it. */
.rank { color: var(--muted); width: 28px; padding-left: 10px; }
.leaderboard-table th:first-child { padding-left: 10px; }

/* ---- League table extras: scoring legend, player avatars, points pill, callout ---- */
.scoring-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  align-items: center;
  border: 1.5px solid var(--border);
  border-radius: 10px;
  padding: 16px 18px;
  margin-bottom: 20px;
}
.legend-col {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1 1 160px;
}
.legend-col strong { font-size: 0.88rem; display: block; }
.legend-col p { margin: 2px 0 0; font-size: 0.8rem; color: var(--muted); }
.legend-icon {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid currentColor;
}
.legend-icon.exact { color: var(--green); }
.legend-icon.correct { color: #f5a623; }
.legend-icon.none { color: var(--muted); }
.legend-divider-v {
  width: 1px;
  align-self: stretch;
  background: var(--border);
}
@media (max-width: 560px) {
  .legend-divider-v { display: none; }
}

.player-cell {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
}
/* The name itself, on both the leaderboard and league standings (they share .player-cell).
   Scoped to exclude the avatar so the initials keep their own sizing, and targeted rather
   than raising the whole table, which would enlarge points and stats columns too.
   Base is the 0.9rem inherited from th/td; this is roughly +2px. */
.player-cell > span:not(.player-avatar) { font-size: 1.03rem; }
/* Flex items default to min-width:auto, which means a long display name cannot shrink
   below its own intrinsic width and instead forces the entire table wider than its card.
   min-width:0 on both the container and the name restores the ability to shrink, and the
   ellipsis makes the truncation deliberate rather than a clip. Applies to league standings
   too, which share .player-cell and have the same six-column squeeze. */
.player-cell { min-width: 0; }
.player-cell > span:not(.player-avatar) {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.player-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--purple);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  font-weight: 800;
  flex: 0 0 auto;
}

.trophy-icon {
  color: #d4a017;
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
}

.trend {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: 0.76rem;
  font-weight: 800;
  white-space: nowrap;
}
.trend svg { flex: 0 0 auto; }
.trend-up { color: var(--green); }
.trend-down { color: var(--red); }
.trend-same { color: var(--muted); font-weight: 700; }

.points-pill {
  display: inline-block;
  min-width: 30px;
  text-align: center;
  padding: 4px 12px;
  border-radius: 8px;
  font-weight: 800;
  background: var(--grey);
  color: var(--text);
}
.points-pill.me {
  background: #d9fbe7;
  color: #0a7a3c;
}

/* Scoped to the leaderboard specifically — shrinks to fit the viewport instead of needing
   to scroll, since 6 columns including wordy headers ("Exact Scores", "Correct Results")
   otherwise don't comfortably fit a narrow phone screen. */
.col-short { display: none; }
@media (max-width: 480px) {
  .leaderboard-table th, .leaderboard-table td { padding: 8px 4px; font-size: 0.78rem; }
  .leaderboard-table .player-cell > span:not(.player-avatar) { font-size: 0.9rem; }
  .leaderboard-table th { font-size: 0.62rem; }
  .leaderboard-table .col-full { display: none; }
  .leaderboard-table .col-short { display: inline; }
  .leaderboard-table .player-avatar { width: 26px; height: 26px; font-size: 0.58rem; }
  .leaderboard-table .points-pill { padding: 3px 8px; min-width: 22px; }
  .leaderboard-table .trend { font-size: 0.68rem; }
  /* The one fixed-width thing in the table, and the reason it only started overflowing
     once results existed: the sparkline is a 60px SVG that renders only for players with
     scored-gameweek history. The viewBox is left intact so the polyline scales down
     proportionally rather than being clipped. */
  /* 40x14 inside a 46px cell. Shrinking to 22px would also fit and cost no truncation, but
     at that size the line is unreadable — a trend you cannot read is not a trend. */
  .leaderboard-table .form-sparkline { width: 40px; height: 14px; }

  /* Fixed layout so the NAME column absorbs the remainder. With auto layout the number
     columns take their natural width, the table becomes 436px inside a 318px wrapper, and
     POINTS ends up off-screen behind a horizontal scroll — measured, not guessed. Fixed
     layout also makes the name truncation actually engage; with a scrolling wrapper there
     is no pressure on the name to shrink and it simply took 246px. */
  .leaderboard-table { width: 100%; table-layout: fixed; }
  .leaderboard-table th, .leaderboard-table td { padding: 8px 2px; }

  /* Rank. padding-left clears the 4px inset bar on tr.me.

     This has to be this specific and this late: `.rank { padding-left: 10px }` earlier in
     the file computed to 4px, because `.leaderboard-table th, .leaderboard-table td` above
     is two selectors to its one AND uses the `padding` shorthand, which resets padding-left
     outright. The rank digit sat underneath the green bar the whole time. */
  .leaderboard-table th:nth-child(1),
  .leaderboard-table td:nth-child(1) { width: 24px; padding-left: 10px; }

  /* Trend, Exact, Correct, Points. Explicit widths, sized to the widest HEADER rather than
     the values — the values are one or two digits, but "CORRECT" is not. */
  /* 46px, not 28: wide enough for the sparkline to sit inside its own cell. At 28 the 38px
     SVG overflowed 16px into the Exact column and only looked acceptable because that
     column happens to be empty there. The 18px comes out of the name, costing one extra
     truncation across twelve real names — measured, not estimated. */
  .leaderboard-table th:nth-child(3), .leaderboard-table td:nth-child(3) { width: 46px; }
  .leaderboard-table th:nth-child(4), .leaderboard-table td:nth-child(4) { width: 40px; }
  .leaderboard-table th:nth-child(5), .leaderboard-table td:nth-child(5) { width: 50px; }
  .leaderboard-table th:nth-child(6), .leaderboard-table td:nth-child(6) { width: 46px; }
  .leaderboard-table thead th { font-size: 0.58rem; }

  /* A smaller avatar buys the name ~10px. Scoped to the leaderboard rather than applied to
     .player-avatar globally, because league standings share the class and are not under the
     same width pressure — shrinking them too would be a change nobody asked for. */
  .leaderboard-table .player-avatar { width: 20px; height: 20px; font-size: 0.55rem; }
  .leaderboard-table .player-cell { gap: 6px; }


  /* The label spans the whole side here rather than centring on the crest column.
       Centred on a 53px column, a 130px name overhangs ~38px each way — outward, that is
       past the row padding and over the accent border. Spanning both columns gives it the
       full ~137px and makes outward overflow impossible. The badge-centred version above
       480px is unaffected, and still preferred where the room exists. */
    .side.home {
      grid-template-areas:
        "label label"
        "crest box";
    }

  .side.away {
      grid-template-areas:
        "label label"
        "box crest";
    }

  .team-label { max-width: 100%; }

  .leaderboard-table .rank { width: 20px; }
}

/* Very narrow phones. The sparkline is a decoration and the name is not, so below this the
   decoration goes. At 320px with it shown, every one of twelve real names truncated. */
@media (max-width: 359px) {
  .leaderboard-table .form-sparkline { display: none; }
  .leaderboard-table th:nth-child(3), .leaderboard-table td:nth-child(3) { width: 28px; }
}

/* The Ko-fi link in the nav menu. Deliberately quieter than the navigation around it —
   it should be findable by someone looking for it and easy to ignore for everyone else. */
/* Section headings in the nav menu.

   Hidden by default: above 680px the menu is a horizontal row, and a row of section
   headings interleaved with links would read as more clutter, not less. They appear only
   in the narrow vertical panel, where grouping is what makes a long list scannable. */
.nav-group-label { display: none; }

.nav-support {
  opacity: 0.75;
  font-size: 0.85rem;
}
.nav-support:hover { opacity: 1; }

/* ---- Gameweek recap ---- */
/* One statement per line with a leading glyph, rather than a table or stat cards: the
   recap reads as a short summary of your week, and a grid would make five one-line facts
   look like data to be scanned instead of a story to be read. */
.recap-lines { margin-top: 14px; }
.recap-line {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 0 0 8px;
  font-size: 0.95rem;
  line-height: 1.5;
}
.recap-line:last-child { margin-bottom: 0; }
/* Fixed width so the text lines up regardless of how wide each emoji renders — they vary
   between platforms and a ragged left edge is very obvious over five lines. */
.recap-icon { flex: 0 0 auto; width: 22px; text-align: center; }

/* ---- Sync log runs table ---- */
@media (max-width: 480px) {
  /* Fixed layout so Detail absorbs the remainder rather than the fixed-width columns
     pushing it off the edge — the same reasoning as the leaderboard. */
  .sync-log-table { width: 100%; table-layout: fixed; }
  .sync-log-table th, .sync-log-table td {
    padding: 8px 5px;
    font-size: 0.76rem;
    vertical-align: top;
  }
  .sync-log-table thead th { font-size: 0.56rem; }

  .sync-log-table th:nth-child(1), .sync-log-table td:nth-child(1) { width: 66px; }
  .sync-log-table th:nth-child(2), .sync-log-table td:nth-child(2) { width: 64px; }

  /* Trigger: "Scheduled" on all but a handful of rows, so it earns none of its 75px here. */
  .sync-log-table th:nth-child(3), .sync-log-table td:nth-child(3) { display: none; }

  /* padding-right keeps this off the Detail column, whose header otherwise reads as
     RESULTSDETAIL with no gap between them. */
  .sync-log-table th:nth-child(4), .sync-log-table td:nth-child(4) {
    width: 34px;
    text-align: right;
    padding-right: 10px;
  }

  /* Took: only interesting when a run is slow, and the summary says so in words. */
  .sync-log-table th:nth-child(5), .sync-log-table td:nth-child(5) { display: none; }
}

/* The unread-notices dot on the Me tab. Positioned against the tab rather than the icon so
   it does not move when the icon changes, and sized to read as a signal rather than a
   count — the number itself is on the Notices link inside /me. */
.bottom-nav .tab-dot {
  position: absolute;
  top: 6px;
  left: 50%;
  margin-left: 6px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--pink);
}

/* ---- Prediction save state ---- */
.save-bar { margin-top: 18px; }

/* Sticky only once there is something to save. A permanent bar would take a slice of a
   small screen for a button that is usually irrelevant — most visits to this page are to
   look, not to change something. */
.save-bar.is-active {
  position: fixed;
  left: 0;
  right: 0;
  /* Above the bottom nav, not over it. */
  bottom: 56px;
  z-index: 15;
  margin: 0;
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
  background: white;
  border-top: 1px solid var(--border);
  box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.08);
}
.save-bar.is-active button { width: 100%; }
.save-bar.is-active .save-hint { margin: 8px 0 0; text-align: center; }

/* Amber, not red. An unsaved prediction is pending, not wrong — red here would sit beside
   the red already used for a missed fixture and mean something entirely different. */
.fixture-row.is-unsaved { border-left-color: #d29200; }

/* The script injects this element rather than decorating existing markup, so it does not
   depend on which lock-state elements a given fixture happens to render. */
.unsaved-flag {
  display: block;
  text-align: center;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #d29200;
  margin-top: 4px;
}

/* ---- Analytics funnel ---- */
.funnel { display: flex; flex-direction: column; gap: 18px; margin-top: 16px; }
.funnel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}
.funnel-label { font-weight: 600; font-size: 0.9rem; }
.funnel-count { font-weight: 800; font-size: 1.1rem; font-variant-numeric: tabular-nums; }
.funnel-pct { font-weight: 600; font-size: 0.8rem; color: var(--muted); margin-left: 4px; }

/* Width is the share of the FIRST stage rather than of the one above, so the bars narrow
   down the page and the shape of the drop-off is readable without the numbers. */
.funnel-bar {
  height: 14px;
  border-radius: 7px;
  background: #ececf1;
  overflow: hidden;
}
.funnel-fill {
  height: 100%;
  border-radius: 7px;
  background: var(--purple);
  min-width: 3px;
}
.funnel-note { margin: 6px 0 0; font-size: 0.75rem; color: var(--muted); }

/* ---- Utility classes ----

   Five, because these are the five inline declarations the views actually repeat:

     text-align:right    68 times   numbers in tables
     margin-top:0        54         the first heading inside a card
     margin:0            18
     margin-bottom:0     16         the last paragraph inside a card
     the flex row        12         a heading with an action beside it

   Not a design system — a design system invented in one sitting is thirty guesses about
   what the views need. Each of these has its evidence attached. */

/* Numbers line up on their last digit when right-aligned, so a column of them can be
   compared by length as well as read. tabular-nums stops the digits themselves changing
   width, which is what makes a column of proportional figures look ragged. */
.numeric {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Headings and paragraphs carry margins that are right in a flow of text and wrong at the
   edge of a card, where the card already provides the spacing. */
.flush-top { margin-top: 0; }
.flush-bottom { margin-bottom: 0; }
.flush { margin: 0; }

/* A heading with something beside it — a count, a link, a filter. Wraps rather than
   squashing, because on a narrow screen the second item is usually a link that becomes
   unreadable long before it becomes unclickable. */
.split-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
}

/* ---- Me hero ---- */
.me-hero h1 { font-size: 2rem; letter-spacing: -0.5px; }

/* Full bleed to the hero padding: the chart is the hero, not an inset diagram. Fixed
   height with preserveAspectRatio="none" so a 38-gameweek season and a 2-gameweek one
   both fill the same band — the shape is the point, not the bar width. */
.hero-chart {
  display: block;
  width: 100%;
  height: 54px;
  margin: 14px 0 4px;
  overflow: visible;
}
/* Green on purple. A fill attribute in the markup loses to a stylesheet rule, so the same
   bars can be purple on white elsewhere without the route knowing where it is rendered. */
.hero-chart rect { fill: var(--green); }
.hero-chart-note {
  margin: 0;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  opacity: 0.55;
}

/* Three across, and STAYING three across on a phone — unlike .quick-stats, which stacks
   below 640px. These values are single digits; stacking them would give three full-width
   cards each holding one character, which is how the flat version got flat. */
.me-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin-bottom: 18px;
}
.me-stats .stat-card { padding: 14px 10px; text-align: center; }
.me-stats .stat-value { font-size: 1.7rem; }

/* The one table on this page that does not fit. Five columns in 286px overflows by 13px,
   so it scrolls sideways inside its wrapper and the last column — the average, which is
   the point of the table — is the part that disappears. Only this one: the other three
   have three or four columns and fit already. */
@media (max-width: 480px) {
  .me-payoff-table { width: 100%; table-layout: fixed; }
  .me-payoff-table th, .me-payoff-table td { padding: 8px 3px; font-size: 0.78rem; }
  .me-payoff-table thead th { font-size: 0.56rem; }
}

/* The insight card. Given the green accent so the eye lands on it before the tables — it
   is the one sentence that answers "so what?", and it was previously the third row of the
   third table. */
.insight-card { border-left: 4px solid var(--green); }

/* The disclosure holding everything else. Styled as a control rather than a heading, so it
   reads as something to open rather than a section that failed to render. */
.more-insights { margin-bottom: 18px; }
.more-insights > summary {
  cursor: pointer;
  list-style: none;
  padding: 14px 18px;
  background: white;
  border-radius: 12px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.06);
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--purple);
}
.more-insights > summary::-webkit-details-marker { display: none; }
.more-insights[open] > summary { margin-bottom: 12px; }

/* Bar rows. One device used across all four breakdowns, so the disclosure reads as a set
   rather than four unrelated tables — and so a reader who understands the first
   understands the rest.

   Grid rather than a table: the label and value size themselves, the bar takes the rest,
   and the note drops to a second line on narrow screens. The tables these replaced needed
   fixed widths and still overflowed at 320px. */
.insight-rows { display: flex; flex-direction: column; gap: 12px; }
.insight-row {
  display: grid;
  grid-template-columns: minmax(64px, auto) 1fr auto;
  grid-template-areas: "label bar value" "note note note";
  align-items: center;
  gap: 4px 10px;
}
.insight-row-label {
  grid-area: label;
  font-size: 0.85rem;
  font-weight: 600;
}
.insight-row-value {
  grid-area: value;
  font-size: 0.85rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.insight-row-note {
  grid-area: note;
  font-size: 0.72rem;
  color: var(--muted);
}

.insight-bar {
  grid-area: bar;
  display: block;
  height: 10px;
  border-radius: 5px;
  background: #ececf1;
  overflow: hidden;
}
/* Purple is the resting state and green marks beating your own average. Two colours only:
   colouring every bar would make the highlight mean nothing. */
.insight-bar-fill {
  display: block;
  height: 100%;
  border-radius: 5px;
  background: var(--purple);
  min-width: 3px;
}
.insight-bar-fill.is-good { background: #00c853; }
.insight-bar-fill.is-neutral { background: var(--muted); }

/* Scoreline chips. Five entries with two figures each was a table only because it was
   easier to write as one. */
.scoreline-pills { display: flex; flex-wrap: wrap; gap: 8px; }
.scoreline-pill {
  display: inline-flex;
  flex-direction: column;
  padding: 7px 12px;
  border-radius: 8px;
  background: #f4f4f7;
  line-height: 1.3;
}
.scoreline-pill strong { font-size: 0.95rem; font-variant-numeric: tabular-nums; }
.scoreline-pill span { font-size: 0.7rem; color: var(--muted); }

/* ---- Me hub ---- */
/* Stacked rows rather than a grid: each entry is a title plus a line of explanation, and
   at that shape two columns on a phone would give neither enough width to be readable. */
.me-links { display: flex; flex-direction: column; gap: 2px; }
.me-links a {
  display: block;
  padding: 10px 0;
  text-decoration: none;
  color: inherit;
  border-bottom: 1px solid var(--border);
}
.me-links a:last-child { border-bottom: none; }
.me-links a strong { display: block; font-size: 0.97rem; }
.me-links a span {
  display: block;
  font-size: 0.8rem;
  color: var(--muted);
  margin-top: 2px;
  line-height: 1.4;
}
.me-links a:hover strong { color: var(--pink); }

/* ---- Achievements ---- */
/* auto-fit rather than a fixed column count: one badge on a phone, three on a desktop,
   without a media query for each. minmax stops a single badge stretching the full width
   and looking like a banner. */
.badge-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 10px;
}
.badge {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
}
.badge-earned { background: #f7fdf9; border-color: rgba(0,255,133,0.35); }
/* Dimmed rather than hidden or greyed to nothing — a locked badge should read as
   available-but-not-yet, not as broken or disabled. */
.badge-locked { opacity: 0.62; }
.badge-icon { font-size: 1.7rem; line-height: 1; flex: 0 0 auto; }
.badge-body { min-width: 0; }
.badge-name { margin: 0 0 2px; font-weight: 700; font-size: 0.98rem; }
.badge-count { color: var(--muted); font-weight: 700; }
.badge-detail { margin: 0; font-size: 0.8rem; color: var(--muted); line-height: 1.4; }

.callout-card {
  display: flex;
  align-items: center;
  gap: 16px;
  background: #f5f1fb;
}
.callout-icon {
  flex: 0 0 auto;
  color: var(--purple);
}
.callout-card strong { font-size: 0.95rem; display: block; }
.callout-card p { margin: 2px 0 0; font-size: 0.85rem; color: var(--muted); }

/* ---- Private leagues ---- */
.league-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
}
.league-name { font-size: 1rem; }

.invite-card {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
}
.invite-card strong { font-size: 0.82rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.3px; }
.invite-code {
  font-family: monospace;
  font-size: 1.3rem;
  font-weight: 800;
  letter-spacing: 2px;
  color: var(--purple);
  background: var(--grey);
  padding: 8px 16px;
  border-radius: 8px;
  margin-top: 4px;
  display: inline-block;
}
.invite-code-inline {
  font-family: monospace;
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 1px;
  color: var(--muted);
}
.invite-link {
  font-family: monospace;
  font-size: 0.76rem;
  color: var(--muted);
  word-break: break-all;
  margin: 4px 0 0;
  max-width: 320px;
}

.league-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
}

/* ---- Dashboard ---- */
.get-started-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  margin-top: 14px;
}
.get-started-grid form {
  border: 1.5px solid var(--border);
  border-radius: 10px;
  padding: 16px;
}
@media (max-width: 560px) {
  .get-started-grid { grid-template-columns: 1fr; }
}

.dashboard-links {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
}
.dashboard-links a {
  font-weight: 600;
  color: var(--purple);
  text-decoration: none;
}
.dashboard-links a:hover { text-decoration: underline; }

.toggle-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
  font-size: 0.92rem;
  cursor: pointer;
  margin-bottom: 4px;
}
.toggle-row input[type=checkbox] {
  width: 18px;
  height: 18px;
  accent-color: var(--pink);
  cursor: pointer;
}

/* ---- Dashboard hero ---- */
.dashboard-hero {
  background: linear-gradient(135deg, var(--purple), #55006b);
  color: white;
  border-radius: 14px;
  padding: 24px 22px;
  margin-bottom: 18px;
}
.dashboard-hero .greeting { font-size: 0.92rem; opacity: 0.85; margin: 0 0 4px; }
.dashboard-hero h1 {
  color: white;
  margin: 0 0 4px;
  font-size: 1.4rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.dashboard-hero .hero-sub { opacity: 0.85; font-size: 0.86rem; margin: 0 0 16px; }
.dashboard-hero .progress-track {
  background: rgba(255,255,255,0.18);
  border-radius: 999px;
  height: 9px;
  overflow: hidden;
  margin-bottom: 6px;
}
.dashboard-hero .progress-fill {
  background: var(--green);
  height: 100%;
  border-radius: 999px;
  transition: width 0.3s ease;
}
.dashboard-hero .progress-fill.progress-fill-alt { background: #ffd166; }
.dashboard-hero .progress-label { font-size: 0.76rem; opacity: 0.85; margin: 0 0 14px; }
.dashboard-hero .countdown-label { font-size: 0.82rem; opacity: 0.9; margin: 0 0 16px; }
.dashboard-hero .countdown { font-weight: 800; }
.dashboard-hero .hero-cta { display: inline-block; }

/* ---- Quick stats ---- */
.quick-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  margin-bottom: 18px;
}
.result-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
  margin-bottom: 18px;
}
@media (max-width: 640px) {
  .quick-stats { grid-template-columns: 1fr; }
}
.stat-card {
  background: white;
  border-radius: 12px;
  padding: 16px 18px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.06);
}
.stat-label {
  font-size: 0.7rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  margin-bottom: 6px;
}
.stat-value { font-size: 1.6rem; font-weight: 800; color: var(--text); }
.stat-value.latest-score { font-size: 1rem; text-transform: uppercase; }
.stat-note { font-size: 0.78rem; color: var(--muted); margin-top: 4px; }
.stat-note.result-good { color: #0a7a3c; font-weight: 700; }
.stat-note.result-bad { color: var(--red); font-weight: 700; }
.stat-link {
  display: block;
  margin-top: 10px;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--purple);
  text-decoration: none;
}
.stat-link:hover { text-decoration: underline; }

/* ---- This week's fixtures list ---- */
.fixture-list-item {
  /* Column, not row: the Predict / Predicted status now sits on its own line beneath the
     fixture rather than being pushed out to the right edge of it. align-items:center is what
     centres that status line under the fixture. */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.fixture-list-item:last-child { border-bottom: none; }
.fixture-list-name {
  /* Takes the space between the two badges so the away badge sits at a consistent position
     on every row, instead of being pushed around by the length of the team names. */
  flex: 1;
  min-width: 0;
  text-align: center;
}
.fixture-list-teams {
  display: flex;
  align-items: center;
  gap: 8px;
  /* flex:none + full width, because the parent is now a column: flex:1 would make this grow
     in the vertical axis instead of the horizontal one. */
  flex: none;
  width: 100%;
  font-size: 0.82rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.2px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Badge images share .crest for sizing/grid placement, but must drop the circle styling —
   club crests aren't circular and a background/shadow behind a transparent PNG looks wrong.
   object-fit: contain keeps non-square badges from distorting. */
.crest.crest-badge {
  width: var(--crest-size);
  height: var(--crest-size);
  flex-shrink: 0;
  /* The source is a 100px raster; high-quality interpolation keeps it clean when the browser
     scales it to the rendered size rather than defaulting to something softer. */
  image-rendering: -webkit-optimize-contrast;
  image-rendering: auto;
  background: none !important;
  border-radius: 0;
  box-shadow: none;
  object-fit: contain;
}

:root { --crest-sm-size: 41px; }
.crest-sm { width: var(--crest-sm-size); height: var(--crest-sm-size); font-size: 0.94rem; }
/* Must follow .crest.crest-badge — same specificity (0,2,0), so source order decides. Without
   this, a badge image on the dashboard would take the full-size 53px instead of the small one. */
.crest-sm.crest-badge { width: var(--crest-sm-size); height: var(--crest-sm-size); }
.fixture-done { color: #0a7a3c; font-weight: 700; font-size: 0.78rem; white-space: nowrap; }
.fixture-predict-link { color: var(--pink); font-weight: 700; font-size: 0.78rem; text-decoration: none; white-space: nowrap; }
.fixture-predict-link:hover { text-decoration: underline; }

/* ---- Compact league rows on the dashboard ---- */
.league-row-compact {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 9px 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.9rem;
}
.league-row-compact:last-child { border-bottom: none; }
.league-row-compact a { color: var(--purple); font-weight: 600; text-decoration: none; font-size: 0.82rem; }

/* ---- Bottom nav (mobile) ---- */
.bottom-nav {
  display: none;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background: white;
  border-top: 1px solid var(--border);
  padding: 6px 0 calc(6px + env(safe-area-inset-bottom));
  z-index: 40;
  box-shadow: 0 -2px 8px rgba(0,0,0,0.06);
}
.bottom-nav a {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  color: var(--muted);
  text-decoration: none;
  font-size: 0.62rem;
  font-weight: 600;
  padding: 4px 4px;
  /* "Leaderboard" is long for a 5-item bar — without this it can wrap to two lines on a
     narrow phone, which makes the whole nav taller and the items misalign. */
  white-space: nowrap;
}
.bottom-nav a svg { width: 20px; height: 20px; }
.bottom-nav a.active { color: var(--pink); }

@media (max-width: 680px) {
  .bottom-nav { display: flex; justify-content: space-around; }
  /* Generous buffer above the nav's own actual height (icon + label + padding + border) —
     62px was cutting it too close in practice, leaving real interactive content on longer
     pages (e.g. admin maintenance, with several stacked tables) partially covered by the
     fixed nav bar. */
  body { padding-bottom: 90px; }
  /* Body padding alone isn't enough — it reserves visual space, but scroll-into-view
     behavior (native, or via Playwright's scrollIntoViewIfNeeded()) has no way to know a
     fixed-position element sits on top of part of the viewport, so it can still land a
     target right at the boundary the nav covers. scroll-margin-bottom is the actual
     mechanism browsers use to leave extra clearance for a fixed element when scrolling
     something into view — applied broadly here since anything could end up being that
     target. */
  * { scroll-margin-bottom: 90px; }
}

/* ---- Results auto-sync status ---- */
.sync-status-card {
  display: flex;
  align-items: center;
  gap: 14px;
}
.sync-status-dot {
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--muted);
}
.sync-status-dot.ok { background: var(--green); }
.sync-status-dot.warn { background: var(--red); }
.sync-status-dot.off { background: var(--muted); }
.sync-status-card code {
  background: var(--grey);
  padding: 1px 6px;
  border-radius: 4px;
  font-size: 0.82rem;
}

.push-preview {
  border: 1.5px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px;
  background: #fafafa;
  max-width: 340px;
}
.push-preview strong { font-size: 0.9rem; display: block; margin-bottom: 4px; }
.push-preview p { margin: 0; font-size: 0.82rem; color: var(--muted); }

.locked-badge {
  background: var(--grey);
  color: var(--muted);
  padding: 2px 10px;
  border-radius: 10px;
  font-size: 0.7rem;
  font-weight: 700;
  vertical-align: middle;
}

/* ---- Gameweek switcher / tab navigation: horizontal pill row ---- */
.gw-nav, .tab-nav {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding-bottom: 4px;
  margin-bottom: 14px;
  -ms-overflow-style: none;
  scrollbar-width: none;
}
.gw-nav::-webkit-scrollbar, .tab-nav::-webkit-scrollbar { display: none; }
.gw-nav a, .tab-nav a {
  flex: 0 0 auto;
  text-decoration: none;
  color: var(--purple);
  font-size: 0.82rem;
  font-weight: 600;
  background: white;
  border: 1.5px solid var(--border);
  padding: 6px 14px;
  border-radius: 999px;
  white-space: nowrap;
}
.gw-nav a.current, .tab-nav a.current {
  background: var(--purple);
  border-color: var(--purple);
  color: white;
}

@media (max-width: 380px) {
  :root { --score-box-col: 64px; }
  .team { font-size: 0.975rem; }
  /* Slightly smaller labels here so the longest one ("Leaderboard") still fits its fifth
     of the bar without clipping, now that they can't wrap. */
  .bottom-nav a { font-size: 0.56rem; padding: 4px 2px; }
  .team-label { font-size: 0.7rem; max-width: 105px; }
  :root { --crest-size: 49px; }
  .crest { font-size: 0.94rem; }
  .score-cell input { width: 40px; height: 38px; font-size: 0.95rem; }
  .fixture-main { gap: 8px; }
  .score-inputs input { width: 36px; height: 36px; font-size: 0.9rem; }
  /* Eased back a little where the boxes themselves shrink to 36px, so the separator stays
     proportionate — still well up on the 1.05rem it was before. */
  .score-sep { font-size: 1.3rem; }
  .fixture-main > .score-sep { height: 38px; } /* matches the shrunk .score-cell input */
  .card { padding: 16px; }
}


/* ---- Notification opt-in banner ---- */
/* Fixed above the mobile bottom nav so it never covers it; the nav is 62px plus safe-area. */
.push-banner {
  position: fixed;
  left: 12px;
  right: 12px;
  bottom: calc(74px + env(safe-area-inset-bottom));
  z-index: 45;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  background: var(--purple);
  color: white;
  border-radius: 12px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.22);
  font-size: 0.82rem;
}
.push-banner-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.push-banner-text strong { font-size: 0.9rem; }
.push-banner-text span { opacity: 0.85; }
.push-banner-actions { display: flex; align-items: center; gap: 8px; flex: none; }
.push-banner .btn-sm { background: var(--pink); color: white; border: none; }
.push-banner-dismiss {
  background: none;
  border: none;
  color: white;
  opacity: 0.75;
  cursor: pointer;
  font-size: 0.78rem;
  text-decoration: underline;
  padding: 0;
}
/* Above 680px the bottom nav is hidden, so the banner can sit lower. */
@media (min-width: 681px) {
  .push-banner { bottom: 16px; left: auto; right: 16px; max-width: 420px; }
}

/* ---- Fixture form & head-to-head panel ---- */
.fixture-history { margin-top: 8px; }
.fixture-history > summary {
  cursor: pointer;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  color: var(--muted);
  list-style: none;
  padding: 4px 0;
  text-align: center;
}
.fixture-history > summary::-webkit-details-marker { display: none; }
.fixture-history > summary::after { content: ' ▾'; }
.fixture-history[open] > summary::after { content: ' ▴'; }
.fixture-history-body {
  padding: 10px 12px;
  background: var(--grey);
  border-radius: 8px;
  margin-top: 4px;
}
.form-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}
.form-team {
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.form-pips { display: flex; gap: 3px; flex: none; }
.form-pip {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.62rem;
  font-weight: 800;
  color: white;
}
.form-W { background: #1a9e5c; }
.form-D { background: var(--muted); }
.form-L { background: var(--red); }
.h2h-heading {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  color: var(--muted);
  margin: 10px 0 4px;
}
.h2h-list { list-style: none; margin: 0; padding: 0; }
.h2h-list li {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 0.76rem;
  padding: 3px 0;
}
.h2h-date { color: var(--muted); flex: none; }
.h2h-score { text-align: right; min-width: 0; }


/* ---- League invite QR ---- */
.qr-details > summary {
  cursor: pointer;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  color: var(--muted);
  list-style: none;
  padding: 4px 0;
}
.qr-details > summary::-webkit-details-marker { display: none; }
.qr-details > summary::after { content: ' ▾'; }
.qr-details[open] > summary::after { content: ' ▴'; }
/* White padding around the code matters: the quiet zone is part of the spec, and scanners
   struggle without it. */
.league-qr {
  display: inline-block;
  background: #fff;
  padding: 10px;
  border-radius: 8px;
  margin-top: 6px;
  line-height: 0;
}
.league-qr img, .league-qr canvas { display: block; }


/* ---- Scoreline frequency bars ---- */
.scoreline-bar {
  display: block;
  height: 10px;
  border-radius: 5px;
  background: var(--purple);
  min-width: 2px;
}


/* ---- Team scorelines, home beside away ---- */
.team-scoreline-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
/* Stacks on narrow screens — two tables side by side is unreadable on a phone. */
@media (max-width: 680px) {
  .team-scoreline-cols { grid-template-columns: 1fr; }
}


/* ---- Still-open predictions in history ---- */
.status-pill.still-open { background: #e7f7ed; color: #1a9e5c; }
/* A link, not just text: if it's still open the useful thing is to go and do it. */
.still-open-link { color: #1a9e5c; font-weight: 700; text-decoration: underline; }


/* ---- Prediction status grid ---- */
.prediction-status-table th, .prediction-status-table td { padding: 8px 6px; }
.pred-fixture-col { text-align: center; font-size: 0.62rem; line-height: 1.2; white-space: nowrap; }
.pred-cell { text-align: center; }
.pred-yes { color: #1a9e5c; font-weight: 800; }
.pred-no { color: var(--muted); font-weight: 700; }
/* A row nobody has touched is the one worth spotting from across the room. */
.pred-none { background: #fde8e8; }
.pred-partial { background: #fff8e6; }


/* ---- Admin index ---- */
.admin-index-groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 18px;
}
.admin-index-group h2 {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--muted);
  margin: 0 0 8px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.admin-index-group a {
  display: block;
  padding: 7px 0;
  font-size: 0.92rem;
  font-weight: 600;
  text-decoration: none;
}
.admin-index-group a:hover { text-decoration: underline; }


/* ---- Per-fixture lock status ---- */
.fixture-lock-status {
  text-align: center;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  margin-top: 6px;
}
/* Green reads as "you can still act on this", which is the state that matters most. */
.lock-open { color: #1a9e5c; }
.lock-locked { color: var(--muted); }
.lock-final { color: var(--purple); }


/* ---- Notification schedule reference ---- */
.notif-type { padding: 12px 0; border-bottom: 1px solid var(--border); }
.notif-type:last-child { border-bottom: none; }
.notif-type-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.notif-subject { margin: 4px 0 6px; font-size: 0.86rem; color: var(--muted); font-style: italic; }
.notif-type p.stat-note { margin: 2px 0; }
/* Flags a notification whose behaviour no longer matches how the app works. */
.notif-warning {
  margin: 6px 0 0;
  padding: 6px 10px;
  background: #fff8e6;
  border-left: 3px solid #d29200;
  font-size: 0.8rem;
}


/* ---- Notification template preview ---- */
.template-preview {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px;
  background: #fff;
  max-height: 420px;
  overflow: auto;
}
.template-preview img { max-width: 100%; }
