/* cs2ctl — single-screen control dashboard.
 *
 * Everything you need mid-match is visible at once: no tabs, no page scroll.
 * The viewport is the frame; individual cards scroll internally if they must.
 * Colour is state and nothing else — green live, amber drifted, red broken.
 */

:root {
  --bg:      #080b10;
  --surface: #10141b;
  --raised:  #171c25;
  --sunken:  #0b0e14;
  --line:    #1f2632;
  --line-2:  #2c3543;

  --ink:   #e3e9f2;
  --ink-2: #99a6b8;
  /* Measured, not chosen: #5c6675 gives 3.17:1 against the dark surface, and
     every "dim" label in the panel wears it. 4.6:1 is the smallest step that
     clears WCAG AA for body text. */
  --ink-3: #7b8799;

  --accent: #e8933c;
  --good:   #3fa873;
  --warn:   #cfa62f;
  --bad:    #d15a49;

  --gap: 18px;
  --pad: 20px;
  --topbar-h: 60px;

  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #e8ebf0; --surface: #fff; --raised: #f4f6f9; --sunken: #eef1f5;
    --line: #dce1e9; --line-2: #c5cdda;
    --ink: #121820; --ink-2: #556070; --ink-3: #7e8896;
  }
}

* { box-sizing: border-box; }
/* Form controls do not inherit colour or font by default: an unstyled button
   is black text in a dark theme, which shipped on the moment chips. */
button, select, input, textarea { color: inherit; font-family: inherit; }
a { color: var(--accent); }
a:hover { color: var(--accent); }
html, body { height: 100%; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font: 14px/1.45 var(--sans); -webkit-font-smoothing: antialiased;
}
.mono, .num { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.dim { color: var(--ink-2); }
.muted { color: var(--ink-3); margin: 0; }
.grow { flex: 1; }
code { font-family: var(--mono); font-size: 0.92em; background: var(--sunken); padding: 0.05em 0.3em; border-radius: 4px; }

.i {
  width: 16px; height: 16px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round; vertical-align: -2px;
}
.i-lg { width: 24px; height: 24px; }
.i-sm { width: 11px; height: 11px; stroke-width: 2.6; }
#i-play polygon, #i-skip polygon, #i-pause rect { fill: currentColor; }

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

.topbar {
  display: flex; align-items: center; gap: 1.9rem;
  height: var(--topbar-h); padding: 0 var(--pad);
  background: var(--surface); border-bottom: 1px solid var(--line);
}
.brand {
  display: inline-flex; align-items: center; gap: 0.45rem;
  font-size: 0.8rem; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase;
}
.brand-mark { color: var(--accent); }
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-3); margin-left: -1.35rem; }
.dot.is-up { background: var(--good); box-shadow: 0 0 0 3px color-mix(in srgb, var(--good) 20%, transparent); }
.dot.is-down { background: var(--bad); box-shadow: 0 0 0 3px color-mix(in srgb, var(--bad) 20%, transparent); }

.read { display: grid; gap: 1px; min-width: 0; }
.read b { font-size: 0.56rem; font-weight: 700; letter-spacing: 0.15em; text-transform: uppercase; color: var(--ink-3); }
.read span { font-size: 0.92rem; font-weight: 550; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 16rem; }

.flags { display: flex; gap: 0.35rem; }
.flag {
  font-size: 0.58rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
  padding: 0.18rem 0.45rem; border-radius: 4px; border: 1px solid currentColor;
}
.flag.warn { color: var(--warn); }
.flag.bad { color: var(--bad); }
.flag.rec { color: var(--bad); }
.flag.rec::before { content: "●"; margin-right: 0.3rem; animation: recblink 1.6s steps(1) infinite; }
@keyframes recblink { 50% { opacity: 0.25; } }
.autorec { display: flex; align-items: center; gap: 0.6rem; margin: 0 0 0.85rem; font-size: 0.8rem; }
.autorec .btn.is-on { color: var(--good); border-color: var(--good); }
.tick { font-size: 0.62rem; color: var(--ink-3); }

.iconbtn {
  display: grid; place-items: center; width: 32px; height: 32px;
  color: var(--ink-2); background: none; border: 1px solid var(--line-2);
  border-radius: 8px; cursor: pointer; font-size: 0.85rem;
}
.iconbtn:hover { color: var(--accent); border-color: var(--accent); }

/* ------------------------------------------------------------ dashboard -- */

.dash {
  display: grid;
  gap: var(--gap);
  padding: var(--gap);
  grid-template-columns: minmax(320px, 1fr) minmax(380px, 1.2fr) minmax(300px, 0.9fr);
  /* Console owns the third column top to bottom: command output is many short
     lines, so it wants height far more than width. Maps and Players share the
     middle. */
  grid-template-areas:
    "control maps console"
    "control side console";
  align-items: stretch;
}
.zone-control { grid-area: control; }
.zone-maps    { grid-area: maps; }
.zone-side    { grid-area: side; }
.zone-console { grid-area: console; grid-template-rows: auto minmax(170px, 1fr); }

/* align-content: stretch, not start — a zone that sizes to its content leaves
   a gap under the last card instead of filling the column. */
.zone { display: grid; gap: var(--gap); min-width: 0; align-content: stretch; }

/* The 1fr row is the one that absorbs slack, so pick the card that looks
   least odd when stretched: Quick spaces its four rows out evenly. */
.zone-control { grid-template-rows: auto auto 1fr; }
.zone-side { grid-template-rows: 1fr; }

/* On a real monitor the whole thing is one screen with no page scroll; cards
   scroll internally instead. Below that it stacks and scrolls normally. */
@media (min-width: 1240px) and (min-height: 780px) {
  body { overflow: hidden; }
  .dash {
    height: calc(100vh - var(--topbar-h));
    /* Players needs the height more than Maps does - the map grid is fixed at
         nine tiles, the player list grows. minmax(0,...) so rows may actually
         shrink rather than overflow the viewport. */
    grid-template-rows: auto minmax(0, 1fr);
  }
  .zone { min-height: 0; }
  .card.fill { min-height: 0; }
  .zone-maps { min-height: 0; }
}

.card {
  display: flex; flex-direction: column; min-height: 0; min-width: 0;
  overflow: hidden;   /* a card never spills past its own frame */
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
}
.card-head {
  display: flex; align-items: center; gap: 0.7rem;
  padding: 0.85rem var(--pad); border-bottom: 1px solid var(--line); flex: none;
}
.card-head h2 {
  display: inline-flex; align-items: center; gap: 0.45rem;
  margin: 0; font-size: 0.64rem; font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-2);
}
.card-head .sub { font-size: 0.66rem; color: var(--ink-3); margin-left: auto; }
.card-head .rp-actions { display: inline-flex; align-items: center; gap: 0.35rem; margin-left: 0.7rem; }
.card-head .rp-actions .btn { display: inline-flex; align-items: center; gap: 0.3rem; }
.card-body { padding: var(--pad); min-height: 0; overflow-y: auto; }
.card-body.scroll, .scroll {
  overflow-y: auto;
  background:
    linear-gradient(var(--surface) 33%, transparent) center top,
    linear-gradient(transparent, var(--surface) 66%) center bottom,
    radial-gradient(farthest-side at 50% 0, rgba(0,0,0,0.4), transparent) center top,
    radial-gradient(farthest-side at 50% 100%, rgba(0,0,0,0.4), transparent) center bottom;
  background-repeat: no-repeat;
  background-size: 100% 26px, 100% 26px, 100% 12px, 100% 12px;
  background-attachment: local, local, scroll, scroll;
}
.card.fill .card-body { flex: 1; }

/* ----------------------------------------------------------- stage deck -- */

.stagedeck { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.stage {
  position: relative; overflow: hidden;
  display: grid; justify-items: center; gap: 0.5rem;
  padding: 1.5rem 0.8rem 1.6rem;
  font: inherit; color: var(--ink-2);
  background: var(--surface); border: 1px solid var(--line-2); border-radius: 14px;
  cursor: pointer; transition: border-color 0.14s, color 0.14s, background 0.14s;
}
.stage:hover:not(:disabled) { border-color: var(--accent); color: var(--ink); }
.stage:active:not(:disabled) { transform: translateY(1px); }
.stage:disabled { opacity: 0.5; cursor: progress; }
.stage-name { font-size: 1.05rem; font-weight: 650; color: var(--ink); letter-spacing: -0.01em; }
.stage-state {
  display: inline-flex; align-items: center; gap: 0.28rem;
  font-family: var(--mono); font-size: 0.62rem; font-weight: 700;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3);
}
.stage-bar { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: var(--line); }
.stage-bar i { display: block; height: 100%; background: var(--ink-3); transition: width 0.35s ease-out; }
.stage.is-on {
  color: var(--good); border-color: var(--good);
  background: color-mix(in srgb, var(--good) 12%, var(--surface));
}
.stage.is-on .stage-state { color: var(--good); }
.stage.is-on .stage-bar i { background: var(--good); }
.stage.is-part { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 60%, var(--line-2)); }
.stage.is-part .stage-state { color: var(--warn); }
.stage.is-part .stage-bar i { background: var(--warn); }

.transport {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(92px, 1fr)); gap: 9px;
  padding: 11px; background: var(--sunken);
  border: 1px solid var(--line); border-radius: 14px;
}
.tbtn {
  display: grid; justify-items: center; align-content: center; gap: 0.45rem;
  padding: 1.05rem 0.5rem;
  font: inherit; font-size: 0.8rem; font-weight: 500; line-height: 1.2;
  text-align: center; color: var(--ink);
  background: var(--raised);
  border: 1px solid var(--line-2); border-radius: 11px;
  cursor: pointer; transition: border-color 0.12s, color 0.12s;
}
.tbtn .i { width: 26px; height: 26px; color: var(--ink-2); transition: color 0.12s; }
.tbtn:hover:not(:disabled) .i { color: var(--accent); }
.tbtn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.tbtn:active:not(:disabled) { transform: translateY(1px); }
.tbtn:disabled { opacity: 0.4; cursor: progress; }

/* Quick actions as tiles, in the same visual language as the stage deck —
   icon over label, real hit area — rather than a row of small buttons lost in
   a stretched card. */
