/* ============================================================================
   HawkSync — Command Console design system
   Dark, amber-accented, mono-for-data, sans-for-chrome.
   Touch-first: every interactive element is at least 48px tall.
   ============================================================================ */

:root {
  /* Surfaces */
  --bg:           #0d0f12;  /* app background */
  --bg-header:    #0a0c0e;  /* top header strip */
  --bg-panel:     #11151a;  /* card / panel body */
  --bg-elevated:  #161b21;  /* hover, active row, modal */
  --border:       #1f2329;
  --border-soft:  #1a1e24;
  --border-amber: #5a3f10;  /* amber-on-dark subtle border */

  /* Text */
  --text:         #d4d4d2;
  --text-strong:  #f5f5f4;
  --text-muted:   #9ca3af;
  --text-dim:     #6b7280;

  /* Accents */
  --amber:        #f5a524;
  --amber-soft:   rgba(245, 165, 36, 0.12);
  --amber-glow:   0 0 12px var(--amber);
  --green:        #22c55e;
  --green-glow:   0 0 8px var(--green);
  --red:          #ef4444;
  --red-soft:     rgba(239, 68, 68, 0.12);
  --blue:         #3b82f6;

  /* Typography */
  --font-sans: 'Outfit', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-mono: 'JetBrains Mono', 'Consolas', 'Courier New', monospace;

  /* Geometry */
  --radius:    8px;
  --radius-lg: 12px;
  --touch-min: 48px;        /* Apple HIG minimum */
  --touch-comfort: 56px;    /* preferred for primary actions */

  /* Layout */
  --rail-width: 220px;
  --header-height: 64px;
  --footer-height: 56px;
}

/* ---------- Reset / base ---------- */

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, h4, h5, h6 { margin: 0; font-weight: 700; color: var(--text-strong); }
a { color: var(--amber); text-decoration: none; }
a:hover { text-decoration: underline; }
button { font-family: inherit; }
input, select, textarea, button { font-family: inherit; font-size: inherit; color: inherit; }
code, kbd, pre { font-family: var(--font-mono); }

/* ---------- App shell (3-area grid: header / main+rail / footer) ---------- */

.cc-app {
  display: grid;
  grid-template-columns: var(--rail-width) 1fr;
  grid-template-rows: var(--header-height) 1fr var(--footer-height);
  height: 100vh;
  background: var(--bg);
}

.cc-header {
  grid-column: 1 / -1;
  background: var(--bg-header);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 28px;
}
.cc-header .brand {
  display: flex; align-items: center; gap: 12px;
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: 17px;
  color: var(--amber);
  letter-spacing: 0.08em;
}
.cc-header .brand .dot {
  width: 8px; height: 8px; background: var(--amber); border-radius: 50%;
  box-shadow: var(--amber-glow);
}
.cc-header .meta {
  display: flex; gap: 28px;
  /* Center each meta item vertically. Without this the inline text
     spans sit at the top of the container while the pills (taller)
     dominate the row, so the text and pills appear misaligned. */
  align-items: center;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-dim);
}
.cc-header .meta .live { color: var(--green); }
.cc-header .meta strong { color: var(--text); margin-left: 6px; }

.cc-rail {
  background: var(--bg-header);
  border-right: 1px solid var(--border);
  padding: 20px 14px;
  display: flex; flex-direction: column; gap: 4px;
  overflow-y: auto;
}
.cc-rail .sep {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-dim);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 16px 16px 4px;
  margin-top: 8px;
}
.cc-rail .sep:first-child { margin-top: 0; }

.cc-nav-item {
  display: flex; align-items: center; gap: 14px;
  height: 48px;
  padding: 0 16px;
  border-radius: var(--radius);
  color: var(--text-muted);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-decoration: none;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.cc-nav-item:hover { background: rgba(255,255,255,0.04); color: var(--text); text-decoration: none; }
.cc-nav-item.active { background: #1f2937; color: var(--amber); }
.cc-nav-item.active .ico { color: var(--amber); }
.cc-nav-item svg { width: 22px; height: 22px; flex-shrink: 0; }
.cc-nav-item .badge {
  margin-left: auto;
  background: var(--red-soft); color: var(--red);
  font-family: var(--font-mono); font-size: 11px; font-weight: 600;
  padding: 2px 8px; border-radius: 999px;
}

.cc-main {
  padding: 24px 32px;
  overflow-y: auto;
  overflow-x: hidden;
  /* Flex column so direct children (page-head, sub-nav, page body)
     can participate in vertical sizing. The chat page uses
     `flex: 1 1 auto` on its main wrapper to fill the remaining
     viewport height below the page head + sub-nav. Other pages
     use the default `flex: 0 1 auto` and just take their natural
     size (same as before). */
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.cc-footer {
  grid-column: 1 / -1;
  background: var(--bg-header);
  border-top: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 28px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
}
.cc-footer .keys { display: flex; gap: 20px; }
.cc-footer kbd {
  font-family: var(--font-mono);
  background: #1f2937;
  border: 1px solid #2d3138;
  padding: 2px 6px;
  border-radius: 3px;
  color: var(--text-muted);
  font-size: 10px;
  margin-right: 4px;
}

/* ---------- Page header ---------- */

.cc-page-head { display: flex; justify-content: space-between; align-items: end; margin-bottom: 20px; }
.cc-page-head .crumb {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
  letter-spacing: 0.08em;
  margin-bottom: 6px;
}
.cc-page-head h1 {
  font-family: var(--font-sans);
  font-size: 24px;
  font-weight: 700;
  color: var(--text-strong);
  letter-spacing: 0.02em;
}

.cc-page-intro {
  font-size: 13px;
  color: var(--text-muted);
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--amber);
  border-radius: var(--radius);
  padding: 12px 16px;
  margin-bottom: 20px;
}
.cc-page-intro code {
  background: var(--bg);
  padding: 1px 5px;
  border-radius: 3px;
  font-size: 12px;
  color: var(--amber);
}

/* ---------- Buttons (all ≥ 48px touch target) ---------- */

.cc-btn {
  height: var(--touch-min);
  padding: 0 22px;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.04em;
  border-radius: var(--radius);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  /* Anchor reset — the .cc-btn class is used on both <button>
     and <a> (e.g. the RemoteAuthGate's LOGIN link). Without
     these, anchors keep the browser default underline + link
     blue/purple which makes the button look like a hyperlink
     instead of a control, and the text-align defaults to the
     left because inline-flex on an <a> only centres its flex
     items if it actually has a non-inline content model.
     text-decoration: none + color: inherit + display:
     inline-flex is enough to make an <a class="cc-btn"> behave
     identically to a <button class="cc-btn">. */
  text-decoration: none;
  color: inherit;
  transition: background 0.12s, border-color 0.12s, transform 0.05s;
}
.cc-btn:hover { background: rgba(255,255,255,0.04); border-color: #2d3138; }
.cc-btn:active { transform: scale(0.98); }
.cc-btn:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }
.cc-btn svg { width: 18px; height: 18px; }

.cc-btn.primary {
  background: var(--amber);
  color: var(--bg-header);
  border-color: var(--amber);
}
.cc-btn.primary:hover { background: #f7b53d; border-color: #f7b53d; }

.cc-btn.danger {
  background: transparent;
  color: var(--red);
  border-color: #4a1f1f;
}
.cc-btn.danger:hover { background: var(--red-soft); }

.cc-btn.large { height: var(--touch-comfort); padding: 0 28px; font-size: 15px; }
.cc-btn.icon-only { width: var(--touch-min); padding: 0; }

.cc-btn-group { display: inline-flex; gap: 10px; }

/* ---------- Panel / card ---------- */

.cc-panel {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.cc-panel-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 14px 20px;
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  color: var(--text-muted);
  letter-spacing: 0.04em;
}
.cc-panel-head .left {
  display: flex; align-items: center; gap: 10px;
  flex: 1; min-width: 0;
}
.cc-panel-foot {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 20px;
  border-top: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-muted);
}
.cc-panel-foot strong { color: var(--text-strong); font-weight: 500; }
.cc-panel-foot > .cc-toggle,
.cc-panel-foot > .cc-toggle + .cc-toggle { margin-left: 0; }
.cc-panel-head .left strong { color: var(--amber); }
.cc-panel-head .right { font-family: var(--font-mono); color: var(--text-dim); font-size: 11px; }
.cc-panel-body { padding: 20px; }

/*
  Info icon — sits in the right slot of a .cc-panel-head to
  surface the card's muted description as a tooltip. The .left
  title row is flex:1 so anything placed after it inside the
  panel-head floats to the right edge automatically. The
  cursor:help signals to the user that hovering will reveal
  more info, and the amber hover state keeps it consistent
  with the rest of the Command Console design system.
*/
.cc-info-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  margin-left: 8px;
  color: var(--text-dim);
  cursor: help;
  transition: color 0.15s ease;
}
.cc-info-icon:hover,
.cc-info-icon:focus-visible {
  color: var(--amber);
}
.cc-info-icon:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 2px;
  border-radius: 2px;
}
.cc-info-icon svg { width: 18px; height: 18px; }

/*
  Active-playlist pill shown in the right panel head of the
  Maps page. Mirrors the original WinForms `btn_activePlaylist`
  (a small 67px-wide read-only button showing "P1" placed at
  the end of the bottom-right toolbar). Surface it here so the
  user always knows which playlist the server is currently
  running, even after they've switched the selected playlist
  in the top selector to edit it.
*/
.cc-active-playlist-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--radius);
  background: var(--amber-soft);
  border: 1px solid var(--border-amber);
  color: var(--amber);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}
.cc-active-playlist-badge .dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--amber);
  box-shadow: 0 0 6px var(--amber);
}
.cc-active-playlist-badge.matches {
  background: rgba(34, 197, 94, 0.1);
  border-color: #1a3a23;
  color: var(--green);
}
.cc-active-playlist-badge.matches .dot { background: var(--green); box-shadow: 0 0 6px var(--green); }

/*
  Panel chrome icons (header + foot) — sized at 20px in the header
  and 16px in the foot so the chrome has visual weight without
  crowding. Inline SVGs without explicit width/height default to
  100% of the parent in modern browsers, which makes a 24x24 viewBox
  inside a flex row stretch to 80+px — hence the explicit sizes.
*/
.cc-panel-head svg { width: 20px; height: 20px; flex-shrink: 0; }
.cc-panel-foot svg { width: 16px; height: 16px; flex-shrink: 0; }

/*
  Defensive global default: any inline SVG inside a cc-panel that
  doesn't have an explicit width/height attribute and isn't matched
  by a more specific selector gets a 14x14 size. More specific
  per-context rules (.cc-toggle svg, .cc-id-card .id-row svg, etc.)
  still win because they have higher specificity.
*/
.cc-panel svg:not([width]):not([height]):not([class*="icon"]) {
  width: 14px; height: 14px; flex-shrink: 0;
}

/* ---------- Table (Command Console style) ---------- */

.cc-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-sans);
}
.cc-table thead th {
  background: var(--bg-header);
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-dim);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 500;
  padding: 14px 20px;
  text-align: left;
  border-bottom: 1px solid var(--border);
}
.cc-table tbody tr {
  border-bottom: 1px solid var(--border-soft);
  cursor: pointer;
  transition: background 0.1s;
}
.cc-table tbody tr:hover { background: rgba(255,255,255,0.02); }
.cc-table tbody tr.selected {
  background: var(--amber-soft);
  border-left: 3px solid var(--amber);
}
.cc-table td {
  padding: 16px 20px;
  font-size: 14px;
  color: var(--text);
  vertical-align: middle;
}
.cc-table .id {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-dim);
}
.cc-table .name { color: var(--text-strong); font-weight: 600; font-size: 15px; }
/* The .tag sits next to the .name (sibling), NOT inside it, so the
   selector targets the table cell directly. */
.cc-table td .tag {
  display: inline-block;
  margin-left: 24px;
  padding: 3px 10px;
  background: var(--red-soft);
  color: var(--red);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  border-radius: 4px;
  letter-spacing: 0.08em;
  border: 1px solid #4a1f1f;
}
.cc-table .when { color: var(--text-muted); font-size: 13px; }

/* ---------- Status dot + pills ---------- */

.cc-status {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
}
.cc-status .dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--green);
  box-shadow: var(--green-glow);
}
.cc-status.offline { color: var(--text-dim); }
.cc-status.offline .dot { background: var(--text-dim); box-shadow: none; }
.cc-status.online { color: var(--green); }

/* Two-line status cell: pill on top, sub-line below. Used in
   the Admin user list to show ONLINE plus "last ping 4s ago" /
   "loopback operator" without expanding the row height. */
.cc-status-cell {
  display: flex; flex-direction: column; gap: 2px;
}
.cc-status-sub {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 400;
  color: var(--text-dim);
  letter-spacing: 0.04em;
  padding-left: 16px; /* aligns under the pill text, past the dot */
}

.cc-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 14px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  color: var(--text-muted);
}
.cc-pill .dot { width: 6px; height: 6px; background: var(--green); border-radius: 50%; }
.cc-pill.live { color: var(--green); border-color: #1a3a23; }
.cc-pill.live .dot { background: var(--green); box-shadow: 0 0 6px rgba(34,197,94,0.4); }
.cc-pill.scoring { color: var(--amber); border-color: var(--border-amber); background: var(--amber-soft); }
.cc-pill.scoring .dot { background: var(--amber); box-shadow: 0 0 6px var(--amber-glow); }
.cc-pill.dev  { color: var(--amber); border-color: var(--border-amber); background: var(--amber-soft); }
.cc-pill.dev  .dot { background: var(--amber); box-shadow: 0 0 6px var(--amber-glow); }

/* ---------- Tabs ---------- */

.cc-tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 20px;
}
.cc-tab {
  height: 48px;
  padding: 0 22px;
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.04em;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.cc-tab:hover { color: var(--text); }
.cc-tab.active {
  color: var(--amber);
  border-bottom-color: var(--amber);
}

/* ---------- Modal — 2-column body variant (user form) ---------- */

.cc-modal.large { max-width: min(1040px, 96vw); }
.cc-modal-body.columns {
  display: grid;
  grid-template-columns: minmax(380px, 1.15fr) minmax(320px, 1fr);
  gap: 18px;
  align-items: stretch;
  padding: 20px 24px;
}
.cc-modal-col {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}
.cc-modal-col > .cc-fieldset { margin-bottom: 0; }

/* Input wrapper — for the floating validation badge */
.cc-input-wrap { position: relative; }
.cc-input-wrap .cc-input { padding-right: 64px; }
.cc-hint-float {
  position: absolute;
  right: 8px;
  bottom: 8px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  padding: 3px 8px;
  border-radius: 4px;
  pointer-events: none;
  display: inline-flex; align-items: center; gap: 4px;
}
.cc-hint-float::before {
  content: "";
  width: 6px; height: 6px; border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 6px currentColor;
}
.cc-hint-float.success { color: var(--green); background: rgba(34, 197, 94, 0.12); border: 1px solid rgba(34, 197, 94, 0.3); }
.cc-hint-float.error   { color: var(--red);   background: var(--red-soft);           border: 1px solid #4a1f1f; }
.cc-hint-float.amber   { color: var(--amber); background: var(--amber-soft);         border: 1px solid var(--border-amber); }

/*
  Password field — wraps the input so the strength / match
  hint can sit BELOW it (as a sibling) without sharing the
  right gutter with the browser's native password reveal
  (::-ms-reveal in Edge, password manager UI in Chrome).
  The native reveal is left intact — it's the user's
  familiar control, and we don't want to fight the
  browser's UA stylesheet to hide it.

  The base .cc-input-wrap .cc-input rule reserves 64px of
  right padding for the username field's absolute-positioned
  .cc-hint-float badge. The password field has no such
  badge — the hint is a sibling below — so we override the
  padding down to a small gutter (32px) for the native eye.
  The browser parks ::-ms-reveal at the content/padding
  boundary, so this effectively pushes the eye ~32px closer
  to the right edge of the field. Bumping it lower (e.g.
  20px) pushes the eye further right; 32px leaves enough
  room that 16-character passwords don't run into the eye.
*/
.cc-pw-field .cc-input { padding-right: 32px; }

/* Inline hint, used as a sibling below a password field.
   Same visual language as .cc-hint-float but takes its own
   line so it never overlaps the input's content or the
   browser's native reveal. Class name is .cc-pw-hint (not
   .cc-hint) so it doesn't merge with the existing .cc-hint
   paragraph style used by the delete confirmation panel
   and other small-text hints — the two are visually
   different on purpose (this is a pill badge, that is a
   dim text run). */
.cc-pw-hint {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 8px;
  padding: 3px 8px;
  border-radius: 4px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
}
.cc-pw-hint::before {
  content: "";
  width: 6px; height: 6px; border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 6px currentColor;
}
.cc-pw-hint.success { color: var(--green); background: rgba(34, 197, 94, 0.12); border: 1px solid rgba(34, 197, 94, 0.3); }
.cc-pw-hint.error   { color: var(--red);   background: var(--red-soft);           border: 1px solid #4a1f1f; }
.cc-pw-hint.amber   { color: var(--amber); background: var(--amber-soft);         border: 1px solid var(--border-amber); }
.cc-pw-hint.muted   { color: var(--text-dim); background: var(--bg-elevated);     border: 1px solid var(--border); }

/* Active cell — vertically centers the checkbox against the (taller)
   username cell and pins it to the right edge of its column. */
.cc-active-cell {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  height: 100%;
  padding-right: 4px;
}
.cc-active-cell .cc-checkbox {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.06em;
}

/* IDENTITY row — USERNAME label on its own line, then the input
   and ACTIVE checkbox share a row so the checkbox centers to the
   input (not to the whole fieldset). */
.cc-identity-grid {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.identity-input-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 16px;
}
.identity-input-row .cc-input-wrap { min-width: 0; }

/* Permissions tree — structured grid with parent/child hierarchy */
.cc-perm-fieldset { display: flex; flex-direction: column; height: 100%; }
.cc-perm-fieldset legend {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%;
  padding-right: 4px;
}
.cc-perm-count {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  color: var(--amber);
  background: var(--amber-soft);
  border: 1px solid var(--border-amber);
  padding: 2px 8px;
  border-radius: 999px;
  letter-spacing: 0.08em;
}
.cc-perm-legend-actions {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.cc-perm-toggle {
  background: var(--amber-soft);
  color: var(--amber);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  padding: 0 12px;
  height: 28px;
  border-radius: 999px;
  border: 1px solid var(--border-amber);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  transition: background 0.12s, border-color 0.12s, transform 0.05s;
}
.cc-perm-toggle:hover { background: rgba(245, 165, 36, 0.22); border-color: var(--amber); }
.cc-perm-toggle:active { transform: scale(0.97); }
.cc-perm-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 18px;
  flex: 1;
  align-items: start;
}
.cc-perm-col {
  display: flex;
  flex-direction: column;
}
.cc-perm-group { break-inside: avoid; }
.cc-perm-parent {
  padding: 0;
  font-weight: 600;
  font-size: 13px;
  color: var(--text-strong);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-family: var(--font-mono);
}
.cc-perm-children {
  margin-left: 24px;
  padding-left: 10px;
  border-left: 2px solid var(--border);
  margin-bottom: 0;
  display: flex;
  flex-direction: column;
}
.cc-perm-child {
  padding: 0;
  font-size: 13px;
  color: var(--text-muted);
}
.cc-perm-child span { font-family: var(--font-sans); text-transform: none; letter-spacing: 0; }

/* ---------- Form inputs (large touch targets) ---------- */

.cc-field { display: flex; flex-direction: column; gap: 8px; }
.cc-field label {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
}
.cc-field label .req { color: var(--amber); margin-left: 4px; }

.cc-input, .cc-select, .cc-textarea {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 15px;
  padding: 0 14px;
  border-radius: var(--radius);
  transition: border-color 0.12s, box-shadow 0.12s;
}
.cc-input, .cc-select { height: var(--touch-comfort); }
.cc-textarea { padding: 14px; min-height: 80px; resize: vertical; line-height: 1.5; }
.cc-input:focus, .cc-select:focus, .cc-textarea:focus {
  outline: none;
  border-color: var(--amber);
  box-shadow: 0 0 0 3px var(--amber-soft);
}
.cc-input:disabled, .cc-select:disabled { opacity: 0.5; cursor: not-allowed; }
.cc-input.is-invalid { border-color: var(--red); }
.cc-input.is-valid { border-color: var(--green); }

.cc-hint {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  min-height: 14px;
}
.cc-hint.error { color: var(--red); }
.cc-hint.success { color: var(--green); }
.cc-hint.muted { color: var(--text-dim); }

.cc-checkbox {
  display: inline-flex; align-items: center; gap: 12px;
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--text);
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.cc-checkbox input[type="checkbox"] {
  width: 22px; height: 22px;
  accent-color: var(--amber);
  cursor: pointer;
}

/* ---------- Fieldset (grouped form sections) ---------- */

.cc-fieldset {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 14px 18px;
  margin-bottom: 16px;
}
.cc-fieldset legend {
  padding: 0 10px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--amber);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 600;
}
.cc-fieldset .row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.cc-fieldset .row > * { min-width: 0; }

/* ---------- Modal (custom, no Bootstrap) ---------- */

.cc-modal-bg {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 24px;
  animation: cc-fade-in 0.15s ease;
}

/* Invisible click-catcher used by the player context menu so
   the user can dismiss the menu by clicking anywhere else
   without dimming the cards behind it. */
.cc-click-catcher {
  position: fixed; inset: 0;
  background: transparent;
  z-index: 999;
}
.cc-modal {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  width: 100%;
  max-width: 720px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: cc-rise 0.2s ease;
}
.cc-modal.large { max-width: 960px; }
.cc-modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px 24px;
  border-bottom: 1px solid var(--border);
}
.cc-modal-head h2 {
  font-size: 17px;
  font-weight: 700;
  color: var(--text-strong);
  letter-spacing: 0.04em;
}
.cc-modal-head.danger h2 { color: var(--red); }
.cc-modal-close {
  width: 44px; height: 44px;
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: 20px;
  border-radius: var(--radius);
  cursor: pointer;
}
.cc-modal-close:hover { background: rgba(255,255,255,0.06); color: var(--text); }
.cc-modal-body {
  padding: 24px;
  overflow-y: auto;
  flex: 1;
}
.cc-modal-foot {
  display: flex; justify-content: flex-end; gap: 10px;
  padding: 16px 24px;
  border-top: 1px solid var(--border);
  background: var(--bg-header);
}

/* Compact confirm modal (used by Maps.razor for the
   "save active playlist" + "set active while online" prompts).
   Mirrors the WinForms MessageBox flow but styled to the
   Command Console design system. */
.cc-confirm-modal {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  width: 100%;
  max-width: 480px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: cc-rise 0.2s ease;
}
.cc-confirm-modal .cc-confirm-body {
  padding: 22px 24px 8px;
  display: flex;
  gap: 16px;
  align-items: flex-start;
}
.cc-confirm-modal .cc-confirm-icon {
  width: 44px; height: 44px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  background: var(--amber-soft);
  color: var(--amber);
  border: 1px solid var(--border-amber);
}
.cc-confirm-modal .cc-confirm-icon.danger {
  background: var(--red-soft);
  color: var(--red);
  border-color: #4a1f1f;
}
.cc-confirm-modal .cc-confirm-icon svg { width: 22px; height: 22px; }
.cc-confirm-modal .cc-confirm-content { flex: 1; min-width: 0; }
.cc-confirm-modal .cc-confirm-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--text-strong);
  letter-spacing: 0.04em;
  margin-bottom: 6px;
}
.cc-confirm-modal .cc-confirm-msg {
  font-size: 13px;
  line-height: 1.5;
  color: var(--text);
}
.cc-confirm-modal .cc-confirm-foot {
  display: flex; justify-content: flex-end; gap: 10px;
  padding: 16px 24px;
  border-top: 1px solid var(--border);
  background: var(--bg-header);
}

@keyframes cc-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes cc-rise { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }

/* ---------- Alerts ---------- */

