/* Score Box admin panel
   ---------------------------------------------------------------------------
   Every colour below is a value the mobile design already uses, so the panel
   reads as the same product rather than a generic dashboard. Sources are the
   design-system colour tables:

     #2563EB  primary action fill (the app's primary pill)
     #155DFC  primary variant / pressed
     #51A2FF  light blue accent — links, "Details >", "View All >"
     #0A2A66  deep blue, the login-hero gradient stop
     #0F172B  darkest navy, the match header bar
     #FAFAFA  bg/canvas       #F8FAFC  bg/slate-50
     #009966  success/win     #E11D48  danger/live     #FFB900  warning/gold

   Brand colour is deliberately rationed. It appears on the primary button,
   the active nav item, the brand mark, the tile edge, the section ticks and
   the login wash — and nowhere else. In a panel this data-dense, colour has
   to carry meaning ("this one", "this state", "this product"); once it is
   also decoration it stops being able to.

   The #0A2A66 → #2563EB login-hero gradient survives at two sizes: the brand
   mark, and the wash behind the sign-in card.

   Type is Inter, matching the app (Inter Medium 10 / Semi Bold 13 in Figma). */

:root {
  /* Brand ramp — #2563EB is the anchor; the rest are tints and shades of it. */
  --blue-50:  #EFF5FF;
  --blue-100: #DBE7FE;
  --blue-200: #BFD4FE;
  --blue-300: #93B4FD;
  --blue-400: #51A2FF;   /* design-system accent */
  --blue-500: #2B7FFF;
  --blue-600: #2563EB;   /* design-system primary */
  --blue-700: #155DFC;   /* design-system pressed */
  --blue-800: #1E40AF;
  --blue-900: #0A2A66;   /* design-system login-hero stop */

  --primary: var(--blue-600);
  --primary-dark: var(--blue-700);
  --primary-tint: var(--blue-50);
  --primary-accent: var(--blue-400);

  --canvas: #FAFAFA;
  --surface: #FFFFFF;
  --slate-50: #F8FAFC;
  --slate-100: #F1F5F9;
  --border: #E2E8F0;
  --border-soft: #EDF1F6;

  --text: #0F172A;
  /* Both secondary greys are one step darker than they were. #94A3B8 measures
     2.56:1 on white — it fails AA wherever it carries text, which is most of
     the small type in this panel: tile labels, row subtitles, hints, pager
     counts, the footer email. Shifting both keeps a two-level hierarchy with
     both levels passing (7.58:1 and 4.76:1). */
  --text-muted: #475569;
  --text-subtle: #64748B;

  --danger: #DC2626;
  --danger-tint: #FEF2F2;
  --live: #E11D48;              /* the app's LIVE dot */
  --warn: #B45309;
  --warn-tint: #FFFBEB;
  --gold: #FFB900;
  --ok: #009966;                /* the app's success/win green */
  --ok-tint: #ECFDF5;

  /* Sidebar rail — one place to retune the whole navigation column.
     A near-black column against the light canvas. Brand colour appears on
     exactly one thing here: the current page, as a solid filled pill. That
     is the whole point of spending the darkest surface in the panel on
     navigation — with nothing else competing, one blue row is unmissable.
     Contrast for every value is measured against --rail further down. */
  --rail: #0D1220;
  --rail-text: #9AA6BF;          /*  7.20:1 on #0D1220 — idle nav labels    */
  --rail-text-strong: #FFFFFF;
  --rail-label: #7E8BA6;         /*  5.14:1 on #0D1220 — group labels       */
  --rail-hover: rgba(255, 255, 255, .055);
  --rail-active: var(--primary);  /* solid fill, not a tint                  */
  --rail-line: rgba(255, 255, 255, .075);

  --radius-sm: 8px;
  --radius: 10px;
  --radius-lg: 14px;
  --radius-xl: 18px;

  /* Two-layer shadows: a tight contact shadow plus a wider soft one. Tinted
     with the brand navy rather than neutral black so elevation stays warm. */
  --shadow-xs: 0 1px 2px rgba(10, 42, 102, .05);
  --shadow: 0 1px 2px rgba(10, 42, 102, .05), 0 2px 6px rgba(10, 42, 102, .05);
  --shadow-md: 0 2px 4px rgba(10, 42, 102, .05), 0 8px 20px -6px rgba(10, 42, 102, .13);
  --shadow-brand: 0 1px 2px rgba(37, 99, 235, .22), 0 6px 16px -4px rgba(37, 99, 235, .34);
  --ring: 0 0 0 3px rgba(37, 99, 235, .18);

  --ease: cubic-bezier(.4, 0, .2, 1);
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 14px;
  line-height: 1.55;
  color: var(--text);
  background: var(--canvas);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ── Auth screen ─────────────────────────────────────────────────────────── */
/* The app puts a #0A2A66 → #2563EB diagonal behind its auth card. This is the
   same gradient, dialled back to a wash so a form on top of it stays readable
   — and it makes the sign-in page unmistakably this product's. */
/* Full-bleed brand backdrop, echoing the app's own login hero. The image is
   an authored SVG (static/img/login-bg.svg), not a photograph — nothing in this
   project is licensed stadium imagery, and an unlicensed one would sit on the
   only page a stranger can reach.

   The gradient underneath is the same one, declared in CSS, so the page never
   flashes white and still looks deliberate if the SVG fails to load. */
.auth-wrap {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
  background:
    url('../img/login-bg.svg') center/cover no-repeat,
    linear-gradient(140deg, var(--blue-900) 0%, #123A86 55%, var(--blue-600) 100%);
  background-color: var(--blue-900);
}
.auth-card {
  width: 100%;
  max-width: 404px;
  background: var(--surface);
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: var(--radius-xl);
  /* A heavier shadow than elsewhere: the card has to lift off a dark ground
     rather than sit on near-white. */
  box-shadow: 0 24px 60px -18px rgba(3, 10, 28, .65), 0 2px 8px rgba(3, 10, 28, .35);
  padding: 34px 32px;
}
.brand { display: flex; align-items: center; gap: 11px; margin-bottom: 26px; }
.sidebar .brand-mark { width: 34px; height: 34px; border-radius: 9px; font-size: 12px; }
.brand-mark {
  width: 40px; height: 40px;
  border-radius: 11px;
  /* The login-hero gradient, at mark scale. */
  background: linear-gradient(140deg, var(--blue-400) 0%, var(--blue-600) 55%, var(--blue-900) 100%);
  color: #fff;
  display: grid; place-items: center;
  font-weight: 800; font-size: 13px; letter-spacing: .04em;
  box-shadow: 0 2px 8px -1px rgba(37, 99, 235, .45);
}
.brand-name { font-weight: 800; font-size: 17px; letter-spacing: -.01em; white-space: nowrap; }
.brand-sub { color: var(--text-subtle); font-size: 12px; margin-top: -2px; }

h1 { font-size: 19px; font-weight: 700; margin: 0 0 4px; letter-spacing: -.01em; }
.lede { color: var(--text-muted); margin: 0 0 22px; font-size: 13px; }

label { display: block; font-size: 12px; font-weight: 600; color: var(--text-muted); margin-bottom: 6px; }
.field { margin-bottom: 16px; }
input[type=email], input[type=password], input[type=text] {
  width: 100%;
  padding: 11px 13px;
  font: inherit;
  color: var(--text);
  background: var(--slate-50);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: border-color .12s, box-shadow .12s, background .12s;
}
input:focus {
  outline: none;
  background: var(--surface);
  border-color: var(--primary);
  box-shadow: var(--ring);
}
input[aria-invalid=true] { border-color: var(--danger); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%;
  padding: 11px 18px;
  font: inherit; font-weight: 600;
  color: #fff;
  /* A shade-to-shade gradient rather than a flat fill, and a shadow in the
     brand hue instead of grey — the button reads as lit from above and stays
     recognisably the app's primary pill. */
  background: linear-gradient(180deg, var(--blue-500) 0%, var(--blue-600) 100%);
  border: 0; border-radius: 999px;
  cursor: pointer;
  box-shadow: var(--shadow-brand);
  transition: background .15s var(--ease), box-shadow .15s var(--ease),
              transform .12s var(--ease);
}
.btn:hover {
  background: linear-gradient(180deg, var(--blue-600) 0%, var(--blue-700) 100%);
  box-shadow: 0 2px 4px rgba(37, 99, 235, .24), 0 10px 22px -6px rgba(37, 99, 235, .40);
}
.btn:active { transform: translateY(1px); box-shadow: var(--shadow-xs); }
.btn:disabled {
  opacity: .5; cursor: not-allowed; box-shadow: none; transform: none;
  background: var(--blue-600);
}
.btn-quiet {
  width: auto; background: var(--surface); color: var(--text-muted);
  border: 1px solid var(--border); padding: 7px 14px; font-size: 13px;
  box-shadow: var(--shadow-xs);
}
.btn-quiet:hover {
  background: var(--blue-50); color: var(--primary-dark);
  border-color: var(--blue-200); box-shadow: var(--shadow-xs);
}
.btn-quiet:active { transform: translateY(1px); }

/* A destructive button at the same weight as .btn-quiet, so the two sit level in
   a row of actions. Danger is carried by colour and by the confirm dialog, not
   by making it bigger — a removal should not be the easiest thing on the page. */
.btn-danger-sm {
  width: auto; background: var(--surface); color: var(--danger);
  border: 1px solid #FECDD3; padding: 7px 14px; font-size: 13px; font-weight: 600;
  box-shadow: var(--shadow-xs);
}
.btn-danger-sm:hover { background: #FFF1F4; border-color: #FDA4AF; }
.btn-danger-sm:active { transform: translateY(1px); }

/* The two ways to settle a report, side by side.
   Rejecting carries a required note, so its form is the wide one; on a narrow
   screen they stack and the note gets the full width. */
.report-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-soft);
}
.report-actions form { display: flex; align-items: center; gap: 8px; }
/* Both labels are two-word verdicts and must not wrap — a button reading
   "Reject — content" / "stays" on two lines looks like a rendering fault. */
.report-actions .btn, .report-actions .btn-danger-sm { white-space: nowrap; }
.reject-form { flex: 1 1 320px; }
.reject-form input[type="text"] {
  flex: 1 1 auto; min-width: 0;
  padding: 7px 11px; font: inherit; font-size: 13px;
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface);
}
.reject-form input[type="text"]:focus-visible {
  outline: 2px solid var(--primary); outline-offset: 1px; border-color: var(--primary);
}