.quick {
  padding: var(--pad); gap: 0; overflow-y: auto; min-height: 0;
  background:
    linear-gradient(var(--surface) 33%, transparent) center top,
    linear-gradient(transparent, var(--surface) 66%) center bottom,
    radial-gradient(farthest-side at 50% 0, rgba(0,0,0,0.4), transparent) center top,
    radial-gradient(farthest-side at 50% 100%, rgba(0,0,0,0.4), transparent) center bottom;
  background-repeat: no-repeat;
  background-size: 100% 26px, 100% 26px, 100% 12px, 100% 12px;
  background-attachment: local, local, scroll, scroll;
  background-color: var(--surface);
}
.qgroup {
  display: flex; flex-direction: column;
  flex: 1 0 auto;
  padding: 0.6rem 0; border-bottom: 1px solid var(--line);
}
.qgroup:first-child { padding-top: 0; }
.qgroup:last-child { padding-bottom: 0; border-bottom: none; }
.qlabel {
  display: block; margin-bottom: 0.5rem;
  font-size: 0.56rem; font-weight: 700; letter-spacing: 0.15em;
  text-transform: uppercase; color: var(--ink-3);
}
.qtiles { display: grid; flex: 1; grid-template-columns: repeat(var(--cols, 4), minmax(0, 1fr)); grid-auto-rows: minmax(56px, 1fr); gap: 8px; }
.qtile {
  display: grid; justify-items: center; align-content: center; gap: 0.35rem;
  min-width: 0; overflow-wrap: anywhere; hyphens: auto;
  padding: 0.5rem 0.4rem;
  font: inherit; font-size: 0.78rem; font-weight: 500; line-height: 1.2;
  text-align: center; color: var(--ink);
  background: var(--raised); border: 1px solid var(--line-2); border-radius: 11px;
  cursor: pointer; transition: border-color 0.12s, color 0.12s, background 0.12s;
}
.qtile .i { color: var(--ink-3); transition: color 0.12s; }
.qstate {
  font-family: var(--mono); font-size: 0.56rem; font-weight: 700;
  letter-spacing: 0.12em; color: var(--ink-3);
}
.qtoggle.is-on { border-color: color-mix(in srgb, var(--good) 55%, var(--line-2)); }
.qtoggle.is-on .i, .qtoggle.is-on .qstate { color: var(--good); }
.qtile:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.qtile:hover:not(:disabled) .i { color: var(--accent); }
.qtile:active:not(:disabled) { transform: translateY(1px); }
.qtile:disabled { opacity: 0.4; cursor: progress; }


.btn {
  display: inline-flex; align-items: center; gap: 0.35rem;
  font: inherit; font-size: 0.8rem;
  color: var(--ink); background: var(--raised);
  border: 1px solid var(--line-2); border-radius: 8px;
  padding: 0.36rem 0.7rem; cursor: pointer; white-space: nowrap;
  transition: border-color 0.12s, color 0.12s;
}
.btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: 0.4; cursor: progress; }
.btn-primary { border-color: var(--accent); color: var(--accent); }
.btn-xs { padding: 0.08rem 0.42rem; font-size: 0.7rem; border-radius: 5px; }
.btn-danger:hover:not(:disabled) { border-color: var(--bad); color: var(--bad); }

.linkbtn {
  border: none; background: none; padding: 0;
  font: inherit; font-size: 0.68rem; color: var(--ink-3);
  cursor: pointer; text-decoration: underline;
}
.linkbtn:hover { color: var(--accent); }
/* Defined after :hover on purpose: same specificity, so the danger colour wins
   in both states and the link never flashes accent-blue on the way to being
   pressed. Weight carries the hover instead. */
.linkbtn.is-danger { color: var(--bad); }
.linkbtn.is-danger:hover { color: var(--bad); font-weight: 700; }

/* ------------------------------------------------------------ map tiles -- */

/* The badge IS the control. No card, no frame, no scrim — the shaped artwork
   sits on the panel background and reacts directly. Upstream ships these as
   512px RGBA with real transparency, so the silhouette does the work. */

.mapgrid {
  display: grid; align-content: center;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px 12px;
}
.mapgrid .is-extra { display: none; }
.mapgrid.is-open .maptile.is-extra { display: grid; }
.mapgrid.is-open .mapgroup.is-extra { display: block; }
.mapmore {
  display: block; width: 100%; margin-top: 0.9rem; padding: 0.4rem;
  font: inherit; font-size: 0.68rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3); background: none; border: 1px dashed var(--line-2); border-radius: 8px; cursor: pointer;
}
.mapmore:hover { color: var(--ink-2); border-color: var(--ink-3); }
.mapmore::before { content: "+ "; }
.is-open + .mapmore::before { content: "− "; }
.mapgroup {
  grid-column: 1 / -1; margin: 0.5rem 0 -0.2rem;
  font-size: 0.6rem; font-weight: 700; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-3);
}
.maptile {
  display: grid; justify-items: center;
  gap: 0.35rem; padding: 0.2rem;
  background: none; border: none; cursor: pointer;
}
/* Absolute inset rather than max-height: percentage max-heights would not
   clamp the badge against the grid area, so a 240px image kept forcing the
   row open. inset:0 gives a definite box and object-fit does the fitting. */
.mt-art {
  position: relative;
  width: 100%; max-width: 104px; aspect-ratio: 1;
}
.maptile img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  /* Dimmed and drained until you go near it, so the live map and whatever the
     cursor is over are the only things holding colour. */
  filter: grayscale(0.55) brightness(0.62) contrast(0.95);
  transition: filter 0.18s, transform 0.18s;
}
.maptile:hover img,
.maptile:focus-visible img {
  filter: none;
  transform: scale(1.08);
}
.maptile:active img { transform: scale(1.02); }

.mt-name {
  font-size: 0.78rem; font-weight: 550; color: var(--ink-3);
  transition: color 0.15s;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.maptile:hover .mt-name { color: var(--ink); }

.mt-key {
  position: absolute; z-index: 1; top: 0; right: 0;
  font-size: 0.58rem; font-weight: 600; color: var(--ink-3);
  border: 1px solid var(--line-2); border-radius: 4px; padding: 0 0.26rem;
  opacity: 0; transition: opacity 0.15s;
}
.maptile:hover .mt-key { opacity: 1; }

.mt-live { display: none; }

/* Live map: full colour plus a soft glow that follows the badge outline —
   drop-shadow works on the alpha channel, which box-shadow could not do. */
.maptile.is-now img {
  filter: drop-shadow(0 0 9px color-mix(in srgb, var(--good) 55%, transparent))
          drop-shadow(0 0 22px color-mix(in srgb, var(--good) 22%, transparent));
}
.maptile.is-now .mt-name { color: var(--good); font-weight: 650; }
.maptile.is-now .mt-live {
  display: inline-flex; align-items: center; gap: 0.22rem;
  position: absolute; z-index: 1; top: 0; left: 0;
  font-size: 0.54rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase;
  color: #06120c; background: var(--good); border-radius: 4px; padding: 0.1rem 0.34rem;
}

/* --------------------------------------------------------------- tables -- */

.alert {
  display: flex; align-items: flex-start; gap: 0.5rem;
  margin: 0 0 0.85rem; padding: 0.6rem 0.75rem;
  border: 1px solid; border-radius: 8px; font-size: 0.8rem;
}
.alert .i { margin-top: 1px; flex: none; }
/* Wrap the message in a span: as bare text with a <code> in it, flex turns
   each fragment into its own column and the sentence reads top-to-bottom. */
.alert > span { flex: 1 1 auto; min-width: 0; }
.alert-warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, transparent); background: color-mix(in srgb, var(--warn) 9%, transparent); }
.alert-bad { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 45%, transparent); background: color-mix(in srgb, var(--bad) 9%, transparent); }

.tbl { width: 100%; border-collapse: collapse; font-size: 0.84rem; }
.tbl th {
  position: sticky; top: 0; background: var(--surface);
  text-align: left; font-size: 0.55rem; font-weight: 700;
  letter-spacing: 0.15em; text-transform: uppercase; color: var(--ink-3);
  padding: 0 0.65rem 0.4rem 0; border-bottom: 1px solid var(--line);
}
.tbl td { padding: 0.44rem 0.65rem 0.44rem 0; border-bottom: 1px solid var(--line); }
.tbl tr:last-child td { border-bottom: none; }
.tbl .num, .tbl th.num { text-align: right; padding-right: 1.1rem; }
.tbl .name { font-weight: 500; }
.tbl tr.is-bot .name { font-weight: 400; color: var(--ink-2); }
.tbl .hot { color: var(--warn); }
.tbl .acts { text-align: right; white-space: nowrap; }
.tbl .acts .btn + .btn { margin-left: 0.28rem; }
.chip {
  font-size: 0.55rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-3); border: 1px solid var(--line-2); border-radius: 4px; padding: 0 0.26rem;
}

/* -------------------------------------------------------------- console -- */

#cmdform { display: flex; gap: 0.45rem; margin-bottom: 0.8rem; }
#cmdinput {
  flex: 1; min-width: 0; font: inherit; font-family: var(--mono); font-size: 0.82rem;
  color: var(--ink); background: var(--sunken);
  border: 1px solid var(--line-2); border-radius: 8px; padding: 0.42rem 0.7rem;
}
#cmdinput:focus { outline: none; border-color: var(--accent); }

.zone-console .card-body { display: flex; flex-direction: column; min-height: 0; }
#console { flex: 1; min-height: 0; }

.log { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.45rem; }
.log-entry { border-left: 2px solid var(--line-2); padding-left: 0.7rem; }
.log-entry.is-fail { border-left-color: var(--bad); }
.log-head { display: flex; gap: 0.8rem; align-items: baseline; justify-content: space-between; }
.log-cmd { font-family: var(--mono); font-size: 0.78rem; background: none; padding: 0; }
.log-meta { font-size: 0.62rem; color: var(--ink-3); white-space: nowrap; }
.log-ms { margin-left: 0.5rem; }
.log-out {
  margin: 0.3rem 0 0; padding: 0.42rem 0.6rem;
  background: var(--sunken); border-radius: 6px;
  font-family: var(--mono); font-size: 0.71rem; line-height: 1.5;
  white-space: pre-wrap; word-break: break-word;
  max-height: 14rem; overflow-y: auto; color: var(--ink-2);
}
.log-entry.is-fail .log-out { color: var(--bad); }

/* --------------------------------------------------------------- drawer -- */

.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 30;
  width: min(560px, 94vw);
  display: flex; flex-direction: column;
  background: var(--surface); border-left: 1px solid var(--line-2);
  box-shadow: -20px 0 50px rgba(0,0,0,0.4);
  transform: translateX(100%);
  visibility: hidden;
  transition: transform 0.22s ease-out, visibility 0s linear 0.22s;
}
.drawer.is-open {
  transform: none;
  visibility: visible;
  transition: transform 0.22s ease-out, visibility 0s;
}
.drawer-head {
  display: flex; align-items: center; gap: 0.7rem;
  padding: 0.9rem var(--pad); border-bottom: 1px solid var(--line); flex: none;
}
.drawer-head h2 {
  display: inline-flex; align-items: center; gap: 0.45rem;
  margin: 0; font-size: 0.64rem; font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-2);
}
.drawer-head .sub { font-size: 0.66rem; color: var(--ink-3); }
/* :first-of-type, not every .linkbtn — with two of them a bare `margin-left:
   auto` on both splits the free space and strands one in the middle of the
   header. Only the first should push. */
