/*
 * Mestre design system — mobile-first (views are wrapped in native shells).
 * Brand: ink / gold / paper.
 */

:root {
  --ink: #0d0d0d;
  --ink-soft: #1c1a17;
  --gold: #c8a96e;
  --gold-dark: #a8894e;
  --paper: #f5f0e8;
  --card: #fffdf8;
  --muted: rgba(13, 13, 13, 0.55);
  --faint: rgba(13, 13, 13, 0.4);
  --border: rgba(13, 13, 13, 0.12);
  --danger: #b3341f;
  --ok: #3f7d55;
  --radius: 14px;
  --shadow: 0 1px 3px rgba(13, 13, 13, 0.08), 0 8px 24px rgba(13, 13, 13, 0.04);
  --tabbar-h: 64px;
  --topnav-h: 60px;
  --sidebar-w: 240px;
  --content-max: 860px;

  /* Two faces, two jobs. Bebas Neue is a caps-only poster face — it earns its
     keep on headings and the wordmark and nowhere else. Syne carries the rest
     of the UI: it has weights, lowercase and tabular figures, so it handles
     labels, prose, timecodes and scores without shouting. */
  --font-display: "Bebas Neue", sans-serif;
  --font-ui: "Syne", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
}

a { color: inherit; text-decoration: none; }

/* Headings are the one place the display face runs. Bebas has no lowercase and
   a short cap height, so it needs a size bump, open tracking and a tight
   leading to sit at the same optical weight the old Syne headings had. */
h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: 0.02em;
  line-height: 1.05;
  margin: 0 0 0.4em;
}
h1 { font-size: 2.1rem; }
h2 { font-size: 1.55rem; }
h3 { font-size: 1.25rem; }

.display {
  font-family: var(--font-display);
  letter-spacing: 0.02em;
  font-weight: 400;
}

/* ---------- Layout ---------- */

.app {
  width: 100%;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 18px;
  background: var(--ink);
  color: var(--paper);
}

.topbar .brand {
  font-family: var(--font-display);
  font-size: 1.7rem;
  letter-spacing: 0.04em;
  color: var(--paper);
}

.topbar .brand span { color: var(--gold); }

.topbar .topbar-action { color: var(--gold); font-size: 0.9rem; font-weight: 600; }

.topbar .back { color: var(--gold); font-size: 0.95rem; }

.content {
  flex: 1;
  padding: 18px 18px calc(var(--tabbar-h) + 28px);
}

.page-title {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 14px;
}

/* ---------- Bottom tab bar ---------- */

.tabbar {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  height: var(--tabbar-h);
  display: flex;
  background: var(--card);
  border-top: 1px solid var(--border);
  box-shadow: 0 -2px 16px rgba(13, 13, 13, 0.05);
  z-index: 30;
}

.tabbar a {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--faint);
}

.tabbar a .ico { font-size: 1.2rem; line-height: 1; }
.tabbar a.active { color: var(--ink); }
.tabbar a.active .ico { color: var(--gold); }

/* Sign-out is a button_to form; make it lay out like the other tabs. */
.tabbar form.button_to { flex: 1; display: flex; }
.tabbar .tabbar-signout {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--faint);
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
}
.tabbar .tabbar-signout .ico { font-size: 1.2rem; line-height: 1; }

/* ---------- Cards ---------- */

.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 16px;
  margin-bottom: 14px;
}

.card.tight { padding: 12px 14px; }

.card-link { display: block; }
.card-link:hover { border-color: var(--gold); box-shadow: 0 2px 12px rgba(13, 13, 13, 0.08); }
.card-link:active { transform: scale(0.995); }
/* The trailing cluster of a list row (badges, ruleset) stays on one line. */
.row .row-end { display: flex; align-items: center; gap: 8px; white-space: nowrap; }

.section-label {
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
  font-weight: 700;
  margin: 22px 0 8px;
}

/* ---------- Buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 18px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
  transition: transform 0.06s ease, background 0.15s ease;
  text-align: center;
}
.btn:active { transform: scale(0.97); }
.btn-block { display: flex; width: 100%; }

.btn-primary { background: var(--gold); color: var(--ink); }
.btn-primary:hover { background: var(--gold-dark); }

.btn-dark { background: var(--ink); color: var(--paper); }

.btn-ghost { background: transparent; border-color: var(--border); color: var(--ink); }

.btn-danger { background: transparent; border-color: rgba(179, 52, 31, 0.4); color: var(--danger); }

.btn-sm { padding: 8px 14px; font-size: 0.85rem; }

/* ---------- Forms ---------- */

.field { margin-bottom: 16px; }

.field label {
  display: block;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  margin-bottom: 6px;
  color: var(--ink);
}

.field .hint { font-size: 0.78rem; color: var(--muted); margin-top: 5px; }

/* Direct-upload progress for match footage — see upload_controller.js. */
.upload-progress {
  height: 6px;
  margin-top: 8px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
}

.upload-progress-bar {
  height: 100%;
  width: 0;
  background: var(--gold);
  transition: width 120ms linear;
}

input[type="text"],
input[type="email"],
input[type="number"],
input[type="date"],
input[type="search"],
select,
textarea {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: #fff;
  font-family: inherit;
  font-size: 1rem;
  color: var(--ink);
}

input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(200, 169, 110, 0.25);
}

textarea { resize: vertical; min-height: 84px; }

/* The sign-in code is data, not decoration — Syne, tabular, and cased up in
   CSS since the UI face (unlike the old display one) has real lowercase. */
.code-input {
  text-align: center;
  font-family: var(--font-ui);
  font-size: 1.9rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-transform: uppercase;
  letter-spacing: 0.32em;
  padding-left: 0.32em;
}

/* ---------- Combobox (single-input autocomplete) ---------- */
/* One text input over a long list — the position taxonomy is 30-odd entries
   deep in a tree, which is miserable as a <select> on a phone. Rows are all
   in the markup; the Stimulus controller hides the ones that don't match. */

.combo { position: relative; }
.combo-input { padding-right: 32px; }
.combo.is-open .combo-input {
  border-color: var(--gold);
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

.combo-clear {
  position: absolute;
  top: 0;
  right: 0;
  width: 32px;
  height: 100%;
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
.combo-clear:hover { color: var(--ink); }

.combo-list {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 40;
  max-height: 260px;
  overflow-y: auto;
  margin: 0;
  padding: 4px 0;
  list-style: none;
  background: #fff;
  border: 1px solid var(--gold);
  border-top: 0;
  border-radius: 0 10px 10px 10px;
  box-shadow: 0 12px 28px rgba(20, 20, 20, 0.16);
}
.combo.is-empty .combo-list { display: none; }

.combo-option {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 7px 12px;
  cursor: pointer;
  font-size: 0.9rem;
}
/* Beats display:flex above — same specificity as [hidden], so it has to win on
   order, otherwise filtered-out rows stay on screen. */
.combo-option[hidden] { display: none; }
.combo-option.is-active { background: rgba(200, 169, 110, 0.2); }
.combo-option.is-blank { font-style: italic; }
.combo-option.is-blank .combo-name { color: var(--muted); }
.combo-name { color: var(--ink); }
.combo-path { font-size: 0.72rem; color: var(--muted); }

.toggle-group { display: flex; gap: 8px; }
.toggle-group label {
  flex: 1;
  text-align: center;
  padding: 11px;
  border: 1px solid var(--border);
  border-radius: 10px;
  cursor: pointer;
  font-weight: 700;
  font-size: 0.9rem;
  background: #fff;
}
.toggle-group input { position: absolute; opacity: 0; }
.toggle-group input:checked + span { color: var(--ink); }
.toggle-group label:has(input:checked) {
  border-color: var(--gold);
  background: rgba(200, 169, 110, 0.16);
}

.inline-fields { display: flex; gap: 12px; }
.inline-fields .field { flex: 1; }

/* ---------- Badges ---------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  background: rgba(13, 13, 13, 0.06);
  color: var(--ink);
}
.badge.gi { background: rgba(13, 13, 13, 0.85); color: var(--paper); }
.badge.no_gi { background: rgba(200, 169, 110, 0.22); color: var(--gold-dark); }
.badge.win { background: rgba(63, 125, 85, 0.16); color: var(--ok); }
.badge.loss { background: rgba(179, 52, 31, 0.14); color: var(--danger); }
.badge.check { background: rgba(200, 169, 110, 0.28); color: var(--gold-dark); }

.belt {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
  font-size: 0.82rem;
}
.belt-bar {
  width: 34px; height: 12px; border-radius: 3px;
  border: 1px solid rgba(13,13,13,0.25);
  position: relative; overflow: hidden;
  background: #eee;
}
.belt-bar .stripes {
  position: absolute; right: 2px; top: 1px; bottom: 1px;
  display: flex; gap: 2px;
}
.belt-bar .stripes i { width: 2px; background: #fff; display: block; }

/* ---------- Lists ---------- */

.stack > * + * { margin-top: 10px; }

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

.meta { color: var(--muted); font-size: 0.85rem; }
.muted { color: var(--muted); }
.faint { color: var(--faint); }

.week-heading {
  font-size: 0.74rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
  font-weight: 700;
  margin: 18px 0 8px;
}

/* ---------- Stats ---------- */

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.stat-tile {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  box-shadow: var(--shadow);
}
.stat-tile .num {
  font-family: var(--font-ui);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  font-size: 2.1rem;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--ink);
}
.stat-tile .num small { font-size: 1rem; color: var(--muted); }
.stat-tile .lbl { font-size: 0.74rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--faint); font-weight: 700; margin-top: 6px; }