.alert {
  display: flex; gap: 10px;
  padding: 11px 13px;
  border-radius: var(--radius);
  font-size: 13px;
  margin-bottom: 18px;
}
.alert-danger { background: var(--danger-tint); color: #991B1B; border: 1px solid #FECACA; }
.alert-warn   { background: var(--warn-tint);   color: #92400E; border: 1px solid #FDE68A; }
.alert-ok     { background: var(--ok-tint);     color: #166534; border: 1px solid #BBF7D0; }
.alert strong { font-weight: 700; }


/* ── App shell: navy rail + light canvas ──────────────────────────────────
   Replaced the top navbar 2026-08-27: the panel derives ~48-58 screens from
   the mobile app's modules, and horizontal nav does not scale past about six.

   The rail carries the brand so the content area does not have to. Everything
   right of it stays near-white, which is what a data-dense tool wants —
   colour there has to mean something (a state, a warning, a link), and it
   cannot mean something if it is also decoration. */

.shell { display: grid; grid-template-columns: 252px 1fr; min-height: 100vh; }

.sidebar {
  position: sticky; top: 0; align-self: start;
  height: 100vh;
  display: flex; flex-direction: column;
  padding: 18px 12px 14px;
  background: var(--rail);
  color: var(--rail-text);
}

/* The brand block and the footer are separated by hairlines rather than by
   whitespace, so the three regions of the column stay legible however long
   the nav list grows. */
.sidebar .brand {
  margin: 0 4px; padding: 4px 6px 18px;
  border-bottom: 1px solid var(--rail-line);
  text-decoration: none; color: inherit;
}
.sidebar .brand:hover { text-decoration: none; }
.sidebar .brand-name {
  color: var(--rail-text-strong);
  font-size: 16px; font-weight: 800;
  letter-spacing: .06em; text-transform: uppercase;
}

.side-nav { flex: 1; overflow-y: auto; margin: 14px -2px 0; padding: 0 2px; }
.side-group + .side-group {
  margin-top: 14px; padding-top: 14px;
  border-top: 1px solid var(--rail-line);
}
.side-group-label {
  font-size: 10.5px; font-weight: 700; letter-spacing: .11em;
  text-transform: uppercase; color: var(--rail-label);
  padding: 0 12px; margin-bottom: 6px;
}
.side-nav a {
  display: flex; align-items: center; gap: 12px;
  min-height: 40px;
  padding: 8px 14px; margin-bottom: 3px;
  border-radius: var(--radius);
  font-size: 13.5px; font-weight: 600;
  color: var(--rail-text);
  transition: background .15s var(--ease), color .15s var(--ease);
}
.side-nav a:hover {
  background: var(--rail-hover); color: var(--rail-text-strong); text-decoration: none;
}
/* The current page is a solid brand fill across the full row — the one place
   in this column where brand colour appears, which is what makes it read at a
   glance rather than needing to be found. */
.side-nav a[aria-current=page] {
  background: var(--rail-active);
  color: #fff;
  font-weight: 700;
  box-shadow: 0 2px 8px -2px rgba(37, 99, 235, .55);
}

/* Icons inherit currentColor, so a row's colour and its glyph can never drift
   apart — including inside the filled pill, where both go white. */
.nav-ico {
  width: 19px; height: 19px; flex: 0 0 auto;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.side-nav a .nav-ico { opacity: .85; }
.side-nav a:hover .nav-ico,
.side-nav a[aria-current=page] .nav-ico { opacity: 1; }
/* The sprite is a definitions-only element; it must not occupy layout. */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* One row: avatar, name over role, and an icon-only sign out. Matches the
   nav rhythm above rather than being a block of its own. */
.side-foot {
  margin-top: 12px; padding: 12px 6px 2px;
  border-top: 1px solid var(--rail-line);
  display: flex; align-items: center; gap: 11px;
}
.side-foot .avatar { flex: 0 0 auto; }
.who-text { min-width: 0; flex: 1; }
.who-name {
  font-weight: 700; font-size: 13.5px; line-height: 1.25;
  color: var(--rail-text-strong);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The role, not the email: it says what this person can do, which is the fact
   that matters when a control is missing from a page. */
.who-role {
  color: var(--rail-label); font-size: 12px; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.signout {
  flex: 0 0 auto;
  display: grid; place-items: center;
  width: 34px; height: 34px; padding: 0;
  background: transparent; border: 0; border-radius: var(--radius-sm);
  color: var(--rail-text); cursor: pointer;
  transition: background .15s var(--ease), color .15s var(--ease);
}
.signout:hover { background: var(--rail-hover); color: var(--rail-text-strong); }
.signout .nav-ico { width: 18px; height: 18px; }

/* Visible to a screen reader, not on screen. The sign-out control is an icon,
   so without this its only name would be the title attribute — which some
   screen readers do not announce. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.avatar {
  width: 38px; height: 38px; border-radius: 999px; flex: 0 0 auto;
  background: var(--primary);
  color: #fff;
  display: grid; place-items: center;
  font-size: 13px; font-weight: 700; letter-spacing: .01em;
}
.avatar-sm { width: 26px; height: 26px; font-size: 10px; }
.avatar-lg { width: 46px; height: 46px; font-size: 16px; }

/* A single wash of brand colour at the top of the canvas. It is what stops
   the content area from starting as a flat grey rectangle, and it fades out
   well before the first row of data. */
.content {
  padding: 28px 30px 64px;
  max-width: 1180px;
  /* A grid item defaults to min-width:auto, so it refuses to shrink below its
     min-content width — and a seven-column table's min-content is wide. That
     pushed the whole column past the viewport and gave the PAGE a horizontal
     scrollbar, clipping the rightmost column at any width between the 860px
     breakpoint and a comfortable desktop. min-width:0 lets 1fr do its job;
     .panel-body below gives the table its own scroll instead. */
  min-width: 0;
  background:
    radial-gradient(760px 260px at 0% -40px, rgba(81, 162, 255, .10) 0%, transparent 72%),
    transparent;
}
.content-bare { margin: 0 auto; }

@media (max-width: 860px) {
  /* Rows must be declared, not inferred: with two auto rows and a 100vh
     min-height, grid's default align-content:stretch hands the leftover
     viewport height to BOTH rows, which padded the rail out to ~197px and
     left a band of dead space under it. */
  .shell { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
  /* Wrap rather than scroll. A horizontally scrolling rail hid the sign-out
     control off the right edge and drew a scrollbar across the navy. */
  .sidebar {
    position: static; height: auto; overflow: visible;
    flex-direction: row; align-items: center; flex-wrap: wrap;
    gap: 10px 14px; padding: 12px 14px;
    border-bottom: 1px solid var(--rail-line);
  }
  /* The brand's bottom hairline is a divider between stacked regions; once the
     rail is a horizontal bar the regions sit side by side and it reads as an
     underline under the wordmark. */
  .sidebar .brand { margin: 0; padding: 0 6px 0 0; border-bottom: 0; }
  .side-nav a { min-height: 34px; padding: 6px 12px; gap: 9px; }
  /* The sidebar itself scrolls horizontally here; a second scroll container
     inside it just drew a stray overlay scrollbar across the nav. */
  .side-nav { display: flex; flex-wrap: wrap; gap: 4px; overflow: visible; margin: 0; padding: 0; }
  .side-nav a { white-space: nowrap; }
  /* display:contents dissolves the group wrappers so every link becomes a
     direct flex child and the row packs evenly. Grouping them here would only
     break lines at arbitrary places — the group labels are hidden anyway. */
  .side-group { display: contents; }
  .side-group + .side-group { margin-top: 0; padding-top: 0; border-top: 0; }
  .side-group-label { display: none; }
  .side-foot { border: 0; margin: 0; padding: 0; flex: 0 0 auto; gap: 8px; }
  .who-text { display: none; }
  .side-foot .avatar { width: 32px; height: 32px; font-size: 12px; }
  .content { padding: 20px 18px 48px; }
}

.page-head { margin-bottom: 24px; }
.page-head h1 { font-size: 25px; margin-bottom: 3px; letter-spacing: -.022em; }
.page-head p { color: var(--text-muted); margin: 0; font-size: 13px; }

.detail-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; flex-wrap: wrap; }
.detail-id { display: flex; align-items: center; gap: 14px; }
.detail-actions { margin: 0; }

.back { display: inline-block; font-size: 13px; font-weight: 600; margin-bottom: 12px; }

.section-title {
  display: flex; align-items: center; gap: 8px;
  font-size: 11px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-muted);
  margin: 32px 0 13px;
}
.section-title::before {
  content: "";
  width: 3px; height: 12px; border-radius: 2px;
  background: linear-gradient(180deg, var(--blue-400), var(--blue-600));
}

/* ── Filters, chips, pager ─────────────────────────────────────────────── */
.filters { display: flex; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; align-items: center; }
/* .filters-spacer lives further down, next to .btn-primary-sm — the two are only
   ever used together and the pair reads better in one place. */
.filters input[type=search] {
  flex: 1; min-width: 240px;
  padding: 9px 13px; font: inherit;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
}
.filters input[type=search]:focus {
  outline: none; border-color: var(--primary);
  box-shadow: var(--ring);
}

.chips { display: flex; gap: 6px; margin-bottom: 16px; flex-wrap: wrap; }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--surface);
  font-size: 12px; font-weight: 600; color: var(--text-muted);
  transition: background .14s var(--ease), border-color .14s var(--ease),
              color .14s var(--ease);
}
.chip:hover {
  background: var(--blue-50); border-color: var(--blue-200);
  color: var(--primary-dark); text-decoration: none;
}
.chip span { color: var(--text-subtle); font-variant-numeric: tabular-nums; }
.chip.is-on {
  background: linear-gradient(180deg, var(--blue-500), var(--blue-600));
  border-color: var(--blue-600); color: #fff;
  box-shadow: 0 1px 2px rgba(37, 99, 235, .24);
}
.chip.is-on:hover { color: #fff; }
.chip.is-on span { color: rgba(255,255,255,.78); }

.pager { display: flex; align-items: center; justify-content: space-between; margin-top: 14px; }
.pager-count { font-size: 12px; color: var(--text-subtle); }

.row-main { display: inline-flex; align-items: center; gap: 10px; color: inherit; }
.row-main:hover { text-decoration: none; }
.row-main:hover strong { color: var(--primary-dark); }
.row-main strong { display: block; font-size: 13px; }
/* The secondary line is a slug or a media URL — identifying, not something
   anyone reads end to end. Left unbounded, a 62-character video URL widened the
   title column to 426px and pushed State and Actions off the panel. Cap it and
   ellipsis the overflow; the full value is one click away in the editor. */
.row-sub {
  display: block; font-size: 11px; color: var(--text-subtle);
  max-width: 26ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Key/value list ────────────────────────────────────────────────────── */
.kv { display: grid; grid-template-columns: 116px 1fr; gap: 0; margin: 0; padding: 6px 18px 14px; }
.kv dt {
  font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-subtle); padding: 8px 0;
}
.kv dd { margin: 0; padding: 8px 0; font-size: 13px; }

.btn-block { width: 100%; }
.btn-danger { background: var(--danger); color: #fff; width: auto; padding: 9px 16px; font-size: 13px; }
.btn-danger:hover { background: #B91C1C; }

/* ── Stat tiles ──────────────────────────────────────────────────────────── */
.grid { display: grid; gap: 14px; }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 940px) { .grid-4, .grid-3 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .grid-4, .grid-3, .grid-2 { grid-template-columns: 1fr; } }

.tile {
  position: relative; overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 17px 18px 16px;
  box-shadow: var(--shadow);
  transition: box-shadow .18s var(--ease), transform .18s var(--ease),
              border-color .18s var(--ease);
}
/* A 2px brand edge along the top of each tile. Cheap, and it turns a grid of
   grey boxes into something that looks assembled on purpose. */
.tile::after {
  content: "";
  position: absolute; inset: 0 0 auto 0; height: 2px;
  background: linear-gradient(90deg, var(--blue-600), var(--blue-400));
  opacity: .85;
}
.tile:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
  border-color: var(--blue-200);
}
.tile-label { font-size: 11px; font-weight: 700; letter-spacing: .06em;
              text-transform: uppercase; color: var(--text-subtle); }
.tile-value {
  font-size: 28px; font-weight: 800; letter-spacing: -.025em;
  margin-top: 6px; line-height: 1.08;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.tile-note  { font-size: 12px; color: var(--text-muted); margin-top: 4px; }
/* A zero is a real answer, not a missing one — recede the number, but leave
   the accent edge alone. Dimming it by value produced a checkerboard, and it
   conflated two unrelated things: "0 draft videos" is neutral while
   "0 failures in 24h" is the best possible reading. */
.tile-value.is-zero { color: var(--text-subtle); font-weight: 700; }

.pill {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px; border-radius: 999px;
  font-size: 11px; font-weight: 700; letter-spacing: .01em;
  /* An inset hairline instead of a border, so a pill never changes the height
     of the row it sits in. */
  box-shadow: inset 0 0 0 1px rgba(15, 23, 42, .05);
}
.pill-live   { background: #FFF1F4; color: var(--live); }
.pill-ok     { background: var(--ok-tint);     color: #047857; }
.pill-warn   { background: var(--warn-tint);   color: #92400E; }
.pill-info   { background: var(--blue-50);     color: var(--primary-dark); }
.pill-muted  { background: var(--slate-100);   color: var(--text-muted); }
.dot { width: 6px; height: 6px; border-radius: 999px; background: currentColor; }
/* LIVE is the one state in the product that pulses, and the app pulses it. */
.pill-live .dot { box-shadow: 0 0 0 3px rgba(225, 29, 72, .16); }

/* ── Panels and tables ───────────────────────────────────────────────────── */
.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.panel-head {
  padding: 14px 18px; border-bottom: 1px solid var(--border-soft);
  background: linear-gradient(180deg, #FCFDFF, var(--surface));
  display: flex; align-items: center; gap: 10px;
}
.panel-head h2 { font-size: 14px; font-weight: 700; margin: 0; letter-spacing: -.005em; }
/* An action in a panel header sits hard right. Named rather than styling
   `.panel-head .btn`, so a header with two buttons keeps them together instead
   of pushing both to opposite ends. */
.head-action { margin-left: auto; }
/* Wide tables scroll inside their own panel rather than pushing the page. */
.panel-body { padding: 4px 0; overflow-x: auto; }

/* Tables carry most of this panel, so they get the most attention: a tinted
   header band that reads as a header without a heavy rule under it, hairline
   separators soft enough to disappear when you are reading down a column, and
   a brand-tinted hover so the row under the cursor is unambiguous. */
table { width: 100%; border-collapse: collapse; }
/* 14px, not 18px: seven columns of 18px padding spent 252px on whitespace and
   pushed the actions column out of view on a 1100px window. 14px still reads
   as a comfortable data table and buys back 56px. Wider content still scrolls
   inside the panel rather than the page. */
th, td { padding: 11px 14px; text-align: left; font-size: 13px; }
th {
  font-size: 10.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-muted);
  background: var(--slate-50);
  border-bottom: 1px solid var(--border-soft);
  white-space: nowrap;
}
tbody tr { transition: background .12s var(--ease); }
tbody tr:hover { background: rgba(81, 162, 255, .055); }
tbody tr + tr td { border-top: 1px solid var(--border-soft); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }

.empty { padding: 34px 18px; text-align: center; color: var(--text-subtle); font-size: 13px; }


.error-wrap { min-height: 70vh; display: grid; place-items: center; text-align: center; }
.error-code { font-size: 52px; font-weight: 800; color: var(--text-subtle); letter-spacing: -.03em; }

/* ── Content management ──────────────────────────────────────────────────── */
.muted { color: var(--text-subtle); }

/* One rule for both, so a select in a form and a select in a filter bar are
   the same control. They were not: a bare <select> inside .field fell back to
   the browser default and read as unfinished next to the styled inputs. */
.filters select,
.field select {
  padding: 9px 13px; font: inherit;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  color: var(--text);
}
.field select { width: 100%; }
.field select:focus { outline: none; border-color: var(--primary); box-shadow: var(--ring); }
/* Pushes whatever follows it to the far end of the controls row, which is where
   every "New …" button belongs. Five list pages were missing the span entirely
   and rendered their button on the left, next to Clear.
   TestCreateButtonsAreRightAligned is what notices that now — nothing else did,
   because a missing span is not an error, just a button in the wrong place. */
.filters-spacer { flex: 1 1 auto; }
.btn-primary-sm { width: auto; padding: 9px 16px; font-size: 13px; }

/* The actions column is pinned to the right edge of the scroll container.
   Tuning column widths page by page to keep it on screen was whack-a-mole —
   one long email, one long channel handle, one more column and it slides off
   again. Sticky means a wide table can scroll under it and the actions stay
   reachable, which is the one column that must never be the hidden one.

   .panel-body is the scroll container (overflow-x: auto), so sticky resolves
   against that rather than the viewport. */
.row-actions {
  text-align: right; white-space: nowrap;
  position: sticky; right: 0; z-index: 1;
  background: var(--surface);
}
th.row-actions { background: var(--slate-50); z-index: 2; }
/* A hairline on the leading edge so the pinned column reads as pinned once
   content starts sliding beneath it. */
.row-actions::after {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: 1px; background: var(--border-soft);
}
/* The row hover tint is on the <tr>, which an opaque sticky cell would hide.
   This is that tint pre-composited over white so the row still reads as one. */
tbody tr:hover .row-actions { background: #F7FAFE; }
.row-actions form { display: inline; }
/* ── Row actions ─────────────────────────────────────────────────────────────
   One shape for every action in a list: a plain text link. Mixing pill buttons
   and bare links in the same column read as two different kinds of control for
   what are peers — View and Suspend do the same class of thing.

   Both <a> and <button> use this, so the two are indistinguishable: a submit
   button has to be stripped of its native chrome to match an anchor. */
.row-link {
  display: inline;
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; font-size: 12px; font-weight: 600;
  color: var(--primary-dark); cursor: pointer;
  text-decoration: none;
}
.row-link:hover { text-decoration: underline; color: var(--primary-dark); }
.row-link-danger { color: var(--danger); }
.row-link-danger:hover { color: var(--danger); }
/* An action that is deliberately unavailable: same shape, no affordance. */
.row-link-muted { color: var(--text-subtle); cursor: default; }
.row-link-muted:hover { text-decoration: none; }
.row-link:disabled { opacity: .35; cursor: not-allowed; }
.row-link:disabled:hover { text-decoration: none; }

/* Spacing lives on the container so a form wrapper cannot change it. */
.row-actions > * + *, .row-actions form + *, .row-actions * + form { margin-left: 14px; }


.thumb {
  width: 56px; height: 34px; flex: 0 0 auto;
  border-radius: 6px; background: var(--slate-50) center/cover no-repeat;
  border: 1px solid var(--border);
}
.thumb-empty { background-image: none; }

/* ── Editor layout ───────────────────────────────────────────────────────── */
.editor { display: grid; grid-template-columns: 1fr 320px; gap: 20px; align-items: start; }
@media (max-width: 980px) { .editor { grid-template-columns: 1fr; } }

.editor-main, .editor-side {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: 20px;
}
.editor-side { position: sticky; top: 20px; }
@media (max-width: 980px) { .editor-side { position: static; } }

.editor label { display: block; font-size: 12px; font-weight: 600; color: var(--text-muted); margin-bottom: 6px; }
.editor .field { margin-bottom: 18px; }
.editor .field:last-child { margin-bottom: 0; }
.field-narrow input { max-width: 160px; }
.field-inline { margin-top: 12px; margin-bottom: 0 !important; }

.editor input[type=text], .editor input[type=url], .editor input[type=number],
.editor input[type=datetime-local], .editor select, .editor textarea {
  width: 100%; padding: 10px 13px; font: inherit; color: var(--text);
  background: var(--slate-50);
  border: 1px solid var(--border); border-radius: var(--radius);
}
.editor textarea { resize: vertical; line-height: 1.6; }
.editor input:focus, .editor select:focus, .editor textarea:focus {
  outline: none; background: var(--surface); border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(37, 99, 235, .14);
}
.editor input.mono, .editor textarea.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px;
}

.req { color: var(--danger); font-weight: 600; font-size: 10px;
       text-transform: uppercase; letter-spacing: .05em; margin-left: 4px; }
.hint { font-size: 11.5px; color: var(--text-subtle); margin: 6px 0 0; line-height: 1.45; }
.hint code { background: #E8EDF4; padding: 1px 4px; border-radius: 3px; }

.preview {
  margin-top: 8px; height: 108px; border-radius: var(--radius);
  background: var(--slate-50) center/cover no-repeat; border: 1px solid var(--border);
}
.preview-16x9 { height: 150px; }

/* Publishing state radios */
.states { border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 14px; margin: 0 0 18px; }
.states legend { font-size: 11px; font-weight: 700; letter-spacing: .06em;
                 text-transform: uppercase; color: var(--text-subtle); padding: 0 4px; }
/* The whole row is the label, so make the whole row look selectable — and
   make the selected one obvious from across the screen, not just from the
   4px radio dot. */
.state {
  display: flex; gap: 9px; align-items: flex-start;
  padding: 8px 10px; margin: 0 -6px 2px; cursor: pointer;
  border-radius: var(--radius-sm);
  transition: background .14s var(--ease), box-shadow .14s var(--ease);
}
.state:hover { background: var(--slate-50); }
.state:has(input:checked) {
  background: var(--blue-50);
  box-shadow: inset 0 0 0 1px var(--blue-200);
}
.state:has(input:checked) strong { color: var(--primary-dark); }
.state input { margin-top: 3px; flex: 0 0 auto; accent-color: var(--primary); }
.state span { display: block; }
.state strong { display: block; font-size: 13px; font-weight: 600; color: var(--text); }
.state em { display: block; font-style: normal; font-size: 11.5px; color: var(--text-subtle); }

.editor-actions { display: flex; gap: 8px; align-items: center; margin-top: 4px; }
.editor-actions .btn { width: auto; }
.editor-actions .btn-quiet { padding: 11px 16px; }

.is-readonly input, .is-readonly select, .is-readonly textarea { pointer-events: none; opacity: .65; }

/* ── Topics: an ordered tab-set manager ──────────────────────────────────── */
.topics-panel { margin-bottom: 8px; }
.panel-foot { padding: 12px 18px; border-top: 1px solid var(--border); background: var(--slate-50); }

table.topics td { vertical-align: middle; }
.col-ord { width: 40px; color: var(--text-subtle); }
tr.is-off td { opacity: .55; }
tr.is-off .toggle { opacity: 1; }

.row-form { display: flex; gap: 8px; align-items: center; margin: 0; flex-wrap: wrap; }
.row-form input[type=text] { flex: 1; min-width: 180px; }
.row-form input[type=text], .row-form select {
  padding: 7px 11px; font: inherit; font-size: 13px; color: var(--text);
  background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
}
.row-form input:focus, .row-form select:focus {
  outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(37,99,235,.14);
}
.btn-tiny { width: auto; padding: 7px 13px; font-size: 12px; }

/* Toggle switch. A real <button>, so it works without JavaScript — the CSP
   forbids inline script and the panel ships none. */
.toggle {
  width: 38px; height: 22px; padding: 2px;
  border: 1px solid var(--border); border-radius: 999px;
  background: #E2E8F0; cursor: pointer;
  display: inline-flex; align-items: center; transition: background .15s;
}
.toggle .knob {
  width: 16px; height: 16px; border-radius: 999px; background: #fff;
  box-shadow: 0 1px 2px rgba(15,23,42,.2); transition: transform .15s;
}
.toggle.is-on { background: var(--primary); border-color: var(--primary); }
.toggle.is-on .knob { transform: translateX(16px); }

.nudge {
  width: 26px; height: 26px; padding: 0; margin-right: 2px;
  border: 1px solid var(--border); border-radius: 7px;
  background: var(--surface); color: var(--text-muted);
  cursor: pointer; font-size: 13px; line-height: 1;
}
.nudge:hover:not(:disabled) { background: var(--slate-50); color: var(--text); }
.nudge:disabled { opacity: .3; cursor: default; }


/* ── Shorts and channels ─────────────────────────────────────────────────── */
.thumb-vert { width: 26px; height: 40px; }
.preview-vert { height: 200px; max-width: 118px; background-size: cover; }

.tag {
  display: inline-block; margin-right: 5px;
  font-size: 10.5px; font-weight: 600; color: var(--primary-dark);
}
.audio { font-size: 10.5px; color: var(--text-subtle); }

.readonly-stats {
  display: flex; gap: 16px; padding: 9px 12px;
  background: var(--slate-50); border: 1px solid var(--border); border-radius: var(--radius);
  font-size: 12px; color: var(--text-muted);
}
.readonly-stats strong { color: var(--text); font-size: 14px; }

/* Takedown: a <details> disclosure so the reason field is required and visible
   before the destructive click, with no JavaScript. */
.takedown { display: inline-block; text-align: left; }
.takedown > summary {
  cursor: pointer; list-style: none;
  font-size: 12px; font-weight: 600; color: var(--danger);
}
.takedown > summary::-webkit-details-marker { display: none; }
.takedown > summary:hover { text-decoration: underline; }
.takedown[open] > summary { margin-bottom: 8px; }
.takedown form {
  min-width: 260px; padding: 12px;
  background: var(--danger-tint); border: 1px solid #FECACA; border-radius: var(--radius);
}
.takedown label { display: block; font-size: 11px; font-weight: 600; color: #991B1B; margin-bottom: 5px; }
.takedown input[type=text] {
  width: 100%; padding: 7px 10px; font: inherit; font-size: 12.5px; margin-bottom: 8px;
  background: #fff; border: 1px solid #FECACA; border-radius: 7px;
}
.takedown input[type=text]:focus {
  outline: none; border-color: var(--danger); box-shadow: 0 0 0 3px rgba(220,38,38,.12);
}

.at { color: var(--text-subtle); font-weight: 600; }
.handle-input { max-width: 170px; }
.chan-avatar {
  display: inline-block; width: 26px; height: 26px; border-radius: 999px;
  background: var(--slate-50) center/cover no-repeat; border: 1px solid var(--border);
}

/* ── Polls ───────────────────────────────────────────────────────────────── */
.pill-danger { background: #FFF1F4; color: var(--live); }

/* A colour-coded left edge per state. The pill on the right already names the
   state; this is what lets you find the one overdue poll in a list of twenty
   without reading any of them. */
.poll-card {
  margin-bottom: 12px;
  border-left: 3px solid var(--border);
  transition: box-shadow .18s var(--ease), border-color .18s var(--ease);
}
.poll-card:hover { box-shadow: var(--shadow-md); }
.poll-card[data-state="LIVE"]      { border-left-color: var(--ok); }
.poll-card[data-state="SCHEDULED"] { border-left-color: var(--primary-accent); }
.poll-card[data-state="OVERDUE"]   { border-left-color: var(--live); }
.poll-card[data-state="DRAFT"]     { border-left-color: #CBD5E1; }
.poll-card[data-state="CLOSED"]    { border-left-color: #94A3B8; }

.poll-head {
  display: flex; justify-content: space-between; gap: 16px; align-items: flex-start;
  padding: 15px 18px; border-bottom: 1px solid var(--border-soft);
  background: linear-gradient(180deg, #FCFDFF, var(--surface));
}
.poll-q { font-size: 14px; font-weight: 700; color: var(--text); }
.poll-q:hover { color: var(--primary-dark); text-decoration: none; }
.poll-meta { font-size: 11.5px; color: var(--text-subtle); margin-top: 3px; }
.poll-badges { display: flex; align-items: center; gap: 10px; white-space: nowrap; }
.poll-votes { font-size: 12px; color: var(--text-muted); font-variant-numeric: tabular-nums; }

.poll-results { padding: 14px 18px; }
.opt + .opt { margin-top: 12px; }
.opt-top { display: flex; justify-content: space-between; gap: 12px; font-size: 12.5px; }
.opt-label { font-weight: 600; }
.opt-num { color: var(--text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.bar {
  margin-top: 5px; height: 7px; border-radius: 999px;
  background: var(--slate-50); border: 1px solid var(--border); overflow: hidden;
}
.bar span { display: block; height: 100%; background: var(--primary); border-radius: 999px; }
.opt-lead {
  display: inline-block; margin-top: 6px;
  padding: 2px 8px; border-radius: 999px;
  background: var(--ok-tint); color: #047857;
  font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em;
}
.opt-none { padding: 4px 0; color: var(--danger); font-size: 12.5px; }

.poll-actions {
  display: flex; gap: 14px; align-items: center;
  padding: 11px 18px; border-top: 1px solid var(--border-soft); background: var(--slate-50);
}
.poll-actions form { margin: 0; }

.opt-row { display: flex; align-items: center; gap: 9px; margin-bottom: 9px; }
.opt-row input[type=text] { flex: 1; }
.opt-letter {
  width: 25px; height: 25px; flex: 0 0 auto; border-radius: 999px;
  background: var(--blue-50);
  box-shadow: inset 0 0 0 1px var(--blue-200);
  display: grid; place-items: center;
  font-size: 11px; font-weight: 800; color: var(--primary-dark);
}
.opt-votes { font-size: 11px; color: var(--text-subtle); white-space: nowrap; }
.opt-votes.is-locked { color: var(--warn); font-weight: 600; }

/* ── CSP-safe utilities ──────────────────────────────────────────────────────
   The panel ships style-src 'self' with no 'unsafe-inline', which blocks the
   style ATTRIBUTE as well as <style> blocks. Anything that would have been an
   inline style has to exist as a class here instead. render_test.go enforces
   this: a style="..." in any template fails the build.                       */
.mt-14    { margin-top: 14px; }
.tile-value-sm { font-size: 19px; }

/* Data-driven bar fills. A width cannot be inlined, so every whole percent is
   a class. 101 rules, ~2 KB, and exact to the same 1% the label rounds to.   */
.w-0 { width: 0%; }
.w-1 { width: 1%; }
.w-2 { width: 2%; }
.w-3 { width: 3%; }
.w-4 { width: 4%; }
.w-5 { width: 5%; }
.w-6 { width: 6%; }
.w-7 { width: 7%; }
.w-8 { width: 8%; }
.w-9 { width: 9%; }
.w-10 { width: 10%; }
.w-11 { width: 11%; }
.w-12 { width: 12%; }
.w-13 { width: 13%; }
.w-14 { width: 14%; }
.w-15 { width: 15%; }
.w-16 { width: 16%; }
.w-17 { width: 17%; }
.w-18 { width: 18%; }
.w-19 { width: 19%; }
.w-20 { width: 20%; }
.w-21 { width: 21%; }
.w-22 { width: 22%; }
.w-23 { width: 23%; }
.w-24 { width: 24%; }
.w-25 { width: 25%; }
.w-26 { width: 26%; }
.w-27 { width: 27%; }
.w-28 { width: 28%; }
.w-29 { width: 29%; }
.w-30 { width: 30%; }
.w-31 { width: 31%; }
.w-32 { width: 32%; }
.w-33 { width: 33%; }
.w-34 { width: 34%; }
.w-35 { width: 35%; }
.w-36 { width: 36%; }
.w-37 { width: 37%; }
.w-38 { width: 38%; }
.w-39 { width: 39%; }
.w-40 { width: 40%; }
.w-41 { width: 41%; }
.w-42 { width: 42%; }
.w-43 { width: 43%; }
.w-44 { width: 44%; }
.w-45 { width: 45%; }
.w-46 { width: 46%; }
.w-47 { width: 47%; }
.w-48 { width: 48%; }
.w-49 { width: 49%; }
.w-50 { width: 50%; }
.w-51 { width: 51%; }
.w-52 { width: 52%; }
.w-53 { width: 53%; }
.w-54 { width: 54%; }
.w-55 { width: 55%; }
.w-56 { width: 56%; }
.w-57 { width: 57%; }
.w-58 { width: 58%; }
.w-59 { width: 59%; }
.w-60 { width: 60%; }
.w-61 { width: 61%; }
.w-62 { width: 62%; }
.w-63 { width: 63%; }
.w-64 { width: 64%; }
.w-65 { width: 65%; }
.w-66 { width: 66%; }
.w-67 { width: 67%; }
.w-68 { width: 68%; }
.w-69 { width: 69%; }
.w-70 { width: 70%; }
.w-71 { width: 71%; }
.w-72 { width: 72%; }
.w-73 { width: 73%; }
.w-74 { width: 74%; }
.w-75 { width: 75%; }
.w-76 { width: 76%; }
.w-77 { width: 77%; }
.w-78 { width: 78%; }
.w-79 { width: 79%; }
.w-80 { width: 80%; }
.w-81 { width: 81%; }
.w-82 { width: 82%; }
.w-83 { width: 83%; }
.w-84 { width: 84%; }
.w-85 { width: 85%; }
.w-86 { width: 86%; }
.w-87 { width: 87%; }
.w-88 { width: 88%; }
.w-89 { width: 89%; }
.w-90 { width: 90%; }
.w-91 { width: 91%; }
.w-92 { width: 92%; }
.w-93 { width: 93%; }
.w-94 { width: 94%; }
.w-95 { width: 95%; }
.w-96 { width: 96%; }
.w-97 { width: 97%; }
.w-98 { width: 98%; }
.w-99 { width: 99%; }
.w-100 { width: 100%; }

/* Thumbnails and previews are <img>, not background-image, for the same
   reason. object-fit reproduces what background-size:cover gave us.          */
/* The placeholder sits behind every thumbnail as a CSS background, so a URL
   that fails to load leaves something deliberate rather than a hole. Editorial
   thumbnails live on a CDN an editor pastes in, so dead links are a matter of
   when, not if — a deleted asset, an outage, a typo.
   static/js/thumbs.js then swaps the src outright, which is what removes the
   browser's broken-image icon; this background is the no-JavaScript floor. */
.thumb-img, .preview-img {
  object-fit: cover; display: block;
  background: var(--slate-50) url('../img/thumb-placeholder.svg') center/cover no-repeat;
}
/* An avatar gets a neutral fill, not the video placeholder — a play button on a
   channel's avatar would be telling the reader the wrong thing. */
.chan-avatar-img { object-fit: cover; display: block; border-radius: 999px; background: var(--slate-50); }
/* Set by thumbs.js once a src has already failed and been replaced. */
.img-fallback { object-fit: contain; }

.hint-warn { color: var(--warn-text, #8A5A00); background: #FFF8E6; border: 1px solid #F5DFA6;
  border-radius: 6px; padding: 8px 10px; }

/* ── Focus, selection, motion ────────────────────────────────────────────────
   Keyboard focus is visible on every interactive element, in the brand blue,
   using :focus-visible so a mouse click does not leave a ring behind. This is
   not decoration: the panel has no JavaScript, so keyboard and screen-reader
   users navigate it entirely by focus order. */
a:focus-visible,
button:focus-visible,
select:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
/* Radios and checkboxes are drawn by the browser; give them the ring too. */
input[type=radio]:focus-visible,
input[type=checkbox]:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}
input[type=radio], input[type=checkbox] { accent-color: var(--primary); }

::selection { background: rgba(81, 162, 255, .26); }

/* A thin scrollbar for the rail, since it only appears once the nav outgrows
   the viewport and a default-width one would crowd the labels. */
.side-nav { scrollbar-width: thin; scrollbar-color: #CBD5E1 transparent; }
.side-nav::-webkit-scrollbar { width: 8px; }
.side-nav::-webkit-scrollbar-thumb {
  background: #CBD5E1; border-radius: 999px;
  border: 2px solid transparent; background-clip: content-box;
}
.side-nav::-webkit-scrollbar-thumb:hover { background: #94A3B8; background-clip: content-box; }

/* Everything above uses transitions and one transform. Honour the setting that
   asks for none of it. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .tile:hover, .btn:active, .btn-quiet:active { transform: none; }
}

/* An alert chip: same shape as the rest, but it only appears when the count is
   non-zero, so its colour is carrying "act on this" rather than "this filter
   exists". */
.chip-alert { border-color: #FBCFE8; background: #FFF1F4; color: var(--live); }
.chip-alert:hover { background: #FFE4EA; border-color: #F9A8C4; color: var(--live); }
.chip-alert span { color: rgba(225, 29, 72, .7); }
.chip-alert.is-on {
  background: var(--live); border-color: var(--live); color: #fff;
  box-shadow: 0 1px 2px rgba(225, 29, 72, .28);
}
.chip-alert.is-on span { color: rgba(255,255,255,.8); }

/* ── Linkable stat tiles ─────────────────────────────────────────────────────
   A tile that counts something a page can list is a link to that page. The
   only visual difference from a plain tile is the affordance on hover — the
   number must not look like body copy that happens to be blue. */
.tile-link {
  display: block; text-decoration: none; color: inherit;
  cursor: pointer;
}
.tile-link:hover { text-decoration: none; border-color: var(--blue-300); }
.tile-link:hover .tile-label { color: var(--primary-dark); }

/* A tile whose count is a problem rather than a status. Red only when non-zero
   — the class is applied conditionally, so an empty queue stays neutral. */
.tile-alert { border-color: #FBCFE8; }
.tile-alert::after { background: linear-gradient(90deg, var(--live), #FB7185); }
.tile-alert .tile-value { color: var(--live); }
.tile-alert:hover { border-color: #F9A8C4; }
.tile-alert:hover .tile-label { color: var(--live); }

.grid-5 { grid-template-columns: repeat(5, 1fr); }
@media (max-width: 1180px) { .grid-5 { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 840px)  { .grid-5 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px)  { .grid-5 { grid-template-columns: 1fr; } }

/* The audit action verb: short, uppercase, monospaced digits off. */
.act {
  font-size: 11px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--primary-dark);
}

/* A secondary fact in a panel heading — a count or a qualifier that belongs
   with the title but must not compete with it. */
.panel-note { margin-left: auto; font-size: 12px; color: var(--text-subtle); }


/* ── Confirmation modal ──────────────────────────────────────────────────────
   A <dialog>, so modal semantics, focus containment and Escape come from the
   platform rather than from us. See static/js/confirm.js. */
.modal {
  padding: 0; border: 0; background: transparent;
  max-width: min(420px, calc(100vw - 32px));
}
.modal::backdrop { background: rgba(10, 25, 55, .48); }
.modal-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: 0 12px 34px -8px rgba(10, 42, 102, .34), 0 2px 6px rgba(10, 42, 102, .10);
  padding: 22px 22px 18px;
  text-align: left;
}
.modal-title {
  margin: 0 0 8px; font-size: 16px; font-weight: 700;
  letter-spacing: -.012em; color: var(--text);
}
.modal-body { margin: 0; font-size: 13.5px; line-height: 1.55; color: var(--text-muted); }
.modal-actions {
  display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px;
}
/* The buttons size to their labels here rather than filling the row. */
.modal-actions .btn { width: auto; padding: 9px 16px; font-size: 13px; }

/* No transition unless the viewer wants one. */
@media (prefers-reduced-motion: no-preference) {
  .modal[open] { animation: modal-in .16s var(--ease); }
  .modal[open]::backdrop { animation: fade-in .16s var(--ease); }
}
@keyframes modal-in {
  from { opacity: 0; transform: translateY(6px) scale(.98); }
  to   { opacity: 1; transform: none; }
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

/* ── M15: access ─────────────────────────────────────────────────────────────
   Reuses the row-link and pill vocabulary; only genuinely new shapes below. */

/* The one-time password. Monospaced, large, selectable — it exists to be read
   aloud or copied, once, and never appears again. */
.otp {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 19px; font-weight: 600; letter-spacing: .06em;
  margin: 8px 0; padding: 10px 12px;
  background: rgba(255, 255, 255, .72);
  border: 1px dashed #F5DFA6; border-radius: var(--radius);
  user-select: all; word-break: break-all;
}

/* Role checkboxes sit inline in a table cell, so they must not add row height. */
.rolebar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.rolechk { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; white-space: nowrap; }
.rolechk input { margin: 0; }

.rolecol { text-align: center; white-space: nowrap; width: 1%; }

/* The permission matrix.
   The checkbox is the whole cell's hit area, not a 13px square in the middle of
   it: this is a grid somebody drags down a column through, and a miss silently
   grants nothing rather than reporting an error. */
.permchk {
  display: block; padding: 4px 10px; cursor: pointer; border-radius: 6px;
}
.permchk:hover { background: var(--surface-2); }
.permchk input { margin: 0; width: 15px; height: 15px; accent-color: var(--primary); cursor: pointer; }
.permchk input:disabled { cursor: default; }
.permchk:has(input:disabled):hover { background: none; }

/* The button row at the foot of a long form, so Save is not lost against the
   last panel's edge. */
.form-foot {
  display: flex; align-items: center; gap: 10px;
  margin: 18px 0 4px;
}
.yes { color: var(--ok); font-weight: 700; }
.no  { color: var(--text-subtle); }

/* A wrapping description under a bold label — the row-sub ellipsis would hide
   most of a capability's explanation. */
.row-sub-wrap {
  display: block; font-size: 11.5px; color: var(--text-subtle);
  line-height: 1.45; max-width: 46ch;
}

.panel-body.pad { padding: 16px 18px; }
.panel.narrow { max-width: 460px; }
.newadmin { max-width: 420px; }
.newadmin .field:last-of-type { margin-bottom: 18px; }

.m-0-hint  { margin: 0; font-size: 13px; color: var(--text-muted); line-height: 1.55; }
.mt-8-hint { margin: 10px 0 0; font-size: 13px; color: var(--text-muted); line-height: 1.55; }

/* Audit payloads. Pre-wrapped and capped: a JSON blob must not set the table's
   width or push the row height to a screenful. */
.audit-detail > summary {
  cursor: pointer; list-style: none;
  font-size: 12px; font-weight: 600; color: var(--primary-dark);
}
.audit-detail > summary::-webkit-details-marker { display: none; }
.audit-detail > summary:hover { text-decoration: underline; }
.audit-json { display: grid; gap: 8px; margin-top: 8px; max-width: 420px; }
.audit-json pre {
  margin: 2px 0 0; padding: 8px 10px; overflow-x: auto;
  background: var(--slate-50); border: 1px solid var(--border-soft);
  border-radius: 6px; font-size: 11.5px; line-height: 1.5;
  white-space: pre-wrap; word-break: break-word;
}
.jlabel {
  font-size: 10px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-subtle);
}

/* ── M12: compliance ─────────────────────────────────────────────────────────
   Data-request cards reuse the poll-card shape; only the state edge and the
   inline action row are new. */
.dsr-card {
  margin-bottom: 12px;
  border-left: 3px solid var(--border);
  transition: box-shadow .18s var(--ease);
}
.dsr-card:hover { box-shadow: var(--shadow-md); }
/* A late request is late, not merely queued — somebody is waiting on a legal
   right, so it gets the same weight as an overdue poll. */
.dsr-late { border-left-color: var(--live); }
.dsr-soon { border-left-color: var(--gold); }
.dsr-kind { font-size: 14px; font-weight: 700; color: var(--text); }

.dsr-notes {
  padding: 10px 18px; font-size: 12.5px; color: var(--text-muted);
  background: var(--slate-50); border-top: 1px solid var(--border-soft);
  white-space: pre-wrap;
}
.dsr-actions {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 11px 18px; border-top: 1px solid var(--border-soft);
  background: var(--slate-50);
}
.dsr-advance { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 260px; }
.dsr-advance input[type=text] {
  flex: 1; min-width: 180px; padding: 7px 11px; font: inherit; font-size: 12.5px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.dsr-advance input[type=text]:focus {
  outline: none; border-color: var(--primary); box-shadow: var(--ring);
}

/* A read-only body still needs to look like the document, not like a disabled
   input — it is the thing being reviewed. */
textarea[readonly] { background: var(--slate-50); color: var(--text-muted); }
select[disabled], input[readonly] { background: var(--slate-50); color: var(--text-muted); }

/* Nowrap secondary text — a date window must not break across lines mid-range. */
.row-sub-nowrap { font-size: 11.5px; color: var(--text-subtle); white-space: nowrap; }

/* ── M13: premium ────────────────────────────────────────────────────────────
   A price input has to be unmistakably a price: the currency sits inside the
   field's frame so the number is never read without it. */
.moneyrow {
  display: flex; align-items: stretch;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--slate-50); overflow: hidden;
}
.moneyrow:focus-within { border-color: var(--primary); box-shadow: var(--ring); }
.moneypre {
  display: grid; place-items: center; padding: 0 11px;
  background: var(--slate-100); border-right: 1px solid var(--border);
  font-size: 12px; font-weight: 700; color: var(--text-muted); letter-spacing: .03em;
}
.moneyrow input {
  flex: 1; border: 0; background: transparent; border-radius: 0;
  font-variant-numeric: tabular-nums;
}
.moneyrow input:focus { outline: none; box-shadow: none; }

/* The struck-through anchor price, beside the real one. */
.anchor {
  margin-left: 6px; font-size: 11.5px; color: var(--text-subtle);
  text-decoration: line-through;
}

/* Entitlement controls on the account page. Inline, because this is something
   done while reading the panel above it rather than on a screen of its own. */
.ent-form {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 12px 18px; border-top: 1px solid var(--border-soft); background: var(--slate-50);
}
.ent-form label { font-size: 12px; font-weight: 600; color: var(--text-muted); }
.ent-form select, .ent-form input[type=number] {
  padding: 6px 10px; font: inherit; font-size: 12.5px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.ent-form input[type=number] { width: 72px; }
.ent-form select:focus, .ent-form input:focus {
  outline: none; border-color: var(--primary); box-shadow: var(--ring);
}
.ent-form .hint { flex-basis: 100%; margin: 0; }
.ent-note {
  padding: 12px 18px; border-top: 1px solid var(--border-soft);
  background: var(--blue-50); font-size: 12.5px; color: var(--text-muted); line-height: 1.5;
}

/* ── M7: competitions, seasons, brackets ─────────────────────────────────────
   A square thumbnail for a crest or logo, where the 16:9 one would letterbox. */
.thumb-sq { width: 34px; height: 34px; }
.preview-sq { height: 96px; max-width: 96px; }

.stage-add { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.stage-add input[type=text] { flex: 1; min-width: 200px; }
.stage-add input[type=number] { width: 96px; }
.stage-add label { font-size: 12px; font-weight: 600; color: var(--text-muted); }
.stage-add .hint { flex-basis: 100%; margin: 0; }

/* The draw sheet. Rounds are columns and scroll horizontally, because a
   128-draw is 8 rounds wide and no amount of layout makes that fit. */
.bracket {
  display: flex; gap: 14px; align-items: flex-start;
  overflow-x: auto; padding: 4px 2px 12px;
}
.bracket-round { flex: 0 0 auto; min-width: 168px; }
.bracket-round-head {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 10.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-muted);
  padding: 0 2px 7px;
}
.bracket-round-head span { color: var(--text-subtle); letter-spacing: 0; }
.bracket-slot {
  display: flex; align-items: center; gap: 7px;
  min-height: 34px; margin-bottom: 5px; padding: 6px 10px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-sm); font-size: 12.5px;
}
.bracket-slot.is-filled { border-left: 3px solid var(--primary); }
.bracket-slot.is-bye { background: var(--slate-50); color: var(--text-subtle); }
.bracket-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bracket-seed {
  display: grid; place-items: center; flex: 0 0 auto;
  width: 19px; height: 19px; border-radius: 999px;
  background: var(--blue-50); color: var(--primary-dark);
  font-size: 10px; font-weight: 700;
}
