/* Cobra Royale — operations console.
   Deliberately not the game's stylesheet: the game is a full-bleed neon arcade and this is a dense
   reading surface. It borrows the palette so it is recognisably the same product, and almost
   nothing else. */

:root {
  --bg: #0b1015;
  --rail: #0e141b;
  --panel: #141c24;
  --panel-2: #1a232d;
  --line: rgba(255, 255, 255, 0.08);
  --line-soft: rgba(255, 255, 255, 0.045);
  --text: #f2f6fa;
  --dim: #97a2ae;
  --muted: rgba(151, 162, 174, 0.66);
  --accent: #14b8a6;
  --accent-soft: rgba(20, 184, 166, 0.14);
  --blue: #3882f6;
  --warn: #f7b955;
  --bad: #ff7a66;
  --good: #38d39f;
  --radius: 12px;

  --sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --display: Poppins, Inter, ui-sans-serif, system-ui, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  min-height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { font-family: var(--display); margin: 0; letter-spacing: -0.01em; }

a { color: var(--accent); }

.is-hidden { display: none !important; }

/* ---------- the gate ---------- */

.gate {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
  /* One off-centre glow, echoing the arena's vignette without imitating it. */
  background: radial-gradient(1100px 620px at 22% -10%, rgba(20, 184, 166, 0.1), transparent 60%), var(--bg);
}

.gate-card {
  width: min(380px, 100%);
  display: grid;
  gap: 12px;
  padding: 28px;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--panel);
  box-shadow: 0 30px 90px rgba(0, 0, 0, 0.5);
}

.gate-mark { width: 46px; height: 40px; }

.gate-card h1 { font-size: 26px; }
.gate-sub { margin: -8px 0 8px; color: var(--muted); font-size: 13px; letter-spacing: 0.08em; text-transform: uppercase; }
.gate-back { margin: 4px 0 0; font-size: 12px; color: var(--muted); text-decoration: none; }
.gate-back:hover { color: var(--text); }

/* ---------- shell ---------- */

.console { display: grid; grid-template-columns: 248px 1fr; min-height: 100vh; }

.rail {
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  gap: 16px;
  padding: 18px 14px;
  background: var(--rail);
  border-right: 1px solid var(--line);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.rail-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }

.rail-brand {
  display: flex; align-items: center; gap: 9px;
  color: var(--text); text-decoration: none;
  font-family: var(--display); font-weight: 700; font-size: 14px;
}

.rail-crown { width: 22px; height: 19px; flex: none; }

/* The site's own wordmark, stacked the way the game sets it. */
.rail-brand-type { display: grid; line-height: 0.9; }
.rail-brand-type strong { font-size: 13px; letter-spacing: 0.09em; }
.rail-brand-type strong + strong { font-size: 12px; color: var(--dim); }

/* The pod block is the reason this is a console and not a settings page: it is watching something
   that is running right now. */
.pod {
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: linear-gradient(180deg, rgba(20, 184, 166, 0.07), transparent);
}

.pod-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }

.pod-pip {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--dim);
  flex: none;
}

.pod-pip.is-live { background: var(--good); box-shadow: 0 0 10px var(--good); }
.pod-pip.is-beating { animation: beat 620ms ease-out; }

@keyframes beat {
  0% { transform: scale(1); }
  35% { transform: scale(2.1); }
  100% { transform: scale(1); }
}

.pod-name {
  font-family: var(--mono); font-size: 12px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.pod-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 10px; margin: 0; }
.pod-stats div { display: grid; gap: 1px; }
.pod-stats dt { font-size: 10px; letter-spacing: 0.07em; text-transform: uppercase; color: var(--muted); }
.pod-stats dd { margin: 0; font-family: var(--mono); font-size: 13px; font-weight: 600; }

.nav { display: grid; gap: 2px; align-content: start; }

.nav-section {
  margin: 14px 0 4px;
  padding: 0 10px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--muted);
}

.nav-section:first-child { margin-top: 0; }

.nav-link {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px;
  border: 0;
  border-radius: 9px;
  background: none;
  color: var(--dim);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  width: 100%;
}

.nav-link:hover { background: rgba(255, 255, 255, 0.05); color: var(--text); }

.nav-link.is-current {
  background: var(--accent-soft);
  color: var(--text);
  font-weight: 600;
  box-shadow: inset 2px 0 0 var(--accent);
}

.nav-glyph { width: 16px; text-align: center; opacity: 0.85; font-size: 13px; }

.rail-foot { display: grid; gap: 8px; padding-top: 12px; border-top: 1px solid var(--line-soft); }
.rail-user { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.main { min-width: 0; display: grid; grid-template-rows: auto auto 1fr; align-content: start; }

.topbar {
  position: sticky; top: 0; z-index: 5;
  display: flex; align-items: center; gap: 14px;
  padding: 16px 24px;
  border-bottom: 1px solid var(--line);
  background: rgba(11, 16, 21, 0.9);
  backdrop-filter: blur(10px);
}

.topbar-title { flex: 1; min-width: 0; }
.topbar-title h1 { font-size: 19px; }
.topbar-note { margin: 2px 0 0; font-size: 12px; color: var(--muted); }
.topbar-actions { display: flex; align-items: center; gap: 10px; }

.stamp { font-family: var(--mono); font-size: 11px; color: var(--muted); white-space: nowrap; }

.metric dd { transition: color 220ms ease-out; }
.metric.is-changed dd { color: var(--accent); }

.banner {
  margin: 16px 24px 0;
  padding: 10px 14px;
  border: 1px solid rgba(255, 122, 102, 0.35);
  border-radius: 10px;
  background: rgba(255, 122, 102, 0.1);
  color: #ffd3ca;
  font-size: 13px;
}

.views { padding: 20px 24px 48px; }
.view { display: grid; gap: 16px; align-content: start; }

/* `display: grid` above beats the UA stylesheet's [hidden] rule, so without this every section
   renders at once and the router appears to do nothing. */
.view[hidden] { display: none; }
[hidden] { display: none !important; }

/* ---------- pieces ---------- */

.card {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  padding: 16px;
  min-width: 0;
}

.card.flush { padding: 0; overflow: hidden; }
.card > .table { overflow-x: auto; }

/* A long table scrolls inside its own card with its header pinned, rather than making the whole
   page thousands of pixels tall. The card was `overflow: hidden`, which did not clip anything —
   it just meant a two-hundred-row members list had nowhere to go. */
.card.flush > .table {
  max-height: calc(100vh - 260px);
  min-height: 200px;
  overflow: auto;
  overscroll-behavior: contain;
}
.card h2 { font-size: 13px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--dim); margin-bottom: 12px; }

.card-head-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

