:root {
  /* Tells the browser the page is dark, which is what turns scrollbars, date
     pickers and other native controls dark. Styling scrollbars by hand below
     covers the rest; without this the browser's own chrome stayed white on a
     black page. */
  color-scheme: dark;

  /* A neutral near-black with warm off-white text, which is the palette Dark
     Reader produces and the one this was asked to match. The previous default
     was blue-tinted and its borders were nearly invisible; both of those are
     what made the extension's version look better than the real thing. */
  --bg: #121314;
  --surface: #1b1d1e;
  --text: #e0ddd8;
  --text-muted: #8a8782;
  /* Present but not bright. Cards have to separate from the page behind them —
     the old #262b33 did not — without the border becoming the loudest thing
     on screen, which #3a4042 was. */
  --border: #2f3335;
  --accent: #2570e8;
  --accent-text: #ffffff;
  --danger: #e06c66;
  --radius: 10px;

  /* Tinted boxes — a message, a warning, a status pill. A foreground and the
     ground it sits on, so a light theme can swap both. */
  --ok: #4ec66a;
  --ok-bg: #14241a;
  --ok-border: #2c5a38;
  --bad: #e06c66;
  --bad-bg: #241615;
  --bad-border: #5a2b28;
  --warn: #d1a44a;
  --warn-bg: #221d11;
  --warn-border: #4f3f1c;

  --scroll-thumb: #3d4143;
  --scroll-thumb-hover: #4e5355;
  --scroll-track: transparent;
}

/* Scrollbars follow whichever palette they sit in: custom properties inherit,
   so a scrolling box inside the dashboard picks up its values rather than the
   page's. Both syntaxes because Firefox implements one and Chrome the other. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--scroll-thumb) var(--scroll-track);
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-track {
  background: var(--scroll-track);
}

::-webkit-scrollbar-thumb {
  background: var(--scroll-thumb);
  border-radius: 6px;
  /* Inset by a transparent border so the thumb reads as floating in the
     track rather than filling it edge to edge. */
  border: 2px solid transparent;
  background-clip: content-box;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--scroll-thumb-hover);
  background-clip: content-box;
}

::-webkit-scrollbar-corner {
  background: var(--scroll-track);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, sans-serif;
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
}

a {
  color: inherit;
}

.impersonation-banner {
  background: var(--warn-bg);
  border-bottom: 1px solid var(--warn-border);
  color: var(--warn);
  font-size: 13px;
  text-align: center;
  padding: 8px 16px;
}

.impersonation-banner a {
  color: inherit;
  font-weight: 600;
}

.topbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20px 32px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

.topbar .brand {
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none;
}

.topbar .actions {
  display: flex;
  gap: 12px;
  align-items: center;
}

.btn {
  display: inline-block;
  padding: 8px 16px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 14px;
  cursor: pointer;
  text-decoration: none;
}

.btn:hover {
  border-color: #c9c9c9;
}

.btn-primary {
  background: var(--accent);
  color: var(--accent-text);
  border-color: var(--accent);
}

.btn-primary:hover {
  opacity: 0.9;
}

.center-page {
  min-height: calc(100vh - 73px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.card {
  width: 100%;
  max-width: 380px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 32px;
}

.card h1 {
  font-size: 20px;
  margin: 0 0 24px;
}

.field {
  margin-bottom: 16px;
}

.field label {
  display: block;
  font-size: 13px;
  color: var(--text-muted);
  margin-bottom: 6px;
}

.field input {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
  background: var(--bg);
  color: var(--text);
}

.field input:focus {
  outline: none;
  border-color: var(--accent);
}

.form-submit {
  width: 100%;
  margin-top: 8px;
  padding: 10px 12px;
  border-radius: 8px;
  border: none;
  background: var(--accent);
  color: var(--accent-text);
  font-size: 14px;
  cursor: pointer;
}

.form-submit:hover {
  opacity: 0.9;
}

.error {
  background: var(--bad-bg);
  color: var(--danger);
  border: 1px solid var(--bad-border);
  border-radius: 8px;
  padding: 10px 12px;
  font-size: 13px;
  margin-bottom: 16px;
}

.switch-link {
  text-align: center;
  margin-top: 20px;
  font-size: 13px;
  color: var(--text-muted);
}

.content {
  padding: 40px 32px;
  max-width: 900px;
  margin: 0 auto;
}

.hero {
  text-align: center;
  padding: 80px 24px;
}

.hero h1 {
  font-size: 28px;
  margin-bottom: 8px;
}

.hero p {
  color: var(--text-muted);
}

table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 20px;
  font-size: 14px;
}

th, td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
}

th {
  color: var(--text-muted);
  font-weight: 500;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

code {
  background: var(--bg);
  padding: 2px 6px;
  border-radius: 4px;
  font-size: 13px;
}

.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.muted {
  color: var(--text-muted);
}

.success {
  background: var(--ok-bg);
  color: var(--ok);
  border: 1px solid var(--ok-border);
  border-radius: 8px;
  padding: 10px 12px;
  font-size: 13px;
  margin-bottom: 16px;
}

.callout {
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  border-radius: 8px;
  padding: 14px 16px;
  margin: 16px 0;
  font-size: 14px;
}

.callout code {
  display: inline-block;
  margin-top: 6px;
  font-size: 15px;
}

.section {
  margin-bottom: 40px;
}

.section h2 {
  font-size: 16px;
  margin-bottom: 12px;
}

.search-form {
  display: flex;
  gap: 8px;
  align-items: center;
  margin: 20px 0;
}

.inline-form {
  display: flex;
  gap: 8px;
  align-items: flex-end;
  flex-wrap: wrap;
}

.inline-form .field {
  margin-bottom: 0;
  flex: 1;
  min-width: 220px;
}

/* Dashboard: scoped dark theme, independent of the light app chrome above it */
.rcon-dashboard {
  --rd-bg: #0e0f10;
  --rd-input: #0d0f13;
  --rd-panel: #1a1c1d;
  --rd-border: #2c3031;
  --rd-text: #e0ddd8;
  --rd-text-muted: #8a8782;
  --rd-accent: #2570e8;
  /* Semantic colours, as a foreground and the tint it sits on. Tokens rather
     than literals so a light theme can supply its own pair — the dark values
     here are unreadable on a pale panel and vice versa. */
  --rd-good: #6fcf97;
  --rd-good-bg: #16261c;
  --rd-bad: #e8776f;
  --rd-bad-bg: #2d1a19;
  --rd-bad-bg-soft: #201514;
  --rd-warn: #c99a52;
  --rd-warn-bg: #221d11;
  --rd-info: #8fb4ff;
  --rd-info-bg: #141a24;
  --rd-caution: #d1a44a;
  background: var(--rd-bg);
  color: var(--rd-text);
  min-height: calc(100vh - 61px);
  padding: 16px 20px;
  font-size: 13px;
}

.rd-empty {
  color: var(--rd-text-muted);
  padding: 40px 0;
  text-align: center;
}

.rd-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  background: var(--rd-panel);
  border: 1px solid var(--rd-border);
  border-radius: 10px;
  padding: 10px 14px;
  margin-bottom: 16px;
}

.rd-filter-input,
.rd-server-field select,
.rd-log-filters input {
  background: var(--rd-input, #0d0f13);
  border: 1px solid var(--rd-border);
  color: var(--rd-text);
  border-radius: 6px;
  padding: 7px 10px;
  font-size: 13px;
}

.rd-filter-input {
  min-width: 220px;
}

.rd-online {
  color: var(--rd-text-muted);
  font-size: 12px;
  letter-spacing: 0.03em;
  white-space: nowrap;
}

.rd-scope-toggle {
  display: flex;
  border: 1px solid var(--rd-border);
  border-radius: 6px;
  overflow: hidden;
}

.rd-scope-btn {
  padding: 6px 12px;
  font-size: 12px;
  color: var(--rd-text-muted);
  text-decoration: none;
  background: var(--rd-input, #0d0f13);
}

.rd-scope-btn.active {
  background: var(--rd-accent);
  color: #fff;
}

.rd-server-field select {
  min-width: 200px;
}

.rd-columns {
  display: flex;
  flex-wrap: nowrap;
  gap: 16px;
  align-items: flex-start;
  width: 100%;
}

.rd-player-panel,
.rd-logs-panel {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  resize: both;
  overflow: hidden;
  height: calc(100vh - 190px);
  min-height: 320px;
  max-height: 90vh;
}

.rd-player-panel {
  width: calc(40% - 8px);
  min-width: 360px;
  max-width: 75%;
  container-type: inline-size;
  container-name: player-panel;
}

.rd-logs-panel {
  width: calc(60% - 8px);
  min-width: 320px;
  max-width: 75%;
}

.rd-panel {
  background: var(--rd-panel);
  border: 1px solid var(--rd-border);
  border-radius: 10px;
}

.rd-panel-header {
  padding: 10px 14px;
  border-bottom: 1px solid var(--rd-border);
  font-weight: 600;
  font-size: 13px;
  flex: 0 0 auto;
}

/* Header that carries a control on the right as well as its title. */
.rd-panel-header-split {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.rd-offline-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 400;
  font-size: 12px;
  color: var(--rd-text-muted);
  white-space: nowrap;
  cursor: pointer;
}

.rd-offline-toggle input {
  cursor: pointer;
}

.rd-table-wrap {
  flex: 1;
  min-height: 0;
  overflow: auto;
}

.rd-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12.5px;
}

.rd-table th {
  position: sticky;
  top: 0;
  background: var(--rd-panel);
  text-align: left;
  padding: 8px 12px;
  color: var(--rd-text-muted);
  font-weight: 500;
  text-transform: uppercase;
  font-size: 11px;
  letter-spacing: 0.03em;
  border-bottom: 1px solid var(--rd-border);
}

.rd-table th.rd-sortable {
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}

.rd-table th.rd-sortable:hover {
  color: var(--rd-text);
}

.rd-table th.rd-sortable .rd-sort-arrow {
  margin-left: 3px;
  opacity: 0.4;
}

.rd-table th.rd-sort-active .rd-sort-arrow {
  opacity: 1;
  color: var(--rd-accent);
}

/* Progressively hide less-essential player list columns as the panel
   (not the viewport) narrows, so Player/Kills/Deaths/K-D always fit. */
@container player-panel (max-width: 900px) {
  .rd-col-server { display: none; }
}
@container player-panel (max-width: 760px) {
  .rd-col-proxy { display: none; }
}
@container player-panel (max-width: 640px) {
  .rd-col-bm { display: none; }
}
@container player-panel (max-width: 560px) {
  .rd-col-steam { display: none; }
}
@container player-panel (max-width: 480px) {
  .rd-col-ping { display: none; }
}

.rd-table td {
  padding: 8px 12px;
  border-bottom: 1px solid var(--rd-border);
  vertical-align: top;
  white-space: nowrap;
}

.rd-table tr.rd-offline {
  opacity: 0.5;
}

.rd-player-name {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 600;
}

.rd-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  font-size: 10px;
  border-radius: 4px;
  background: #232833;
  color: var(--rd-text-muted);
  border: none;
  text-decoration: none;
  cursor: pointer;
}

