/* admin.css — the staff console's own surface.
 *
 * Loaded ONLY by the admin page. Nothing here dresses a player-facing surface,
 * so a console that grows a control does not widen the sheet every other page
 * downloads.
 *
 * IT DRESSES FROM THE SHARED SHEET WHEREVER ONE EXISTS. Buttons are .btn /
 * .btn-primary / .btn-ghost, inputs are bare elements (site.css styles them by
 * tag), and the page frame is .wrap / .section / .sec-head / .eyebrow /
 * .page-h1 / .dim. What is HERE is what site.css genuinely does not have: a
 * panel, a data table, a field grid, and an odds readout.
 *
 * ZERO COLOUR LITERALS. Role tokens only, per webcore's stylesheet law — the
 * frontend suite scans this file for a hex or an rgb() and fails on either.
 *
 * =========================================================================
 * THE [hidden] CANCELS ARE LOAD-BEARING, NOT TIDINESS
 * =========================================================================
 * The browser's own `[hidden] { display: none }` is a UA rule, and ANY author
 * `display` outranks it. adminrail.css gives `#admin-root` a `display: grid`
 * and this sheet gives the panels and rows a `display` of their own, so every
 * one of them needs its own cancel or it ships permanently visible. That is
 * not hypothetical: a console in this fleet leaked an admin tab to every
 * visitor because a `display` rule beat the `hidden` attribute, and the
 * attribute looked correct in the markup the whole time.
 *
 * `!important` here beats even an inline style, which is deliberate: the
 * fragment ALSO ships `style="display:none"` so the console stays dark on a
 * page whose stylesheet has not arrived, and the two must agree in both
 * directions rather than fight.
 */

/* ---------- the shell ---------- */

#admin-root { margin-top: 30px; }
#admin-root[hidden] { display: none !important; }

.admin-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px 18px;
  margin-bottom: 18px;
}
.admin-head[hidden] { display: none !important; }

#admin-who {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
}

/* ---------- one panel ---------- */

#admin-root > .panel {
  padding: 22px 22px 24px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  box-shadow: var(--shadow-panel);
}
#admin-root > .panel[hidden] { display: none !important; }

.admin-panel-head { margin-bottom: 18px; }
.admin-panel-head p { margin-top: 6px; }

/* ---------- the action bar ---------- */

.admin-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
  padding-bottom: 16px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--border);
}
.admin-bar[hidden] { display: none !important; }
.admin-spacer { flex: 1 1 auto; }

.admin-filter { min-width: 0; flex: 1 1 190px; max-width: 320px; }

.admin-switch {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--fg-dim);
}
.admin-switch input { margin: 0; }
.admin-switch:hover { color: var(--fg); }

/* ---------- status, refusal, hint ---------- */

.admin-note {
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--fg-mute);
}
.admin-note[hidden] { display: none !important; }
.admin-note.ok { color: var(--ok); }
.admin-note.warn { color: var(--warn); }

/* A refusal is the SERVER'S sentence, rendered verbatim. It never replaces the
   status line — an owner has to be able to see both what was refused and what
   the panel currently holds. */
.admin-error {
  margin-top: 10px;
  padding: 10px 13px;
  border: 1px solid var(--bad-dim);
  border-radius: var(--r-s);
  background: var(--surface-sink);
  color: var(--bad);
  font-family: var(--font-ui);
  font-size: 13px;
  line-height: 1.45;
}
.admin-error[hidden] { display: none !important; }

.admin-hint {
  margin-top: 6px;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--fg-mute);
  line-height: 1.45;
}

/* ---------- the data table ---------- */
/* site.css defines no table rules at all, so every one of these is this
   sheet's own. The wrapper scrolls rather than the page: a console table is
   wide by nature and a body that scrolls sideways is the defect that hides. */

.admin-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-ui);
  font-size: 13px;
}
.admin-table[hidden] { display: none !important; }

.admin-table th,
.admin-table td {
  padding: 9px 12px;
  text-align: left;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.admin-table th {
  font-family: var(--font-display);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-mute);
  border-bottom-color: var(--border-2);
}
.admin-table th.num,
.admin-table td.num { text-align: right; }
.admin-table th.mid,
.admin-table td.mid { text-align: center; }
.admin-table tbody tr:hover { background: var(--surface-hover); }
.admin-table tr[hidden] { display: none !important; }

.admin-table td.name {
  white-space: normal;
  color: var(--fg);
  font-weight: 500;
}
.admin-table .diet {
  display: block;
  font-size: 11px;
  color: var(--fg-mute);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* A row's own id under its label. Every refusal the quest lane returns names
   the row BY ID, so the id has to be on screen exactly as it is written -
   mono, and never folded to the label's case. */
.admin-table .ident {
  display: block;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-mute);
}