.chart {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  height: 140px;
  padding-top: 8px;
}
.chart .bar-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; height: 100%; justify-content: flex-end; }
.chart .bar {
  width: 100%;
  max-width: 26px;
  background: linear-gradient(180deg, var(--gold), var(--gold-dark));
  border-radius: 5px 5px 0 0;
  min-height: 3px;
}
.chart .bar.empty { background: rgba(13,13,13,0.08); }
.chart .bar-lbl { font-size: 0.64rem; color: var(--faint); font-weight: 600; }

/* ---------- Quick actions ---------- */

.quick-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.quick {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.quick .ico { font-size: 1.5rem; }
.quick .t { font-weight: 700; font-size: 0.95rem; }
.quick .d { font-size: 0.78rem; color: var(--muted); }

/* ---------- Flash ---------- */

.flash { width: 100%; }
.flash > div {
  padding: 12px 18px;
  font-size: 0.9rem;
  font-weight: 600;
  text-align: center;
}
.flash .notice { background: rgba(63, 125, 85, 0.14); color: var(--ok); }
.flash .alert { background: rgba(179, 52, 31, 0.12); color: var(--danger); }

/* ---------- Empty states ---------- */

.empty {
  text-align: center;
  padding: 48px 20px;
  color: var(--muted);
}
.empty .ico { font-size: 2.4rem; margin-bottom: 8px; }

/* ---------- Errors ---------- */

.form-errors {
  background: rgba(179, 52, 31, 0.08);
  border: 1px solid rgba(179, 52, 31, 0.25);
  border-radius: 10px;
  padding: 12px 14px;
  margin-bottom: 16px;
  color: var(--danger);
  font-size: 0.88rem;
}
.form-errors ul { margin: 6px 0 0; padding-left: 18px; }

/* ---------- Auth screens ---------- */

.auth-wrap {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  max-width: 420px;
  margin: 0 auto;
  padding: 32px 24px;
}
.auth-wrap .brand {
  font-family: var(--font-display);
  font-size: 4rem;
  line-height: 0.9;
  letter-spacing: 0.03em;
}
.auth-wrap .brand span { color: var(--gold); }
.auth-wrap .tagline { color: var(--muted); margin-bottom: 32px; }
.auth-wrap p { margin-bottom: 16px; }
.auth-wrap .btn + .btn { margin-top: 10px; }

/* What an app is asking to read, on the OAuth consent screen. */
.consent-list {
  margin: 0 0 16px;
  padding: 14px 16px 14px 32px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
}
.consent-list li { margin: 4px 0; font-size: 0.92rem; }

/* ---------- Wide screens (tablet / desktop) ---------- */

@media (min-width: 860px) {
  /* Bottom tab bar becomes a horizontal top bar. */
  .tabbar {
    top: 0;
    bottom: auto;
    left: 0;
    transform: none;
    width: 100%;
    max-width: none;
    height: var(--topnav-h);
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    gap: 4px;
    padding: 0 24px;
    border-top: none;
    border-bottom: 1px solid var(--border);
    box-shadow: 0 2px 16px rgba(13, 13, 13, 0.05);
  }

  .tabbar::before {
    content: "MESTRE";
    display: block;
    font-family: var(--font-display);
    font-size: 1.7rem;
    letter-spacing: 0.04em;
    color: var(--ink);
    margin-right: 24px;
  }

  .tabbar a {
    flex: none;
    flex-direction: row;
    justify-content: center;
    gap: 8px;
    width: auto;
    padding: 8px 14px;
    border-radius: 10px;
    font-size: 0.92rem;
    font-weight: 600;
    letter-spacing: 0;
    text-transform: none;
    color: var(--muted);
  }

  .tabbar a .ico { font-size: 1.15rem; }
  .tabbar a:hover { background: rgba(13, 13, 13, 0.04); color: var(--ink); }
  .tabbar a.active { background: rgba(200, 169, 110, 0.16); color: var(--ink); }

  /* Sign-out is pushed to the far right of the bar. */
  .tabbar form.button_to { flex: none; margin-left: auto; }
  .tabbar .tabbar-signout {
    flex: none;
    flex-direction: row;
    gap: 8px;
    width: auto;
    padding: 8px 14px;
    border-radius: 10px;
    font-size: 0.92rem;
    letter-spacing: 0;
    text-transform: none;
    color: var(--muted);
  }
  .tabbar .tabbar-signout:hover { background: rgba(13, 13, 13, 0.04); color: var(--ink); }

  /* Signed-in pages: content sits below the fixed top bar and centres. Signed-out
     (auth) pages have no bar, so they stay centred by the base `.app` rule. */
  .app { min-height: auto; }

  body:has(.tabbar) .app {
    max-width: var(--content-max);
    margin: 0 auto;
    padding-top: var(--topnav-h);
  }

  /* Flash sits below the fixed top bar. */
  .flash { width: 100%; }

  .content { padding: 28px 32px 48px; }

  /* Topbar reads as a page header rather than an app chrome bar. */
  .topbar {
    position: static;
    background: transparent;
    color: var(--ink);
    padding: 22px 32px 0;
  }
  .topbar .brand { display: none; }
  .topbar .back { color: var(--gold-dark); }
  .topbar .topbar-action { color: var(--gold-dark); }
}

/* Roomy desktop: let the two-up sections breathe. */
@media (min-width: 1100px) {
  :root { --content-max: 960px; }
}

/* ── Breakdown layout ───────────────────────────────────────────────────
 * Stacked, full-width zones: labels bar across the top, then a large video,
 * then the scrobbler dock directly beneath it. The labels bar and the track
 * both scroll horizontally so neither constrains the video's size.
 */
.tl-page { display: block; }

/* Let the breakdown page use the full width below the top bar. On desktop the
   breakdown becomes a grid: a facts strip across the top, the video on the left
   with the scrobbler dock beneath it, and the timeline labels as a vertical
   rail down the right. */
@media (min-width: 860px) {
  body:has(.tl-wide) .app {
    max-width: none;
    margin: 0;
    padding-top: var(--topnav-h);
  }

  .tl-page:has(> .tl-labels) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    grid-template-areas:
      "meta   labels"
      "stage  labels"
      "dock   labels";
    grid-template-rows: auto auto 1fr;
    column-gap: 20px;
    align-items: start;
  }
  .tl-page > .tl-meta   { grid-area: meta; }
  .tl-page > .tl-labels { grid-area: labels; }
  .tl-page > .tl-stage  { grid-area: stage; }
  .tl-page > .tl-dock   { grid-area: dock; }
  /* Edge states span the content column. */
  .tl-page > .form-errors,
  .tl-page > .empty { grid-column: meta-start / stage-end; }

  /* Labels become a vertical, scrollable rail down the right. */
  .tl-labels {
    margin-bottom: 0;
    position: sticky;
    top: calc(var(--topnav-h) + 20px);
    align-self: start;
  }
  .tl-labels-row {
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 6px;
    overflow-x: hidden;
    overflow-y: auto;
    max-height: calc(100vh - var(--topnav-h) - 120px);
    padding: 2px 4px 2px 0;
  }
  .tl-chip {
    max-width: none;
    width: 100%;
  }
}