.rd-icon-btn:hover {
  color: var(--rd-text);
}

.rd-steamid {
  margin-top: 2px;
  color: var(--rd-text-muted);
  font-size: 11px;
  display: flex;
  align-items: center;
  gap: 6px;
}

.rd-copy {
  background: none;
  border: none;
  color: var(--rd-text-muted);
  cursor: pointer;
  font-size: 11px;
  padding: 0;
}

.rd-proxy-badge {
  font-size: 10px;
  font-weight: 700;
  padding: 2px 6px;
  border-radius: 4px;
}

.rd-proxy-badge.yes {
  background: var(--rd-bad-bg);
  color: var(--rd-bad);
}

.rd-proxy-badge.no {
  background: var(--rd-good-bg);
  color: var(--rd-good);
}

.rd-proxy-badge.unknown {
  color: var(--rd-text-muted);
}

.rd-log-filters {
  display: flex;
  gap: 10px;
  align-items: end;
  flex-wrap: wrap;
  padding: 10px 14px;
  border-bottom: 1px solid var(--rd-border);
  flex: 0 0 auto;
}

.rd-log-filters label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 11px;
  color: var(--rd-text-muted);
}

.rd-log-filters input[type="text"] {
  flex: 1;
  min-width: 140px;
}

.rd-log-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 8px 14px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.7;
}

.rd-log-line {
  display: flex;
  gap: 8px;
  white-space: pre-wrap;
  word-break: break-word;
  border-bottom: 1px solid #1c2028;
  padding: 3px 0;
}

.rd-log-time {
  color: var(--rd-text-muted);
  flex-shrink: 0;
}

/* --- audit pages: staff lookup log and Discord command log --------------- */

.audit-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 28px;
}