/* An action in a card heading belongs at the far end, not tucked against the title where it reads
   as part of the label. */
.card-head-row > .button { margin-left: auto; }

/* align-items: start, so a card is as tall as what is in it. Stretched to match its neighbour, an
   empty "Recent wins" became a nine-hundred-pixel hole next to a long list. */
.split { display: grid; grid-template-columns: 1.5fr 1fr; gap: 16px; align-items: start; }
.split.even { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.chart-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; align-items: start; }
.stack { display: grid; gap: 10px; }

.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; }

.metric {
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}

.metric dt { font-size: 10px; letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted); }
.metric dd { margin: 4px 0 0; font-family: var(--display); font-size: 26px; font-weight: 700; font-variant-numeric: tabular-nums; }
.metric .metric-note { display: block; margin-top: 2px; font-family: var(--sans); font-size: 11px; font-weight: 400; color: var(--muted); }

.note { margin: 0; font-size: 13px; color: var(--muted); }
.status { margin: 0; font-size: 13px; color: var(--dim); min-height: 20px; }
.status.is-bad { color: var(--bad); }

/* ---------- controls ---------- */

.button {
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--panel-2);
  color: var(--text);
  font: inherit;
  font-weight: 600;
  font-size: 13px;
  padding: 8px 14px;
  cursor: pointer;
}