/* A small muted rule over a rail — the same job as .section-label, so it wears
   the same UI face rather than the display one. */
.tl-rail-title {
  font-family: var(--font-ui);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-size: 0.75rem;
  color: var(--muted);
}

/* ── Labels bar (across the top) ────────────────────────────────────────── */
.tl-labels { margin-bottom: 16px; }
.tl-labels-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}
.tl-labels-row {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 6px;
  scrollbar-width: thin;
}
.tl-chip {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  flex: none;
  max-width: 260px;
  padding: 8px 12px;
  text-align: left;
  cursor: pointer;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.tl-chip:hover { border-color: var(--muted); }
.tl-chip.is-active {
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
}
.tl-chip-body { min-width: 0; }

/* ── Video stage ────────────────────────────────────────────────────────── */
.tl-stage { min-width: 0; }
.tl-video-wrap {
  position: relative;
  width: 100%;
  border-radius: 12px;
  background: #000;
  overflow: hidden;
}
/* The player is capped so the footage, the scrobbler and the block toolbar all
   land on one screen — the controller measures the room left under the video and
   writes --tl-video-max. 72vh is the pre-JS fallback. */
.tl-video {
  display: block;
  width: 100%;
  max-height: var(--tl-video-max, 72vh);
  margin: 0 auto;
  background: #000;
  object-fit: contain;
}

/* Mini player: once the stage scrolls off the top the player re-docks to a
   small floating panel in the top-right, so the footage stays watchable while
   you work the scrobbler and corrections below. ✕ dismisses it until the stage
   scrolls back into view. */
.tl-video-wrap.is-docked {
  position: fixed;
  top: calc(var(--topnav-h) + 12px);
  right: 16px;
  width: min(360px, 44vw);
  z-index: 40;
  border: 1px solid var(--border);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.35);
}
.tl-video-wrap.is-docked .tl-video { max-height: none; }

.tl-dock-close { display: none; }
.tl-video-wrap.is-docked .tl-dock-close {
  display: grid;
  place-items: center;
  position: absolute;
  top: 6px;
  right: 6px;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  font-size: 0.75rem;
  line-height: 1;
  cursor: pointer;
}
.tl-video-wrap.is-docked .tl-dock-close:hover { background: rgba(0, 0, 0, 0.9); }

@media (max-width: 600px) {
  .tl-video-wrap.is-docked {
    width: min(230px, 54vw);
    right: 10px;
    top: calc(var(--topnav-h) + 8px);
  }
}
/* Facts strip across the top of the breakdown: Status / Frames / Duration laid
   out as inline label–value pairs rather than stacked rows. */