.audit-stats > div {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.audit-stat-n {
  font-size: 20px;
  font-weight: 600;
}

/* A plain flex row of bars rather than a charting library: this is one series
   of daily counts, and a dependency would cost more than it explains. */
.audit-chart {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 130px;
  padding-top: 8px;
  overflow-x: auto;
}

.audit-bar-col {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  gap: 4px;
  height: 100%;
  min-width: 18px;
  flex: 1 1 0;
}

.audit-bar {
  width: 100%;
  min-height: 2px;
  background: #4f8ef7;
  border-radius: 2px 2px 0 0;
}

.audit-bar-col:hover .audit-bar {
  background: #7aa9f9;
}

.audit-bar-label {
  font-size: 10px;
  color: var(--muted);
  white-space: nowrap;
}

.audit-barcell {
  width: 30%;
  min-width: 90px;
}

.audit-hbar {
  height: 10px;
  min-width: 3px;
  background: #4f8ef7;
  border-radius: 3px;
}

.audit-row-selected {
  background: rgba(79, 142, 247, 0.1);
}

.audit-sub {
  font-size: 11px;
}

/* Marks a line in a player's chat log as spoken or typed. Voice is transcribed
   by a plugin and can be wrong, so it is labelled rather than blended in with
   text the player definitely wrote. */
.pl-say {
  flex-shrink: 0;
  border-radius: 4px;
  padding: 0 6px;
  font-size: 11px;
  letter-spacing: 0.04em;
  white-space: nowrap;
}

.pl-say-voice {
  color: #d6a75f;
  background: rgba(214, 167, 95, 0.14);
}

.pl-say-text {
  color: #7a8899;
  background: rgba(122, 136, 153, 0.12);
}

.rd-log-server {
  flex-shrink: 0;
  color: #8b7fd6;
  background: rgba(139, 127, 214, 0.12);
  border-radius: 4px;
  padding: 0 5px;
  max-width: 140px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rd-log-tag {
  flex-shrink: 0;
  font-weight: 700;
  min-width: 44px;
}

.rd-cat-chat .rd-log-tag,
.rd-cat-team_chat .rd-log-tag { color: var(--rd-good); }
[class*="rd-cat-death_"] .rd-log-tag { color: var(--rd-bad); }
.rd-cat-npc_kill .rd-log-tag { color: #7d8590; }
.rd-cat-report .rd-log-tag { color: #e6a23c; }
.rd-cat-join .rd-log-tag { color: #6fb5cf; }
.rd-cat-leave .rd-log-tag { color: #7d8590; }
.rd-cat-kick .rd-log-tag,
.rd-cat-eac_kick .rd-log-tag { color: #e6a23c; }
.rd-cat-event .rd-log-tag,
.rd-cat-save_stats .rd-log-tag { color: #8b7fd6; }
.rd-cat-generic .rd-log-tag { color: var(--rd-text-muted); }
.rd-cat-warning .rd-log-tag { color: var(--rd-caution); }

dialog.modal {
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 24px;
  width: 100%;
  max-width: 420px;
  background: var(--surface);
  color: var(--text);
}

dialog.modal::backdrop {
  background: rgba(0, 0, 0, 0.4);
}

dialog.modal h3 {
  margin: 0 0 16px;
  font-size: 16px;
}

.checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
  cursor: pointer;
}

.checkbox-row input[type="checkbox"] {
  margin-top: 3px;
}

/* The one-line explanation of what a log category actually contains. Wraps
   onto its own line under the label rather than sitting beside it, so the
   labels stay in a scannable column. */
.checkbox-row {
  flex-wrap: wrap;
}

.category-hint {
  flex-basis: 100%;
  margin-left: 26px;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.45;
}

.checkbox-row:last-of-type {
  border-bottom: none;
}

.category-checklist {
  max-height: 400px;
  overflow-y: auto;
}

dialog.modal-wide {
  max-width: 760px;
}

.category-columns {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  max-height: 55vh;
  overflow-y: auto;
}

.category-group-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-bottom: 10px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--border);
  font-size: 13px;
  cursor: pointer;
}

.category-group .category-checklist {
  max-height: none;
  overflow-y: visible;
}

.perm-expand summary {
  cursor: pointer;
  font-size: 12px;
  color: var(--text-muted);
  margin: 4px 0;
}

.perm-expand .checkbox-row {
  padding: 6px 0;
}

@media (max-width: 640px) {
  .category-columns {
    grid-template-columns: 1fr;
  }
}

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 20px;
}

.search-form input[type="text"] {
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 14px;
  min-width: 240px;
  background: var(--surface);
  color: var(--text);
}

/* Player lookup page */
.pl-flash {
  background: var(--rd-good-bg);
  border: 1px solid #2a4a36;
  color: var(--rd-good);
  border-radius: 8px;
  padding: 10px 14px;
  margin-bottom: 16px;
  font-size: 13px;
}

.pl-header {
  display: flex;
  gap: 20px;
  align-items: flex-start;
  background: var(--rd-panel);
  border: 1px solid var(--rd-border);
  border-radius: 10px;
  padding: 20px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}

.pl-avatar {
  width: 84px;
  height: 84px;
  border-radius: 10px;
  flex-shrink: 0;
}

.pl-header-info {
  flex: 1;
  min-width: 260px;
}

.pl-header-info h1 {
  margin: 0 0 6px;
  font-size: 20px;
}

.pl-header-info p {
  margin: 4px 0;
  font-size: 13px;
}

.pl-header-stats {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
}

.pl-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 80px;
}

.pl-stat-value {
  font-size: 20px;
  font-weight: 700;
}

.pl-stat-label {
  font-size: 11px;
  color: var(--rd-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.pl-toolbar {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  background: var(--rd-panel);
  border: 1px solid var(--rd-border);
  border-radius: 10px;
  padding: 12px 16px;
  margin-bottom: 16px;
  font-size: 13px;
}

.pl-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  color: var(--rd-text-muted);
}

/* The cards are arranged on a 60-column grid because 60 divides by 2, 3, 4, 5
   and 6 — so three, four or five cards on a row all land on exact boundaries
   with no ragged remainder. See utils/profileLayout.js. */
.pl-grid {
  display: grid;
  grid-template-columns: repeat(var(--pl-columns, 60), 1fr);
  gap: 16px;
  align-items: start;
  /* The drop marker and size badge are placed against this box. */
  position: relative;
}

.pl-card {
  background: var(--rd-panel);
  border: 1px solid var(--rd-border);
  border-radius: 10px;
  padding: 16px;
  /* Grid items refuse to shrink past their content unless told to, which
     would let one wide table push a narrowed card back out again. */
  min-width: 0;
  display: flex;
  flex-direction: column;
  position: relative;
}

/* Order, width and height are read only above the breakpoint. They are custom
   properties rather than the real ones precisely so the phone can ignore a
   saved layout wholesale — a hardcoded grid-column could not be switched off
   this way. */
@media (min-width: 1001px) {
  /* Rows are one thin unit tall and each card spans as many as its own height
     needs, measured in script. Without this the grid gives every card in a row
     the same height as the tallest one, so a short card leaves dead space that
     nothing below can rise into — you could not put the kill log under a short
     F7 panel while a tall Linked accounts sat beside it. Packing this way lets
     each column fill independently, which is what the two hand-split columns
     used to do before any of this was arrangeable.

     row-gap is 0 because a gap would be inserted between every one of those
     thin rows; the space between cards is built into the span instead. */
  .pl-grid {
    grid-auto-rows: var(--pl-row-unit, 4px);
    column-gap: 16px;
    row-gap: 0;
  }

  .pl-card {
    order: var(--pl-order, 0);
    grid-column: span var(--pl-span, 30);
    grid-row: span var(--pl-rows, 60);
    height: var(--pl-height, auto);
  }
}

/* Phones get one fixed, clean column: default order, full width, natural
   height, and no editing affordances. */
@media (max-width: 1000px) {
  .pl-grid {
    display: flex;
    flex-direction: column;
    /* The grid rule above sets align-items: start so cards keep their own
       height; as a flex column that would instead shrink each one to its
       content width. */
    align-items: stretch;
  }

  .pl-layout-tools,
  .pl-card-grip,
  .pl-resize {
    display: none !important;
  }
}

.pl-card h2 {
  margin: 0 0 10px;
  font-size: 14px;
  flex: 0 0 auto;
}

/* Everything below the heading scrolls, so pinning a card to a height hides
   nothing: the content is still reachable, and the heading stays put rather
   than scrolling away with it. Horizontal too — a table narrower than its
   columns must scroll rather than be cut off, which is the whole reason a
   card can be narrowed at all. */
.pl-card-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
}

/* Controls inside a card wrap instead of overflowing when it is narrowed —
   an option that cannot be reached is worse than one that has moved down a
   line. */
.pl-card .section-header,
.pl-card .pl-range-links,
.pl-card .pl-mute-form,
.pl-card .pl-note-form {
  flex-wrap: wrap;
}

/* --- layout editing ------------------------------------------------------ */

.pl-layout-tools {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.pl-layout-hint {
  font-size: 11px;
  max-width: 260px;
  line-height: 1.35;
}

.pl-card-grip {
  position: absolute;
  top: 6px;
  right: 8px;
  padding: 2px 6px;
  border-radius: 5px;
  color: var(--rd-text-muted);
  font-size: 13px;
  line-height: 1;
  cursor: grab;
  opacity: 0;
  transition: opacity 0.12s;
  user-select: none;
}

.pl-card:hover .pl-card-grip,
.pl-card-grip:focus-visible {
  opacity: 1;
}

.pl-card-grip:hover {
  background: var(--rd-border);
  color: var(--rd-text);
}

/* The grab area is much wider than the line it draws. A 4px target on a card
   edge is findable by eye but miserable to actually hit, so the hit box
   straddles the edge and reaches into the gap between cards, while ::after
   keeps the visible line thin. */
.pl-resize {
  position: absolute;
  opacity: 0;
  transition: opacity 0.12s;
}

.pl-resize::after {
  content: '';
  position: absolute;
  background: var(--rd-accent);
  border-radius: 2px;
}

.pl-card:hover .pl-resize {
  opacity: 0.35;
}

.pl-resize:hover,
.pl-resizing .pl-resize {
  opacity: 0.9;
}

.pl-resize-x {
  top: 8px;
  bottom: 8px;
  right: -7px;
  width: 14px;
  cursor: col-resize;
}

.pl-resize-x::after {
  top: 0;
  bottom: 0;
  left: 5px;
  width: 4px;
}

.pl-resize-y {
  left: 8px;
  right: 8px;
  bottom: -7px;
  height: 14px;
  cursor: row-resize;
}

.pl-resize-y::after {
  left: 0;
  right: 0;
  top: 5px;
  height: 4px;
}

.pl-resize-xy {
  right: -7px;
  bottom: -7px;
  width: 18px;
  height: 18px;
  cursor: nwse-resize;
}

.pl-resize-xy::after {
  right: 5px;
  bottom: 5px;
  width: 11px;
  height: 11px;
  border-bottom-right-radius: 9px;
}

.pl-card-dragging {
  opacity: 0.5;
  outline: 2px dashed var(--rd-accent);
}

/* Where the card will land. The grid deliberately does not rearrange while a
   card is being dragged — it used to, and the card being aimed at slid out
   from under the pointer — so this bar is the only thing that moves. */
.pl-drop-marker {
  position: absolute;
  width: 3px;
  border-radius: 2px;
  background: var(--rd-accent);
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
  pointer-events: none;
  z-index: 30;
}

/* The width being dragged to. A column is under 2% of the page, so without
   this you cannot tell whether two cards will add up to a full line. */
.pl-size-badge {
  position: absolute;
  transform: translate(-100%, 0);
  margin: 6px;
  padding: 2px 8px;
  border-radius: 6px;
  background: var(--rd-accent);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
  pointer-events: none;
  z-index: 30;
}

body.pl-layout-busy {
  user-select: none;
  cursor: grabbing;
}

.pl-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12.5px;
}

.pl-table th {
  text-align: left;
  color: var(--rd-text-muted);
  font-weight: 500;
  text-transform: uppercase;
  font-size: 10.5px;
  letter-spacing: 0.03em;
  padding: 6px 8px;
  border-bottom: 1px solid var(--rd-border);
}

.pl-table td {
  padding: 6px 8px;
  border-bottom: 1px solid var(--rd-border);
}

.pl-ban {
  border: 1px solid var(--rd-border);
  border-radius: 8px;
  padding: 8px 12px;
  margin-bottom: 8px;
}

.pl-ban summary {
  cursor: pointer;
  font-size: 12.5px;
}

.pl-ban p {
  margin: 8px 0 0;
  color: var(--rd-text-muted);
  font-size: 12.5px;
}

.pl-note {
  border-bottom: 1px solid var(--rd-border);
  padding: 8px 0;
  font-size: 12.5px;
}

.pl-badge {
  display: inline-block;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 1px 5px;
  border-radius: 4px;
  background: #1a2338;
  color: var(--rd-info);
  vertical-align: middle;
}

.pl-tickets-section + .pl-tickets-section {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--rd-border);
}

.pl-subhead {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--rd-text-muted);
  margin: 0 0 8px;
}

.pl-ticket-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.pl-ticket-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 4px 0;
}

.pl-ticket-list li + li {
  border-top: 1px solid var(--rd-border);
}

.pl-ticket-type {
  font-weight: 600;
}

.pl-combat-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  padding-bottom: 14px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--rd-border);
}

.pl-hitchart {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}

@media (max-width: 700px) {
  .pl-hitchart {
    grid-template-columns: 1fr;
  }
}

.pl-hitchart-col h3 {
  margin: 0 0 8px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--rd-text-muted);
}

.pl-hitchart-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  padding: 3px 0;
}

.pl-hitchart-label {
  width: 70px;
  flex-shrink: 0;
  text-transform: capitalize;
  color: var(--rd-text-muted);
}

.pl-hitchart-track {
  flex: 1;
  height: 8px;
  border-radius: 4px;
  background: #171a21;
  overflow: hidden;
}

.pl-hitchart-fill {
  display: block;
  height: 100%;
  border-radius: 4px;
  min-width: 2px;
}

.pl-hitchart-fill.dealt {
  background: var(--rd-good);
}

.pl-hitchart-fill.taken {
  background: var(--rd-bad);
}

.pl-hitchart-value {
  width: 80px;
  flex-shrink: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.pl-combat-table td {
  font-size: 12.5px;
}

.pl-verdict {
  display: inline-block;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  padding: 2px 7px;
  border-radius: 4px;
  margin-right: 4px;
}

.pl-verdict-likely {
  background: #3a2a12;
  color: var(--rd-warn);
}

.pl-verdict-possible {
  background: #1a2338;
  color: var(--rd-info);
}

.pl-verdict-weak {
  background: #1c1c1c;
  color: var(--rd-text-muted);
}

.pl-verdict-very-likely {
  background: var(--rd-bad-bg);
  color: var(--rd-bad);
}

.pl-score-big {
  font-size: 30px;
  font-weight: 700;
  line-height: 1.1;
  margin-top: 6px;
}

.pl-verdict-banner {
  border: 1px solid var(--rd-border);
  border-left-width: 3px;
  border-radius: 8px;
  padding: 12px 16px;
  margin-bottom: 16px;
  font-size: 13px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.pl-verdict-banner strong { font-size: 12px; letter-spacing: 0.04em; }
.pl-verdict-banner span { color: var(--rd-text-muted); }
.pl-verdict-banner-very-likely { border-left-color: var(--rd-bad); background: var(--rd-bad-bg-soft); }
.pl-verdict-banner-likely { border-left-color: var(--rd-warn); background: var(--rd-warn-bg); }
.pl-verdict-banner-possible { border-left-color: var(--rd-info); background: var(--rd-info-bg); }
.pl-verdict-banner-weak { border-left-color: #4a4a4a; background: #141414; }

/* --- signal breakdown --- */
.pl-signals {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px;
}

.pl-signal-unavailable { opacity: 0.5; }

.pl-signal-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  font-size: 12.5px;
  margin-bottom: 5px;
}

.pl-signal-label { font-weight: 600; }

.pl-signal-points { font-variant-numeric: tabular-nums; font-weight: 700; font-size: 12px; }
.pl-signal-points.pos { color: var(--rd-good); }
.pl-signal-points.neg { color: var(--rd-bad); }
.pl-signal-points.zero { color: var(--rd-text-muted); }

.pl-signal-bar {
  height: 5px;
  border-radius: 3px;
  background: #171a21;
  overflow: hidden;
}

.pl-signal-fill {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--rd-good);
}

.pl-signal-fill.neg { background: var(--rd-bad); }

.pl-signal-detail { font-size: 11.5px; margin-top: 5px; line-height: 1.45; }

/* --- activity timeline --- */
.pl-timeline { margin-top: 4px; }

.pl-timeline-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}