.drawer-head .linkbtn:first-of-type { margin-left: auto; }
.drawer-body { padding: var(--pad); overflow-y: auto; flex: 1; }

.scrim {
  position: fixed; inset: 0; z-index: 25;
  background: rgba(0,0,0,0.5); opacity: 0; pointer-events: none;
  transition: opacity 0.2s;
}
.scrim.is-open { opacity: 1; pointer-events: auto; }

.cvargrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(228px, 1fr)); gap: 1.4rem; }
.cvargroup h3 {
  margin: 0 0 0.45rem; font-size: 0.58rem; font-weight: 800;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--accent);
}
.cvar {
  display: grid; grid-template-columns: minmax(0, 1fr) 5.2rem; align-items: center; gap: 0.6rem;
  padding: 0.3rem 0; border-bottom: 1px solid var(--line);
}
.cvar:last-child { border-bottom: none; }
.cvar label { font-family: var(--mono); font-size: 0.73rem; color: var(--ink-2); cursor: pointer; overflow-wrap: anywhere; }
.cvar input {
  font-family: var(--mono); font-size: 0.75rem; text-align: right; width: 100%;
  color: var(--ink); background: var(--sunken);
  border: 1px solid var(--line-2); border-radius: 5px; padding: 0.16rem 0.4rem;
}
.cvar input:focus { outline: none; border-color: var(--accent); }
.cvar input.is-unknown { color: var(--ink-3); }

/* ---------------------------------------------------------------- toast -- */

#toast {
  position: fixed; left: 50%; bottom: 1.4rem; transform: translate(-50%, 1.6rem);
  background: var(--raised); border: 1px solid var(--line-2);
  border-left: 3px solid var(--good); border-radius: 9px;
  padding: 0.55rem 0.95rem; font-size: 0.82rem;
  box-shadow: 0 10px 30px rgba(0,0,0,0.45);
  opacity: 0; pointer-events: none; z-index: 40;
  transition: opacity 0.15s, transform 0.15s;
  max-width: min(90vw, 40rem);
}
#toast.is-show { opacity: 1; transform: translate(-50%, 0); }
#toast.is-bad { border-left-color: var(--bad); }

/* -------------------------------------------------------------- narrow -- */

@media (max-width: 1239px) {
  .dash {
    grid-template-columns: 1fr 1fr;
    grid-template-areas: "control console" "maps console" "side console";
  }
  .mapgrid { grid-auto-rows: minmax(120px, auto); }
}
@media (max-width: 820px) {
  .dash {
    grid-template-columns: 1fr; padding: 12px; gap: 12px;
    grid-template-areas: "control" "maps" "side" "console";
  }
  .topbar { height: auto; flex-wrap: wrap; gap: 0.7rem 1.2rem; padding: 0.7rem 1rem; }
  .tick { display: none; }
  .stagedeck, .transport { grid-template-columns: repeat(2, 1fr); }
}

@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }

/* -------------------------------------------------------------- access -- */

.acc { display: grid; gap: 0.55rem; }
.accrow { display: grid; grid-template-columns: 5.2rem minmax(0, 1fr) auto; align-items: center; gap: 0.6rem; }
.acckey { font-size: 0.56rem; font-weight: 700; letter-spacing: 0.15em; text-transform: uppercase; color: var(--ink-3); }
.accrow input {
  width: 100%; min-width: 0; font: inherit; font-family: var(--mono); font-size: 0.76rem;
  color: var(--ink); background: var(--sunken);
  border: 1px solid var(--line-2); border-radius: 6px; padding: 0.28rem 0.5rem;
}
.accrow input:focus { outline: none; border-color: var(--accent); }
.accstate {
  font-size: 0.58rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
  padding: 0.16rem 0.45rem; border-radius: 4px; border: 1px solid currentColor;
}
.accstate.on { color: var(--good); }
.accstate.off { color: var(--ink-3); }
.btn-on { color: var(--good); border-color: var(--good); }
.acchint { margin: 0.75rem 0 0; font-size: 0.68rem; color: var(--ink-3); line-height: 1.5; }

.bans { margin-top: 0.9rem; }
.banhead { display: flex; align-items: center; gap: 0.5rem; }
.banhead .linkbtn { margin-left: auto; }
.bancount { font-size: 0.62rem; color: var(--ink-3); }
/* Capped and scrolled rather than left to grow: this card shares a column with
   Players, which is the one that should get the leftover height. */
.banlist { display: grid; gap: 0.3rem; margin-top: 0.45rem; max-height: 9rem; overflow-y: auto; }
.banrow { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: 0.5rem; }
.banid { font-size: 0.72rem; overflow-x: auto; white-space: nowrap; }
.banmeta { font-size: 0.6rem; color: var(--ink-3); white-space: nowrap; }

.connect {
  display: flex; align-items: center; gap: 0.5rem; min-width: 0;
  margin-top: 0.85rem; padding: 0.5rem 0.6rem;
  background: var(--sunken); border: 1px solid var(--line-2); border-radius: 8px;
}
.connect-cmd {
  flex: 1; min-width: 0; background: none; padding: 0;
  font-size: 0.74rem; color: var(--ink-2);
  overflow-x: auto; white-space: nowrap;
}
.acchint.warn { display: flex; gap: 0.4rem; align-items: flex-start; color: var(--warn); margin-top: 0.5rem; }
.acchint.warn .i { margin-top: 2px; flex: none; }
/* The message is one flex ITEM, never loose text: a flex row treats each run
   of text either side of a <code> as its own item and lays the sentence out
   in columns. Same trap as .alert > span. */
.acchint.warn > span { flex: 1 1 auto; min-width: 0; }

/* Currently-running config. Same green as the live stage and an ON toggle:
   in this panel green always means "this is what the server is doing". */
.qdot { display: none; }
.qtile.is-active {
  border-color: var(--good);
  background: color-mix(in srgb, var(--good) 10%, var(--raised));
}
.qtile.is-active .i { color: var(--good); }
.qtile.is-active > span:not(.qdot) { color: var(--good); font-weight: 600; }
.qtile.is-active .qdot {
  display: block; position: absolute; top: 6px; right: 6px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--good);
}
.qtile { position: relative; }

/* ---------------------------------------------------------- live scoreboard */

.scoreline {
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: 0.15rem 1rem;
  margin: 0 0 1rem; padding: 0.75rem 1.1rem 0.6rem;
  background: var(--sunken); border: 1px solid var(--line); border-radius: 11px;
}
/* names hug the score from both sides; score is the true centre, and the
   round/map meta sits in a second row directly beneath it */
.scoreline .scorenameform:first-of-type { justify-self: end; }
.scoreline .scorenameform:last-of-type { justify-self: start; }
.scoremeta { grid-column: 2; grid-row: 2; justify-self: center; white-space: nowrap; }
/* The score is the headline of the whole card — let it read from across the
   room. The card body scrolls, so the extra height spends the card's slack
   rather than squeezing the table. */
.scorenum { font-size: 2.1rem; font-weight: 800; letter-spacing: 0.05em; line-height: 1; }
.scoremeta { font-size: 0.66rem; color: var(--ink-3); }
.scoreline.is-live .scorenum { color: var(--ink); }
.scoreline:not(.is-live) .scorenum { color: var(--ink-2); }

.feed { list-style: none; margin: 0.9rem 0 0; padding: 0.6rem 0 0; border-top: 1px solid var(--line); }
.feeditem {
  display: flex; gap: 0.5rem; align-items: baseline;
  font-size: 0.73rem; padding: 0.1rem 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.feedw { font-size: 0.66rem; color: var(--accent); }

/* Inline team rename on the score strip: looks like a label, edits like a field. */
.scorenameform { display: inline-flex; min-width: 0; }
.scorename {
  font: inherit; font-size: 0.72rem; font-weight: 800;
  letter-spacing: 0.16em; text-transform: uppercase;
  width: 9.5rem; min-width: 0;
  background: transparent; border: 1px solid transparent; border-radius: 6px;
  padding: 0.2rem 0.4rem;
  overflow: hidden; text-overflow: ellipsis;
  transition: border-color 0.12s, background 0.12s;
}
.scorename.ct { color: var(--team-ct); text-align: right; }
.scorename.t  { color: var(--team-t); text-align: left; }
.scorename:hover { border-color: var(--line-2); }
.scorename:focus { outline: none; border-color: var(--accent); background: var(--surface); }
.scorename::placeholder { color: inherit; opacity: 0.9; }

/* ------------------------------------------------------------ recent games */
.recent { margin-top: 0.9rem; padding-top: 0.6rem; border-top: 1px solid var(--line); }
.recent-h {
  display: block; margin-bottom: 0.35rem;
  font-size: 0.56rem; font-weight: 700; letter-spacing: 0.15em;
  text-transform: uppercase; color: var(--ink-3);
}
.recent-row {
  display: flex; gap: 0.7rem; align-items: baseline;
  font-size: 0.76rem; padding: 0.14rem 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.recent-teams { overflow: hidden; text-overflow: ellipsis; }

/* ------------------------------------------------- team-grouped scoreboard */
/* Team identity colours validated (dataviz six checks, dark surface):
   CT #4c93da / T #c28428 — CVD dE 24, contrast >=3:1. Names always label
   them, so identity is never colour-alone. */
:root { --team-ct: #4c93da; --team-t: #c28428; }

.squadhead td { padding: 0.55rem 0 0.25rem; border-bottom: 1px solid var(--line); }
.squadname {
  font-size: 0.62rem; font-weight: 800; letter-spacing: 0.15em;
  text-transform: uppercase; color: var(--ink-3);
  border-left: 3px solid var(--line-2); padding-left: 0.5rem;
}
.squadname.ct { color: var(--team-ct); border-left-color: var(--team-ct); }
.squadname.t  { color: var(--team-t);  border-left-color: var(--team-t); }

.crown { color: var(--accent); margin-right: 0.3rem; font-size: 0.72rem; }

/* ADR: magnitude, so the bar is a neutral tint (never a team hue) behind an
   ink number — text wears text tokens, the bar carries the size. */
.adrwrap { position: relative; display: inline-block; width: 3.4rem; text-align: right; }
.adrwrap i {
  position: absolute; right: 0; top: 15%; height: 70%;
  background: color-mix(in srgb, var(--ink-3) 26%, transparent);
  border-radius: 3px;
}
.adrwrap em { position: relative; font-style: normal; padding-right: 0.25rem; }

/* -------------------------------------------------------------- history page */
body.page { overflow: auto !important; }
.histwrap { max-width: 880px; margin: 0 auto; padding: 1.4rem; display: grid; gap: 1.1rem; }
a.brand, a.btn, a.linkbtn { text-decoration: none; }
a.brand { color: var(--ink); }
a.brand:visited { color: var(--ink); }
a.linkbtn { text-decoration: underline; }
.histhead { gap: 0.8rem; }
.histbadge { border-radius: 6px; }
.histscore { font-size: 1.15rem; font-weight: 800; }
.histteams { font-size: 0.8rem; color: var(--ink-2); }
.histteam { font-weight: 700; }
.histteam.ct { color: var(--team-ct); }
.histteam.t { color: var(--team-t); }
.histhead .sub { margin-left: auto; }
.recent { position: relative; }
.recent-all { position: absolute; top: 0.6rem; right: 0; }

/* ------------------------------------------------------------ stats pages -- */
.inline { display: inline; }
.importcell { white-space: nowrap; }
.importcell form { display: inline; margin-left: 0.4rem; }
.statgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr)); gap: 0.6rem; margin-bottom: 1rem; }
.stat { border: 1px solid var(--line-2); border-radius: 8px; padding: 0.55rem 0.7rem; }
.stat b { display: block; font-size: 1.25rem; font-weight: 650; }
.stat span { font-size: 0.66rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3); }
.tbl a.plink { color: inherit; text-decoration: none; border-bottom: 1px dotted var(--line-2); }
.tbl a.plink:hover { border-bottom-color: currentColor; }
.tbl td.rating { font-weight: 650; }
.tbl td.num, .tbl th.num { white-space: nowrap; }
/* Above / below, on the pair that passes CVD - not the panel's state green
   and red, which a deutan reader cannot tell apart (ΔE 5.8, measured). */