.cc-alert {
  padding: 12px 16px;
  border-radius: var(--radius);
  font-size: 14px;
  margin-bottom: 12px;
  display: flex; align-items: center; gap: 10px;
}
.cc-alert.error { background: var(--red-soft); color: var(--red); border: 1px solid #4a1f1f; }
.cc-alert.success { background: rgba(34, 197, 94, 0.1); color: var(--green); border: 1px solid #1a3a23; }
.cc-alert.info { background: var(--amber-soft); color: var(--amber); border: 1px solid var(--border-amber); }

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

.cc-row { display: flex; gap: 16px; align-items: stretch; }
.cc-row > * { min-width: 0; }
.cc-col-grow { flex: 1; }
.cc-spacer-md { height: 20px; }
.cc-spacer-lg { height: 40px; }

.cc-empty {
  padding: 40px 20px;
  text-align: center;
  color: var(--text-dim);
  font-size: 14px;
}

/* ---------- Utility text ---------- */

.mono { font-family: var(--font-mono); }
.muted { color: var(--text-muted); }
.dim { color: var(--text-dim); }
.amber { color: var(--amber); }
.green { color: var(--green); }
.red { color: var(--red); }

/* ============================================================================
   Dashboard
   Command Console layout for the at-a-glance server view. The page is
   composed of a full-width status bar at the top, then a 4-up grid of
   equal cards, then a 2-up row of wider cards below. All grid gutters
   are 16px, cards are the standard cc-panel styling (border, header,
   body, optional foot).
   ============================================================================ */

/* ---------- Top status bar ---------- */

.cc-statusbar {
  display: grid;
  /* 2-col layout: status pill on the left (1fr = fills available
     space), map/time/process meta on the right (auto = its natural
     width). The center player count was removed — the PLAYERS card
     shows it via its bigcount. */
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 24px;
  padding: 14px 24px;
  margin-bottom: 18px;
  background: linear-gradient(180deg, var(--bg-elevated), var(--bg-panel));
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: 0 1px 0 rgba(255,255,255,0.02) inset;
}
.cc-statusbar.online  { border-top: 2px solid var(--green); }
.cc-statusbar.offline { border-top: 2px solid var(--text-dim); }

.cc-statusbar .left   { display: flex; align-items: center; gap: 10px; justify-content: flex-start; }
.cc-statusbar .right  { display: flex; align-items: center; gap: 14px; justify-content: flex-end; flex-wrap: wrap; }

.cc-statusbar .center .label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  color: var(--text-dim);
  text-transform: uppercase;
}
.cc-statusbar .center .val {
  font-family: var(--font-mono);
  font-size: 28px;
  font-weight: 600;
  color: var(--text-strong);
  letter-spacing: 0.02em;
}

.cc-statusbar .meta {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--text-muted);
}
.cc-statusbar .meta svg { width: 14px; height: 14px; }
.cc-statusbar .meta strong { color: var(--text-strong); font-weight: 600; margin-left: 4px; }

.cc-pill.amber  { color: var(--amber); border-color: var(--border-amber); background: var(--amber-soft); }
.cc-pill.amber  .dot { background: var(--amber); box-shadow: 0 0 6px var(--amber-glow); }
.cc-pill.offline { color: var(--text-dim); border-color: var(--border); }
.cc-pill.offline .dot { background: var(--text-dim); }

/* ---------- Dashboard grid (4 + 1+3 layout) ---------- */

.cc-dash-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  /* Top row sizes to its content (the four summary cards). Bottom row
     takes the remaining vertical space so the Quick Config + Recent
     Activity cards stretch to fill the viewport. Requires a defined
     grid height, which the `flex: 1 1 auto` below provides — the grid
     participates in .cc-main's flex column and grows into whatever's
     left after the status bar. */
  grid-template-rows: auto 1fr;
  align-items: stretch;
  gap: 16px;
  flex: 1 1 auto;
  min-height: 0;
}
/* Bottom-row cards use a 1 + 3 column split (1/4 + 3/4). Recent
   Activity is the wider one because the chat log wants horizontal
   room; Quick Config is just a column of toggles so it doesn't
   need much. */
.cc-dash-grid .cc-dash-wide-half {
  grid-column: span 1;
}
.cc-dash-grid .cc-dash-wide-3q {
  grid-column: span 3;
}
/* Top-row card that spans 2 of the 4 columns. Used by the PLAYERS
   card after the CURRENT MAP card was removed — the top row is now
   3 cards: SERVER IDENTITY (1 col) | PLAYERS (2 cols) | NEXT MAP
   (1 col). */
.cc-dash-grid .cc-dash-wide-2q {
  grid-column: span 2;
}

/* When the two wide bottom cards are in stretch mode, their bodies
   need to be a flex column so the inner content (config grid /
   chat log) can grow into the available space and scroll internally
   instead of pushing the page to scroll. The cc-panel-stretch rule
   already gives the body `flex: 1 1 auto` + `min-height: 0` — we
   just need the column-direction on the body itself. The .cc-dash-body
   class is added to both bottom-row panel bodies in Home.razor. */
.cc-panel.cc-panel-stretch > .cc-panel-body.cc-dash-body {
  display: flex;
  flex-direction: column;
}
/* The Quick Config toggle grid grows to fill the body. The toggles
   stay pinned to the top (grid's default align-content: start) and
   the .cc-config-bottom row sits right after, anchoring to the
   bottom of the body when the card is taller than its content. */
.cc-panel.cc-panel-stretch > .cc-panel-body.cc-dash-body > .cc-config-grid {
  flex: 1 1 auto;
  min-height: 0;
}
/* The Recent Activity chat log fills the body, scrolling internally
   once the entries overflow. Overrides the 220px cap used in the
   static .cc-chat-log rule. */
.cc-panel.cc-panel-stretch > .cc-panel-body.cc-dash-body > .cc-chat-log {
  flex: 1 1 auto;
  min-height: 100px;
  max-height: none;
}

/* Quick Actions lives in a narrow (1/4) card on the dashboard. The
   3-col .cc-config-grid default is too cramped at that width; force
   a 2-column layout with 6 explicit rows. The markup declares the
   items in column-major order (col 1 first, col 2 second) so
   `grid-auto-flow: column` is required — without it the items fill
   row-by-row and the second column's content drops to the bottom
   half of the card. The minmax(0, 1fr) rows let the grid distribute
   the available height evenly across the 6 rows when the card is
   stretched by `.cc-panel-stretch`. Wins over the global
   `.cc-config-grid` rule via higher specificity. */
.cc-dash-grid .cc-config-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: repeat(6, minmax(0, 1fr));
  grid-auto-flow: column;
}
/* Blank placeholder button — same shape and height as a real toggle
   so the 2x6 grid stays visually uniform, but with a dashed border
   and dimmed text to signal "not wired up yet". Future quick-action
   buttons can replace these by swapping the cc-toggle-blank class
   for a real cc-toggle with content. */
.cc-toggle.cc-toggle-blank {
  border-style: dashed;
  border-color: var(--border-soft);
  background: transparent;
  color: var(--text-dim);
  cursor: default;
  /* No content to push the height — the base .cc-toggle rule already
     sets min-height: 48px, which is what we want for grid alignment. */
}
.cc-toggle.cc-toggle-blank:hover {
  border-color: var(--border);
  background: transparent;
}

/* Readonly toggle — applied when the viewer doesn't have permission
   to change the setting (e.g. remote caller with no JWT, viewing the
   dashboard without being behind the auth gate). Visual cues:
     • cursor: not-allowed (signals "you can't click this")
     • opacity 0.65 (slight dim, not full grey-out — still readable)
     • pointer-events: none on the wrapper would be more thorough
       but breaks focus / tooltip; instead the @onclick handler in
       the page early-returns when !_isAllowedThrough, so even if
       the click somehow fires, the state doesn't change. */
.cc-toggle.cc-toggle-readonly {
  cursor: not-allowed;
  opacity: 0.65;
}
.cc-toggle.cc-toggle-readonly:hover {
  /* Suppress the hover lift that real toggles have so the readonly
     state is visually consistent. */
  border-color: inherit;
  background: inherit;
}
/* Busy toggle — applied while a save + push is in flight. The click
   handler early-returns, the cursor changes to "wait", and the
   toggle dims slightly so it's obvious the click registered. */
.cc-toggle.cc-toggle-busy {
  cursor: wait;
  opacity: 0.7;
  pointer-events: none;
}

/* Floating quick-config toast — appears in the bottom-right of the
   viewport when a quick-config toggle completes. Sits above the
   page footer and uses the same amber/danger accent colors as the
   chat page's toast. position: fixed so it doesn't reflow the page
   contents. The .cc-quick-toast-title is a one-liner with the
   action label; .cc-quick-toast-msg is the human-readable detail. */