.pl-timeline-name {
  width: 130px;
  flex-shrink: 0;
  font-size: 12px;
  color: var(--rd-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pl-timeline-track {
  position: relative;
  flex: 1;
  height: 26px;
  background: #12141a;
  border: 1px solid var(--rd-border);
  border-radius: 5px;
  overflow: hidden;
}

.pl-timeline-tick {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: #23262e;
}

.pl-timeline-bar {
  position: absolute;
  top: 4px;
  bottom: 4px;
  border-radius: 2px;
  min-width: 2px;
}

.pl-timeline-bar.a { background: #5b8def; }
.pl-timeline-bar.b { background: var(--rd-warn); }
.pl-timeline-bar.open { box-shadow: 0 0 0 1px var(--rd-good) inset; }

.pl-timeline-axis {
  position: relative;
  height: 16px;
  margin-left: 140px;
  font-size: 10.5px;
  color: var(--rd-text-muted);
}

.pl-timeline-axis-tick {
  position: absolute;
  transform: translateX(-50%);
  white-space: nowrap;
}

.pl-timeline-axis-end { position: absolute; right: 0; }

.pl-timeline-note { font-size: 12px; margin: 10px 0 0; }

/* --- ip type chips --- */
.pl-iptype {
  display: inline-block;
  font-size: 9.5px;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: 4px;
  background: #1c1c1c;
  color: var(--rd-text-muted);
  vertical-align: middle;
}

.pl-iptype-residential { background: var(--rd-good-bg); color: var(--rd-good); }
.pl-iptype-business { background: var(--rd-warn-bg); color: var(--rd-warn); }
.pl-iptype-mobile { background: var(--rd-info-bg); color: var(--rd-info); }
.pl-iptype-proxyvpn,
.pl-iptype-hosting,
.pl-iptype-tor { background: #2a1717; color: #d98b8b; }

.pl-pct { font-variant-numeric: tabular-nums; font-weight: 600; }
.pl-pct.hi { color: var(--rd-good); }
.pl-pct.mid { color: var(--rd-warn); }
.pl-pct.lo { color: var(--rd-text-muted); }

.pl-pfp-compare {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

.pl-pfp-compare img {
  width: 64px;
  height: 64px;
  border-radius: 8px;
  flex-shrink: 0;
}

.pl-pfp-verdict { text-align: center; flex: 1; font-size: 11.5px; }
.pl-pfp-verdict .muted { margin-top: 5px; }

.pl-subhead { font-size: 12px; margin: 14px 0 8px; color: var(--rd-text-muted); }

.pl-compare-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  background: var(--rd-panel);
  border: 1px solid var(--rd-border);
  border-radius: 10px;
  padding: 20px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}

.pl-compare-side {
  display: flex;
  align-items: center;
  gap: 14px;
  flex: 1;
  min-width: 200px;
}

.pl-compare-side-right {
  justify-content: flex-end;
  text-align: right;
}

.pl-compare-side h1 {
  margin: 0 0 4px;
  font-size: 17px;
}

.pl-compare-side p {
  margin: 0;
}

.pl-compare-verdict {
  text-align: center;
  flex-shrink: 0;
}

.pl-compare-verdict .pl-verdict {
  font-size: 13px;
  padding: 4px 12px;
}

.pl-compare-bans {
  display: flex;
  gap: 24px;
}

.pl-compare-bans > div {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-start;
}

.pl-friend-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.pl-friend-list li {
  background: #171a21;
  border: 1px solid var(--rd-border);
  border-radius: 6px;
  padding: 4px 10px;
  font-size: 12.5px;
}

/* The compare page also renders standalone (direct link), but the primary
   entry point is this modal — dark-themed to match the rest of the RCON
   dashboard rather than the light `.modal` base used elsewhere in the app. */
dialog.pl-compare-modal {
  background: var(--rd-panel);
  color: var(--rd-text);
  border: 1px solid var(--rd-border);
  border-radius: 12px;
  padding: 24px;
  width: 95vw;
  max-width: 1200px;
  max-height: 90vh;
  overflow-y: auto;
  position: relative;
}

.pl-compare-modal-close {
  position: absolute;
  top: 10px;
  right: 14px;
  background: none;
  border: none;
  color: var(--rd-text-muted);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
  padding: 4px;
}

.pl-compare-modal-close:hover {
  color: var(--rd-text);
}

.pl-compare-modal-body {
  margin-top: 8px;
}

.pl-note-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
}

.pl-note-form select,
.pl-note-form textarea {
  background: var(--rd-input, #0d0f13);
  border: 1px solid var(--rd-border);
  color: var(--rd-text);
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 13px;
  font-family: inherit;
}

.pl-range-links {
  display: flex;
  gap: 6px;
}

.pl-log-list {
  height: 260px;
  border: 1px solid var(--rd-border);
  border-radius: 8px;
}

/* ---------------------------------------------------------------------------
   Phone / small-screen support
   ---------------------------------------------------------------------------
   Goal is core functionality and painless navigation, not a bespoke mobile UI.
   Three things were actually broken before this block existed:

   1. `.rd-columns` is a nowrap flex row whose children carry min-widths
      (360 + 320 + gap = 696px). Below that the logs panel simply sat off the
      right edge of the screen — invisible and unreachable on a phone.
   2. Bare `<table>`s (org servers, player lookup cards) are wider than a phone
      and pushed the whole *page* sideways instead of scrolling themselves.
   3. Inputs at 13px trigger iOS Safari's focus auto-zoom, which leaves the
      page zoomed in and the layout shifted after every tap into a field.

   Kept deliberately narrow in scope: layout/reach/tap-size only, no restyling.
   --------------------------------------------------------------------------- */

/* 960px, not 900: the panels' min-widths (360 + 320 + 16 gap + 40 page padding)
   stop fitting inside the width their percentage rules actually get well before
   the row itself runs out of room, so the logs panel overflows the page by up to
   ~30px anywhere below ~960. Stacking from there covers the whole band. */
@media (max-width: 960px) {
  /* Stack the dashboard's two panels instead of running them off-screen. */
  .rd-columns {
    flex-direction: column;
    flex-wrap: wrap;
  }

  .rd-player-panel,
  .rd-logs-panel {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    /* Viewport-height panels stop making sense once stacked: two of them would
       mean two full screens of scrolling before any page content. Cap each so
       both are reachable, and let them grow on taller phones. */
    height: auto;
    max-height: none;
    /* `resize` handles are a mouse affordance; they do nothing useful on touch
       and only steal drags from the scroll container underneath. */
    resize: none;
  }

  .rd-player-panel .rd-table-wrap,
  .rd-logs-panel .rd-log-list {
    max-height: 60vh;
  }
}

@media (max-width: 760px) {
  /* Tables scroll inside themselves rather than widening the page. Tables
     already sitting in an overflow wrapper are left alone — nesting a second
     scroll container inside the first makes both awkward to drive by thumb. */
  table {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  /* `overflow` must be reset alongside `display` here: any overflow value
     other than `visible` makes the table its own scroll container, which
     re-anchors the sticky `th` to the table instead of the wrapper — the
     header then scrolls away under the rows. */
  .rd-table-wrap table {
    display: table;
    overflow: visible;
  }

  /* Trim the generous desktop chrome so content gets the width instead. */
  .topbar {
    padding: 12px 16px;
    gap: 10px;
    flex-wrap: wrap;
  }

  .topbar .actions {
    gap: 8px;
  }

  /* The lookup takes a row of its own rather than squeezing between the brand
     and the buttons — it is the widest control up there and the one most
     likely to be used on a phone. */
  .nav-lookup {
    order: 3;
    flex-basis: 100%;
    max-width: none;
    margin: 0;
  }

  .rcon-dashboard {
    padding: 12px;
  }

  .rd-toolbar {
    padding: 10px;
    gap: 8px;
  }

  /* These min-widths are sized for a desktop toolbar row; on a phone they
     force awkward wrapping or overflow. Let them use the row they get. */
  .rd-filter-field,
  .rd-filter-input,
  .rd-server-field,
  .rd-server-field select,
  .search-form input[type="text"] {
    min-width: 0;
    width: 100%;
  }

  /* Each control gets its own row rather than three cramped ones sharing. */
  .rd-filter-field,
  .rd-server-field {
    flex-basis: 100%;
  }

  .rd-toolbar-spacer {
    display: none;
  }

  .pl-header,
  .pl-compare-header {
    padding: 14px;
    gap: 12px;
  }

  .pl-card {
    padding: 12px;
  }

  /* Side-by-side compare halves stack; the right half stops being right-aligned
     once it is no longer beside anything. */
  .pl-compare-side {
    min-width: 0;
    flex-basis: 100%;
  }

  .pl-compare-side-right {
    justify-content: flex-start;
    text-align: left;
  }

  dialog.modal {
    padding: 18px;
  }

  /* Must follow `dialog.modal`: the compare dialog carries both classes and the
     two selectors have equal specificity, so source order decides. Full-bleed
     because a 95vw dialog with desktop padding leaves almost no room for the
     two-column comparison content on a phone. */
  dialog.pl-compare-modal {
    width: 100vw;
    max-width: 100vw;
    max-height: 100vh;
    height: 100vh;
    border-radius: 0;
    padding: 16px 12px;
  }

  /* Same reasoning for the fight popup, and it must follow dialog.modal too. */
  dialog.pl-fight-modal {
    width: 100vw;
    max-width: 100vw;
    max-height: 100vh;
    height: 100vh;
    border-radius: 0;
    padding: 16px 12px;
  }

  .rd-table th,
  .rd-table td {
    padding: 8px;
  }
}

@media (max-width: 760px) {
  /* iOS Safari zooms the viewport when a focused field is under 16px. The zoom
     persists after blur, so every tap into a filter box leaves the user
     pinch-zooming back out. 16px is the documented threshold, not a style pick. */
  input,
  select,
  textarea,
  .rd-filter-input,
  .rd-server-field select,
  .nav-lookup input,
  .rd-log-filters input,
  .pl-note-form select,
  .pl-note-form textarea {
    font-size: 16px;
  }
}

@media (pointer: coarse) {
  /* Touch targets. The copy-SteamID button in particular rendered at 11x13px,
     which is essentially untappable; the surrounding row stays the same size
     because these grow via padding into space the row already had. */
  .rd-copy {
    padding: 6px;
    margin: -6px;
    font-size: 13px;
  }

  .rd-icon-btn {
    min-width: 28px;
    height: 28px;
    font-size: 11px;
  }

  .pl-compare-modal-close {
    font-size: 28px;
    padding: 10px;
    top: 4px;
    right: 6px;
  }

  /* Panels are sized by CSS on touch; the drag handle only gets in the way. */
  .rd-player-panel,
  .rd-logs-panel {
    resize: none;
  }
}

/* Team history module on the player lookup page */
.pl-count {
  font-weight: 400;
  font-size: 12px;
}

.pl-loadmore {
  margin-top: 10px;
}

.pl-loadmore-btn {
  font-size: 12px;
  padding: 4px 10px;
}

.pl-loadmore-choices {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.pl-loadmore-choices .btn {
  font-size: 12px;
  padding: 4px 10px;
}

.pl-team-current {
  border: 1px solid var(--rd-border);
  border-radius: 8px;
  padding: 10px 12px;
  margin-bottom: 12px;
}

.pl-team-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 12px;
  margin-bottom: 8px;
}

.pl-team-badge {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 2px 6px;
  border-radius: 4px;
  background: var(--rd-good-bg);
  color: var(--rd-good);
}

.pl-team-server {
  font-weight: 600;
}

.pl-badge-leader {
  font-size: 9px;
  font-weight: 700;
  padding: 1px 5px;
  border-radius: 3px;
  background: #2a2416;
  color: var(--rd-caution);
}

/* SteamIDs sit under the name so the column stays narrow enough to read on a
   phone rather than forcing the table into horizontal scroll. */
.pl-steamid-sub {
  font-size: 10px;
  color: var(--rd-text-muted);
  margin-top: 2px;
}

/* Mute controls on the player lookup page */
.pl-mute-org {
  border: 1px solid var(--rd-border);
  border-radius: 8px;
  padding: 10px 12px;
  margin-bottom: 10px;
}

.pl-mute-head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  margin-bottom: 8px;
}

.pl-badge-muted {
  font-size: 9px;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: 3px;
  background: #3a2416;
  color: #e6a34c;
}

.pl-mute-form {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  align-items: center;
}

.pl-mute-form input[type="text"] {
  flex: 1;
  min-width: 140px;
  background: var(--rd-input, #0d0f13);
  border: 1px solid var(--rd-border);
  color: var(--rd-text);
  border-radius: 6px;
  padding: 7px 10px;
  font-size: 13px;
}

/* Discord role chips on the org page */
.role-view {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.role-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  padding: 2px 8px;
  border: 1px solid var(--border);
  border-radius: 10px;
  white-space: nowrap;
}

.role-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-muted);
  flex: 0 0 auto;
}

/* A role we hold a grant for but Discord did not return — deleted, or the bot
   cannot see it. Shown as the raw ID so it is still editable, but marked. */
.role-chip-unknown {
  color: var(--text-muted);
  border-style: dashed;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
}

.role-edit-btn {
  padding: 2px 10px;
  font-size: 12px;
}

/* Steam <-> Discord identity, on the profile header. */
.pl-discord-line code {
  font-size: 12px;
}

.pl-discord-name {
  margin-left: 6px;
  opacity: 0.85;
}

.pl-link-btn {
  background: none;
  border: none;
  padding: 0;
  margin-left: 6px;
  font: inherit;
  color: var(--rd-accent, #3b82f6);
  text-decoration: underline;
  cursor: pointer;
}

.pl-link-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

.pl-discord-checked {
  margin-left: 6px;
  font-size: 11px;
}

/* An answer old enough that the account could have been unlinked since. */
.pl-discord-checked.stale {
  opacity: 0.6;
  font-style: italic;
}

/* ==========================================================================
   Org detail page
   ========================================================================== */

.org-page {
  max-width: 1040px;
}

.org-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 24px;
}

.org-head h1 {
  margin: 0 0 6px;
  font-size: 24px;
  letter-spacing: -0.02em;
}

.org-head-meta {
  margin: 0;
  font-size: 13px;
}

.org-head-actions {
  display: flex;
  gap: 8px;
  flex: 0 0 auto;
}

/* Each area of the page is one bordered card, so where a section starts and
   stops is visible without counting heading sizes. */
.org-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px 20px 20px;
  margin-bottom: 20px;
}

.org-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 32px;
}

.org-card-head h2 {
  margin: 0;
  font-size: 15px;
}

.org-card-head-actions {
  display: flex;
  gap: 8px;
}

.org-card-empty {
  margin: 12px 0 0;
  font-size: 14px;
}

.org-table {
  margin-top: 12px;
}

.org-table th {
  padding-top: 0;
}

.org-table td {
  vertical-align: middle;
}

.org-table tr:last-child td {
  border-bottom: none;
}

/* Actions live on one line at the end of the row rather than stacking, which
   is what forced member rows to three lines each. */
.org-col-actions {
  width: 1%;
  white-space: nowrap;
  text-align: right;
}

.org-actions {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
  align-items: center;
}

.org-actions form {
  display: inline;
  margin: 0;
}

.btn-sm {
  padding: 4px 10px;
  font-size: 12px;
}

.btn-danger {
  color: var(--danger);
  border-color: var(--bad-border);
}

.btn-danger:hover {
  background: var(--bad-bg);
}

/* Status words as pills, so "connected" and "error" are distinguishable at a
   glance instead of being two identical-looking lowercase words. */
.pill {
  display: inline-flex;
  align-items: center;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 10px;
  border: 1px solid var(--border);
  white-space: nowrap;
}

.pill-ok {
  color: var(--ok);
  background: var(--ok-bg);
  border-color: var(--ok-border);
}

.pill-bad {
  color: var(--danger);
  background: var(--bad-bg);
  border-color: var(--bad-border);
}

.pill-idle {
  color: var(--text-muted);
  background: var(--bg);
}

.pill-accent {
  color: var(--text);
  background: var(--bg);
  margin-left: 4px;
}

/* The permission count. Cursor + underline say the full list is on hover. */
.pill-count {
  text-transform: none;
  font-size: 12px;
  letter-spacing: 0;
  cursor: help;
  border-style: dashed;
}

.org-inline-note {
  display: inline-block;
  max-width: 260px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
  font-size: 12px;
}

.org-status-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 12px 20px;
  margin: 14px 0 20px;
  padding: 14px 16px;
  background: var(--bg);
  border-radius: 8px;
  font-size: 13px;
}