.rtg-hi { color: var(--hi); } .rtg-lo { color: var(--lo); }
.teamhead td { padding-top: 0.7rem; font-size: 0.66rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3); }
.roundstrip { display: flex; flex-wrap: wrap; gap: 3px; margin: 0.2rem 0 0.8rem; }
.roundstrip i { display: block; width: 14px; height: 14px; border-radius: 3px; background: var(--line-2); }
.roundstrip i.a { background: color-mix(in srgb, var(--good) 70%, transparent); }
.roundstrip i.b { background: color-mix(in srgb, var(--warn) 70%, transparent); }
.roundstrip i.half { margin-left: 8px; }

/* ------------------------------------------------------- steam assets -- */
.av { position: relative; display: inline-block; width: 22px; height: 22px; vertical-align: -6px; margin-right: 0.5rem; flex: none; }
.av .avpic { display: block; width: 100%; height: 100%; border-radius: 4px; object-fit: cover; background: var(--line-2); }
.av .avframe { position: absolute; left: -11%; top: -11%; width: 122%; height: 122%; pointer-events: none; }
.av.lg { width: 92px; height: 92px; margin: 0; vertical-align: baseline; }
.av.lg .avpic { border-radius: 8px; }
.av.av-empty { background: var(--line-2); border-radius: 8px; }
.tbl a.plink { display: inline-flex; align-items: center; }
.hero {
  position: relative; overflow: hidden; isolation: isolate;
  display: flex; align-items: flex-end; gap: 1.1rem; min-height: 168px;
  padding: 1.2rem 1.25rem; color: #fff; background: #171a21;
}
.hero .heroplate { position: absolute; inset: 0; z-index: -2; width: 100%; height: 100%; object-fit: cover; }
.hero .heroblur { filter: blur(28px) saturate(1.3); transform: scale(1.3); opacity: 0.7; }
.hero::before { content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg, rgba(10,12,18,0.05) 20%, rgba(10,12,18,0.78) 100%); }
.hero h1 { margin: 0 0 0.15rem; font-size: 1.55rem; font-weight: 700; letter-spacing: -0.01em; text-shadow: 0 1px 8px rgba(0,0,0,0.65); }
.hero .sub { color: rgba(255,255,255,0.82); text-shadow: 0 1px 4px rgba(0,0,0,0.6); }
.hero .herotext { flex: 1; min-width: 0; }
.hero .herolink { color: #fff; border-color: rgba(255,255,255,0.4); background: rgba(0,0,0,0.25); }

/* Stats tables are wide; give them room, and scroll rather than clip on a phone. */
/* `hidden` loses to any author `display` — .rp-film sets display:flex, so an
   empty film box showed at page load. */
[hidden] { display: none !important; }

.histwrap.wide { max-width: 1440px; }
/* The replay is the only thing on the page that gains from a big monitor — a
   table past 1440px just gets harder to read — so it alone breaks out of the
   column, centred, up to 2560px (1760 was chosen when 1920 was the biggest
   screen in the room). The viewport term leaves room for the
   scrollbar; without it the bleed is what causes the horizontal scroll. */
#replay { --bleed: min(2560px, calc(100vw - 3rem)); width: max(100%, var(--bleed)); margin-left: calc((100% - max(100%, var(--bleed))) / 2); }
.wide .card-body { overflow-x: auto; }
.wide .tbl { min-width: 720px; }
.tbl tr.is-fragment td { color: var(--ink-3); }
.tbl tr.is-fragment td b { font-weight: 500; }

/* --------------------------------------------------------- post-match -- */
.stat b .av { vertical-align: -5px; }
.progress { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 0.4rem; }
.progress .pr { display: flex; flex-direction: column; align-items: center; gap: 2px; width: 30px; }
.progress .pr i { display: block; width: 100%; height: 10px; border-radius: 3px; background: var(--line-2); }
.progress .pr.a i { background: color-mix(in srgb, var(--good) 70%, transparent); }
.progress .pr.b i { background: color-mix(in srgb, var(--warn) 70%, transparent); }
.progress .pr em { font-style: normal; font-size: 0.6rem; color: var(--ink-3); font-family: var(--mono, monospace); }
.progress .pr.half { margin-left: 12px; }
.moments { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr)); gap: 0.35rem 1rem; }
.moment { display: flex; align-items: center; gap: 0.55rem; font-size: 0.86rem; padding: 0.3rem 0; border-bottom: 1px solid var(--line-2); }
.moment .mkind { font-size: 0.62rem; letter-spacing: 0.1em; padding: 0.15rem 0.4rem; border-radius: 4px; border: 1px solid currentColor; }
.moment.ace .mkind, .moment.clutch .mkind { color: var(--accent); }
.moment .mwho { display: inline-flex; align-items: center; font-weight: 550; }
.legend::before { content: "●"; margin-right: 0.25rem; }
.legend.ct { color: var(--good); } .legend.t { color: var(--warn); }
.duels th .rot { display: inline-block; max-width: 6rem; overflow: hidden; text-overflow: ellipsis; }
.duel.won b { color: var(--good); }
.duel.lost b { color: var(--bad); }
.stat b { overflow-wrap: anywhere; }
.chip-a { color: var(--good); border-color: var(--good); }
.chip-b { color: var(--warn); border-color: var(--warn); }
.rounds .feedcell { white-space: normal; }
.fk { display: inline-flex; align-items: center; font-size: 0.74rem; margin: 0 0.45rem 0.15rem 0; white-space: nowrap; }
.fk i { font-style: normal; margin: 0 0.15rem; color: var(--ink-3); }
.fk.a { color: var(--good); } .fk.b { color: var(--warn); }
.pistolrow td { background: color-mix(in srgb, var(--accent) 5%, transparent); }

/* ------------------------------------------------------------- replay -- */
/* moment chips: the real entry point now */
/* One row, filling the width: columns of equal width that stretch when there
   are few moments and scroll when there are many. Content-sized chips left a
   third of a 2500px row empty. */
.rp-moments {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(11rem, 1fr);
  gap: 7px; margin-bottom: 0.8rem; overflow-x: auto; padding-bottom: 5px; scrollbar-width: thin;
}
.rp-moment-art { position: relative; display: block; aspect-ratio: 16/9; border-radius: 7px;
  overflow: hidden; background: #000; margin-bottom: 0.2rem; }
.rp-moment-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.rp-moment-angles { position: absolute; right: 4px; bottom: 4px; padding: 0.05rem 0.3rem;
  border-radius: 999px; background: rgba(8,11,16,0.75); color: #fff; font-size: 0.58rem; }
.rp-moment {
  display: flex; flex-direction: column; gap: 1px; align-items: stretch;
  padding: 0.35rem 0.4rem 0.45rem; border: 1px solid var(--line); border-radius: 10px;
  background: var(--raised); cursor: pointer; font: inherit; text-align: left; color: inherit;
  min-width: 0; transition: border-color 0.12s, transform 0.12s;
}
.rp-moment:active { transform: translateY(1px); }
.rp-moment:hover { border-color: var(--ink-3); }
.rp-moment.is-on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.rp-moment b { font-size: 0.74rem; font-weight: 650; padding: 0 0.2rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rp-moment-who { font-size: 0.74rem; color: var(--ink-2); }
.rp-moment i { font-style: normal; font-size: 0.62rem; color: var(--ink-3);
  font-family: var(--mono, monospace); padding: 0 0.2rem; }

/* The angle chips in the clips theatre. (The replay's own camera picker is
   the bank in .rp-cams - these two were the same component until ten angles
   made a row of pills the wrong answer for one of them.) */
.povchip {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.22rem 0.5rem 0.22rem 0.28rem;
  border: 1px solid var(--line-2); border-radius: 999px; background: none; cursor: pointer;
  font: inherit; font-size: 0.76rem; color: var(--ink-2);
}
.povchip:hover { border-color: var(--ink-3); color: var(--ink); }
.povchip.is-on { border-color: var(--accent); color: var(--ink); font-weight: 650; }
.rp-star { color: var(--accent); }

/* The round rail is a TIMELINE, not a numbered keypad: who won each round,
   how they won it and the score it made. 24 numbers in a row used a quarter
   of the width and said only "there were 24 rounds". */
.rp-rounds { display: flex; gap: 3px; margin-bottom: 0.9rem; }
.rp-round {
  flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 0.3rem 0.1rem 0.25rem; border: 1px solid var(--line); border-radius: 7px;
  background: var(--raised); color: var(--ink-2); cursor: pointer; font: inherit;
  transition: border-color 0.12s, background 0.12s;
}
.rp-round:hover { border-color: var(--ink-3); color: var(--ink); }
.rr-n { font: 650 0.68rem/1 var(--mono); display: inline-flex; align-items: flex-start; gap: 1px; }
/* NOT a dot: the dot on this cell already means "has footage", and two accent
   dots on one 46px tile is a riddle. In the flow, not hung off the side of
   the number, so it cannot spill into the next cell. */
.rr-pistol { font-style: normal; color: var(--ink-3); font-size: 0.52rem; line-height: 1.1; }
.rr-bar { display: block; width: 100%; height: 3px; border-radius: 2px; background: var(--line-2); }
.rp-round.a .rr-bar { background: var(--good); }
.rp-round.b .rr-bar { background: var(--warn); }
.rr-how .i { width: 12px; height: 12px; }
.rp-round.a .rr-how { color: color-mix(in srgb, var(--good) 75%, var(--ink-2)); }
.rp-round.b .rr-how { color: color-mix(in srgb, var(--warn) 75%, var(--ink-2)); }
.rr-score { font-size: 0.6rem; color: var(--ink-3); }
.rp-round.half { margin-left: 14px; }
.rp-round.has-film { position: relative; }
.rp-round.has-film::after {
  content: ""; position: absolute; right: 3px; top: 3px; width: 5px; height: 5px;
  border-radius: 50%; background: var(--accent);
}
.rp-round.is-on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--raised)); color: var(--ink); }
.rp-round.is-on .rr-score { color: var(--ink-2); }
/* Twenty-four cells with an icon and a score need room; below that they drop
   back to just the number. */