/* A cell an owner has edited but not saved. The mark is a left edge rather
   than a fill: a fill on a row that is also hovered reads as selection. */
.admin-table tr.dirty td:first-child { box-shadow: inset 2px 0 0 var(--accent); }

.admin-table input[type="number"] { width: 92px; text-align: right; }

/* NO CAP is the honest word for a cap of zero or less, and it is written where
   the number would be so nobody has to remember which way round it reads. */
.admin-nocap {
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-mute);
}
/* It is toggled by the controller, and it is the label that carries the whole
   meaning of a zero. Nothing may leave it standing beside a real cap. */
.admin-nocap[hidden] { display: none !important; }

.admin-live {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--fg-dim);
}
.admin-live.hot { color: var(--warn); }
.admin-live.unknown { color: var(--fg-mute); }

/* ---------- the field grid ---------- */

.admin-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 16px 20px;
}
.admin-fields[hidden] { display: none !important; }

.admin-field { min-width: 0; }
.admin-field label,
.admin-field-label {
  display: block;
  margin-bottom: 6px;
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 500;
  color: var(--fg-dim);
}
.admin-field input,
.admin-field select { width: 100%; }
.admin-field.dirty label { color: var(--accent); }
.admin-field[hidden] { display: none !important; }

.admin-inline {
  display: flex;
  gap: 10px;
  align-items: center;
}
.admin-inline input { flex: 1 1 auto; min-width: 0; }

.admin-figure {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 19px;
  color: var(--fg);
}

/* ---------- the odds readout ---------- */
/* Every number in here is computed by the SERVER from the same values the
   tables deal on. This sheet only lays them out. */

.admin-odds {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
  margin-bottom: 18px;
}
.admin-odds[hidden] { display: none !important; }

.admin-odd {
  padding: 12px 14px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-s);
}
/* ★ ONE TILE can now be hidden on its own - the points console drops the SPIN
   tile for a box whose wheel has never run - and the grid's own cancel does not
   reach a child. `.admin-odd b` carries a `display`, and an author `display`
   outranks the browser's `[hidden]` rule; this is the same protection
   `.admin-odds` has always had, one level down. */
.admin-odd[hidden] { display: none !important; }
.admin-odd b {
  display: block;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 18px;
  color: var(--fg);
}
.admin-odd span {
  display: block;
  margin-top: 3px;
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-mute);
}
/* An edge at or below zero is the house losing money on every round. It is
   never merely a number on this page. */
.admin-odd.bad { border-color: var(--bad-dim); }
.admin-odd.bad b { color: var(--bad); }

.admin-games {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 14px;
  margin-bottom: 18px;
}
.admin-games[hidden] { display: none !important; }

/* ---------- the shop price list ---------- */

.admin-shop-section { margin-bottom: 26px; }
.admin-shop-section h3 {
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-mute);
  margin-bottom: 10px;
}
.admin-price-lane {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.admin-price-lane em {
  font-style: normal;
  font-family: var(--font-ui);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg-mute);
}
.admin-price-lane input[type="number"] { width: 104px; text-align: right; }

/* ---------- sub-blocks ----------
   A `.admin-sub` is a second section INSIDE one tab: the tick interval and the
   tier ladder under Earnings, the spin bank and the history under Points. The
   class has been in the markup since the Earnings tab grew its three sections
   and NEVER HAD A RULE — every one of those blocks ran straight into the one
   above it, so a panel with three headings read as one wall of controls. The
   rule is separation only (space and a hairline); it invents no colour and no
   type, so the blocks that already shipped keep their exact appearance apart
   from finally being told apart. */

.admin-sub {
  margin-top: 28px;
  padding-top: 22px;
  border-top: 1px solid var(--border);
}
.admin-sub[hidden] { display: none !important; }
/* The first block under a panel head does not need a line above it: the head's
   own lede already separates it, and a rule there reads as a stray divider. */
.admin-panel-head + .admin-sub { border-top: 0; padding-top: 0; }

/* ---------- points console ----------
   A MOVEMENT IS READ BY ITS SIGN BEFORE IT IS READ AS A NUMBER. The site has no
   "good" token, so a credit takes the accent (its one positive colour) and a
   debit takes `--bad`; the SIGN is always printed too, because colour alone is
   not a readable difference for everybody and a red 500 next to a green 500 is
   the same glyph. */

.admin-move { font-variant-numeric: tabular-nums; }
.admin-move.up { color: var(--accent); }
.admin-move.down { color: var(--bad); }

/* The name-search results. Each row is a BUTTON, not a link: picking one runs a
   second lookup in place, and a link would be a navigation that loses the
   panel. */
.admin-pick {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--fg);
  cursor: pointer;
}
.admin-pick:hover,
.admin-pick:focus-visible { color: var(--accent-hi); }
.admin-pick .tail {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-mute);
  margin-left: 8px;
}