.org-status {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}

.org-status-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-muted);
}

.org-status > span:last-child {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.org-subhead {
  font-size: 13px;
  margin: 24px 0 4px;
}

.org-note {
  font-size: 13px;
  margin: 0 0 10px;
  max-width: 70ch;
}

.org-note-small {
  font-size: 12px;
  margin: 6px 0 0;
  max-width: 70ch;
}

.org-guild-field {
  max-width: 420px;
  margin-top: 4px;
}

.org-form-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 16px;
  font-size: 13px;
}

.org-id-input {
  width: 100%;
  padding: 6px 10px;
  font-size: 13px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: inherit;
}

.org-commands td {
  vertical-align: top;
  padding-top: 8px;
  padding-bottom: 8px;
}

.org-cmd-desc {
  font-size: 13px;
  line-height: 1.4;
}

.org-col-roles {
  width: 42%;
}

.org-cred-note {
  font-size: 12px;
  margin-top: 2px;
}

.org-cred-form {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
}

.org-cred-input {
  width: 220px;
  padding: 4px 10px;
  font-size: 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: inherit;
}

/* --- role / category picker ---------------------------------------------- */

.idpick {
  position: relative;
}

/* The chips wrap inside their own box; 'Edit' stays pinned to the right of
   the first line rather than being pushed onto a line of its own once a
   command has more than a couple of roles. */
.idpick-summary {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  cursor: pointer;
  list-style: none;
  padding: 4px 6px;
  margin: -4px -6px;
  border-radius: 6px;
}

.idpick-summary::-webkit-details-marker {
  display: none;
}

.idpick-summary:hover,
.idpick-details[open] .idpick-summary {
  background: var(--bg);
}

.idpick-chips {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-wrap: wrap;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 20px;
}

.idpick-edit {
  font-size: 12px;
  line-height: 20px;
  color: var(--text-muted);
  text-decoration: underline;
  flex: 0 0 auto;
}

/* Floating rather than in flow: an open list must not push the other twenty
   command rows down the page. */
.idpick-menu {
  position: absolute;
  z-index: 20;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  min-width: 240px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
  padding: 8px;
}

.idpick-filter {
  width: 100%;
  padding: 5px 9px;
  margin-bottom: 6px;
  font-size: 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--surface);
  color: inherit;
}

.idpick-list {
  max-height: 260px;
  overflow-y: auto;
}

.idpick-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 6px;
  border-radius: 6px;
  cursor: pointer;
}

.idpick-option:hover {
  background: var(--bg);
}

.idpick-option input {
  flex: 0 0 auto;
  margin: 0;
}

/* A table narrower than its columns clips its last one rather than
   scrolling, which put Kick and Wipe out of reach on a phone. Not applied to
   the commands table: its role menus float outside the row, and overflow here
   would clip them — it stacks instead, below. */
.org-table-wrap {
  overflow-x: auto;
}

/* width:100% pins the table box to the container, so cells that cannot shrink
   any further spill out of it and get clipped — the wrapper never sees
   anything to scroll. A floor on the table's own width lets the box grow
   instead, which is what gives the wrapper something to scroll. A fixed value
   rather than max-content: on a collapsed-border table the intrinsic
   calculation runs away, measuring these columns in the tens of thousands of
   pixels. Only applied below the breakpoint, so wide screens are untouched. */
@media (max-width: 780px) {
  .org-table-wrap > .org-table {
    min-width: 640px;
  }
}

@media (max-width: 780px) {
  .org-head {
    flex-direction: column;
  }

  .org-card-head {
    flex-wrap: wrap;
  }

  .org-card-head h2 {
    flex: 1 0 100%;
  }

  .org-col-roles {
    width: auto;
  }

  .org-cred-form {
    justify-content: flex-start;
  }

  .org-cred-input {
    width: 150px;
  }

  /* Descriptions are the first thing to go when there is no room for them —
     the command name is what the row is actually about. */
  .org-commands .org-cmd-desc,
  .org-members th:nth-child(3),
  .org-members td:nth-child(3) {
    display: none;
  }

  /* One command per block instead of per row: at this width the roles picker
     needs the full line, and a two-column table would leave it a sliver. */
  .org-commands,
  .org-commands tbody,
  .org-commands tr,
  .org-commands td {
    display: block;
    width: auto;
  }

  .org-commands thead {
    display: none;
  }

  .org-commands tr {
    padding: 10px 0;
    border-bottom: 1px solid var(--border);
  }

  .org-commands td {
    border: none;
    padding: 2px 0;
  }
}

/* Three short numeric fields on one line instead of three stacked rows. */
.field-row {
  display: flex;
  gap: 10px;
}

.field-row .field {
  flex: 1;
  min-width: 0;
}

/* --- saved layouts dropdown ---------------------------------------------- */

.pl-layouts {
  position: relative;
}

.pl-layouts > summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
}

.pl-layouts > summary::-webkit-details-marker {
  display: none;
}

.pl-layouts-count {
  display: inline-block;
  min-width: 16px;
  padding: 0 4px;
  margin-left: 4px;
  border-radius: 8px;
  background: var(--rd-border);
  font-size: 10px;
  text-align: center;
}

/* Opens upward-left of the button: the toolbar sits at the top right of a
   dense page, and a menu dropped straight down covers the first row of cards. */
.pl-layouts-menu {
  position: absolute;
  z-index: 40;
  top: calc(100% + 6px);
  right: 0;
  width: 420px;
  max-width: 88vw;
  max-height: 70vh;
  overflow-y: auto;
  padding: 12px 14px 14px;
  border: 1px solid var(--rd-border);
  border-radius: 10px;
  background: var(--rd-panel);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
  text-align: left;
  cursor: default;
}

.pl-layouts-menu h3 {
  margin: 14px 0 6px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--rd-text-muted);
}

.pl-layouts-menu h3:first-child {
  margin-top: 0;
}

.pl-layouts-empty {
  font-size: 12px;
  margin: 0;
}

.pl-layouts-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.pl-layouts-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 0;
  border-bottom: 1px solid var(--rd-border);
}

.pl-layouts-list li:last-child {
  border-bottom: none;
}

.pl-layouts-apply {
  flex: 0 0 auto;
  max-width: 130px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 3px 10px;
  border: 1px solid var(--rd-border);
  border-radius: 6px;
  background: none;
  color: inherit;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.pl-layouts-apply:hover {
  border-color: var(--rd-accent);
  color: var(--rd-accent);
}

/* The code is long by nature; it truncates here and is copied by the button
   rather than being selected by hand. */
.pl-layouts-code {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 10px;
  color: var(--rd-text-muted);
}

.pl-layouts-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
}

.pl-layouts-row input[type="text"] {
  flex: 1 1 auto;
  min-width: 0;
  padding: 5px 9px;
  font-size: 12px;
  border: 1px solid var(--rd-border);
  border-radius: 6px;
  background: var(--rd-bg);
  color: inherit;
}

.pl-layouts-note {
  font-size: 11px;
  line-height: 1.35;
}

/* --- combat log as a timeline of fights ---------------------------------- */

.pl-fights {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 6px;
}

.pl-fight-day {
  display: flex;
  align-items: center;
  gap: 10px;
}

.pl-fight-daylabel {
  flex: 0 0 auto;
  width: 54px;
  display: flex;
  justify-content: space-between;
  font-size: 11px;
}

/* One day, 00:00 to 24:00 left to right. A single axis across the whole week
   crushed every fight into a few pixels; a track per day gives each one a
   position you can read, and puts the hours between them on screen. */
.pl-fight-track {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  height: 26px;
  border-radius: 6px;
  background: var(--rd-bg);
  border: 1px solid var(--rd-border);
}

.pl-fight-tick {
  position: absolute;
  top: 4px;
  bottom: 4px;
  width: 1px;
  background: var(--rd-border);
}

/* The marker straddles its own time rather than starting at it, so a fight at
   midnight and one at 24:00 are not clipped by the ends of the track. */
.pl-fight {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 2px 4px;
  border: none;
  border-radius: 8px;
  background: none;
  cursor: pointer;
  line-height: 1;
}

.pl-fight:hover,
.pl-fight:focus-visible {
  background: var(--rd-border);
}

.pl-fight-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: 0 0 auto;
}

.pl-fight-count {
  font-size: 10px;
  font-weight: 600;
  color: var(--rd-text);
}

/* The same tokens the rest of the dashboard uses, so a light theme gets the
   darker variants rather than pale dots on a pale track. */
.pl-fight-won .pl-fight-dot,
i.pl-fight-won { background: var(--rd-good); }
.pl-fight-lost .pl-fight-dot,
i.pl-fight-lost { background: var(--rd-bad); }
.pl-fight-traded .pl-fight-dot,
i.pl-fight-traded { background: var(--rd-caution); }
.pl-fight-skirmish .pl-fight-dot,
i.pl-fight-skirmish { background: var(--rd-text-muted); }

.pl-fight-key {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-top: 10px;
  font-size: 11px;
}

.pl-fight-key span {
  display: flex;
  align-items: center;
  gap: 5px;
}

/* dialog.modal, not .pl-fight-modal: this element carries both classes, and
   the plain class loses to dialog.modal on specificity — which pinned the
   popup at that rule's 420px and made an eight-column table scroll sideways.
   The colours come with it, or a dark page opens a white dialog. */
dialog.pl-fight-modal {
  width: 95vw;
  max-width: 1150px;
  max-height: 88vh;
  overflow-y: auto;
  background: var(--rd-panel);
  color: var(--rd-text);
  border: 1px solid var(--rd-border);
  border-radius: 12px;
  position: relative;
}

/* Nothing inside needs to be squeezed at that width, so the columns keep
   their natural size and the table stops wrapping mid-cell. */
dialog.pl-fight-modal .pl-table {
  width: 100%;
}

.pl-fight-summary {
  margin: 0 0 12px;
  font-size: 12px;
}

/* The blow that ended it, so the moment the fight turned is findable without
   reading every row's HP column. */
.pl-fight-kill td {
  background: rgba(230, 195, 76, 0.12);
  font-weight: 600;
}

/* ==========================================================================
   Colour themes
   --------------------------------------------------------------------------
   Two palettes make up the site: the chrome (:root) used by the account, org
   and login pages, and the dashboard's own (.rcon-dashboard) used by the feed
   and player pages. A theme sets both, so the site is one thing rather than a
   light shell around a dark app.

   The dashboard block needs the extra `.rcon-dashboard` in its selector to
   outrank the palette declared on that element itself.
   ========================================================================== */

/* Default is the bare :root above — dark, and the palette everything else
   starts from. The themes here only say how they differ from it.
   -------------------------------------------------------------------------- */

/* --- Slate: cooler, softer, teal accent ----------------------------------- */
html[data-theme="slate"] {
  --bg: #171b21;
  --surface: #1e242c;
  --text: #dfe6ee;
  --text-muted: #93a1b3;
  --border: #2c343f;
  --accent: #2dd4bf;
  --accent-text: #0b1114;
  --scroll-thumb: #3c4756;
}