@media (max-width: 900px) {
  .rp-round { padding: 0.25rem 0.1rem; }
  .rr-how, .rr-score { display: none; }
}
/* Film first: it is the reason to open the page, so it gets the room and the
   map sits alongside as the companion. A round with no footage collapses the
   film column and the map takes it all back. */
/* 1.78 : 1 is not a taste call — the film is 16:9 and the map is square, so at
   that ratio they are the same HEIGHT and the stage has one bottom edge
   instead of 300px of dead space beside the film. */
/* TWO columns, not three. The map and the feed shared a row with the film and
   both ended halfway down it, so the stage had a column of nothing in it and
   the film was narrower than it needed to be. They share a rail now, and the
   rail stretches to the film's height. */
.rp-stage { display: grid; grid-template-columns: minmax(0, 1fr) clamp(25rem, calc(34% - 100px), 46rem);
  gap: 0.9rem; align-items: stretch; }
.rp-stage.no-film { grid-template-columns: minmax(0, 1fr) clamp(25rem, calc(34% - 100px), 46rem); }
.rp-stage.no-film .rp-film, .rp-stage.no-film .rp-cams { display: none; }
.rp-stage.no-film .rp-film-stage { display: grid; place-items: center; }
.rp-stage.no-film .rp-film-stage::after {
  content: "no footage for this round — the map still plays";
  color: var(--ink-3); font-size: 0.8rem;
}
@media (max-width: 1150px) {
  .rp-stage, .rp-stage.no-film { grid-template-columns: minmax(0, 1fr); }
}
.rp-film-stage { min-width: 0; display: flex; flex-direction: column; gap: 0.6rem; }
/* Rows, not a stack: the feed takes whatever is left after the map and the
   legend, and `minmax(0, 1fr)` stops it pushing the whole stage taller than
   the film - which is what left a gap under the camera bank. */
.rp-rail {
  display: grid; grid-template-rows: auto auto minmax(0, 1fr); gap: 0.55rem; min-width: 0;
  /* The FILM sets the height of the stage, never the feed: a definite height
     of zero makes this column contribute nothing to the row, and min-height
     stretches it back to whatever the film column came out at. Without it a
     long kill feed pushed the stage 127px taller than the film and left the
     gap under the camera bank. */
  height: 0; min-height: 100%;
}
/* The legend is not decoration: the marks are circles, and a circle means
   nothing until something says what it is. Each swatch is the real mark. */
.rp-legend {
  display: flex; flex-wrap: wrap; gap: 0.15rem 0.7rem; margin: 0; padding: 0;
  list-style: none; font-size: 0.66rem; color: var(--ink-3);
}
.rp-legend li { display: inline-flex; align-items: center; gap: 0.3rem; }
.rp-legend i { width: 13px; height: 13px; border-radius: 50%; flex: none; }
.lg-smoke { background: rgba(205,210,222,0.55); border: 1.5px solid rgba(238,240,246,0.9); }
.lg-fire { background: rgba(232,110,30,0.45); border: 1.5px solid rgba(255,158,70,0.95); }
  content: ""; position: absolute; left: 50%; top: 0; width: 2px; height: 100%;
  margin-left: -1px; background: var(--ink-2); border-radius: 1px;
}
.lg-you { background: none; border: 2px dashed var(--accent); }
.rp-map { position: relative; aspect-ratio: 1; background: #0e1116; border-radius: 10px; overflow: hidden; }
.rp-map img, .rp-map canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.rp-map img { opacity: 0.9; }
.rp-map canvas { cursor: pointer; }
.rp-banner { position: absolute; left: 50%; top: 6%; transform: translateX(-50%); padding: 0.35rem 0.8rem; border-radius: 6px;
  background: rgba(10,12,18,0.75); color: #fff; font-size: 0.85rem; font-weight: 600; letter-spacing: 0.04em; white-space: nowrap; }
.rp-film { margin: 0; display: flex; flex-direction: column; gap: 0.3rem; position: relative; }
.rp-film video { width: 100%; aspect-ratio: 16/9; border-radius: 10px; background: #000; display: block; cursor: zoom-in; }
.rp-film-stage .rp-film { gap: 0.45rem; }
.rp-film-label { font-size: 0.82rem; }
.rp-film figcaption { display: flex; align-items: center; justify-content: space-between; gap: 0.4rem;
  font-size: 0.7rem; color: var(--ink-3); }
.rp-film-label { font-weight: 600; color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rp-side { display: flex; flex-direction: column; gap: 0.55rem; min-width: 0; min-height: 0; }
.rp-status { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; }
.rp-clock { font-size: 1.5rem; font-weight: 650; }
.rp-alive { font-size: 1rem; font-weight: 650; font-family: var(--mono, monospace); }
.rp-alive .a { color: var(--good); } .rp-alive .b { color: var(--warn); }
.rp-alive i { font-style: normal; color: var(--ink-3); margin: 0 0.25rem; font-size: 0.8rem; }

/* kill feed: one row per kill, dim until it happens, clickable to seek */
/* fills whatever height the map leaves rather than stopping short of it */
.rp-feed { display: flex; flex-direction: column; gap: 1px; flex: 1; min-height: 0; overflow-y: auto; margin: 0 -0.3rem;
  /* The rail is bounded by the film, so the feed usually has more than it can
     show. Fading the last few pixels says "there is more" where a row cut in
     half says "this is broken". */
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 18px), transparent 100%);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 18px), transparent 100%); }
/* One line per kill, in the game's own order: killer, weapon, victim. */
.kf { display: flex; align-items: center; gap: 0.35rem; width: 100%;
  padding: 0.26rem 0.3rem; border: 0; border-radius: 5px;
  background: none; text-align: left; cursor: pointer; font: inherit; font-size: 0.76rem; opacity: 0.35; }