.tl-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 22px;
  margin: 0 0 14px;
}
.tl-meta .row {
  width: auto;
  justify-content: flex-start;
  gap: 8px;
  font-size: 0.9rem;
}
.tl-meta .row > span:first-child {
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.72rem;
  font-weight: 700;
}

.tl-dock { margin-top: 16px; }

/* Readout + track + block toolbar: the group that has to stay on screen with the
   video, and the one the controller measures to size the player. */
.tl-scrobbler { margin: 0 0 12px; }

/* Block toolbar — one bar clamped to the bottom edge of the track, holding
   everything you do to the block under the playhead: its range, the position and
   advantage selects, and the split. Follows the current position as it plays. */
.tl-fixpanel { margin: 0; }
.tl-fixform {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 0 0 10px 10px;
  background: var(--paper);
}
/* The controller shows one row at a time with the `hidden` attribute, and a
   display of anything but `none` beats it — so say it explicitly. */
.tl-fixform[hidden] { display: none; }
.tl-fix-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: none;
  font-size: 0.82rem;
  color: var(--muted);
}
/* Cut the current block in two at the playhead — the last tool along the bar. */
.tl-splitbar {
  display: flex;
  align-items: center;
  flex: none;
  margin: 0;
}
.tl-split-btn {
  padding: 4px 10px;
  border: 1px dashed var(--border);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
}
.tl-split-btn:hover { border-color: var(--gold-dark); color: var(--gold-dark); }
.tl-fix-label strong {
  font-family: var(--font-ui);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* ── Scrobbler readout + zoom (above the track) ─────────────────────────── */
.tl-readout {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 8px;
}
.tl-readout .tl-time {
  font-family: var(--font-ui);
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  flex: none;
}
.tl-zoom {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  margin-left: auto;
}
.tl-zoom-btn {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--card);
  color: var(--ink);
}
.tl-zoom-btn:hover { border-color: var(--muted); }
.tl-zoom-label {
  min-width: 34px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  color: var(--muted);
}

/* ── Zoomable, scrolling track ──────────────────────────────────────────── */
.tl-track-scroll {
  overflow-x: auto;
  overflow-y: hidden;
  padding: 3px 0;
  scrollbar-width: thin;
  /* Deliberately not `scroll-behavior: smooth` — the controller keeps the
     playhead in view by nudging scrollLeft every frame, and an animated
     scroller would still be gliding to the last target when the next arrives.
     Jumps that should glide (a chip click, a zoom) opt in per call instead. */
}
.tl-track {
  position: relative;
  /* At 1× the track fills the viewport; zoom widens it so it scrolls. */
  width: calc(100% * var(--tl-zoom, 1));
  min-width: 100%;
  height: 48px;
  border-radius: 8px;
  background: rgba(13, 13, 13, 0.06);
  cursor: pointer;
  touch-action: none;
  user-select: none;
}
/* Split into three stacked lanes: the dyadic position row (the correctable
   blocks — grips and the fix panel work here), then one row per fighter with
   that fighter's own state stream. The lanes carry their own backgrounds, so
   the track itself goes transparent; a single playhead (a direct child of the
   track) still spans all three. */
.tl-track--split {
  height: 126px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  background: none;
  border-radius: 0;
}
.tl-lane {
  position: relative;
  flex: 1;
  border-radius: 6px;
  overflow: hidden;
  background: rgba(13, 13, 13, 0.06);
  /* A stable-identity fighter row carries a thin accent in its colour; the
     role-based fallback rows leave --fighter unset and show none. */
  box-shadow: inset 3px 0 0 var(--fighter, transparent);
}
/* Who a row belongs to, said in the row itself — floats over the left edge,
   never intercepts the scrub. */
.tl-lane-tag {
  position: absolute;
  top: 2px;
  left: 6px;
  z-index: 1;
  font-size: 0.6rem;
  line-height: 1;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  pointer-events: none;
  white-space: nowrap;
}
.tl-track:focus-visible {
  outline: 2px solid var(--gold-dark);
  outline-offset: 2px;
}
.tl-seg {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--seg);
  /* 2px surface gap between adjacent fills */
  box-shadow: inset 2px 0 0 var(--card);
  opacity: 0.5;
  transition: opacity 0.12s ease;
}
.tl-seg:first-of-type { box-shadow: none; }
/* A fighter-lane state block: same geometry as a segment block, but it isn't a
   correction target — it reports, the position row is where you edit. */
.tl-span {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--seg);
  box-shadow: inset 2px 0 0 var(--card);
  opacity: 0.7;
}
.tl-span:first-of-type { box-shadow: none; }
/* Flux: the time BETWEEN known positions. Hatched grey, deliberately unlike
   any settled colour — the timeline admits it was in transition rather than
   asserting a position it can't stand behind. */
.tl-seg.is-flux,
.tl-span.is-flux {
  background: repeating-linear-gradient(
    -45deg,
    rgba(13, 13, 13, 0.16) 0 5px,
    rgba(13, 13, 13, 0.04) 5px 10px
  );
  opacity: 0.85;
}
/* Known, but shakily — a low-confidence read never looks as solid as a clean
   one. */