html[data-theme="slate"] .rcon-dashboard {
  --rd-bg: #141920;
  --rd-input: #10151b;
  --rd-panel: #1c222a;
  --rd-border: #2c343f;
  --rd-text: #dfe6ee;
  --rd-text-muted: #8695a8;
  --rd-accent: #2dd4bf;
  --scroll-thumb: #3c4756;
}

/* --- Ember: warm dark, amber accent --------------------------------------- */
html[data-theme="ember"] {
  --bg: #16120f;
  --surface: #1e1915;
  --text: #ece4dc;
  --text-muted: #a2948a;
  --border: #33291f;
  --accent: #f59e0b;
  --accent-text: #1a1206;
  --scroll-thumb: #4a3a2a;
}

html[data-theme="ember"] .rcon-dashboard {
  --rd-bg: #120f0c;
  --rd-input: #0f0c09;
  --rd-panel: #1c1713;
  --rd-border: #33291f;
  --rd-text: #ece4dc;
  --rd-text-muted: #9b8d82;
  --rd-accent: #f59e0b;
  --scroll-thumb: #4a3a2a;
}

/* --- Daylight: light throughout ------------------------------------------- */
html[data-theme="daylight"] {
  color-scheme: light;

  --bg: #fafafa;
  --surface: #ffffff;
  --text: #1a1a1a;
  --text-muted: #6b6b6b;
  --border: #e5e5e5;
  --accent: #1a1a1a;
  --accent-text: #ffffff;
  --danger: #c0392b;

  /* The tinted boxes go back to a dark foreground on a pale ground. */
  --ok: #1e7e45;
  --ok-bg: #eafaf1;
  --ok-border: #b7ebc6;
  --bad: #c0392b;
  --bad-bg: #fdecea;
  --bad-border: #f5c6c1;
  --warn: #6b5a1e;
  --warn-bg: #fff8e1;
  --warn-border: #f0dca0;

  --scroll-thumb: #c3c8d0;
  --scroll-thumb-hover: #a9b0ba;
}

html[data-theme="daylight"] .rcon-dashboard {
  --rd-bg: #f4f5f7;
  --rd-input: #ffffff;
  --rd-panel: #ffffff;
  --rd-border: #dfe3e8;
  --rd-text: #1a1d21;
  --rd-text-muted: #67707c;
  --rd-accent: #2563eb;
  /* The semantic pairs invert: a dark foreground on a pale tint. The dark
     theme's pale greens and reds vanish against white. */
  --rd-good: #15803d;
  --rd-good-bg: #e7f6ec;
  --rd-bad: #b91c1c;
  --rd-bad-bg: #fdeaea;
  --rd-bad-bg-soft: #fdf3f3;
  --rd-warn: #b45309;
  --rd-warn-bg: #fdf3e3;
  --rd-info: #1d4ed8;
  --rd-info-bg: #e9eefc;
  --rd-caution: #a16207;
  --scroll-thumb: #c3c8d0;
  --scroll-thumb-hover: #a9b0ba;
}

/* The compare and fight dialogs borrow the dashboard palette but sit outside
   .rcon-dashboard in the DOM, so they need the theme's values directly. */
html[data-theme="daylight"] dialog.pl-compare-modal,
html[data-theme="daylight"] dialog.pl-fight-modal {
  --rd-panel: #ffffff;
  --rd-border: #dfe3e8;
  --rd-text: #1a1d21;
  --rd-text-muted: #67707c;
}

html[data-theme="slate"] dialog.pl-compare-modal,
html[data-theme="slate"] dialog.pl-fight-modal {
  --rd-panel: #1c222a;
  --rd-border: #2c343f;
  --rd-text: #dfe6ee;
  --rd-text-muted: #8695a8;
}

html[data-theme="ember"] dialog.pl-compare-modal,
html[data-theme="ember"] dialog.pl-fight-modal {
  --rd-panel: #1c1713;
  --rd-border: #33291f;
  --rd-text: #ece4dc;
  --rd-text-muted: #9b8d82;
}

/* --- the picker on the account page --------------------------------------- */

.theme-choices {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 12px;
  margin: 14px 0 16px;
}

.theme-choice {
  display: block;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
  cursor: pointer;
  background: var(--surface);
}

.theme-choice:hover {
  border-color: var(--accent);
}

.theme-choice input {
  margin-right: 6px;
}

.theme-choice-name {
  font-size: 14px;
  font-weight: 600;
}

.theme-choice-note {
  display: block;
  margin: 2px 0 8px 20px;
  font-size: 12px;
  color: var(--text-muted);
}

/* A miniature of the two surfaces a theme actually changes, so the choice is
   made by looking rather than by reading four colour names. */
.theme-swatch {
  display: flex;
  height: 46px;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--border);
}

.theme-swatch span {
  flex: 1;
  display: flex;
  align-items: flex-end;
  padding: 4px;
  gap: 3px;
}

.theme-swatch i {
  display: block;
  width: 10px;
  height: 10px;
  border-radius: 3px;
}

/* --- permissions editor --------------------------------------------------- */

/* Wider than a plain modal: thirteen switches with a line of explanation each
   read as a wall in one narrow column. Two columns halve the height, and the
   dialog scrolls rather than growing past the window. */
dialog.perm-modal {
  width: 95vw;
  max-width: 880px;
  max-height: 88vh;
  overflow-y: auto;
}

.perm-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 2px 20px;
  margin-bottom: 4px;
}

/* The old rows put the name and its description in one wrapping flex line,
   which is what pushed titles to the right and let descriptions escape. A
   fixed checkbox column with the text beside it cannot do that. */
.perm-item {
  display: grid;
  grid-template-columns: 18px 1fr;
  align-items: start;
  gap: 10px;
  padding: 8px 0;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
}

.perm-item:hover {
  background: var(--bg);
}

.perm-item input {
  margin: 2px 0 0;
}

.perm-item-text {
  min-width: 0;
}

.perm-item-name {
  display: block;
  font-size: 13px;
  font-weight: 600;
}

.perm-item-desc {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-muted);
}

.perm-presets {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 10px 12px;
  margin-bottom: 4px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  font-size: 13px;
}

.perm-preset-note {
  flex: 1 1 100%;
  font-size: 11px;
}

.perm-preset-form {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  font-size: 13px;
}

.perm-preset-row {
  display: flex;
  gap: 8px;
  margin-top: 6px;
}

.perm-preset-row input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: inherit;
  font-size: 14px;
}

/* The org's presets, listed where the members they apply to are. */
.org-presets {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
  font-size: 12px;
}

.org-preset {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 4px 2px 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
}

.org-preset form {
  display: inline;
  margin: 0;
}

.org-preset-hint {
  flex: 1 1 100%;
  font-size: 11px;
}

@media (max-width: 700px) {
  .perm-grid {
    grid-template-columns: 1fr;
  }
}

/* A grid item's default minimum is its own content, and so is a flex item's,
   so a long hint could size its column instead of wrapping inside it. Give
   these somewhere to shrink to, and a long unbroken word somewhere to break. */
.category-columns > .category-group,
.category-group .category-checklist,
.category-group .checkbox-row,
.category-group .checkbox-row > * {
  min-width: 0;
}

.category-hint {
  max-width: 100%;
  overflow-wrap: anywhere;
}

/* A dialog renders where it sits in the DOM, and these live inside the
   actions cell of the members table, which is both right-aligned and nowrap —
   so every label in the permissions editor came out right-aligned, and no
   description ever wrapped. That is what pushed the names to the far side and
   ran the descriptions off the edge into the next column. A dialog should
   never inherit text layout from whatever it happened to be nested in, so
   reset both here rather than at each of the places that go wrong. */
dialog.modal {
  text-align: left;
  white-space: normal;
}

/* The hidden attribute has to actually hide. A class that sets display beats
   the browser's own [hidden] rule, so the attribute did nothing on anything
   styled below: the refresh bar sat on every profile page showing the word
   "Starting" and its 2% default width, which looked exactly like a scan that
   had begun and then wedged. Nothing was wrong with the scan — there was no
   scan. Declared once, here, so no later rule can reintroduce it. */
[hidden] {
  display: none !important;
}

/* The refresh progress bar. Deliberately plain: it reports which source is
   being fetched and how many are done, which is all the accuracy there is
   to have when each step is someone else's API taking however long it takes. */

.pl-progress {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-width: 260px;
}

.pl-progress-track {
  flex: 1;
  height: 6px;
  min-width: 120px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
}

.pl-progress-bar {
  height: 100%;
  width: 2%;
  border-radius: 999px;
  background: var(--accent);
  transition: width 220ms ease;
}

.pl-progress-label {
  white-space: nowrap;
}

.pl-external-empty {
  margin: 8px 0 0;
}

/* ---------------------------------------------------------------------------
   Player lookup in the nav bar.

   It sits between the brand and the page actions and takes the slack, so it is
   the widest thing up there — which is right, because it is the one control
   every page shares. The results list is positioned against the form, so the
   form is the containing block.
   --------------------------------------------------------------------------- */
.nav-lookup {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  max-width: 460px;
  margin: 0 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0 10px;
}

.nav-lookup:focus-within {
  border-color: var(--accent);
}

.nav-lookup-icon {
  color: var(--muted);
  font-size: 13px;
  line-height: 1;
  flex: none;
}

.nav-lookup input {
  flex: 1;
  min-width: 0;
  background: none;
  border: none;
  color: var(--text);
  font-size: 13px;
  padding: 8px 0;
  /* Safari draws its own control furniture on type=search. */
  appearance: none;
  -webkit-appearance: none;
}

.nav-lookup input:focus {
  outline: none;
}

.nav-lookup input::-webkit-search-decoration,
.nav-lookup input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

