/* ===== ExcelRate Movement Analysis ===== */
:root {
  color-scheme: light dark;
  --ink:      #0e1726;
  --ink-2:    #3a4a63;
  --muted:    #647289;
  --line:     #e3e8f0;
  --bg:       #f4f6fa;
  --card:     #ffffff;
  --navy:     #122b54;
  --accent:   #2f6df6;
  --accent-d: #1d51c9;
  --lime:     #16b86b;
  --amber:    #e88a1a;
  --red:      #d93a3a;
  /* A soft, extended shadow rather than a tight one -- surfaces read as
     floating above the page instead of outlined onto it, the materials
     feel the craft lens asks for. Three layers: a hairline contact shadow,
     a short-range layer for edge definition, a long, very faint layer that
     does the actual "floating" work. */
  --shadow:   0 1px 1px rgba(16,30,54,.03), 0 4px 14px rgba(16,30,54,.05), 0 20px 48px rgba(16,30,54,.06);
  --shadow-hover: 0 1px 1px rgba(16,30,54,.03), 0 8px 22px rgba(16,30,54,.08), 0 28px 64px rgba(16,30,54,.09);
  --radius:   20px;
  --radius-sm: 12px;

  /* Neutral surfaces one and two steps above the page background --
     the app's most-repeated "faint fill" (nested rows, dropzones, chip
     tracks) and "hairline fill" (grid lines, dividers, quiet dividers).
     Kept as their own step from --card so a nested surface still reads as
     recessed against a card, in both appearances. */
  --surface-2: #fafbfd;
  --surface-3: #eef1f6;

  /* Semantic state tints: each pairs a pale fill with a border and an ink
     color, reused verbatim by every badge/chip/pill in the app (verdicts,
     KPI tiles, quiz/exam feedback, alerts) -- one decision per state
     instead of the dozen near-duplicate hex pairs this file used to carry.
     --tint-accent-ink intentionally equals --accent-d for the family that
     needs a stronger tone than --accent itself (chart headline etc). */
  --tint-accent:        #f1f5ff;
  --tint-accent-border: #d7e2fb;
  --tint-buggy:         #f1eafe;
  --tint-buggy-border:  #d6c4fb;
  --tint-success:       #e8faf1;
  --tint-success-border:#9fe0c1;
  --tint-success-ink:   #0f7a47;
  --tint-danger:        #fdecec;
  --tint-danger-border: #f3b6b6;
  --tint-danger-ink:    #a12a2a;
  --tint-warning:       #fff4e5;
  --tint-warning-border:#f3c98a;
  --tint-warning-ink:   #a05a00;

  --focus-ring: 0 0 0 3px rgba(47,109,246,.28);
  --ease: cubic-bezier(.2,.7,.3,1);
  --line-strong: #b9c4d6;
}

/* One dark palette for the whole app: only the tokens above are redefined,
   so anything already written as var(--ink)/var(--card)/var(--line)/etc.
   (the overwhelming majority of this file) repaints for free. State tints
   (success/danger/warning/accent) get their own dark-appropriate values
   further down, next to the components that use them, rather than here --
   the pale-on-white pairing that works in light mode needs a different
   fill/ink balance on a dark surface, not just an inversion.

   Two ways in: the system appearance (this media query), or the top-bar
   toggle forcing one appearance via `data-theme` on <html> (auth.js
   `applyTheme`, run before first paint so there's no flash). The system
   query is guarded by :not([data-theme="light"]) so an explicit "light"
   choice overrides a dark system, and the same declarations repeat under
   :root[data-theme="dark"] below (outside the media query, and after it
   in source order) so an explicit "dark" choice applies over a light
   system too. Duplicated once rather than shared, since plain CSS has no
   way to reuse a declaration block across two selectors -- keep both in
   sync if either changes. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ink:      #eef2f9;
    --ink-2:    #b9c3d6;
    --muted:    #8794ac;
    --line:     #262f42;
    --bg:       #0b1220;
    --card:     #131b2c;
    --surface-2:#101725;
    --surface-3:#1b2438;
    --accent:   #5b8cff;
    --accent-d: #7fa4ff;
    --lime:     #34c785;
    --amber:    #e8a23a;
    --red:      #f0605f;
    --line-strong: #37425c;
    --shadow:   0 1px 1px rgba(0,0,0,.24), 0 4px 14px rgba(0,0,0,.28), 0 20px 48px rgba(0,0,0,.32);
    --shadow-hover: 0 1px 1px rgba(0,0,0,.24), 0 8px 22px rgba(0,0,0,.34), 0 28px 64px rgba(0,0,0,.4);
    --tint-accent:        #182544;
    --tint-accent-border: #5578c2;
    --tint-buggy:         #2a2145;
    --tint-buggy-border:  #6b55a8;
    --tint-success:       #12291f;
    --tint-success-border:#2f6b4c;
    --tint-success-ink:   #34c785;
    --tint-danger:        #351a1c;
    --tint-danger-border: #8a3b3d;
    --tint-danger-ink:    #f0605f;
    --tint-warning:       #332108;
    --tint-warning-border:#8a6423;
    --tint-warning-ink:   #e8a23a;
  }
}
:root[data-theme="dark"] {
  --ink:      #eef2f9;
  --ink-2:    #b9c3d6;
  --muted:    #8794ac;
  --line:     #262f42;
  --bg:       #0b1220;
  --card:     #131b2c;
  --surface-2:#101725;
  --surface-3:#1b2438;
  --accent:   #5b8cff;
  --accent-d: #7fa4ff;
  --lime:     #34c785;
  --amber:    #e8a23a;
  --red:      #f0605f;
  --line-strong: #37425c;
  --shadow:   0 1px 1px rgba(0,0,0,.24), 0 4px 14px rgba(0,0,0,.28), 0 20px 48px rgba(0,0,0,.32);
  --shadow-hover: 0 1px 1px rgba(0,0,0,.24), 0 8px 22px rgba(0,0,0,.34), 0 28px 64px rgba(0,0,0,.4);
  --tint-accent:        #182544;
  --tint-accent-border: #5578c2;
  --tint-buggy:         #2a2145;
  --tint-buggy-border:  #6b55a8;
  --tint-success:       #12291f;
  --tint-success-border:#2f6b4c;
  --tint-success-ink:   #34c785;
  --tint-danger:        #351a1c;
  --tint-danger-border: #8a3b3d;
  --tint-danger-ink:    #f0605f;
  --tint-warning:       #332108;
  --tint-warning-border:#8a6423;
  --tint-warning-ink:   #e8a23a;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* The `hidden` attribute must always win over class-level `display` rules
   (e.g. .error-card sets display:flex), otherwise hidden sections show. */
[hidden] { display: none !important; }

body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  background: var(--bg);
  color: var(--ink);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  transition: background-color .2s var(--ease), color .2s var(--ease);
}

/* One consistent keyboard-focus ring for every native interactive element
   that doesn't already define its own (several components above have a
   more specific rule already and simply win by specificity). Mouse/touch
   clicks never show it -- :focus-visible only engages for keyboard and
   other non-pointer focus, so it adds nothing to look at while clicking
   around, only while tabbing. */
a:focus-visible, button:focus-visible, summary:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
  body { transition: none; }
}

code, .mono { font-family: 'JetBrains Mono', monospace; }

/* ---- top bar ---- */
.topbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px 24px; flex-wrap: wrap;
  padding: 16px 32px;
  background: var(--navy);
  color: #fff;
}
@media (max-width: 760px) {
  .topbar { padding: 14px 16px; }
  .topnav { flex-wrap: wrap; row-gap: 4px; }
  .brand-sub { display: none; }
}
.brand { display: flex; align-items: center; gap: 12px; }
.brand-logo {
  display: block; height: 26px; width: auto;
  background: #f4f6fa; padding: 5px 10px; border-radius: 8px;
}
.brand-mark { color: var(--accent); font-size: 16px; transform: translateY(-1px); }
.brand-name { font-weight: 800; font-size: 19px; letter-spacing: -.02em; }
.brand-sub  { font-weight: 500; font-size: 13px; color: #9db0d0; }
.topbar-note { font-size: 12.5px; color: #9db0d0; }

/* ---- unchecked-clip backlog banner (captures.html/Captures) ----
   The clip-review backlog: every phone's clips still owing an admin a
   verdict/approval, or (the one reason an operator sees it too) an invalid
   clip nobody has commented on yet. It sits in the flow at the very top of
   the page and deliberately has no close control -- it clears by reviewing
   the clips, not by dismissing it. Being in the flow, it scrolls away with
   the rest of the page instead of following the reader down over the
   content. */
.unchecked-banner {
  display: block; margin: 0 0 24px;
  background: var(--tint-danger); border: 1px solid var(--red); border-left-width: 5px;
  border-radius: 12px; padding: 12px 16px;
  font-size: 12.5px; color: var(--tint-danger-ink);
}
.unchecked-title {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; font-weight: 800; color: var(--red); letter-spacing: -.01em;
}
/* a slow pulse -- enough to catch the eye on load without ever settling into
   something the page's own motion, so it isn't mistaken for a spinner */
.unchecked-dot {
  width: 8px; height: 8px; border-radius: 50%; background: var(--red);
  flex: none; animation: unchecked-pulse 2.4s ease-in-out infinite;
}
@keyframes unchecked-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
@media (prefers-reduced-motion: reduce) {
  .unchecked-dot { animation: none; }
}
.unchecked-list { list-style: none; margin: 9px 0 0; padding: 0; }
.unchecked-row {
  padding: 7px 0; border-top: 1px solid rgba(217,58,58,.18);
}
.unchecked-row:first-child { border-top: 0; padding-top: 0; }
.unchecked-row-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
/* the phone name jumps to that phone -- the banner is a work queue, so the
   fastest thing it can do is take you to the clips it is counting */
.unchecked-phone {
  font: 600 12.5px 'Inter', sans-serif; color: var(--tint-danger-ink);
  background: none; border: 0; padding: 0; cursor: pointer; text-align: left;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.unchecked-phone:hover { color: var(--red); text-decoration: underline; }
.unchecked-n { font-weight: 800; color: var(--red); flex: none; }
/* the exact action(s) outstanding for this phone, spelled out rather than
   left for the admin to work out from a bare count */
.unchecked-reasons {
  list-style: none; margin: 4px 0 0; padding: 0;
  font-size: 11.5px; color: var(--tint-danger-ink); line-height: 1.4;
}
.unchecked-reasons li { padding: 1px 0; }
@media (max-width: 760px) {
  .unchecked-banner { margin-bottom: 18px; }
}

/* ---- top nav ---- */
.topnav { display: flex; align-items: center; gap: 6px; }
.topnav-link {
  color: #9db0d0; text-decoration: none; font-size: 13.5px; font-weight: 600;
  padding: 7px 14px; border-radius: 8px; transition: .15s;
}
.topnav-link:hover { color: #fff; background: rgba(255,255,255,.08); }
.topnav-link.active { color: #fff; background: rgba(47,109,246,.28); }

/* overflow ("...") menu next to Captures / Matches / Ready for platform,
   holding the rest of the nav (Analyze, Analytics, Operators exam). Same
   pill language as .topnav-link so the trigger reads as part of the nav
   rather than a bolted-on control; it also picks up .active (via the "active"
   class added server-side, i.e. baked into each page's own markup) when the
   current page is one of the items tucked inside it, so the ladder never
   goes fully unhighlighted. */
.topnav-more { position: relative; }
.topnav-more-btn {
  color: #9db0d0; background: none; border: 0; cursor: pointer;
  font-size: 15px; font-weight: 700; letter-spacing: 1px; line-height: 1;
  padding: 7px 12px; border-radius: 8px; transition: .15s;
}
.topnav-more-btn:hover { color: #fff; background: rgba(255,255,255,.08); }
.topnav-more-btn.active,
.topnav-more-btn[aria-expanded="true"] { color: #fff; background: rgba(47,109,246,.28); }
.topnav-more-menu {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 20;
  display: flex; flex-direction: column; gap: 2px; min-width: 168px;
  background: var(--card); border: 1px solid var(--line); border-radius: 10px;
  padding: 6px; box-shadow: 0 10px 28px rgba(15,25,50,.18);
}
.topnav-more-link {
  color: var(--ink-2); text-decoration: none; font-size: 13.5px; font-weight: 600;
  padding: 8px 10px; border-radius: 7px; white-space: nowrap;
}
.topnav-more-link:hover { background: var(--tint-accent); color: var(--ink); }
.topnav-more-link.active { color: var(--accent); background: var(--tint-accent); }

/* ===== captures page ===== */
.device-row { display: flex; flex-wrap: wrap; gap: 10px; }
/* one phone per line while "Phone access" is checked (see renderDevices in
   captures.js) -- keeps each chip's flag icon and/or checkbox unambiguous. */
.device-row.device-row-stacked { flex-direction: column; align-items: flex-start; }
/* a card whose title shares its line with a control on the right (the phone
   picker's "Show status" toggle) -- the title's own bottom margin moves to
   the row so both sit on one baseline */
.card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 16px;
}
.card-head .card-title { margin-bottom: 0; }
/* groups every toggle on the right of a card-head into one flex item, so
   card-head's justify-content:space-between keeps them side by side instead
   of spreading a 3-way split (title / toggle / toggle) apart -- same reason
   clips-head-right exists below for the clips list's own head row. */
.card-head-right { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
/* same pill as the clip list's player search, so the two read as one
   control language */
.status-vis-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border: 1px solid var(--tint-accent-border); border-radius: 999px;
  font: 600 12px 'Inter', sans-serif; color: var(--ink-2);
  background: var(--tint-accent); cursor: pointer; user-select: none; white-space: nowrap;
}
.status-vis-toggle:hover { border-color: var(--accent); color: var(--accent); }
.status-vis-toggle input {
  accent-color: var(--accent); width: 14px; height: 14px; margin: 0; cursor: pointer;
}

/* the "Show status" breakdown table under the phone chips: one row per
   phone, what each still owes. Scrolls sideways on narrow screens rather
   than squeezing the columns. */
.status-panel { margin-top: 16px; overflow-x: auto; }
.status-table {
  border-collapse: collapse; width: 100%; min-width: 560px;
  font-size: 12.5px; color: var(--ink-2);
}
.status-table th, .status-table td {
  padding: 7px 10px; text-align: center; white-space: nowrap;
}
.status-table thead th {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--muted); border-bottom: 1px solid var(--line);
}
/* the two grouped headers ("Incomplete profile" / "No valid clip") get a
   light rule under them so it's clear which columns they cover */
.status-table thead tr:first-child th[colspan] {
  border-bottom: 1px solid var(--line); color: var(--ink-2);
}
.status-table tbody th, .status-table tfoot th { text-align: left; font-weight: 700; }
.status-table tbody tr { border-top: 1px solid var(--line); }
.status-table tbody tr:first-child { border-top: 0; }
.status-table tbody tr:hover { background: var(--surface-2); }
/* the phone currently being browsed, so the table and the chip row agree */
.status-table tr.st-current { background: var(--tint-accent); }
.status-table tr.st-current:hover { background: var(--tint-accent); }
.st-phone {
  font: 700 12.5px 'Inter', sans-serif; color: var(--ink);
  background: none; border: 0; padding: 0; cursor: pointer;
}
.st-phone:hover { color: var(--accent); text-decoration: underline; }
.st-dim { color: var(--muted); }
.st-ready { font-weight: 800; color: var(--ink); }
.st-ready-all { color: var(--tint-success-ink); }
/* how far along this phone is, under its count -- the table is a work
   queue, and a bar reads faster than dividing two numbers by eye */
.st-bar {
  height: 3px; border-radius: 2px; background: var(--surface-3);
  margin: 3px auto 0; width: 54px; overflow: hidden;
}
.st-bar i { display: block; height: 100%; background: var(--accent); }
.st-ready-all + .st-bar i { background: var(--lime); }
/* a count of players still outstanding; zero is deliberately quiet so the
   numbers that need attention are the only thing that stands out */
.st-n { font-weight: 700; color: var(--tint-warning-ink); }
.st-zero { color: var(--line-strong); }
.status-table tfoot tr {
  border-top: 2px solid var(--line); background: var(--surface-2); font-weight: 700;
}
.st-total-label { color: var(--muted); text-transform: uppercase;
  font-size: 11px; letter-spacing: .05em; }
.status-note {
  margin-top: 10px; font-size: 11.5px; color: var(--muted); line-height: 1.45;
  max-width: 760px;
}

/* "Phone access" toggle (guy@excelrate.football only, see captures.js's
   isPhoneAccessAdmin/operator_phones_get/set) -- same pill language as
   "Show status" beside it, with a lock glyph so it doesn't read as just
   another view preference. */
.phone-access-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border: 1px solid var(--tint-accent-border); border-radius: 999px;
  font: 600 12px 'Inter', sans-serif; color: var(--ink-2);
  background: var(--tint-accent); cursor: pointer; user-select: none; white-space: nowrap;
}
.phone-access-toggle:hover { border-color: var(--accent); color: var(--accent); }
.phone-access-toggle input {
  accent-color: var(--accent); width: 14px; height: 14px; margin: 0; cursor: pointer;
}
/* the dropdown sits right beside the toggle (card-head-right), same row --
   picking an operator adds .device-chip-check to each phone chip below
   instead of showing a list of its own (see phoneAccessCheckboxHtml /
   deviceChip in captures.js). */
.phone-access-select {
  font: 600 12px 'Inter', sans-serif; color: var(--ink);
  padding: 6px 10px; border: 1px solid var(--tint-accent-border); border-radius: 999px;
  background: var(--tint-accent); cursor: pointer;
}
/* the checkbox itself: a small pill right next to its chip, a sibling
   rather than nested inside the chip's own <button> so a click on it never
   also triggers the chip's click-to-browse handler. */
/* Only rendered when a flag icon and/or access checkbox sits beside the
   chip as a sibling (see deviceChip in captures.js) -- the dashed frame
   groups them visually so it's clear which icon/checkbox goes with which
   chip. */
.device-chip-wrap {
  display: inline-flex; align-items: center; gap: 4px;
  border: 1px dashed var(--line-strong); border-radius: 12px; padding: 3px;
}
.device-chip-check {
  display: inline-flex; align-items: center;
  border: 1px solid var(--line); border-radius: 10px; background: var(--surface-2);
  padding: 10px 12px; cursor: pointer;
}
.device-chip-check:hover { border-color: var(--accent); }
.device-chip-check input[type="checkbox"] {
  accent-color: var(--accent); width: 15px; height: 15px; cursor: pointer; margin: 0;
}
.device-chip-check input[type="checkbox"]:disabled { cursor: wait; }

/* "N/M players complete" on a phone chip, shown only while "Show status" is
   on: green once the whole phone is done, amber while there is work left,
   grey until the count resolves. */
.device-ready {
  font: 800 11px 'Inter', sans-serif; border-radius: 999px;
  padding: 1px 8px; background: var(--surface-3); color: var(--ink-2); white-space: nowrap;
}
.device-ready-all  { background: var(--tint-success); color: var(--tint-success-ink); }
.device-ready-part { background: var(--tint-warning); color: var(--tint-warning-ink); }
.device-ready-wait { color: var(--muted); letter-spacing: .06em; }

.device-chip {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 10px;
  padding: 10px 16px; font: 600 14px 'Inter', sans-serif; color: var(--ink-2);
  cursor: pointer; transition: .15s;
}
.device-chip:hover { border-color: var(--line-strong); color: var(--ink); }
.device-chip.active {
  border-color: var(--accent); background: var(--tint-accent); color: var(--accent);
  box-shadow: 0 0 0 3px rgba(47,109,246,.12);
}
.device-dot { font-size: 16px; line-height: 1; }
.device-dot.device-flag { display: inline-flex; }
.device-dot.device-flag svg {
  display: block; border-radius: 2px; box-shadow: 0 0 0 1px rgba(0, 0, 0, .12);
}

/* Flag editing (while "Phone access" is checked -- see phoneAccessEditingFlags
   in captures.js): the flag icon pulls out of the chip into its own sibling
   button, same reason as .device-chip-check above, with a dropdown of the
   known flags anchored under it. */
.device-flag-wrap { position: relative; display: inline-flex; }
.device-flag-edit {
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 10px; background: var(--surface-2);
  padding: 8px 10px; cursor: pointer;
}
.device-flag-edit:hover, .device-flag-edit.active { border-color: var(--accent); }
.device-flag-picker {
  position: absolute; top: calc(100% + 4px); left: 0; z-index: 20;
  display: flex; flex-direction: column; min-width: 140px;
  background: var(--card); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 8px 24px rgba(20, 30, 60, .14); padding: 4px; gap: 2px;
}
.device-flag-option {
  display: flex; align-items: center; gap: 8px;
  border: none; background: none; border-radius: 6px; padding: 6px 8px;
  font: 600 13px 'Inter', sans-serif; color: var(--ink-2); cursor: pointer; text-align: left;
}
.device-flag-option:hover { background: var(--tint-accent); color: var(--ink); }
.device-flag-option.active { background: var(--tint-accent); color: var(--accent); }

/* incomplete-profile mark (missing photos and/or bio) -- on a "By player"
   group's title and, aggregated, on that phone's device chip. Hover for the
   native title tooltip listing exactly what's missing. */
.issue-mark {
  cursor: help; font-size: 11.5px; font-weight: 700; line-height: 1;
  color: var(--red); white-space: nowrap;
}

.player-swatch {
  width: 18px; height: 18px; border-radius: 50%; flex-shrink: 0;
  border: 1px solid rgba(16,30,54,.12);
  display: inline-flex; align-items: center; justify-content: center;
}
.player-swatch-inner {
  width: 8px; height: 8px; border-radius: 50%;
  border: 1px solid rgba(0,0,0,.25);
}
.player-name { font-weight: 600; }
.player-num { color: var(--muted); font-family: 'JetBrains Mono', monospace; font-size: 12px; }

.clips-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 14px; flex-wrap: wrap; gap: 10px;
}
.clips-head-right { display: flex; align-items: center; gap: 12px; }

/* group-by-player / group-by-test blocks: collapsed by default, native
   <details> expand-on-click, styled like the per-clip chart fold. */
.group-block {
  margin-bottom: 14px; background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 0;
  transition: box-shadow .2s var(--ease), transform .2s var(--ease);
}
/* the whole card is the visual click target for its summary, so the lift
   reads as "this expands" rather than decoration -- collapsed state only,
   an open block has nothing left to invite a click toward. */
.group-block:not([open]):hover { box-shadow: var(--shadow-hover); transform: translateY(-1px); }
@media (prefers-reduced-motion: reduce) {
  .group-block:not([open]):hover { transform: none; }
}
.group-block > summary {
  list-style: none; cursor: pointer; padding: 16px 20px;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
.group-block > summary::-webkit-details-marker { display: none; }
.group-block > summary:hover .group-title { color: var(--accent); }
.group-title { font-size: 15px; font-weight: 700; letter-spacing: -.01em; flex: 1; }
.group-count {
  background: var(--surface-3); color: var(--ink-2); border-radius: 999px;
  padding: 1px 9px; font-size: 11px;
}
/* Verdict tally on a group's collapsed header -- how many of its videos are
   valid / invalid / still unjudged, and how many of the invalid ones have
   been commented on or had that comment approved, without opening it (see
   groupVerdictsHtml in captures.js). Same colour language as the per-clip
   verdict button (.invalid-btn.verdict-*) and the .approved-chip so they
   read as one system. */
.group-verdicts { display: flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 700; }
.gv-count { border-radius: 999px; padding: 1px 9px; white-space: nowrap; }
.gv-valid { background: var(--tint-success); color: var(--tint-success-ink); }
.gv-invalid { background: var(--tint-danger); color: var(--red); }
.gv-unjudged { background: var(--surface-3); color: var(--muted); }
/* The two comment tallies ride INSIDE the invalid chip -- they only count
   invalid clips, so they are part of what "3 invalid" means rather than
   chips of their own (see groupVerdictsHtml). A middot separates them from
   the count they qualify; "approved" borrows the green of the "Approved by
   Admin" chip it is counting, which reads cleanly on the chip's pale red. */
.gv-sub { font-weight: 600; opacity: .85; }
.gv-sub::before { content: "·"; margin: 0 4px; opacity: .55; font-weight: 700; }
.gv-sub-approved { color: var(--tint-success-ink); opacity: 1; }
.group-chevron { color: var(--muted); font-size: 13px; transition: transform .2s; }
.group-block[open] > summary .group-chevron { transform: rotate(180deg); }
.group-body { padding: 0 20px 20px; }

/* "By player" name search (hidden in "By test" mode) -- filters the day
   sections down to the matching players and force-opens the days holding
   them, see applyPlayerFilter in captures.js. Sized to sit level with the
   grouping toggle beside it. */
.player-search {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border: 1px solid var(--tint-accent-border); border-radius: 999px;
  background: var(--tint-accent); white-space: nowrap;
}
.player-search:focus-within { border-color: var(--accent); }
.player-search-icon { color: var(--muted); font-size: 14px; line-height: 1; }
.player-search:focus-within .player-search-icon { color: var(--accent); }
.player-search input {
  font: 600 12px 'Inter', sans-serif; color: var(--ink);
  border: 0; outline: none; background: transparent; width: 130px; padding: 0;
}
.player-search input::placeholder { color: var(--muted); font-weight: 500; }
.player-search input::-webkit-search-cancel-button { cursor: pointer; }
.player-search-count { font: 600 11px 'Inter', sans-serif; color: var(--muted); }
.player-search-count:empty { display: none; }
/* a query nobody matches: the box says so rather than leaving an empty list */
.player-search-empty { border-color: var(--red); background: var(--tint-danger); }
.player-search-empty .player-search-count { color: var(--red); }

/* "By player" mode: one section per test day, newest first, the players
   who tested that day A-Z inside it (see renderClips in captures.js). Each
   day is a collapsed <details>; the heading stays a quiet date divider --
   not a card -- with only a small caret marking that it opens. */
.day-section { margin-bottom: 26px; }
.day-section[open] { margin-bottom: 32px; }
.day-heading {
  list-style: none; cursor: pointer;
  display: flex; align-items: baseline; gap: 10px; margin: 0;
  padding-bottom: 6px; border-bottom: 1px solid var(--line);
  font-size: 13px; font-weight: 700; letter-spacing: .02em;
  text-transform: uppercase; color: var(--ink-2);
}
.day-heading::-webkit-details-marker { display: none; }
.day-heading:hover { color: var(--accent); }
.day-caret {
  font-size: 10px; color: var(--muted); transition: transform .18s;
  display: inline-block; transform-origin: 45% 55%;
}
.day-heading:hover .day-caret { color: var(--accent); }
.day-section[open] > .day-heading .day-caret { transform: rotate(90deg); }
.day-heading-count {
  font-size: 11px; font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--muted);
}
.day-body { padding-top: 10px; }

/* a "By player" group's profile photos, above its clip-grid */
.group-photos {
  border: 1px solid var(--line); border-radius: 12px; padding: 14px;
  background: var(--surface-2); margin-bottom: 16px;
}

/* mobile-app bio pills (height/weight/DOB/strong foot), above the player's
   profile photos in that same box -- see playerBioLine in captures.js. */
.player-bio { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.bio-pill {
  background: var(--surface-3); color: var(--ink-2); border-radius: 999px;
  padding: 3px 10px; font-size: 12px; font-weight: 600;
}

/* a "By player" group's per-test sub-groups: nested + lighter, collapsed
   by default. */
.test-subgroup.group-block {
  background: var(--surface-2); box-shadow: none; margin: 0 0 10px;
}
.test-subgroup:last-child { margin-bottom: 0; }
.test-subgroup > summary { padding: 12px 16px; }
.test-subgroup .group-body { padding: 0 16px 16px; }

/* a "By test" group's sort-by-KPI toolbar, above its clip-grid */
.group-sort {
  display: flex; align-items: center; gap: 8px; margin-bottom: 16px;
  font-size: 12.5px; color: var(--muted);
}
.group-sort label { font-weight: 600; }
.group-sort-select {
  font: inherit; font-size: 12.5px; color: var(--ink);
  background: var(--card); border: 1px solid var(--line); border-radius: 7px;
  padding: 5px 8px;
}
.group-sort-dir {
  font: inherit; font-size: 12.5px; font-weight: 700; color: var(--ink-2);
  background: var(--card); border: 1px solid var(--line); border-radius: 7px;
  padding: 5px 10px; cursor: pointer; white-space: nowrap;
}
.group-sort-dir:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.group-sort-dir:disabled { opacity: .5; cursor: default; }
/* "⤓ Report" on a "By player" header (captures.js reportButton): same quiet
   outline as the sort toggle, so it doesn't compete with the player's name. */
.report-btn {
  font: inherit; font-size: 11.5px; font-weight: 700; color: var(--ink-2);
  background: var(--card); border: 1px solid var(--line); border-radius: 999px;
  padding: 2px 10px; cursor: pointer; white-space: nowrap;
}
.report-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.report-btn:disabled { opacity: .6; cursor: progress; }
/* "◎ Mark cones for all videos", after the sort controls */
.group-markall { margin-left: 8px; }
.group-markall-status { font-weight: 600; color: var(--ink-2); }

.clip-grid { display: grid; grid-template-columns: 1fr; gap: 16px; }

/* Each clip is its own <details>, collapsed by default -- a "By test" group
   can have hundreds of clips, and rendering every one's video pair + KPIs +
   chart open at once was both a wall of scrolling and a lot of unplayed
   <video> elements sitting in the DOM. Same collapsed-by-default pattern as
   .group-block, one level down. */
.clip-card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 0;
  transition: box-shadow .2s var(--ease), transform .2s var(--ease);
}
.clip-card:not([open]):hover { box-shadow: var(--shadow-hover); transform: translateY(-1px); }
@media (prefers-reduced-motion: reduce) {
  .clip-card:not([open]):hover { transform: none; }
}
/* the clip a #clip= deep link (Analytics suspect popup) landed on */
.clip-card.deeplink-target {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.clip-card > summary.clip-top {
  list-style: none; cursor: pointer; padding: 20px; margin-bottom: 0;
}
.clip-card > summary.clip-top::-webkit-details-marker { display: none; }
.clip-top {
  display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap;
  margin-bottom: 16px;
}
.clip-card-chevron { color: var(--muted); font-size: 13px; transition: transform .2s; flex: none; align-self: center; }
.clip-card[open] > summary.clip-top .clip-card-chevron { transform: rotate(180deg); }
.clip-card-body { padding: 0 20px 20px; }
.clip-id { flex: 1; min-width: 180px; }
.clip-test { font-size: 16px; font-weight: 700; letter-spacing: -.01em; }
.clip-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin-top: 4px; font-size: 12.5px; color: var(--muted);
}
.clip-player { font-weight: 600; color: var(--ink-2); }
.clip-num { font-family: 'JetBrains Mono', monospace; }
.clip-time, .clip-size { font-family: 'JetBrains Mono', monospace; font-size: 11.5px; }
.clip-analyzed { font-size: 11px; color: var(--muted); opacity: .75; }

/* The KPI a "By test" group is currently sorted by (see sortKpiChip in
   captures.js) -- accent-colored so it reads as "this is why this card is
   where it is in the list" at a glance, distinct from the neutral
   status-badge/clip-meta text around it, without expanding the card. */
.clip-sort-kpi {
  align-self: flex-start; display: inline-flex; align-items: baseline; gap: 5px;
  background: var(--tint-accent); color: var(--accent); border-radius: 999px;
  padding: 4px 12px; font-size: 12px; font-weight: 700; white-space: nowrap;
}
.clip-sort-kpi-label { font-weight: 600; opacity: .8; }
.clip-sort-kpi strong { font-size: 13px; font-weight: 800; }
.clip-sort-kpi-unit { font-size: 10.5px; font-weight: 600; opacity: .8; margin-left: 1px; }

.status-badge {
  align-self: flex-start; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  padding: 4px 10px; border-radius: 999px; white-space: nowrap;
}
.status-badge.ok   { background: var(--tint-success); color: var(--tint-success-ink); }
.status-badge.busy { background: var(--tint-accent); color: var(--accent); }
.status-badge.wait { background: var(--surface-3); color: var(--muted); }
.status-badge.warn { background: var(--tint-warning); color: var(--amber); }
.status-badge.err  { background: var(--tint-danger); color: var(--red); }

/* "Mark cones" reuses the shared .reanalyze-btn pill styling but in the warn
   accent so it reads as an action the clip is waiting on. */
.markcones-btn { border-color: var(--tint-warning-border); color: var(--amber); }
.markcones-btn:hover { border-color: var(--amber); color: var(--amber); background: var(--tint-warning); }

.reanalyze-btn {
  align-self: flex-start; font: inherit; font-size: 11.5px; font-weight: 700;
  color: var(--accent); background: var(--tint-accent); border: 1px solid var(--tint-accent-border);
  padding: 4px 10px; border-radius: 999px; cursor: pointer; white-space: nowrap;
}
.reanalyze-btn:hover:not(:disabled) { background: var(--tint-accent); }
.reanalyze-btn:disabled { opacity: .65; cursor: default; }

.clip-pending {
  width: 100%; height: 100%; min-height: 160px; border-radius: 11px;
  background: var(--surface-2); border: 1px dashed var(--line-strong); color: var(--muted);
  display: flex; align-items: center; justify-content: center; text-align: center;
  padding: 18px; font-size: 13px;
}

/* clip-card while a re-analysis is in flight -- see toLoadingCard() */
.clip-loading { text-align: center; padding: 34px 10px 26px; }

/* Compact metrics: a distance/segment's time and its average speed share one
   row (see metricRows() in captures.js) instead of each taking a whole tile.
   All KPIs are equal weight (no highlighted/primary metric). Two fixed
   columns so a lone chip in the last row sits at half width, not stretched
   full-width, matching every other row's chip size. */
.clip-metrics { display: flex; flex-direction: column; gap: 6px; margin-top: 16px; }
.cm-row { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.cm-chip {
  min-width: 0; display: flex; align-items: baseline; justify-content: space-between;
  gap: 8px; background: var(--surface-2); border: 1px solid var(--line); border-radius: 8px;
  padding: 6px 10px;
}
.cm-chip-label {
  font-size: 11px; font-weight: 600; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cm-chip-val { font-size: 13.5px; font-weight: 800; letter-spacing: -.01em; white-space: nowrap; }
.cm-unit { font-size: 10.5px; font-weight: 600; opacity: .75; margin-left: 2px; }
.cm-unit.deg { vertical-align: top; }

/* Skeleton card (see skeletonClipCard in captures.js): a clip whose day
   hasn't finished loading in the background yet -- everything real (test,
   player, date, size, the original video) shows immediately, a spinner
   sits where the annotated video/KPIs/chart will appear once it lands. */
.clip-card-skeleton .clip-top { opacity: .85; }
.clip-skel-loading {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  color: var(--muted); font-size: 13.5px; font-weight: 600;
  padding: 18px 4px; text-align: center;
}
.clip-skel-metrics { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 16px; }
.mini-spinner {
  width: 14px; height: 14px; flex: none;
  border: 2px solid #e3e8f0; border-top-color: var(--accent);
  border-radius: 50%; animation: spin 0.8s linear infinite;
}

.cap-empty {
  color: var(--muted); font-size: 14px; padding: 18px 4px; text-align: center;
}

/* Switching phones: shown in place of the clip list until the newly selected
   phone's own skeleton lands, so the previous phone's players never linger
   under the new phone's name (see clipsLoadingBlock in captures.js). Three
   shimmering bars stand in for the player/test group rows that will replace
   them, sized like a collapsed .group-block summary. */
.cap-loading { padding: 4px 0 8px; }
.cap-loading-note {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  color: var(--muted); font-size: 13.5px; font-weight: 600;
  padding: 6px 4px 16px; text-align: center;
}
.cap-skel-row {
  height: 57px; margin-bottom: 14px; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--card);
  box-shadow: var(--shadow); position: relative; overflow: hidden;
}
.cap-skel-row::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, var(--surface-3) 45%, var(--surface-3) 55%, transparent);
  transform: translateX(-100%); animation: cap-skel-sweep 1.25s ease-in-out infinite;
}
.cap-skel-row:nth-child(3)::after { animation-delay: .15s; }
.cap-skel-row:nth-child(4)::after { animation-delay: .3s; }
@keyframes cap-skel-sweep { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
  .cap-skel-row::after { animation: none; opacity: .5; transform: none; }
}

/* per-clip chart: a lighter details block nested inside a clip card */
.clip-chart.card {
  margin: 16px 0 0; box-shadow: none; border-radius: 11px;
  background: var(--surface-2);
}
.clip-chart > summary { padding: 14px 16px; }
.clip-chart .fold-title { font-size: 14px; }
.clip-chart .fold-body { padding: 0 16px 16px; }
.cc-mount .chart-wrap { margin-top: 8px; }
.cc-mount .chart-tabs { margin-bottom: 4px; }

/* per-clip comments: same nested-fold look as .clip-chart, above */
.clip-comments.card {
  margin: 16px 0 0; box-shadow: none; border-radius: 11px;
  background: var(--surface-2);
}
.clip-comments > summary { padding: 14px 16px; }
.clip-comments .fold-title { font-size: 14px; }
.clip-comments .fold-body { padding: 0 16px 16px; }
.comment-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 12px; }
.comment-empty { font-size: 12.5px; color: var(--muted); }
.comment-row {
  background: var(--card); border: 1px solid var(--line); border-radius: 9px;
  padding: 8px 12px;
}
.comment-meta {
  display: flex; align-items: center; gap: 8px; margin-bottom: 3px;
}
.comment-email {
  font-size: 12px; font-weight: 700; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.comment-time { font-size: 11px; color: var(--muted); white-space: nowrap; }
.comment-delete {
  margin-left: auto; font: inherit; font-size: 12px; line-height: 1;
  color: var(--muted); background: none; border: none; cursor: pointer;
  padding: 2px 4px; border-radius: 5px; flex: none;
}
.comment-delete:hover:not(:disabled) { color: var(--red); background: var(--tint-danger); }
.comment-delete:disabled { opacity: .5; cursor: default; }
.comment-text { font-size: 13.5px; white-space: pre-wrap; word-break: break-word; }
/* "Approved by Admin" -- one green chip for the clip's WHOLE comments
   element, in its bottom-left corner, under the thread and its compose box,
   rather than on any single comment: what an admin signs off is the
   explanation, and the explanation is the thread (see captures.js's
   approvalChip). Approval is a single platform-wide fact, so everyone sees
   the same chip; only an admin gets it as a <button>, and only an admin
   ever sees the pale "Approve" state below -- for everyone else an
   unapproved thread renders no chip at all. Solid green so it reads as an
   approval at a glance rather than as one more clickable control.
   Left-aligned against the same edge as the comment rows and the textarea
   above it (.fold-body's padding), which is what makes it read as the
   thread's own footer rather than as part of the compose box. */
.comment-approval {
  display: flex; align-items: center; justify-content: flex-start;
  margin-top: 12px;
}
.approved-chip {
  display: inline-flex; align-items: center; gap: 4px; font: inherit;
  font-size: 12px; font-weight: 800; line-height: 1; letter-spacing: .01em;
  color: #fff; background: var(--lime); border: 1px solid #0f9d59;
  border-radius: 999px; padding: 4px 10px;
}
button.approved-chip { cursor: pointer; }
button.approved-chip:hover:not(:disabled) { background: color-mix(in srgb, var(--lime) 75%, black); }
button.approved-chip:disabled { opacity: .6; cursor: default; }
/* Admin-only, and deliberately quiet: an unapproved comment is the normal
   state, not a warning, so the chip waits in outline form until clicked. */
.approved-chip.pending {
  color: var(--tint-success-ink); background: var(--tint-success); border-color: var(--tint-success-border); font-weight: 700;
}
button.approved-chip.pending:hover:not(:disabled) { background: var(--tint-success-border); }
.comment-form { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.comment-form textarea {
  width: 100%; resize: vertical; font: inherit; font-size: 13px;
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  padding: 8px 12px;
}
.comment-form textarea:focus { outline: none; border-color: var(--accent); }
.comment-form button {
  font: inherit; font-size: 12.5px; font-weight: 700; color: #fff;
  background: var(--accent); border: none; border-radius: 8px;
  padding: 8px 16px; cursor: pointer; white-space: nowrap;
}
.comment-form button:hover:not(:disabled) { background: var(--accent-d); }
.comment-form button:disabled { opacity: .6; cursor: default; }

main { max-width: 1080px; margin: 0 auto; padding: 40px 24px 64px; }

/* ---- hero ---- */
.hero { margin-bottom: 34px; }
.hero h1 {
  font-size: 34px; font-weight: 800; letter-spacing: -.03em;
  line-height: 1.18; max-width: 760px; color: var(--ink);
}
.hero-lead {
  margin-top: 12px; font-size: 15.5px; color: var(--ink-2); max-width: 680px;
}

.test-grid {
  display: grid; grid-template-columns: repeat(5, 1fr);
  gap: 12px;
}
.test-chip {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 14px; transition: .15s var(--ease);
  cursor: pointer;
}
.test-chip:hover { box-shadow: var(--shadow); transform: translateY(-1px); }
@media (prefers-reduced-motion: reduce) {
  .test-chip:hover { transform: none; }
}
.test-chip:hover { border-color: var(--line-strong); background: var(--surface-2); }
.test-chip:focus-visible {
  outline: 2px solid rgba(47,109,246,.45); outline-offset: 2px;
}
.test-chip.active {
  border-color: var(--accent); background: var(--tint-accent);
  box-shadow: 0 0 0 3px rgba(47,109,246,.12);
}
.test-chip .tc-thumb {
  width: 100%; height: 120px; border-radius: 9px; overflow: hidden;
  background: #0e1726; margin-bottom: 10px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line);
}
.test-chip .tc-thumb img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.test-chip .tc-head { display: flex; align-items: center; gap: 8px; }
.test-chip .tc-icon { font-size: 18px; line-height: 1; }
.test-chip .tc-name { font-weight: 700; font-size: 14px; }
.test-chip .tc-desc { font-size: 12px; color: var(--muted); margin-top: 6px; }
.test-chip .tc-req {
  margin-top: 10px; padding: 8px 10px;
  background: var(--tint-warning); border: 1px solid var(--tint-warning-border);
  border-left: 3px solid var(--amber);
  border-radius: 7px;
  font-size: 11.5px; font-weight: 700; line-height: 1.35;
  color: var(--tint-warning-ink); text-transform: uppercase; letter-spacing: .02em;
  display: flex; align-items: flex-start; gap: 7px;
}
.test-chip .tc-req-icon {
  flex-shrink: 0;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--amber); color: #fff;
  font-size: 11px; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
  text-transform: none;
}

/* ---- cards ---- */
.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 28px; margin-bottom: 24px;
  transition: box-shadow .2s var(--ease), transform .2s var(--ease), border-color .2s var(--ease);
}
.card-title {
  font-size: 13px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; color: var(--muted); margin-bottom: 16px;
}
.block-title { font-size: 17px; font-weight: 700; letter-spacing: -.01em; }
.block-sub { font-size: 13.5px; color: var(--muted); margin: 4px 0 16px; }

/* ---- dropzone ---- */
.dropzone {
  display: block; border: 2px dashed var(--line-strong); border-radius: var(--radius-sm);
  padding: 38px 20px; text-align: center; cursor: pointer;
  transition: .15s; background: var(--surface-2);
}
.dropzone:hover, .dropzone.drag { border-color: var(--accent); background: var(--tint-accent); }
.dropzone-icon {
  width: 46px; height: 46px; margin: 0 auto 12px; border-radius: 50%;
  background: var(--accent); color: #fff; font-size: 21px;
  display: flex; align-items: center; justify-content: center;
}
.dropzone-text { font-size: 15px; }
.dropzone-hint { font-size: 12.5px; color: var(--muted); margin-top: 7px; }
.dropzone-hint code, .hero-lead code {
  background: var(--surface-3); padding: 1px 5px; border-radius: 4px; font-size: 11.5px;
}

/* ---- detected file ---- */
.file-detected { animation: fade .2s ease; }
.fd-row { display: flex; align-items: center; gap: 14px; }
.fd-icon {
  width: 44px; height: 44px; border-radius: 11px; flex-shrink: 0;
  background: var(--tint-accent); color: var(--accent);
  display: flex; align-items: center; justify-content: center; font-size: 21px;
}
.fd-info { flex: 1; min-width: 0; }
.fd-name {
  font-weight: 600; font-size: 14.5px; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.fd-test { font-size: 13px; color: var(--accent); font-weight: 600; }
.fd-desc { font-size: 13.5px; color: var(--ink-2); margin: 12px 0 4px; }
.fd-req {
  margin-top: 12px; padding: 12px 14px;
  background: var(--tint-warning); border: 1px solid var(--tint-warning-border);
  border-left: 4px solid var(--amber);
  border-radius: 9px;
  font-size: 13.5px; line-height: 1.45;
  color: var(--tint-warning-ink);
  display: flex; align-items: flex-start; gap: 10px;
}
.fd-req strong { color: var(--amber); text-transform: uppercase; letter-spacing: .04em; font-size: 12px; }
.fd-req-icon {
  flex-shrink: 0;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--amber); color: #fff;
  font-size: 14px; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
}
.link-btn {
  background: none; border: none; color: var(--muted);
  cursor: pointer; font-size: 13px; text-decoration: underline;
}
.link-btn:hover { color: var(--ink); }

/* ---- buttons ---- */
.primary-btn {
  margin-top: 18px; width: 100%; padding: 14px;
  background: var(--accent); color: #fff; border: none; border-radius: 999px;
  font-size: 15px; font-weight: 700; font-family: inherit; cursor: pointer;
  transition: background-color .15s var(--ease), transform .15s var(--ease), box-shadow .15s var(--ease);
}
.primary-btn:hover:not(:disabled) { background: var(--accent-d); transform: translateY(-1px); box-shadow: 0 6px 16px rgba(47,109,246,.28); }
.primary-btn:active:not(:disabled) { transform: translateY(0); box-shadow: none; }
.primary-btn:disabled { background: var(--line-strong); cursor: not-allowed; }
.ghost-btn {
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 999px;
  padding: 9px 16px; font-size: 13.5px; font-weight: 600; cursor: pointer;
  font-family: inherit; color: var(--ink-2); transition: .15s var(--ease);
}
.ghost-btn:hover { border-color: var(--accent); color: var(--accent); background: var(--tint-accent); }
@media (prefers-reduced-motion: reduce) {
  .primary-btn:hover:not(:disabled), .primary-btn:active:not(:disabled) { transform: none; }
}

.select-test-hint {
  margin-top: 10px; text-align: center;
  font-size: 13px; font-weight: 600; color: var(--amber);
}

/* ---- progress ---- */
.progress-card { text-align: center; padding: 40px 24px; }
.spinner {
  width: 42px; height: 42px; margin: 0 auto 18px;
  border: 4px solid #e3e8f0; border-top-color: var(--accent);
  border-radius: 50%; animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.progress-text { font-size: 16px; font-weight: 700; }
.progress-sub { font-size: 13.5px; color: var(--muted); margin-top: 6px; }
.progress-timer {
  font-family: 'JetBrains Mono', monospace; font-size: 13px;
  color: var(--accent); margin-top: 12px; font-weight: 500;
}

/* ---- error ---- */
.error-card { display: flex; gap: 14px; align-items: flex-start; border-color: var(--tint-danger-border); }
.error-icon {
  width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0;
  background: var(--red); color: #fff; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
}
.error-title { font-weight: 700; }
.error-msg { font-size: 13.5px; color: var(--ink-2); margin-top: 3px; }

/* ---- results ---- */
.results { animation: fade .25s ease; }
.results-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  margin-bottom: 18px;
}
.results-kicker {
  font-size: 11.5px; font-weight: 700; letter-spacing: .12em;
  color: var(--accent);
}
.results-title { font-size: 24px; font-weight: 800; letter-spacing: -.02em; }

.metric-row {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 14px; margin-bottom: 20px;
}
.metric {
  background: var(--navy); color: #fff; border-radius: var(--radius);
  padding: 20px; box-shadow: var(--shadow);
}
.metric.secondary { background: var(--card); color: var(--ink); border: 1px solid var(--line); }
.metric .m-label {
  font-size: 12px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .05em; opacity: .75;
}
.metric.secondary .m-label { color: var(--muted); opacity: 1; }
.metric .m-value { font-size: 30px; font-weight: 800; margin-top: 7px; letter-spacing: -.02em; }
.metric .m-unit { font-size: 15px; font-weight: 600; opacity: .7; margin-left: 3px; }
/* degree sign: raised to the top-right of the number */
.metric .m-unit.m-deg { vertical-align: top; margin-left: 1px; line-height: 1; }

/* ---- videos ---- */
.video-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
/* Original + annotated + normalized (see clipCard) -- three across rather
   than a lone third video on its own row half the width of the card. */
.video-trio { grid-template-columns: repeat(3, 1fr); }
.video-pair figure { margin: 0; }
.video-pair figcaption {
  font-size: 13px; font-weight: 600; color: var(--ink-2); margin-bottom: 7px;
  display: flex; align-items: center; gap: 8px;
}
.video-pair video {
  width: 100%; border-radius: 11px; background: #0e1726;
  display: block; max-height: 460px;
}
/* Click-to-load: the <video> shows only its poster (a low-res still) until
   the play button on top of it is pressed -- see captures.js's videoFrame/
   wireVideoPlayButtons. A rendition with no thumbnail (annotated/normalized
   never get one, only Original does) has nothing to size itself against
   before it's pressed, hence the min-height floor -- without it the frame
   would collapse to a sliver and the play button would have nowhere to sit.
   overflow:hidden clips the video's own border-radius corners cleanly since
   the frame, not the video, is now the positioning root. */
.video-frame {
  position: relative; min-height: 160px; max-height: 460px;
  border-radius: 11px; overflow: hidden; background: #0e1726;
}
.video-play-btn {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 64px; height: 64px; border-radius: 50%; border: none;
  background: rgba(10, 16, 28, .55); color: #fff;
  font-size: 22px; line-height: 64px; text-align: center; padding: 0 0 0 4px;
  cursor: pointer; transition: background .15s ease, transform .15s ease;
}
.video-play-btn:hover { background: rgba(10, 16, 28, .75); transform: translate(-50%, -50%) scale(1.06); }
.tag {
  font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; background: var(--tint-accent); color: var(--accent);
  padding: 2px 7px; border-radius: 5px;
}
/* ---- invalid clips ----
   Ruled unusable by an admin: ignored by player bests, the analytics
   dashboard and the platform sync. Set aside visibly rather than hidden --
   a clip nobody can see is a clip somebody re-uploads.

   The dimming is applied to the clip's CONTENT, never to the card itself.
   opacity and filter on an ancestor cannot be undone by a descendant, so a
   card-level dim would drag the "Invalid" badge and the undo button down
   with it -- exactly the two things that have to stay readable. For the
   same reason the dim is never put on .clip-card-body itself: the videos
   are the evidence of what was actually filmed, and they need to read
   normally so whoever is deciding whether to overturn the call can
   actually see them. That goes for all three -- Original, Normalized and
   Annotated -- so the whole .video-pair is carved out of the dim and only
   everything below the videos is greyed. */
.clip-invalid > summary .clip-id { text-decoration: line-through; text-decoration-thickness: 1px; }
.clip-invalid > summary .clip-id,
.clip-invalid > summary .clip-sort-kpi,
.clip-invalid > summary .status-badge { opacity: .45; }
.clip-invalid .clip-card-body > *:not(.video-pair):not(.clip-comments) { opacity: .45; filter: grayscale(.85); }
.clip-invalid:hover > summary .clip-id,
.clip-invalid:hover .clip-card-body > *:not(.video-pair):not(.clip-comments) { opacity: .7; }
/* .clip-comments is carved out of the blanket rule above for the same
   reason the invalid-badge/undo-button are, per the note up top -- an
   opacity/filter dim on it would drag the "Approved by Admin" chip down
   with it, and its green is supposed to read identically on every clip,
   valid or not. Dimming each of the card's other pieces individually
   instead leaves .comment-approval a sibling, not a descendant, of
   anything dimmed. */
.clip-invalid .clip-comments .fold-title,
.clip-invalid .clip-comments .fold-chevron,
.clip-invalid .clip-comments .comment-meta,
.clip-invalid .clip-comments .comment-text,
.clip-invalid .clip-comments .comment-empty,
.clip-invalid .clip-comments .comment-form { opacity: .45; filter: grayscale(.85); }
.clip-invalid:hover .clip-comments .fold-title,
.clip-invalid:hover .clip-comments .fold-chevron,
.clip-invalid:hover .clip-comments .comment-meta,
.clip-invalid:hover .clip-comments .comment-text,
.clip-invalid:hover .clip-comments .comment-empty,
.clip-invalid:hover .clip-comments .comment-form { opacity: .7; }

/* The word, not just the grey. Shown to EVERY role: operators have
   .status-badge hidden (see the operator-view block below) and a desaturated
   card otherwise reads as "still loading", so this is deliberately not in
   that hide list. It sits outside the dimmed content above, at full
   contrast. */
.invalid-badge {
  align-self: flex-start;
  font-size: 11px; font-weight: 800; text-transform: uppercase;
  letter-spacing: .05em; padding: 4px 10px; border-radius: 999px;
  background: var(--tint-danger); color: var(--red); white-space: nowrap;
  border: 1px solid var(--tint-danger-border);
}
/* One button, three faces: grey while nobody has ruled on the clip, green
   once it is marked valid, red once it is marked invalid. The colour is
   driven by the class the button carries, never by the card's
   .clip-invalid -- the button has to keep reading at full contrast on a
   dimmed card (see the note at the top of this block), and it is also the
   only thing on screen that says which way the last click went. */
.invalid-btn {
  white-space: nowrap;
  border-color: var(--line); color: var(--muted); background: var(--surface-3);
}
.invalid-btn:hover:not(:disabled) { background: var(--surface-3); }
.invalid-btn.verdict-valid {
  border-color: var(--tint-success-border); color: var(--tint-success-ink); background: var(--tint-success);
}
.invalid-btn.verdict-valid:hover:not(:disabled) { background: var(--tint-success-border); }
.invalid-btn.verdict-invalid {
  border-color: var(--tint-danger-border); color: var(--red); background: var(--tint-danger);
}
.invalid-btn.verdict-invalid:hover:not(:disabled) { background: var(--tint-danger-border); }

/* The "delete this duplicate from the portal" affordance next to an INVALID
   clip's verdict button (deleteButton in captures.js) -- shown only once a
   valid clip of the same player+test exists, so the note always names the
   clip that makes this one safe to drop. Not touched by the .clip-invalid
   dimming rules above, same as .invalid-btn, for the same reason: it has to
   read at full contrast on a greyed-out card. */
.delete-clip-group { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.delete-clip-note { font-size: 11px; color: var(--muted); }
.delete-clip-btn { border-color: var(--tint-danger-border); color: var(--red); }
.delete-clip-btn:hover:not(:disabled) { background: var(--tint-danger); }

/* One chip per normalisation step, each carrying what it achieved on this
   clip (see normChips). Under the video rather than in its caption: three
   chips are wider than a third-of-a-card caption, and reading them against
   the picture they describe beats reading them above it. */
.vchips {
  display: flex; flex-wrap: wrap; gap: 5px; margin-top: 7px;
}
.vchips:empty { display: none; }
.vchip {
  font-size: 10.5px; font-weight: 600; letter-spacing: .01em;
  background: var(--tint-accent); color: var(--accent);
  padding: 2px 7px; border-radius: 5px; white-space: nowrap;
  cursor: help;
}
.vchip b { font-weight: 800; font-variant-numeric: tabular-nums; }
/* Not an error -- a margin the camera could not give, or a pose sample that
   would not fit. Worth noticing, not worth alarming about. */
.vchip.warn { background: var(--tint-warning); color: var(--tint-warning-ink); }

/* ---- chart ---- */
.chart-card { padding: 24px; }
.chart-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 18px; flex-wrap: wrap; margin-bottom: 6px;
}
.chart-tabs {
  display: inline-flex; background: var(--surface-3); border-radius: 9px; padding: 3px;
  gap: 2px;
}
.chart-tabs:empty { display: none; }
.chart-tab {
  background: none; border: none; padding: 7px 14px; cursor: pointer;
  font: 600 12.5px 'Inter', sans-serif; color: var(--ink-2);
  border-radius: 7px; transition: .15s;
}
.chart-tab:hover { color: var(--ink); }
.chart-tab.active {
  background: var(--card); color: var(--accent);
  box-shadow: 0 1px 3px rgba(16,30,54,.07);
}

.chart-controls {
  display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.chart-toggle {
  display: inline-flex; background: var(--surface-3); border-radius: 9px; padding: 3px;
  gap: 2px;
}
.chart-toggle-btn {
  background: none; border: none; padding: 7px 12px; cursor: pointer;
  font: 600 12.5px 'Inter', sans-serif; color: var(--ink-2);
  border-radius: 7px; transition: .15s;
}
.chart-toggle-btn:hover { color: var(--ink); }
.chart-toggle-btn.active {
  background: var(--card); color: var(--accent);
  box-shadow: 0 1px 3px rgba(16,30,54,.07);
}

.chart-wrap {
  position: relative;
  width: 100%; margin-top: 10px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 12px;
  padding: 8px 4px;
}
.chart-svg {
  width: 100%; height: auto; display: block;
  font-family: 'Inter', sans-serif;
}
.ch-plot { fill: #ffffff; stroke: var(--line); stroke-width: 1; }
.ch-grid { stroke: var(--surface-3); stroke-width: 1; }
.ch-line { fill: none; stroke: var(--accent); stroke-width: 2.25; stroke-linejoin: round; stroke-linecap: round; }
.ch-area { fill: var(--accent); opacity: 0.10; }
.ch-axis { font-size: 10.5px; fill: var(--muted); font-weight: 500; }
.ch-axis-label { font-size: 11px; fill: var(--ink-2); font-weight: 600; }
.ch-event { font-size: 10.5px; font-weight: 700; }
.ch-thresh { font-size: 10px; font-weight: 600; }
.ch-hover-cursor circle { filter: drop-shadow(0 1px 2px rgba(47,109,246,.4)); }
.ch-video-cursor line { stroke: var(--ink); }

.chart-tooltip {
  position: absolute; pointer-events: none;
  background: rgba(14,23,38,.94); color: #fff;
  border-radius: 8px; padding: 9px 11px;
  font: 500 11.5px 'Inter', sans-serif;
  min-width: 120px;
  box-shadow: 0 4px 14px rgba(16,30,54,.18);
  z-index: 3;
}
.ch-tip-row {
  display: flex; justify-content: space-between; gap: 10px;
  padding: 1px 0;
}
.ch-tip-k { color: #9db0d0; }
.ch-tip-v {
  font-family: 'JetBrains Mono', monospace; font-size: 12px;
  font-weight: 600;
}

.chart-legend {
  margin-top: 12px;
  display: flex; flex-wrap: wrap; gap: 8px 16px;
  font-size: 12px; color: var(--ink-2);
}
.ch-lg { display: inline-flex; align-items: center; gap: 6px; }
.ch-lg-sw {
  width: 14px; height: 3px; background: var(--accent); border-radius: 2px;
  display: inline-block;
}
.ch-lg-sw-dash {
  height: 0; border-top: 2px dashed var(--accent); background: none; border-radius: 0;
}

/* ---- steps ---- */
.steps { list-style: none; counter-reset: step; }
.steps li {
  position: relative; padding: 0 0 16px 44px; margin-bottom: 4px;
}
.steps li:not(:last-child)::before {
  content: ''; position: absolute; left: 15px; top: 30px; bottom: 2px;
  width: 2px; background: var(--line);
}
.steps li .step-num {
  position: absolute; left: 0; top: 0;
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--tint-accent); color: var(--accent);
  font-weight: 700; font-size: 14px;
  display: flex; align-items: center; justify-content: center;
}
.steps li .step-title { font-weight: 700; font-size: 14.5px; }
.steps li .step-detail { font-size: 13.5px; color: var(--ink-2); margin-top: 2px; }

/* ---- formula ---- */
.formula {
  margin-top: 12px; background: var(--surface-2); border: 1px solid var(--line);
  border-left: 3px solid var(--accent); border-radius: 10px; padding: 16px 18px;
}
.formula-label {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; color: var(--muted);
}
.formula-expr {
  font-family: 'JetBrains Mono', monospace; font-size: 16px;
  font-weight: 500; margin-top: 6px; color: var(--navy);
}
.formula-sub {
  font-family: 'JetBrains Mono', monospace; font-size: 13px;
  color: var(--ink-2); margin-top: 8px; white-space: pre-line;
}
.formula-why { font-size: 13px; color: var(--muted); margin-top: 10px; }

/* ---- tables ---- */
.param-table { width: 100%; border-collapse: collapse; }
.param-table tr { border-bottom: 1px solid var(--line); }
.param-table tr:last-child { border-bottom: none; }
.param-table td { padding: 10px 4px; font-size: 13.5px; vertical-align: top; }
.param-table td:first-child { color: var(--ink-2); width: 55%; }
.param-table td:last-child {
  text-align: right; font-weight: 600;
  font-family: 'JetBrains Mono', monospace; font-size: 13px;
}

/* ---- cost ---- */
.cost-card { border-color: var(--tint-success-border); background: linear-gradient(180deg, color-mix(in srgb, var(--tint-success) 65%, var(--card)), var(--card) 70%); }
.cost-headline {
  font-size: 34px; font-weight: 800; color: var(--lime);
  letter-spacing: -.02em; margin-bottom: 12px;
}
.cost-table td:last-child { color: var(--ink); }
.cost-note { font-size: 12.5px; color: var(--muted); margin-top: 12px; }

/* ---- collapsible cards (details / summary) ---- */
details.fold { padding: 0; }
details.fold > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; gap: 14px; padding: 20px 24px;
}
details.fold > summary::-webkit-details-marker { display: none; }
details.fold > summary:hover .fold-title { color: var(--accent); }
.fold-title { font-size: 17px; font-weight: 700; letter-spacing: -.01em; flex: 1; }
.fold-peek { font-size: 18px; font-weight: 800; color: var(--lime); }
.fold-chevron { color: var(--muted); font-size: 13px; transition: transform .2s; }
details.fold[open] > summary .fold-chevron { transform: rotate(180deg); }
.fold-body { padding: 0 24px 24px; }

.footer {
  text-align: center; font-size: 12.5px; color: var(--muted);
  padding: 28px 16px;
}

@keyframes fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .results, .file-detected, .exam-slide-img { animation: none; }
}

/* ===== profile pictures (rendered inside a "By player" group, see
   .group-photos in the clips-grouping section below) ===== */
.profile-set { margin-bottom: 14px; }
.profile-set:last-child { margin-bottom: 0; }
.profile-set-label {
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; font-weight: 600; color: var(--muted); margin-bottom: 6px;
}
.profile-latest {
  background: var(--accent); color: #fff; border-radius: 999px;
  padding: 1px 8px; font-size: 10px; font-weight: 700; letter-spacing: .03em;
}
.profile-older { margin-top: 4px; }
.profile-older > summary {
  cursor: pointer; list-style: none; user-select: none;
  font-size: 12px; font-weight: 700; color: var(--accent);
  padding: 6px 0; display: inline-flex; align-items: center; gap: 6px;
}
.profile-older > summary::-webkit-details-marker { display: none; }
.profile-older > summary::before { content: '▸'; font-size: 11px; transition: transform .15s; }
.profile-older[open] > summary::before { transform: rotate(90deg); }
.profile-older[open] > summary { margin-bottom: 8px; }
.profile-shots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.profile-shot { margin: 0; }
.profile-shot img {
  width: 100%; aspect-ratio: 3 / 4; object-fit: cover; border-radius: 8px;
  background: var(--surface-3); cursor: zoom-in; display: block; transition: .15s;
}
.profile-shot img:hover { box-shadow: 0 0 0 3px rgba(47,109,246,.18); }
.profile-shot figcaption {
  margin-top: 5px; text-align: center; font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em; color: var(--muted);
}
.img-lightbox {
  position: fixed; inset: 0; z-index: 100; background: rgba(8,12,20,.86);
  display: flex; align-items: center; justify-content: center; padding: 24px; cursor: zoom-out;
}
.img-lightbox img { max-width: 100%; max-height: 100%; border-radius: 10px; }

/* ===== single-leg jump (paired right + left) ===== */
.slj-pair {
  border: 1px solid var(--line); border-radius: 14px; padding: 16px;
  background: linear-gradient(180deg, var(--surface-2), var(--card));
}
.slj-pair-head {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 10px; margin-bottom: 14px;
}
.slj-pair-id { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.slj-pair-title { font-size: 18px; font-weight: 800; color: var(--ink); }
.slj-asym {
  display: flex; flex-direction: column; align-items: flex-end;
  background: var(--tint-danger); border: 1px solid var(--tint-danger-border); border-radius: 10px;
  padding: 6px 12px;
}
.slj-asym-val { font-size: 20px; font-weight: 800; color: var(--red); line-height: 1; }
.slj-asym-lbl { font-size: 11px; font-weight: 600; color: var(--muted); margin-top: 2px; }
.slj-asym-wait { background: var(--surface-3); border-color: var(--line); }
.slj-pair-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 760px) { .slj-pair-grid { grid-template-columns: 1fr; } }

@media (max-width: 1080px) {
  .test-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 760px) {
  .test-grid { grid-template-columns: 1fr 1fr; }
  .video-pair { grid-template-columns: 1fr; }
  .hero h1 { font-size: 24px; }
  main { padding: 28px 16px 48px; }
  .test-chip .tc-thumb { height: 100px; }
}

/* ===== cone-marking overlay ===== */
.results-head-actions { display: flex; gap: 10px; align-items: center; }

.marker-overlay {
  position: fixed; inset: 0; z-index: 1000;
  background: #0b1220;
  display: flex; flex-direction: column;
}
.marker-panel { flex: 1; display: flex; flex-direction: column; min-height: 0; }

.marker-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  padding: 14px 20px; background: #0e1726; border-bottom: 1px solid #1e2c44;
}
.marker-kicker {
  font-size: 11.5px; font-weight: 700; letter-spacing: .12em; color: #7fa4ff;
}
.marker-context {
  font-size: 12.5px; font-weight: 600; color: #aebbd2; margin-bottom: 3px;
}
.marker-instr { font-size: 14px; font-weight: 600; color: #eef2f9; margin-top: 3px; }
.marker-head-actions { display: flex; align-items: center; gap: 12px; }
.marker-jump {
  font: inherit; font-size: 13px; width: 230px; color: #eef2f9;
  background: #16223a; border: 1px solid #33415c; border-radius: 8px; padding: 7px 10px;
}
.marker-jump::placeholder { color: #8494ad; }
.marker-jump[hidden] { display: none; }
.marker-progress {
  font-family: 'JetBrains Mono', monospace; font-size: 12.5px;
  color: #aebbd2; font-weight: 500;
}
.marker-head .primary-btn { width: auto; margin-top: 0; padding: 11px 18px; }
.marker-head .ghost-btn {
  background: transparent; color: #cdd7e8; border-color: #33415c;
}
.marker-head .ghost-btn:hover { border-color: #7fa4ff; color: #fff; }

.marker-stage-wrap {
  position: relative; flex: 1; min-height: 0; overflow: hidden;
  background:
    repeating-conic-gradient(#151f31 0% 25%, #101827 0% 50%) 50% / 26px 26px;
  touch-action: none; cursor: crosshair;
}
.marker-stage {
  position: absolute; top: 0; left: 0;
  transform-origin: 0 0; will-change: transform;
  box-shadow: 0 10px 40px rgba(0,0,0,.5);
}
.marker-loading {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
}
.marker-loading[hidden] { display: none; }
.marker-loading .spinner { margin: 0; border-color: #33415c; border-top-color: #7fa4ff; }
.marker-img {
  display: block; width: 100%; height: 100%;
  user-select: none; -webkit-user-drag: none; pointer-events: none;
}
.marker-svg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: visible; }
.mk-handle { cursor: grab; }
.mk-handle.mk-active { filter: drop-shadow(0 0 3px rgba(255,255,255,.9)); }
.mk-gatesurface { pointer-events: none; }

.marker-tools {
  position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%);
  display: flex; align-items: center; gap: 6px;
  background: rgba(14,23,38,.92); border: 1px solid #29374f;
  border-radius: 12px; padding: 6px; backdrop-filter: blur(4px);
}
.marker-tools button {
  background: transparent; border: none; color: #dbe3f2;
  font-family: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
  min-width: 34px; height: 32px; padding: 0 10px; border-radius: 8px;
  transition: .15s;
}
.marker-tools button:hover { background: #24324a; color: #fff; }
.marker-tools-sep { width: 1px; height: 20px; background: #33415c; margin: 0 3px; }
.marker-hint {
  position: absolute; left: 16px; bottom: 20px;
  font-size: 12px; color: #8494ad; pointer-events: none;
  background: rgba(14,23,38,.7); padding: 5px 10px; border-radius: 8px;
}
@media (max-width: 760px) {
  .marker-hint { display: none; }
  .marker-head { padding: 12px 14px; }
  .marker-head .primary-btn { padding: 10px 12px; font-size: 13.5px; }
}

/* ===== current-vs-new algorithm comparison ===== */
.an-filter-row {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px 18px;
}
.an-filter-row + .an-filter-row { margin-top: 10px; }
.an-filter-group { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.an-filter-label {
  font-size: 11px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted); margin-right: 2px;
}
.an-filter-hint { font-size: 12.5px; color: var(--muted); }
.an-chip {
  border: 1px solid var(--line); background: var(--surface-2); border-radius: 999px;
  padding: 4px 12px; font: 600 12.5px 'Inter', sans-serif; color: var(--ink-2);
  cursor: pointer; transition: .15s; display: inline-flex; align-items: center;
  margin: 2px 3px 2px 0;
}
.an-chip:hover { border-color: var(--line-strong); color: var(--ink); }
.an-chip.active { border-color: var(--accent); background: var(--tint-accent); color: var(--accent); }
.an-chip-dot { width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; }
.an-input {
  border: 1px solid var(--line); border-radius: 8px; padding: 5px 10px;
  font: 500 13px 'Inter', sans-serif; color: var(--ink); background: var(--card);
}
.an-note { font-size: 12.5px; color: var(--muted); margin-top: 12px; }
.an-note-line { display: flex; align-items: center; gap: 8px; }
.an-load-warn { color: var(--tint-warning-ink); margin-top: 4px; }

/* loaders: the pre-data card + the "N phones still loading" filter chip */
.an-loading-card { text-align: center; padding: 40px 24px; color: var(--muted); }
.an-loading-text { font-size: 13.5px; }
.an-chip-loader {
  cursor: default; color: var(--muted); background: var(--card); gap: 7px;
}
.an-chip-loader:hover { border-color: var(--line); color: var(--muted); }
.an-sub { font-size: 13px; color: var(--muted); margin: -6px 0 14px; }

/* KPI stat tiles */
.an-kpis {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px;
  margin: 18px 0;
}
@media (max-width: 900px) { .an-kpis { grid-template-columns: repeat(2, 1fr); } }
.an-kpis.kpis-compact { grid-template-columns: repeat(2, 1fr); max-width: 460px; }
.an-tile {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 14px 18px;
}
.an-tile-label { font-size: 12px; font-weight: 600; color: var(--muted); }
.an-tile-value { font-size: 28px; font-weight: 800; letter-spacing: -.02em; line-height: 1.25; }
.an-tile-sub { font-size: 12px; color: var(--muted); line-height: 1.4; }
.an-unit { font-size: 14px; font-weight: 600; color: var(--muted); }
.an-tile-good .an-tile-value { color: var(--tint-success-ink); }
.an-tile-bad .an-tile-value { color: var(--red); }
.an-tile-warn .an-tile-value { color: var(--tint-warning-ink); }

/* chart chrome */
.an-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 0 0 12px; }
.an-legend-item {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12.5px; color: var(--ink-2);
}
.an-swatch { width: 10px; height: 10px; border-radius: 3px; }
.an-chart { display: block; max-width: 100%; }
.an-chart .an-grid { stroke: var(--surface-3); stroke-width: 1; }
.an-chart .an-axis { stroke: var(--line-strong); stroke-width: 1; }
.an-chart .an-tick { fill: var(--muted); font-size: 11px; font-family: 'Inter', sans-serif; }
.an-chart [data-bhit]:focus { outline: none; }
.an-chart [data-bhit]:focus-visible {
  outline: 2px solid var(--accent); outline-offset: -2px;
}
.an-chart .an-limit { stroke: var(--red); stroke-width: 1; }
.an-chart .an-limit-txt { fill: var(--red); font-size: 10.5px; font-family: 'Inter', sans-serif; }

/* outcomes by test — HTML stacked horizontal bars */
.an-hrow { display: flex; align-items: center; gap: 12px; margin-bottom: 9px; }
.an-hlabel {
  flex: 0 0 150px; font-size: 13px; font-weight: 600; color: var(--ink-2);
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
}
.an-hcount { color: var(--muted); font-size: 11.5px; font-weight: 500; }
.an-htrack {
  display: flex; gap: 2px; height: 22px; min-width: 24px;
  border-radius: 0 6px 6px 0;
}
.an-htrack:focus { outline: none; }
.an-htrack:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.an-hseg {
  min-width: 3px; display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.an-hseg:first-child { border-radius: 4px 0 0 4px; }
.an-hseg:last-child { border-radius: 0 4px 4px 0; }
.an-hseg:only-child { border-radius: 4px; }
.an-hseg-val { color: #fff; font-size: 11px; font-weight: 600; }
.an-htrack:hover .an-hseg { filter: brightness(1.08); }

/* failure reasons */
.an-frow { padding: 9px 0; border-bottom: 1px solid var(--surface-3); }
.an-frow:last-child { border-bottom: none; }
.an-fsummary { cursor: pointer; list-style: none; }
.an-fsummary::-webkit-details-marker { display: none; }
.an-fsummary:hover .an-fmsg { color: var(--accent); }
.an-frow[open] > .an-fsummary .group-chevron { transform: rotate(180deg); }
.an-fmeta .group-chevron { margin-left: auto; }
/* the clips behind a failure group: one line each, linked to Captures */
.an-fclips { margin: 6px 0 4px; border-left: 3px solid var(--surface-3); padding-left: 12px; }
.an-fclip {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  padding: 5px 0; border-bottom: 1px dashed var(--surface-3); font-size: 12.5px;
}
.an-fclip:last-child { border-bottom: none; }
.an-fclip-player { font-weight: 600; color: var(--ink-2); }
.an-fclip-dim { color: var(--muted); }
.an-fclip-msg { color: var(--muted); font-size: 11.5px; flex-basis: 100%; order: 9; }
.an-fclip .an-clip-link { margin-left: auto; }
.an-fbar-wrap { height: 5px; background: var(--surface-3); border-radius: 3px; margin-bottom: 7px; }
.an-fbar { height: 5px; border-radius: 3px; min-width: 4px; }
.an-fmeta { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.an-fcount { font-size: 16px; font-weight: 800; min-width: 26px; }
.an-fmsg { font-size: 13px; color: var(--ink); }
.an-ftests { font-size: 11.5px; color: var(--muted); margin-top: 3px; }

/* status chip (color never carries meaning alone — always with its label) */
.an-status-chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-weight: 600; font-size: 12px; color: var(--ink-2); white-space: nowrap;
}
.an-status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.an-suspect { color: var(--tint-warning-ink); font-size: 12px; }

/* sanity section — one collapsible block per test */
.an-h3 { font-size: 14px; font-weight: 700; margin: 18px 0 8px; }
.an-test-block { margin-bottom: 12px; }
.an-test-block > summary { padding: 13px 18px; }
.an-test-block .group-body { padding: 0 18px 14px; }
.an-badge {
  border-radius: 999px; font-size: 11px; font-weight: 700; padding: 2px 10px;
  white-space: nowrap;
}
.an-badge-warn { background: var(--tint-warning); color: var(--tint-warning-ink); }
.an-badge-ok { background: var(--tint-success); color: var(--tint-success-ink); }
.an-badge-danger { background: var(--tint-danger); color: var(--red); }
.an-metric-row { cursor: pointer; }
.an-metric-row:hover td { background: var(--surface-2); }
.an-metric-row:focus { outline: none; }
.an-metric-row:focus-visible td { background: var(--tint-accent); }
.an-mchev {
  color: var(--muted); font-size: 10px; display: inline-block;
  margin-right: 7px; transition: transform .15s;
}
.an-metric-row.open .an-mchev { transform: rotate(90deg); }
.an-strip-cell { width: 196px; }
.an-strip { display: block; }
.an-hist-row td { background: var(--surface-2); padding: 12px 10px 14px; }

/* tables */
.an-tablewrap { overflow-x: auto; }
.an-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.an-table th {
  text-align: left; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .05em; color: var(--muted); padding: 8px 10px;
  border-bottom: 1px solid var(--line); white-space: nowrap;
}
.an-table td { padding: 7px 10px; border-bottom: 1px solid var(--surface-3); vertical-align: top; }
.an-table tr:last-child td { border-bottom: none; }
.an-table th.num, .an-table td.num {
  text-align: right; font-variant-numeric: tabular-nums;
}
.mono-cell { font-family: 'JetBrains Mono', monospace; font-size: 11.5px; white-space: nowrap; }

/* ---- index.html: "Ready for platform" roster ---- */
/* table-layout: fixed makes the columns split the card's width instead of
   growing to fit their widest cell -- combined with the wrap override below,
   that's what keeps this table from ever needing horizontal scroll. */
.roster-table { table-layout: fixed; width: 100%; }
.roster-table td, .roster-table th {
  vertical-align: middle; white-space: normal; overflow-wrap: break-word;
}
.roster-table tr:hover { background: var(--surface-2); }
.roster-table .an-badge, .roster-table .an-status-chip { white-space: normal; }
.roster-centered-col { text-align: center; }

/* On platform cell: everyone gets a red/green status badge; admins get a
   single button instead (not both), same wording as the badge ("On
   platform" / "Not on platform") as the main label, colored by STATE (green
   = on platform, red = not on platform) -- not by the action the click
   would take -- the trailing arrow (.pf-arrow) is a secondary, muted hint
   that it's clickable and which way it goes, not the label itself. */
.pf-btn-on {
  border-color: var(--tint-success-border); color: var(--tint-success-ink); background: var(--tint-success);
}
.pf-btn-on:hover:not(:disabled) { background: var(--tint-success-border); }
.pf-btn-off {
  border-color: var(--tint-danger-border); color: var(--red); background: var(--tint-danger);
}
.pf-btn-off:hover:not(:disabled) { background: var(--tint-danger-border); }
.pf-btn .pf-arrow { font-size: 11px; opacity: .65; margin-left: 2px; }

/* "Physical test manually approved" cell: same badge/button split as the
   On platform cell above, colored by state. Only feeds the Status column
   (readiness.js's effectiveReady) -- never changes the Physical tests
   column itself. */
.pa-btn-on {
  border-color: var(--tint-success-border); color: var(--tint-success-ink); background: var(--tint-success);
}
.pa-btn-on:hover:not(:disabled) { background: var(--tint-success-border); }
.pa-btn-off {
  border-color: var(--tint-warning-border); color: var(--tint-warning-ink); background: var(--tint-warning);
}
.pa-btn-off:hover:not(:disabled) { background: var(--tint-warning-border); }
/* "🐞 algorithm bug" flag (physModalListHtml): neutral until flagged, then
   red -- unlike pa-btn-on/off this isn't approved/not-approved, so it gets
   its own on-state colour rather than reusing the success/warning ones. */
.bug-btn.bug-btn-on {
  border-color: var(--tint-danger-border); color: var(--tint-danger-ink); background: var(--tint-danger);
}
.bug-btn.bug-btn-on:hover:not(:disabled) { background: var(--tint-danger-border); }
td .st-zero { font-size: 12.5px; }

/* tooltip — one shared instance, positioned near the pointer */
.viz-tip {
  position: absolute; z-index: 50; background: #0e1726; color: #fff;
  border-radius: 8px; padding: 8px 11px;
  font: 500 12px 'Inter', sans-serif; pointer-events: none;
  box-shadow: 0 6px 24px rgba(16,30,54,.3); max-width: 300px;
}
.viz-tip-title { font-weight: 700; margin-bottom: 4px; }
.viz-tip-row { display: flex; align-items: center; gap: 7px; line-height: 1.55; }
.viz-tip-key { width: 10px; height: 3px; border-radius: 2px; display: inline-block; flex-shrink: 0; }
.viz-tip-val { font-weight: 700; margin-left: auto; padding-left: 14px; }

/* suspect popup: the clips behind a ⚠ count, each linked to Captures */
.an-sus-btn {
  border: 1px solid var(--tint-warning-border); background: var(--tint-warning); color: var(--tint-warning-ink);
  border-radius: 999px; padding: 2px 10px; font: 700 12px 'Inter', sans-serif;
  cursor: pointer; white-space: nowrap; transition: .15s;
}
.an-sus-btn:hover { border-color: var(--amber); background: var(--tint-warning); }
.an-clip-link {
  color: var(--accent); font-weight: 600; font-size: 12px;
  text-decoration: none; white-space: nowrap;
}
.an-clip-link:hover { text-decoration: underline; }
.an-modal-overlay {
  position: fixed; inset: 0; z-index: 70; background: rgba(14,23,38,.45);
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
.an-modal {
  background: var(--card); border-radius: var(--radius); box-shadow: var(--shadow);
  width: min(560px, 100%); max-height: 76vh; display: flex; flex-direction: column;
}
.an-modal-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px 20px; border-bottom: 1px solid var(--line);
}
.an-modal-title { font-size: 14.5px; font-weight: 700; }
.an-modal-close {
  border: none; background: transparent; color: var(--muted); font-size: 15px;
  cursor: pointer; padding: 4px 8px; border-radius: 8px; transition: .15s;
}
.an-modal-close:hover { background: var(--surface-3); color: var(--ink); }
/* flex: 1 + min-height: 0 is what actually makes overflow-y:auto scroll
   inside a flex column -- without min-height:0 a flex child's default
   min-height:auto (its content's own height) can push the whole .an-modal
   taller than its max-height instead of scrolling, silently truncating
   whatever falls past the viewport edge. */
.an-modal-list { flex: 1; min-height: 0; overflow-y: auto; padding: 6px 20px 14px; }
.an-modal-item { padding: 12px 0; border-bottom: 1px solid var(--surface-3); }
.an-modal-item:last-child { border-bottom: none; }
.an-modal-meta { font-size: 12.5px; font-weight: 600; color: var(--ink-2); }
.an-modal-reason { font-size: 12.5px; color: var(--tint-warning-ink); margin: 3px 0 6px; }

/* "Physical tests" popup (openPhysModal in readiness.js): one block per
   STATUS_TESTS test. Each block reads top-to-bottom, left-to-right the way
   the decision actually gets made: the test's name and its automatic
   result up top with the Approve button right beside them (the verdict),
   then the evidence below -- the annotated video on the left, its KPI
   numbers as a sidebar to its right, since those are what you read off the
   video rather than a separate thing. Noticeably wider/taller than a
   plain popup (this one carries three videos) -- .an-modal's own
   width/max-height are the fallback for every OTHER popup on this page. */
.pa-modal { width: min(1040px, 96vw); max-height: 88vh; }
.pa-modal-item { display: flex; flex-direction: column; gap: 12px; padding: 16px 0; }
.pa-modal-test-head {
  display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap;
}
.pa-modal-test-title { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pa-modal-test-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; flex-shrink: 0; }
.pa-modal-test-title .an-modal-meta { font-size: 14px; }
.pa-modal-media { display: flex; gap: 22px; align-items: flex-start; flex-wrap: wrap; }
.pa-modal-video-col { flex: 3 1 380px; min-width: 280px; }
.pa-modal-media video { width: 100%; max-height: 340px; border-radius: 11px; background: #0e1726; display: block; }
.pa-modal-videoframe { min-height: 170px; max-height: 340px; }
/* KPI sidebar: two columns of chips instead of one long stack, now that
   the popup is wide enough to give it real room. */
.pa-modal-kpis {
  flex: 2 1 260px; min-width: 240px; align-self: stretch;
  display: grid; grid-template-columns: 1fr 1fr; grid-auto-rows: min-content; gap: 8px;
}
.pa-modal-kpis > .cm-chip { min-width: 0; } /* let a long label ellipsize instead of stretching its grid column */
.pa-modal-empty { padding: 6px 0; font-size: 12.5px; color: var(--muted); }
.pa-btn-sm { padding: 3px 12px; font-size: 12px; flex-shrink: 0; }

/* Captures' "Watch all annotated videos" popup (captures.js openWatchAll):
   nearly the whole viewport, the video filling all of it but a KPI sidebar,
   one player at a time, the decision buttons in a footer. */
.wa-overlay { padding: 2vh 2vw; }
.wa-modal { width: 96vw; height: 96vh; max-height: 96vh; }
.wa-media { flex: 1; min-height: 0; display: flex; gap: 18px; }
.wa-video-col { flex: 1; min-width: 0; min-height: 0; display: flex; }
.wa-video-col video {
  width: 100%; height: 100%; object-fit: contain; display: block;
  background: #0e1726; border-radius: 11px;
}
.wa-media .pa-modal-kpis {
  flex: 0 0 260px; min-width: 0; overflow-y: auto; grid-template-columns: 1fr; align-content: start;
}
@media (max-width: 760px) {
  .wa-media { flex-direction: column; }
  .wa-media .pa-modal-kpis { flex: 0 0 auto; max-height: 30vh; grid-template-columns: 1fr 1fr; }
}
.wa-kicker { font-size: 11.5px; font-weight: 700; letter-spacing: .12em; color: var(--accent); margin-bottom: 3px; }
.wa-body { flex: 1; min-height: 0; display: flex; padding: 14px 20px; }
.wa-loading { flex: 1; display: flex; align-items: center; justify-content: center; }
.wa-loading .spinner { margin: 0; }
.wa-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 12px; flex-wrap: wrap;
  padding: 14px 20px; border-top: 1px solid var(--line);
}
.wa-foot[hidden] { display: none; }
.wa-head-actions { display: flex; align-items: center; gap: 10px; }
.wa-jump {
  font: inherit; font-size: 13px; color: var(--ink); width: 230px;
  background: var(--card); border: 1px solid var(--line); border-radius: 8px; padding: 6px 10px;
}
.wa-jump[hidden] { display: none; }
.wa-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.wa-actions .primary-btn { width: auto; margin-top: 0; padding: 9px 20px; }
.wa-err { padding: 0 20px 14px; color: var(--tint-danger-ink); font-size: 12.5px; }
.wa-err[hidden] { display: none; }

/* "Send to platform" review popup (openSendModal in readiness.js): a
   verdict banner, then the player the way the platform will get them --
   photos + identity side by side, then bio / results / tests / matches as
   grids of labelled values. Every value carries a status icon (.pv-icon:
   the glyph AND the color, never color alone) and, right under it, the
   check that explains what is wrong (.pv-note). A fixed footer (outside
   the scrolling body) holds the confirm, so it never scrolls out of reach. */
/* Sized for reading, not glancing (2026-09-19: widened from 940px/90vh and
   the type inside taken up a step -- it is the last look at a player before
   scouts see them). */
.pv-modal { width: min(1180px, 96vw); max-height: 94vh; }
.pv-modal .an-modal-title { font-size: 16px; }
.pv-body { padding: 20px 26px 22px; }
.pv-loading { display: flex; align-items: center; justify-content: center; gap: 10px; padding: 56px 4px; }

.pv-icon {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 17px; height: 17px; border-radius: 50%; font-size: 10.5px; font-weight: 800; line-height: 1;
}
.pv-icon-ok { background: var(--tint-success); color: var(--tint-success-ink); }
.pv-icon-warning { background: var(--tint-warning); color: var(--tint-warning-ink); }
.pv-icon-error { background: var(--red); color: #fff; }

.pv-verdict { border: 1px solid; border-radius: var(--radius-sm); padding: 16px 20px; }
.pv-verdict-ok { background: var(--tint-success); border-color: var(--tint-success-border); }
.pv-verdict-warning { background: var(--tint-warning); border-color: var(--tint-warning-border); }
.pv-verdict-error { background: var(--tint-danger); border-color: var(--tint-danger-border); }
.pv-verdict-title { display: flex; align-items: center; gap: 10px; font-size: 16.5px; font-weight: 700; }
.pv-verdict-sub { font-size: 13.5px; color: var(--ink-2); margin: 4px 0 0 27px; }
.pv-verdict-list { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.pv-verdict-item {
  font-size: 13.5px; line-height: 1.5; color: var(--ink-2);
  padding-left: 12px; border-left: 3px solid var(--tint-warning-border);
}
.pv-verdict-item-error { border-left-color: var(--red); }
.pv-verdict-item b { color: var(--ink); margin-right: 4px; }
.pv-verdict-fix { color: var(--ink); font-weight: 600; }
/* a blocker among the findings: says in words that this one stops the Send,
   since an error (needs a tick) is drawn in the same red */
.pv-verdict-block {
  display: inline-block; margin-right: 7px; padding: 1px 8px; border-radius: 999px;
  background: var(--red); color: #fff; font-size: 10.5px; font-weight: 700; white-space: nowrap;
}

.pv-section { margin-top: 26px; }
.pv-h {
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); margin: 0 0 9px;
}
.pv-h .pv-raw { text-transform: none; letter-spacing: 0; margin-left: 6px; }
.pv-raw { font-size: 12.5px; font-weight: 500; color: var(--muted); }
.pv-none { color: var(--muted); font-weight: 600; font-style: italic; }

.pv-hero { display: flex; gap: 28px; flex-wrap: wrap; align-items: flex-start; }
.pv-photos { flex: 1 1 480px; min-width: 0; }
.pv-photo-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.pv-photo { margin: 0; min-width: 0; }
/* Background-removed photos are transparent PNGs -- a recessed surface,
   not the card's own white, so the cut-out edge is visible to check. */
.pv-photo-frame {
  aspect-ratio: 3 / 4; border-radius: var(--radius-sm); overflow: hidden;
  background: var(--surface-3); border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center;
}
.pv-photo-frame img { width: 100%; height: 100%; object-fit: contain; display: block; }
.pv-photo-frame span { display: none; font-size: 12px; font-weight: 700; }
.pv-photo-missing .pv-photo-frame { border: 1px dashed var(--tint-warning-border); background: var(--tint-warning); }
.pv-photo-missing .pv-photo-frame span { display: block; color: var(--tint-warning-ink); }
.pv-photo-failed .pv-photo-frame { border-color: var(--red); background: var(--tint-danger); }
.pv-photo-failed .pv-photo-frame img { display: none; }
.pv-photo-failed .pv-photo-frame span { display: block; color: var(--red); }
.pv-photo figcaption { display: flex; flex-direction: column; gap: 2px; margin-top: 8px; font-size: 13.5px; }
.pv-photo-tag { font-size: 12px; color: var(--muted); }

.pv-identity { flex: 1 1 380px; min-width: 0; }
.pv-name {
  display: flex; align-items: center; gap: 10px; font-size: 26px; font-weight: 800;
  letter-spacing: -.02em; margin-bottom: 12px; overflow-wrap: anywhere;
}
.pv-swatch {
  display: inline-block; width: 13px; height: 13px; border-radius: 50%;
  border: 1px solid var(--line-strong); margin-right: 6px; vertical-align: -1px;
}
.pv-flag { display: inline-block; margin-right: 6px; vertical-align: -1px; }

/* align-items: start -- a card with a long explanation under it must not
   stretch its row-mates into tall, mostly empty boxes. */
.pv-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; align-items: start; }
.pv-grid-tight { grid-template-columns: repeat(auto-fill, minmax(165px, 1fr)); }
.pv-field {
  min-width: 0; background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 12px 15px;
}
.pv-field-warning { border-color: var(--tint-warning-border); }
.pv-field-error { border-color: var(--red); background: var(--tint-danger); }
.pv-field-label { font-size: 12px; font-weight: 600; color: var(--muted); margin-bottom: 5px; }
.pv-field-value {
  display: flex; align-items: center; gap: 9px; font-size: 16.5px; font-weight: 700;
  overflow-wrap: anywhere;
}
.pv-note { font-size: 13px; line-height: 1.5; margin-top: 7px; }
.pv-note-error { color: var(--tint-danger-ink); font-weight: 600; }
.pv-note-warning { color: var(--tint-warning-ink); }
.pv-note-info { color: var(--muted); }
.pv-splits { margin-top: 5px; }
.pv-result-src { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 7px; }

.pv-lines { display: flex; flex-direction: column; }
.pv-line {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 11px 0; border-bottom: 1px solid var(--surface-3); font-size: 14.5px;
}
.pv-line:last-child { border-bottom: none; }
.pv-line-main { font-weight: 600; flex: 1 1 200px; min-width: 0; }

.pv-foot {
  border-top: 1px solid var(--line); padding: 14px 26px;
  display: flex; align-items: center; gap: 12px 16px; flex-wrap: wrap; justify-content: flex-end;
}
.pv-ack {
  flex: 1 1 300px; display: flex; align-items: flex-start; gap: 8px;
  font-size: 13.5px; font-weight: 600; color: var(--tint-danger-ink); cursor: pointer;
}
.pv-ack input { margin-top: 2px; accent-color: var(--red); }
.pv-foot-err { flex: 1 1 100%; font-size: 12.5px; color: var(--red); text-align: right; }
.pv-foot-btns { display: flex; gap: 10px; margin-left: auto; }
.pv-foot .primary-btn { width: auto; margin-top: 0; padding: 12px 28px; font-size: 14.5px; }
.pv-foot .ghost-btn { padding: 11px 20px; font-size: 14.5px; }
/* chips and badges inside the popup follow its larger type */
.pv-body .an-badge { font-size: 12px; padding: 3px 11px; }
.pv-body .an-status-chip, .pv-body .an-clip-link { font-size: 13px; }
.pv-body .cm-unit { font-size: 12px; }
.pv-icon { width: 19px; height: 19px; font-size: 11.5px; }
.pv-foot .ghost-btn:disabled { opacity: .5; cursor: not-allowed; }
@media (max-width: 560px) {
  .pv-foot-btns { width: 100%; }
  .pv-foot-btns > * { flex: 1; }
}

/* ===== buggy clips (isBuggy in captures.js) =====
   Analysis failed, or Max speed > best segment average + 0.5 km/h. */
.clip-card.clip-buggy { background: var(--tint-buggy); border-color: var(--tint-buggy-border); }
.buggy-only-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border: 1px solid var(--tint-buggy-border); border-radius: 999px;
  font: 600 12px 'Inter', sans-serif; color: var(--ink-2);
  background: var(--tint-buggy); cursor: pointer; user-select: none; white-space: nowrap;
}
.buggy-note {
  flex-basis: 100%; margin: -4px 0 0; text-align: right;
  font: 500 12px 'Inter', sans-serif; color: var(--muted);
}
.buggy-only-toggle input {
  accent-color: #8b5cf6; width: 14px; height: 14px; margin: 0; cursor: pointer;
}

/* ===== operators exam ===== */
.exam-main { max-width: 900px; }
.exam-intro { margin-top: 12px; font-size: 15.5px; color: var(--ink-2); max-width: 640px; }

.exam-card { padding: 20px; }

.exam-progress-row {
  display: flex; align-items: center; gap: 14px; margin-bottom: 16px;
}
.exam-progress-label {
  font-size: 12.5px; font-weight: 700; color: var(--muted);
  white-space: nowrap; font-family: 'JetBrains Mono', monospace;
}
.exam-progress-track {
  flex: 1; height: 6px; border-radius: 999px; background: var(--line); overflow: hidden;
}
.exam-progress-fill {
  height: 100%; background: var(--accent); border-radius: 999px;
  transition: width .25s ease;
}

.exam-slide-wrap {
  border-radius: 12px; overflow: hidden; background: #0e1726;
  border: 1px solid var(--line); box-shadow: var(--shadow);
  aspect-ratio: 16 / 9;
  display: flex; align-items: center; justify-content: center;
}
.exam-slide-img {
  width: 100%; height: 100%; object-fit: contain; display: block;
  animation: fade .2s ease;
}

.exam-controls {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; margin-top: 20px;
}
.exam-controls .ghost-btn { flex-shrink: 0; }
.exam-controls .primary-btn {
  width: auto; margin-top: 0; padding: 12px 26px; flex-shrink: 0;
}

.exam-timer { position: relative; width: 52px; height: 52px; flex-shrink: 0; }
.exam-timer-ring { width: 100%; height: 100%; transform: rotate(-90deg); }
.exam-timer-track {
  fill: none; stroke: var(--line); stroke-width: 3;
}
.exam-timer-arc {
  fill: none; stroke: var(--accent); stroke-width: 3; stroke-linecap: round;
  stroke-dasharray: 106.8; stroke-dashoffset: 0;
  transition: stroke-dashoffset 1s linear, stroke .2s ease;
}
.exam-timer.done .exam-timer-arc { stroke: var(--lime); }
.exam-timer-text {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font: 700 12.5px 'JetBrains Mono', monospace; color: var(--ink-2);
}
.exam-timer.done .exam-timer-text::after { content: '\2713'; font-size: 16px; color: var(--lime); }
.exam-timer.done .exam-timer-text { font-size: 0; }

.exam-done-card {
  text-align: center; padding: 48px 24px;
}
.exam-done-icon {
  width: 56px; height: 56px; margin: 0 auto 16px; border-radius: 50%;
  background: var(--tint-success); color: var(--lime); font-size: 26px; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
}
.exam-done-card h2 { font-size: 22px; font-weight: 800; letter-spacing: -.02em; }
.exam-done-card p { margin-top: 8px; color: var(--ink-2); font-size: 14.5px; }
.exam-done-sub { color: var(--muted); font-size: 13.5px; max-width: 440px; margin-left: auto; margin-right: auto; }
.exam-done-actions {
  margin-top: 22px; display: flex; align-items: center; justify-content: center; gap: 12px;
}
.exam-done-actions .primary-btn, .exam-done-actions .ghost-btn { width: auto; margin-top: 0; padding: 12px 22px; }

/* ===== quiz ===== */
.quiz-card { padding: 26px 24px; }
.quiz-kicker {
  font: 700 11.5px 'JetBrains Mono', monospace; letter-spacing: .09em; text-transform: uppercase;
  color: var(--accent); margin-top: 4px;
}
.quiz-prompt {
  margin-top: 8px; font-size: 20px; font-weight: 800; letter-spacing: -.015em;
  line-height: 1.35; color: var(--ink);
}
.quiz-hint { margin-top: 6px; font-size: 13.5px; color: var(--muted); }
.quiz-body { margin-top: 22px; }

/* -- choice list (multiple choice / true-false) -- */
.quiz-options { display: grid; gap: 10px; }
.quiz-option {
  display: flex; align-items: center; gap: 12px; text-align: left;
  background: var(--surface-2); border: 1.5px solid var(--line); border-radius: 12px;
  padding: 13px 16px; font: 600 14.5px 'Inter', sans-serif; color: var(--ink-2);
  cursor: pointer; transition: .15s;
}
.quiz-option:hover { border-color: var(--line-strong); background: var(--surface-2); }
.quiz-option-mark {
  flex-shrink: 0; width: 20px; height: 20px; border-radius: 50%; border: 2px solid var(--line-strong);
  display: flex; align-items: center; justify-content: center; font-size: 12px; color: transparent;
  transition: .15s;
}
.quiz-option.multi .quiz-option-mark { border-radius: 6px; }
.quiz-option.selected {
  border-color: var(--accent); background: var(--tint-accent); color: var(--ink);
  box-shadow: 0 0 0 3px rgba(47,109,246,.12);
}
.quiz-option.selected .quiz-option-mark { border-color: var(--accent); background: var(--accent); color: #fff; }
.quiz-option[disabled], .quiz-body.locked .quiz-option { cursor: default; pointer-events: none; }
.quiz-option.correct { border-color: var(--lime); background: var(--tint-success); color: var(--ink); }
.quiz-option.correct .quiz-option-mark { border-color: var(--lime); background: var(--lime); color: #fff; }
.quiz-option.incorrect { border-color: var(--red); background: var(--tint-danger); color: var(--ink); }
.quiz-option.incorrect .quiz-option-mark { border-color: var(--red); background: var(--red); color: #fff; }

/* -- numeric input -- */
.quiz-numeric { display: flex; align-items: center; gap: 12px; max-width: 260px; }
.quiz-numeric input {
  flex: 1; padding: 13px 14px; font: 700 17px 'JetBrains Mono', monospace; color: var(--ink);
  border: 1.5px solid var(--line); border-radius: 11px; background: var(--surface-2); min-width: 0;
}
.quiz-numeric input:focus { outline: none; border-color: var(--accent); background: var(--card); }
.quiz-numeric-unit { font: 600 13.5px 'Inter', sans-serif; color: var(--muted); white-space: nowrap; }

/* -- drag: ordering list -- */
.order-list { display: flex; flex-direction: column; gap: 8px; user-select: none; }
.order-item {
  display: flex; align-items: center; gap: 12px;
  background: var(--surface-2); border: 1.5px solid var(--line); border-radius: 11px;
  padding: 12px 14px; font: 600 14px 'Inter', sans-serif; color: var(--ink-2);
  touch-action: none; position: relative;
}
.order-item-num {
  flex-shrink: 0; width: 22px; height: 22px; border-radius: 50%; background: var(--line);
  color: var(--ink-2); font: 700 11.5px 'JetBrains Mono', monospace;
  display: flex; align-items: center; justify-content: center;
}
.order-item-grip {
  margin-left: auto; flex-shrink: 0; color: var(--muted); font-size: 15px; cursor: grab;
  padding: 4px 6px; line-height: 1; letter-spacing: 1px;
}
.order-item.dragging {
  opacity: .95; border-color: var(--accent); box-shadow: var(--shadow); cursor: grabbing;
  position: relative; z-index: 5;
}
.order-item.correct { border-color: var(--lime); background: var(--tint-success); }
.order-item.incorrect { border-color: var(--red); background: var(--tint-danger); }

/* -- drag: slots & chips (cone placement, matching) -- */
.slots-tray {
  display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 26px; min-height: 46px;
}
.drag-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--navy); color: #fff; border-radius: 9px; padding: 9px 14px;
  font: 700 13px 'Inter', sans-serif; cursor: grab; touch-action: none;
  box-shadow: 0 2px 6px rgba(16,30,54,.18); user-select: none;
}
.drag-chip.cone-chip { background: var(--amber); color: var(--tint-warning-ink); }
.drag-chip.camera-chip { background: var(--accent); color: #fff; }
.drag-chip.dragging { opacity: .25; }
/* On reveal: a chip is judged on where it ended up, not on the slot it sits
   in — an opaque chip would otherwise hide the slot's own border. */
.drag-chip.chip-right { box-shadow: 0 0 0 3px var(--lime); }
.drag-chip.chip-wrong { box-shadow: 0 0 0 3px var(--red); }
.drag-ghost {
  position: fixed; z-index: 999; pointer-events: none; transform: translate(-50%, -50%);
  box-shadow: 0 10px 28px rgba(16,30,54,.28); border-radius: 9px;
}

.match-slots { display: flex; flex-direction: column; gap: 10px; }
.match-slot {
  display: flex; align-items: center; gap: 14px;
  border: 1.5px dashed var(--line-strong); border-radius: 11px; padding: 10px 14px; min-height: 46px;
  background: var(--surface-2);
}
.match-slot-label { font: 700 13.5px 'Inter', sans-serif; color: var(--ink-2); width: 150px; flex-shrink: 0; }
.match-slot-drop { flex: 1; display: flex; }
.match-slot.filled { border-style: solid; border-color: var(--line-strong); }
.match-slot.correct { border-color: var(--lime); background: var(--tint-success); border-style: solid; }
.match-slot.incorrect { border-color: var(--red); background: var(--tint-danger); border-style: solid; }

.track-wrap { padding: 6px 4px 34px; }
.track-line {
  position: relative; height: 4px; background: var(--line); border-radius: 2px; margin: 0 24px;
}
.track-slot {
  position: absolute; top: 50%; width: 52px; height: 52px; margin: -26px 0 0 -26px;
  border: 2px dashed var(--line-strong); border-radius: 50%; background: var(--surface-2);
  display: flex; align-items: center; justify-content: center;
}
.track-slot.filled { border-style: solid; border-color: var(--line-strong); }
.track-slot.correct { border-color: var(--lime); background: var(--tint-success); border-style: solid; }
.track-slot.incorrect { border-color: var(--red); background: var(--tint-danger); border-style: solid; }
.track-slot-label {
  position: absolute; top: 100%; left: 50%; transform: translateX(-50%); margin-top: 8px;
  font: 700 11px 'JetBrains Mono', monospace; color: var(--muted); white-space: nowrap;
}
/* A placed chip stays grabbable so it can be picked up and dropped
   somewhere else — don't let it fall out of the pointer-event hit test. */
.track-end-cone {
  position: absolute; top: 50%; transform: translate(-50%, -50%);
  color: var(--amber); font-size: 20px; line-height: 1; pointer-events: none;
}

/* -- two-row "field" layout (e.g. running line vs. off to the side) -- */
.field-wrap { display: flex; flex-direction: column; gap: 56px; }
.field-row { display: flex; align-items: center; gap: 16px; }
.field-row-label {
  width: 150px; flex-shrink: 0; font: 700 12.5px 'Inter', sans-serif; color: var(--muted);
}
.field-row-track { flex: 1; }
.field-row-track .track-wrap { padding: 6px 4px 46px; }

/* -- hotspot (click-the-mistake) -- */
.hotspot-wrap {
  border-radius: 12px; overflow: hidden; border: 1px solid var(--line); background: #16233d;
}
.hotspot-wrap svg { display: block; width: 100%; height: auto; }
.hotspot-target { cursor: pointer; }
.hotspot-target:hover .hotspot-hitbox { fill: rgba(47,109,246,.14); }
.hotspot-hitbox { fill: transparent; transition: .15s; }
.hotspot-target.selected .hotspot-hitbox { fill: rgba(47,109,246,.22); stroke: var(--accent); stroke-width: 2; }
.hotspot-target.correct .hotspot-hitbox { fill: rgba(22,184,107,.22); stroke: var(--lime); stroke-width: 2; }
.hotspot-target.incorrect .hotspot-hitbox { fill: rgba(217,58,58,.22); stroke: var(--red); stroke-width: 2; }

/* -- feedback banner -- */
.quiz-feedback {
  margin-top: 18px; padding: 14px 16px; border-radius: 11px;
  border: 1.5px solid var(--line); background: var(--surface-2);
}
.quiz-feedback.is-correct { border-color: var(--lime); background: var(--tint-success); }
.quiz-feedback.is-incorrect { border-color: var(--red); background: var(--tint-danger); }
.quiz-feedback-head { font: 800 13.5px 'Inter', sans-serif; letter-spacing: -.01em; }
.quiz-feedback.is-correct .quiz-feedback-head { color: var(--tint-success-ink); }
.quiz-feedback.is-incorrect .quiz-feedback-head { color: var(--tint-danger-ink); }
.quiz-feedback-body { margin-top: 5px; font-size: 13.5px; color: var(--ink-2); line-height: 1.5; }

/* ===== results ===== */
.exam-results-card { text-align: center; padding: 40px 24px; }
.results-score {
  font: 800 44px 'JetBrains Mono', monospace; letter-spacing: -.02em; color: var(--ink);
}
.results-band {
  display: inline-block; margin-top: 8px; padding: 5px 14px; border-radius: 999px;
  font: 700 12.5px 'Inter', sans-serif; text-transform: uppercase; letter-spacing: .06em;
}
.results-band.pass { background: var(--tint-success); color: var(--tint-success-ink); }
.results-band.fail { background: var(--tint-danger); color: var(--tint-danger-ink); }
.results-sub { margin-top: 10px; color: var(--ink-2); font-size: 14px; max-width: 460px; margin-left: auto; margin-right: auto; }

.results-review {
  margin-top: 26px; text-align: left; display: flex; flex-direction: column; gap: 8px;
}
.review-item {
  display: flex; align-items: flex-start; gap: 12px;
  border: 1px solid var(--line); border-radius: 11px; padding: 12px 14px; background: var(--surface-2);
}
.review-mark {
  flex-shrink: 0; width: 22px; height: 22px; border-radius: 50%; font: 800 12px 'Inter', sans-serif;
  display: flex; align-items: center; justify-content: center; margin-top: 1px;
}
.review-item.right .review-mark { background: var(--tint-success); color: var(--lime); }
.review-item.wrong .review-mark { background: var(--tint-danger); color: var(--red); }
.review-text { flex: 1; min-width: 0; }
.review-q { font: 700 13.5px 'Inter', sans-serif; color: var(--ink); line-height: 1.4; }
.review-a { margin-top: 3px; font-size: 12.5px; color: var(--muted); line-height: 1.45; }

@media (max-width: 640px) {
  .exam-controls { flex-wrap: wrap; }
  .exam-controls .primary-btn { order: 3; width: 100%; }
  .exam-done-actions { flex-direction: column; }
  .exam-done-actions .primary-btn, .exam-done-actions .ghost-btn { width: 100%; }
  .match-slot-label { width: 110px; font-size: 12.5px; }
  .field-row { flex-direction: column; align-items: stretch; gap: 6px; }
  .field-row-label { width: auto; }
}

/* ===== auth (login.html + the guard in auth.js) ===== */
/* auth.js removes this class once the session check settles; keeps every
   protected page's body invisible until then, so there's no flash of
   content before the redirect-to-login kicks in. */
html.auth-pending body { visibility: hidden; }

.auth-main { max-width: 420px; padding-top: 64px; }
.auth-card { padding: 32px; }
.auth-form { display: flex; flex-direction: column; }
.auth-title { font-size: 22px; font-weight: 800; letter-spacing: -.02em; color: var(--ink); }
.auth-sub { margin-top: 6px; font-size: 13.5px; color: var(--muted); line-height: 1.5; }

.auth-field {
  display: flex; flex-direction: column; gap: 6px;
  margin-top: 18px; font-size: 13px; font-weight: 600; color: var(--ink-2);
}
.auth-field input {
  font-family: inherit; font-size: 15px; color: var(--ink);
  padding: 11px 13px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); transition: .15s;
}
.auth-field input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(47,109,246,.12); }

.auth-hint { margin-top: 10px; font-size: 12px; color: var(--muted); line-height: 1.5; }

.auth-error {
  margin-top: 16px; padding: 10px 13px; border-radius: 9px;
  background: var(--tint-danger); color: var(--tint-danger-ink); font-size: 13px; font-weight: 600; line-height: 1.45;
}
.auth-note {
  margin-top: 16px; padding: 10px 13px; border-radius: 9px;
  background: var(--tint-success); color: var(--tint-success-ink); font-size: 13px; font-weight: 600; line-height: 1.45;
}

.auth-link {
  margin-top: 14px; background: none; border: none; padding: 0;
  font-family: inherit; font-size: 13px; font-weight: 600; color: var(--accent);
  cursor: pointer; align-self: center;
}
.auth-link:hover { color: var(--accent-d); text-decoration: underline; }

/* ---- signed-in control injected into the top bar ---- */
.topnav-user { display: flex; align-items: center; gap: 12px; margin-left: 12px; padding-left: 16px; border-left: 1px solid rgba(255,255,255,.18); }
.topnav-user-email { font-size: 12.5px; color: #cdd7e8; }
.topnav-signout {
  background: transparent; border: 1px solid #33415c; border-radius: 8px;
  padding: 6px 12px; font-family: inherit; font-size: 12.5px; font-weight: 600;
  color: #cdd7e8; cursor: pointer; transition: .15s;
}
.topnav-signout:hover { border-color: #7fa4ff; color: #fff; }

/* ---- appearance toggle (auth.js initThemeToggle) ----
   Lives on the navy top bar on every page, including login.html (that
   bar always has .brand; on signed-in pages auth.js slots this in right
   before Sign out). The navy bar itself never changes with the page's
   own appearance -- see the note on body.operator-view below for why
   the bar is a fixed brand color -- so this button's own colors are
   fixed too, not theme tokens. */
.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; width: 32px; height: 32px; border-radius: 999px;
  background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.14);
  color: #fff; font-size: 15px; line-height: 1; cursor: pointer; padding: 0;
  transition: background-color .15s var(--ease), transform .2s var(--ease);
}
.theme-toggle:hover { background: rgba(255,255,255,.16); transform: rotate(-12deg); }
@media (prefers-reduced-motion: reduce) {
  .theme-toggle:hover { transform: none; }
}

/* ---- level badge + ladder popover (gamification, see auth.js) ---- */
.level-badge {
  position: relative; display: flex; align-items: center; gap: 7px;
  padding: 6px 12px; border-radius: 8px;
  background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.14);
}
.level-badge-label { font-size: 12.5px; font-weight: 700; color: #fff; }
.level-badge > .level-icon { width: 22px; height: 22px; flex: none; display: block; }
.level-popover {
  display: none; position: absolute; top: calc(100% + 10px); right: 0; z-index: 40;
  width: 296px; background: var(--card); color: var(--ink); border-radius: 12px;
  box-shadow: var(--shadow); padding: 14px; text-align: left; cursor: default;
}
.level-badge:hover .level-popover,
.level-badge:focus .level-popover,
.level-badge:focus-within .level-popover { display: block; }
.level-popover-title {
  font-size: 11.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--muted); margin-bottom: 10px;
}
.level-ladder { list-style: none; display: flex; flex-direction: column; gap: 10px; }
.level-ladder-item { display: flex; gap: 11px; align-items: flex-start; }
.level-ladder-item > .level-icon { width: 30px; height: 30px; flex: none; display: block; }
/* Levels still ahead read as locked -- greyed out, so the colour arriving is
   itself the reward for getting there. brightness/contrast are here because
   the art is shaded: grayscale(1) alone turns a lit 3D render into mud rather
   than into something that reads as "not yet earned", so this lifts it back
   up and flattens the shading out. Slightly higher opacity than flat art
   needed for the same reason -- the characters are darker to begin with. */
.level-ladder-item.upcoming > .level-icon {
  filter: grayscale(1) brightness(1.18) contrast(.82);
  opacity: .45;
}
.level-ladder-item.current {
  background: var(--tint-accent); border-radius: 9px;
  margin: -5px -7px; padding: 5px 7px;
}
.level-ladder-name { font-size: 13px; font-weight: 700; color: var(--ink); }
.level-ladder-item.upcoming .level-ladder-name { color: var(--ink-2); }
.level-ladder-item.current .level-ladder-name { color: var(--accent-d); }
.level-ladder-blurb { font-size: 11.5px; color: var(--muted); line-height: 1.4; margin-top: 1px; }

@media (max-width: 640px) {
  .auth-main { padding-top: 32px; }
  .topnav-user { margin-left: 0; padding-left: 0; border-left: none; }
  .level-popover { right: auto; left: 0; }
}

/* ---- operator view: Captures shows the original video, no controls ----
 * auth.js adds body.operator-view for the "operators" Cognito group. Hides,
 * on every clip card:
 *  - every rendition of the clip except the original: the normalized crop
 *    AND the annotated analysis. The original is always the FIRST <figure>
 *    in .video-pair -- in captures.js's clipCard (original, normalized,
 *    annotated) and in skeletonClipCard -- so hiding every figure after
 *    the first covers both shapes with one selector, and covers any
 *    rendition added later by default.
 *  - KPI/metric displays and the frame-by-frame chart
 *  - the "By test" sort-by-KPI toolbar (pointless once those values aren't shown)
 *  - every control/status: the "Analyzed <date>" label, the
 *    "Failed"/"Analysing…"/etc. status-badge pill (an analysed clip shows
 *    no pill to anyone -- see STATUS in captures.js), and every .reanalyze-btn
 *    (the Valid/Invalid verdict and Mark cones buttons) -- operators get a
 *    read-only original-video viewer, nothing to trigger or read a status
 *    off.
 * A standing CSS rule, not a one-time DOM pass, so it also covers every
 * card captures.js renders later (they load asynchronously). */
body.operator-view .video-pair { grid-template-columns: 1fr; }
body.operator-view .video-pair > figure:not(:first-child),
body.operator-view .clip-metrics,
body.operator-view .cmp-metrics,
body.operator-view .clip-chart,
body.operator-view .cmp-chart-mount,
body.operator-view .clip-sort-kpi,
body.operator-view .group-sort,
body.operator-view .clip-analyzed,
body.operator-view .status-badge,
body.operator-view .reanalyze-btn {
  display: none !important;
}

/* Why :not(:first-child) and not a rule per rendition: this used to hide
   figure:last-child, and when the normalized rendition was added in the
   middle, that positional rule hid the analysis but left the normalized crop
   showing to operators. Keying off the ONE figure that should stay -- the
   original, always first -- inverts that failure mode: a rendition added
   later is hidden until someone deliberately unhides it, rather than leaking.
   Plain CSS3 too, so unlike a :has() selector there is no browser where it
   is dropped and operators are shown everything. */

/* ---- operator view: exam intro copy ----
 * exam.html has two intro paragraphs -- one mentions the one-minute-per-slide
 * wait (operators), the other doesn't (Guy/staff, who navigate freely). Same
 * body.operator-view class as above picks which one shows. */
.exam-intro-operator { display: none; }
body.operator-view .exam-intro-operator { display: block; }
body.operator-view .exam-intro-staff { display: none; }

/* ===== Players page (players.js, 2026-11-03) ===== */
.feed-note { margin: -6px 0 14px; font-size: 12px; color: var(--muted); }
.cell-sub { font-size: 12px; color: var(--muted); }
.pf-err { color: var(--red); font-size: 12px; margin-top: 4px; }
.md-line { display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: baseline; padding: 2px 0; }
.md-line + .md-line { border-top: 1px solid var(--line); }
.md-line small { color: var(--muted); font-weight: 500; }
.md-speed { font-weight: 700; font-variant-numeric: tabular-nums; }
.team-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 8px 16px; margin-top: 10px; }
.team-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.team-name { font-weight: 600; overflow-wrap: anywhere; }
.team-check { display: flex; align-items: center; gap: 8px; min-height: 32px; }

.mp-who { display: flex; align-items: center; gap: 10px; }
.mp-photo { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; object-position: top; background: var(--surface-3); flex: none; }
.mp-num { width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center; flex: none; background: var(--navy); color: #fff; font-weight: 800; font-variant-numeric: tabular-nums; }
.mp-team { margin: 22px 0 10px; } .mp-team:first-child { margin-top: 0; }