.cc-quick-toast {
  position: fixed;
  bottom: 56px;       /* sit above the cc-footer */
  right: 24px;
  z-index: 1000;
  min-width: 240px;
  max-width: 360px;
  padding: 10px 14px;
  border-radius: var(--radius);
  background: var(--bg-panel);
  border: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: 12px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  animation: cc-toast-slide-in 180ms ease-out;
}
.cc-quick-toast-success { border-color: #1a3a23; background: rgba(34, 197, 94, 0.10); }
.cc-quick-toast-error   { border-color: #4a1f1f; background: rgba(255, 100, 100, 0.10); }
.cc-quick-toast-warning { border-color: #4a3a1f; background: rgba(245, 165, 36, 0.10); }
.cc-quick-toast-title {
  font-weight: 600;
  color: var(--text-strong);
  margin-bottom: 2px;
}
.cc-quick-toast-success .cc-quick-toast-title { color: var(--green); }
.cc-quick-toast-error   .cc-quick-toast-title { color: var(--red); }
.cc-quick-toast-warning .cc-quick-toast-title { color: var(--amber); }
.cc-quick-toast-msg {
  color: var(--text-muted);
  line-height: 1.4;
}
@keyframes cc-toast-slide-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (max-width: 1100px) {
  /* Stack to 2 columns at the "tablet portrait" breakpoint. Disable
     the fill-the-remaining-height behavior because stacking means
     each card is its own row — there's no "remainder" to fill. All
     wide cards (PLAYERS at 2 cols, plus the bottom row's 1-col +
     3-col split) now span the full 2-col width so they each get a
     row. The .cc-config-grid is already 2 cols from the dashboard
     rule above, so no extra override needed. */
  .cc-dash-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: auto;
    flex: 0 1 auto;
  }
  .cc-dash-grid .cc-dash-wide-half { grid-column: span 2; }
  .cc-dash-grid .cc-dash-wide-2q   { grid-column: span 2; }
  .cc-dash-grid .cc-dash-wide-3q   { grid-column: span 2; }
}
@media (max-width: 700px) {
  /* Single column at the phone breakpoint. */
  .cc-dash-grid { grid-template-columns: 1fr; }
  .cc-dash-grid .cc-dash-wide-half { grid-column: span 1; }
  .cc-dash-grid .cc-dash-wide-2q   { grid-column: span 1; }
  .cc-dash-grid .cc-dash-wide-3q   { grid-column: span 1; }
  .cc-statusbar { grid-template-columns: 1fr; text-align: center; }
  .cc-statusbar .left, .cc-statusbar .right { justify-content: center; }
}

/* ---------- Card: CURRENT MAP / NEXT MAP ---------- */

.cc-map-card,
.cc-next-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px 18px 12px;
}
.cc-map-card .map-name,
.cc-next-card .map-name {
  font-family: var(--font-sans);
  font-size: 26px;
  font-weight: 700;
  line-height: 1.1;
  color: var(--text-strong);
  margin-top: 2px;
}
.cc-map-card .map-file,
.cc-next-card .map-file {
  font-size: 12px;
  letter-spacing: 0.02em;
  margin-top: -2px;
}

.cc-tag-row {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-top: 4px;
}
.cc-tag {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.2;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 6px;
}
/* SVG inside a .cc-tag — must align its visual center to the text
   baseline, not the SVG box center. Three fixes stacked:
     1. `display: block` — kills the inline-baseline behaviour that
        would otherwise push the text down a pixel or two.
     2. `vertical-align: middle` — belt-and-suspenders for older
        browsers / inline flex edge cases.
     3. `align-self: center` — explicit cross-axis alignment on the
        SVG so it always matches the text's vertical center.
   Without this, the first tag (with icon) sits slightly higher than
   the second tag (text only) because the 12x12 SVG box and the 11px
   line-height text have different visual centers. */
.cc-tag svg { 
  width: 12px; 
  height: 12px; 
  opacity: 0.8; 
  flex-shrink: 0; 
  display: block;
  vertical-align: middle;
  align-self: center;
}
.cc-tag.blue { color: #6aa9ff; border-color: #1e3a5f; background: rgba(58,128,255,0.08); }
.cc-tag.red  { color: #ff6b6b; border-color: #5a1f1f; background: rgba(255,80,80,0.08); }

/* Team scores (Blue vs Red progress bars). */
.cc-team-scores {
  display: flex; flex-direction: column; gap: 10px;
  margin-top: 6px;
}
.cc-team-scores .team-head {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.cc-team-scores .team-head svg { width: 14px; height: 14px; }
.cc-team-scores .team-head strong {
  font-size: 15px;
  font-weight: 700;
  margin-left: auto;
  color: var(--text-strong);
}
.cc-team-scores .team.red .team-head strong { color: #ff8585; }
.cc-team-scores .team.blue .team-head strong { color: #7ab0ff; }
.cc-team-scores .bar {
  height: 8px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 999px;
  overflow: hidden;
}
.cc-team-scores .bar .fill {
  height: 100%;
  border-radius: 999px;
  transition: width 250ms ease;
}
.cc-team-scores .bar .fill.blue { background: linear-gradient(90deg, #2b6dd6, #4a8ff5); }
.cc-team-scores .bar .fill.red  { background: linear-gradient(90deg, #d33a3a, #ff6b6b); }

/* Team score pills — split-pill design. Left half is the team name on the
   team color; right half is the score on a slightly darker shade of the same
   team color. 2 pills for 2-team matches, 4 pills for 4-team matches.
   Each pill is a vertical column with a progress bar below it (only when
   the bar is meaningful per the audit — KOTH/SD/AD hide the bar).

   Grid layout (not flex-wrap) so 4 teams always land in ONE row when the
   card is in 4-team mode. .cols-2 (default) and .cols-4 (set by the
   page when gameEnableFourTeams) pick the explicit column count. */
.cc-team-scores {
  display: grid;
  grid-template-columns: 1fr 1fr;   /* 2-team default */
  gap: 8px;
  margin-top: 8px;
}
.cc-team-scores.cols-4 {
  grid-template-columns: repeat(4, 1fr);  /* 4-team — all in one row */
}
.cc-team-scores .team-col {
  display: flex;
  flex-direction: column;
  gap: 4px;
  /* No min-width — the grid column track sizes each column, and the
     pill inside can shrink to fit. Without this, 4 pills at the
     default 88px min would overflow the narrow CURRENT MAP card
     (1/4 of the dashboard top row) and VIOLET wraps to row 2. */
  min-width: 0;
}
/* Team pill: dark base + subtle team-color tints, fully rounded. */
.cc-team-scores .team-pill {
  display: flex;
  align-items: stretch;
  height: 30px;
  border-radius: 999px;
  overflow: hidden;
  font-family: var(--font-mono);
  font-weight: 700;
  letter-spacing: 0.04em;
  font-size: 11px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
}
.cc-team-scores .team-pill .pill-name {
  flex: 1 1 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  text-transform: uppercase;
  font-size: 10px;
  border-right: 1px solid currentColor;
  opacity: 0.92;
  /* Defensive: when 4 pills share a row, "VIOLET" / "YELLOW" can be
     wider than the 50% half. Clip + ellipsis rather than overflow. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cc-team-scores .team-pill .pill-score {
  flex: 1 1 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 800;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}
/* 4-team variant — tighter pills so BLUE/RED/YELLOW/VIOLET all fit
   in one row of the CURRENT MAP card. Drops the name font from 10px
   to 9px (VIOLET at 9px ≈ 30px, fits in the 50% half) and the score
   font from 13px to 11px. Padding goes to 0 on both halves so the
   flex split has no internal margin eating space. */
.cc-team-scores.cols-4 .team-pill {
  height: 28px;
  font-size: 10px;
}
.cc-team-scores.cols-4 .team-pill .pill-name {
  font-size: 9px;
  padding: 0 4px;
  letter-spacing: 0.02em;
}
.cc-team-scores.cols-4 .team-pill .pill-score {
  font-size: 11px;
  padding: 0 4px;
}
/* BLUE — base #6aa9ff (matches .cc-tag.blue) */
.cc-team-scores .team-pill.blue   { color: #6aa9ff; border-color: rgba(58,128,255,0.35); }
.cc-team-scores .team-pill.blue   .pill-name  { background: rgba(58,128,255,0.08); }
.cc-team-scores .team-pill.blue   .pill-score { background: rgba(58,128,255,0.18); }
/* RED — base #ff6b6b (matches .cc-tag.red) */
.cc-team-scores .team-pill.red    { color: #ff6b6b; border-color: rgba(255,80,80,0.35); }
.cc-team-scores .team-pill.red    .pill-name  { background: rgba(255,80,80,0.08); }
.cc-team-scores .team-pill.red    .pill-score { background: rgba(255,80,80,0.18); }
/* YELLOW — same family as the theme amber */
.cc-team-scores .team-pill.yellow { color: #fbbf24; border-color: rgba(251,191,36,0.32); }
.cc-team-scores .team-pill.yellow .pill-name  { background: rgba(251,191,36,0.08); }
.cc-team-scores .team-pill.yellow .pill-score { background: rgba(251,191,36,0.18); }
/* VIOLET */
.cc-team-scores .team-pill.violet { color: #a78bfa; border-color: rgba(167,139,250,0.32); }
.cc-team-scores .team-pill.violet .pill-name  { background: rgba(167,139,250,0.08); }
.cc-team-scores .team-pill.violet .pill-score { background: rgba(167,139,250,0.18); }
.cc-team-scores .team-bar {
  height: 6px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 999px;
  overflow: hidden;
}
.cc-team-scores .team-bar .fill {
  height: 100%;
  border-radius: 999px;
  transition: width 250ms ease;
}
/* Bar fill matches pill tint (no gradient — flat team-color fill) */
.cc-team-scores .team-bar.blue   .fill { background: #6aa9ff; }
.cc-team-scores .team-bar.red    .fill { background: #ff6b6b; }
.cc-team-scores .team-bar.yellow .fill { background: #fbbf24; }
.cc-team-scores .team-bar.violet .fill { background: #a78bfa; }

/* ---------- Card: MATCH STATUS ---------- compact team pills (no bar) ---------- */
/* Compact variant: drops the bar (no percent to show), tightens the pill,
   and stacks the row so 2-4 team pills fit in the narrower card. */
.cc-team-scores.compact {
  display: flex; flex-direction: row; gap: 6px; flex-wrap: wrap;
  margin-top: 4px;
}
.cc-team-scores.compact .team-pill {
  height: 24px; min-width: 0; flex: 1 1 0; font-size: 10px;
}
.cc-team-scores.compact .team-pill .pill-name  { font-size: 9px; padding: 0 8px; }
.cc-team-scores.compact .team-pill .pill-score { font-size: 11px; min-width: 24px; padding: 0 6px; }

/* Top-killer row: team-color dot, name, leader-dot fill, kills, K/D */
.cc-players-card .kill-row .team-dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  margin-right: 2px;
  background: var(--text-dim);
  box-shadow: 0 0 0 1px rgba(0,0,0,0.4);
}
.cc-players-card .kill-row .team-dot.blue   { background: #6aa9ff; }
.cc-players-card .kill-row .team-dot.red    { background: #ff6b6b; }
.cc-players-card .kill-row .team-dot.yellow { background: #fbbf24; }
.cc-players-card .kill-row .team-dot.violet { background: #a78bfa; }
.cc-players-card .kill-row .team-dot.none   { background: var(--text-dim); }

.cc-players-card .kill-row .kd {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
  min-width: 44px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Players card footer: ping on the left, "View all →" link pushed right */
.cc-panel-foot.players-foot {
  justify-content: space-between;
}
.cc-panel-foot .foot-link {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--amber);
  text-decoration: none;
  letter-spacing: 0.04em;
}
.cc-panel-foot .foot-link:hover { color: var(--text-strong); }
.cc-panel-foot .foot-empty {
  font-family: var(--font-mono);
  color: var(--text-dim);
}

/* ---------- Card: SERVER IDENTITY ---------- */

.cc-id-card {
  display: flex; flex-direction: column; gap: 10px;
  padding: 16px 18px 12px;
}
.cc-id-card .id-title {
  font-family: var(--font-sans);
  font-size: 20px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--text-strong);
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}
.cc-id-card .id-rows {
  display: flex; flex-direction: column; gap: 6px;
}
.cc-id-card .id-row {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-muted);
}
.cc-id-card .id-row svg { width: 13px; height: 13px; opacity: 0.75; flex-shrink: 0; }
.cc-id-card .id-row strong {
  color: var(--text-strong);
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
  min-width: 0;
}
/* MOTD is a free-form user-typed string — keep it on one line so an
   unusually long message can't break the card layout. */
.cc-id-card .id-row.motd strong {
  display: block;
}

/* ---------- Card: MATCH STATUS ---------- */

.cc-players-card {
  display: flex; flex-direction: column; gap: 10px;
  padding: 16px 18px 12px;
}
/* Player count floated right of the panel header on the PLAYERS
   card. The big number (current players) is amber-tinted + bold; the
   /max-slots is dimmer so the eye lands on the current count first.
   Sized to balance against the "PLAYERS" title on the left. */
.cc-panel-head .right.players-count {
  display: inline-flex;
  align-items: baseline;
  gap: 2px;
  font-family: var(--font-mono);
  font-size: 20px;
  font-weight: 700;
  line-height: 1;
  color: var(--text-strong);
  letter-spacing: 0.02em;
}
.cc-panel-head .right.players-count .players-count-num { color: var(--amber); }
.cc-panel-head .right.players-count .players-count-sep {
  color: var(--text-dim);
  margin: 0 3px;
  font-weight: 400;
}
.cc-panel-head .right.players-count .players-count-max {
  color: var(--text-dim);
  font-size: 14px;
  font-weight: 500;
}
.cc-players-card .subhead,
.cc-next-card .subhead {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--text-dim);
  text-transform: uppercase;
  margin-top: 4px;
  display: flex; align-items: center; gap: 6px;
}
.cc-players-card .subhead svg,
.cc-next-card .subhead svg { width: 12px; height: 12px; opacity: 0.7; flex-shrink: 0; }
.cc-players-card .subhead::before {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}
.cc-players-card .subhead::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}
.cc-next-card .subhead {
  margin-top: 8px;
}
.cc-next-card .subhead::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.cc-players-card .kill-list {
  display: flex; flex-direction: column; gap: 4px;
  margin-top: 2px;
}
.cc-players-card .kill-row {
  display: flex; align-items: center; gap: 6px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text);
  padding: 2px 0;
}
.cc-players-card .kill-row .rank {
  display: inline-flex; align-items: center; gap: 3px;
  color: var(--amber);
  font-weight: 600;
  min-width: 28px;
}
.cc-players-card .kill-row .rank svg { width: 11px; height: 11px; }
.cc-players-card .kill-row .name {
  color: var(--text-strong);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 60%;
  min-width: 0;
}
.cc-players-card .kill-row .dots {
  flex: 1;
  height: 1px;
  background-image: linear-gradient(90deg, var(--border) 50%, transparent 50%);
  background-size: 6px 1px;
  background-repeat: repeat-x;
  margin: 0 4px;
  min-width: 12px;
  align-self: center;
}
.cc-players-card .kill-row .score {
  color: var(--amber);
  font-weight: 600;
  min-width: 18px;
  text-align: right;
}
.cc-players-card .kill-empty {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-dim);
  font-style: italic;
  padding: 8px 0;
}

/* Per-team top-killers grid. One column per team, lined up directly
   below the matching team-count pill above. The grid is 2 cols in
   2-team mode and 4 cols in 4-team mode (matches the pill row via
   the same cols-2/cols-4 class). Each column is a min-width: 0
   flex column so long player names truncate instead of pushing the
   grid into overflow. */
.cc-players-card .cc-top-killers-grid {
  display: grid;
  gap: 14px;
  min-width: 0;
}
.cc-players-card .cc-top-killers-grid.cols-2 { grid-template-columns: 1fr 1fr; }
.cc-players-card .cc-top-killers-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
.cc-players-card .team-killers {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  /* Left accent stripe in the team color so the column reads as
     "this is the BLUE column" even when the header is scrolled
     out of view. */
  border-left: 2px solid var(--team-color, var(--border));
  padding-left: 8px;
}
.cc-players-card .team-killers.blue   { --team-color: #6aa9ff; }
.cc-players-card .team-killers.red    { --team-color: #ff6b6b; }
.cc-players-card .team-killers.yellow { --team-color: #fbbf24; }
.cc-players-card .team-killers.violet { --team-color: #a78bfa; }
.cc-players-card .team-killers-head {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--team-color, var(--text-dim));
  margin-bottom: 4px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--border);
}
.cc-players-card .team-killers-empty {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
  font-style: italic;
  padding: 4px 0;
}
/* In the per-team layout the team-dot and medal icon on each row
   are redundant — the column already color-codes the team and the
   team-color header names it. The leader-dot (`.dots`) element is
   also dropped: the score is pushed to the right edge with
   `margin-left: auto` so the name fills all the middle space
   instead of being squeezed between two fixed-width elements. */
.cc-players-card .team-killers .kill-row { padding: 1px 0; gap: 4px; }
.cc-players-card .team-killers .kill-row .rank {
  /* Plain number, no medal SVG — saves ~14px per row × 6 rows ×
     N teams. */
  min-width: 14px;
  font-size: 10px;
  color: var(--text-dim);
  font-weight: 500;
  flex-shrink: 0;
}
.cc-players-card .team-killers .kill-row .name {
  /* flex: 1 + min-width: 0 lets the name grow to fill all the
     space between the rank and the score, then truncate with an
     ellipsis if it would still overflow. max-width: none
     overrides the global `.kill-row .name { max-width: 60% }`
     cap so a 4-col grid doesn't leave 40% of the row empty. */
  flex: 1;
  min-width: 0;
  max-width: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
}
/* score + kd sit on the right edge of the row. `margin-left: auto`
   on the score pushes both it and the kd to the right so the name
   can stretch the full middle. The kd is a decimal ratio
   (kills / deaths, 2dp) and gets a slightly wider min-width than
   the score so the column lines up across rows; both right-align
   for clean tabular reading. */
.cc-players-card .team-killers .kill-row .score {
  margin-left: auto;
  min-width: 16px;
  text-align: right;
  font-size: 11px;
  color: var(--amber);
  font-weight: 600;
  flex-shrink: 0;
}
.cc-players-card .team-killers .kill-row .kd {
  min-width: 34px;
  text-align: right;
  font-size: 11px;
  color: var(--text-muted);
  font-family: var(--font-mono);
  flex-shrink: 0;
}

/* ---------- Card: NEXT MAP + PLAYLIST (up next) ---------- */

.cc-next-card .next-meta {
  display: flex; flex-direction: column; gap: 4px;
  margin-top: 6px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
}
.cc-next-card .next-row {
  display: flex; align-items: center; gap: 6px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-muted);
}
.cc-next-card .next-row svg { width: 12px; height: 12px; opacity: 0.75; }
.cc-next-card .next-row strong { color: var(--text-strong); font-weight: 500; }

.cc-next-card .upnext-list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  margin-top: 4px;
}
.cc-next-card .upnext-item {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 6px;
  min-height: 40px;
}
.cc-next-card .upnext-thumb {
  width: 36px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, #2a3038, #1a1e24);
  border: 1px solid var(--border);
  border-radius: 4px;
  flex-shrink: 0;
}
.cc-next-card .upnext-idx {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  color: var(--amber);
}
.cc-next-card .upnext-name {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
  min-width: 0;
}
/* Game-type chip on the right side of the UP NEXT item. Amber-tinted
   pill with the short game type code (TDM / DM / CTF / etc). Sticks
   to the right because the name's `flex: 1` absorbs the rest of the
   row width. */
.cc-next-card .upnext-type {
  flex-shrink: 0;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--amber-soft);
  border: 1px solid var(--border-amber);
  color: var(--amber);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}
.cc-next-card .upnext-empty {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-dim);
  font-style: italic;
  padding: 8px 0;
  grid-column: span 2;
}

/* ---------- Card: QUICK ACTIONS (narrow, with toggles + action buttons) ---------- */

.cc-config-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 10px;
}
.cc-config-bottom {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.cc-toggle {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 12px;
  min-height: 48px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-muted);
  transition: border-color 120ms ease, background 120ms ease;
}
.cc-toggle svg { width: 14px; height: 14px; opacity: 0.7; flex-shrink: 0; }
.cc-toggle strong { color: var(--text-strong); font-weight: 600; margin-left: auto; }
.cc-toggle.on  { border-color: #1a3a23; background: rgba(34, 197, 94, 0.06); color: var(--text); }
.cc-toggle.on  strong { color: var(--green); }
.cc-toggle.on  svg { color: var(--green); opacity: 0.9; }
.cc-toggle.off { border-color: var(--border); }
.cc-toggle.off strong { color: var(--text-dim); }
.cc-toggle.off svg { color: var(--text-dim); opacity: 0.5; }
.cc-toggle.amber { border-color: var(--border-amber); background: var(--amber-soft); }
.cc-toggle.amber strong { color: var(--amber); }
.cc-toggle.amber svg { color: var(--amber); opacity: 0.9; }

@media (max-width: 900px) {
  .cc-config-grid   { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cc-config-bottom { grid-template-columns: 1fr; }
}

/* ---------- Card: RECENT ACTIVITY (wider, with chat log) ---------- */

.cc-chat-log {
  display: flex; flex-direction: column; gap: 6px;
  font-family: var(--font-mono);
  font-size: 13px;
  min-height: 100px;
  max-height: 220px;
  overflow-y: auto;
}
.cc-chat-empty {
  color: var(--text-dim);
  font-style: italic;
  padding: 16px 0;
  text-align: center;
}
.cc-chat-line {
  display: flex; align-items: center; gap: 8px;
  padding: 2px 0;
  color: var(--text);
}
.cc-chat-line .ts { color: var(--text-dim); font-size: 11px; }
.cc-chat-line .chat-icon { width: 12px; height: 12px; color: var(--text-dim); flex-shrink: 0; }
.cc-chat-line .who { font-weight: 600; }
.cc-chat-line .who.server { color: var(--amber); }
.cc-chat-line.server .who { color: var(--amber); }
.cc-chat-line.blue   .who { color: #7ab0ff; }
.cc-chat-line.red    .who { color: #ff8585; }
.cc-chat-line .text { color: var(--text); }

/* ============================================================================
   CHAT PAGE (port of WinForms tabChat.cs)

   Four stacked sections, no tabs:
     1. LIVE STREAM  (full-width) — chatInstance.ChatLog + send row
     2. AUTO MESSAGES
     3. SLAP MESSAGES
     4. CHAT HISTORY  (filterable, paginated, exportable — Phase 3)

   Phase 1 only wires Section 1. Phases 2-4 land the rest in turn.
   Extends the existing .cc-chat-log / .cc-chat-line (originally
   sized for the dashboard RECENT ACTIVITY card at max 220px)
   with a larger .cc-chat-panel-body for the page's full-width
   primary stream, plus the channel chip group, send row, and
   section 2-4 placeholders.
   ============================================================================ */

/* Larger chat log when used on the Chat page (vs the dashboard
   card where max-height is 220px). 360px shows ~14 lines on a
   13px mono font — enough to scan the recent stream without
   losing the surrounding context. The container scrolls when
   the log overflows. */
.cc-chat-panel-body {
  padding: 12px 16px 16px !important;
}
.cc-chat-panel-body > .cc-chat-log {
  max-height: 360px;
  min-height: 200px;
  padding: 4px 0;
}
.cc-chat-panel-body .cc-chat-line .who-name {
  color: var(--text-muted);
  font-weight: 500;
  min-width: 80px;
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cc-chat-panel-body .cc-chat-line .text {
  /* Allow long messages to wrap inside the stream instead of
     pushing the row off the right edge. */
  white-space: pre-wrap;
  word-break: break-word;
  flex: 1 1 auto;
  min-width: 0;
}
/* 4-team channel colours — same palette as the Players page team
   accents. */
.cc-chat-line.yellow .who { color: #fbbf24; }
.cc-chat-line.violet .who { color: #a78bfa; }
.cc-chat-line.global .who { color: #34d399; }
.cc-chat-line.command .who { color: #f472b6; }

/* --- Send row --- */
.cc-chat-send-row {
  display: flex; align-items: stretch; gap: 8px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.cc-chat-send-input {
  flex: 1 1 auto;
  min-width: 0;
  /* Match the touch-target guidelines — 44px min, 48px preferred. */
  min-height: 48px;
}
.cc-chat-send-row .cc-btn {
  min-width: 96px;
  min-height: 48px;
  font-family: var(--font-mono);
  font-weight: 700;
  letter-spacing: 0.04em;
}
.cc-chat-send-error {
  margin-top: 8px;
  padding: 8px 12px;
  background: rgba(220, 38, 38, 0.08);
  border: 1px solid rgba(220, 38, 38, 0.4);
  border-radius: var(--radius-sm);
  color: #fca5a5;
  font-size: 12px;
  font-family: var(--font-mono);
}

/* --- Channel chip group --- */
.cc-channel-chips {
  display: flex; align-items: stretch;
  gap: 4px;
  flex-wrap: nowrap;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 3px;
}
.cc-channel-chip {
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  padding: 0 12px;
  min-height: 40px;
  border-radius: 4px;
  cursor: pointer;
  transition: background 80ms, color 80ms, border-color 80ms;
  white-space: nowrap;
}
.cc-channel-chip:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.04);
}
.cc-channel-chip.active {
  background: var(--amber);
  color: #1a1a1a;
  border-color: var(--amber);
  box-shadow: 0 0 0 1px var(--amber);
}
.cc-channel-chip:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 1px;
}

/* --- Placeholder stub for Sections 2-4 (lands in Phases 2-3) --- */
.cc-chat-stub {
  padding: 28px 24px;
  text-align: center;
  color: var(--text-dim);
  font-size: 13px;
  font-style: italic;
  background: var(--bg-elevated);
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  line-height: 1.6;
}
.cc-chat-stub code {
  font-family: var(--font-mono);
  background: rgba(255, 255, 255, 0.04);
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 12px;
  color: var(--text);
  font-style: normal;
}

/* ---------- Panel header "hint" (right-aligned subtle text) ---------- */

.cc-panel-head .right.hint {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
}
.cc-panel-head .right.hint.mono { font-family: var(--font-mono); }

/* ============================================================================
   Settings page (port of the WinForms ProfileTab)

   Two-zone layout:
     1. .cc-settings-grid   - the five cards (Identity / Network / Game
                              Process / Restrictions / Launch Flags) laid
                              out as a 2x2 + 1 grid that collapses cleanly
                              on tablet.
     2. .cc-action-bar      - the sticky bottom bar with the dirty
                              counter, server-running warning, and the
                              Save / Reset buttons.

   Plus the inner form bits: .cc-form (form layout), .cc-field (one
   labelled input), .cc-field-dirty (the amber outline on a field whose
   value differs from the saved theInstance), .cc-toggles-row (a row
   that holds one or two compact checkboxes with their values inline).
   ============================================================================ */

.cc-settings-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  /* Leave room for the sticky action bar at the bottom so the
     last card's content isn't hidden behind it. */
  padding-bottom: 100px;
  /* The grid cells in a row share a single height (align-items:
     stretch is the default), so the IDENTITY card stretches to
     match the taller NETWORK card. The .cc-panel-stretch modifier
     on the IDENTITY card then lets the MOTD field fill the
     remaining vertical space. */
  align-items: stretch;
}
.cc-settings-grid > .cc-panel:nth-child(5) {
  /* The 5th card (Launch Flags) is a single-column card. It used
     to span both columns but that left a big empty cell in row 3
     once the RESTRICTIONS card was flattened to 1 row of 4 items.
     Now it flows into row 3 left and the right cell is recovered
     whitespace. */
  grid-column: span 1;
}
@media (max-width: 900px) {
  .cc-settings-grid { grid-template-columns: 1fr; }
}

/* ============================================================================
   GAMEPLAY PAGE — 3-region grid (left/center/right-tabs) + sticky footer bar

   Mirrors the original WinForms `tabGamePlay.Designer.cs` 4-column layout
   but folds the right action rail down into the sticky bottom bar (same
   pattern as Settings.razor). This keeps the form columns wide on
   tablet viewports and the action buttons are always visible
   regardless of scroll position.

     Left rail (40%):  Team Passwords + Match Win Conds + Game Play Options
                       + Friendly Fire  (Phase 4)
     Center rail (20%): Match Settings  (label|value 2-col)
     Right rail (40%):  TabControl (Restrictions / Overrides / Commands)
     Sticky footer:     Save / Reset / Start / Update / Lock / Pause

   Three breakpoints:
     ≥ 1200px  : 3-col (1.4fr 0.8fr 1.4fr), footer bar pinned
     900-1200  : same 3-col, footer bar is the same sticky bar
     <  900px  : single column stacked, footer bar is the same sticky bar
   ============================================================================ */

.cc-gameplay-grid {
  display: grid;
  /* Outer layout (2 columns):
       Left col (2fr)  |  Right col (1fr)
       Team Passwords  |  Tabs card
       + subgrid       |  (restrictions/
       (Win+GPO | MS)  |   overrides/commands)

     The left col is itself a 2-row flex column:
       Row 1: Team Passwords (auto height, full width of left col)
       Row 2: a 2-col subgrid containing
              • Win Conditions + Game Play Options (with FF row at
                the bottom of the GPO card body)
              • Match Settings
     The right col is the standalone Tabs card filling the
     entire right side (no subgrid needed). Both columns
     stretch to fill the page height.

     The 2fr/1fr split keeps the proportions of the previous
     3-col layout (the old left + center combined are now
     the left col; the old right col stays the right col). */
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  grid-template-areas: "left right";
  grid-row-gap: 16px;
  grid-column-gap: 16px;
  align-items: stretch;
  align-content: start;
}

/* LEFT COL wrapper — flex column holding the password banner
   on top (auto height) and the 2-col subgrid below (fills
   remainder of the left col). */
.cc-gameplay-left-col {
  grid-area: left;
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-height: 0;
  min-width: 0;
}
.cc-gameplay-left-col > .cc-panel.cc-team-passwords {
  flex: 0 0 auto;
}

/* 2-col subgrid inside the left col — holds Match Settings on
   the left (sub-col 1, ~30% of the left col width) and Win
   Conditions + Game Play Options on the right (sub-col 2, the
   remaining ~70%). The asymmetric split is intentional: the
   tall Match Settings list (10 rows of label|value) only needs
   a narrow column, while the Game Play Options card with its
   3-col toggle grid + Friendly Fire section at the bottom
   benefits from the wider sub-col. Stretches to fill the
   remainder of the left col.

   The two children are physically declared in source order as
   `cc-gameplay-left` then `cc-gameplay-center`, but the visual
   order is swapped via the `order` properties below so Match
   Settings renders in the narrow sub-col 1 and Win Conds + GPO
   fills the wide sub-col 2. */
.cc-gameplay-subgrid {
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 7fr);
  gap: 16px;
  align-items: stretch;
  min-height: 0;
  min-width: 0;
}
.cc-gameplay-subgrid > .cc-gameplay-center { order: 1; }
.cc-gameplay-subgrid > .cc-gameplay-left   { order: 2; }

.cc-gameplay-left,
.cc-gameplay-center,
.cc-gameplay-tabs {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
  min-height: 0;
}

/* LEFT COLUMN — flex column with two cards:
     1. Match Win Conditions (top, natural height)
     2. Game Play Options (flex:1, fills the remainder of the column)
   The Game Play Options card itself becomes a flex column so its
   body grows to fill the card height; the Friendly Fire row sits
   at the bottom of that body, separated from the option toggles
   above by a hairline rule. The body uses overflow-y: auto so a
   long option grid scrolls inside the card instead of pushing
   the FF section out of the column. */
.cc-gameplay-left > .cc-panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.cc-gameplay-left > .cc-panel.cc-match-conditions {
  /* Win Conditions — natural height, anchored to the top */
  flex: 0 0 auto;
}
.cc-gameplay-left > .cc-panel.cc-gameplay-options {
  /* Game Play Options — fills the remainder of the left column */
  flex: 1 1 auto;
}
.cc-gameplay-left > .cc-panel .cc-panel-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow-y: auto;
}

/* CENTER COLUMN — Match Settings card fills the column height.
   The match-settings list inside the body grows to fill the body
   so empty space appears below the last field row. */
.cc-gameplay-center > .cc-panel {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
.cc-gameplay-center > .cc-panel .cc-panel-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow-y: auto;
}
.cc-gameplay-center > .cc-panel .cc-match-settings {
  flex: 1;
}

/* RIGHT COLUMN — Tabs card fills the entire column. Tabs sit at
   the top; the active tab's panel fills the remainder of the
   column. The first card in a tab uses its natural height
   (just the content it needs — e.g. Role Restrictions is a
   single 1×4 row of role toggle pills), and any subsequent
   card (e.g. Weapon Restrictions with its 24-weapon grid)
   fills the remaining space. A tab with a single card (e.g.
   Commands → Chat Commands) fills the column on its own. The
   panel-body of each card uses overflow-y: auto so tall content
   (e.g. the 24-weapon grid) scrolls inside its own card
   instead of pushing the layout out of the viewport. */
.cc-gameplay-tabs > .cc-tab-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-height: 0;
}
.cc-gameplay-tabs > .cc-tab-panel > .cc-panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
/* Sibling cards (not the first) fill the remaining space, and a
   lone card fills the whole column. */
.cc-gameplay-tabs > .cc-tab-panel > .cc-panel:not(:first-child),
.cc-gameplay-tabs > .cc-tab-panel > .cc-panel:only-child {
  flex: 1 1 0;
}
/* The first card in a multi-card tab stays at natural height —
   just the space the row(s) of content need. */
.cc-gameplay-tabs > .cc-tab-panel > .cc-panel:first-child:not(:only-child) {
  flex: 0 0 auto;
}
.cc-gameplay-tabs > .cc-tab-panel > .cc-panel .cc-panel-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

/* Pin the 2 grid children to named template areas. The outer
   grid has just two columns: a left col wrapper (containing
   Team Passwords + the 2-col subgrid) and a right col with
   the standalone Tabs card. The left and center rails live
   inside the subgrid, not the outer grid, so they don't need
   grid-area declarations here. */
.cc-gameplay-left-col { grid-area: left;  align-self: stretch; justify-self: stretch; min-height: 0; }
.cc-gameplay-tabs     { grid-area: right; align-self: stretch; justify-self: stretch; min-height: 0; }

/* Tabs inside the right rail — same .cc-tabs / .cc-tab as the global
   definition, but reduced height so 3 tabs fit in 48px total instead
   of 64px. Margin resets the 20px bottom from the global rule. */
.cc-gameplay-tabs > .cc-tabs {
  margin-bottom: 0;
}
.cc-gameplay-tabs .cc-tab {
  height: 44px;
  font-size: 12px;
  padding: 0 14px;
  flex: 1 1 0;
  text-align: center;
  letter-spacing: 0.06em;
}

/* Tab panels — just a vertical stack of cards inside the right rail. */
.cc-tab-panel {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
  margin-top: 16px;
}

/* Placeholder card — for Phase 5 (Weapons grid) which is the only
   section still in placeholder territory. Phase 4 (Friendly Fire +
   Role Restrictions) now has real, wired forms. */
.cc-placeholder {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 18px;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.01);
  color: var(--text-muted);
  font-size: 13px;
}
.cc-placeholder .badge {
  display: inline-flex;
  align-self: flex-start;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--amber);
  background: var(--amber-soft);
  border: 1px solid var(--border-amber);
  padding: 3px 8px;
  border-radius: 4px;
  text-transform: uppercase;
}
.cc-placeholder strong { color: var(--text); font-weight: 600; }
.cc-placeholder .hint { font-size: 12px; color: var(--text-dim); }

/* Match Settings — label|value 2-col grid mirroring the original
   WinForms `groupBox_match` (2-col × 11-row tableLayoutMatch). The
   label column is right-aligned and takes 60% of the card width
   (3fr share); the value column takes the remaining 40% (2fr share)
   — enough to fit a NumberField with –/+ stepper buttons + the
   centered numeric input. The old fixed 88px track was sized for
   the input-only layout. */
.cc-match-settings {
  display: grid;
  grid-template-columns: 3fr 2fr;
  gap: 0;
}
.cc-match-settings .cc-ms-row {
  display: contents;
}
.cc-match-settings .cc-ms-label {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 8px 10px 8px 4px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  text-align: right;
  min-height: 48px;
  border-bottom: 1px solid var(--border-soft);
  white-space: nowrap;
}
.cc-match-settings .cc-ms-value {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4px;
  min-height: 48px;
  border-bottom: 1px solid var(--border-soft);
}
.cc-match-settings .cc-ms-row:last-child .cc-ms-label,
.cc-match-settings .cc-ms-row:last-child .cc-ms-value {
  border-bottom: none;
}
.cc-match-settings .cc-input,
.cc-match-settings .cc-select {
  height: 40px;
  font-size: 13px;
  /* Fill the value column so the select matches the width of the
     NumberField wrappers (which are 100% of the cell). The select
     was previously 80px wide — visible in the Loop Maps row where
     "On" sat in a narrow pill while the rows above and below
     (with stepper buttons) stretched the full column width. */
  width: 100%;
  text-align: center;
}
/* Native browser dropdown chevron sits flush to the right edge,
   crowding the centered "On" / "Off" / "Cycle" label. Push the
   label left with right padding so there's clear breathing room
   between the text and the arrow. */
.cc-match-settings .cc-select {
  padding-right: 28px;
}
.cc-match-settings .cc-field-dirty .cc-input,
.cc-match-settings .cc-field-dirty .cc-select {
  border-color: var(--amber);
  box-shadow: 0 0 0 1px var(--amber-soft);
}

/* In the Match Settings grid the NumberField now renders WITH
   stepper buttons (the 40% value column is wide enough). Fill the
   cell so the –/+ buttons + input share the available width, and
   shorten the stepper buttons to 28px so the input gets a decent
   share of the row. Height matches the 40px row to keep the label
   and value baselines aligned. */
.cc-match-settings .cc-number-field {
  width: 100%;
  height: 40px;
  min-height: 40px;
  font-size: 13px;
}
.cc-match-settings .cc-stepper-btn {
  width: 28px;
  height: 40px;
  font-size: 14px;
}

/* Narrow screens — single-column stack, footer sticky bar is the
   same; the cc-action-bar's internal media query handles the button
   wrap behaviour. */
@media (max-width: 1300px) {
  .cc-gameplay-grid {
    grid-template-columns: 1fr;
  }
  .cc-match-settings {
    grid-template-columns: minmax(0, 1fr) 88px;
  }
}

/* Friendly Fire / Role Restrictions — 3-up or 4-up toggle row.
   Mirrors the existing .cc-toggles-row but uses a more relaxed
   gap (12px instead of 10px) because these cards sit in the
   narrow center rail / a single column of the right rail and
   the visual breathing room matters. Same .cc-toggle pill as
   everywhere else; the 3-state pill pattern is reserved for
   weapons (Phase 5). */
.cc-ff-toggles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}
/* Auto-expand to 4 columns when the row has 4 children, regardless
   of class. Originally this matched only `.cc-toggle` children, but
   Role Restrictions uses `.cc-weapon-pill` buttons (same shape, just
   different styling), so the 4-up rule never fired there. Matching
   any 4th child makes the rule work for both button types. */
.cc-ff-toggles:has(:nth-child(4)) {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
/* .cc-ff-toggles-stack overrides the 3-up / 4-up row to a single
   column. Used by the Friendly Fire card when it lives in the
   narrow 20% center rail — "Warn on Kill" (11 chars) overflows
   into the next cell at 3 cols × ~67px, so we stack the 3 toggles
   vertically. Role Restrictions (4-up in the right rail) keeps
   the default row layout. */
.cc-ff-toggles.cc-ff-toggles-stack,
.cc-ff-toggles.cc-ff-toggles-stack:has(.cc-toggle:nth-child(4)) {
  grid-template-columns: 1fr;
}

/* Team Passwords is now a child of the .cc-gameplay-left-col flex
   column wrapper, not a direct child of the outer grid, so it no
   longer needs a grid-column span. It naturally fills the width
   of the left col (which is 2fr of the 2-col outer grid). */

/* Friendly Fire 4-column row — 3 toggle pills + 1 number field
   in a single horizontal row. The pills are bottom-aligned with
   the number field's input so the row reads as one unit. The
   number field has a small label above the input (matching the
   weapon-pill text height) for visual parity. */
.cc-ff-4col {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
  align-items: end;
}
.cc-ff-numfield {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;     /* let the NumberField shrink to fit the column */
}
.cc-ff-numfield > label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Friendly Fire section — sits at the BOTTOM of the Game Play
   Options card body, visually separated from the option toggles
   above by a hairline + small label. The mt:auto pushes the
   whole section to the bottom of the flex column, regardless of
   how many toggles are above it. The label mirrors the .left
   style of the panel heads for visual consistency. */
.cc-ff-section {
  margin-top: auto;
  padding-top: 16px;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.cc-ff-section-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.cc-ff-section-label svg { width: 16px; height: 16px; flex-shrink: 0; }

/* Action bar — small vertical hairline between the live server
   controls (Start/Update/Lock/Pause) and the form controls
   (Reset/Save). Same 1px pattern as the .cc-action-rail-divider
   we used when the action rail was on the right, just styled for
   the horizontal footer variant. */
.cc-action-bar-divider {
  width: 1px;
  height: 32px;
  background: var(--border);
  margin: 0 4px;
  flex: 0 0 auto;
}
@media (max-width: 600px) {
  .cc-action-bar-divider {
    /* On tiny viewports, rotate the divider into a horizontal
       hairline so it still reads as a separator when buttons
       wrap to multiple rows. */
    width: auto;
    height: 1px;
    flex: 1 1 100%;
    margin: 4px 0;
  }
}

/* ============================================================================
   WEAPON RESTRICTIONS — 3-state click-to-cycle grid (Phase 5)

   Mirrors the original WinForms `tableLayoutWeaponRestrictions` (4×7 with
   Select All / Select None helpers in row 0). Each weapon is a pill that
   cycles through 3 states on click:

     Disabled (gray)   — weapon* = false, restrictedWeapon* = false
                          weapon not in rotation
     Threshold (green) — weapon* = true,  restrictedWeapon* = false
                          weapon available when player count >= threshold
     Always (amber)    — weapon* = true,  restrictedWeapon* = true
                          weapon always available regardless of threshold

   Left click: any → Disabled, then Disabled → Threshold on next click
   Right click: Disabled → Threshold → Always → Threshold (no skip from
                Disabled to Always — must enable first, then add restricted)

   Each pill has a `title` attribute that explains the state on hover/long-
   press. The grid uses 4 columns to match the original; on narrow screens
   it collapses to 2 cols at 900px and 1 col at 560px (same breakpoints as
   .cc-options-grid).
   ============================================================================ */

.cc-weapon-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}

.cc-weapon-pill {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elevated);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  user-select: none;
  text-align: center;
  line-height: 1.2;
  transition: border-color 120ms, background 120ms, color 120ms, box-shadow 120ms;
  -webkit-tap-highlight-color: transparent;
}
.cc-weapon-pill:hover { border-color: var(--text-muted); }
.cc-weapon-pill:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--amber);
}

/* Disabled — weapon not in rotation. Gray text on dark surface. */
.cc-weapon-pill.disabled {
  color: var(--text-dim);
  background: var(--bg-elevated);
  border-color: var(--border);
}
/* Threshold — weapon available when player count >= FullWeaponThreshold.
   Soft green tint, matches the .cc-toggle.on look. */
.cc-weapon-pill.threshold {
  color: var(--green);
  background: rgba(34, 197, 94, 0.10);
  border-color: #1a3a23;
}
.cc-weapon-pill.threshold:hover {
  background: rgba(34, 197, 94, 0.16);
}
/* Always — weapon always available regardless of player count. Amber tint
   to distinguish from the threshold-on state. */
.cc-weapon-pill.always {
  color: var(--amber);
  background: var(--amber-soft);
  border-color: var(--border-amber);
}
.cc-weapon-pill.always:hover {
  background: rgba(245, 165, 36, 0.18);
}

/* Select All / Select None — full-width row above the grid.
   Same .cc-btn base but smaller height (40px) to match the pill
   height (48px) visually. */
.cc-weapon-helpers {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 10px;
}
.cc-weapon-helpers .cc-btn {
  height: 40px;
  min-height: 40px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
}

@media (max-width: 900px) {
  .cc-weapon-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .cc-weapon-grid { grid-template-columns: 1fr; }
}

/* Per-weapon dirty outline — same amber ring used on regular form
   fields. Toggled on/off by the dirty tracker. */
.cc-weapon-pill.dirty {
  border-color: var(--amber);
  box-shadow: 0 0 0 1px var(--amber-soft);
}

/* Disabled pill — used by the Friendly Fire gated toggles
   (Show Tags / Warn on Kill) when the parent FF Enabled toggle
   is off. The native HTML `disabled` attribute prevents click
   events; this rule makes the visual state match: grayed out,
   not-allowed cursor, no hover effect. Mirrors the .cc-disabled
   style previously applied to the old .cc-toggle children. */
.cc-weapon-pill:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

/* ============================================================================
   NumberField — centered numeric input with optional –/+ stepper.
   Used by Gameplay (and reusable everywhere). The wrapper is a
   single visual unit: one border, one focus ring. The native
   browser spinner arrows are hidden; the stepper buttons take
   their place on touch + desktop. All values are clamped to
   [Min, Max] by the component itself, but we still set the HTML
   min/max attrs for the browser's built-in validation hints.

   Why the wrapper owns the border (not the input): the input
   is borderless so the focus ring (border + box-shadow on the
   wrapper's :focus-within) covers the whole stepper as one unit
   instead of lighting up just the input.

   Why tabular-nums on the input: with center alignment, default
   proportional digits would make the value visually jump as
   digits change width. tabular-nums locks each digit to the
   same width so the value stays rock-steady.
   ============================================================================ */

.cc-number-field {
  display: flex;
  align-items: stretch;
  min-height: 48px;
  width: 100%;
  max-width: 200px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  transition: border-color 120ms, box-shadow 120ms;
  overflow: hidden;
}
.cc-number-field:focus-within {
  border-color: var(--amber);
  box-shadow: 0 0 0 3px var(--amber-soft);
}
.cc-number-field.cc-field-dirty {
  border-color: var(--amber);
  box-shadow: 0 0 0 1px var(--amber-soft);
}
.cc-number-field.cc-field-dirty:focus-within {
  /* When the field is dirty, keep the amber outline but lose the
     thick focus ring — the dirty state already shows "this needs
     attention"; a second ring on focus would be visually noisy. */
  box-shadow: 0 0 0 1px var(--amber-soft);
}
.cc-number-field.cc-disabled {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

/* Input — borderless inside the wrapper, centered, tabular digits.
   The wrapper provides the visual border + focus ring, so the
   input's own border/box-shadow is suppressed via !important. */
.cc-number-input {
  border: none !important;
  background: transparent !important;
  border-radius: 0 !important;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  min-width: 4ch;
  flex: 1 1 auto;   /* grow to fill the space between the – and + buttons
                       so the + button floats to the right edge of the
                       wrapper. The 4ch min-width keeps it readable
                       when the wrapper is very narrow. */
  padding: 0 4px;
  height: auto;
  min-height: inherit;
  color: var(--text);
  -moz-appearance: textfield;  /* Firefox hide spinner */
  outline: none;
}
.cc-number-input:focus {
  border: none !important;
  box-shadow: none !important;
}
/* Hide the native browser spinner (Chrome / Safari / Edge). */
.cc-number-input::-webkit-outer-spin-button,
.cc-number-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* Compact variant (no stepper buttons, used in narrow columns
   like the Match Settings grid). The input fills the wrapper. */
.cc-number-field--compact .cc-number-input {
  flex: 1 1 auto;
  min-width: 0;
  max-width: none;
  width: 100%;
}

/* Stepper buttons — dark elevated surface, amber on hover, no
   spinner arrows of their own. Width 36px keeps the field
   compact; the input still has 4-7ch of room for the value. */
.cc-stepper-btn {
  flex: 0 0 36px;
  background: var(--bg-elevated);
  border: none;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 18px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 120ms, color 120ms;
}
.cc-stepper-btn:first-child {
  border-right: 1px solid var(--border);
}
.cc-stepper-btn:last-child {
  border-left: 1px solid var(--border);
}
.cc-stepper-btn:hover:not(:disabled) {
  background: rgba(245, 165, 36, 0.10);
  color: var(--amber);
}
.cc-stepper-btn:active:not(:disabled) {
  background: rgba(245, 165, 36, 0.20);
}
.cc-stepper-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
/* When the wrapper is dirty, tint the divider lines between the
   stepper buttons and the input so the whole unit reads as one
   "needs attention" element instead of three separate pieces. */
.cc-field-dirty .cc-stepper-btn:first-child {
  border-right-color: var(--amber);
}
.cc-field-dirty .cc-stepper-btn:last-child {
  border-left-color: var(--amber);
}

/* ============================================================================
   MAPS PAGE — 2-col layout (available maps + current playlist)

   Mirrors the original WinForms tabMaps panel: a 5-button playlist
   selector across the top, a toolbar of edit actions, then a
   2-column table layout (available maps on the left, current
   playlist on the right) with a per-row action button column
   (add / remove / up / down), then a final row of live map
   controls (skip / score / play next).

   Three breakpoints:
     ≥ 1200px  : full 2-col (47% 53%) — right column (current
                 playlist) gets a small edge to absorb its wider
                 Actions chrome (~150px vs the left's 50px + button)
                 without the Name/File cells wrapping.
     900-1200  : same 2-col but tables wrap to 2-line rows
     <  900px  : single column stacked
   ============================================================================ */

.cc-maps-grid {
  display: grid;
  /* 47/53 split — right column is 6% wider to absorb the 100px
     difference in fixed-width table chrome (150px Actions vs 50px +
     button) without making the available-maps column look starved.
     minmax(0, …) keeps the grid from blowing out on narrow
     viewports; the inner tables scroll instead. */
  grid-template-columns: minmax(0, 47fr) minmax(0, 53fr);
  gap: 16px;
  /* Fill the available vertical space inside .cc-main so the inner
     panels can scroll internally instead of making the page scroll.
     The grid itself does NOT scroll — each panel's table area does. */
  align-items: stretch;
  flex: 1 1 auto;
  min-height: 0;
  padding-bottom: 24px;
}
/* =====================================================================
   PLAYERS PAGE — 5-col × 10-row grid of player cards (50 max)
   =====================================================================
   Each card is a compact ~195×40 px tile with the player's slot #
   + country flag + team icon + name + IP + ping + action menu.
   Empty slots render a dim "Slot N · empty" placeholder. Cards
   are hidden entirely when the server is offline (no fake
   "Slot Empty" cards to scroll through). */

.cc-players-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 8px;
  padding-bottom: 24px;
}
@media (max-width: 1400px) {
  .cc-players-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-width: 1100px) {
  .cc-players-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 800px) {
  .cc-players-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 540px) {
  .cc-players-grid { grid-template-columns: 1fr; }
}

.cc-player-card {
  position: relative;
  display: grid;
  grid-template-columns: 24px 20px 1fr auto auto;
  grid-template-rows: 22px 18px;
  align-items: center;
  gap: 0 6px;
  height: 48px;
  padding: 4px 8px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text);
  overflow: hidden;
  transition: border-color 0.1s ease, background 0.1s ease;
}
.cc-player-card:hover {
  border-color: var(--border-amber);
  background: var(--bg-elevated);
}

/* Team color accent — left border in team color, like the
   WinForms PlayerCardV2 colour-coded person icon. */
.cc-player-card.team-0 { border-left: 3px solid var(--text-dim); }
.cc-player-card.team-1 { border-left: 3px solid #60a5fa; }   /* Blue */
.cc-player-card.team-2 { border-left: 3px solid #f87171; }   /* Red */
.cc-player-card.team-3 { border-left: 3px solid #fbbf24; }   /* Yellow */
.cc-player-card.team-4 { border-left: 3px solid #a78bfa; }   /* Violet */
.cc-player-card.proxy   { border-left: 3px solid #f59e0b; }   /* Proxy/VPN warning */

.cc-player-card-slot {
  grid-row: 1 / 3;
  grid-column: 1;
  font-weight: 700;
  font-size: 13px;
  color: var(--amber);
  text-align: center;
  align-self: center;
}
.cc-player-card-flag {
  grid-row: 1 / 3;
  grid-column: 2;
  width: 20px;
  height: 15px;
  align-self: center;
  border-radius: 2px;
  object-fit: cover;
  background: var(--bg-elevated);
}
.cc-player-card-name {
  grid-row: 1;
  grid-column: 3;
  font-weight: 600;
  font-size: 12px;
  color: var(--text-strong);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  align-self: end;
}
.cc-player-card-ip {
  grid-row: 2;
  grid-column: 3;
  font-size: 10px;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  align-self: start;
  cursor: pointer;
  transition: color 0.1s ease;
}
.cc-player-card-ip:hover { color: var(--amber); }

/* Right rail — ping (col 4) + 3-dot menu (col 5), both spanning
   the full 2-row height so they're vertically centred in the
   card. Ping sits to the LEFT of the menu button. */
.cc-player-card-ping {
  grid-row: 1 / 3;
  grid-column: 4;
  font-size: 10px;
  color: var(--text-dim);
  text-align: right;
  align-self: center;
  justify-self: end;
  white-space: nowrap;
  line-height: 1;
  padding-right: 4px;
}
.cc-player-card-menu {
  grid-row: 1 / 3;
  grid-column: 5;
  width: 36px;
  height: 32px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-muted);
  font-size: 22px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  align-self: center;
  justify-self: center;
  border-radius: 4px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.1s ease, color 0.1s ease, border-color 0.1s ease;
}
.cc-player-card-menu:hover:not(:disabled) {
  background: var(--bg-elevated);
  color: var(--amber);
  border-color: var(--border-amber);
}
.cc-player-card-menu.open {
  background: var(--amber-soft);
  color: var(--amber);
  border-color: var(--border-amber);
}

/* Empty slot state — dim, italic, no team accent. */
.cc-player-card.empty {
  border-style: dashed;
  border-color: var(--border-soft);
  color: var(--text-dim);
  background: transparent;
}
.cc-player-card.empty .cc-player-card-slot { color: var(--text-dim); }
.cc-player-card.empty .cc-player-card-name { color: var(--text-dim); font-weight: 400; font-style: italic; }

/* Server-offline state — all cards hidden, show one big empty
   message instead. */
.cc-players-offline {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 200px;
  color: var(--text-dim);
  font-style: italic;
  font-size: 14px;
  background: var(--bg-panel);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
}

/* =====================================================================
   PLAYER CONTEXT MENU — modal-style dropdown (3-dot action menu)
   =====================================================================
   Same pattern as the WinForms ContextMenuStrip but rendered as
   a Blazor modal that floats over the card. Uses .cc-modal-bg
   for the click-to-dismiss backdrop and .cc-confirm-modal for
   the panel chrome (smaller than the full modal). */

.cc-player-menu {
  position: fixed;
  z-index: 1000;
  min-width: 220px;
  max-width: 280px;
  background: var(--bg-panel);
  border: 1px solid var(--border-amber);
  border-radius: var(--radius);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--text);
  /* overflow must stay visible so absolute-positioned submenus
     (WARN / BAN / SWITCH TEAM dropdowns at left:100%) can
     break out of the menu box. The rounded corner clipping is
     handled separately on the .cc-player-menu-items below. */
  animation: cc-rise 0.15s ease;
}
.cc-player-menu-head {
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-header);
  border-top-left-radius: var(--radius);
  border-top-right-radius: var(--radius);
}
.cc-player-menu-head .name {
  font-weight: 700;
  color: var(--text-strong);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cc-player-menu-head .ping {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
  margin-top: 2px;
}
.cc-player-menu-items {
  display: flex;
  flex-direction: column;
  padding: 4px 0;
  /* No max-height / overflow on the items container — the
     sub-panels (position:absolute fly-outs) need to escape the
     items box. With ~10 items at ~40px each the menu stays
     under 500px tall, well within the 900px viewport. If the
     menu ever needs to scroll, the caller can render fewer
     items or the layout can switch to a 2-column menu. */
  border-bottom-left-radius: var(--radius);
  border-bottom-right-radius: var(--radius);
}
.cc-player-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: transparent;
  border: none;
  color: var(--text);
  font-family: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  width: 100%;
  transition: background 0.08s ease, color 0.08s ease;
}
.cc-player-menu-item:hover { background: var(--bg-elevated); color: var(--amber); }
.cc-player-menu-item:disabled { color: var(--text-dim); cursor: not-allowed; }
.cc-player-menu-item:disabled:hover { background: transparent; color: var(--text-dim); }
.cc-player-menu-item svg { width: 16px; height: 16px; flex-shrink: 0; opacity: 0.7; }
.cc-player-menu-item.danger { color: #fca5a5; }
.cc-player-menu-item.danger:hover { background: rgba(220, 38, 38, 0.15); color: #fecaca; }
/* Spectate — only rendered when the page is hosted inside the
   HawkSyncViewer WinForms client (X-HawkSync-Viewer header
   detected). The eye icon + amber accent signal "this will
   spawn a local process action". Same danger-tier visual
   weight as KILL (red icon) so the user knows it's a
   side-effect-bearing action. */
.cc-player-menu-item.spectate { color: var(--amber); }
.cc-player-menu-item.spectate:hover { background: rgba(245, 158, 11, 0.18); color: #fde68a; }
.cc-player-menu-divider {
  height: 1px;
  background: var(--border);
  margin: 4px 0;
}
.cc-player-menu-sub {
  position: relative;
}
.cc-player-menu-sub-toggle {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  width: 100%;
  background: transparent;
  border: none;
  color: var(--text);
  font-family: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  transition: background 0.08s ease, color 0.08s ease;
}
.cc-player-menu-sub-toggle:hover { background: var(--bg-elevated); color: var(--amber); }
.cc-player-menu-sub-toggle::after {
  content: "›";
  margin-left: auto;
  color: var(--text-dim);
  font-size: 16px;
}
/* Sub-panel: a SEPARATE menu that flies out to the RIGHT of
   its parent toggle (not flowing inline). Hidden by default;
   shown on hover of the parent .cc-player-menu-sub. The panel
   has its own border + shadow so it reads as a separate menu
   floating next to the main one.

   max-height + overflow-y keeps a long WARN sub-panel
   (e.g. 30+ slap messages) from blowing past the bottom of
   the viewport — the list becomes internally scrollable
   instead. The :hover / :focus-within show rules still
   apply; only the height of the rendered list is capped. */
.cc-player-menu-sub-panel {
  position: absolute;
  left: 100%;
  top: -4px;
  min-width: 220px;
  max-width: 320px;
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  background: var(--bg-panel);
  border: 1px solid var(--border-amber);
  border-radius: var(--radius);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
  padding: 4px 0;
  display: none;
  z-index: 1;
  /* Slim dark-theme scrollbar so the panel doesn't shout
     "native widget" when the slap list overflows. */
  scrollbar-width: thin;
  scrollbar-color: var(--border-amber) transparent;
}
.cc-player-menu-sub-panel::-webkit-scrollbar { width: 6px; }
.cc-player-menu-sub-panel::-webkit-scrollbar-track { background: transparent; }
.cc-player-menu-sub-panel::-webkit-scrollbar-thumb {
  background: var(--border-amber);
  border-radius: 3px;
}
.cc-player-menu-sub:hover > .cc-player-menu-sub-panel,
.cc-player-menu-sub:focus-within > .cc-player-menu-sub-panel { display: block; }

/* Flip directions — applied by playerMenu.js when a sub-panel
   would overflow the viewport. Mirrors the Windows / macOS
   right-click menu behaviour: the fly-out opens toward the
   nearest available edge instead of off-screen.

   `.flip-horizontal` anchors the panel to the LEFT edge of
   the parent toggle (was: right edge).
   `.flip-vertical`   anchors the panel to the BOTTOM edge of
   the parent toggle (was: top edge).
   Both can combine when the parent toggle is in the bottom-
   right corner of the viewport. */
.cc-player-menu-sub-panel.flip-horizontal {
  left: auto;
  right: 100%;
}
.cc-player-menu-sub-panel.flip-vertical {
  top: auto;
  bottom: -4px;
}

/* The WARN sub-panel is the only one that can grow long.
   When it has 7+ slaps, the sub-toggle gets a "Browse all…"
   item rendered at the bottom of the panel. That item is
   styled like a normal menu item but with an inline chevron
   that signals "opens a different surface" — matches the
   same `›` hint the toggle itself uses. */
.cc-player-menu-browse-all {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: transparent;
  border: 0;
  width: 100%;
  text-align: left;
  cursor: pointer;
  color: var(--amber);
  font-size: 13px;
  font-family: inherit;
  border-top: 1px solid var(--border);
  margin-top: 4px;
  padding-top: 12px;
  transition: background 0.08s ease, color 0.08s ease;
}
.cc-player-menu-browse-all:hover {
  background: var(--bg-elevated);
  color: #fde68a;
}
.cc-player-menu-browse-all .chev { margin-left: auto; opacity: 0.7; }

/* =====================================================================
   SLAP-PICKER MODAL — full-list view opened by the WARN sub-menu's
   "Browse all slaps" item. Uses the standard .cc-modal-bg / .cc-modal
   chrome so it composes with the rest of the design system.
   ===================================================================== */

.cc-slap-picker {
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-height: min(640px, 80vh);
}

/* Search input — matches the .cc-input style used elsewhere but
   with a 56px touch target (--touch-comfort) so the operator
   can type on a tablet without mis-hits. The wrapper carries
   a search icon and a clear button. */
.cc-slap-search {
  position: relative;
  display: flex;
  align-items: center;
}
.cc-slap-search svg.icon {
  position: absolute;
  left: 16px;
  width: 18px;
  height: 18px;
  color: var(--text-dim);
  pointer-events: none;
}
.cc-slap-search input {
  width: 100%;
  height: var(--touch-comfort);
  padding: 0 44px 0 46px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 14px;
  outline: none;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.cc-slap-search input:focus {
  border-color: var(--amber);
  box-shadow: 0 0 0 3px rgba(245, 165, 36, 0.15);
}
.cc-slap-search input::placeholder { color: var(--text-dim); }
.cc-slap-search .clear {
  position: absolute;
  right: 8px;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 0;
  color: var(--text-dim);
  cursor: pointer;
  border-radius: var(--radius);
}
.cc-slap-search .clear:hover { color: var(--text); background: var(--bg-elevated); }
.cc-slap-search .clear svg { width: 14px; height: 14px; }

/* Filtered list — scrollable region with the slap messages
   rendered as touch-friendly buttons (56px tall). When the
   filter matches nothing we show an empty-state row. */
.cc-slap-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  scrollbar-width: thin;
  scrollbar-color: var(--border-amber) transparent;
}
.cc-slap-list::-webkit-scrollbar { width: 8px; }
.cc-slap-list::-webkit-scrollbar-track { background: transparent; }
.cc-slap-list::-webkit-scrollbar-thumb {
  background: var(--border-amber);
  border-radius: 4px;
}
.cc-slap-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  width: 100%;
  min-height: var(--touch-comfort);
  padding: 14px 16px;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  transition: background 0.08s ease, color 0.08s ease;
}
.cc-slap-row:last-child { border-bottom: 0; }
.cc-slap-row:hover,
.cc-slap-row:focus-visible {
  background: var(--bg-elevated);
  color: var(--amber);
  outline: none;
}
.cc-slap-row .icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  margin-top: 2px;
  opacity: 0.6;
}
.cc-slap-row .text {
  flex: 1 1 auto;
  word-break: break-word;
  line-height: 1.4;
}
.cc-slap-row mark {
  background: rgba(245, 165, 36, 0.25);
  color: var(--amber);
  padding: 0 1px;
  border-radius: 2px;
}
.cc-slap-empty {
  padding: 32px 16px;
  text-align: center;
  color: var(--text-dim);
  font-style: italic;
  font-size: 13px;
}
.cc-slap-count {
  font-size: 12px;
  color: var(--text-dim);
  font-family: var(--font-mono);
  padding: 0 2px;
}

/* =====================================================================
   ACTION BAR — Players page header (count + status pills)
   ===================================================================== */

.cc-players-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.cc-players-toolbar .cc-pill { font-family: var(--font-mono); font-size: 12px; }
.cc-players-toolbar .cc-toolbar-spacer { flex: 1 1 auto; }
.cc-players-toolbar .cc-btn { min-height: 32px; padding: 0 12px; font-size: 12px; }

/* =====================================================================
   END PLAYERS PAGE
   ===================================================================== */

.cc-maps-col {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
  /* Allow the column to shrink below its content height so the
     inner panel can flex to fill and the table can scroll. */
  min-height: 0;
}
@media (max-width: 1100px) {
  .cc-maps-grid { grid-template-columns: 1fr; }
}

/* Playlist selector (P1..P5) — full-width row above the 2-col grid.
   Each pill is a compact 48px touch target with the active
   playlist highlighted in amber. */
.cc-playlist-selector {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 8px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px;
}
.cc-playlist-selector .cc-btn {
  height: 56px;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0 8px;
  white-space: nowrap;
  position: relative;
}
.cc-playlist-selector .cc-btn.active {
  border-color: var(--amber);
  background: var(--amber-soft);
  color: var(--amber);
}
.cc-playlist-selector .cc-btn.active::after {
  /* Small dot in the top-right corner of the active playlist */
  content: "";
  position: absolute;
  top: 6px;
  right: 6px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--amber);
  box-shadow: var(--amber-glow);
}

/* Action grid (Refresh / Randomize / Clear / Set Active) — 4-col
   row above the Available Maps panel. Mirrors the P1-P5 selector
   visual pattern (same chrome, same height, same letter-spacing)
   but with 4 columns instead of 5 and no "active" state. The
   primary action (Set Active) keeps the .cc-btn primary styling
   so the user can pick it out of the row at a glance.

   Spacing note: NO margin-bottom here. The .cc-maps-col uses
   `gap: 16px` between children, which already provides the
   vertical space between this row and the Available Maps panel
   below it. The P1-P5 selector relies on the same gap. Adding
   margin-bottom here would push the Available Maps panel down
   by 12px more than the right column's Current Playlist panel,
   breaking the visual alignment. */
.cc-maps-action-row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px;
}
.cc-maps-action-row .cc-btn {
  height: 56px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0 6px;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.cc-maps-action-row .cc-btn svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}
/* Pulse the REFRESH button in amber when the server profile path
   has changed since the last map scan. The user can still click
   any button, but the eye-catching pulse tells them a full PFF+
   custom rescan is pending (and that clicking REFRESH will pick
   up the new path's maps with calculated sizes). */
.cc-maps-action-row .cc-btn.amber-pulse {
  border-color: var(--amber);
  color: var(--amber);
  animation: cc-amber-pulse 1.6s ease-in-out infinite;
}
@keyframes cc-amber-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(245, 165, 36, 0.0); }
  50%      { box-shadow: 0 0 0 6px rgba(245, 165, 36, 0.18); }
}

/* Map type filter (All / DM / TDM / ... / FB) — 5-col grid that
   wraps to 2 rows of 5 (mirrors the original WinForms tabMaps
   layout, which had two `TableLayoutPanel`s stacked: top row was
   DM/TDM/ALL/KOTH/TKOTH, bottom row was AD/CTF/CO-OP/FB/SD).
   The "All" filter is the default; specific game-type filters
   narrow the available-maps table to just maps of that type. */
.cc-map-type-filter {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 6px;
  margin-bottom: 10px;
}
.cc-map-type-filter .cc-btn {
  height: 40px;
  min-height: 40px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  padding: 0 6px;
  width: 100%;
}
.cc-map-type-filter .cc-btn.active {
  border-color: var(--amber);
  background: var(--amber-soft);
  color: var(--amber);
}
@media (max-width: 720px) {
  /* On narrow screens collapse to a single column so the
     3-letter codes stay readable. */
  .cc-map-type-filter { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Fill the column: each .cc-panel inside .cc-maps-col stretches to
   fill the available vertical space (between the action row above
   and the action bar below). The panel becomes a flex column so the
   body can flex to fill, and the table area can scroll internally. */
.cc-maps-col > .cc-panel {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
.cc-maps-col > .cc-panel > .cc-panel-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  /* Hide overflow on the body so the inner scroll wrapper handles it
     (avoids a double-scrollbar if both the body and the wrapper
     scroll independently). */
  overflow: hidden;
  padding: 16px;
}

/* Scrollable wrapper for the maps table. Sits inside the panel body,
   after the map type filter. The wrapper takes all remaining vertical
   space; the table inside scrolls vertically. The table's <thead>
   uses `position: sticky; top: 0` (set on .cc-maps-table thead th
   above) so the column headers stay visible while the user scrolls
   through long map lists. */
.cc-maps-table-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* The table inside has its own border + background; the wrapper
     doesn't add visual chrome so the table looks like a single
     self-contained scroll area. */
  margin-top: 12px;
  border-radius: var(--radius);
}

/* Maps table — same look-and-feel as the Files page table
   (`.cc-files-table`) but with the map-specific columns. The
   right column ("Actions") is narrower (60-90px) to leave more
   room for the map data. */
.cc-maps-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-size: 12px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.cc-maps-table thead th {
  text-align: left;
  font-weight: 600;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  background: var(--bg-elevated);
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 1;
}
.cc-maps-table tbody td {
  padding: 8px 12px;
  border-bottom: 1px solid var(--border-soft);
  color: var(--text);
  vertical-align: middle;
}
.cc-maps-table tbody tr:last-child td { border-bottom: none; }
.cc-maps-table tbody tr:hover { background: rgba(255, 255, 255, 0.02); }
/* Highlighting for the current / next map in the playlist. */
.cc-maps-table tbody tr.cc-row-current { background: rgba(34, 197, 94, 0.10); }
.cc-maps-table tbody tr.cc-row-next    { background: rgba(59, 130, 246, 0.10); }
.cc-maps-table tbody tr.cc-row-loop    { background: rgba(245, 165, 36, 0.10); }

/* Action button column in the maps table — small 32px square
   buttons in a horizontal row. */
.cc-maps-actions {
  display: flex;
  gap: 4px;
  align-items: center;
  white-space: nowrap;
}
.cc-maps-actions .cc-btn {
  width: 32px;
  height: 32px;
  min-height: 32px;
  padding: 0;
  font-size: 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.cc-maps-actions .cc-btn:disabled { opacity: 0.3; cursor: not-allowed; }

/* =====================================================================
   DRAG-AND-DROP REORDERING (current playlist table)
   =====================================================================
   The whole <tr> is the drag source + drop target. The user picks up
   a row (cursor → grabbing, row → 40% opacity), drags it over another
   row, and an amber 2px line appears either above or below the target
   row to show where the item will land. The split is the cursor's
   Y position relative to the row's top edge — < 20px = above, >= 20
   = below (rows are typically 40-48px tall).

   A subtle "≡" handle at the start of the name cell is the
   discoverability hint. The cursor is `grab` on the rest of the row
   so the user knows the entire row is grabbable, not just the handle. */
.cc-maps-table tbody tr[draggable="true"] { cursor: grab; }
.cc-maps-table tbody tr[draggable="true"]:hover { background: rgba(255, 255, 255, 0.04); }
.cc-maps-table tbody tr.dragging {
    opacity: 0.4;
    cursor: grabbing;
}
/* The amber drop indicator uses an inset box-shadow applied to EVERY
   <td> in the row so the line spans the full row width. With
   `border-collapse: collapse` the shadows from adjacent cells visually
   merge into a single continuous line. Using inset box-shadow (not
   border-top/border-bottom) means no layout shift — the row height
   stays exactly the same whether the indicator is showing or not. */
.cc-maps-table tbody tr.drag-over-above td {
    box-shadow: inset 0 2px 0 0 var(--amber);
}
.cc-maps-table tbody tr.drag-over-below td {
    box-shadow: inset 0 -2px 0 0 var(--amber);
}

/* Drag handle — a small "≡" icon at the start of the name cell.
   Inline with the map name, muted by default, amber on hover (and
   while the parent row is being dragged). */
.cc-maps-drag-handle {
    display: inline-block;
    color: var(--muted);
    margin-right: 8px;
    cursor: grab;
    user-select: none;
    font-size: 14px;
    line-height: 1;
    vertical-align: middle;
    transition: color 120ms ease;
}
.cc-maps-drag-handle:hover { color: var(--amber); }
.cc-maps-table tbody tr.dragging .cc-maps-drag-handle { color: var(--amber); }

/* Map type tag (DM / TDM / etc.) — small mono pill, color-coded
   so each game type reads at a glance. */
.cc-maps-type-tag {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  padding: 2px 6px;
  border-radius: 4px;
  background: var(--bg-elevated);
  color: var(--text-muted);
  border: 1px solid var(--border);
}
.cc-maps-type-tag.type-0 { color: #60a5fa; border-color: #1e3a5f; background: rgba(96, 165, 250, 0.10); }   /* DM */
.cc-maps-type-tag.type-1 { color: #a78bfa; border-color: #3b2e5e; background: rgba(167, 139, 250, 0.10); }   /* TDM */
.cc-maps-type-tag.type-2 { color: #34d399; border-color: #1a4a3a; background: rgba(52, 211, 153, 0.10); }   /* COOP */
.cc-maps-type-tag.type-3 { color: #fb923c; border-color: #5a3416; background: rgba(251, 146, 60, 0.10); }   /* TKOTH */
.cc-maps-type-tag.type-4 { color: #f59e0b; border-color: #5a3f10; background: rgba(245, 158, 11, 0.10); }   /* KOTH */
.cc-maps-type-tag.type-5 { color: #f43f5e; border-color: #5a1a2a; background: rgba(244, 63, 94, 0.10); }    /* SD */
.cc-maps-type-tag.type-6 { color: #eab308; border-color: #5a4810; background: rgba(234, 179, 8, 0.10); }    /* AD */
.cc-maps-type-tag.type-7 { color: #22d3ee; border-color: #155a66; background: rgba(34, 211, 238, 0.10); }   /* CTF */
.cc-maps-type-tag.type-8 { color: #c084fc; border-color: #3b1e5e; background: rgba(192, 132, 252, 0.10); }   /* FB */

/* Size tier tag — small mono pill, color-coded by map size
   (small/medium/large/huge). */
.cc-maps-size-tag {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 2px 6px;
  border-radius: 4px;
  background: var(--bg-elevated);
  color: var(--text-muted);
  border: 1px solid var(--border);
  /* Keep the size label on a single line (e.g. "Extra Large") even
     when the column gets narrow — the cell can ellipsis-truncate
     other content but the tag itself should never wrap. */
  white-space: nowrap;
}
.cc-maps-size-tag.tier-small  { color: #34d399; border-color: #1a4a3a; }
.cc-maps-size-tag.tier-medium { color: #fbbf24; border-color: #5a4410; }
.cc-maps-size-tag.tier-large  { color: #fb923c; border-color: #5a3416; }
.cc-maps-size-tag.tier-huge   { color: #f43f5e; border-color: #5a1a2a; }

/* Available-maps search box — sits in the right of the panel head,
   next to the title. Compact icon + text input, mono font for the
   typed query. 220px wide so the placeholder fits on one line, with
   a max-width so the input shrinks on narrow panels. */
.cc-maps-search {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 0 1 auto;
  min-width: 0;
}
.cc-maps-search svg {
  width: 14px;
  height: 14px;
  color: var(--text-dim);
  flex-shrink: 0;
}
.cc-maps-search .cc-input {
  width: 220px;
  max-width: 100%;
  height: 28px;
  font-size: 12px;
  padding: 0 10px;
}

/* Empty state for the maps tables when no rows match the current
   filter or playlist. */
.cc-maps-empty {
  padding: 32px 12px;
  text-align: center;
  color: var(--text-dim);
  font-size: 13px;
  background: var(--bg-panel);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
}
/* Drop-zone modifier on the empty-playlist state. The whole <div>
   becomes a valid drop target (HTML5 drag events wired in
   Maps.razor), and when the user is hovering with a dragged map
   the dashed border turns amber + the background picks up a soft
   amber tint so the affordance is obvious. */
.cc-maps-drop-zone {
  transition: border-color 120ms ease, background 120ms ease;
  cursor: copy;
}
.cc-maps-drop-zone.drop-zone-active {
  border-color: var(--amber);
  border-style: solid;
  background: var(--amber-soft);
  color: var(--amber);
}

/* (Old .cc-map-controls bar removed — Skip / Score / Play Next and
   the Now/Next status moved into the .cc-action-bar at the bottom
   of the page. See .cc-action-bar-status for the new status style.) */

/*
  Panel modifier: stretch the body to fill the panel's height and
  let any .cc-field-grow child (e.g. MOTD) expand into the leftover
  vertical space. Without this, the body sizes to its content and
  the field has nothing to grow into — so a 1-line MOTD sits at the
  top of the card and the rest is empty.

  Used on the IDENTITY card so the textarea grows to match the
  NETWORK card's height when both share a row.
*/
.cc-panel.cc-panel-stretch {
  display: flex;
  flex-direction: column;
}
.cc-panel.cc-panel-stretch > .cc-panel-body {
  flex: 1 1 auto;
  min-height: 0;
}

/* ---- Form layout inside a card ---- */

.cc-form {
  display: flex; flex-direction: column;
  gap: 14px;
  padding: 18px 20px 20px;
}
.cc-field {
  display: flex; flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.cc-field label {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.cc-field .muted { color: var(--text-dim); text-transform: none; letter-spacing: 0; font-weight: 400; }
.cc-field .req { color: var(--amber); }

/*
  Dirty-state outline. A 2px amber border + 6% amber background tint
  makes it obvious which field you changed without making the whole
  form look like an error.
*/
.cc-field-dirty .cc-input,
.cc-field-dirty .cc-select,
.cc-field-dirty .cc-textarea {
  border-color: var(--amber);
  background: rgba(245, 165, 36, 0.06);
}
.cc-field-dirty label {
  color: var(--amber);
}

/* Narrow field — used for short numeric inputs (port, timeout, etc.) */
.cc-field-narrow .cc-input { max-width: 140px; }

/* Side-by-side field row (e.g. Server Name + Country on one line).
   `cc-field-grow` takes the remaining space; `cc-field-country` is
   a fixed narrow column for 2-letter codes / short trailing values. */
.cc-field-row {
  display: flex; align-items: flex-end;
  gap: 12px;
}
.cc-field-row > .cc-field { min-width: 0; }
.cc-field-row > .cc-field-grow { flex: 1 1 auto; }
.cc-field-row > .cc-field-country { flex: 0 0 15%; min-width: 80px; }
.cc-field-row > .cc-field-country .cc-input { width: 100%; }
/* Quarter-width field — used for the Bind Port next to Bind IP on
   the NETWORK card. 25% of the row, same min-width guard as the
   country column so the port number never gets squeezed below
   4-digit readability. */
.cc-field-row > .cc-field-quarter { flex: 0 0 25%; min-width: 90px; }
.cc-field-row > .cc-field-quarter .cc-input { width: 100%; }

/*
  Standalone grow field — e.g. MOTD on the IDENTITY card, where the
  field should expand to fill whatever vertical space remains in
  the card. The parent .cc-form is `display: flex; flex-direction:
  column`, so the field's flex:1 + min-height: 0 makes the
  textarea grow to fill the column.
*/
.cc-field-grow { flex: 1 1 auto; min-height: 0; }
.cc-textarea-grow { flex: 1; min-height: 80px; resize: vertical; }

/* Input with adjacent action button (browse, detect, show/hide). */
.cc-input-with-action {
  display: flex; align-items: stretch;
  gap: 0;
}
.cc-input-with-action .cc-input,
.cc-input-with-action .cc-select {
  flex: 1; min-width: 0;
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.cc-input-with-action .cc-btn {
  flex: 0 0 auto;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  border-left: 0;
  padding: 0 16px;
  /* Match the input height (--touch-comfort = 56px). The base .cc-btn
     is --touch-min (48px), which leaves the button 8px shorter than
     the input it sits next to and breaks the shared edge alignment. */
  height: var(--touch-comfort);
  min-height: var(--touch-comfort);
}
.cc-input-with-action .cc-btn-icon {
  padding: 0 12px;
  /* Square icon button: same height + width as the input it sits
     next to, so the rounded corners on the shared edge line up. */
  min-width: 56px;
  height: var(--touch-comfort);
}

/* Date row — input + detached label button used for the Start Date /
   Expire Date row in BanRecordEditor. Unlike .cc-input-with-action
   the button is NOT joined to the input (no shared edge, no removed
   border, no removed rounded corner) — it sits next to the input
   with a real gap, has its own border, and is smaller (84px min-width
   vs the previous 16px+text+16px+chunky-pad). The result reads as
   two side-by-side controls rather than a hybrid. */
.cc-date-row {
  display: flex;
  align-items: stretch;
  gap: 8px;
}
.cc-date-row .cc-input {
  flex: 1 1 auto;
  min-width: 0;
}
.cc-date-row .cc-label-button {
  flex: 0 0 auto;
  min-width: 84px;
  padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background-color: var(--bg-elevated);
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color 120ms, border-color 120ms, background-color 120ms;
}
.cc-date-row .cc-label-button:hover:not(:disabled) {
  color: var(--amber);
  border-color: var(--amber);
  background-color: var(--amber-soft);
}
/* Active = the toggle is currently ON (e.g. expire date is set).
   Solid amber border + amber text so the user can see at a glance
   which optional fields they have turned on. */
.cc-date-row .cc-label-button.active {
  color: var(--amber);
  border-color: var(--amber);
  background-color: var(--bg-elevated);
}
.cc-date-row .cc-label-button.active:hover {
  background-color: var(--amber-soft);
}

/* Toggle row — one or more compact checkboxes with their value inline.
   No wrap: the row always fits on one line, with each .cc-toggle child
   sharing the row width equally (flex: 1 1 0). The label inside the
   toggle is allowed to wrap so a 4-up row (e.g. the NETWORK card's
   Dedicated / Local Play / Require Nova / Enable Remote) doesn't
   overflow when the card is at half-width. */
.cc-toggles-row {
  display: flex; align-items: center;
  gap: 10px;
  flex-wrap: nowrap;
}
.cc-toggles-row > .cc-toggle { flex: 1 1 0; min-width: 0; justify-content: center; }
/* The label is a span, which doesn't wrap by default. Allow it to
   wrap onto a second line so the toggle can shrink to its share of
   the row width without clipping the text. */
.cc-toggles-row > .cc-toggle .cc-toggle-label {
  min-width: 0;
  white-space: normal;
  text-align: center;
  line-height: 1.2;
}
/* Help text block that lives directly under a .cc-toggles-row.
   Used by the NETWORK card on the Profile page to explain what
   the Enable Remote toggle actually gates (loopback firewall).
   Smaller font, muted color, with a tiny top margin so it reads
   as a caption rather than another control row. */
.cc-toggle-help {
  font-size: 12px;
  line-height: 1.5;
  margin-top: 8px;
  padding: 8px 12px;
  background: var(--bg-elevated);
  border-left: 2px solid var(--border-amber);
  border-radius: 4px;
}
.cc-toggle-help strong { color: var(--text); font-weight: 600; }

/* Toggle pill — bigger than the standard checkbox so it fills the
   48px touch target. Active = green check, inactive = grey X. */
.cc-toggle {
  display: inline-flex; align-items: center;
  gap: 8px;
  padding: 10px 14px;
  min-height: 48px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
  transition: border-color 120ms, background 120ms, color 120ms;
}
.cc-toggle input[type="checkbox"] {
  width: 18px; height: 18px;
  margin: 0;
  accent-color: var(--green);
  cursor: pointer;
}
.cc-toggle .cc-toggle-label { color: inherit; }
.cc-toggle.on { color: var(--text); border-color: #1a3a23; background: rgba(34,197,94,0.06); }
.cc-toggle.on .cc-toggle-label { color: var(--green); font-weight: 600; }
.cc-toggle.off { color: var(--text-dim); }
.cc-toggle.off .cc-toggle-label { color: var(--text-dim); }
.cc-toggle.cc-disabled { opacity: 0.45; cursor: not-allowed; pointer-events: none; }

/* Mono input style — used for IPs, ports, paths, file names */
.cc-input-mono { font-family: var(--font-mono); }

/* Disabled form controls — used for value fields whose parent
   toggle is off, so the field is always visible (no layout shift
   on toggle) but clearly inert. */
.cc-input:disabled,
.cc-select:disabled,
.cc-textarea:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ---- RESTRICTIONS card — 4-up grid ----
   Each of the 4 restrictions (Min Ping / Max Ping / Kick Idle /
   Connection Timeout) is a vertical module: a small mono label on
   top, then a control row of (compact toggle + value input) or
   just a value input. The toggle has no inline text — the label
   on top carries the meaning and the pill's color + check state
   carry the on/off signal. The value field is always shown (so
   the row doesn't reflow when a toggle flips) but disabled when
   its toggle is off. */

/* Bordered subsection inside a card body — used for the
   restrictions row nested inside the NETWORK card so the 4
   controls read as a visually isolated group rather than
   blending into the bind/port/password fields above. */
.cc-card-section {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 12px 14px 14px;
  background: rgba(255, 255, 255, 0.015);
}
.cc-card-section-header {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 10px;
}

/* Server Options grid — 16 toggles laid out in a 4-col grid.
   Each toggle is a standard .cc-toggle pill (checkbox + label) —
   same component used on the Settings page network card. The
   "compact" variant strips the 8px internal gap and shortens
   the 48px min-height to 40px so 4 fit comfortably across on a
   1280px viewport without crowding the touch targets too far
   for a 1024px tablet. */
.cc-options-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
}
/* In the Game Play Options grid (4-col on desktop, ~67-85px per toggle),
   the toggle's flex row can't fit "Left Leaning" / "Map Skipping" on
   one line. The label is allowed to wrap to a second line below the
   checkbox; the text inside the label wraps at word boundaries. */
.cc-options-grid > .cc-toggle {
  flex-wrap: wrap;
  row-gap: 2px;
  justify-content: center;
  min-width: 0;
}
.cc-options-grid > .cc-toggle input[type="checkbox"] {
  flex: 0 0 auto;
}
.cc-options-grid > .cc-toggle .cc-toggle-label {
  flex: 1 1 100%;
  min-width: 0;
  text-align: center;
  line-height: 1.2;
  overflow-wrap: break-word;
}
/* On the Gameplay page the left rail is narrower (~340px at 1280px
   when the center column was widened to 1fr), so 4-col toggles are
   only ~67px wide and labels like "Auto Balance" wrap to 3 lines.
   Drop to 3-col at the Gameplay scope only — the rest of the app
   (Bans / Settings / Stats) keeps 4-col. */
.cc-gameplay-left .cc-options-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 900px) {
  .cc-gameplay-left .cc-options-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .cc-gameplay-left .cc-options-grid { grid-template-columns: 1fr; }
}
@media (max-width: 900px) {
  .cc-options-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .cc-options-grid { grid-template-columns: 1fr; }
}
.cc-restrictions-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  /* No outer padding — the .cc-card-section wrapper provides it
     when the grid is nested inside the NETWORK card. */
}
.cc-restriction {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.cc-restriction > label {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cc-restriction-control {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 48px;
}
/* In the restrictions grid, the toggle is a compact 40px square
   pill with just the checkbox — no inline text. 40px is below
   the standard 48px touch target, but the value input next to
   it is the primary control and is itself 40px+ tall, so the
   combined row is comfortably tappable. */
.cc-restriction .cc-toggle {
  flex: 0 0 auto;
  min-width: 40px;
  min-height: 40px;
  padding: 0 10px;
  justify-content: center;
}
.cc-restriction .cc-input {
  flex: 1;
  min-width: 0;
  max-width: none;
}
/* Unit suffix ("ms" / "s") sits to the right of the value input
   so the label on top can stay short ("Min Ping" instead of
   "Min Ping (ms)") and still fit in a quarter-width cell. */
.cc-restriction-unit {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  flex: 0 0 auto;
  min-width: 18px;
  text-align: left;
}

@media (max-width: 720px) {
  /* On narrow viewports, stack the 4 modules into 2 rows of 2
     so each cell has enough width for the value input. */
  .cc-restrictions-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---- Launch Flags card summary ---- */

.cc-flags-summary {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.cc-flags-stats {
  display: flex; gap: 18px;
}
.cc-flags-stats .stat {
  display: flex; flex-direction: column; gap: 2px;
  padding: 8px 14px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  min-width: 90px;
  text-align: center;
}
.cc-flags-stats .stat .num {
  font-family: var(--font-mono);
  font-size: 24px;
  font-weight: 700;
  color: var(--amber);
  line-height: 1;
}
.cc-flags-stats .stat .lbl {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.12em;
  color: var(--text-dim);
  text-transform: uppercase;
}

/* ---- Sticky action bar (bottom) ---- */

.cc-action-bar {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  z-index: 100;
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px;
  padding: 14px 28px;
  background: linear-gradient(180deg, rgba(13,15,18,0) 0%, var(--bg-header) 30%);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-top: 1px solid var(--border);
  box-shadow: 0 -2px 12px rgba(0,0,0,0.4);
  transform: translateY(0);
  transition: border-color 200ms, background 200ms;
}
.cc-action-bar.dirty {
  border-top-color: var(--amber);
  background: linear-gradient(180deg, rgba(245,165,36,0.04) 0%, var(--bg-header) 30%);
}
.cc-action-bar-left,
.cc-action-bar-right {
  display: flex; align-items: center; gap: 12px;
  flex-wrap: wrap;
}
.cc-action-bar .cc-btn[disabled] { opacity: 0.4; cursor: not-allowed; }

/* Status text shown in the action bar's left side (e.g. the Maps
   page's "Now: x · Next: y" line). Subtle monospace caption that
   doesn't compete with the action buttons. */
.cc-action-bar-status {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-muted);
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.cc-action-bar-status strong {
  color: var(--text);
  font-weight: 600;
}

/* ---- Flags modal (inside the cc-modal large chrome) ---- */

.cc-flags-categories {
  display: flex; flex-direction: column; gap: 14px;
}
.cc-flags-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
@media (max-width: 700px) {
  .cc-flags-grid { grid-template-columns: 1fr; }
}

.cc-flag-toggle {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  user-select: none;
  transition: border-color 120ms, background 120ms;
  min-height: 48px;
}
.cc-flag-toggle input[type="checkbox"] {
  width: 16px; height: 16px;
  margin: 2px 0 0 0;
  accent-color: var(--green);
  flex-shrink: 0;
  cursor: pointer;
}
.cc-flag-toggle.on  { border-color: #1a3a23; background: rgba(34,197,94,0.06); }
.cc-flag-toggle.off { border-color: var(--border); }
.cc-flag-body { flex: 1; min-width: 0; }
.cc-flag-switch {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  color: var(--amber);
  line-height: 1.2;
  margin-bottom: 2px;
}
.cc-flag-toggle.on .cc-flag-switch { color: var(--green); }
.cc-flag-desc {
  font-size: 11px;
  color: var(--text-dim);
  line-height: 1.3;
}

/* ---------- Toast / live status (top-right) ---------- */

.cc-toast-stack {
  position: fixed; top: 80px; right: 28px;
  z-index: 2000;
  display: flex; flex-direction: column; gap: 10px;
  pointer-events: none;
}
.cc-toast {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--amber);
  border-radius: var(--radius);
  padding: 12px 18px;
  font-size: 14px;
  color: var(--text);
  box-shadow: 0 8px 24px rgba(0,0,0,0.3);
  min-width: 240px;
  pointer-events: auto;
}
.cc-toast.success { border-left-color: var(--green); }
.cc-toast.error { border-left-color: var(--red); }
.cc-toast.warning { border-left-color: var(--amber); }
.cc-toast.info { border-left-color: var(--text-dim); }

/* ---------- Busy overlay (greys out the page during server ops) ----------
 * Shown while IsServerBusy is true: Start/Stop, Update, Lock, Pause,
 * Save, Import, Export. The backdrop is opaque enough to clearly indicate
 * the page is locked, and the centered card names the current operation
 * so the operator knows what the system is doing. pointer-events on the
 * overlay block clicks on underlying controls (nav, buttons, form
 * fields) so the operator can't kick off a second operation or
 * navigate away mid-action. The overlay is removed the moment
 * IsServerBusy clears, revealing the resulting toast / state.
 */
.cc-busy-overlay {
  position: fixed; inset: 0;
  background: rgba(8, 10, 14, 0.72);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  z-index: 3000;
  display: flex; align-items: center; justify-content: center;
  animation: cc-busy-fade-in 120ms ease-out;
  pointer-events: auto;       /* block clicks on everything below */
  user-select: none;
}
.cc-busy-card {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg, 12px);
  padding: 28px 36px;
  min-width: 320px; max-width: 480px;
  text-align: center;
  box-shadow: 0 16px 48px rgba(0,0,0,0.5);
  display: flex; flex-direction: column; align-items: center; gap: 14px;
}
.cc-busy-spinner {
  width: 40px; height: 40px;
  border: 3px solid rgba(255,255,255,0.12);
  border-top-color: var(--green, #4ade80);
  border-radius: 50%;
  animation: cc-busy-spin 800ms linear infinite;
}
.cc-busy-title {
  font-size: 16px; font-weight: 600;
  color: var(--text);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.cc-busy-msg {
  font-size: 13px;
  color: var(--text-dim);
  line-height: 1.45;
}
@keyframes cc-busy-spin {
  to { transform: rotate(360deg); }
}
@keyframes cc-busy-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ---------- Stat cards (dashboard / overview) ---------- */

.cc-stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; }
.cc-stat {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 16px 20px;
}
.cc-stat .label {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.cc-stat .val {
  font-size: 28px;
  font-weight: 700;
  color: var(--text-strong);
  margin-top: 4px;
  line-height: 1.1;
}
.cc-stat .delta {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--green);
  margin-top: 2px;
}

/* ---------- Responsive (tablet → smaller) ---------- */

@media (max-width: 1100px) {
  :root { --rail-width: 64px; }
  .cc-rail .sep, .cc-nav-item span, .cc-nav-item .badge { display: none; }
  .cc-nav-item { justify-content: center; padding: 0; }
}
@media (max-width: 700px) {
  .cc-app { grid-template-columns: 1fr; grid-template-rows: var(--header-height) 1fr var(--footer-height); }
  .cc-rail { display: none; }
  .cc-header { padding: 0 16px; }
  .cc-header .meta { display: none; }
  .cc-main { padding: 16px; }
}

/* ============================================================================
   Files page (port of the WinForms FileManager sub-panel)

   Two zones:
     1. .cc-files-toolbar - the action bar at the top (Refresh / Upload /
                            Download / Delete + file counts on the right)
     2. .cc-files-table   - the scrollable file list with selectable rows

   Plus the supporting bits: file icon color-by-extension, the empty state
   shown when the server path isn't configured or no files exist yet, and
   the modal delete-confirmation list.
   ============================================================================ */

.cc-files-layout {
  display: flex; flex-direction: column;
  gap: 16px;
  padding-bottom: 24px;
}

.cc-files-toolbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 14px 20px !important;
}
.cc-files-toolbar-left,
.cc-files-toolbar-right {
  display: flex; align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.cc-files-counts {
  display: flex; gap: 10px;
  flex-wrap: wrap;
}
.cc-files-counts .count {
  display: flex; flex-direction: column; gap: 2px;
  padding: 6px 14px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  min-width: 80px;
  text-align: right;
}
.cc-files-counts .count .num {
  font-family: var(--font-mono);
  font-size: 16px;
  font-weight: 700;
  color: var(--text-strong);
  line-height: 1;
}
.cc-files-counts .count.amber .num { color: var(--amber); }
.cc-files-counts .count .lbl {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* Danger variant for the Delete button */
.cc-btn.danger {
  border-color: rgba(220, 38, 38, 0.4);
  color: #f87171;
}
.cc-btn.danger:hover {
  background: rgba(220, 38, 38, 0.08);
  border-color: #dc2626;
  color: #fca5a5;
}
.cc-btn.danger:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  background: transparent;
}

/* File table */
.cc-files-table-wrap {
  padding: 0 !important;
  overflow-x: auto;
}
.cc-files-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.cc-files-table thead th {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-align: left;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  background: rgba(255, 255, 255, 0.02);
  white-space: nowrap;
}
.cc-files-table tbody tr {
  cursor: pointer;
  transition: background 100ms;
}
.cc-files-table tbody tr:hover {
  background: rgba(245, 165, 36, 0.04);
}
.cc-files-table tbody tr.selected {
  background: rgba(245, 165, 36, 0.08);
  box-shadow: inset 3px 0 0 var(--amber);
}
.cc-files-table tbody td {
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
.cc-files-table tbody tr:last-child td {
  border-bottom: none;
}
.cc-files-table .col-check { width: 40px; }
.cc-files-table .col-name  { min-width: 200px; }
.cc-files-table .col-size  { width: 100px; text-align: right; color: var(--text-muted); }
.cc-files-table .col-ext   { width: 80px; }
.cc-files-table .col-date  { width: 160px; color: var(--text-muted); }

.cc-files-table input[type="checkbox"] {
  width: 18px; height: 18px;
  margin: 0;
  accent-color: var(--amber);
  cursor: pointer;
}

.cc-files-table .col-name {
  display: flex; align-items: center; gap: 10px;
}
.file-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  flex-shrink: 0;
}
.file-icon svg { width: 14px; height: 14px; }
.file-icon.ext-bms { color: var(--amber); background: rgba(245,165,36,0.08); }
.file-icon.ext-mis { color: #60a5fa; background: rgba(96,165,250,0.08); }
.file-icon.ext-til { color: #a78bfa; background: rgba(167,139,250,0.08); }
.file-icon.ext-zip { color: #fbbf24; background: rgba(251,191,36,0.08); }
.file-icon.ext-other { color: var(--text-dim); }
.file-name {
  font-size: 13px;
  color: var(--text-strong);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Tag pills in the Type column */
.cc-tag {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 3px 8px;
  border-radius: 4px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  color: var(--text-muted);
}
.cc-tag.ext-bms { color: var(--amber); border-color: rgba(245,165,36,0.4); background: rgba(245,165,36,0.06); }
.cc-tag.ext-mis { color: #60a5fa; border-color: rgba(96,165,250,0.4); background: rgba(96,165,250,0.06); }
.cc-tag.ext-til { color: #a78bfa; border-color: rgba(167,139,250,0.4); background: rgba(167,139,250,0.06); }
.cc-tag.ext-zip { color: #fbbf24; border-color: rgba(251,191,36,0.4); background: rgba(251,191,36,0.06); }

/* Empty state — shown when no server path or no files */
.cc-files-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center;
  padding: 60px 24px;
  gap: 12px;
  color: var(--text-dim);
}
.cc-files-empty svg {
  width: 48px; height: 48px;
  opacity: 0.4;
  color: var(--amber);
}
.cc-files-empty .title {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--text);
  margin-top: 4px;
}
.cc-files-empty .msg {
  font-size: 13px;
  max-width: 360px;
  line-height: 1.5;
}

/* Delete-confirmation modal list */
.cc-files-delete-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 200px;
  overflow-y: auto;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.cc-files-delete-list li {
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  color: var(--text);
}
.cc-files-delete-list li:last-child { border-bottom: none; }

/* Chat page layout — vertical stack of panels with consistent
   16px gap. The page sets a single <div class="cc-chat-layout">
   wrapper around all four sections.

   When the page only has a single LIVE STREAM panel, the
   `flex: 1 1 auto` + the .cc-panel-stretch modifier on the panel
   make the log fill the available viewport height (below the
   page head and sub-nav). The log scrolls internally instead
   of the page itself. `.cc-main` is set to a flex column so
   the chat layout can use flex to fill the remaining space
   (instead of `height: 100%` which would overflow the page). */
.cc-chat-layout {
  display: flex; flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  gap: 16px;
  /* Leave a little breathing room above the page-bottom pills
     (the user can add a sticky footer action bar later if
     needed). */
  padding-bottom: 24px;
}

/* When the LIVE STREAM panel is in stretch mode, the panel body
   needs to be a flex column so the chat log can grow to fill
   the available space and the send row sits at the bottom.
   The existing .cc-panel-stretch > .cc-panel-body rule already
   sets flex: 1 1 auto + min-height: 0; we just need to make
   it a flex column with column direction. */
.cc-panel.cc-panel-stretch {
  flex: 1 1 auto;
  min-height: 0;
}
.cc-panel.cc-panel-stretch > .cc-panel-body.cc-chat-panel-body {
  display: flex;
  flex-direction: column;
}
/* The chat log fills the remaining vertical space inside the
   stretched panel body, scrolling internally. Override the
   .cc-chat-panel-body > .cc-chat-log cap (max-height: 360px,
   min-height: 200px) which is sized for the static chat page
   case where the panel doesn't stretch. The dashboard card
   still uses the original 220px max-height via the plain
   .cc-chat-log rule. */
.cc-panel.cc-panel-stretch > .cc-panel-body.cc-chat-panel-body > .cc-chat-log {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
}

/* ============================================================================
   CHAT SUB-NAV — horizontal tab bar shared across /chat sub-pages

   Replaces the single-page tabbed layout with 4 separate
   sub-routes (live stream, auto messages, slap messages, history).
   Each sub-page renders <ChatSubNav /> at the top so the user
   can switch between them without going back to the main nav.

   The active tab is highlighted via Blazor's NavLink component
   (which adds aria-current="page" automatically) + a CSS modifier
   class `.cc-subnav-tab--active` that the global nav-link
   framework also adds.
   ============================================================================ */

.cc-subnav {
  display: flex;
  align-items: stretch;
  gap: 4px;
  margin-bottom: 20px;
  padding: 4px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow-x: auto;
  /* Pin the nav to the tab's intrinsic height so the flex algorithm
     on the page (.cc-main → .cc-bans-layout / .cc-chat-layout) can't
     shrink it below the tab and clip the 44px tab in half. Without
     this, sub-pages whose layout panel grows (proxy / firewall /
     chat auto / chat slap) squeeze the nav to ~25-30px. */
  min-height: 44px;
  flex: 0 0 auto;
}
.cc-subnav-tab {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  padding: 0 16px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 4px;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: color 80ms, background 80ms, border-color 80ms;
}
.cc-subnav-tab svg {
  width: 14px; height: 14px;
  flex-shrink: 0;
}
.cc-subnav-tab:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.04);
}
.cc-subnav-tab:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 1px;
}
.cc-subnav-tab.active,
.cc-subnav-tab[aria-current="page"] {
  background: var(--amber);
  color: #1a1a1a;
  border-color: var(--amber);
  box-shadow: 0 0 0 1px var(--amber);
}
@media (max-width: 720px) {
  .cc-subnav {
    flex-direction: column;
  }
  .cc-subnav-tab {
    justify-content: flex-start;
  }
}

/* ============================================================================
   CHAT — auto / slap CRUD list rows (Phase 2)

   Used by Sections 2 (AUTO MESSAGES) and 3 (SLAP MESSAGES) of
   the Chat page. Each section has the same shape:
     • .cc-crud-add    — top add row (inputs + ADD button)
     • .cc-crud-list   — scrollable list of current entries
     • .cc-crud-row    — one entry (text + × delete button)
     • .cc-crud-empty  — empty state

   Both panels use the same styles so the two sections look
   consistent. Auto messages have an extra "trigger" label and
   the row shows the minutes-suffix; slap messages have just
   the text.
   ============================================================================ */

.cc-crud-add {
  display: grid;
  /* 3-col: sequence | text input | ADD button. The auto-message
     page uses all three (trigger was removed in the redesign);
     the slap page uses just input + button via
     .cc-crud-add-no-trigger. */
  grid-template-columns: 80px minmax(0, 1fr) auto;
  align-items: stretch;
  gap: 8px;
  margin-bottom: 12px;
}
/* Slap add row has no sequence column — drop the leading 80px so
   the input + button fill the row cleanly. */
.cc-crud-add.cc-crud-add-no-trigger {
  grid-template-columns: minmax(0, 1fr) auto;
}
.cc-crud-add .cc-crud-input {
  min-width: 0;
  /* Lock both height and min-height — .cc-input defaults to
     --touch-comfort (56px) which would push this row taller
     than the seq NumberField + button. Set both to 44px so
     all three add-row elements line up exactly. */
  height: 44px;
  min-height: 44px;
  font-family: var(--font-mono);
}
.cc-crud-add .cc-crud-seq {
  min-height: 44px;
  text-align: right;
  font-family: var(--font-mono);
}
/* NumberField inside the seq column of .cc-crud-add. The column
   is 80px wide; the wrapper + centered input fit in that budget
   (no steppers — the user removed them for a cleaner row). All
   three add-row elements are 44px tall so they line up
   exactly: wrapper 44px, message input 44px, button 44px. */
.cc-crud-add .cc-number-field {
  width: 80px;
  max-width: 80px;
  min-height: 44px;
  height: 44px;
}
.cc-crud-add .cc-number-field .cc-input {
  min-height: 44px;
  height: 44px;
  font-size: 13px;
  padding: 0 4px;
}
.cc-crud-add .cc-btn {
  /* Lock both height and min-height — .cc-btn defaults to
     --touch-min (48px). Set both to 44px to match the seq
     NumberField + message input. */
  height: 44px;
  min-width: 96px;
  min-height: 44px;
  font-family: var(--font-mono);
  font-weight: 700;
  letter-spacing: 0.04em;
}
/* On tablet/narrow viewports, the row collapses so the seq
   + input share a row and the button drops to a second row. */
@media (max-width: 720px) {
  .cc-crud-add {
    grid-template-columns: 80px 1fr;
  }
  .cc-crud-add .cc-crud-seq {
    text-align: left;
  }
  .cc-crud-add .cc-btn {
    grid-column: 1 / -1;
  }
}

.cc-crud-list {
  display: flex; flex-direction: column;
  gap: 4px;
  max-height: 240px;
  overflow-y: auto;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 4px;
}
/* When the AUTO/SLAP MESSAGES panel is in stretch mode (the
   .cc-chat-layout parent uses flex to fill the .cc-main
   viewport), the panel body becomes a flex column. The list
   (.cc-crud-list) sits at the top with `flex: 1 1 auto` so it
   grows to fill the available body space, and the add row
   (.cc-crud-add) sits at the bottom with its natural height.
   The list scrolls internally so the overall page window
   doesn't scroll when there are many entries. The .cc-crud-body
   class is on the panel body when stretched. */
.cc-panel.cc-panel-stretch > .cc-panel-body.cc-crud-body {
  display: flex;
  flex-direction: column;
}
.cc-panel.cc-panel-stretch > .cc-panel-body.cc-crud-body > .cc-crud-list {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;  /* override the 240px cap used in the static case */
}
/* Bottom-of-page add row (chat-auto, chat-history). The
   `.cc-crud-add-bottom` modifier adds a top border so the
   row reads as a "footer action bar" separated from the
   scrollable list above, and drops the bottom margin (the
   panel padding already gives breathing room). */
.cc-crud-add.cc-crud-add-bottom {
  margin-top: 12px;
  margin-bottom: 0;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.cc-crud-list:empty {
  display: none;
}

.cc-crud-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px;
  border-radius: 4px;
  font-size: 13px;
  color: var(--text);
  transition: background 80ms;
}
.cc-crud-row:hover {
  background: rgba(255, 255, 255, 0.03);
}
.cc-crud-row .cc-crud-row-text {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  white-space: pre-wrap;
  word-break: break-word;
}
.cc-crud-row .cc-crud-row-meta {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--amber);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
/* Sequence chip on auto-message rows. Mono #N label so the
   operator can see the order within a batch at a glance. */
.cc-crud-row .cc-crud-row-seq {
  flex: 0 0 auto;
  min-width: 36px;
  text-align: right;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
  letter-spacing: 0.02em;
}
/* Computed fire-time pill on auto-message rows. Shows when the
   message will actually broadcast (e.g. "AT START", "+15s",
   "+1m30s") based on the current DELAY setting. */
.cc-crud-row .cc-crud-row-fire {
  flex: 0 0 auto;
  min-width: 60px;
  text-align: right;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--amber);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  white-space: nowrap;
}
/* Drag handle (the ⋮⋮ on the left of each row) + the row's
   batch chip + action buttons (link / unlink / delete). The
   row uses display:flex so these sit in a row alongside the
   message text. */
.cc-crud-row {
  /* already display:flex from the base rule */
  gap: 8px;
}
.cc-crud-row .cc-crud-row-handle {
  flex: 0 0 auto;
  width: 18px;
  text-align: center;
  color: var(--text-dim);
  cursor: grab;
  user-select: none;
  font-size: 16px;
  line-height: 1;
  align-self: stretch;
  display: flex; align-items: center; justify-content: center;
}
.cc-crud-row .cc-crud-row-handle:active { cursor: grabbing; }
.cc-crud-row .cc-crud-row-batch {
  flex: 0 0 auto;
  min-width: 80px;
  display: flex; align-items: center;
}
.cc-batch-pill {
  display: inline-flex; align-items: center;
  padding: 2px 8px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border-radius: 999px;
  background: rgba(34, 197, 94, 0.16);
  color: #86efac;
  border: 1px solid rgba(34, 197, 94, 0.4);
  white-space: nowrap;
}
.cc-batch-pill.cc-batch-pill-unlinked {
  background: rgba(120, 120, 120, 0.12);
  color: var(--text-dim);
  border-color: var(--border);
}
.cc-crud-row .cc-crud-row-actions {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 4px;
  margin-left: auto;  /* push to the right end of the row */
}
.cc-crud-row .cc-crud-row-link,
.cc-crud-row .cc-crud-row-unlink {
  appearance: none;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-dim);
  width: 28px;
  height: 28px;
  border-radius: 4px;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px;
  transition: color 80ms, background 80ms, border-color 80ms;
}
.cc-crud-row .cc-crud-row-link:hover:not(:disabled) {
  color: #86efac;
  background: rgba(34, 197, 94, 0.08);
  border-color: rgba(34, 197, 94, 0.4);
}
.cc-crud-row .cc-crud-row-unlink:hover:not(:disabled) {
  color: var(--amber);
  background: rgba(245, 158, 11, 0.08);
  border-color: rgba(245, 158, 11, 0.4);
}
.cc-crud-row .cc-crud-row-link:disabled,
.cc-crud-row .cc-crud-row-unlink:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}
.cc-crud-row .cc-crud-row-text {
  flex: 1 1 auto;
  min-width: 0;
}
/* Visual cue: the row being dragged + the drop target. The
   dragged row gets a dimmer outline; the drop target gets a
   strong amber top-border to show where it'll land. */
.cc-crud-row[draggable="true"] {
  cursor: default;
}
.cc-crud-row.cc-crud-row-dragging {
  opacity: 0.5;
}
.cc-crud-row.cc-crud-row-drop-target {
  border-top: 2px solid var(--amber);
  margin-top: -2px;  /* compensate for the border so layout doesn't jump */
}
/* Batched rows get a subtle left-border to visually group
   them. The .cc-crud-row-batched class is applied when the
   message's batch number is shared with another message. */
.cc-crud-row.cc-crud-row-batched {
  border-left: 2px solid rgba(34, 197, 94, 0.5);
  padding-left: 10px;  /* compensate for the border */
}
.cc-crud-row .cc-crud-row-del {
  flex: 0 0 auto;
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-dim);
  width: 32px;
  height: 32px;
  border-radius: 4px;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: color 80ms, background 80ms, border-color 80ms;
}
.cc-crud-row .cc-crud-row-del:hover {
  color: #f87171;
  background: rgba(220, 38, 38, 0.08);
  border-color: rgba(220, 38, 38, 0.4);
}
.cc-crud-row .cc-crud-row-del:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 1px;
}
.cc-crud-row .cc-crud-row-del svg {
  width: 14px; height: 14px;
}

.cc-crud-empty {
  padding: 16px 12px;
  text-align: center;
  color: var(--text-dim);
  font-style: italic;
  font-size: 12px;
}

/* Auto-message batch group. The list is partitioned by
   TriggerSeconds — each group gets a thin caption row so the
   operator can see which messages fire together. */
.cc-auto-group {
  display: flex; flex-direction: column;
  gap: 2px;
}
.cc-auto-group + .cc-auto-group {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--border-soft);
}
.cc-auto-group-head {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
  padding: 4px 12px 2px;
}

/* Per-page-head inline control (e.g. the "DELAY (s)" input on
   the AUTO MESSAGES page). Sits in the panel head's .right
   slot, next to the existing count/count badges. Small input
   + label inline. */
.cc-head-control {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-right: 12px;
}
.cc-head-control-label {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap;
}
.cc-head-control-input {
  width: 56px;
  min-height: 26px;
  font-size: 12px;
  padding: 2px 6px;
  text-align: right;
}
/* Compact NumberField sizing when used inside a .cc-head-control.
   The default NumberField is 200px max with 24px steppers + a
   30px min-height — too big for a head control. Shrink to fit
   the 56px-wide slot with smaller steppers so the +/− and the
   centered input all fit. */
.cc-head-control .cc-number-field {
  width: 80px;
  max-width: 80px;
  min-height: 26px;
  height: 26px;
}
.cc-head-control .cc-number-field .cc-stepper-btn {
  width: 18px;
  height: 26px;
  font-size: 12px;
}
.cc-head-control .cc-number-field .cc-input {
  min-height: 26px;
  height: 26px;
  font-size: 12px;
  padding: 2px 4px;
}
.cc-head-control-count {
  white-space: nowrap;
}

/* ============================================================================
   CHAT — floating toast (Phase 2)

   Single floating toast in the bottom-right corner. Auto-dismisses
   after 3 seconds. Used by the auto/slap add/remove handlers to
   surface the CommandResult.Success / .Message inline (instead of
   MessageBox.Show like the WinForms tab).
   ============================================================================ */

.cc-chat-toast {
  position: fixed;
  right: 20px;
  bottom: 60px;
  z-index: 1000;
  display: flex; flex-direction: column; gap: 8px;
  pointer-events: none;
  max-width: 360px;
}
.cc-chat-toast .toast {
  pointer-events: auto;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  font-size: 12px;
  animation: cc-toast-in 180ms ease-out;
}
.cc-chat-toast .toast.success {
  border-left-color: #22c55e;
}
.cc-chat-toast .toast.error {
  border-left-color: #dc2626;
}
.cc-chat-toast .toast .title {
  font-family: var(--font-mono);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--text-strong);
  margin-bottom: 2px;
}
.cc-chat-toast .toast.success .title { color: #4ade80; }
.cc-chat-toast .toast.error .title { color: #f87171; }
.cc-chat-toast .toast .message {
  color: var(--text);
  line-height: 1.4;
  word-break: break-word;
}
@keyframes cc-toast-in {
  from { opacity: 0; transform: translateX(20px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* ============================================================================
   CHAT — history filter row + results table + pagination (Phase 3)

   Replaces the WinForms tabChat → ChatHistory sub-panel:
     - filter row: date range / type / player / search + Load/Clear
     - results table: timestamp / team / player / message
     - pagination bar: ◀ PREV / page-of-N / page-size / NEXT ▶ / Export

   All controls are 44px min-height to keep the touch-first
   contract. The filter row collapses to a single column on
   tablet/narrow viewports.
   ============================================================================ */

/* --- Filter row --- */
.cc-history-filters {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1.4fr auto auto;
  gap: 8px;
  margin-bottom: 12px;
  align-items: stretch;
}
.cc-history-filters .cc-history-field {
  display: flex; flex-direction: column; gap: 4px;
  min-width: 0;
}
.cc-history-filters .cc-history-field label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.cc-history-filters .cc-history-field select,
.cc-history-filters .cc-history-field input {
  width: 100%;
  min-height: 44px;
  font-family: var(--font-mono);
  font-size: 12px;
}
.cc-history-filters .cc-btn {
  align-self: end;
  min-width: 88px;
  min-height: 44px;
  font-family: var(--font-mono);
  font-weight: 700;
  letter-spacing: 0.04em;
}

/* Custom date row — only visible when date range is "Custom" */
.cc-history-custom-dates {
  display: flex; align-items: end; gap: 8px;
  margin-bottom: 12px;
  padding: 8px 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.cc-history-custom-dates .cc-history-field {
  flex: 0 0 200px;
  min-width: 0;
}
.cc-history-custom-dates .cc-history-field input[type="date"] {
  width: 100%;
  min-height: 40px;
  font-family: var(--font-mono);
  font-size: 12px;
  color-scheme: dark; /* makes the native date picker use dark theme */
}

/* --- Results table --- */
.cc-history-table-wrap {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: auto;
  max-height: 360px;
}

/* When the CHAT HISTORY panel is in stretch mode (parent of
   .cc-chat-layout uses flex to fill the .cc-main viewport), the
   panel body becomes a flex column. The filter row + custom
   date pickers sit at the top, the table-wrap grows to fill the
   remaining space (scrolling internally), and the pagination
   bar pins to the bottom — mirroring the chat live-stream
   send-row pattern.

   The .cc-history-body class is on the panel body when
   stretched. */
.cc-panel.cc-panel-stretch > .cc-panel-body.cc-history-body {
  display: flex;
  flex-direction: column;
}
.cc-panel.cc-panel-stretch > .cc-panel-body.cc-history-body > .cc-history-table-wrap {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;  /* override the 360px cap used in the static case */
}
.cc-history-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}
.cc-history-table thead th {
  position: sticky; top: 0; z-index: 1;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-align: left;
  padding: 10px 14px;
  background: var(--bg-card);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.cc-history-table tbody td {
  padding: 8px 14px;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
  font-family: var(--font-mono);
}
.cc-history-table tbody tr:last-child td { border-bottom: none; }
.cc-history-table tbody tr:hover { background: rgba(245, 165, 36, 0.04); }
.cc-history-table .col-ts     { color: var(--text-muted); white-space: nowrap; width: 160px; }
.cc-history-table .col-team   { width: 80px; font-weight: 600; }
.cc-history-table .col-team.server { color: var(--amber); }
.cc-history-table .col-team.blue   { color: #7ab0ff; }
.cc-history-table .col-team.red    { color: #ff8585; }
.cc-history-table .col-team.yellow { color: #fbbf24; }
.cc-history-table .col-team.violet { color: #a78bfa; }
.cc-history-table .col-team.global { color: #34d399; }
.cc-history-table .col-team.command { color: #f472b6; }
.cc-history-table .col-player { color: var(--text); width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cc-history-table .col-text   { color: var(--text); word-break: break-word; }
.cc-history-empty {
  padding: 32px 16px;
  text-align: center;
  color: var(--text-dim);
  font-style: italic;
}

/* --- Pagination bar ---
   Mirrors the .cc-chat-send-row style on the live-stream page
   (border-top + padding-top so it visually separates from the
   table above). Pinned to the bottom of the stretched panel
   via the flex column body. */
.cc-history-pagination {
  display: flex; align-items: center; gap: 8px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  flex-wrap: wrap;
}
.cc-history-pagination .cc-pagination-label {
  flex: 1 1 auto;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-muted);
  min-width: 0;
}
.cc-history-pagination .cc-pagination-pagesize {
  display: flex; align-items: center; gap: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
}
.cc-history-pagination .cc-pagination-pagesize select {
  min-height: 36px;
  font-family: var(--font-mono);
  font-size: 12px;
  background: var(--bg-elevated);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0 8px;
}
.cc-history-pagination .cc-btn {
  min-height: 44px;
  min-width: 88px;
  font-family: var(--font-mono);
  font-weight: 700;
  letter-spacing: 0.04em;
}
.cc-history-pagination .cc-btn.cc-btn-export {
  min-width: 120px;
  border-color: rgba(245, 165, 36, 0.4);
  color: var(--amber);
}
.cc-history-pagination .cc-btn.cc-btn-export:hover {
  background: rgba(245, 165, 36, 0.08);
}

/* Filter row collapses to a single column on tablet/narrow */
@media (max-width: 900px) {
  .cc-history-filters {
    grid-template-columns: 1fr 1fr;
  }
  .cc-history-filters .cc-btn { min-width: 0; }
}
@media (max-width: 600px) {
  .cc-history-filters {
    grid-template-columns: 1fr;
  }
  .cc-history-custom-dates {
    flex-direction: column;
    align-items: stretch;
  }
  .cc-history-custom-dates .cc-history-field { flex: 1 1 auto; }
}



/* ============================================================================
   Bans page layout � vertical stack of panels with consistent 16px gap,
   same shape as cc-chat-layout. Each sub-page (Records / Proxy / Firewall)
   uses this wrapper so the page-head + sub-nav + content stack fills the
   viewport. The .cc-panel-stretch modifier lets the record panel grow to
   fill the remaining space (sub-nav takes ~44px, head ~80px, rest goes to
   the panel). Stage 1 only uses the skeleton panel; Stages 2-5 will add
   more panels inside this wrapper.
   ============================================================================ */
.cc-bans-layout {
  display: flex; flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  gap: 16px;
  padding: 16px 0 24px 0;
}
.cc-bans-skeleton-body {
  padding: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.cc-bans-skeleton {
  max-width: 640px;
  text-align: center;
}
.cc-bans-skeleton h2 {
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--amber);
  margin: 0 0 12px 0;
}
.cc-bans-skeleton p {
  margin: 8px 0;
  line-height: 1.5;
  color: var(--text);
}
.cc-bans-skeleton .dim {
  color: var(--text-dim);
  font-size: 12px;
}
.cc-bans-skeleton code {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--amber);
  background: rgba(245, 165, 36, 0.08);
  padding: 1px 6px;
  border-radius: 3px;
}

/* ============================================================================
   BANS — Stage 2: 2x2 read-only record grid.

   Mirrors the WinForms `BansTab` layout where four DataGridViews
   sit in a 2x2 (Banned/Whitelisted × Names/IPs). Each cell is
   a `cc-panel` with its own head + table + empty state.

   The whole grid wrapper uses `display: grid; grid-template-columns:
   1fr 1fr;` so the two columns share the page width. Each panel
   uses `cc-panel-stretch` so the table inside it fills the
   remaining vertical space and scrolls internally — matches the
   chat history / auto / slap pattern.
   ============================================================================ */

.cc-bans-grid-2x2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  flex: 1 1 auto;
  min-height: 0;
}
@media (max-width: 900px) {
  .cc-bans-grid-2x2 { grid-template-columns: 1fr; }
}

.cc-bans-record {
  display: flex; flex-direction: column;
  min-height: 0;
}
.cc-bans-record .cc-bans-table-wrap {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-top: none;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  padding: 4px 0;
}
.cc-bans-record .cc-bans-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}
.cc-bans-record .cc-bans-table thead th {
  position: sticky; top: 0; z-index: 1;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-align: left;
  padding: 10px 14px;
  background: var(--bg-card);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.cc-bans-record .cc-bans-table tbody td {
  padding: 8px 14px;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
  font-family: var(--font-mono);
  word-break: break-word;
}
.cc-bans-record .cc-bans-table tbody tr:last-child td { border-bottom: none; }
.cc-bans-record .cc-bans-table tbody tr:hover { background: rgba(245, 165, 36, 0.04); }
.cc-bans-record .cc-bans-table .col-name { color: var(--text); font-weight: 500; }
.cc-bans-record .cc-bans-table .col-ip   { color: var(--text); font-weight: 500; font-family: var(--font-mono); }
.cc-bans-record .cc-bans-table .col-date { color: var(--text-muted); white-space: nowrap; width: 150px; }
.cc-bans-record .cc-bans-table .col-type { width: 90px; }
.cc-bans-record .cc-bans-table .col-type .cc-pill-perm { color: #fca5a5; }
.cc-bans-record .cc-bans-table .col-type .cc-pill-temp { color: #fbbf24; }
.cc-bans-record .cc-bans-table .col-actions { width: 80px; text-align: right; }
.cc-bans-record .cc-bans-table .col-actions button {
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-dim);
  width: 28px; height: 28px;
  border-radius: 4px;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: color 80ms, background 80ms, border-color 80ms;
}
.cc-bans-record .cc-bans-table .col-actions button:hover {
  color: var(--amber);
  background: rgba(245, 165, 36, 0.08);
  border-color: rgba(245, 165, 36, 0.3);
}
.cc-bans-record .cc-bans-table .col-actions button.cc-bans-row-del:hover {
  color: #f87171;
  background: rgba(220, 38, 38, 0.08);
  border-color: rgba(220, 38, 38, 0.4);
}
.cc-bans-record .cc-bans-empty {
  padding: 32px 16px;
  text-align: center;
  color: var(--text-dim);
  font-style: italic;
  font-size: 12px;
}

/* ============================================================================
   BANS — Stage 3: record editor + delete confirm modals.

   Modals are the standard Command Console pattern (same overlay
   + card + head/body/foot structure as the Admin modal). Sized
   to fit a 1024x768 tablet without scrolling. Fits ~640px wide
   for the editor and ~480px for the delete confirm.
   ============================================================================ */

.cc-modal-overlay {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(0, 0, 0, 0.6);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  backdrop-filter: blur(2px);
}
.cc-modal {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  width: 100%;
  max-width: 640px;
  max-height: calc(100vh - 48px);
  display: flex; flex-direction: column;
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6);
  overflow: hidden;
}
.cc-modal-sm { max-width: 480px; }

.cc-modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 20px;
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--amber);
}
.cc-modal-head .left {
  display: flex; align-items: center; gap: 10px;
}
.cc-modal-head .left svg { width: 20px; height: 20px; flex-shrink: 0; }
.cc-modal-close {
  appearance: none;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-dim);
  width: 32px; height: 32px;
  border-radius: 4px;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: color 80ms, background 80ms;
}
.cc-modal-close:hover {
  color: var(--text);
  background: rgba(255, 255, 255, 0.06);
}
.cc-modal-close svg { width: 16px; height: 16px; }

.cc-modal-body {
  padding: 20px;
  display: flex; flex-direction: column; gap: 14px;
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
}
.cc-modal-foot {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 20px;
  border-top: 1px solid var(--border);
  background: var(--bg-header);
}
.cc-modal-spacer { flex: 1 1 auto; }

/* ---- Modal form fields ---- */
.cc-form-row {
  display: flex; flex-direction: column; gap: 6px;
}
.cc-form-row.disabled { opacity: 0.5; }
.cc-form-row-split {
  display: grid; grid-template-columns: 1fr 140px;
  gap: 12px;
}
/* Equal-width two-column variant. Used for the Start Date / Expire Date
   row in BanRecordEditor. The second column is bumped to 1.15fr so the
   two date inputs line up at the same visual width — without the bump,
   the 84px label button on the right steals width from the expire
   input and the start input ends up noticeably wider. */
.cc-form-row-equal {
  display: grid; grid-template-columns: 1fr 1.15fr;
  gap: 12px;
}
.cc-form-col { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.cc-form-label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  display: flex; align-items: center; gap: 6px;
}
.cc-form-label input[type="checkbox"] { width: 14px; height: 14px; cursor: pointer; }
.cc-form-divider {
  height: 1px;
  background: var(--border);
  margin: 4px 0;
}
.cc-form-radios {
  display: flex; gap: 12px;
  flex-wrap: wrap;
}
.cc-form-radios .cc-radio {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  color: var(--text-muted);
  min-height: 36px;
  user-select: none;
  transition: border-color 80ms, background 80ms, color 80ms;
}
.cc-form-radios .cc-radio:hover { border-color: var(--text-dim); color: var(--text); }
.cc-form-radios .cc-radio.active {
  border-color: var(--amber);
  background: var(--amber-soft);
  color: var(--amber);
}
.cc-form-radios .cc-radio input { accent-color: var(--amber); }
.cc-form-radios .cc-radio input:disabled { cursor: not-allowed; }
.cc-textarea {
  min-height: 80px;
  resize: vertical;
  font-family: var(--font-mono);
  font-size: 12px;
}
.cc-form-error {
  padding: 8px 12px;
  background: rgba(220, 38, 38, 0.08);
  border: 1px solid rgba(220, 38, 38, 0.4);
  border-radius: var(--radius-sm);
  color: #fca5a5;
  font-size: 12px;
  font-family: var(--font-mono);
}

/* ---- Confirm modal ---- */
.cc-confirm-text {
  margin: 0;
  color: var(--text);
  line-height: 1.5;
  font-size: 13px;
}
.cc-confirm-text strong { color: var(--amber); }
.cc-confirm-meta {
  padding: 8px 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 12px;
  color: var(--text);
}
.cc-confirm-meta .dim { color: var(--text-dim); margin-right: 4px; }
.cc-confirm-meta div { margin: 4px 0; }
.cc-confirm-actions {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-top: 8px;
}
.cc-confirm-actions .cc-btn { flex: 1 1 auto; min-width: 100px; }

/* ---- "New" buttons in panel heads ---- */
.cc-bans-new-btn {
  appearance: none;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  color: var(--amber);
  height: 32px;
  padding: 0 12px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  display: inline-flex; align-items: center; gap: 6px;
  transition: background 80ms, border-color 80ms;
}
.cc-bans-new-btn:hover {
  background: var(--amber-soft);
  border-color: var(--border-amber);
}
.cc-bans-new-btn svg { width: 12px; height: 12px; }
.cc-bans-panel-actions {
  display: inline-flex; align-items: center; gap: 6px;
  margin-right: 12px;
}
.cc-btn.cc-btn-danger {
  border-color: rgba(220, 38, 38, 0.4);
  color: #f87171;
}
.cc-btn.cc-btn-danger:hover {
  background: rgba(220, 38, 38, 0.08);
  border-color: rgba(220, 38, 38, 0.6);
}

/* ============================================================================
   BANS — Stage 4: Proxy Check sub-page styles.

   The proxy sub-page has two panels:
     1. Proxy Check Settings (toggle, API key, cache days, action
        matrix, geo mode, service provider)
     2. Geo-block Countries (add row + datagrid)

   Reuses the modal form patterns (.cc-form-row, .cc-form-radios,
   etc.) plus some proxy-specific layout helpers below.
   ============================================================================ */

/* (proxy .cc-toggle override removed — the Enable Proxy Check pill
   now reuses .cc-weapon-pill from the gameplay restrictions, with
   .disabled (off) and .always (on) state classes.) */

.cc-form-row-narrow { max-width: 200px; }
.cc-form-actions {
  display: flex; align-items: center; gap: 8px;
  margin-top: 4px;
}
.cc-form-radios.cc-form-radios-vertical {
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
}
.cc-form-radios.cc-form-radios-vertical .cc-radio {
  width: 100%;
}

.cc-proxy-body {
  display: flex; flex-direction: column; gap: 16px;
  padding: 20px;
}

/* 3-column action matrix (Proxy / VPN / TOR) */
.cc-proxy-action-grid {
  display: grid; grid-template-columns: 1fr 1fr 1fr;
  gap: 12px;
}
@media (max-width: 700px) {
  .cc-proxy-action-grid { grid-template-columns: 1fr; }
}
.cc-proxy-action-col {
  display: flex; flex-direction: column; gap: 6px;
  padding: 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.cc-proxy-action-label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 4px;
}

/* Action per Threat Type (75%) + Service Provider (25%) on the same row.
   The left column hosts the 3-col action matrix; the right column is the
   stacked radio list for the chosen threat-intel service.
   `align-items: stretch` (NOT `start`) so the right Servers box lines up
   in height with the taller 3-option PROXY/VPN/TOR boxes on the left. */
.cc-form-row-action-sp {
  display: grid;
  grid-template-columns: 3fr 1fr;
  gap: 12px;
  align-items: stretch;
}
.cc-proxy-action-wrap { min-width: 0; }
.cc-proxy-sp-wrap { min-width: 0; }
/* The Servers (service provider) box lives alone in the 25% column and
   must stretch in BOTH dimensions:
   - width 100% to fill the 25% column's full width
   - flex 1 to fill the column's height (the column is a flex column
     container, so the main-axis stretch needs explicit flex:1) */
.cc-proxy-sp-col {
  width: 100%;
  flex: 1 1 auto;
}
@media (max-width: 900px) {
  .cc-form-row-action-sp { grid-template-columns: 1fr; }
}

.cc-proxy-test-result {
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.5;
}
.cc-proxy-test-result.ok {
  background: rgba(34, 197, 94, 0.08);
  border: 1px solid rgba(34, 197, 94, 0.3);
  color: #4ade80;
}
.cc-proxy-test-result.err {
  background: rgba(220, 38, 38, 0.08);
  border: 1px solid rgba(220, 38, 38, 0.4);
  color: #fca5a5;
}

.cc-countries-table-wrap {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  max-height: 360px;
  overflow-y: auto;
}
.cc-countries-table-wrap .cc-bans-table {
  font-size: 12px;
}
.cc-countries-table-wrap .cc-bans-table thead th {
  position: sticky; top: 0; z-index: 1;
}

/* ============================================================================
   BANS / PROXY — 2-column layout (Settings + Country list)

   The /bans/proxy sub-page splits the page body into two panels:
     Left  : PROXY CHECK SETTINGS (enable, API key, cache, action matrix,
             geo mode, service provider, save/test/reset) — form-driven,
             intrinsic height.
     Right : GEO-BLOCK COUNTRIES (search + master ISO 3166 list with
             per-row 20% code / 80% name + block/unblock toggle). The
             country list scrolls internally so it never pushes the
             settings panel off-screen.

   Grid template `1.2fr 1.1fr` gives the settings column a bit more
   room (it carries the threat-type 3-up matrix), but the country
   panel is wide enough to read the country name + fit the BLOCK /
   REMOVE button without clipping.

   At ≤1100px the grid stacks to a single column (settings on top,
   country list below) so 1024px tablets still see both panels
   without horizontal scroll.
   ============================================================================ */
.cc-proxy-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.1fr);
  gap: 16px;
  align-items: stretch;
  min-height: 0;
}
@media (max-width: 1100px) {
  .cc-proxy-grid { grid-template-columns: minmax(0, 1fr); }
  /* At narrow widths the two panels stack into 1 column. With
     align-items: stretch (the default) the grid splits the available
     height between the two rows, leaving the country panel with
     only ~230px and the list wrap with ~13px. Let the panels be
     their natural heights instead — the settings panel sizes to its
     ~540px form, the country panel uses its 500-720px range, and
     the layout overflows so the user scrolls the page. The country
     list still scrolls internally (the panel-stretch + max-height
     cap keep the wrap bounded). */
  .cc-proxy-grid { align-items: start; }
}

/* 2-col layout for the Firewall sub-page: FIREWALL + NETLIMITER
   SETTINGS (left) + NETLIMITER LIVE LOG (right). Same breakpoint
   and stacking behavior as the proxy grid — at ≤1100px the two
   panels stack into a single column and each keeps its natural
   height. The log panel is a placeholder today (the live API
   isn't ported yet) but lives in the right column so the layout
   is ready when the log is implemented. */
.cc-firewall-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 3fr);
  gap: 16px;
  align-items: start;
  min-height: 0;
}
@media (max-width: 1100px) {
  .cc-firewall-grid { grid-template-columns: minmax(0, 1fr); }
}

/* Each form row in the Firewall panel stacks vertically (label
   above control). The base .cc-toggle is inline-flex so it
   would hug its content — force it to full width here so the
   OFF/ON pill stretches to fill the (now narrow) panel. */
.cc-form-row > .cc-toggle { width: 100%; }

/* Section header: small ON/OFF pill button + section title on
   the same line. Replaces the old label-above-toggle pattern
   for the 3 toggles on the firewall page (Windows Firewall,
   NetLimiter, Connection Limit). The pill is a clickable
   `<button>` (not a hidden checkbox + label), so the state
   flips on click. ON state uses the amber tint from the
   gameplay restrictions; OFF state is dim gray on the
   elevated surface. */
.cc-form-section-head {
  display: flex; align-items: center; gap: 10px;
  margin: 0;
}
.cc-form-section-title {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text);
}
.cc-toggle-pill {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 56px;
  padding: 4px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--bg-elevated);
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: border-color 120ms, background 120ms, color 120ms;
}
.cc-toggle-pill:hover:not(:disabled) { border-color: var(--text-muted); }
.cc-toggle-pill:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--amber);
}
.cc-toggle-pill.on {
  background: var(--amber-soft);
  border-color: var(--amber);
  color: var(--amber);
}
.cc-toggle-pill.on:hover:not(:disabled) {
  background: rgba(245, 165, 36, 0.18);
}
.cc-toggle-pill:disabled { opacity: 0.45; cursor: not-allowed; }

/* Pill button group embedded in a panel header (right of the
   title). Mirrors the spacing rhythm of the rest of the panel
   head — the existing .cc-panel-head .left is a flex row with
   ~8px gap, so this just adds a left margin so the pills
   visually separate from the title. */
.cc-panel-head-pills {
  display: inline-flex; align-items: center; gap: 6px;
  margin-left: 12px;
}

/* NetLimiter live log table. Sits in the right column of the
   firewall grid and shows the per-IP active-connection state
   that the ban-management ticker writes into
   `State.Bans.NetLimiterConnectionLogs` every ~1s.

   Structure mirrors the existing .cc-bans-table family so the
   visual rhythm matches the rest of the Bans section:
     .cc-netlimiter-log-body  — flex column so the wrap fills
     .cc-netlimiter-log-wrap  — the actual scroll container
     .cc-netlimiter-log       — the table itself
     .col-rowid / .col-ip / .col-cons  — fixed-width mono columns
     .col-vpn / .col-notes              — flexible columns

   The panel is given .cc-panel-stretch so the body fills the
   3fr grid track; the wrap has its own max-height + overflow
   so very long lists scroll inside the panel (not the page). */
.cc-netlimiter-log-body { padding: 0; }
.cc-netlimiter-log-wrap {
  width: 100%;
  max-height: 540px;
  min-height: 0;
  overflow-y: auto;
  overflow-x: auto;
}
.cc-netlimiter-log {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.cc-netlimiter-log thead th {
  position: sticky;
  top: 0;
  background: var(--bg-elevated);
  color: var(--text-muted);
  text-align: left;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 11px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  z-index: 1;
}
.cc-netlimiter-log tbody td {
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  vertical-align: middle;
}
.cc-netlimiter-log tbody tr:hover td { background: var(--bg-hover); }
.cc-netlimiter-log tbody tr.active td.col-cons { color: var(--amber); font-weight: 600; }
.cc-netlimiter-log .col-rowid { width: 56px;  text-align: right; }
.cc-netlimiter-log .col-ip    { width: 140px; }
.cc-netlimiter-log .col-cons  { width: 92px;  text-align: right; }
.cc-netlimiter-log .col-vpn   { width: 110px; }
.cc-netlimiter-log .col-notes { /* flexible — takes the rest */ }
@media (max-width: 1100px) {
  .cc-netlimiter-log-wrap { max-height: 360px; }
}

/* Filter Name row on the firewall page. Combines a text input
   (typed value) with a <datalist> (auto-suggest from the bridge's
   filter list) and a refresh button. The datalist is the
   lightweight native HTML way to get a dropdown + free-text input
   in a single control — the user can pick from the list or type
   any custom name. */
.cc-filter-input-row {
  display: flex; align-items: stretch; gap: 8px;
  width: 100%;
}
.cc-filter-input-row .cc-input { flex: 1 1 auto; }
.cc-filter-input-row .cc-btn-icon {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px;
  padding: 0 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  color: var(--text-muted);
  border-radius: 6px;
  cursor: pointer;
  transition: border-color 120ms, color 120ms, background 120ms;
}
.cc-filter-input-row .cc-btn-icon svg { width: 18px; height: 18px; }
.cc-filter-input-row .cc-btn-icon:hover:not(:disabled) {
  border-color: var(--amber);
  color: var(--amber);
  background: var(--amber-soft);
}
.cc-filter-input-row .cc-btn-icon:disabled { opacity: 0.45; cursor: not-allowed; }
.cc-filter-input-row .cc-btn-icon:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--amber);
}
/* Inline hint icons (re-uses the existing 14x14 svg used in
   .cc-form-hint). The error message sits inline in the hint text. */
.cc-hint-icon { width: 14px; height: 14px; display: inline-block; vertical-align: -2px; }
.cc-hint-error { color: var(--amber); margin-left: 6px; }

/* Combobox — used for the Filter Name field on BansFirewall.razor.
   Wraps a text input + clear / toggle / refresh buttons in a single
   flex row, then a dropdown panel below shows the available filters
   (populated by RefreshFilters from the NetLimiter bridge).

   The native <datalist> only shows suggestions after typing and is
   invisible at rest — bad for touch. This combobox shows the full
   list on focus, supports keyboard nav (arrow up/down wraps around,
   enter accepts the highlighted item, escape closes), and includes
   a checkmark next to the currently-selected value.

   The panel is absolutely positioned so it floats over the content
   below instead of pushing the form down. The container is
   `position: relative` to anchor the panel. */
.cc-combobox {
  position: relative;
  width: 100%;
}
.cc-combobox .cc-combobox-row {
  display: flex;
  align-items: stretch;
  gap: 8px;
  width: 100%;
}
.cc-combobox .cc-combobox-row .cc-input {
  flex: 1 1 auto;
  min-width: 0;
}
/* The clear / toggle / refresh buttons all share the same .cc-btn-icon
   base. Slight variant: a clear button that only shows its X when
   there is text, and a chevron that rotates 180° when the panel is
   open. */
.cc-combobox .cc-btn-icon {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px;
  padding: 0 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  color: var(--text-muted);
  border-radius: 6px;
  cursor: pointer;
  transition: border-color 120ms, color 120ms, background 120ms, opacity 120ms;
}
.cc-combobox .cc-btn-icon svg { width: 18px; height: 18px; }
.cc-combobox .cc-btn-icon:hover:not(:disabled) {
  border-color: var(--amber);
  color: var(--amber);
  background: var(--amber-soft);
}
.cc-combobox .cc-btn-icon:disabled { opacity: 0.45; cursor: not-allowed; }
.cc-combobox .cc-btn-icon:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--amber);
}
/* Chevron rotates when the panel is open, giving a clear visual cue
   that the dropdown is currently active. */
.cc-combobox .cc-combobox-chevron {
  transition: transform 160ms ease-out;
}
.cc-combobox.open .cc-combobox-chevron { transform: rotate(180deg); }
/* Highlight the input when the dropdown is open so the user sees
   that they're in "list mode". */
.cc-combobox.open .cc-input {
  border-color: var(--amber);
  box-shadow: 0 0 0 1px var(--amber);
}

/* The dropdown panel — absolute so it floats over the form below.
   4px offset from the input row for visual separation, 1px border
   + box-shadow so it reads as a popup. Max-height caps the list to
   ~7 items and lets the rest scroll — at this height the user can
   see the current selection and a few options above/below.
   Background is the panel surface (--bg-panel) so content below
   doesn't bleed through — must be a solid color, not transparent. */
.cc-combobox-panel {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 50;
  background-color: var(--bg-panel);
  background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.02), transparent);
  border: 1px solid var(--amber);
  border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.55);
  max-height: 320px;
  overflow-y: auto;
}
.cc-combobox-list {
  list-style: none;
  margin: 0;
  padding: 4px 0;
}
.cc-combobox-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 14px;
  cursor: pointer;
  color: var(--text);
  font-size: 14px;
  user-select: none;
  background-color: var(--bg-panel);
  border-left: 3px solid transparent;
  transition: background-color 80ms, border-color 80ms, color 80ms;
}
.cc-combobox-item:hover,
.cc-combobox-item.highlighted {
  background-color: var(--amber-soft);
  border-left-color: var(--amber);
  color: var(--amber);
}
.cc-combobox-item-name {
  flex: 1 1 auto;
  min-width: 0;
  /* Truncate long filter names with an ellipsis instead of wrapping */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cc-combobox-match {
  color: var(--amber);
  font-weight: 600;
}
.cc-combobox-item-check {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  color: var(--amber);
}
.cc-combobox-empty {
  padding: 14px;
  background-color: var(--bg-panel);
  color: var(--text-muted);
  font-size: 13px;
  text-align: center;
  font-style: italic;
}
.cc-combobox-empty strong {
  color: var(--text);
  font-style: normal;
  font-weight: 600;
}

/* Top row of the proxy settings form: [Enable pill] [API Key] [Cache Days]
   side-by-side. All three columns share the same structure (label +
   control) so they line up perfectly at the bottom. The Enable pill
   reuses .cc-weapon-pill .disabled/.always from the gameplay
   restrictions (48px tall). The inputs in this row are bumped to
   48px min-height (from the default 36px) to match the pill, so the
   label + control stacks are all 66px tall and align at the bottom
   via align-items: end. */
.cc-form-row-proxy-top {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) 130px;
  gap: 12px;
  align-items: end;
}
.cc-proxy-enable-wrap,
.cc-proxy-api-key-wrap,
.cc-proxy-cache-days-wrap {
  display: flex; flex-direction: column; gap: 6px;
  min-width: 0;
}
.cc-proxy-enable-wrap .cc-form-label,
.cc-proxy-api-key-wrap .cc-form-label,
.cc-proxy-cache-days-wrap .cc-form-label { margin: 0; }
.cc-proxy-enable-pill {
  width: auto;
  height: var(--touch-comfort);   /* 56px — match the .cc-input height in the same row */
  min-height: var(--touch-comfort);
  padding: 0 18px;
}
.cc-proxy-api-key,
.cc-proxy-cache-days {
  width: 100%;
  min-height: 48px;   /* match the .cc-weapon-pill 48px so all three columns line up */
}
.cc-proxy-cache-days { text-align: center; }

/* The country panel carries a ~250-row master list. Without a cap it
   sizes to the row content and pushes the grid row (and the sibling
   settings panel via align-items: stretch) to ~9000px tall, which
   breaks the flex chain on .cc-country-list-wrap and stops the list
   from scrolling internally. Cap the panel to viewport - chrome
   (page head + subnav + panel head + padding) so the country list
   always scrolls inside the panel and the settings panel has a
   matching height to stretch into.

   The cap stays at all viewport widths (including ≤1100px when the
   grid stacks to 1-col). The user scrolls the page to see the panel,
   then scrolls the list inside the panel. Without the cap, the
   panel grows to 9000px and the list is part of the page scroll,
   which is harder to use on touch + makes the settings panel hard
   to reach. */
.cc-country-panel {
  max-height: calc(100vh - 200px);
  min-height: 500px;   /* at narrow viewports the grid cell splits the
                          available height between the two stacked
                          panels and the country panel can end up
                          with only ~230px; force a usable height so
                          the list has room to scroll */
}

.cc-proxy-body.cc-proxy-body-stretch {
  /* Make the body fill the remaining height in the right column so the
     country list grows to fill the viewport. The country list itself
     is `flex: 1 1 auto; min-height: 0` so it scrolls internally. */
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

.cc-form-row-meta {
  color: var(--text-dim);
  font-size: 11px;
  padding: 0 0 4px 0;
}

.cc-country-list-wrap {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  display: flex;
  flex-direction: column;
}

.cc-country-row {
  display: flex;
  align-items: center;
  gap: 0;
  padding: 0;
  border-bottom: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: 12px;
  transition: background 80ms;
  min-height: 36px;
}
.cc-country-row:last-child { border-bottom: none; }
.cc-country-row:hover { background: rgba(245, 165, 36, 0.04); }
.cc-country-row.blocked {
  background: rgba(245, 165, 36, 0.10);
}
.cc-country-row.blocked:hover {
  background: rgba(245, 165, 36, 0.16);
}

/* 20% code / 80% name — code is fixed at 20% of the row width
   (with a sensible floor) so it lines up across rows; name takes
   the remaining space. */
.cc-country-code {
  flex: 0 0 20%;
  min-width: 56px;
  padding: 8px 10px 8px 12px;
  font-weight: 700;
  color: var(--amber);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.cc-country-row:not(.blocked) .cc-country-code { color: var(--text); }

.cc-country-name {
  flex: 1 1 auto;
  min-width: 0;
  padding: 8px 10px 8px 0;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cc-country-toggle {
  flex: 0 0 auto;
  appearance: none;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  padding: 6px 12px;
  margin-right: 8px;
  min-height: 28px;
  min-width: 96px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: color 80ms, background 80ms, border-color 80ms;
}
.cc-country-toggle.add:hover {
  color: var(--amber);
  background: rgba(245, 165, 36, 0.10);
  border-color: rgba(245, 165, 36, 0.4);
}
.cc-country-toggle.add:active {
  background: rgba(245, 165, 36, 0.18);
}
.cc-country-toggle.remove {
  color: #f87171;
  border-color: rgba(220, 38, 38, 0.3);
  background: rgba(220, 38, 38, 0.06);
}
.cc-country-toggle.remove:hover {
  color: #fca5a5;
  background: rgba(220, 38, 38, 0.12);
  border-color: rgba(220, 38, 38, 0.5);
}
.cc-country-toggle:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ---- Form hint (used on proxy + firewall pages) ---- */
.cc-form-hint {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 8px 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  font-size: 12px;
  line-height: 1.5;
}
.cc-form-hint svg { width: 14px; height: 14px; flex-shrink: 0; margin-top: 2px; }
.cc-form-hint code {
  font-family: var(--font-mono);
  font-size: 11px;
  background: rgba(245, 165, 36, 0.08);
  color: var(--amber);
  padding: 1px 5px;
  border-radius: 3px;
}

/* ============================================================================
   STATS — 2x2 CRUD grid (Babstats top, Lobby bottom)

   The grid is a flex item that fills the available vertical space
   between the page head and the <StatsSubNav />. Its 2 rows share
   the height equally (1fr 1fr). The form column has a min-width
   guard for readability (labels + inputs) and the table column gets
   the remaining 2/3 of the width.

   Each form panel is a flex column with `min-height: 0` so its
   `cc-stats-form-body` can scroll internally when the form is
   taller than the row (e.g. short viewports). The form's internal
   spacing is dense (8px row gap, 32px input min-height) to fit
   the 5-6 fields + button row in ~280px.

   On narrow viewports the grid collapses to a single column with
   the form stacked above its table.
   ============================================================================ */
.cc-stats-grid {
  display: grid;
  grid-template-columns: minmax(320px, 1fr) 2fr;
  grid-template-rows: minmax(360px, auto) minmax(360px, auto);
  gap: 16px;
  margin-bottom: 20px;
}
@media (max-width: 900px) {
  .cc-stats-grid {
    grid-template-columns: 1fr;
    grid-template-rows: auto;
  }
}

.cc-stats-form {
  display: flex; flex-direction: column;
  min-height: 0;
}
.cc-stats-form-body {
  display: flex; flex-direction: column; gap: 6px;
  padding: 10px 12px;
}

.cc-stats-form-hint {
  display: flex; align-items: flex-start; gap: 8px;
  margin: 0;
  padding: 6px 10px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-dim);
  font-size: 11px;
  line-height: 1.4;
}
.cc-stats-form-hint svg { width: 12px; height: 12px; flex-shrink: 0; margin-top: 2px; }

.cc-stats-form-row {
  display: flex; flex-direction: column; gap: 2px;
}
.cc-stats-form-row label {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  line-height: 1.2;
}
.cc-stats-form-row .cc-input {
  min-height: 30px;
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 3px 8px;
}
.cc-stats-form-row-split {
  display: grid; grid-template-columns: 1fr 90px;
  gap: 6px;
}
.cc-stats-form-row-toggles {
  display: flex; gap: 6px; flex-wrap: wrap;
}
.cc-stats-form-row-toggles .cc-toggle {
  flex: 1 1 0;
  min-width: 0;
  padding: 3px 8px;
  min-height: 30px;
  font-size: 10px;
  gap: 4px;
}
.cc-stats-form-row-toggles .cc-toggle label {
  font-size: 9px;
  letter-spacing: 0.04em;
  white-space: nowrap;
  text-transform: uppercase;
}

.cc-stats-form-buttons {
  display: flex; gap: 6px; flex-wrap: wrap;
  justify-content: flex-end;  /* float right */
  margin-top: 2px;
}
.cc-stats-form-buttons .cc-btn {
  flex: 0 0 90px;             /* fixed width so VALIDATE / NEW / ADD
                                 (and SAVE / REMOVE in edit mode) are
                                 all the same size */
  min-height: 30px; font-size: 10px; padding: 0 10px; letter-spacing: 0.04em;
}

/* Section dividers inside the stats forms. Small uppercase mono
   caption with a thin border-bottom to group related fields
   (Connection / Behavior / Timing for Babstats, Identity / Network
   / Security for Lobby). Visually breaks up the form without adding
   heavy chrome. */
.cc-stats-form-section {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
  padding: 8px 0 2px;
  margin-top: 4px;
  border-bottom: 1px solid var(--border-soft);
}
.cc-stats-form-section:first-child {
  /* No top spacing for the very first section in a form — the
     panel-body already has its own padding. */
  margin-top: 0;
  padding-top: 0;
  border-bottom: none;
}

/* Split-row variant where the right cell is a toggle pill instead
   of a number field. Used in the Lobby form (Game Port + Enabled).
   Keeps the existing 1fr / 90px grid columns. */
.cc-stats-form-row-toggle {
  justify-content: flex-end;
}
.cc-stats-form-row-toggle .cc-weapon-pill {
  width: 90px;
  min-height: 30px;
  font-size: 10px;
  letter-spacing: 0.06em;
}

/* 2x2 settings grid: packs BEHAVIOR (toggles) and TIMING (number
   fields) side by side as a 2-row, 2-col block. Section label lives
   in the left column (right-aligned, mono caption), controls live
   in the right column as a 2-col sub-grid of pills + number fields.

   Layout:
   | BEHAVIOR   | [Enabled]  [Announce]  |
   | TIMING (s) | [Update ]  [Report  ]  |

   Used in the Babstats form to keep the four small controls compact
   instead of stacking them as 4 separate rows. The horizontal rule
   above + below visually groups the block as one section. */
.cc-stats-form-2x2 {
  display: grid;
  grid-template-columns: 88px 1fr;
  grid-template-rows: auto auto;
  gap: 8px 10px;
  align-items: stretch;
  padding: 10px 0;
  border-top: 1px solid var(--border-soft);
  border-bottom: 1px solid var(--border-soft);
  margin: 4px 0 2px;
}
.cc-stats-form-2x2-label {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-dim);
  padding-right: 8px;
  border-right: 1px solid var(--border-soft);
  text-align: right;
  line-height: 1.2;
  min-height: 30px;
}
.cc-stats-form-2x2-cells {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
  align-items: end;          /* align pill to the field's bottom edge
                                 (the .cc-stats-form-row is taller than
                                 the pill because of the label) */
}
/* Behavior row: pills are direct children (no .cc-stats-form-row
   wrapper) — fill the cell width. */
.cc-stats-form-2x2-cells > .cc-weapon-pill {
  width: 100%;
  min-height: 30px;
  font-size: 10px;
  letter-spacing: 0.06em;
  padding: 0 6px;
}
/* Timing row: .cc-stats-form-row is the cell child (label + field
   stacked vertically). Tighten the inner label + field heights so
   the row matches the pill row's 30px min-height. */
.cc-stats-form-2x2-cells > .cc-stats-form-row { gap: 1px; }
.cc-stats-form-2x2-cells > .cc-stats-form-row label {
  font-size: 8px;
  letter-spacing: 0.06em;
}
.cc-stats-form-2x2-cells > .cc-stats-form-row .cc-number-field {
  min-height: 30px;
  height: 30px;
  width: 100%;
  max-width: none;
  font-size: 12px;
}
.cc-stats-form-2x2-cells > .cc-stats-form-row .cc-number-field .cc-input {
  font-size: 12px;
  min-height: 30px;
}
.cc-stats-form-2x2-cells > .cc-stats-form-row .cc-stepper-btn {
  width: 24px;
  height: 30px;
  font-size: 12px;
}

/* ---- Table panel (Babstats / Lobby) ---- */
.cc-stats-table {
  display: flex; flex-direction: column;
  min-height: 0;
}
.cc-stats-table-wrap {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-top: none;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
}
.cc-stats-table-empty {
  padding: 24px 16px;
  text-align: center;
  color: var(--text-dim);
  font-style: italic;
  font-size: 12px;
}
.cc-stats-mini-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}
.cc-stats-mini-table thead th {
  position: sticky; top: 0; z-index: 1;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-align: left;
  padding: 10px 14px;
  background: var(--bg-card);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.cc-stats-mini-table tbody td {
  padding: 8px 14px;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
  font-family: var(--font-mono);
  word-break: break-word;
}
.cc-stats-mini-table tbody tr:last-child td { border-bottom: none; }
.cc-stats-mini-table tbody tr {
  cursor: pointer;
  transition: background 80ms;
}
.cc-stats-mini-table tbody tr:hover { background: rgba(245, 165, 36, 0.04); }
.cc-stats-mini-table tbody tr.selected { background: rgba(245, 165, 36, 0.08); box-shadow: inset 3px 0 0 var(--amber); }
.cc-stats-mini-table .col-name { color: var(--text); font-weight: 500; }
.cc-stats-mini-table .col-url  { color: var(--text); }
.cc-stats-mini-table .col-num  { text-align: right; color: var(--text-muted); width: 80px; }
.cc-stats-mini-table .col-flag { width: 60px; }
.cc-stats-mini-table .col-flag .on  { color: var(--green); font-weight: 700; }
.cc-stats-mini-table .col-flag .off { color: var(--text-dim); }
.cc-stats-mini-table .col-act  { width: 36px; text-align: right; }
.cc-stats-mini-table .col-act .cc-bans-row-del {
  width: 24px; height: 24px;
  margin-right: 4px;
}

/* ============================================================================
   STATS — live section (Player Stats / Weapon Stats / Stats Log tabs)

   Same pattern as .cc-chat-layout + .cc-bans-layout. The .cc-stats-live
   wrapper sits below the 3-tab <StatsSubNav /> and the 2x2 CRUD grid,
   takes the remaining viewport height, and houses a single full-width
   panel (the active tab's content).
   ============================================================================ */
.cc-stats-live {
  display: flex; flex-direction: column;
  flex: 0 0 auto;
  min-height: 0;
  gap: 12px;
  padding-bottom: 24px;
}
.cc-stats-live-body {
  display: flex; flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-top: none;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  padding: 0;
}

/* ---- Player stats table (25 cols, mono body, sticky header) ---- */
.cc-playerstats-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 11px;
  font-family: var(--font-mono);
}
.cc-playerstats-table thead th {
  position: sticky; top: 0; z-index: 1;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-align: right;
  padding: 8px 10px;
  background: var(--bg-card);
  border-bottom: 1px solid var(--border);
  border-right: 1px solid var(--border);
  white-space: nowrap;
}
.cc-playerstats-table thead th.col-name { text-align: left; }
.cc-playerstats-table thead th:last-child { border-right: none; }
.cc-playerstats-table tbody td {
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
  border-right: 1px solid var(--border);
  text-align: right;
  color: var(--text-muted);
  white-space: nowrap;
}
.cc-playerstats-table tbody td:last-child { border-right: none; }
.cc-playerstats-table tbody td.col-name { text-align: left; color: var(--text); font-weight: 500; }
.cc-playerstats-table tbody tr:last-child td { border-bottom: none; }
.cc-playerstats-table tbody tr:hover { background: rgba(245, 165, 36, 0.04); }
.cc-playerstats-table tbody tr.active-row { background: rgba(34, 197, 94, 0.08); }

/* ---- Weapon stats table (5 cols) ---- */
.cc-weaponstats-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
  font-family: var(--font-mono);
}
.cc-weaponstats-table thead th {
  position: sticky; top: 0; z-index: 1;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-align: left;
  padding: 10px 14px;
  background: var(--bg-card);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.cc-weaponstats-table thead th.col-num { text-align: right; }
.cc-weaponstats-table tbody td {
  padding: 6px 14px;
  border-bottom: 1px solid var(--border);
  color: var(--text-muted);
}
.cc-weaponstats-table tbody td.col-name { color: var(--text); font-weight: 500; }
.cc-weaponstats-table tbody td.col-num  { text-align: right; }
.cc-weaponstats-table tbody tr:last-child td { border-bottom: none; }
.cc-weaponstats-table tbody tr:hover { background: rgba(245, 165, 36, 0.04); }

/* ---- Stats log table (2 cols) ---- */
.cc-statslog-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
  font-family: var(--font-mono);
}
.cc-statslog-table thead th {
  position: sticky; top: 0; z-index: 1;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  text-align: left;
  padding: 10px 14px;
  background: var(--bg-card);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.cc-statslog-table thead th.col-ts { width: 180px; }
.cc-statslog-table tbody td {
  padding: 6px 14px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
  vertical-align: top;
  word-break: break-word;
}
.cc-statslog-table tbody td.col-ts { color: var(--text-muted); white-space: nowrap; }
.cc-statslog-table tbody tr:last-child td { border-bottom: none; }
.cc-statslog-table tbody tr:hover { background: rgba(245, 165, 36, 0.04); }

.cc-stats-empty {
  padding: 32px 16px;
  text-align: center;
  color: var(--text-dim);
  font-style: italic;
  font-size: 12px;
}

/* ============================================================================
   Remote auth gate (Login page + auth wall)
   ============================================================================ */

/* Full-bleed dark backdrop for the login page. Hides the main app
   chrome (header / left nav / footer) so the user sees nothing but
   the login card. */
.cc-login-page {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  /* Subtle radial so the card has something to sit on instead of
     looking like a flat rectangle. */
  background-image: radial-gradient(
    ellipse at top,
    rgba(245, 165, 36, 0.05) 0%,
    transparent 60%
  );
}
.cc-login-card {
  width: 100%;
  max-width: 380px;
  padding: 32px 32px 24px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.cc-login-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-sans);
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--text-strong);
}
.cc-login-brand .dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--amber);
  box-shadow: 0 0 12px var(--amber);
}
.cc-login-sub {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  color: var(--text-muted);
  text-transform: uppercase;
  margin-top: -8px;
  margin-bottom: 8px;
}
.cc-login-card .cc-form-row { gap: 8px; }
.cc-login-card .cc-form-label { color: var(--amber); }
.cc-login-card .cc-input { padding: 14px 16px; }
.cc-login-error {
  padding: 10px 12px;
  background: rgba(239, 68, 68, 0.10);
  border: 1px solid rgba(239, 68, 68, 0.40);
  color: var(--red);
  font-size: 13px;
  border-radius: 4px;
  font-family: var(--font-mono);
}
.cc-login-submit {
  width: 100%;
  height: 48px;
  margin-top: 8px;
  font-size: 13px;
  letter-spacing: 0.12em;
}
.cc-login-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 8px;
  font-size: 11px;
  color: var(--text-dim);
  font-family: var(--font-mono);
}
.cc-login-footer code {
  color: var(--text-muted);
  background: var(--bg-elevated);
  padding: 1px 6px;
  border-radius: 3px;
}
.cc-version-pill {
  color: var(--amber);
  font-weight: 600;
}

/* Auth wall — the panel that RemoteAuthGate renders instead of
   the page body when a remote user isn't logged in. Sits inside
   the normal main area (under the header) so the user can still
   see the brand strip and confirm the server is up. */
.cc-remote-auth-wall {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
  padding: 24px;
}
.cc-remote-auth-card {
  max-width: 420px;
  padding: 32px;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}
.cc-remote-auth-card svg {
  width: 48px;
  height: 48px;
  color: var(--amber);
}
.cc-remote-auth-title {
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--amber);
}
.cc-remote-auth-msg {
  color: var(--text-muted);
  font-size: 13px;
  line-height: 1.5;
}
.cc-remote-auth-msg code {
  background: var(--bg-elevated);
  color: var(--text);
  padding: 1px 6px;
  border-radius: 3px;
  font-family: var(--font-mono);
  font-size: 12px;
}
.cc-remote-auth-card .cc-btn {
  /* Wider than the default button so the LOGIN text reads as
     the centrepiece of the card instead of a tiny corner link.
     240px is wide enough for "LOGIN" + the chevron icon with
     comfortable padding on each side, even at the design
     system's small text size. */
  min-width: 240px;
  height: 44px;
  /* Belt-and-suspenders: the base .cc-btn is inline-flex which
     centres its flex children, but if a future change drops
     that (or if the button's only child is a non-flex text
     node), text-align: center keeps the label centred
     regardless. */
  text-align: center;
}
.cc-remote-auth-card .cc-btn svg {
  width: 16px;
  height: 16px;
}
.cc-remote-auth-hint {
  font-size: 11px;
  color: var(--text-dim);
  font-style: italic;
}

/* Header auth buttons (LOGIN / LOGOUT) — primary-button
   style with an amber fill, dark text, and a visible label.
   Same colour treatment as the viewer's CONNECT button so
   the auth chrome reads as "the same kind of action button"
   across both surfaces. Both buttons share the same shape
   so the top-right slot looks consistent whether the user
   is signing in (login) or signing out (logout). */
.cc-header-logout,
.cc-header-login {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 36px;
  padding: 0 16px;
  margin-left: 4px;
  border: 1px solid #b45309;            /* darker amber border, anchors the fill */
  border-radius: var(--radius);
  background: var(--amber);               /* #f59e0b */
  color: #1a1208;                        /* near-black, sits on amber */
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 120ms, border-color 120ms, transform 80ms;
}
.cc-header-logout:hover,
.cc-header-login:hover {
  background: #fbbf24;                    /* lighter amber on hover */
  border-color: #92400e;
}
.cc-header-logout:active,
.cc-header-login:active {
  transform: translateY(1px);             /* press feedback */
}
.cc-header-logout svg,
.cc-header-login svg {
  width: 14px;
  height: 14px;
}

/* Link code pill — the 6-digit one-time code the admin types
   in the game chat (!link 123456) to bind their in-game player
   slot to their admin session. Lives between the username pill
   and the logout button in the header. Uses the same .cc-pill
   shape but with a "LABEL value" composition plus an inline
   ↻ button for regeneration. */
.cc-link-code {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;                              /* matches pill baseline */
  padding: 0 10px;
  margin-left: 4px;
  border-radius: var(--radius);
  border: 1px solid var(--border-amber);
  background: var(--amber-soft);
  color: var(--amber);
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  user-select: text;                         /* code must be copyable-ish; the
                                               user said paste is blocked in
                                               the game so no copy button */
}
.cc-link-code .dot {
  width: 6px;
  height: 6px;
  background: var(--amber);
  border-radius: 50%;
  box-shadow: 0 0 6px var(--amber-glow);
}
.cc-link-code-label {
  color: var(--amber);
  opacity: 0.75;
  letter-spacing: 0.12em;
}
.cc-link-code-value {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.18em;                    /* 6 digits with breathing room */
  color: #fef3c7;                            /* brighter amber, makes the
                                               code itself the visual focus */
  text-transform: none;
}
.cc-link-code-regen {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  margin-left: 2px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: var(--amber);
  cursor: pointer;
  transition: background 100ms, border-color 100ms;
}
.cc-link-code-regen:hover {
  background: rgba(245, 165, 36, 0.15);
  border-color: var(--border-amber);
}
.cc-link-code-regen:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.cc-link-code-regen svg {
  width: 12px;
  height: 12px;
}
/* Collapsed-rail grid — applied to .cc-app when the sidebar
   is hidden (remote visitor, not yet authenticated). Without
   this the 2-column grid would leave a blank left column
   where the rail used to be. */
.cc-app.cc-app-no-rail {
  grid-template-columns: 1fr;
}