.nav-lookup-clear {
  flex: none;
  background: none;
  border: none;
  color: var(--muted);
  font-size: 17px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}

.nav-lookup-clear:hover {
  color: var(--text);
}

.nav-lookup-results {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 60;
  margin: 0;
  padding: 4px;
  list-style: none;
  max-height: 340px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.35);
}

.nav-lookup-result {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 9px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
}

.nav-lookup-result:hover,
.nav-lookup-result.active {
  background: var(--accent);
  color: #fff;
}

.nav-lookup-result.active .nav-lookup-meta,
.nav-lookup-result.active .nav-lookup-alias,
.nav-lookup-result:hover .nav-lookup-meta,
.nav-lookup-result:hover .nav-lookup-alias {
  color: rgba(255, 255, 255, 0.8);
}

.nav-lookup-dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--muted);
  opacity: 0.5;
}

.nav-lookup-dot.online {
  background: #6fcf97;
  opacity: 1;
}

.nav-lookup-name {
  flex: none;
  max-width: 46%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nav-lookup-alias {
  color: var(--muted);
  font-size: 11px;
  font-style: italic;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nav-lookup-meta {
  margin-left: auto;
  padding-left: 8px;
  color: var(--muted);
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 45%;
}

/* ---------------------------------------------------------------------------
   Dashboard toolbar, now that looking a player up has moved out of it.

   What is left is three controls about this list, so they are labelled and
   grouped instead of sitting in an undifferentiated row: filter these rows,
   choose the server, choose how wide to cast. The spacer pushes the server and
   scope controls to the right so the filter keeps the left edge.
   --------------------------------------------------------------------------- */
.rd-toolbar-spacer {
  flex: 1;
}

.rd-filter-field {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 1 320px;
  min-width: 220px;
  background: var(--rd-input, #0d0f13);
  border: 1px solid var(--rd-border);
  border-radius: 6px;
  padding: 0 10px;
}

.rd-filter-field:focus-within {
  border-color: var(--rd-accent);
}

.rd-filter-icon {
  color: var(--rd-text-muted);
  font-size: 12px;
  line-height: 1;
  flex: none;
}

/* The field draws the border now, so the input inside it draws none. */
.rd-filter-field .rd-filter-input {
  flex: 1;
  min-width: 0;
  background: none;
  border: none;
  padding: 7px 0;
  appearance: none;
  -webkit-appearance: none;
}

.rd-filter-field .rd-filter-input:focus {
  outline: none;
}

.rd-filter-input::-webkit-search-decoration,
.rd-filter-input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

.rd-filter-clear {
  flex: none;
  background: none;
  border: none;
  color: var(--rd-text-muted);
  font-size: 16px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}

.rd-filter-clear:hover {
  color: var(--rd-text);
}

.rd-filter-count {
  flex: none;
  color: var(--rd-text-muted);
  font-size: 11px;
  white-space: nowrap;
}

/* Nothing matched is worth saying louder than "3 of 40". */
.rd-filter-count.empty {
  color: var(--rd-bad);
}

.rd-field-label {
  color: var(--rd-text-muted);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}

.rd-server-field,
.rd-scope-field {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
}

.rd-online {
  display: flex;
  align-items: baseline;
  gap: 5px;
  padding-left: 4px;
}

.rd-online-value {
  color: var(--rd-text);
  font-size: 15px;
  font-weight: 600;
}

.rd-online-label {
  color: var(--rd-text-muted);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* A refresh that failed says so in the bar's own colour rather than sitting
   at a hopeful blue while the label explains it did not work. */
.pl-progress-bar.failed {
  background: var(--rd-bad, #e8776f);
}

/* Queued rather than running: the work is going to happen but reports no step
   of its own, so the bar shows motion without claiming a precision it does not
   have. A determinate width would be a number we invented. */
.pl-progress-bar.waiting {
  background: linear-gradient(90deg,
    var(--rd-border, #2c3031) 0%,
    var(--accent) 50%,
    var(--rd-border, #2c3031) 100%);
  background-size: 200% 100%;
  animation: pl-progress-waiting 1.4s linear infinite;
}

@keyframes pl-progress-waiting {
  from { background-position: 200% 0; }
  to { background-position: 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  .pl-progress-bar.waiting {
    animation: none;
  }
}

/* A BattleMetrics note, rebuilt from their rich text. Their notes are written
   in a much wider editor than this card, so long evidence links need somewhere
   to break rather than pushing the card wider than its grid column. */
.pl-ban-note {
  margin: 6px 0 0;
  line-height: 1.5;
  overflow-wrap: anywhere;
}

.pl-ban-note a {
  color: var(--accent);
}

.pl-ban-note code {
  padding: 1px 4px;
  border-radius: 4px;
  background: var(--rd-input, rgba(127, 127, 127, 0.15));
}

/* ==========================================================================
   Ticket bot pages
   ========================================================================== */

.tkt-page .field select,
.tkt-page .field textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font: inherit;
  font-size: 14px;
  background: var(--bg);
  color: var(--text);
}

.tkt-page .field textarea {
  resize: vertical;
  min-height: 96px;
  line-height: 1.45;
}

.tkt-page .field select:focus,
.tkt-page .field textarea:focus {
  outline: none;
  border-color: var(--accent);
}

.pill-warn {
  color: var(--warn);
  background: var(--warn-bg);
  border-color: var(--warn-border);
}

/* The setup list: what is done fades back, so what is left is what stands out. */
.tkt-checklist {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  display: grid;
  gap: 7px;
  font-size: 13px;
}

.tkt-check {
  display: flex;
  gap: 10px;
  align-items: flex-start;
}

.tkt-check-mark {
  flex: 0 0 20px;
  height: 20px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  border: 1px solid var(--border);
  color: var(--text-muted);
}

.tkt-check > span:last-child {
  padding-top: 1px;
}

.tkt-check-done .tkt-check-mark {
  color: var(--ok);
  background: var(--ok-bg);
  border-color: var(--ok-border);
}

.tkt-check-done > span:last-child {
  color: var(--text-muted);
}

.tkt-check-warn .tkt-check-mark {
  color: var(--warn);
  background: var(--warn-bg);
  border-color: var(--warn-border);
}

.tkt-check-detail {
  font-size: 12px;
}

.tkt-sub {
  font-size: 12px;
  margin-top: 2px;
}

.tkt-count {
  font-weight: 400;
  margin-left: 4px;
}

.tkt-inline-check {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  font-size: 13px;
  margin: 2px 0 6px;
  max-width: 70ch;
}

.tkt-inline-check input {
  margin-top: 3px;
}

.tkt-add-type {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 16px;
}

.tkt-add-type input[type="text"],
.tkt-add-type select,
.tkt-role-add select,
.tkt-role-add input {
  padding: 8px 10px;
  font: inherit;
  font-size: 13px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
}

.tkt-add-type input[type="text"] {
  flex: 1 1 260px;
}

.tkt-warnings {
  list-style: none;
  margin: 4px 0 0;
  padding: 10px 12px;
  display: grid;
  gap: 4px;
  font-size: 13px;
  color: var(--warn);
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  border-radius: 8px;
}

.tkt-error-list {
  margin: 6px 0 0;
  padding-left: 18px;
}

.tkt-visibility {
  border: none;
  padding: 0;
  margin: 0 0 16px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 8px;
}

.tkt-visibility label {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 10px 12px;
  font-size: 13px;
  line-height: 1.4;
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
}

.tkt-visibility label:has(input:checked) {
  border-color: var(--accent);
  background: var(--bg);
}

.tkt-visibility input {
  margin-top: 3px;
}

.tkt-emoji-field {
  flex: 0 0 140px !important;
}

.tkt-question {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 14px 2px;
  margin: 0 0 12px;
}

.tkt-question legend {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-muted);
  padding: 0 6px;
}

.tkt-q-options {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 18px;
  font-size: 13px;
  margin: -6px 0 12px;
}

.tkt-q-options label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.tkt-q-options select {
  padding: 6px 10px;
  font: inherit;
  font-size: 13px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
}

.tkt-private-note {
  display: none;
}

/* A private type never shows its form; the questions stay editable, but step
   back and say why. */
.tkt-dimmed .tkt-question {
  opacity: 0.45;
}

.tkt-dimmed .tkt-private-note {
  display: inline;
  color: var(--warn);
}

.tkt-roles th,
.tkt-roles td {
  text-align: center;
}

.tkt-roles th:first-child,
.tkt-roles td:first-child {
  text-align: left;
}

.tkt-roles th[title] {
  cursor: help;
}

.tkt-role-add {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}

.tkt-role-add select,
.tkt-role-add input {
  min-width: 240px;
}

.tkt-save-bar {
  margin: 0 0 20px;
}

.tkt-reason {
  max-width: 280px;
  font-size: 13px;
}

@media (max-width: 780px) {
  .tkt-emoji-field {
    flex: 1 1 auto !important;
  }
}

/* A read-only "field" — a setting shown for reference that lives elsewhere. */
.tkt-static-field {
  margin: 0;
  padding: 10px 12px;
  font-size: 13px;
  line-height: 1.4;
  border: 1px dashed var(--border);
  border-radius: 8px;
}

/* Re-fetch control for the ticket system's Discord-sourced dropdowns. */
.tkt-refresh-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.tkt-refresh-icon {
  display: inline-block;
  font-size: 13px;
  line-height: 1;
}

.tkt-refresh-btn.is-loading {
  opacity: 0.6;
  pointer-events: none;
}

.tkt-refresh-btn.is-loading .tkt-refresh-icon {
  animation: tkt-spin 0.7s linear infinite;
}

@keyframes tkt-spin {
  to { transform: rotate(360deg); }
}