.tl-span.is-uncertain { opacity: 0.35; }
/* Only the segment under the playhead is emphasised — a clear top accent, no
   sweeping border that reads as the other positions flashing. */
.tl-seg.is-active {
  opacity: 1;
  box-shadow: inset 0 3px 0 var(--ink), inset 2px 0 0 var(--card);
}
/* Positioned by transform from the controller, one write per animation frame.
   No transition: the playhead is already re-rendered every frame, so easing it
   would only ever leave it trailing behind the video (and the pointer). */
.tl-playhead {
  position: absolute;
  top: -3px;
  bottom: -3px;
  left: 0;
  width: 2px;
  background: var(--ink);
  pointer-events: none;
  will-change: transform;
  z-index: 2;
}
.tl-playhead::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: 11px;
  height: 11px;
  border-radius: 999px;
  background: var(--ink);
  transform: translate(-50%, -40%);
}
/* Caught on a block edge or a point marker mid-drag: the playhead goes gold so
   the time holding still under a moving finger reads as landing on something. */
.tl-playhead.is-snapped { background: var(--gold-dark); }
.tl-playhead.is-snapped::before {
  background: var(--gold-dark);
  transform: translate(-50%, -40%) scale(1.25);
}

/* ── Time ruler above the track ─────────────────────────────────────────── */
/* Sits inside the scroller and matches the track's zoomed width, so the ticks
   stay pinned to the footage as it scrolls. Contents are drawn by the
   controller, which picks an interval that fits the current zoom. */
.tl-ruler {
  position: relative;
  height: 14px;
  width: calc(100% * var(--tl-zoom, 1));
  min-width: 100%;
  margin-bottom: 2px;
  pointer-events: none;
}
.tl-tick {
  position: absolute;
  top: 0;
  padding-left: 4px;
  border-left: 1px solid var(--border);
  font-size: 0.66rem;
  line-height: 13px;
  letter-spacing: 0.02em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ── Hover preview ──────────────────────────────────────────────────────── */
/* A ghost of the playhead following the pointer, captioned with the time and
   the block under it — read the track without seeking away from your place. */
.tl-ghost {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 1px;
  background: var(--muted);
  opacity: 0.55;
  pointer-events: none;
  z-index: 3;
}
.tl-ghost[hidden] { display: none; }
.tl-ghost-label {
  position: absolute;
  bottom: 100%;
  left: 0;
  margin-bottom: 3px;
  padding: 2px 6px;
  border-radius: 5px;
  background: var(--ink);
  color: var(--paper, #fff);
  font-size: 0.68rem;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.tl-ghost-label.is-flipped { left: auto; right: 0; }

/* ── Boundary handles ───────────────────────────────────────────────────── */
/* One per edge between two blocks. Starts below the point-marker flags so those
   stay clickable, and only shows itself once the track is hovered — the track
   reads as a picture of the roll until you go to edit it. */
.tl-grips {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 5;
}
.tl-grip {
  position: absolute;
  top: 16px;
  bottom: 0;
  width: 13px;
  padding: 0;
  border: none;
  background: none;
  transform: translateX(-50%);
  pointer-events: auto;
  cursor: ew-resize;
  touch-action: none;
  opacity: 0;
  transition: opacity 0.12s ease;
}
.tl-track:hover .tl-grip,
.tl-grip:focus-visible,
.tl-grip.is-dragging { opacity: 1; }
/* The grab bar itself — narrower than the hit area, which stays finger-sized. */
.tl-grip::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 3px;
  border-radius: 999px;
  background: var(--gold-dark);
  transform: translateX(-50%);
}
.tl-grip:focus-visible {
  outline: 2px solid var(--gold-dark);
  outline-offset: 1px;
  border-radius: 3px;
}
.tl-grip.is-dragging::before { width: 5px; }
/* Keep the resize cursor for the whole drag, even once the pointer has left the
   handle it is dragging. */
.tl-page.is-trimming,
.tl-page.is-trimming * { cursor: ew-resize !important; }

/* ── Shuttle rate + shortcut hints ──────────────────────────────────────── */
/* Sits next to the timecode and reads as part of it, so it takes the same face:
   the UI one, tabular, not the display face. */
.tl-rate {
  font-family: var(--font-ui);
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--gold-dark);
  font-variant-numeric: tabular-nums;
  flex: none;
}
.tl-rate[hidden] { display: none; }
.tl-move-form { display: none; }
.tl-keys {
  margin: 6px 0 0;
  font-size: 0.68rem;
  color: var(--muted);
  line-height: 1.5;
}
.tl-keys kbd {
  display: inline-block;
  min-width: 1.1em;
  margin-right: 1px;
  padding: 0 4px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--card);
  font-family: inherit;
  font-size: 0.95em;
  text-align: center;
  color: var(--ink);
}
/* Below a phone's width the hint line is more clutter than help — every one of
   these has a control on screen anyway. */
@media (max-width: 640px) {
  .tl-keys { display: none; }
}

/* Shared label bits (chips + swatches). */
.tl-swatch {
  width: 8px;
  height: 28px;
  border-radius: 999px;
  flex: none;
}
.tl-row-pos {
  display: block;
  font-weight: 700;
  font-size: 0.9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tl-row-time {
  display: block;
  font-family: var(--font-ui);
  font-variant-numeric: tabular-nums;
  font-size: 0.8rem;
  color: var(--muted);
}

/* --- scrobbler: human-in-the-loop corrections -------------------------- */
/* A reviewed segment gets a gold ring so corrected data is visible. */
.tl-seg.is-corrected { box-shadow: inset 0 0 0 2px var(--gold); }

/* --- scrobbler: blocks worth checking ---------------------------------- */
/* A block the model read from a single frame. These are the narrowest blocks on
   the track by definition, so they're hatched rather than outlined — an edge
   treatment on a 4px sliver is invisible. The chips and the fix panel carry a
   "?" glyph for the same blocks, so the flag never rests on the fill alone. */
.tl-seg.is-flagged {
  background-image: repeating-linear-gradient(
    45deg, rgba(250, 248, 243, 0.7) 0 2px, transparent 2px 5px
  );
}
.tl-chip.is-flagged { border-color: var(--gold-dark); }
.tl-flag {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 16px;
  height: 16px;
  border-radius: 999px;
  background: rgba(200, 169, 110, 0.28);
  color: var(--gold-dark);
  font-size: 0.7rem;
  font-weight: 700;
}
/* Inside the toolbar the fields run along the bar rather than stacking, so the
   whole thing stays one row deep and the video keeps the height. */
.tl-correct {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  flex: 1;
  min-width: 260px;
}
.tl-correct label,
.tl-correct .tl-fix-field {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1;
  min-width: 160px;
  margin: 0;
}
.tl-correct .hint { flex: none; margin: 0; white-space: nowrap; }
.tl-correct .combo { flex: 1; min-width: 0; }
.tl-correct select { flex: 1; min-width: 0; padding: 6px 10px; font-size: 0.9rem; }
.tl-correct .combo-input { padding: 6px 32px 6px 10px; font-size: 0.9rem; }
/* The open list has to sit over the panel below it, not stretch the card. */
.tl-correct .tl-fix-field { position: relative; z-index: 2; }
.tl-saved {
  font-size: 0.8rem;
  color: var(--muted);
  min-width: 56px;
}
.tl-saved.is-saved { color: var(--gold); font-weight: 700; }

/* --- scrobbler: point scoring ------------------------------------------ */
/* Tag panel: one action row per fighter, tapped to award points at the playhead. */
.tl-scoring {
  margin: 0 0 12px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--paper);
}
.tl-scoring-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 8px;
}
.tl-scoring-head form.button_to { margin: 0 0 0 auto; }
.tl-propose-btn {
  padding: 4px 10px;
  border: 1px dashed var(--border);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
}
.tl-propose-btn:hover { border-color: var(--gold-dark); color: var(--gold-dark); }
.tl-score-form { display: flex; flex-direction: column; gap: 8px; }
.tl-score-fighter {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.tl-score-name {
  min-width: 96px;
  font-weight: 800;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  /* Fighter accent, matching their lane; role-based rows leave --fighter unset. */
  color: var(--fighter, var(--ink));
}
.tl-score-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.tl-score-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
}
.tl-score-btn:hover {
  border-color: var(--fighter, var(--muted));
  box-shadow: inset 0 0 0 1px var(--fighter, transparent);
}
.tl-score-pts {
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--fighter, var(--gold-dark));
}
/* Advantages and penalties are their own rows, captioned and quieter than the
   scoring actions — they don't add to the score (bar ADCC's −1 penalties). */
.tl-score-column {
  min-width: 34px;
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.tl-score-btn.is-advantage,
.tl-score-btn.is-penalty {
  padding: 3px 8px;
  font-size: 0.74rem;
  font-weight: 500;
}
.tl-score-btn.is-penalty { border-style: dashed; }
.tl-score-btn.is-advantage .tl-score-pts,
.tl-score-btn.is-penalty .tl-score-pts { color: var(--muted); }
/* Submission-only rulesets: no tagging UI, just why. */
.tl-scoring-note {
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted);
}

/* Markers overlaid on the track — a thin coloured pin with a +N flag. */
.tl-scores {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 4;
}
.tl-score-marker {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  padding: 0;
  border: none;
  background: var(--score, var(--ink));
  transform: translateX(-1px);
  pointer-events: auto;
  cursor: pointer;
}
.tl-score-flag {
  position: absolute;
  top: 1px;
  left: 50%;
  transform: translateX(-50%);
  padding: 0 5px;
  border-radius: 999px;
  background: var(--score, var(--ink));
  color: #fff;
  font-size: 0.6rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1.5;
  white-space: nowrap;
}
/* Advantages and penalties sit lower on the pin so they don't collide with the
   point flags, and read hollow — they aren't part of the score. */
.tl-score-marker.is-advantage .tl-score-flag,
.tl-score-marker.is-penalty .tl-score-flag {
  top: auto;
  bottom: 1px;
  background: var(--paper);
  color: var(--score, var(--ink));
  box-shadow: inset 0 0 0 1px var(--score, var(--ink));
  font-weight: 700;
}
.tl-score-marker.is-penalty { opacity: 0.8; }

/* Proposed (unconfirmed) awards read as ghosts: dashed pin, hollow flag. */
.tl-score-marker.is-proposed {
  background: none;
  border-left: 2px dashed var(--score, var(--ink));
  opacity: 0.75;
}
.tl-score-marker.is-proposed .tl-score-flag {
  background: var(--paper);
  color: var(--score, var(--ink));
  box-shadow: inset 0 0 0 1px var(--score, var(--ink));
}

/* Running tally + removable ledger of every award. */
.tl-scoreboard { margin-top: 12px; }
.tl-tallies { display: flex; gap: 10px; flex-wrap: wrap; }
.tl-tally {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-left: 4px solid var(--fighter, var(--muted));
  border-radius: 8px;
  background: var(--paper);
}
.tl-tally-name {
  font-weight: 700;
  font-size: 0.8rem;
}
.tl-tally-points {
  font-family: var(--font-ui);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  font-size: 1.4rem;
  letter-spacing: -0.02em;
  line-height: 1;
  color: var(--fighter, var(--ink));
}
/* The advantage / penalty columns of the scoreboard: counted, not scored, so
   they read small beside the points. */
.tl-tally-col {
  display: inline-flex;
  align-items: baseline;
  gap: 3px;
  font-family: var(--font-ui);
  font-variant-numeric: tabular-nums;
  font-size: 1rem;
  line-height: 1;
  color: var(--muted);
}
.tl-tally-col-key {
  font-family: inherit;
  font-size: 0.6rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
/* Ahead on the ruleset's own order: points, then advantages, then penalties. */
.tl-tally.is-leading {
  border-color: var(--gold-dark);
  box-shadow: 0 0 0 1px var(--gold-dark);
}
.tl-scoreboard-decision {
  margin: 8px 0 0;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--muted);
}
/* Advantages and penalties in the ledger: present, but not the score. */
.tl-ledger-row.is-advantage .tl-ledger-pts,
.tl-ledger-row.is-penalty .tl-ledger-pts { color: var(--muted); }
.tl-ledger {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.tl-ledger-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
}
.tl-ledger-row .tl-swatch { width: 6px; height: 20px; }
.tl-ledger-time {
  font-family: var(--font-ui);
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  min-width: 56px;
}
.tl-ledger-name { font-weight: 700; min-width: 96px; }
.tl-ledger-label { flex: 1; color: var(--muted); }
.tl-ledger-pts {
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.tl-ledger-del {
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card);
  color: var(--muted);
  font-size: 0.8rem;
  line-height: 1;
  cursor: pointer;
}
.tl-ledger-del:hover { border-color: var(--muted); color: var(--ink); }
.tl-ledger-row form.button_to { margin: 0; }
/* Pending proposals: dimmed row, "proposed" flag, accept (✓) next to dismiss. */
.tl-ledger-row.is-proposed .tl-ledger-name,
.tl-ledger-row.is-proposed .tl-ledger-pts { opacity: 0.65; }
.tl-ledger-flag {
  font-style: normal;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--gold-dark);
}
.tl-ledger-accept {
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card);
  color: var(--gold-dark);
  font-size: 0.8rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}
.tl-ledger-accept:hover { border-color: var(--gold-dark); }
.tl-tally-proposed {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--muted);
}
.tl-scoreboard-empty {
  margin: 8px 0 0;
  font-size: 0.82rem;
  color: var(--muted);
}

.reanalyse-form {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 16px;
}
.reanalyse-form select { width: auto; padding: 8px 10px; font-size: 0.85rem; }

/* --- admin area -------------------------------------------------------- */
.admin-nav {
  display: flex;
  gap: 6px;
  padding: 8px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--paper);
  overflow-x: auto;
}
.admin-nav a {
  padding: 6px 12px;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}
.admin-nav a:hover { color: var(--ink); background: var(--card); }
.admin-user { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.admin-user-body { display: flex; align-items: center; gap: 8px; min-width: 0; }
.admin-user-email { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Corpus curation rows — same shape as the user rows, with room for two buttons. */
.admin-annotation { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.admin-annotation-body { display: flex; align-items: center; gap: 8px; min-width: 0; flex-wrap: wrap; }
.admin-annotation-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