/* ---------- lists (recent changes, hidden-colour admins) ---------- */

.admin-recent { margin-top: 24px; }
.admin-recent[hidden] { display: none !important; }
.admin-recent h3 {
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-mute);
  margin-bottom: 8px;
}
.admin-recent ul,
.admin-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.admin-list[hidden] { display: none !important; }
.admin-recent li,
.admin-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 12px;
  padding: 8px 0;
  border-top: 1px solid var(--border);
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--fg-dim);
}
.admin-recent li time,
.admin-list li .who {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-mute);
}

/* ---------- temp skins ----------
   The panel's markup is built by js/rentals.js, which shipped with NO
   stylesheet at all: on the shop page it rendered as a stack of bare labels
   and a naked select, which is most of why it read as something that had been
   left lying there. It lives in the Staff console now (his ruling 2026-08-09)
   and it is dressed like the rest of it - the same field grid, the same list
   rules, the same note colours - so it reads as one console rather than as a
   widget somebody parked. Nothing here is a new visual idea: every value below
   is one already used above in this file. */
.rentals-head { display: none; }          /* the panel's own head says it */

.rentals-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px 18px;
  align-items: end;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--border);
}
.rentals-field { min-width: 0; }
.rentals-field span {
  display: block;
  margin-bottom: 6px;
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 500;
  color: var(--fg-dim);
}
.rentals-field input,
.rentals-field select { width: 100%; }
/* The button is a field of its own in the grid, so it lines up with the
   inputs' baseline instead of hanging under them. */
.rentals-form .btn { align-self: end; }

.rentals-status {
  margin: 12px 0 0;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--fg-dim);
}
.rentals-status:empty { display: none; }
.rentals-status.is-bad { color: var(--bad); }

.rentals-list { margin-top: 6px; }
.rentals-list .dim { font-family: var(--font-ui); font-size: 12px; }

.rentals-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 12px;
  padding: 10px 0;
  border-top: 1px solid var(--border);
}
.rentals-row-main { flex: 1 1 220px; min-width: 0; }
.rentals-row-main h3 {
  margin: 0;
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 600;
  color: var(--fg);
}
.rentals-row-facts {
  margin: 3px 0 0;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-mute);
}
/* A row that is no longer running is stated in ink, never by fill alone. */
.rentals-row.is-expired .rentals-row-main h3,
.rentals-row.is-revoked .rentals-row-main h3 { color: var(--fg-dim); }

/* ---------- zombie console ---------- */
/* The one panel on this page whose rows are PEOPLE rather than settings, so a
   row has to read as a person: the name in ink, the Steam ID under it in the
   mono face an admin copies from. */

.zom-who { display: block; min-width: 0; }
.zom-who b {
  display: block;
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 600;
  color: var(--fg);
  overflow-wrap: anywhere;
}
.zom-sid {
  display: block;
  margin-top: 2px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-mute);
}

/* THE TIER IS STATED IN A WORD, NEVER IN COLOUR ALONE. The badge carries its
   own text, so it survives a monochrome display, a colour-blind reader and a
   printed screenshot; the tint is the second signal, not the only one. */
.zom-badge {
  display: inline-block;
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 2px;
  font-family: var(--font-ui);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--fg-dim);
}
.zom-badge[data-tier="zombie"] {
  color: var(--accent-hi);
  border-color: var(--accent);
}
.zom-badge[data-tier="infected"] {
  color: var(--fg-dim);
  border-style: dashed;
}

/* The paste box is a Steam ID field: mono, and wide enough for all 17 digits
   plus a little, so a pasted value is readable end to end before it is sent. */
#zom-sid {
  font-family: var(--font-mono);
  letter-spacing: .04em;
  max-width: 26ch;
}

/* ---------- phone ---------- */

@media (max-width: 720px) {
  #admin-root > .panel { padding: 16px 12px 18px; }
  .admin-bar { gap: 8px 10px; }
  .admin-filter { flex: 1 1 100%; max-width: none; }
  .admin-spacer { display: none; }
  .admin-bar .btn { flex: 1 1 auto; min-width: 0; }
  .admin-table th,
  .admin-table td { padding: 8px 6px; }
  /* THE WHOLE ROW HAS TO FIT A PHONE. The wrapper scrolls, but a control an
     owner has to discover by swiping sideways is a control they do not find,
     and the cap and the lock are the two this page exists for. So NO CAP
     stacks ABOVE its input here instead of sitting beside it, which buys back
     the ~54px that was pushing the lock column off the edge. */
  .admin-nocap { display: block; margin-bottom: 3px; font-size: 10px; }
  .admin-table input[type="number"] { width: 62px; }
  .admin-figure { font-size: 17px; }
}