.kf:hover { background: var(--line-2); opacity: 1; }
.kf.is-past { opacity: 1; }
.kf.is-now { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.kf-t { flex: none; font-size: 0.66rem; color: var(--ink-3); width: 2.2rem; }
.kf-a { font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kf-v { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kf-w { flex: none; display: inline-flex; align-items: center; gap: 0.15rem;
  padding: 0.05rem 0.32rem; border-radius: 4px; background: color-mix(in srgb, var(--ink) 9%, transparent);
  font-size: 0.62rem; color: var(--ink-2); white-space: nowrap; }
/* The weapon, as the game draws it. A mask rather than an <img>: the file has
   no fill of its own, so this paints it in whatever colour the row is. */
.kf-gun { display: block; width: 34px; height: 13px; background: currentColor;
  -webkit-mask: var(--art) center/contain no-repeat; mask: var(--art) center/contain no-repeat; }
.kf-wname { font-size: 0.62rem; }
.kf-w::after { content: "→"; margin-left: 0.15rem; color: var(--ink-3); }
.kf-tag { margin-left: auto; flex: none; font-size: 0.56rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-3); border: 1px solid var(--line-2); border-radius: 3px; padding: 0 0.22rem; }
.kf-tag.first { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }

/* timeline: native range for dragging, a pip layer above it for what happened */
/* ---------------------------------------------------------- transport --
   One row held the play button, the scrub, the clock, the sound, three
   toggles, the speed and two actions, so the scrub was squeezed between
   things that never move and the right-hand end was a jumble. Three lanes:
   the scrub gets the full width, the controls sit under it in groups, and the
   key explains the marks. */
.rp-transport {
  margin-top: 0.7rem; padding: 0.7rem 0.8rem 0.6rem;
  border: 1px solid var(--line); border-radius: 12px; background: var(--raised);
  display: flex; flex-direction: column; gap: 0.55rem;
}
.rp-bar { display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap; }
.rp-track { position: relative; width: 100%; padding-top: 13px; padding-bottom: 10px; }
.rp-hover {
  position: absolute; top: -18px; transform: translateX(-50%); pointer-events: none;
  padding: 0.05rem 0.3rem; border-radius: 4px; background: var(--ink); color: var(--surface);
  font: 0.66rem/1.5 var(--mono); white-space: nowrap;
}
.rp-time { font-size: 0.85rem; color: var(--ink-3); white-space: nowrap; letter-spacing: 0.01em; }
.rp-time b { color: var(--ink); font-weight: 650; }
.rp-time i { font-style: normal; margin: 0 0.15rem; opacity: 0.6; }

/* A control you press should look pressable at arm's length. 42px for the
   transport, 32px for the secondary ones - the old 30px circles read as
   decoration. */
.btn-round { width: 42px; height: 42px; }
.rp-iconbtn {
  display: grid; place-items: center; width: 32px; height: 32px; flex: none;
  border: 1px solid transparent; border-radius: 9px; background: none;
  color: var(--ink-2); cursor: pointer;
}
.rp-iconbtn:hover { color: var(--ink); background: color-mix(in srgb, var(--ink) 8%, transparent); }
.rp-iconbtn.is-off { color: var(--ink-3); }

/* Volume: the slider grows out of the button rather than sitting there taking
   room, because it is set once a session. */
.rp-vol { display: flex; align-items: center; gap: 0.25rem; }
.rp-volume { -webkit-appearance: none; appearance: none; width: 0; opacity: 0;
  height: 4px; border-radius: 2px; background: var(--line-2); cursor: pointer;
  transition: width 0.16s ease, opacity 0.16s ease; }
.rp-vol:hover .rp-volume, .rp-vol:focus-within .rp-volume { width: 70px; opacity: 1; }
.rp-volume::-webkit-slider-thumb { -webkit-appearance: none; appearance: none;
  width: 11px; height: 11px; border-radius: 50%; background: var(--ink); border: 0; }
.rp-volume::-moz-range-thumb { width: 11px; height: 11px; border-radius: 50%; background: var(--ink); border: 0; }

/* Speed is four fixed choices, which is a segmented control, not a dropdown
   you have to open to find out what it says. */
.segs { display: inline-flex; border: 1px solid var(--line-2); border-radius: 9px; overflow: hidden; }
.segs button {
  padding: 0.28rem 0.5rem; border: 0; background: none; color: var(--ink-2);
  font: 600 0.74rem/1 var(--mono); cursor: pointer;
}
.segs button + button { border-left: 1px solid var(--line-2); }
.segs button:hover { color: var(--ink); background: color-mix(in srgb, var(--ink) 8%, transparent); }
.segs button.is-on { color: var(--surface); background: var(--ink-2); }

/* What is on screen right now, in the bar rather than orphaned on its own
   line under the film. */
.rp-now { flex: 1; min-width: 0; font-size: 0.8rem; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The key. Every mark on the scrub is a colour and a shape, and nothing said
   which was which. */
.rp-key { display: flex; flex-wrap: wrap; gap: 0.1rem 0.85rem; margin: 0; padding: 0;
  list-style: none; font-size: 0.65rem; color: var(--ink-3); }
.rp-key li { display: inline-flex; align-items: center; gap: 0.28rem; }
.rp-key i { display: block; flex: none; }
.k-mine { width: 8px; height: 8px; background: var(--hi); transform: rotate(45deg); border-radius: 1px; }
.k-died { width: 9px; height: 9px; position: relative; }
.k-died::before, .k-died::after { content: ""; position: absolute; left: 50%; top: 0; width: 2px; height: 100%;
  margin-left: -1px; background: var(--lo); border-radius: 1px; }
.k-died::before { transform: rotate(45deg); } .k-died::after { transform: rotate(-45deg); }
.k-kill { width: 3px; height: 10px; border-radius: 2px; opacity: 0.55; }
.k-kill.a { background: var(--good); } .k-kill.b { background: var(--warn); margin-left: 2px; }
.k-plant { width: 4px; height: 10px; border-radius: 2px; background: var(--accent); }
.k-defuse { width: 4px; height: 10px; border-radius: 2px; background: var(--good); }
.k-film { width: 14px; height: 3px; border-radius: 2px; background: var(--accent); }
.rp-marks { position: absolute; left: 0; right: 0; top: 0; height: 12px; }
.mk { position: absolute; top: 0; width: 3px; height: 10px; margin-left: -1.5px; padding: 0; border: 0; border-radius: 2px;
  background: var(--line-2); cursor: pointer; }
.mk::after { content: ""; position: absolute; inset: -5px -4px; }   /* easier to hit than 3px */
.mk:hover { transform: scaleY(1.35); }
/* Every kill is a skull in the colour of whoever died - ten players, so at
   most ten a round, which fits without crowding. */
.mk.kill {
  width: 13px; height: 13px; margin-left: -6.5px; top: -1px; background: none;
  display: grid; place-items: center; opacity: 0.72;
}
.mk.kill .i { width: 12px; height: 12px; }
.mk.kill.a { color: var(--good); } .mk.kill.b { color: var(--warn); }
.mk.kill.n { color: var(--ink-3); }
.mk.kill:hover { opacity: 1; transform: scale(1.2); }
/* THIS camera's round: what the player you are watching did, and when they
   died. Diamond for a kill, cross for the death, so the two never rest on
   colour. */
.mk.pov { width: 9px; height: 9px; margin-left: -4.5px; top: 1px; border-radius: 2px; opacity: 1; }
.mk.pov.got { background: var(--hi); transform: rotate(45deg); }
.mk.pov.got:hover { transform: rotate(45deg) scale(1.25); }
.mk.pov.lost { background: none; box-shadow: none; }
.mk.pov.lost::before, .mk.pov.lost::after {
  content: ""; position: absolute; left: 50%; top: 0; width: 2px; height: 100%;
  margin-left: -1px; background: var(--lo); border-radius: 1px;
}
.mk.pov.lost::before { transform: rotate(45deg); }
.mk.pov.lost::after { transform: rotate(-45deg); }
/* The sparse marks - one bomb, one defuse - can afford to BE what they are.
   Kills stay ticks: there are a dozen a round and a dozen glyphs is a mess. */
.mk.plant, .mk.defuse, .mk.boom {
  width: 16px; height: 16px; margin-left: -8px; top: -3px; background: none;
  display: grid; place-items: center; border-radius: 50%;
}
.mk.plant .i, .mk.defuse .i, .mk.boom .i { width: 13px; height: 13px; }
.mk.plant { color: var(--accent); }
.mk.defuse { color: var(--good); }
.mk.boom { color: var(--bad); }
.mk.plant:hover, .mk.defuse:hover, .mk.boom:hover { transform: scale(1.2); background: var(--raised); }
/* Where footage exists, in its own lane under the bar. A whole-round archive
   is a quiet strip - it is true of every round now, so shouting it says
   nothing; a clip rendered FOR a moment keeps the accent, because that span is
   the part somebody chose. */
.rp-film-band { position: absolute; left: 0; right: 0; bottom: 0; height: 4px; }
/* Everything in view has footage: said once, under the bar, instead of a band
   the width of the screen. */
.rp-track.has-film .rp-film-band::after {
  content: ""; position: absolute; left: 0; right: 0; top: 1px; height: 2px; border-radius: 1px;
  background: repeating-linear-gradient(90deg,
    color-mix(in srgb, var(--ink-2) 40%, transparent) 0 10px, transparent 10px 16px);
}
.fb { position: absolute; top: 0; height: 4px; padding: 0; border: 0; border-radius: 2px; cursor: pointer; }
.fb.whole { background: color-mix(in srgb, var(--ink-2) 38%, transparent); height: 3px; top: 0.5px; }
.fb.moment { background: var(--accent); height: 4px; box-shadow: 0 0 0 1px var(--surface); }
.fb:hover { filter: brightness(1.3); }

/* A bar you can actually see and grab, not a dot on a hairline. The played
   part is painted with a gradient stop at --played, which draw() keeps in
   step with the clock; the handle is a slab tall enough to read as a
   playhead against the pip lane above it. */
.rp-scrub {
  -webkit-appearance: none; appearance: none;
  display: block; width: 100%; height: 16px; margin: 0;
  background: none; cursor: pointer;
}
.rp-scrub::-webkit-slider-runnable-track {
  height: 14px; border-radius: 4px;
  background: linear-gradient(to right, color-mix(in srgb, var(--accent) 62%, var(--sunken)) var(--played, 0%), var(--line-2) var(--played, 0%));
}
.rp-scrub::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 6px; height: 22px; margin-top: -4px;
  border: 0; border-radius: 3px; background: var(--ink);
  box-shadow: 0 0 0 2px var(--surface), 0 1px 3px rgba(0, 0, 0, 0.35);
}
.rp-scrub::-moz-range-track { height: 14px; border-radius: 4px; background: var(--line-2); }
.rp-scrub::-moz-range-progress { height: 14px; border-radius: 4px 0 0 4px; background: color-mix(in srgb, var(--accent) 62%, var(--sunken)); }
.rp-scrub::-moz-range-thumb {
  width: 6px; height: 22px; border: 0; border-radius: 3px; background: var(--ink);
  box-shadow: 0 0 0 2px var(--surface), 0 1px 3px rgba(0, 0, 0, 0.35);
}
.rp-scrub:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent); }
.rp-opt { accent-color: var(--ink-2); font-size: 0.78rem; color: var(--ink-2); display: inline-flex; align-items: center; gap: 0.3rem; }

/* jump-to-moment chips */
.rp-jump { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.7rem; }
.rp-jump-empty { font-size: 0.72rem; color: var(--ink-3); }
.jc { display: inline-flex; align-items: baseline; gap: 0.35rem; padding: 0.25rem 0.5rem; border-radius: 999px;
  border: 1px solid var(--line-2); background: none; cursor: pointer; font: inherit; font-size: 0.72rem; color: var(--ink-2); opacity: 0.55; }
.jc:hover { background: var(--line-2); opacity: 1; }
.jc.is-past { opacity: 1; }
.jc b { font-size: 0.62rem; letter-spacing: 0.07em; text-transform: uppercase; }
.jc-t { font-size: 0.64rem; color: var(--ink-3); }
.jc.tA b { color: var(--good); } .jc.tB b { color: var(--warn); }
.jc.multi, .jc.clutch { border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
.jc.plant b, .jc.boom b { color: var(--bad); }
.jc.defuse b { color: var(--good); }

/* --------------------------------------------------------------- clips -- */
.clipgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); gap: 1rem; }
.clip { margin: 0; display: flex; flex-direction: column; gap: 0.4rem; }
.clip video { width: 100%; height: auto; aspect-ratio: 1; border-radius: 10px; background: #0e1116; display: block; }
.clip-ph { width: 100%; aspect-ratio: 1; border-radius: 10px; background: var(--line-2);
  display: grid; place-items: center; font-size: 0.74rem; color: var(--ink-3); }
.clip figcaption { display: flex; flex-direction: column; gap: 0.15rem; font-size: 0.78rem; }
.clip figcaption b { font-weight: 650; }
.clip figcaption .mono { font-size: 0.66rem; }
.dlrow { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.15rem; }
.dlrow .linkbtn { font-size: 0.7rem; }
.clip-err { font-size: 0.66rem; color: var(--bad); overflow-wrap: anywhere; }
.clip.failed .clip-ph { color: var(--bad); }

/* ----------------------------------------------------- player positions -- */
/* Blue/orange rather than the panel's green/red: red-green is the common
   colour blindness and green vs red measured deltaE 5.8 for a deutan reader,
   which is indistinguishable. This pair passes every check in both modes. */
.posmap { --kill: #3b86c9; --death: #cf7c22; }
.posbar { display: flex; align-items: center; gap: 0.9rem; flex-wrap: wrap; margin-bottom: 0.7rem; }
.postabs { display: flex; gap: 4px; }
.postab { padding: 0.25rem 0.6rem; border: 1px solid var(--line-2); border-radius: 999px; background: none;
  cursor: pointer; font: inherit; font-size: 0.72rem; color: var(--ink-2); }
.postab.is-on { border-color: var(--accent); color: var(--ink); font-weight: 650; }
.poslegend { display: flex; align-items: center; gap: 0.8rem; margin-left: auto; font-size: 0.74rem; color: var(--ink-2); }
.pl { display: inline-flex; align-items: center; gap: 0.3rem; }
.pl svg { width: 14px; height: 14px; }
.pl.kills svg circle { fill: #3b86c9; }
.pl.deaths svg path { stroke: #cf7c22; stroke-width: 2.5; fill: none; stroke-linecap: round; }
.posmap { display: none; margin: 0 auto; position: relative; width: min(100%, 720px); }
.posmap.is-on { display: block; }
.posmap img { width: 100%; height: auto; border-radius: 10px; background: #0e1116; display: block; }
.posmap svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.posmap .kill { fill: var(--kill); stroke: rgba(6, 9, 14, 0.75); stroke-width: 2; }
.posmap .death { stroke: var(--death); stroke-width: 3.5; stroke-linecap: round; fill: none;
  paint-order: stroke; filter: drop-shadow(0 0 1.5px rgba(6, 9, 14, 0.9)); }
.posmap .killers { display: none; }
.posmap.show-killers .killers { display: block; }
.posmap .killers circle { fill: none; stroke: var(--death); stroke-width: 2; opacity: 0.55; stroke-dasharray: 3 3; }
.posmap figcaption { margin-top: 0.45rem; font-size: 0.74rem; color: var(--ink-3); }
.posmap figcaption b { color: var(--ink-2); }

/* ======================================================= analytics kit ===
 * The stats half of the panel is READ, not operated: browsed on a big
 * screen, shown to people who did not build it, and every number on it is a
 * claim. It earns a component set of its own rather than borrowing the
 * operator controls.
 *
 * Three rules, applied everywhere below:
 *   1. If it navigates, it looks like it does - hover, cursor, a chevron.
 *   2. A magnitude carries a MARK, not only a colour. Bars are thin, sit on
 *      a baseline, and never replace the number.
 *   3. A file is a chip with a size on it, not a blue filename.
 */

:root {
  /* Above/below average. Not the green/red pair the panel uses for state:
     that measured ΔE 5.8 for a deutan reader (indistinguishable) on the
     validator in the dataviz skill. This pair passes every check in both
     modes, and is already what the duel map uses for kills/deaths. */
  --hi: #3b86c9;
  --lo: #cf7c22;
}

/* --- page header --------------------------------------------------------
   "PAGE / Stats" was a label for a control panel. A stats page wants a title
   and its actions, with the breadcrumb doing the work the label was doing. */
.topbar .read b { color: var(--ink-3); }

/* --- table polish ------------------------------------------------------ */
.tbl tbody tr { transition: background 0.12s; }
.tbl.rows tbody tr:hover { background: color-mix(in srgb, var(--ink) 4%, transparent); }
.tbl th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.tbl th.sortable:hover { color: var(--ink); }
.tbl th.sortable::after {
  content: "↕"; margin-left: 0.25em; opacity: 0.25; font-size: 0.9em;
}
.tbl th.sort-asc::after { content: "↑"; opacity: 1; color: var(--accent); }
.tbl th.sort-desc::after { content: "↓"; opacity: 1; color: var(--accent); }
/* A rule between column groups says "these belong together" without a second
   header row: output | impact | rating. */
.tbl .grp { box-shadow: inset 1px 0 0 var(--line); }

/* --- rating: a number with a meter, never a colour on its own ----------- 
   1.00 is average, so the mark diverges from the middle: right of centre is
   above, left is below, and the length is how far. */
.rtg { display: inline-flex; flex-direction: column; align-items: flex-end; gap: 3px; }
.rtg b { font-weight: 650; font-variant-numeric: tabular-nums; }
.rtg i {
  position: relative; display: block; width: 46px; height: 3px;
  background: var(--line); border-radius: 2px; overflow: hidden;
}
.rtg i::before {                       /* the midpoint tick, always visible */
  content: ""; position: absolute; left: 50%; top: -1px; width: 1px; height: 5px;
  background: var(--line-2);
}
.rtg i span { position: absolute; top: 0; height: 100%; border-radius: 2px; }
.rtg .up { background: var(--hi); }
.rtg .dn { background: var(--lo); }

/* --- a bar inside a table cell, for plain magnitudes ------------------- */
.barcell { display: flex; align-items: center; justify-content: flex-end; gap: 0.5rem; }
.barcell i { display: block; height: 4px; border-radius: 2px; background: var(--hi); opacity: 0.75; }

/* --- two shares of one whole (CT vs T) --------------------------------- */
.splitbar { display: flex; gap: 2px; width: 92px; height: 6px; }
.splitbar i { display: block; height: 100%; border-radius: 2px; }
.splitbar .ct { background: color-mix(in srgb, var(--hi) 80%, transparent); }
.splitbar .t  { background: color-mix(in srgb, var(--lo) 80%, transparent); }

/* --- map badge + name -------------------------------------------------- */
.mapchip { display: inline-flex; align-items: center; gap: 0.5rem; min-width: 0; }
.mapchip img { width: 26px; height: 26px; object-fit: contain; flex: none; }
.mapchip b { font-weight: 550; }

/* --- a file, with its size --------------------------------------------- */
.filechip {
  display: inline-flex; align-items: center; gap: 0.35rem; max-width: 100%;
  padding: 0.22rem 0.5rem; border: 1px solid var(--line-2); border-radius: 7px;
  font: 0.7rem/1.2 var(--mono); color: var(--ink-2); text-decoration: none;
}
.filechip:hover { border-color: var(--accent); color: var(--accent); }
.filechip .i { width: 13px; height: 13px; }
.filechip em { font-style: normal; color: var(--ink-3); }

/* --- match history: a row you pick, not a row you read ----------------- */
.matchlist { display: grid; gap: 8px; }
.matchrow {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center; gap: 0.9rem;
  padding: 0.7rem 0.8rem; border: 1px solid var(--line); border-radius: 10px;
  background: var(--raised); color: inherit; text-decoration: none;
  transition: border-color 0.12s, transform 0.12s;
}
.matchrow:hover { border-color: var(--accent); }
.matchrow:active { transform: translateY(1px); }
.matchrow .art { width: 44px; height: 44px; object-fit: contain; flex: none; }
.matchrow .art-blank {
  width: 44px; height: 44px; border-radius: 9px; background: var(--sunken);
  display: grid; place-items: center; color: var(--ink-3);
}
.matchrow .who { min-width: 0; }
.matchrow .who b { display: block; font-size: 0.95rem; font-weight: 600; }
.matchrow .who span { font-size: 0.72rem; color: var(--ink-3); }
.matchrow .score {
  font: 650 1.15rem/1 var(--mono); font-variant-numeric: tabular-nums;
  display: flex; align-items: baseline; gap: 0.3rem; color: var(--ink-3);
}
.matchrow .score .win { color: var(--ink); }
.matchrow .score .a.win { color: var(--good); }
.matchrow .score .b.win { color: var(--accent); }
.matchrow .meta { display: flex; align-items: center; gap: 0.5rem; }
.matchrow .go { color: var(--ink-3); }
.matchrow:hover .go { color: var(--accent); }
.matchrow.is-fragment { opacity: 0.66; }
@media (max-width: 700px) {
  .matchrow { grid-template-columns: auto minmax(0, 1fr) auto; }
  .matchrow .meta { display: none; }
}

/* A badge that says what you can DO with a match, not what it is. */
.havechip {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.16rem 0.45rem; border-radius: 999px; font-size: 0.66rem;
  border: 1px solid var(--line-2); color: var(--ink-2); white-space: nowrap;
}
.havechip .i { width: 12px; height: 12px; }
.havechip.is-film { border-color: color-mix(in srgb, var(--accent) 55%, transparent); color: var(--accent); }

.statgrid.summary { margin-bottom: var(--gap); }
.statgrid.summary .stat { background: var(--surface); border-color: var(--line); }
.demorow { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; margin-top: 0.9rem; }
.demorow .dim { font-size: 0.66rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-3); }

/* --- gallery: posters, not players ------------------------------------- */
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); gap: 0.9rem; }
.tile {
  display: flex; flex-direction: column; gap: 0.45rem; padding: 0; margin: 0;
  border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
  background: var(--raised); color: inherit; text-align: left; cursor: pointer;
  font: inherit; transition: border-color 0.12s, transform 0.12s;
}
.tile:hover { border-color: var(--accent); }
.tile:active { transform: translateY(1px); }
.tile-art { position: relative; display: block; background: #000; aspect-ratio: 16/9; }
.tile-art.square { aspect-ratio: 1; }
.tile-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tile-play {
  position: absolute; inset: 0; display: grid; place-items: center; color: #fff;
  background: linear-gradient(to top, rgba(0,0,0,0.45), transparent 55%);
  opacity: 0.85; transition: opacity 0.12s;
}
.tile:hover .tile-play { opacity: 1; }
.tile-play .i { filter: drop-shadow(0 1px 4px rgba(0,0,0,0.6)); }
.tile-angles {
  position: absolute; right: 6px; top: 6px; padding: 0.1rem 0.4rem; border-radius: 999px;
  background: rgba(8,11,16,0.72); color: #fff; font-size: 0.62rem; letter-spacing: 0.03em;
}
.tile-cap { display: block; padding: 0 0.65rem 0.6rem; min-width: 0; }
.tile-cap b { display: block; font-size: 0.82rem; font-weight: 600; }
.tile-cap span { font-size: 0.68rem; color: var(--ink-3); font-family: var(--mono); }
.tile-ph { position: absolute; inset: 0; display: grid; place-items: center; color: var(--ink-3); font-size: 0.75rem; }
.tile.is-failed { opacity: 0.6; }

/* --- the one player ---------------------------------------------------- */
body.has-theatre { overflow: hidden; }
.theatre {
  position: fixed; inset: 0; z-index: 50; display: grid; place-items: center;
  background: rgba(4,6,10,0.82); padding: 2rem;
}
.th-box {
  width: min(1200px, 100%); background: var(--surface); border: 1px solid var(--line-2);
  border-radius: 12px; overflow: hidden; box-shadow: 0 24px 60px rgba(0,0,0,0.5);
}
.th-head { display: flex; align-items: center; gap: 0.6rem; padding: 0.6rem 0.8rem; border-bottom: 1px solid var(--line); }
.th-head b { font-size: 0.95rem; }
.th-sub { font-size: 0.72rem; font-family: var(--mono); }
.th-video { display: block; width: 100%; max-height: 72vh; background: #000; }
.th-povs { display: flex; flex-wrap: wrap; gap: 5px; padding: 0.6rem 0.8rem; }
.th-povs:empty { display: none; }

/* --- render jobs ------------------------------------------------------- */
.joblist { display: grid; gap: 6px; }
.job {
  display: flex; align-items: center; gap: 0.8rem;
  padding: 0.5rem 0.7rem; border: 1px solid var(--line); border-radius: 9px; background: var(--raised);
}
.job-name { min-width: 0; flex: 1; }
.job-name b { display: block; font-size: 0.84rem; font-family: var(--mono); }
.job-name span { font-size: 0.7rem; color: var(--ink-3); }

/* --- replay transport --------------------------------------------------
   The row under the film is a transport, not a toolbar: one round button
   that plays, the scrub, then the things you set once and forget. */
.btn-round {
  display: grid; place-items: center; width: 38px; height: 38px; flex: none;
  border-radius: 50%; border: 1px solid var(--line-2); background: var(--raised);
  color: var(--ink); cursor: pointer; transition: border-color 0.12s, color 0.12s;
}
.btn-round:hover { border-color: var(--accent); color: var(--accent); }
.btn-round.is-playing { border-color: var(--accent); color: var(--accent); }
.btn-round:active { transform: translateY(1px); }
.rp-toggles { display: flex; align-items: center; gap: 0.1rem; padding: 0.15rem;
  border: 1px solid var(--line); border-radius: 999px; }
.rp-toggles .rp-opt { padding: 0.15rem 0.5rem; border-radius: 999px; }
.rp-toggles .rp-opt:hover { background: color-mix(in srgb, var(--ink) 7%, transparent); }
.rp-actions { display: inline-flex; align-items: center; gap: 0.35rem; }
.rp-actions .btn { display: inline-flex; align-items: center; gap: 0.3rem; }
/* A camera tab wears the face: at ten of them the names are all you have,
   and a face is quicker to find than a word. */
.rp-moment-who { display: flex; align-items: center; gap: 0.32rem; padding: 0 0.2rem; }
.rp-moment-who .av { width: 16px; height: 16px; border-radius: 4px; margin: 0; }

.pagetitle b { color: var(--accent); }
.pagetitle span { font-size: 1rem; font-weight: 650; }
@media (min-width: 1100px) { .statgrid.even6 { grid-template-columns: repeat(6, minmax(0, 1fr)); } }

/* ------------------------------------------------------- camera bank --
   Ten cameras is not a row of pills. Pills are for one-of-a-few choices with
   short labels; this is a bank of ten people split across two sides, and the
   question it answers is "who is on my team and which of them am I in".
   So: faces at a readable size, grouped by side, the live one lifted. */
.rp-cams { display: flex; flex-wrap: wrap; gap: 0.9rem; align-items: center; justify-content: space-between; }
.cam-side { flex: 0 0 auto; }
.rp-cams:empty { display: none; }
.cam-side { display: flex; align-items: stretch; gap: 0.3rem; padding: 0.25rem;
  border-radius: 12px; background: color-mix(in srgb, var(--ink) 4%, transparent); }
.cam-side-label {
  writing-mode: vertical-rl; transform: rotate(180deg);
  font-size: 0.55rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-3); display: grid; place-items: center; padding: 0 0.1rem;
}
.cam-side.a .cam-side-label { color: color-mix(in srgb, var(--good) 80%, var(--ink-3)); }
.cam-side.b .cam-side-label { color: color-mix(in srgb, var(--warn) 80%, var(--ink-3)); }
.cam {
  display: flex; flex-direction: column; align-items: center; gap: 0.25rem;
  width: 74px; padding: 0.3rem 0.15rem 0.35rem; border: 1px solid transparent;
  border-radius: 10px; background: none; color: var(--ink-2); cursor: pointer;
  font: inherit; transition: background 0.12s, border-color 0.12s, transform 0.12s;
}
.cam img, .cam .cam-blank {
  width: 38px; height: 38px; border-radius: 9px; object-fit: cover; display: block;
  box-shadow: 0 0 0 2px var(--raised), 0 0 0 3.5px var(--line-2);
}
.cam .cam-blank { display: grid; place-items: center; background: var(--sunken); font-size: 0.7rem; }
.cam b { font-size: 0.66rem; font-weight: 600; max-width: 100%; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.cam:hover { color: var(--ink); background: color-mix(in srgb, var(--ink) 8%, transparent); }
.cam.a img, .cam.a .cam-blank { box-shadow: 0 0 0 2px var(--raised), 0 0 0 3.5px var(--good); }
.cam.b img, .cam.b .cam-blank { box-shadow: 0 0 0 2px var(--raised), 0 0 0 3.5px var(--warn); }
.cam.is-on { color: var(--ink); border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent); transform: translateY(-1px); }
.cam.is-on img, .cam.is-on .cam-blank { box-shadow: 0 0 0 2px var(--raised), 0 0 0 3.5px var(--accent); }
.cam .cam-star { color: var(--accent); font-size: 0.6rem; }

/* A strip of chips with no heading reads as chrome. Two words and a sentence
   say what the row indexes and what clicking does. */
.rp-striphead { display: flex; align-items: baseline; gap: 0.45rem; margin: 0 0 0.35rem; }
.rp-striphead .i { width: 13px; height: 13px; color: var(--accent); align-self: center; }
.rp-striphead b { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase; }
.rp-striphead span { font-size: 0.7rem; color: var(--ink-3); min-width: 0; }
.rp-rounds { margin-bottom: 0.9rem; }
.cam-score { display: flex; align-items: baseline; gap: 0.3rem; font: 650 1.5rem/1 var(--mono);
  font-variant-numeric: tabular-nums; color: var(--ink-3); }
.cam-score .a { color: var(--good); } .cam-score .b { color: var(--warn); }
.cam-score i { font-style: normal; font-size: 1rem; opacity: 0.5; }

/* The map zooms in place - the panel is not going to grow a bigger map, so
   the map has to be able to get closer. */
.rp-map { touch-action: none; }
.rp-map.is-zoomed { cursor: grab; }
.rp-map.is-zoomed:active { cursor: grabbing; }
.rp-unzoom {
  position: absolute; right: 8px; bottom: 8px; display: none; align-items: center; gap: 0.3rem;
  padding: 0.2rem 0.5rem; border: 1px solid var(--line-2); border-radius: 8px;
  background: rgba(10,13,18,0.8); color: var(--ink); font: 0.7rem/1.4 var(--sans); cursor: pointer;
}
.rp-map.is-zoomed .rp-unzoom { display: inline-flex; }
.rp-unzoom .i { width: 12px; height: 12px; }
/* A dead camera is spectating somebody else: still moving, no longer theirs. */
.cam.is-dead { opacity: 0.45; }
.cam.is-dead img, .cam.is-dead .cam-blank { filter: grayscale(1); box-shadow: 0 0 0 2px var(--raised), 0 0 0 3.5px var(--line-2); }
.cam.is-dead b::after { content: " ✕"; color: var(--lo); }
.rp-key .k-ico { width: 13px; height: 13px; }
.rp-key .k-ico.plant { color: var(--accent); }
.rp-key .k-ico.defuse { color: var(--good); }

/* the pen layer sits over the replay canvas and under nothing */
.rp-draw { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.rp-map.is-drawing { cursor: crosshair; }
.rp-tools { position: absolute; left: 8px; top: 8px; display: flex; gap: 4px; }
.rp-tools button {
  display: grid; place-items: center; width: 28px; height: 28px; padding: 0;
  border: 1px solid var(--line-2); border-radius: 8px; background: rgba(10,13,18,0.72);
  color: var(--ink-2); cursor: pointer;
}
.rp-tools button:hover { color: var(--ink); border-color: var(--ink-3); }
.rp-tools button.is-on { color: var(--accent); border-color: var(--accent); }
.rp-tools .i { width: 14px; height: 14px; }

/* CS Demo Manager's icons are filled shapes, not 2px strokes like Lucide's. */
.i.solid { fill: currentColor; stroke: none; }
/* the game's own marks, at label size */
.rp-key .k-ico.died { color: var(--lo); }
.rp-legend .lg-ico { width: 13px; height: 13px; }
.rp-legend .lg-ico.flash { color: #f5e6b0; }
.rp-legend .lg-ico.he { color: var(--bad); }
.rp-legend .lg-ico.bomb { color: var(--accent); }
/* The camera's own death is the same skull in their own team's colour, ringed
   so it reads as "this is you" rather than as a fourth colour nobody can tell
   from team B's amber. */
.mk.pov.lost { width: 19px; height: 19px; margin-left: -9.5px; top: -4px; border-radius: 50%;
  display: grid; place-items: center; background: var(--surface);
  box-shadow: 0 0 0 2px var(--accent); opacity: 1; }
.mk.pov.lost::before, .mk.pov.lost::after { content: none; }
.mk.pov.lost .i { width: 13px; height: 13px; }
.mk.pov.lost.a { color: var(--good); } .mk.pov.lost.b { color: var(--warn); }
.mk.pov.lost:hover { transform: scale(1.15); }
.kf-mk { width: 11px; height: 11px; color: var(--ink-2); }
.kf-mk.hs { color: var(--accent); }
/* what a tick on the timeline actually was */
.rp-tip {
  position: absolute; bottom: calc(100% + 2px); transform: translateX(-50%);
  display: flex; align-items: center; gap: 0.35rem; white-space: nowrap;
  padding: 0.22rem 0.45rem; border: 1px solid var(--line-2); border-radius: 8px;
  background: var(--surface); box-shadow: 0 6px 20px rgba(0,0,0,0.45);
  font-size: 0.74rem; pointer-events: none; z-index: 3;
}
.rp-tip .tip-t { font-size: 0.66rem; color: var(--ink-3); }
.rp-key .k-ico.a { color: var(--good); } .rp-key .k-ico.b { color: var(--warn); margin-left: -3px; }
.rp-key .k-ring { display: grid; place-items: center; width: 17px; height: 17px; border-radius: 50%;
  background: var(--surface); box-shadow: 0 0 0 1.5px var(--accent); color: var(--ink-2); }
.rp-key .k-ring .i { width: 11px; height: 11px; }
/* the camera's own mark stands in for the generic one at that tick */
.mk.kill.is-replaced { display: none; }

/* mark in / mark out, and the span they make */
.rp-cut { display: inline-flex; align-items: center; gap: 0.3rem; }
.rp-cut .btn.is-on { border-color: var(--accent); color: var(--accent); }
.rp-cutmsg { font-size: 0.7rem; color: var(--ink-3); }
.mk.cutband {
  top: 1px; height: 9px; margin-left: 0; border-radius: 3px; pointer-events: none;
  background: color-mix(in srgb, var(--accent) 28%, transparent);
  box-shadow: inset 1px 0 0 var(--accent), inset -1px 0 0 var(--accent);
}
.pubgroup { display: inline-flex; align-items: center; gap: 0.4rem; margin-left: 0.7rem; }
.pubgroup .btn { display: inline-flex; align-items: center; gap: 0.3rem; }

/* --- the guest list ---------------------------------------------------- */
.crewlist { display: grid; gap: 8px; }
.crewrow {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: 0.9rem;
  padding: 0.55rem 0.8rem; border: 1px solid var(--line); border-radius: 10px;
  background: var(--raised);
}
/* Waiting is the state that wants answering, so it is the one with an edge. */
.crewrow.is-waiting { border-color: color-mix(in srgb, var(--accent) 55%, var(--line)); }
.crewrow.is-waiting > .i { color: var(--accent); }
.crewrow.is-in > .i { color: var(--ink-3); }
.crewrow .who { min-width: 0; }
.crewrow .who b { display: block; font-size: 0.9rem; font-weight: 600; }
.crewrow .who span { font-size: 0.72rem; color: var(--ink-3); }

/* --- the footage is the half behind the login --------------------------- */
/* Over the video box exactly - the caption underneath stays readable, and it
   is the video that is locked, not the panel. */
.rp-locked {
  position: absolute; top: 0; left: 0; right: 0; aspect-ratio: 16/9;
  display: grid; align-content: center;
  justify-items: center; gap: 0.4rem; text-align: center; padding: 1.5rem;
  background: color-mix(in srgb, var(--sunken) 92%, transparent);
  border-radius: inherit;
}
.rp-locked .i { width: 22px; height: 22px; color: var(--accent); }
.rp-locked span { font-size: 0.78rem; color: var(--ink-3); }
.rp-locked .btn { margin-top: 0.4rem; }
/* The published site's front door, beside the page title. */
.signin { display: inline-flex; align-items: center; gap: 0.5rem; }