.button:hover { background: #222d3a; }
.button:disabled { opacity: 0.5; cursor: default; }
.button.primary { background: var(--accent); border-color: transparent; color: #04231f; }
.button.primary:hover { background: #25d2bf; }
.button.ghost { background: transparent; }
.button.ghost:hover { background: rgba(255, 255, 255, 0.06); }
.button.small { padding: 5px 10px; font-size: 12px; }
.button.danger { color: #ffb3a6; border-color: rgba(255, 122, 102, 0.35); }
.button.danger:hover { background: rgba(255, 110, 90, 0.16); color: #ffd9d2; }

.field { display: grid; gap: 5px; min-width: 0; }
.field > span { font-size: 12px; font-weight: 600; color: var(--dim); display: flex; justify-content: space-between; gap: 10px; }
.field small { font-weight: 400; color: var(--muted); }

input[type="text"], input[type="email"], input[type="password"], input[type="number"], input[type="search"], select {
  width: 100%;
  padding: 8px 11px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: #0d141b;
  color: var(--text);
  font: inherit;
  font-size: 13px;
}

input:focus-visible, select:focus-visible, button:focus-visible, a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.search { max-width: 320px; }

.icon-button {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: transparent;
  color: var(--dim);
  font-size: 16px;
  cursor: pointer;
}

.icon-button:hover { color: var(--text); background: rgba(255, 255, 255, 0.06); }

.segmented { display: inline-flex; gap: 2px; padding: 3px; border: 1px solid var(--line); border-radius: 10px; background: var(--panel); }

.segmented button {
  border: 0; border-radius: 7px; background: none;
  color: var(--dim); font: inherit; font-size: 12px; font-weight: 600;
  padding: 5px 12px; cursor: pointer;
}

.segmented button.is-current { background: var(--accent-soft); color: var(--text); }

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

.table { display: grid; font-size: 12.5px; }
.table .row { display: grid; gap: 10px; padding: 9px 14px; align-items: center; border-top: 1px solid var(--line-soft); }
.table .row > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.table .row:hover:not(.is-head) { background: rgba(255, 255, 255, 0.025); }

.table .row.is-head {
  position: sticky; top: 0; z-index: 2;
  border-top: 0;
  background: var(--panel-2);
  color: var(--muted);
  font-size: 10px; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase;
}

.table .num { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.table .empty { padding: 20px 14px; color: var(--muted); }

/* The widest table in the console, and it used to run past the right edge — which clipped the
   Erase button, the one control where a half-visible target is genuinely dangerous. The table
   scrolls inside its own card now rather than being cut off by it. */
.members .row { grid-template-columns: minmax(110px, 1.2fr) minmax(150px, 1.6fr) 92px 52px 48px 56px 104px minmax(110px, 1fr) 196px; }

/* Erase is not a peer of Account and Export. It sits in the same row because that is where the
   account is, but it should never be the button a tired thumb reaches for by accident. */
.members .row-actions .button.danger { opacity: 0.72; }
.members .row:hover .row-actions .button.danger { opacity: 1; }

.table.members { overflow-x: auto; }
.table.members .row { min-width: 940px; }
.matches .row { grid-template-columns: 92px 84px 64px 1fr; }

/* Passkeys. The flag column is narrow and often empty, and sits immediately before the action so
   "this one is suspect" and "revoke it" are read together rather than at opposite ends of a row. */
.passkeys .row { grid-template-columns: minmax(110px, 1.2fr) minmax(160px, 1.6fr) 78px 104px 104px 84px 96px; }
.passkey-detail .row { grid-template-columns: minmax(120px, 1.3fr) minmax(140px, 1.4fr) 104px 104px 84px 96px; }
.fleet .row { grid-template-columns: 60px minmax(96px, 1.2fr) 88px 120px 88px 68px 62px 78px 168px; }

/* The add/edit form. Deliberately plain — it is four fields and a list of addresses, and anything
   more elaborate would be dressing up a thing nobody opens twice a month. */
.field-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 12px; }
.field { display: grid; gap: 5px; font-size: 11.5px; color: var(--muted); }
.field input[type="text"], .field input[type="number"], .field textarea {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
  color: var(--text);
  padding: 8px 10px;
  font: inherit;
  font-size: 12.5px;
}
.field textarea { resize: vertical; font-family: var(--mono); }
.field.checkbox { grid-auto-flow: column; align-items: center; justify-content: start; gap: 8px; }
.field input:disabled { opacity: 0.55; }

/* A round nobody is in is context, not news. Still readable, still clickable, just quieter than
   the one match that has people in it. */
.matches .row.is-idle { color: var(--muted); }
.purchases .row { grid-template-columns: minmax(90px, 1fr) minmax(120px, 1.4fr) 54px 74px 78px 72px 96px 104px minmax(96px, 1fr) 132px; }
.table.purchases { overflow-x: auto; }
.table.purchases .row { min-width: 1080px; }
.snakes .row { grid-template-columns: minmax(90px, 1.4fr) 70px 70px 60px 72px; }

.table .row.is-clickable { cursor: pointer; }
.table .row.is-clickable:hover { background: var(--accent-soft); }
.wins .row { grid-template-columns: minmax(90px, 1.2fr) 80px 68px 58px minmax(96px, 1fr); }
.sessions .row { grid-template-columns: minmax(70px, 0.9fr) minmax(110px, 1.5fr) 68px 96px 96px; }

/* A count beside a card's title, so the heading answers "how many" before the table is read. */
/* The Live / Sandbox switch, above the Stripe card it decides the contents of. Sits apart from
   the card so it reads as choosing what is below rather than as a field within it. */
.stripe-modes { margin-bottom: 14px; width: fit-content; justify-self: start; }

.card-count {
  margin-left: 10px;
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: none;
}

.scroll-x { overflow-x: auto; }

.pill {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  background: rgba(255, 255, 255, 0.07);
  color: var(--dim);
}

.pill.is-good { background: rgba(56, 211, 159, 0.16); color: var(--good); }
.pill.is-bad { background: rgba(255, 122, 102, 0.16); color: var(--bad); }
.pill.is-warn { background: rgba(247, 185, 85, 0.16); color: var(--warn); }

.row-actions { display: flex; gap: 6px; overflow: visible; }

/* ---------- charts ---------- */

.bars { display: grid; gap: 6px; }
.bar-row { display: grid; grid-template-columns: 110px 1fr 60px; gap: 10px; align-items: center; }
.bar-label { font-size: 11px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bar-track { height: 9px; border-radius: 5px; background: rgba(255, 255, 255, 0.06); overflow: hidden; display: flex; }
.bar-fill { height: 100%; background: var(--accent); }
.bar-fill.alt { background: var(--blue); }
.bar-value { font-family: var(--mono); font-size: 11.5px; text-align: right; color: var(--dim); }

/* Charts whose value is a sentence rather than a number. "12 players · 105 games" in a
   sixty-pixel column wraps onto four lines and turns a two-row chart into a wall. */
.wide-values .bar-row { grid-template-columns: 96px 1fr 150px; }
.wide-values .bar-value { white-space: nowrap; font-family: inherit; }

/* A sentence under a chart, for the bar whose label cannot hold its own explanation. */
.chart-note {
  margin: 12px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--line-soft);
  color: var(--muted);
  font-size: 11.5px;
  line-height: 1.5;
}

/* ---------- time series ---------- */

/* A shape without a number is decoration, so the total leads and the trend follows it. */
.series-headline { display: flex; align-items: baseline; gap: 7px; margin-bottom: 12px; }
.series-headline strong { font-size: 24px; font-variant-numeric: tabular-nums; }
.series-headline span { color: var(--muted); font-size: 11.5px; }

.series-trend { margin-left: auto; font-weight: 700; }
.series-trend.up { color: var(--accent); }
.series-trend.down { color: var(--warn, #ffb454); }

/* Columns on an even time axis, so a gap looks like a gap. The horizontal bar list this replaced
   sorted days into rows and made a quiet Tuesday indistinguishable from one with no data. */
.series {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  align-items: end;
  gap: 2px;
  height: 92px;
}

.series-column { display: flex; align-items: flex-end; height: 100%; }

.series-fill {
  width: 100%;
  min-height: 0;
  border-radius: 2px 2px 0 0;
  background: var(--accent);
}

/* A day with nothing in it still gets its slot, and a hairline so it reads as a measured zero
   rather than as a missing column. */
.series-column.is-empty { border-bottom: 1px solid rgba(255, 255, 255, 0.1); }

.series-axis {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 2px;
  margin-top: 6px;
  color: var(--muted);
  font-size: 9.5px;
  font-variant-numeric: tabular-nums;
}

.series-axis span { white-space: nowrap; }

.legend { display: flex; gap: 14px; margin-top: 10px; font-size: 11px; color: var(--muted); }
.legend span { display: flex; align-items: center; gap: 5px; }
.swatch { width: 9px; height: 9px; border-radius: 3px; }

/* ---------- balance / settings rows ---------- */

.tuning-group { display: grid; gap: 10px; padding: 16px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); }
.tuning-map { font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent); }
.tuning-row { display: grid; grid-template-columns: 1fr 58px; gap: 12px; align-items: end; }
.tuning-row input[type="range"] { width: 100%; accent-color: var(--accent); }
.tuning-value { font-family: var(--mono); font-weight: 700; font-size: 13px; padding-bottom: 5px; }

.setting-row, .product-row {
  display: grid;
  gap: 10px;
  align-items: end;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}

.setting-row { grid-template-columns: 1fr auto; }
.product-row { grid-template-columns: minmax(120px, 1.4fr) 80px 92px 78px 84px auto; }

/* Set up, or not, on the rail itself. Deliberately a dot rather than a count or a badge: it is
   read a hundred times and acted on twice, and the hover title carries the sentence. */
.nav-dot {
  margin-left: auto;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: transparent;
}

.nav-dot.is-good { background: var(--good); }
.nav-dot.is-warn { background: var(--warn); }

/* Nothing set at all: a hollow ring rather than a colour, because "not in use" is a state and not
   a complaint. */
.nav-dot.is-off { box-shadow: inset 0 0 0 1px var(--muted); }

.integration { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 0; border-top: 1px solid var(--line-soft); }
.integration:first-child { border-top: 0; }
.integration-name { display: grid; gap: 1px; }
.integration-name small { color: var(--muted); font-size: 11px; font-family: var(--mono); }

/* ---------- responsive ---------- */

.rail-only { display: none; }

.rail-scrim {
  position: fixed; inset: 0; z-index: 8;
  background: rgba(0, 0, 0, 0.55);
}

@media (max-width: 1100px) {
  .split, .chart-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 860px) {
  .console { grid-template-columns: 1fr; }
  .rail-only { display: grid; }

  .rail {
    position: fixed; z-index: 9; top: 0; left: 0;
    width: 264px;
    transform: translateX(-100%);
    transition: transform 180ms ease-out;
  }

  .rail.is-open { transform: translateX(0); }

  .views { padding: 16px 14px 40px; }
  .topbar { padding: 12px 14px; }
  .banner { margin: 12px 14px 0; }

  /* Wide tables scroll inside their own card rather than the page. */
  .table { min-width: 760px; }
  .card.flush { overflow-x: auto; }

  .product-row { grid-template-columns: 1fr 1fr; }
  .setting-row { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  .pod-pip.is-beating { animation: none; }
  .rail { transition: none; }
}

/* ---------- one match ---------- */

.match-dialog {
  width: min(720px, calc(100vw - 32px));
  max-height: calc(100vh - 64px);
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel);
  color: var(--text);
  overflow: auto;
}

.match-dialog::backdrop { background: rgba(0, 0, 0, 0.6); }

.match-dialog-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.match-dialog-head h2 { font-size: 18px; font-family: var(--mono); }
.match-dialog .metrics { margin: 12px 0 16px; }
.match-dialog .table { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.match-dialog .table .row.is-head { background: var(--panel-2); }

/* ---------- one member ---------- */

.member-dialog { width: min(920px, calc(100vw - 32px)); }

.gift-form {
  display: grid;
  gap: 10px;
  margin: 4px 0 20px;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel-2);
}

.gift-form h3, .dialog-heading {
  font-size: 12px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--dim);
}

.gift-row {
  display: grid;
  grid-template-columns: 120px minmax(220px, 1fr) auto;
  gap: 10px;
  align-items: end;
}

.gift-form .status { grid-column: 1 / -1; }
.dialog-heading { margin: 18px 0 8px; }

.member-dialog .invoices .row {
  grid-template-columns: 82px 110px 52px 74px 78px 94px minmax(120px, 1fr);
  min-width: 700px;
}

.member-dialog .history .row {
  grid-template-columns: 120px 70px 90px minmax(180px, 1fr) 70px;
  min-width: 620px;
}

.member-dialog .table { overflow-x: auto; }

@media (max-width: 620px) {
  .member-dialog { width: calc(100vw - 20px); padding: 14px; }
  .gift-row { grid-template-columns: 1fr; }
  .gift-row .button { width: 100%; }
}

/* ---------- platform setup ---------- */

.platform-head { display: grid; gap: 8px; }
.platform-top { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.platform-top h2 { margin: 0; font-size: 17px; letter-spacing: -0.01em; text-transform: none; color: var(--text); }
.platform-link { font-size: 12.5px; font-weight: 600; text-decoration: none; }
.platform-link:hover { text-decoration: underline; }

.callback {
  display: grid;
  grid-template-columns: minmax(120px, 200px) 1fr auto;
  gap: 10px;
  align-items: center;
  padding: 9px 0;
  border-top: 1px solid var(--line-soft);
}

.callback:first-of-type { border-top: 0; }
.callback-label { font-size: 12.5px; font-weight: 600; color: var(--dim); }

.callback-value {
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--text);
  background: #0d141b;
  border: 1px solid var(--line);
  border-radius: 7px;
  padding: 6px 9px;
  overflow-x: auto;
  white-space: nowrap;
}

.callback-help { margin: 0 0 4px; font-size: 11.5px; color: var(--muted); }

.setting-help { grid-column: 1 / -1; margin: 0; font-size: 11.5px; color: var(--muted); }

/* Something the server can tell is wrong with the value. Warn rather than bad: the setting is
   saved and the platform may well be working — this is the thing that will look like a different
   problem in a fortnight. */
.setting-problem {
  grid-column: 1 / -1;
  margin: 2px 0 0;
  padding-left: 9px;
  border-left: 2px solid var(--warn);
  font-size: 11.5px;
  line-height: 1.5;
  color: #f6dcae;
}

/* The four ids the sign-in page hands to every browser anyway, shown rather than dotted. In the
   mono face because it is a value to compare against a dashboard character by character, and
   allowed to wrap because a Google client id is 72 characters long. */
.setting-value {
  font-family: var(--mono);
  font-size: 11px;
  overflow-wrap: anywhere;
}

/* Between the settings a platform needs and the ones it only needs on an unusual host. */
.setting-divider {
  margin: 6px 0 0;
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Says plainly that this one is not editable yet, and offers the one action that fixes it. */
.setting-adopt {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 4px;
  padding: 9px 11px;
  border: 1px solid rgba(247, 185, 85, 0.3);
  border-radius: 9px;
  background: rgba(247, 185, 85, 0.09);
}

.setting-adopt p { margin: 0; font-size: 12px; color: #f6dcae; flex: 1; min-width: 200px; }

@media (max-width: 700px) {
  .callback { grid-template-columns: 1fr; }
  .callback-value { width: 100%; }
}

.platform-notes { display: grid; gap: 8px; border-left: 2px solid var(--accent); }
.platform-notes .note { line-height: 1.55; }

.match-dialog-actions { display: flex; align-items: center; gap: 10px; }

/* Links out of a card head — today, through to Grafana. Pushed to the right so the card still
   reads as a heading with somewhere to go, rather than a heading with a menu. */
.head-links { margin-left: auto; display: flex; gap: 14px; flex-wrap: wrap; }

.head-link {
  color: var(--accent);
  font-size: 12px;
  font-weight: 650;
  text-decoration: none;
  white-space: nowrap;
}

.head-link:hover { text-decoration: underline; text-underline-offset: 3px; }

/* ---------- pod performance ---------- */

.perf { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 12px; }

/* The embedded Grafana. A fixed height rather than an aspect ratio: dashboards are tall and the
   card sits in a column, so a ratio would give it an absurd height on a wide screen. Resizable
   because one number of rows never suits every dashboard. */
.grafana-frame { margin-top: 16px; }
.grafana-frame iframe {
  width: 100%;
  height: 620px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
  display: block;
  resize: vertical;
  overflow: auto;
}


.perf-stat { display: grid; gap: 2px; }
.perf-stat dt { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }

/* Marks the figures that carry an explanation, so it is discoverable rather than a hover people
   have to guess at. */
.perf-hint {
  display: inline-grid;
  place-items: center;
  width: 13px; height: 13px;
  border: 1px solid var(--line);
  border-radius: 50%;
  font-size: 9px;
  color: var(--muted);
  cursor: help;
  vertical-align: 1px;
}

.perf-stat:hover .perf-hint { border-color: var(--accent); color: var(--accent); }
.perf-stat dd { margin: 0; font-family: var(--mono); font-size: 19px; font-weight: 700; }
.perf-stat small { display: block; font-family: var(--sans); font-size: 11px; font-weight: 400; color: var(--muted); }

.perf-stat.is-warn dd { color: var(--warn); }
.perf-stat.is-bad dd { color: var(--bad); }

/* ---------- filters ---------- */

/* The row of controls above a table. Wraps rather than overflows, and the count is pushed to the
   far end so it stays where the eye already looks for it whatever else is in the row. */
.filter-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.filter-bar > .stamp { margin-left: auto; }
.filter-bar .search { flex: 1 1 200px; min-width: 0; }

/* Something the row names but cannot open: a guest, an erased account, a bundle with no App Store
   id. Quieter than a value and visibly not a link, so it does not read as missing data. */
.is-nobody { color: var(--muted); font-style: italic; }

/* A cell that opens something. Styled as text rather than as a button because a table full of
   buttons is a table nobody can read. */
.linkish {
  border: 0; padding: 0; background: none;
  font: inherit; color: var(--text);
  text-align: left; cursor: pointer;
  border-bottom: 1px dashed var(--line);
  max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.linkish:hover { color: var(--accent); border-bottom-color: var(--accent); }

/* ---------- game log, rounds, bundles, support ---------- */

/* Ten columns since the log learned to say how a game went, which is two more than fit a laptop.
   It scrolls inside its own card rather than being clipped by it — the same answer the members
   table needed, and for the same reason: a half-visible column is worse than one you scroll to. */
.plays .row {
  grid-template-columns:
    minmax(96px, 1.1fr) minmax(120px, 1.4fr) minmax(104px, 1.1fr) 96px 82px
    minmax(140px, 1.5fr) minmax(96px, 1fr) 104px 116px 92px;
}

.table.plays { overflow-x: auto; }
.table.plays .row { min-width: 1166px; }

/* Every name in this column is a way through to that player's whole history, and the console's
   usual .linkish underline is invisible until the pointer is on it. A door nobody can see is a
   door nobody opens — so this one shows, quietly, before it is hovered. */
.plays .linkish { border-bottom-color: rgba(20, 184, 166, 0.4); }

/* Player search. Three tables, one question: who matched, how their rounds went, and what they
   started. The plays table here is the game log's minus the audio column — the question on this
   screen is who somebody is, and whether they had music on is not part of the answer. */
.people .row { grid-template-columns: minmax(100px, 1.2fr) minmax(140px, 1.5fr) 62px 66px 54px 74px 62px 62px 104px 104px; }
.player-rounds .row { grid-template-columns: minmax(100px, 1.2fr) 96px 84px 96px 74px 60px 74px minmax(96px, 1fr) 100px; }
.player-plays .row { grid-template-columns: minmax(90px, 1fr) minmax(110px, 1.2fr) minmax(150px, 1.6fr) 84px minmax(96px, 1fr) 108px 96px; }
.deaths .row { grid-template-columns: 74px minmax(140px, 1.4fr) 72px 82px 92px 78px 92px; }
.products .row { grid-template-columns: minmax(120px, 1.4fr) 70px 78px 84px minmax(140px, 1.4fr) minmax(120px, 1.2fr) 84px 116px 148px; }
.support .row { grid-template-columns: 92px 88px minmax(160px, 2fr) minmax(140px, 1.4fr) minmax(100px, 1fr) 88px 132px; }

/* A card heading that is inside a flush card, so it needs the padding the card gave up. */
.card-head-row.inset { padding: 14px 14px 10px; }
.card-head-row.inset h2 { margin-bottom: 0; }

.ticket-subject { font-weight: 600; }

/* ---------- balance ---------- */

/* One card per map, in the console's own language — this was a bare bordered stack with an
   accent-coloured h3 and a full-width primary bar for a button, which matched nothing else here. */
.tuning-group { display: grid; gap: 14px; }
.tuning-group.is-dirty { border-color: rgba(247, 185, 85, 0.45); }

.tuning-row {
  display: grid;
  grid-template-columns: 1fr 62px 104px;
  grid-template-areas: "label value marks" "effect effect effect";
  gap: 4px 12px;
  align-items: center;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line-soft);
}

.tuning-row:last-of-type { padding-bottom: 0; border-bottom: 0; }
/* min-width: 0 because a grid item defaults to min-content, and a range input's intrinsic width
   is wider than the phone column it is in — enough to make the label its own two-pixel scroller. */
.tuning-row > .field { grid-area: label; min-width: 0; }
.tuning-value { grid-area: value; }
.tuning-marks { grid-area: marks; display: grid; gap: 1px; justify-items: end; }

/* Stacked, not spread. `.field > span` lays a label and its hint out as a space-between row,
   which is right for a short suffix and wrong here: on a wide screen it put "how much is on the
   map, for its size" a thousand pixels away from the word "Food", and both then read as captions
   for whatever happened to be beneath them. */
.field > .tuning-name { display: grid; gap: 2px; justify-content: start; }
.tuning-name small { color: var(--muted); font-size: 11px; font-weight: 400; }
/* margin-inline: 0 because the UA stylesheet gives a range input a couple of pixels either side,
   which on top of width: 100% made every label a two-pixel horizontal scroller on a phone. */
.tuning-row input[type="range"] {
  width: 100%;
  accent-color: var(--accent);
  margin: 6px 0 0;
}
.tuning-value { font-family: var(--mono); font-weight: 700; font-size: 14px; text-align: right; }

/* Says what the default is and whether this dial is on it. Without it every value is a bare
   percentage with nothing to judge it against. */
.tuning-default { font-size: 10.5px; color: var(--muted); text-align: right; }
.tuning-row.is-off-default .tuning-value { color: var(--warn); }
.tuning-row.is-off-default .tuning-default { color: var(--warn); }

/* What the dial was before this visit touched it. Off-default and moved are different states and
   need different colours: a map can sit off its default for months quite deliberately, where a
   dial that has moved since the page loaded is unsaved work. */
.tuning-was { font-size: 10.5px; color: var(--blue); text-align: right; }

/* The consequence, in the game's terms. Grid-placed on its own row under the slider so it lines
   up with the label rather than with the number. */
.tuning-effect {
  grid-area: effect;
  margin: 2px 0 0;
  font-size: 11.5px;
  color: var(--dim);
}

.tuning-effect.is-moved { color: var(--text); }

.tuning-actions { flex-wrap: wrap; }
.tuning-summary { margin: 0; font-size: 12px; color: var(--muted); }

.stack.wide { gap: 16px; }

/* Wide enough to read, narrow enough that a slider and the number it sets are the same glance
   apart. A full-width row on a 1440 screen put "100%" nine hundred pixels from its own dial.
   Capped on the whole view rather than on the cards, so the line above them keeps their edge. */
.view[data-view="balance"] { max-width: 860px; }

/* ---------- one support request ---------- */

.ticket-dialog { max-width: 720px; }

.ticket-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin: 12px 0 16px; }
.ticket-facts dt { font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); margin-bottom: 3px; }
.ticket-facts dd { margin: 0; font-size: 13px; }

/* The message, readable. It used to be a table cell with white-space: nowrap, so a paragraph
   became a single 1,800-pixel line inside a 730-pixel box. */
.ticket-message {
  margin: 0 0 16px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel-2);
  font-size: 13px;
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.ticket-actions { flex-wrap: wrap; }

/* The conversation so far. Bordered rather than indented: a thread that reads as a quote reads as
   the player's words, and these are ours. */
.ticket-thread { display: grid; gap: 10px; margin: 0 0 16px; }
.ticket-reply-item {
  border: 1px solid var(--line, rgba(255, 255, 255, 0.12));
  border-left: 3px solid var(--accent, #14b8a6);
  border-radius: 8px;
  padding: 10px 12px;
}
.ticket-reply-who {
  margin: 0 0 4px; font-size: 11px; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--muted);
}
.ticket-reply-who.is-bad { color: var(--danger, #ff4d6d); }
.ticket-reply-body { margin: 0; font-size: 13px; line-height: 1.55; white-space: pre-wrap; }

.ticket-reply-label {
  display: block; font-size: 10px; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 6px;
}
.ticket-reply {
  width: 100%; box-sizing: border-box; resize: vertical; margin-bottom: 12px;
  font: inherit; font-size: 13px; line-height: 1.55; padding: 10px 12px;
  color: inherit; background: var(--panel-soft, rgba(255, 255, 255, 0.05));
  border: 1px solid var(--line, rgba(255, 255, 255, 0.12)); border-radius: 8px;
}
.ticket-reply:focus-visible { outline: 2px solid var(--accent, #14b8a6); outline-offset: 1px; }

/* ---------- platform status ---------- */

/* Several platform statuses are a sentence rather than a state. They were being set in a pill,
   which is badge type at 11px on a coloured lozenge — unreadable at that length, and it made a
   plain explanation look like a warning. */
.platform-status { margin: 0 0 6px; font-size: 12.5px; color: var(--dim); line-height: 1.5; }
.platform-status.is-warn { color: var(--warn); }
.note.is-warn { color: var(--warn); }
.note.is-bad { color: var(--bad); }

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

/* Measured rather than eyeballed, by walking the DOM for boxes whose right edge passes the
   viewport. Two real breaks turned up at 390px, and both are here. */

@media (max-width: 860px) {
  /* The topbar could not shrink below 420px in a 390px viewport, because the stamp is nowrap and
     the actions never wrapped — which put the Refresh button 16px off the right edge of a sticky,
     backdrop-filtered header. Letting the pair drop below the title fixes it without hiding
     anything. */
  .topbar { flex-wrap: wrap; min-width: 0; }
  .topbar-title { flex: 1 1 100%; order: -1; }
  .topbar-actions { flex: 1 1 auto; }

  /* A table with min-width: 760px inside a plain card had nowhere to scroll, so it pushed the
     whole page sideways: the overview's document was 980px wide in a 390px viewport. The flush
     cards already had a scroller; the plain ones did not. */
  .card > .table { max-width: 100%; }
  .card { overflow-x: auto; }
}

@media (max-width: 620px) {
  /* Hints under the value rather than beside it — at this width there is no room for three
     columns and the readout was being squeezed into two characters. */
  .tuning-row {
    grid-template-columns: 1fr auto;
    grid-template-areas: "label value" "marks marks" "effect effect";
  }
  /* Side by side on one line here rather than stacked, and packed to the left: two auto columns
     in a full-width grid would otherwise spread, leaving "default" adrift in the middle. */
  .tuning-marks { justify-items: start; justify-content: start; grid-auto-flow: column; gap: 10px; }
  .tuning-was:empty { display: none; }
  .tuning-default, .tuning-was { text-align: left; }
  .filter-bar .search { flex-basis: 100%; }
  .filter-bar > .stamp { margin-left: 0; }
}

/* The bot name list. A chip each rather than a comma-separated textarea, because the thing being
   edited is a list and a stray comma in a textarea silently loses an entry. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 12px;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel-2);
  padding: 4px 6px 4px 12px;
  font-size: 13px;
  white-space: nowrap;
}

.chip-remove {
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 15px;
  line-height: 1;
  padding: 2px 6px;
  cursor: pointer;
}

.chip-remove:hover { background: rgba(255, 255, 255, 0.08); color: var(--text); }
.chip-remove:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The Year in review switch.
 *
 * One control in a card of prose, where every other checkbox in this console sits inside a
 * .field-grid and gets its spacing from the grid. Without this the box is jammed between the
 * paragraph above it and the line below, and the three read as one block of text with a tick in
 * the middle rather than as a decision with its explanation. Caught by looking at the page. */
#review-card .field.checkbox { margin: 16px 0 6px; }
#review-card .field.checkbox span { font-size: 14px; color: var(--text); }

.music-upload {
  display: grid;
  grid-template-columns: minmax(180px, 1fr) minmax(150px, 220px) minmax(220px, 1fr) auto;
  gap: 12px;
  align-items: end;
}
.music-library { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
.music-scene { min-width: 0; padding: 18px; border-right: 1px solid var(--line); }
.music-scene:last-child { border-right: 0; }
.music-scene h3 { margin: 0 0 14px; }
.music-track { display: grid; gap: 9px; padding: 12px 0; border-top: 1px solid var(--line); }
.music-track strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.music-track audio { width: 100%; height: 34px; }
.music-track .button { justify-self: start; }

@media (max-width: 900px) {
  .music-upload { grid-template-columns: 1fr 1fr; }
  .music-library { grid-template-columns: 1fr; }
  .music-scene { border-right: 0; border-bottom: 1px solid var(--line); }
}

@media (max-width: 560px) {
  .music-upload { grid-template-columns: 1fr; }
}

/* =============================================================================================
   Operations, safety, money and the dossier.
   ============================================================================================= */

/* A heading between two metric strips, so "Today" and "Over the last 30 days" cannot be read as
   one row of numbers. Quiet, because it is a label rather than a card title. */
.section-heading {
  margin: 0;
  font-family: var(--sans);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---------- the attention strip ---------- */

.attention { display: grid; gap: 8px; }

/* Severity is a word, a shape and a colour — never a colour alone. The left bar is the shape and
   the badge is the word, so the strip triages identically for somebody who cannot separate the
   amber from the red. */
.attention-item {
  display: grid;
  grid-template-columns: 78px 1fr;
  gap: 12px;
  align-items: center;
  width: 100%;
  min-height: 44px;
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-left-width: 4px;
  border-radius: var(--radius);
  background: var(--panel);
  color: inherit;
  font: inherit;
  text-align: left;
}

button.attention-item { cursor: pointer; }
button.attention-item:hover { background: var(--panel-2); }

.attention-level {
  justify-self: start;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: rgba(255, 255, 255, 0.07);
  color: var(--dim);
}

.attention-words { display: grid; gap: 1px; min-width: 0; }
.attention-words strong { font-size: 13.5px; font-weight: 700; }
.attention-words small { font-size: 11.5px; color: var(--muted); }

/* A foreign origin is named in the detail line, and it is a hostname somebody else chose — up to
   the 128 characters the column holds, with no spaces in it. Without this an unbroken one pushes
   the strip off the side of the overview, which is the first screen an operator opens. */
.attention-words small { overflow-wrap: anywhere; }

.attention-item.is-bad { border-left-color: var(--bad); }
.attention-item.is-bad .attention-level { background: rgba(255, 122, 102, 0.18); color: var(--bad); }
.attention-item.is-warn { border-left-color: var(--warn); }
.attention-item.is-warn .attention-level { background: rgba(247, 185, 85, 0.18); color: var(--warn); }
.attention-item.is-good { border-left-color: var(--good); }
.attention-item.is-good .attention-level { background: rgba(56, 211, 159, 0.16); color: var(--good); }

/* ---------- queue counts in the rail ---------- */

/* A number, not a dot. "3" and "31" are different amounts of work and a dot says neither; the
   title attribute says it in words for anybody who cannot see the badge at all. */
.nav-count {
  margin-left: auto;
  min-width: 20px;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  color: var(--dim);
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 700;
  text-align: center;
}

.nav-count.is-urgent { background: rgba(255, 122, 102, 0.2); color: var(--bad); }

/* ---------- reports ---------- */

/* The queue had no column template at all, so every cell stacked and one report was nine lines
   tall. Nine columns now, and it scrolls inside its own card rather than being clipped by it. */
.safety .row {
  grid-template-columns:
    26px 84px 62px minmax(110px, 1.2fr) 96px 62px minmax(150px, 2fr) 92px minmax(230px, 1.1fr);
}

.table.safety { overflow-x: auto; }
.table.safety .row { min-width: 1090px; }

/* The urgent card is the same rows without the tick or the flag — everything in it is urgent, so
   a column repeating that on every line is a column saying nothing. */
#safety-urgent .row { grid-template-columns: 84px minmax(110px, 1.2fr) 96px minmax(150px, 2fr) 92px minmax(230px, 1.1fr); }
#safety-urgent.table .row { min-width: 860px; }

.card.urgent { border-color: rgba(255, 122, 102, 0.4); }
.card.urgent > h2, .card.urgent .card-head-row h2 { color: var(--bad); }

.safety .row.is-urgent { background: rgba(255, 122, 102, 0.05); }

.appeals .row { grid-template-columns: 92px minmax(180px, 1.2fr) minmax(180px, 2fr) 92px 168px; }
.audit .row { grid-template-columns: 116px minmax(110px, 1.2fr) 100px 96px minmax(110px, 1.2fr) 96px 132px; }

.table.audit { overflow-x: auto; }
.table.audit .row { min-width: 900px; }

/* What was decided, and by whom, in one cell. The second line is the half that was missing. */
.decision-cell { display: grid; gap: 1px; align-content: center; overflow: hidden; }
.decision-cell > .pill { justify-self: start; }
.decision-cell small { font-size: 10.5px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* A 44px target for a checkbox that is 16px, because the row is what somebody aims at. */
.row-tick { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); cursor: pointer; }
.row-tick:disabled { cursor: default; opacity: 0.35; }

.bulk-bar {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
  background: var(--accent-soft);
}

.bulk-bar.is-busy { opacity: 0.6; pointer-events: none; }
.bulk-count { font-weight: 700; }
.bulk-bar select { min-height: 32px; }

/* ---------- money ---------- */

.money-slices .row { grid-template-columns: 110px 104px 92px 62px 82px 104px 96px; }
.cosmetics .row { grid-template-columns: minmax(140px, 1.6fr) 132px 84px 96px 104px; }

/* Devices: the name and its raw identifier share the first column, so it gets the room. Nine
   columns is a lot, and the alternative was hiding the hardware — which is the column that turns
   "this model is slow" into "phones with four gigabytes are slow". */
.devices .row {
  grid-template-columns:
    minmax(150px, 1.7fr) 68px 72px 96px 104px 96px 96px 108px minmax(120px, 1fr);
}

/* Foreign origins: sites calling this server as though it were their own. The origin gets the
   room because it is the answer — a domain is what an operator reads, decides about, and looks up.
   It is also the most directly attacker-chosen string on any screen here, so it is rendered as
   text and never as markup; `word-break` is what stops a deliberately enormous one from stretching
   the table off the side of the page. */
.origins .row {
  grid-template-columns: minmax(180px, 2fr) 168px 92px 108px 108px minmax(90px, 1fr);
}
.origins .row > span:first-child { overflow-wrap: anywhere; word-break: break-word; }

/* An origin that is still here a day later is not a crawler. Tinted as well as counted, because
   the point of the screen is picking those out of a list of one-off visitors at a glance — and
   the tint is never the only signal: the metric above says how many, and the span between the two
   date columns is the reason. */
.origins .row.is-sustained { background: rgba(255, 122, 102, 0.07); }

/* Two lines in one cell: the thing, and what it is really called underneath. */
.stacked { display: grid; gap: 1px; min-width: 0; }
.stacked small { color: var(--muted); font-family: var(--mono); font-size: 10.5px; }

/* Sandbox is money that was never paid, so a row of it is tinted rather than merely labelled —
   the label is still there and still does the work, this is what makes a block of them obvious
   at the scale somebody scans a table. */
.money-slices .row.is-sandbox { background: rgba(247, 185, 85, 0.06); }

.banner.is-warn { border-color: rgba(247, 185, 85, 0.45); color: var(--warn); }

/* ---------- coin debt ---------- */

/* Deliberately not styled like the reports queue. Nobody on this screen has been reported, and a
   table that looked like the moderation queue is how somebody ends up being told they were. */
.debt .row { grid-template-columns: minmax(110px, 1.2fr) minmax(140px, 1.5fr) 78px 156px minmax(140px, 1.2fr) 104px 232px; }

.table.debt { overflow-x: auto; }
.table.debt .row { min-width: 1060px; }

/* Owing is a state, not an alarm: a quiet left edge rather than a red row. What is red is the
   write-off button, which is the only thing here that spends money. */
.debt .row.is-owing { box-shadow: inset 3px 0 0 var(--warn); }

.note.inset { padding: 0 14px 10px; }

/* ---------- the dossier ---------- */

.dossier-dialog { max-width: 1040px; }
.dossier-flags { display: grid; gap: 8px; margin-bottom: 14px; }
.dossier-body { display: grid; gap: 18px; margin-top: 18px; }

.dossier-section {
  display: grid;
  gap: 10px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

.dossier-section h3 { font-size: 14px; }

/* The debt panel is boxed and the conduct panels are not, because the one mistake that must not
   happen on this dialog is reading a refund as misconduct. */
.dossier-section.is-debt {
  padding: 16px;
  border: 1px solid rgba(247, 185, 85, 0.35);
  border-radius: var(--radius);
  background: rgba(247, 185, 85, 0.04);
}

.dossier-names .row { grid-template-columns: minmax(120px, 1.4fr) 92px 156px; }
.dossier-decisions .row { grid-template-columns: 156px 110px 110px minmax(120px, 1.2fr) 148px; }
.dossier-reports .row { grid-template-columns: 84px 62px minmax(110px, 1.2fr) 100px minmax(150px, 2fr) 148px 156px; }
.dossier-purchases .row { grid-template-columns: 156px 62px 84px 90px 104px 104px; }
.dossier-coins .row { grid-template-columns: 156px 78px 116px 104px minmax(140px, 1.6fr); }
.dossier-unlocks .row { grid-template-columns: minmax(140px, 1.6fr) 92px 156px; }
.dossier-rounds .row { grid-template-columns: 156px 88px 96px 96px 62px minmax(110px, 1.2fr); }
.dossier-passkeys .row { grid-template-columns: minmax(120px, 1.3fr) minmax(120px, 1.2fr) 116px 116px; }

.dossier-body .table { overflow-x: auto; }
.dossier-body .table .row { min-width: 620px; }

@media (max-width: 900px) {
  .attention-item { grid-template-columns: 1fr; gap: 4px; }
  .dossier-body .table .row { min-width: 560px; }
}

/* Compliance.

   A long document on a console built for tables, so the one thing this has to get right is line
   length: a risk assessment read at the full width of a 27-inch monitor is a risk assessment
   nobody finishes. The body is capped at a reading measure and everything else follows from it.

   The review stamps sit above the prose rather than inside it. The date is the reason this screen
   exists — the document itself is in the repository and always was. */
.compliance-head { padding-bottom: 14px; border-bottom: 1px solid var(--line-soft); margin-bottom: 18px; }
.compliance-head h2 { margin-bottom: 6px; }
.compliance-head .hint { margin: 0 0 10px; max-width: 68ch; }

.compliance-stamps { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }

.compliance-body { max-width: 78ch; font-size: 13.5px; line-height: 1.62; color: var(--dim); }
.compliance-body h2,
.compliance-body h3,
.compliance-body h4 { color: var(--text); text-transform: none; letter-spacing: 0; }
.compliance-body h2 { font-size: 17px; margin: 30px 0 10px; }
.compliance-body h3 { font-size: 14.5px; margin: 22px 0 8px; }
.compliance-body h4 { font-size: 13px; margin: 18px 0 6px; }
.compliance-body p { margin: 0 0 12px; }
.compliance-body strong { color: var(--text); font-weight: 600; }
.compliance-body code {
  font-family: var(--mono);
  font-size: 12px;
  padding: 1px 4px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.05);
}
.compliance-body ul,
.compliance-body ol { margin: 0 0 14px; padding-left: 22px; }
.compliance-body li { margin-bottom: 6px; }
.compliance-body hr { border: 0; border-top: 1px solid var(--line-soft); margin: 24px 0; }
.compliance-body blockquote {
  margin: 0 0 14px;
  padding: 10px 14px;
  border-left: 2px solid var(--warn);
  background: rgba(247, 185, 85, 0.05);
  border-radius: 0 var(--radius) var(--radius) 0;
}

/* Wide tables scroll in their own box. Two of these documents are mostly table, and one is a
   seven-column risk grid that would otherwise push the whole page sideways. */
.compliance-body .md-scroll { overflow-x: auto; margin: 0 0 16px; }
.compliance-body table { border-collapse: collapse; min-width: 520px; width: 100%; font-size: 12.5px; }
.compliance-body th,
.compliance-body td {
  border: 1px solid var(--line-soft);
  padding: 7px 10px;
  text-align: left;
  vertical-align: top;
}
.compliance-body th {
  color: var(--muted);
  font-weight: 600;
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}

.companions .row { grid-template-columns: minmax(180px, 1fr) minmax(220px, 2.4fr); }
.companions .row > span:first-child { font-family: var(--mono); font-size: 12px; }
.companions .row > span:last-child { white-space: normal; }


/* ---------- one report, opened ----------
 *
 * The queue is a list of accusations and a list is the wrong shape for the moment somebody decides
 * one: the free text was truncated into a cell, the reporter was never shown at all, and no other
 * report by the same person was anywhere near the decision. This is where those live. */

.table .row.is-openable { cursor: pointer; }

.table .row.is-openable:hover { background: rgba(255, 255, 255, 0.05); }

.table .row.is-openable:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Which row is open, so the queue and the card below it agree without the reader having to
   remember what they clicked. */
.table .row.is-open { background: rgba(255, 255, 255, 0.08); }

.report-detail { display: grid; gap: 14px; }

.report-detail h3 {
  margin: 4px 0 0;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  opacity: .62;
}

/* Label and value, not a table: there is exactly one of each of these, and a header over a single
   row is a table pretending to be a list. */
.report-facts {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 6px 18px;
  margin: 0;
}

.report-facts dt {
  font-size: 12px;
  letter-spacing: .04em;
  text-transform: uppercase;
  opacity: .55;
}

.report-facts dd { margin: 0; min-width: 0; word-break: break-word; }

.report-status { display: inline-flex; align-items: center; gap: 6px; }

/* In full and wrapped. This is the only place in the feature the allegation is readable without a
   tooltip, which is the point of the card. */
.report-said {
  margin: 0;
  padding: 12px 14px;
  border: 1px solid var(--line, rgba(255, 255, 255, 0.12));
  border-radius: 10px;
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-word;
}

.report-related { display: grid; gap: 8px; }

.report-related-list {
  display: grid;
  gap: 5px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.report-related-list li { font-size: 13px; }

.report-related-rest { opacity: .68; }

/* A setting with a closed set of legal values, offered as buttons rather than as a box to type
   into. The season is the one that has this: a text field whose placeholder said "Set value" was
   not a toggle, and it was reported twice as the server being unable to turn a season on by hand
   while the mechanism underneath worked perfectly. */
.setting-choices {
  display: flex;
  margin-top: 8px;
  flex-wrap: wrap;
  gap: 8px;
}

/* The one in force. A border and a fill rather than colour alone — this is an admin page and the
   question it answers is "which of these is on", which must survive being read quickly. */
.setting-choices .choice.is-on {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  font-weight: 700;
}

.setting-choices .choice[disabled] { opacity: .55; }

/* One game, opened. The three tables that each hold a third of it, drawn as groups so a missing
   one can say WHY it is missing rather than showing a column of dashes — "no device report" and
   "a device that reported zeroes" are different facts. */

.play-detail { margin-bottom: 12px; }
.play-detail h3 { margin: 0 0 8px; font-size: 13px; letter-spacing: .04em; text-transform: uppercase; }

.play-detail-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 8px 18px;
  margin: 0;
}

.play-detail-grid dt {
  font-size: 11px;
  letter-spacing: .03em;
  text-transform: uppercase;
  opacity: .62;
}

.play-detail-grid dd {
  margin: 2px 0 0;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}

/* A row that opens something says so before it is clicked. */
.plays .is-openable { cursor: pointer; }
.plays .is-openable:hover { background: rgba(255, 255, 255, .05); }
.plays .is-openable:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }
