/* =============================================================================
 * Kingshot Web — app.css
 * Quiet, minimal, data-first.
 *
 * Organization:
 *   1. Tokens        — design system vars
 *   2. Reset + base  — typography, elements
 *   3. Utilities     — text helpers, spacing
 *   4. Layout        — app shells (map / page)
 *   5. Primitives    — buttons, inputs, cards, tags, tables, alerts
 *   6. Sidebar       — rail sections, filters, scanner, legend
 *   7. Map page      — Leaflet skin, markers, popups, panels
 *   8. Live updates  — panel, feed, indicator
 *   9. Alliances     — overview page
 *  10. Scan history  — session cards
 *  11. Settings      — form, accounts
 *  12. Responsive    — tiny viewport tweaks
 * ========================================================================== */

/* ---------- 1. Tokens ---------------------------------------------------- */

@import url("https://fonts.bunny.net/css?family=geist:400,500,600,700|geist-mono:400,500");

:root {
  /* Surface */
  --bg-0:        #0c0e12;
  --bg-1:        #12151b;
  --bg-2:        #171b22;
  --bg-3:        #1e232c;

  /* Text */
  --text-0:      #ece8df;
  --text-1:      #c4c0b6;
  --text-2:      #8b8d94;
  /* WCAG AA fix 2026-07-16: #61646c only hit 2.66:1 on --bg-3 (needs
     4.5:1 for normal text) -- lightened while keeping the same cool
     blue-grey undertone (r<g<b ratio preserved). Now >=4.62:1 on every
     --bg-N. */
  --text-3:      #878b96;

  /* Lines */
  --line:        rgba(255, 255, 255, 0.06);
  --line-strong: rgba(255, 255, 255, 0.12);

  /* Accents — one amber. */
  --accent:      #d4a85a;
  --accent-soft: rgba(212, 168, 90, 0.14);
  --accent-dim:  #9e7e3d;
  --ok:          #6fa37a;
  --green:       #6fa37a;
  --warn:        #d49a58;
  --danger:      #c26a5a;
  --shield:      #6ea8c8;

  /* Typography */
  --ff-sans:     "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --ff-mono:     "Geist Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* Scale (4px base) */
  --sp-1:  4px;
  --sp-2:  8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-8: 32px;

  /* Radii */
  --r-1: 3px;
  --r-2: 5px;
  --r-3: 8px;

  /* Transitions */
  --tr-fast: 80ms ease-out;
  --tr-base: 140ms ease-out;

  /* Geometry */
  --rail-left-w:  280px;
  --nav-h:        48px;

  /* Glass (v2 layout) */
  --glass-bg:          rgba(18, 21, 27, 0.78);
  --glass-bg-opaque:   rgba(18, 21, 27, 0.96);
  --glass-border:      rgba(255, 255, 255, 0.09);
  --glass-border-hi:   rgba(255, 255, 255, 0.18);
  --glass-shadow:      0 8px 32px rgba(0, 0, 0, 0.45), 0 2px 8px rgba(0, 0, 0, 0.3);
  --glass-shadow-hi:   0 12px 40px rgba(0, 0, 0, 0.55), 0 4px 12px rgba(0, 0, 0, 0.4);
  --topbar-h:          44px;
  --card-header-h:     32px;
  --card-radius:       8px;
  --z-map:             0;
  --z-card:            100;
  --z-card-focus:      200;
  --z-topbar:          300;
  --z-sheet:           400;
  --z-card-max:        500;
}

/* ---------- 2. Reset + base --------------------------------------------- */

*,
*::before,
*::after { box-sizing: border-box; }

html, body { margin: 0; padding: 0; height: 100%; }

body {
  font-family: var(--ff-sans);
  font-size: 13px;
  line-height: 1.45;
  color: var(--text-0);
  background: var(--bg-0);
  -webkit-font-smoothing: antialiased;
  font-variant-numeric: tabular-nums;
}

h1, h2, h3, h4 {
  margin: 0;
  font-weight: 600;
  letter-spacing: -0.01em;
}

a {
  color: var(--text-1);
  text-decoration: none;
  transition: color var(--tr-fast);
}
a:hover { color: var(--text-0); }

em, i { font-style: normal; }

button, input, select, textarea { font: inherit; color: inherit; }

button { cursor: pointer; border: 0; background: none; }

input, select, textarea { min-width: 0; }

::selection { background: var(--accent); color: var(--bg-0); }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: 5px;
  border: 2px solid var(--bg-0);
}
::-webkit-scrollbar-thumb:hover { background: var(--text-3); }

/* ---------- 3. Utilities ------------------------------------------------- */

.text-dim   { color: var(--text-2); }
.text-muted { color: var(--text-2); }
.text-sm    { font-size: 11px; }
.text-mono  { font-family: var(--ff-mono); }

.mt-1 { margin-top: var(--sp-1); }
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-6); }
.mb-2 { margin-bottom: var(--sp-2); }
.mb-3 { margin-bottom: var(--sp-3); }

.separator { height: 1px; background: var(--line); }

/* ---------- 4. Layout ---------------------------------------------------- */

body.app-map {
  display: grid;
  grid-template-columns: 1fr var(--rail-left-w);
  grid-template-areas: "map sidebar";
  overflow: hidden;
}
/* v2 (glass-on-map) — full-bleed map, no legacy sidebar column */
body.app-map.app-v2 {
  display: block;
}
body.app-map.app-v2 #map {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
}

/* Full-viewport backdrop image, visible in the corners outside the diamond
   game-bounds shape. The diamond itself is filled back to the original
   dark --bg-0 color by a Leaflet polygon in leaflet-base.js (custom
   'mapFillPane', z-index 150 — below the grid canvas at 250 and the
   default overlayPane at 400, so grid lines and markers stay unaffected).
   ManifestStaticFilesStorage rewrites this url() to the content-hashed
   filename at collectstatic time, same as any other collected asset. */
#map {
  grid-area: map;
  height: 100vh;
  position: relative;
  background-color: var(--bg-0);
  background-image: url("../images/background.8ee0e7130543.png");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

#sidebar {
  grid-area: sidebar;
  height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg-1);
  border-left: 1px solid var(--line);
  overflow: hidden;
}

body.app-page {
  background: var(--bg-0);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.page-nav {
  height: var(--nav-h);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 0 var(--sp-5);
  background: var(--bg-1);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 10;
}
.page-nav-logo {
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-0);
}
.page-nav-logo em {
  color: var(--accent);
  font-weight: 500;
}
.page-nav-sep { color: var(--text-3); }
.page-nav-title { color: var(--text-1); font-weight: 500; }
.page-nav-back {
  margin-left: auto;
  font-size: 12px;
  color: var(--text-2);
}

.page-main {
  flex: 1;
  padding: var(--sp-8) var(--sp-5);
}
.page-container {
  max-width: 960px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}
.page-container--wide { max-width: 1200px; }

/* ---------- 5. Primitives ------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: 7px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-2);
  background: var(--bg-2);
  color: var(--text-0);
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  transition: background var(--tr-fast), border-color var(--tr-fast), color var(--tr-fast);
}
.btn:hover:not(:disabled) { background: var(--bg-3); border-color: var(--text-3); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg-0);
  font-weight: 600;
}
.btn-primary:hover:not(:disabled) { background: #e4b96a; border-color: #e4b96a; }

.btn-ghost {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--text-1);
}
.btn-ghost:hover:not(:disabled) { background: var(--bg-2); color: var(--text-0); }

.btn-danger {
  background: transparent;
  border-color: rgba(194, 106, 90, 0.4);
  color: var(--danger);
}
.btn-danger:hover:not(:disabled) {
  background: rgba(194, 106, 90, 0.08);
  border-color: var(--danger);
}

.btn-success {
  background: transparent;
  border-color: rgba(111, 163, 122, 0.4);
  color: var(--ok);
}
.btn-success:hover:not(:disabled) {
  background: rgba(111, 163, 122, 0.08);
  border-color: var(--ok);
}

.btn-sm   { padding: 4px 9px; font-size: 11px; }
.btn-full { width: 100%; }

.btn-pulse { animation: pulse 1.6s ease-in-out infinite; }
@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--accent-soft); }
  50%      { box-shadow: 0 0 0 6px transparent; }
}

/* --- Inputs --- */
input[type="text"],
input[type="search"],
input[type="number"],
input[type="password"],
select,
textarea,
.cfg-input {
  display: block;
  width: 100%;
  padding: 6px 9px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  color: var(--text-0);
  font-size: 12px;
  transition: border-color var(--tr-fast), background var(--tr-fast);
}
input[type="text"]:focus,
input[type="search"]:focus,
input[type="number"]:focus,
input[type="password"]:focus,
select:focus,
textarea:focus,
.cfg-input:focus {
  outline: 0;
  border-color: var(--accent);
  background: var(--bg-3);
}

/* iOS Safari auto-zooms the page on focus of any input whose computed
   font-size is below 16px -- bump to 16px on mobile only so the compact
   12px sizing above is preserved on desktop/tablet.
   !important is deliberate, not laziness: dozens of card-specific rules
   throughout this file (.aa-field input, .sa-form textarea,
   .acct-textarea, ...) set their own smaller font-size with a class
   selector, which beats a plain `input`/`textarea` element selector on
   specificity alone regardless of where in the file either rule sits --
   discovered 2026-08-05 when this fix (originally scoped to the generic
   rule + a couple of known offenders) turned out to only actually apply
   to the one card that has no such override. Chasing every current and
   future per-card override individually is exactly the failure mode
   that caused that gap; !important on this one narrow, mobile-only,
   font-size-only rule is the durable fix. */
@media (max-width: 767px) {
  input[type="text"],
  input[type="search"],
  input[type="number"],
  input[type="password"],
  select,
  textarea,
  .cfg-input {
    font-size: 16px !important;
  }
}

input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 3px;
  background: var(--line-strong);
  border-radius: 2px;
  outline: none;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  border: 2px solid var(--bg-1);
}
input[type="range"]::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
  border: 2px solid var(--bg-1);
}

input[type="checkbox"] {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  border: 1px solid var(--line-strong);
  border-radius: 3px;
  background: var(--bg-2);
  cursor: pointer;
  position: relative;
  transition: border-color var(--tr-fast), background var(--tr-fast);
  vertical-align: middle;
  margin: 0;
  flex-shrink: 0;
}
input[type="checkbox"]:hover { border-color: var(--text-3); }
input[type="checkbox"]:checked {
  background: var(--accent);
  border-color: var(--accent);
}
input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  left: 3px;
  top: 0;
  width: 5px;
  height: 9px;
  border: solid var(--bg-0);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

output {
  display: inline-block;
  min-width: 28px;
  text-align: right;
  font-family: var(--ff-mono);
  font-size: 11px;
  color: var(--text-1);
}

/* --- Card --- */
.card {
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  padding: var(--sp-5);
}
.card-title {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-2);
  margin-bottom: var(--sp-4);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--line);
}

/* --- Alert --- */
.alert {
  padding: 10px 14px;
  border-radius: var(--r-2);
  border: 1px solid var(--line);
  font-size: 12px;
  background: var(--bg-1);
}
.alert-success {
  background: rgba(111, 163, 122, 0.08);
  border-color: rgba(111, 163, 122, 0.3);
  color: var(--ok);
}

.empty-state {
  padding: var(--sp-8) var(--sp-5);
  text-align: center;
  color: var(--text-2);
  font-size: 13px;
}

/* ---------- 6. Sidebar --------------------------------------------------- */

.sidebar-header {
  padding: var(--sp-4);
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: var(--sp-3);
  border-bottom: 1px solid var(--line);
  background: var(--bg-1);
}
.sidebar-logo {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--text-0);
}
.sidebar-logo em {
  color: var(--accent);
  font-weight: 500;
  letter-spacing: 0;
}

.sidebar-nav { display: flex; gap: 4px; }
.sidebar-nav a {
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border-radius: var(--r-2);
  font-size: 14px;
  color: var(--text-2);
  transition: background var(--tr-fast), color var(--tr-fast);
}
.sidebar-nav a:hover { background: var(--bg-3); color: var(--text-0); }

.lang-toggle {
  display: inline-flex;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-2);
  overflow: hidden;
}
.lang-btn {
  padding: 2px 7px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  background: transparent;
  color: var(--text-2);
  transition: background var(--tr-fast), color var(--tr-fast);
}
.lang-btn.is-active { background: var(--accent); color: var(--bg-0); }
.lang-btn:not(.is-active):hover { background: var(--bg-3); color: var(--text-0); }

.sidebar-body {
  flex: 1;
  overflow-y: auto;
  padding: 0 0 var(--sp-4);
}

.sidebar-footer {
  padding: 6px var(--sp-4);
  font-size: 11px;
  color: var(--text-2);
  font-family: var(--ff-mono);
  border-top: 1px solid var(--line);
  background: var(--bg-0);
  min-height: 22px;
}

/* --- Collapsible sections --- */
.section { border-bottom: 1px solid var(--line); }
.section-header {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: var(--sp-2);
  padding: 10px var(--sp-4);
  cursor: pointer;
  user-select: none;
  transition: background var(--tr-fast);
}
.section-header:hover { background: var(--bg-2); }
.section-addon { display: inline-flex; align-items: center; gap: var(--sp-2); cursor: pointer; }
.section-addon:empty { display: none; }
.section-title {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-1);
}
.section-chevron {
  color: var(--text-3);
  font-size: 10px;
  transition: transform var(--tr-base);
}
.section.is-open > .section-header > .section-chevron { transform: rotate(180deg); }
.section-body { display: none; padding: 0 var(--sp-4) var(--sp-4); }
.section.is-open > .section-body { display: block; }

/* --- Field --- */
.field {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  column-gap: var(--sp-3);
  row-gap: 4px;
  padding: var(--sp-2) 0;
}
.field > label {
  font-size: 11px;
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  grid-column: 1;
}
.field > input[type="range"] { grid-column: 1 / -1; }
.field > output { grid-row: 1; grid-column: 2; }
.field > select,
.field > input[type="checkbox"] {
  grid-column: 2;
  justify-self: end;
}
.field > .ctrl { width: 100%; grid-column: 1 / -1; }

/* --- Multi-select checkbox dropdown (core/multiselect.js) --- */
.ks-ms { position: relative; width: 100%; }
.ks-ms-trigger {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  width: 100%; padding: 6px 9px;
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--r-2);
  color: var(--text-0); font-size: 12px; cursor: pointer;
  transition: border-color var(--tr-fast), background var(--tr-fast);
}
.ks-ms-trigger:hover { border-color: var(--line-strong); }
.ks-ms.is-open .ks-ms-trigger,
.ks-ms-trigger:focus { outline: 0; border-color: var(--accent); background: var(--bg-3); }
.ks-ms-trigger.is-filtered { color: var(--accent); border-color: var(--accent); }
.ks-ms-trigger-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; }
.ks-ms-trigger-arrow { flex-shrink: 0; font-size: 10px; color: var(--text-2); }
.ks-ms.is-open .ks-ms-trigger-arrow { transform: rotate(180deg); }
.ks-ms-panel {
  position: fixed; z-index: 500;
  background: var(--glass-bg, rgba(18,20,26,0.94));
  border: 1px solid var(--glass-border, rgba(255,255,255,0.1));
  border-radius: var(--r-2);
  box-shadow: var(--glass-shadow, 0 6px 24px rgba(0,0,0,0.4));
  max-height: 280px; flex-direction: column;
  overflow: hidden;
}
/* `display:flex` must only apply while NOT [hidden] -- setting it
   unconditionally on the bare class overrides the UA stylesheet's
   `[hidden]{display:none}`, so a "closed" panel stayed laid out at its
   last fixed position and kept intercepting clicks underneath it. */
.ks-ms-panel:not([hidden]) { display: flex; }
@supports (backdrop-filter: blur(8px)) {
  .ks-ms-panel { backdrop-filter: blur(10px) saturate(1.2); -webkit-backdrop-filter: blur(10px) saturate(1.2); }
}
.ks-ms-actions {
  display: flex; gap: var(--sp-1); padding: 6px; border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.ks-ms-action {
  flex: 1; padding: 3px 8px; font-size: 11px; font-weight: 600;
  background: var(--bg-3); border: 1px solid var(--line); border-radius: var(--r-1);
  color: var(--text-1); cursor: pointer;
}
.ks-ms-action:hover { color: var(--text-0); border-color: var(--accent); }
.ks-ms-options { overflow-y: auto; padding: 4px; }
.ks-ms-option {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 4px 6px; border-radius: var(--r-1); font-size: 12px; cursor: pointer;
}
.ks-ms-option:hover { background: var(--bg-3); }
.ks-ms-option input[type="checkbox"] { flex-shrink: 0; margin: 0; }
.ks-ms-option span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- Search --- */
.search-wrap { position: relative; display: flex; align-items: center; }
/* [hidden] alone loses to the class's own `display:flex` above (equal
   specificity, source order/class wins over the UA default) -- the
   Rearrange search box (map.html) toggles this attribute to hide itself
   when the plan is empty, which silently did nothing without this. */
.search-wrap[hidden] { display: none; }
.search-icon {
  position: absolute;
  left: 9px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-3);
  font-size: 13px;
  pointer-events: none;
}
.search-input { padding-left: 26px !important; }
.search-results { margin-top: var(--sp-2); max-height: 240px; overflow-y: auto; }
.search-result {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: var(--sp-2);
  align-items: center;
  padding: 6px var(--sp-2);
  border-radius: var(--r-2);
  cursor: pointer;
  font-size: 12px;
  transition: background var(--tr-fast);
}
.search-result:hover { background: var(--bg-3); }
.sr-nick-col { min-width: 0; display: flex; flex-direction: column; }
.sr-nick { color: var(--text-0); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sr-formerly { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sr-abbr { color: var(--accent); font-size: 11px; }
.sr-lv   { color: var(--text-2); font-family: var(--ff-mono); font-size: 11px; }

.sidebar-apply-row {
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--line);
  background: var(--bg-1);
}

/* --- Scanner --- */
.scan-status-line {
  font-size: 11px;
  color: var(--text-1);
  margin-bottom: var(--sp-2);
  font-family: var(--ff-mono);
}
.scan-bar-wrap {
  height: 3px;
  background: var(--line);
  border-radius: 2px;
  overflow: hidden;
  margin-bottom: var(--sp-2);
}
.scan-bar {
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width 300ms ease-out;
}
.scan-bar.is-running {
  background: linear-gradient(90deg, var(--accent) 0%, var(--accent-dim) 50%, var(--accent) 100%);
  background-size: 200% 100%;
  animation: barShift 1.8s linear infinite;
}
@keyframes barShift {
  from { background-position: 0 0; }
  to   { background-position: -200% 0; }
}
.scan-counts {
  font-size: 11px;
  color: var(--text-2);
  font-family: var(--ff-mono);
  margin-bottom: var(--sp-3);
}
.scan-actions { display: flex; gap: var(--sp-2); }
.scan-actions .btn { flex: 1; }
.scan-log {
  display: none;
  margin-top: var(--sp-3);
  max-height: 160px;
  overflow: auto;
  font-family: var(--ff-mono);
  font-size: 10.5px;
  color: var(--text-2);
  background: var(--bg-0);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  padding: var(--sp-2);
  white-space: pre-wrap;
  word-break: break-all;
}

/* --- Area scan --- */
.area-scan-actions { display: flex; gap: var(--sp-2); }
.area-scan-actions .btn { flex: 1; }

.goto-row { display: flex; gap: var(--sp-2); align-items: center; }
.goto-row input[type="number"] { width: 0; flex: 1; min-width: 0; }
.goto-row input[type="number"]::-webkit-outer-spin-button,
.goto-row input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.goto-row .btn { flex: 0 0 auto; }
.goto-readout { font-variant-numeric: tabular-nums; }

.topbar-coord {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
  min-width: 92px;
  text-align: center;
  padding: 2px 10px;
  border: 1px solid var(--glass-border-hi, rgba(255,255,255,0.12));
  border-radius: 6px;
  white-space: nowrap;
  user-select: none;
}
.area-scan-info {
  margin-top: var(--sp-3);
  font-family: var(--ff-mono);
  font-size: 10.5px;
  color: var(--text-2);
  min-height: 15px;
}
.area-scan-crosshair {
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  color: var(--accent);
  font-size: 18px;
  text-shadow: 0 0 6px var(--accent);
  pointer-events: none;
}

/* --- Session info --- */
.session-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.stat-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-2);
  margin-bottom: 2px;
}
.stat-value {
  font-family: var(--ff-mono);
  font-size: 18px;
  font-weight: 500;
  color: var(--text-0);
}
.age-line {
  font-size: 11px;
  color: var(--text-2);
  font-family: var(--ff-mono);
}
.age-line.is-stale { color: var(--warn); }

/* --- Legend --- */
.legend-res {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: 11px;
  padding: 3px 0;
  color: var(--text-1);
}
.legend-dot {
  width: 8px;
  height: 8px;
  border-radius: 2px;
}
/* --- Movements --- */
/* Intel card body becomes a flex column so the movements list can grow and
   scroll on its own while the session-info block stays pinned at the top. */
.card-layer > .card[data-card="intel"] > .card-body {
  display: flex;
  flex-direction: column;
}
.card-layer > .card[data-card="intel"] #movements-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-right: 4px;
}
.card-layer > .card[data-card="intel"] #movements-list::-webkit-scrollbar { width: 6px; }
.card-layer > .card[data-card="intel"] #movements-list::-webkit-scrollbar-thumb {
  background: var(--glass-border-hi);
  border-radius: 3px;
}

.movement-item {
  padding: var(--sp-2);
  border-radius: var(--r-2);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background var(--tr-fast), border-color var(--tr-fast);
  margin-bottom: 4px;
}
.movement-item:hover {
  background: var(--bg-2);
  border-color: var(--line);
}
.mv-header {
  display: flex;
  gap: 6px;
  align-items: baseline;
  font-size: 12px;
}
.mv-nick { color: var(--text-0); font-weight: 500; }
.mv-abbr { color: var(--accent); font-size: 11px; }
.mv-lv {
  margin-left: auto;
  color: var(--text-2);
  font-family: var(--ff-mono);
  font-size: 10.5px;
}
.mv-when {
  color: var(--text-3);
  font-family: var(--ff-mono);
  font-size: 10px;
}
.mv-coords {
  font-family: var(--ff-mono);
  font-size: 10.5px;
  color: var(--text-2);
  margin-top: 2px;
}

.res-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 4px 0;
  font-size: 12px;
}

/* ---------- City/player detail card (merged tap-info + history +
   resources + recent marches) ---------- */
.city-detail-info {
  padding-bottom: var(--sp-3);
  margin-bottom: var(--sp-3);
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  line-height: 1.7;
}
.city-detail-info b { font-size: 15px; }
.city-detail-section { margin-bottom: var(--sp-4); }
.city-detail-section:last-child { margin-bottom: 0; }
.city-detail-section-h {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-2);
  margin-bottom: 6px;
}
.city-detail-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
}
.city-detail-pager-btn {
  width: 22px;
  height: 22px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  color: var(--text-1);
  cursor: pointer;
  transition: background var(--tr-fast);
}
.city-detail-pager-btn:hover:not(:disabled) { background: var(--bg-3); }
.city-detail-pager-btn:disabled { opacity: 0.35; cursor: default; }
.city-march-item {
  padding: var(--sp-2);
  border-radius: var(--r-2);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background var(--tr-fast), border-color var(--tr-fast);
  margin-bottom: 4px;
}
.city-march-item:hover { background: var(--bg-2); border-color: var(--line); }
.cm-header { display: flex; justify-content: space-between; gap: 6px; font-size: 12px; }
.cm-type { color: var(--text-0); font-weight: 500; }
.cm-coords { font-family: var(--ff-mono); font-size: 10.5px; margin-top: 2px; }

/* ---------- 7. Map page -------------------------------------------------- */

.leaflet-container {
  background: var(--bg-0);
  font-family: var(--ff-sans);
  font-size: 12px;
}
.leaflet-container a { color: var(--accent); }

.leaflet-control-zoom {
  border: 1px solid var(--line-strong) !important;
  box-shadow: none !important;
  background: var(--bg-1) !important;
  border-radius: var(--r-2) !important;
  overflow: hidden;
}
.leaflet-control-zoom a {
  background: var(--bg-1) !important;
  color: var(--text-1) !important;
  border-bottom: 1px solid var(--line) !important;
  width: 28px !important;
  height: 28px !important;
  line-height: 28px !important;
  font-size: 16px !important;
}
.leaflet-control-zoom a:hover {
  background: var(--bg-3) !important;
  color: var(--text-0) !important;
}
.leaflet-control-zoom a:last-child { border-bottom: 0 !important; }

.leaflet-control-attribution {
  background: rgba(12, 14, 18, 0.75) !important;
  color: var(--text-3) !important;
  font-size: 10px !important;
  padding: 1px 6px !important;
  border: 1px solid var(--line) !important;
  border-radius: var(--r-2) 0 0 0;
}
.leaflet-control-attribution a { color: var(--text-2) !important; }

.leaflet-popup-content-wrapper {
  background: var(--bg-1) !important;
  color: var(--text-0) !important;
  border: 1px solid var(--line-strong) !important;
  border-radius: var(--r-3) !important;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5) !important;
  padding: 2px !important;
}
.leaflet-popup-content {
  margin: 10px 14px !important;
  font-size: 12px !important;
  line-height: 1.55 !important;
  color: var(--text-1);
}
.leaflet-popup-content b { color: var(--text-0); font-weight: 600; }
.leaflet-popup-tip {
  background: var(--bg-1) !important;
  border: 1px solid var(--line-strong) !important;
}
.leaflet-popup-close-button {
  color: var(--text-3) !important;
  font-size: 16px !important;
  padding: 4px 6px !important;
}
.leaflet-popup-close-button:hover { color: var(--text-0) !important; }

/* Hover tooltips (replaces the click popups for marker/resource/march details). */
.leaflet-tooltip.ks-tooltip {
  background: var(--glass-bg, rgba(18,20,26,0.92));
  color: var(--text-0);
  border: 1px solid var(--glass-border, rgba(255,255,255,0.1));
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 12px;
  line-height: 1.35;
  box-shadow: var(--glass-shadow, 0 6px 24px rgba(0,0,0,0.4));
  white-space: nowrap;
}
@supports (backdrop-filter: blur(8px)) {
  .leaflet-tooltip.ks-tooltip { backdrop-filter: blur(10px) saturate(1.2); -webkit-backdrop-filter: blur(10px) saturate(1.2); }
}
.leaflet-tooltip.ks-tooltip::before { display: none; }
.leaflet-tooltip-top.ks-tooltip::before,
.leaflet-tooltip-bottom.ks-tooltip::before { border: none; }

.popup-abbr {
  font-family: var(--ff-mono);
  font-size: 11px;
  color: var(--accent);
}

.marker-cluster, .marker-cluster div { background: transparent !important; }

/* ---------- 8. Live updates --------------------------------------------- */

.live-indicator {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: var(--r-2);
  transition: background var(--tr-fast);
}
.live-indicator:hover { background: var(--bg-3); }

.live-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-3);
  transition: background var(--tr-fast);
}
.live-indicator.is-active .live-dot {
  background: var(--ok);
  box-shadow: 0 0 6px var(--ok);
  animation: livePulse 1.6s ease-in-out infinite;
}
@keyframes livePulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.45; }
}
.live-count {
  font-size: 10px;
  font-family: var(--ff-mono);
  color: var(--text-1);
  min-width: 12px;
  text-align: center;
}
.live-status {
  font-size: 11px;
  color: var(--text-2);
  font-family: var(--ff-mono);
}
.live-preview-item {
  display: flex;
  gap: 6px;
  align-items: center;
  font-size: 11px;
  padding: 4px 0;
}
.live-event-icon { font-size: 13px; }
.live-event-text {
  color: var(--text-0);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
}

.live-panel {
  position: fixed;
  right: calc(var(--rail-left-w) + 16px);
  bottom: 16px;
  width: 340px;
  max-height: 440px;
  background: var(--bg-1);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-3);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55);
  display: none;
  flex-direction: column;
  z-index: 500;
}
.live-panel.is-open { display: flex; }

.live-panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
}
.live-panel-title {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-1);
}
.live-panel-body {
  overflow-y: auto;
  padding: var(--sp-2);
  flex: 1;
}

.live-event {
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-2);
  border: 1px solid transparent;
  margin-bottom: 3px;
  cursor: pointer;
  transition: background var(--tr-fast), border-color var(--tr-fast);
}
.live-event:hover {
  background: var(--bg-2);
  border-color: var(--line);
}
.live-event-row {
  display: flex;
  gap: 6px;
  align-items: center;
  font-size: 11px;
  color: var(--text-2);
}
.live-event-label {
  color: var(--text-1);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.live-event-time {
  margin-left: auto;
  font-family: var(--ff-mono);
  font-size: 10.5px;
}
.live-event-body {
  font-size: 12px;
  margin-top: 2px;
  color: var(--text-0);
}
.live-event-details {
  font-size: 10.5px;
  color: var(--text-2);
  font-family: var(--ff-mono);
  margin-top: 2px;
}

/* --- Rearrange planner card --- */
.rearrange-list {
  max-height: 240px;
  overflow-y: auto;
}
.rearrange-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-2);
  border-radius: var(--r-2);
  border: 1px solid transparent;
  margin-bottom: 3px;
  font-size: 12px;
}
.rearrange-row:hover {
  background: var(--bg-2);
  border-color: var(--line);
}
.rearrange-nick {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rearrange-coords {
  flex: 0 0 auto;
  font-family: var(--ff-mono);
  font-size: 10.5px;
}
.rearrange-remove {
  flex: 0 0 auto;
  background: none;
  border: none;
  color: var(--text-2);
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
  padding: 2px 4px;
}
.rearrange-remove:hover { color: var(--text-0); }
.rearrange-remove:disabled { opacity: .3; cursor: default; }
.rearrange-remove:disabled:hover { color: var(--text-2); }
/* 🔒/🔓 toggle (rearrange.js:toggleLock) -- dim/plain when unlocked so it
   doesn't compete with the remove button, full-color accent once locked
   so a protected entry is obvious at a glance in the list too, not just
   on the map ghost (see .rearrange-ghost-castle.is-locked below). */
.rearrange-lock {
  flex: 0 0 auto;
  background: none;
  border: none;
  color: var(--text-2);
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
  padding: 2px 4px;
  opacity: .55;
}
.rearrange-lock:hover { opacity: 1; }
.rearrange-lock.is-locked { opacity: 1; }
.rearrange-actions {
  display: flex;
  gap: var(--sp-2);
}
.rearrange-actions .btn { flex: 1 1 0; }
/* Diamond clip on a square div -- same N/E/S/W-pointed shape and bounding
   box convention as SquareMarker's canvas diamond (leaflet-base.js), so a
   ghost castle visually matches a real one's footprint at any zoom. */
.rearrange-ghost-castle {
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
  opacity: 0.68;
  box-shadow: 0 0 0 2px rgba(255,255,255,0.55) inset;
  cursor: grab;
}
.rearrange-ghost-castle:active { cursor: grabbing; }
/* Locked ghost (rearrange.js:toggleLock) -- gold ring instead of white,
   and a plain (not grab) cursor since it can no longer be dragged. */
.rearrange-ghost-castle.is-locked {
  box-shadow: 0 0 0 2px var(--accent) inset;
  cursor: default;
}
.rearrange-ghost-castle.is-locked:active { cursor: default; }

/* Territory wash for alliance HQ/banner ghosts (rearrange.js) -- same
   diamond shape, much lower opacity, non-interactive so it never steals
   the drag/click from the ghost structure sitting on top of it. */
.rearrange-ghost-territory {
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
  opacity: 0.2;
  pointer-events: none;
}

/* "Already in the plan" badge on the REAL marker's own spot (rearrange.js
   addEntry()'s originBadge) -- fixed size (not geography-scaled, unlike
   the ghost/territory diamonds above) so it stays a small, readable dot
   at any zoom instead of tracking the footprint size. pointer-events:none
   so it never blocks clicking the real marker underneath. */
.rearrange-planned-badge {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 700;
  color: #1a1408;
  box-shadow: 0 0 0 2px rgba(0,0,0,0.55);
  pointer-events: none;
}

/* Manually-marked unoccupiable tile (rearrange.js) -- same diamond shape
   as every other footprint marker, styled as a clear warning so it reads
   as "don't plan here" at a glance, distinct from the amber ghost/plan
   styling used for actual planned moves. */
.rearrange-obstacle-tile {
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
  background: var(--danger);
  opacity: 0.55;
  box-shadow: 0 0 0 2px rgba(255,255,255,0.4) inset;
  cursor: pointer;
}

.rearrange-order {
  max-height: 200px;
  overflow-y: auto;
  margin-top: 6px;
}
.rearrange-order:empty { margin-top: 0; }
.rearrange-order-title {
  color: var(--text-2);
  font-weight: 500;
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: 0.06em;
  margin: 6px 0 4px;
}
.rearrange-order-row {
  font-size: 12px;
  padding: 3px 2px;
  display: flex;
  gap: 6px;
  align-items: flex-start;
}
.rearrange-order-num {
  color: var(--accent);
  font-family: var(--ff-mono);
  flex: 0 0 auto;
}
.rearrange-order-dep {
  font-size: 10.5px;
  color: #e0a05a;
  margin-top: 1px;
}
.rearrange-order-dep.rearrange-order-free {
  color: var(--text-2);
}
.rearrange-warn {
  color: #e0a05a;
  font-size: 11.5px;
  font-weight: 500;
  margin: 6px 0 3px;
}
.rearrange-warn-row {
  font-size: 11.5px;
  color: var(--text-1);
  padding: 2px 0 2px 8px;
  border-left: 2px solid #e0a05a;
  margin-bottom: 2px;
}

/* --- History table (used inside the city-detail card) --- */
.history-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 11.5px;
}
.history-table th {
  text-align: left;
  color: var(--text-2);
  font-weight: 500;
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: 0.08em;
  padding: 6px 8px;
  border-bottom: 1px solid var(--line-strong);
  background: var(--bg-1);
}
.history-table td {
  padding: 6px 8px;
  border-bottom: 1px solid var(--line);
  color: var(--text-1);
  font-family: var(--ff-mono);
}
.history-table td:nth-child(2) {
  font-family: var(--ff-sans);
  color: var(--text-0);
  /* Allow long CJK / Cyrillic / mixed-script nicknames to wrap inside the cell
     instead of overflowing the table. */
  word-break: break-word;
  overflow-wrap: anywhere;
}
.history-table tr.is-moved td:nth-child(3) { color: var(--warn); }
.history-table tr.is-renamed td:nth-child(2) { color: var(--accent); }
/* Invisible filler rows on a short page (e.g. the last one) — keeps the
   table a constant height across pages so the sections below it don't
   shift when paging (city-detail card). */
.history-table tr.history-pad-row { visibility: hidden; }
.history-table tr.history-pad-row td { border-bottom-color: transparent; }

/* ── LIVE badge in topbar ─────────────────────────────────────────── */
.live-badge {
  display: inline-block;
  padding: 1px 6px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  background: rgba(111, 200, 122, 0.18);
  color: #6fc87a;
  border: 1px solid rgba(111, 200, 122, 0.45);
  border-radius: 3px;
  animation: live-pulse 1.6s ease-in-out infinite;
}
@keyframes live-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}
.live-badge--inline {
  font-size: 9px;
  padding: 0 5px;
  vertical-align: middle;
  margin-left: 6px;
}
.live-badge[hidden] { display: none; }

.scan-badge {
  display: inline-block;
  padding: 1px 6px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  background: rgba(255, 165, 0, 0.18);
  color: #ffa85c;
  border: 1px solid rgba(255, 165, 0, 0.45);
  border-radius: 3px;
  animation: scan-blink 1.2s ease-in-out infinite;
  margin-left: 4px;
}
@keyframes scan-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}
.scan-badge[hidden] { display: none; }

.next-scan-line {
  margin-top: var(--sp-1);
  font-size: 11px;
  color: var(--text-2);
  font-family: var(--ff-mono);
}
.next-scan-line strong {
  color: var(--text-1);
  margin-left: 4px;
}
.next-scan-line[hidden] { display: none; }

/* ── Marches card ─────────────────────────────────────────────────── */
.marches-toolbar {
  display: flex;
  gap: var(--sp-2);
  align-items: center;
  margin-bottom: var(--sp-2);
}
/* .march-filter is reused outside .marches-toolbar too (Live panel's march
   type filter) — kept unscoped so it isn't tied to that one toolbar. */
.march-filter,
.marches-toolbar .march-search {
  background: var(--bg-1);
  border: 1px solid var(--line);
  color: var(--text-0);
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 12px;
}
/* Type dropdown + search box split the toolbar's remaining width evenly
   (equal flex-basis:0, not content/width-driven) so the search input
   isn't squeezed down to near-unreadable -- reported 2026-08-05. The
   dropdown is actually core/multiselect.js's .ks-ms wrapper (it hides
   the real #march-type-filter <select> and inserts this next to it) --
   .ks-ms's base rule sets width:100%, which (with flex-basis:auto
   defaulting to that width) made it claim the whole row and crowd out
   everything else; overridden here to match .march-search's sizing.
   min-width:0 overrides the flex-item default (min-width:auto, floored
   at content size) so both can actually shrink to their 50% share
   instead of forcing overflow. */
.marches-toolbar .ks-ms,
.marches-toolbar .march-search {
  flex: 1 1 0;
  width: auto;
  min-width: 0;
}
.marches-list { overflow: auto; max-height: calc(100% - 40px); }
.marches-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 11.5px;
}
.marches-table th {
  text-align: left;
  color: var(--text-2);
  font-weight: 500;
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: 0.08em;
  padding: 6px 8px;
  border-bottom: 1px solid var(--line-strong);
  background: var(--bg-1);
  position: sticky;
  top: 0;
}
.marches-table td {
  padding: 5px 8px;
  border-bottom: 1px solid var(--line);
  color: var(--text-1);
  font-family: var(--ff-mono);
}
.marches-table tbody tr { cursor: pointer; transition: background var(--tr-fast); }
.marches-table tbody tr:hover { background: var(--bg-2); }
/* Origin/target coordinate cells — click to fly the map there without
   expanding the row (event.stopPropagation() in the click handler).
   Underline only reveals on hover so the table doesn't look cluttered
   with permanent link styling. */
.march-coord-link {
  cursor: pointer;
  text-decoration-line: underline;
  text-decoration-color: transparent;
  text-underline-offset: 2px;
  transition: text-decoration-color var(--tr-fast), color var(--tr-fast);
}
.march-coord-link:hover {
  color: var(--accent);
  text-decoration-color: currentColor;
}

/* Outposts card — same look as .marches-table but rows aren't clickable. */
.outposts-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 11.5px;
}
.outposts-table th {
  text-align: left;
  color: var(--text-2);
  font-weight: 500;
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: 0.08em;
  padding: 6px 8px;
  border-bottom: 1px solid var(--line-strong);
  background: var(--bg-1);
  position: sticky;
  top: 0;
}
.outposts-table td {
  padding: 5px 8px;
  border-bottom: 1px solid var(--line);
  color: var(--text-1);
  font-family: var(--ff-mono);
}

/* Outposts card — per-alliance accordion groups, mirrors .al-group. */
.op-group + .op-group { margin-top: var(--sp-4); }
.op-group-header {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  cursor: pointer;
  padding: 4px 2px;
  margin-bottom: 4px;
  user-select: none;
}
.op-chevron {
  color: var(--text-3);
  font-size: 10px;
  width: 10px;
  display: inline-block;
  transition: color var(--tr-fast);
}
.op-group-header:hover .op-chevron { color: var(--accent); }
.op-group-label {
  font-family: var(--ff-mono);
  font-weight: 600;
  color: var(--accent);
  font-size: 13px;
  letter-spacing: 0.02em;
}
.op-group-count {
  font-family: var(--ff-mono);
  font-size: 11px;
  color: var(--text-3);
}
.op-name-link {
  cursor: pointer;
  color: var(--accent);
  transition: color var(--tr-fast);
}
.op-name-link:hover { text-decoration: underline; }

/* 7 columns in a 560px-default card leaves the Name column (e.g. "Fortress
   1") too narrow at table-layout:auto -- the browser wraps it at the space
   instead of shrinking a less content-critical column. Desktop/tablet only;
   the <560px stacked layout below already has its own nowrap+ellipsis on
   this cell. Reported 2026-08-07. */
.fs-table td:first-child { white-space: nowrap; }

/* Forts & Sanctuaries card — two stacked flat sections (pages/sanctuaries.js),
   mirrors the .alliances-head divider styling for each section's header. */
.fs-page {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
}
.fs-section-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  padding-bottom: var(--sp-2);
  margin-bottom: var(--sp-2);
  border-bottom: 1px solid var(--line);
}
.fs-section-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-0);
  letter-spacing: 0.01em;
}

.fs-phase-control {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
}
.fs-phase-btn {
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-1);
  color: var(--text-1);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  transition: background var(--tr-fast), color var(--tr-fast);
}
.fs-phase-btn:hover { background: var(--bg-3); color: var(--accent); }
.fs-phase-current {
  font-weight: 600;
  color: var(--accent);
  font-size: 13px;
}
.fs-phase-hint { margin-left: auto; }

.fs-reward-cell { text-align: center; }
.fs-reward-icon {
  width: 26px;
  height: 26px;
  vertical-align: middle;
  border-radius: var(--r-1);
}

/* Below 560px: 7 real columns (Name/Coords/Alliance/2 countdowns/2 reward
   icons) had no mobile treatment at all -- plain table-layout:auto just
   grew past the card's width with nothing to contain it, so the table
   ran off the right edge with no scrollbar the user could find/use.
   Reported 2026-08-06. .fs-table (not the shared .outposts-table, which
   the Outposts card also uses with a different column order -- see
   pages/sanctuaries.js) restacks each row into a card: name+alliance,
   then coordinates/start/defend each on their own line, then both
   reward icons together on a last line. Standard table/tbody/tr
   block/flex override, same technique as the other roster fixes; no JS
   changes beyond the one class name added to scope this. */
@media (max-width: 560px) {
  .fs-table thead { display: none; }
  .fs-table, .fs-table tbody { display: block; width: 100%; }
  .fs-table tr {
    display: flex; flex-wrap: wrap; align-items: baseline;
    column-gap: 8px; row-gap: 3px;
    padding: 8px 4px; border-bottom: 1px solid var(--line);
  }
  .fs-table td { padding: 0; border: 0; }
  .fs-table td:nth-child(1) {
    order: 1; flex: 1 1 auto; min-width: 0; font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .fs-table td:nth-child(3) { order: 2; flex: 0 0 auto; }
  .fs-table td:nth-child(2) {
    order: 3; flex-basis: 100%; font-size: .72rem; color: var(--text-3,var(--text-2));
  }
  .fs-table td:nth-child(4), .fs-table td:nth-child(5) { flex-basis: 100%; font-size: .72rem; }
  .fs-table td:nth-child(4) { order: 4; }
  .fs-table td:nth-child(4)::before { content: "Start: "; color: var(--text-3,var(--text-2)); }
  .fs-table td:nth-child(5) { order: 5; }
  .fs-table td:nth-child(5)::before { content: "Defend: "; color: var(--text-3,var(--text-2)); }
  .fs-table td:nth-child(6), .fs-table td:nth-child(7) {
    order: 6; flex: 0 0 auto; display: flex; align-items: center; gap: 4px; font-size: .68rem;
  }
  .fs-table td:nth-child(6)::before { content: "Now: "; color: var(--text-3,var(--text-2)); }
  .fs-table td:nth-child(7)::before { content: "Next: "; color: var(--text-3,var(--text-2)); margin-left: 6px; }
}

.march-type-badge {
  display: inline-block;
  padding: 1px 6px;
  border-radius: 3px;
  font-size: 10px;
  font-family: var(--ff-sans);
}
.march-type-attack { background: rgba(194, 106, 90, 0.18); color: #e89685; }
.march-type-gather { background: rgba(111, 163, 122, 0.18); color: #8fc59c; }
.march-type-other  { background: rgba(150, 150, 170, 0.18); color: var(--text-2); }
.march-eta-cell { font-variant-numeric: tabular-nums; }
.march-eta-arrived { color: var(--text-3); font-style: italic; }
.march-arrived-cell { font-variant-numeric: tabular-nums; color: var(--text-2); white-space: nowrap; }

/* History limit selector */
.march-limit-wrap {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
}

/* Expanded detail row (history view) */
.marches-table tbody tr.march-row.is-expanded { background: var(--bg-2); }
.marches-table tbody tr.march-detail-row { cursor: default; background: var(--bg-1); }
.marches-table tbody tr.march-detail-row:hover { background: var(--bg-1); }
.marches-table tbody tr.march-detail-row > td {
  padding: var(--sp-3);
  font-family: var(--ff-sans);
  border-bottom: 1px solid var(--line-strong);
}
.march-detail-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--sp-3);
  row-gap: 4px;
  font-size: 12px;
}
.march-detail-grid .d-row {
  display: flex;
  gap: 6px;
  align-items: baseline;
  min-width: 0;
}
.march-detail-grid .d-lbl {
  color: var(--text-3);
  flex: 0 0 100px;
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: 0.06em;
}
.march-detail-grid .d-row > span:last-child {
  color: var(--text-0);
  word-break: break-word;
}
.march-detail-section { margin-top: var(--sp-2); }
.march-detail-section-h {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-2);
  margin-bottom: 4px;
}
.march-detail-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--sp-3);
  row-gap: 2px;
  font-size: 12px;
  font-family: var(--ff-mono);
}
.march-detail-list li { color: var(--text-1); }
.march-detail-pill {
  display: inline-block;
  padding: 0 6px;
  border-radius: 3px;
  font-size: 10px;
  font-family: var(--ff-sans);
  background: rgba(194, 106, 90, 0.18);
  color: #e89685;
  margin-left: 4px;
}
.march-detail-actions { margin-top: var(--sp-2); display: flex; justify-content: flex-end; }
.march-rally { border-top: 1px dashed rgba(255,255,255,0.08); padding-top: var(--sp-2); }
.march-rally-totals {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2px;
  margin-bottom: var(--sp-2);
  padding: 6px 8px;
  background: rgba(249, 168, 37, 0.06);
  border: 1px solid rgba(249, 168, 37, 0.15);
  border-radius: 4px;
}
.march-rally-total-row {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  font-family: var(--ff-mono);
  font-size: 12px;
}
.march-rally-total-row .d-lbl { min-width: 130px; }
.march-rally-total-row strong { color: var(--text-1); }
.march-rally-total-grand {
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px solid rgba(249, 168, 37, 0.25);
  font-size: 13px;
}
.march-rally-total-grand strong { color: #ffd166; }
.march-rally-slaves { grid-template-columns: 1fr; }
.march-phase-cluster {
  display: inline-block;
  padding: 1px 5px;
  border-radius: 3px;
  background: rgba(255,255,255,0.06);
  font-family: var(--ff-mono);
  font-size: 11px;
  color: var(--text-2);
  letter-spacing: 1px;
}
.march-phase-cluster.march-phase-single { letter-spacing: 0; }
.march-phases-list { grid-template-columns: 1fr; }
.march-phases-list li { display: flex; gap: 6px; align-items: baseline; flex-wrap: wrap; }
.march-phase-glyph {
  display: inline-block;
  width: 18px;
  text-align: center;
  font-family: var(--ff-mono);
  color: var(--text-2);
}

/* Below 560px the Active Marches toolbar has 4 real controls (type
   filter, search, limit, count) plus the hidden <select> multiselect.js
   leaves in the DOM -- reported 2026-08-06: the limit selector and
   "N marches · M phases" count text (both fixed/content-width, ~215px
   combined) were eating most of a ~350px toolbar, squeezing the type
   filter and search down to ~50px each, unreadable. Wraps the toolbar
   onto 2 lines instead: filter+search (the controls someone actually
   types/picks into) keep the full width on line 1, limit+count (just
   informational/secondary) drop to line 2.
   The .marches-table itself was a real 7-column HTML table with zero
   mobile treatment -- same underlying problem as the Alliance Activity
   roster fixed the same day (table-layout squeezing everything to
   illegible/overflowing), except here nothing capped the overflow at
   all, so origin/target/descriptor/arrival just ran off the right edge
   of the card with no way to reach them, which is what made rows hard
   to even tell apart, let alone tap to expand. Restacked into a
   flex-wrap card per row (standard "responsive table" override: table/
   tbody/tr become block/flex, breaking them out of table layout) -- no
   JS changes, same tap-to-expand handler still fires since it's bound
   to the .march-row element itself, not its layout. */
@media (max-width: 560px) {
  /* Grid instead of flex-wrap here -- a flex-basis:100% force-break on
     march-limit-wrap (tried first) claims the ENTIRE line for itself
     since that's what flex-basis:100% means, pushing #march-count onto
     its own 3rd line instead of sitting beside it. An explicit 2x2 grid
     places all 4 exactly where intended without that fragility. */
  .marches-toolbar {
    display: grid; grid-template-columns: 1fr 1fr;
    grid-template-areas: "filter search" "limit count";
    row-gap: 6px;
  }
  .marches-toolbar .ks-ms { grid-area: filter; }
  .marches-toolbar .march-search { grid-area: search; }
  .marches-toolbar .march-limit-wrap { grid-area: limit; }
  .marches-toolbar #march-count { grid-area: count; align-self: center; }

  .marches-table, .marches-table tbody { display: block; width: 100%; }
  .marches-table thead { display: none; }
  .marches-table tr.march-row {
    display: flex; flex-wrap: wrap; align-items: center;
    column-gap: 8px; row-gap: 3px;
    padding: 8px 10px;
  }
  .marches-table tr.march-row > td { padding: 0; border: 0; }
  /* Line 1: player name (grows + ellipsizes), type badge, ETA -- the
     "who/what/when" someone scans a march list for first. */
  .marches-table tr.march-row > td:nth-child(1) {
    order: 1; flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-weight: 600;
  }
  .marches-table tr.march-row > td:nth-child(2) { order: 2; flex: 0 0 auto; }
  .marches-table tr.march-row > td:nth-child(7) {
    order: 3; flex: 0 0 auto; font-size: .7rem; white-space: nowrap;
  }
  /* Line 2 (wraps naturally, no forced break needed): phase, origin, target. */
  .marches-table tr.march-row > td:nth-child(3),
  .marches-table tr.march-row > td:nth-child(4),
  .marches-table tr.march-row > td:nth-child(5) {
    order: 4; flex: 0 0 auto; font-size: .72rem; color: var(--text-2);
  }
  /* Descriptor: often a longer free-text sentence -- always its own
     trailing full-width line (flex-basis:100%), and skipped entirely
     when blank rather than leaving an empty line. */
  .marches-table tr.march-row > td:nth-child(6) {
    order: 5; flex: 0 0 100%; font-size: .7rem; color: var(--text-2);
  }
  .marches-table tr.march-row > td:nth-child(6):empty { display: none; }

  .marches-table tr.march-detail-row { display: block; }
  .marches-table tr.march-detail-row > td { display: block; }
  .march-detail-grid { grid-template-columns: 1fr; }
}

/* Special tile icons (king/turret/fortress/sanctuary/frontier_shed) */
.special-tile-icon {
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  line-height: 1;
  color: var(--st-color, #fff);
  background: rgba(0,0,0,0.55);
  border: 1.5px solid var(--st-color, #fff);
  border-radius: 6px;
  text-shadow: 0 0 4px rgba(0,0,0,0.8);
  box-shadow: 0 0 6px rgba(0,0,0,0.4);
  pointer-events: auto;
}
/* Per-subtype artwork replacing the glyph (e.g. frontier_shed/Outpost).
   Sized to fill whatever iconSize the divIcon was given in loaders.js
   (currently 34px for image-based subtypes) rather than a fixed px value. */
.special-tile-icon-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 0 4px rgba(0,0,0,0.8));
  pointer-events: auto;
}
.popup-special-tile { font-size: 12px; }
.popup-special-tile .popup-title { font-size: 13px; margin-bottom: 4px; }
.popup-special-tile-img {
  display: block;
  width: 96px;
  height: 96px;
  object-fit: contain;
  margin: 0 auto 8px;
}
.moved-badge {
  color: var(--warn);
  font-size: 10px;
  margin-left: 4px;
}

/* ---------- 9. Alliances page ------------------------------------------ */

.alliances-page { gap: var(--sp-6); }

.alliances-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--sp-5);
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--line);
}
.alliances-title {
  font-size: 24px;
  font-weight: 600;
  letter-spacing: -0.02em;
  margin: 0 0 var(--sp-2) 0;
}
.alliances-summary {
  font-size: 12px;
  color: var(--text-2);
}
.alliances-summary strong {
  color: var(--text-0);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.alliances-controls {
  display: flex;
  gap: var(--sp-4);
  align-items: center;
}
.alliances-controls .search-input {
  width: 240px;
  padding-left: 9px !important;
}
.alliances-minfilter {
  display: grid;
  grid-template-columns: auto 100px auto;
  align-items: center;
  gap: var(--sp-2);
  padding: 4px 10px;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
}
.alliances-minfilter input[type="range"] { width: 100%; }

/* --- Groups --- */
.alliances-groups {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
}

.al-group-header {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  margin-bottom: var(--sp-2);
}
.al-group-label {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-1);
}
.al-group-count {
  font-family: var(--ff-mono);
  font-size: 11px;
  color: var(--text-3);
}

/* --- Alliance table --- */
.alliances-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  overflow: hidden;
  table-layout: fixed;
}
.alliances-table th {
  text-align: left;
  font-size: 10px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-2);
  padding: 8px 14px;
  border-bottom: 1px solid var(--line-strong);
  background: var(--bg-0);
  white-space: nowrap;
}
.alliances-table th.num { text-align: right; }

.alliances-table col,
.al-th-name  { width: 28%; }
.al-th-count { width: auto; }
.al-th-lv    { width: 90px; }
.al-th-avg   { width: 80px; }
.al-th-power { width: 140px; }

tr.al-row {
  cursor: pointer;
  transition: background var(--tr-fast);
}
tr.al-row > td {
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
  font-size: 12px;
}
tr.al-row:last-of-type > td { border-bottom: 0; }
tr.al-row:hover { background: var(--bg-2); }
tr.al-row.is-open {
  background: var(--bg-2);
}
tr.al-row.is-open > td {
  border-bottom-color: var(--line-strong);
}

.al-name-wrap {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.al-chevron {
  color: var(--text-3);
  font-size: 10px;
  width: 10px;
  display: inline-block;
  transition: color var(--tr-fast);
}
tr.al-row.is-open .al-chevron { color: var(--accent); }

.al-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35);
  flex-shrink: 0;
}

.al-abbr {
  font-family: var(--ff-mono);
  font-weight: 600;
  color: var(--accent);
  font-size: 13px;
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.al-count-wrap {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}
.al-count-num {
  font-family: var(--ff-mono);
  font-size: 13px;
  color: var(--text-0);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  min-width: 28px;
}
.al-bar {
  position: relative;
  flex: 1;
  height: 4px;
  max-width: 220px;
  background: var(--line);
  border-radius: 2px;
  overflow: hidden;
}
.al-bar-fill {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  background: var(--accent);
  opacity: 0.7;
  transition: opacity var(--tr-fast);
}
tr.al-row:hover .al-bar-fill { opacity: 1; }

.al-lv-cell,
.al-avg-cell,
.al-power-cell {
  text-align: right;
  font-family: var(--ff-mono);
  font-size: 12.5px;
  color: var(--text-1);
  font-variant-numeric: tabular-nums;
}

/* --- Detail row (expanded) --- */
tr.al-detail > td.al-detail-cell {
  padding: var(--sp-4) var(--sp-5);
  background: var(--bg-0);
  border-bottom: 1px solid var(--line-strong);
}
tr.al-detail:last-child > td.al-detail-cell { border-bottom: 0; }

/* Members subtable inside expanded row */
.al-detail-cell .members-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
  background: transparent;
}
.al-detail-cell .members-table th {
  background: transparent;
  padding: 6px 10px;
  border-bottom: 1px solid var(--line-strong);
  text-align: left;
  font-size: 10px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-3);
}
.al-detail-cell .members-table th.num { text-align: right; }
.al-detail-cell .members-table td {
  padding: 6px 10px;
  border-bottom: 1px solid var(--line);
  color: var(--text-1);
}
.al-detail-cell .members-table td.num {
  text-align: right;
  font-family: var(--ff-mono);
  font-variant-numeric: tabular-nums;
}
.al-detail-cell .members-table tr:last-child td { border-bottom: 0; }
.members-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}
.members-table th {
  text-align: left;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-2);
  font-weight: 500;
  padding: 6px 8px;
  border-bottom: 1px solid var(--line-strong);
}
.members-table td {
  padding: 6px 8px;
  border-bottom: 1px solid var(--line);
  color: var(--text-1);
}
.members-table tr:last-child td { border-bottom: 0; }
/* The whole row is clickable (jumps to the member's castle), but the name
   is the actual "link" a user reads -- same gold+underline hover treatment
   as .aa-nick/.brr-nick (Alliance Activity/Bear Rally rosters), which the
   member row was missing (only cursor:pointer, reported 2026-08-07 as not
   obviously clickable). */
.al-member-nick { cursor: pointer; }
.member-row:hover .al-member-nick { color: var(--accent); text-decoration: underline; }

/* Click-to-sort headers (Name/Lv/Power) in the member subtable. */
.al-detail-cell .members-table th[data-member-sort],
.members-table th[data-member-sort] { cursor: pointer; user-select: none; }
.al-detail-cell .members-table th[data-member-sort]:hover,
.members-table th[data-member-sort]:hover { color: var(--accent); }
.al-sort-arrow { color: var(--accent); }

/* Below 560px: originally a table-layout:fixed + hard-pixel-width fix
   (2026-08-06) for Lv/Position/Status wrapping mid-value. Replaced
   2026-08-08 after swapping Status for Power (reported "power column
   pushed off screen") -- Power's values can run to 7-8 digits with
   commas (e.g. "9,876,543"), which doesn't fit a 64px budget, and
   Position's own worst case ("(1199, 1199)") barely fit its 80px budget
   already. Cramming 4 columns into ~290px of mobile width via fixed
   pixel budgets is inherently fragile. Same stacked-row fix as the Bear
   Rally roster / Forts & Sanctuaries table: hide the header row, turn
   each <tr> into a flex-wrapped row -- Name gets its own prominent line
   (ellipsized if too long), Lv/Power sit inline next to it (both short),
   Position (the longest value) gets its own full-width line below where
   it has room. */
@media (max-width: 560px) {
  /* Header row kept (not display:none) -- it's the only place the
     click-to-sort <th data-member-sort> targets live (Name/Lv/Power;
     Position isn't sortable, see MEMBER_SORT_COLUMNS in alliances.js).
     Hiding it entirely (as first shipped) silently removed the ability
     to sort on mobile -- reported 2026-08-08. Restyled as a compact row
     of tappable pills above the stacked member rows instead of a
     column-aligned header, since the rows below aren't columnar anymore. */
  .al-detail-cell .members-table thead tr,
  .members-table thead tr {
    display: flex; flex-wrap: wrap; gap: 6px;
    padding: 4px 4px 8px;
    border-bottom: 1px solid var(--line-strong);
  }
  .al-detail-cell .members-table th,
  .members-table th {
    padding: 3px 9px; border: 1px solid var(--line); border-radius: 999px;
    font-size: .68rem; white-space: nowrap;
  }
  .al-detail-cell .members-table th:not([data-member-sort]),
  .members-table th:not([data-member-sort]) { display: none; }
  .al-detail-cell .members-table, .al-detail-cell .members-table tbody,
  .members-table, .members-table tbody { display: block; width: 100%; }
  .al-detail-cell .members-table tr,
  .members-table tr {
    display: flex; flex-wrap: wrap; align-items: baseline;
    column-gap: 8px; row-gap: 2px; padding: 6px 4px;
  }
  .al-detail-cell .members-table td,
  .members-table td {
    padding: 0; border: 0; width: auto; white-space: nowrap;
    font-family: inherit; text-align: left;
  }
  .al-detail-cell .members-table td:nth-child(1),
  .members-table td:nth-child(1) {
    order: 1; flex: 1 1 auto; min-width: 0; font-weight: 600;
    overflow: hidden; text-overflow: ellipsis;
  }
  .al-detail-cell .members-table td:nth-child(2),
  .members-table td:nth-child(2) { order: 2; flex: 0 0 auto; font-size: .72rem; }
  .al-detail-cell .members-table td:nth-child(2)::before,
  .members-table td:nth-child(2)::before { content: "Lv "; color: var(--text-3,var(--text-2)); }
  .al-detail-cell .members-table td:nth-child(4),
  .members-table td:nth-child(4) { order: 3; flex: 0 0 auto; font-size: .72rem; }
  .al-detail-cell .members-table td:nth-child(4)::before,
  .members-table td:nth-child(4)::before { content: "Power "; color: var(--text-3,var(--text-2)); }
  .al-detail-cell .members-table td:nth-child(3),
  .members-table td:nth-child(3) {
    order: 4; flex-basis: 100%; font-size: .72rem; color: var(--text-3,var(--text-2));
  }
}

/* ---------- 10. Scan history page ------------------------------------- */

.session-card {
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  overflow: hidden;
}
.session-card-header {
  /* Flex + order, not CSS Grid with fixed pixel column widths -- the old
     grid (grid-template-columns: 58px 170px 110px 80px 1fr auto) sized
     columns for the LONGEST realistic content per field, but the moment
     any field's actual content ran wider than its budget (long stats
     line, added kid/heartbeat fields) it wrapped inside its own grid
     cell instead of reflowing the row, breaking alignment against the
     single-line neighbors. Reported 2026-08-08. Same flex+order
     technique already used by this rule's own <900px override below
     (fixed 2026-08-06 for the identical reason) -- applying it to the
     base rule too means every field just wraps onto its own line when
     it doesn't fit, at any width, instead of only below 900px. Exact
     cross-row column alignment (which the grid approach bought at the
     cost of this bug) is no longer guaranteed field-for-field, but
     every row's own content stays internally aligned and readable,
     which is what actually matters here.  */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--sp-4);
  row-gap: 6px;
  padding: 12px 16px;
  transition: background var(--tr-fast);
}
.session-id-tag {
  order: 1;
  font-family: var(--ff-mono);
  font-size: 11px;
  color: var(--text-2);
  padding: 2px 6px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 3px;
}
.session-kid-tag {
  order: 2;
  font-family: var(--ff-mono);
  font-size: 11px;
  color: var(--text-2);
  padding: 2px 6px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 3px;
}
.session-date {
  order: 3;
  font-family: var(--ff-mono);
  font-size: 12px;
  color: var(--text-1);
  white-space: nowrap;
}
.session-status-badge { order: 4; }
.session-mode-badge { order: 5; }
.session-badge {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 2px 7px;
  border-radius: 3px;
  font-weight: 600;
  white-space: nowrap;
}
.badge-completed   { background: rgba(111, 163, 122, 0.12); color: var(--ok); }
.badge-running     { background: rgba(212, 168, 90, 0.14);  color: var(--accent); }
.badge-interrupted { background: rgba(212, 154, 88, 0.12); color: var(--warn); }
.badge-failed      { background: rgba(194, 106, 90, 0.12); color: var(--danger); }
/* Scan History: session mode (live listener daemon vs a run_tcp_scan run) */
.badge-mode-live { background: rgba(110, 168, 200, 0.14); color: var(--shield); }
.badge-mode-scan { background: rgba(150, 150, 160, 0.12); color: var(--text-1); }

.session-heartbeat {
  order: 6;
  font-size: 11px;
  color: var(--text-3);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
/* >120min stale (see core/utils.js formatAge / cleanup_stale_live_sessions'
   own default staleness window) -- same warning color as badge-interrupted. */
.session-heartbeat.is-stale { color: var(--warn); }

.session-stats {
  /* flex-basis:100% (not margin-left:auto) -- auto-margin only looks
     right when the rest of the row actually fits on the same line and
     stats gets pushed to that line's own right edge. In practice id+kid+
     date+status+mode+heartbeat rarely all fit on one line, so stats
     wrapped to its own line anyway MOST of the time -- but margin-left:
     auto still right-aligned it there too, leaving it dangling on the
     right with nothing above it, unrelated to the left-aligned line
     above. Reported 2026-08-08 ("still looks all over the place").
     flex-basis:100% makes it deterministically own-line + left-aligned
     every time, matching the rest of the row instead of shifting based
     on how much fit above it. */
  order: 7;
  flex-basis: 100%;
  margin-top: 2px;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-5);
  font-size: 12px;
  color: var(--text-2);
}
.session-stat strong {
  color: var(--text-0);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}

/* ---------- 11. Settings page ------------------------------------------ */

.form-field { margin-bottom: var(--sp-4); }
.form-field:last-child { margin-bottom: 0; }
.form-field--checkbox { margin-bottom: var(--sp-3); }

.form-label {
  display: block;
  font-size: 11px;
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 5px;
}
.form-label-inline {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  cursor: pointer;
  font-size: 12px;
  color: var(--text-0);
}
.form-help {
  font-size: 11px;
  color: var(--text-2);
  margin-top: 5px;
  line-height: 1.5;
}
.form-error {
  font-size: 11px;
  color: var(--danger);
  margin-top: 5px;
}
.form-grid-2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-4);
}
.form-grid-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-4);
}
.form-actions {
  display: flex;
  justify-content: flex-end;
  padding-top: var(--sp-4);
}
.btn-save {
  padding: 9px 20px;
  background: var(--accent);
  color: var(--bg-0);
  border: 1px solid var(--accent);
  border-radius: var(--r-2);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: background var(--tr-fast);
}
.btn-save:hover { background: #e4b96a; }

.acct-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: var(--sp-4);
}
.acct-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-4);
  padding: 10px 14px;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  transition: border-color var(--tr-fast);
}
.acct-row:hover { border-color: var(--line-strong); }
.acct-row--inactive { opacity: 0.55; }
.acct-info {
  display: flex;
  gap: var(--sp-4);
  align-items: baseline;
  font-size: 12px;
  flex-wrap: wrap;
}
.acct-label { font-weight: 600; color: var(--text-0); }
.acct-server {
  font-family: var(--ff-mono);
  font-size: 11px;
  color: var(--text-2);
}
.acct-auth-preview {
  font-family: var(--ff-mono);
  font-size: 10.5px;
  color: var(--text-3);
}
.acct-used {
  font-size: 10.5px;
  color: var(--text-2);
}
.acct-actions {
  display: flex;
  gap: var(--sp-2);
}
.acct-auth-full {
  flex-basis: 100%;
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: var(--sp-2);
  padding-top: var(--sp-2);
  border-top: 1px dashed var(--line);
}
/* `display: flex` above wins over the UA `[hidden] { display: none }`,
   so we restore the hidden behaviour explicitly. */
.acct-auth-full[hidden] { display: none; }

.form-config-status {
  padding: 8px 12px;
  border-radius: var(--r-2);
  font-size: 12px;
  border: 1px solid transparent;
}
.form-config-status--pending { background: var(--bg-2); color: var(--text-2); border-color: var(--line); }
.form-config-status--ok      { background: rgba(111, 200, 122, 0.12); color: #8fc59c; border-color: rgba(111, 200, 122, 0.4); }
.form-config-status--err     { background: rgba(194, 106, 90, 0.12); color: #e89685; border-color: rgba(194, 106, 90, 0.4); }
.form-config-status[hidden]  { display: none; }
.text-success { color: #8fc59c; }
.text-danger { color: var(--danger); }

/* Settings card Danger Zone — permanently delete a kingdom's data
   (map/templates/map/_settings_card.html, map/views/misc.py's
   KingdomDeleteView). Same .card sectioning as the rest of this card,
   just with a warning-colored left accent so it visually reads as
   "different from the settings above it" at a glance. */
.dz-card { border-left: 3px solid var(--danger); }
.dz-preview {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
}
.dz-preview-counts {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-family: var(--ff-mono);
  font-size: 12px;
}
.dz-row {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-3);
  color: var(--text-1);
}
.dz-row strong { color: var(--text-0); font-variant-numeric: tabular-nums; }
.autoscan-toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-top: var(--sp-2);
}
.acct-auth-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.acct-auth-textarea {
  width: 100%;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-1);
  color: var(--text-0);
  font-family: var(--ff-mono);
  font-size: 11px;
  padding: 6px 8px;
  resize: vertical;
  word-break: break-all;
  white-space: pre-wrap;
}
.acct-empty {
  padding: var(--sp-6) var(--sp-4);
  text-align: center;
  color: var(--text-2);
  font-size: 12px;
  background: var(--bg-2);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-2);
}
.acct-add-details {
  border-top: 1px solid var(--line);
  padding-top: var(--sp-3);
}
.acct-add-details summary {
  display: inline-flex;
  list-style: none;
  cursor: pointer;
}
.acct-add-details summary::-webkit-details-marker { display: none; }
.acct-add-form {
  margin-top: var(--sp-4);
  padding: var(--sp-4);
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
}
.acct-textarea {
  font-family: var(--ff-mono);
  font-size: 10.5px;
  resize: vertical;
  min-height: 60px;
}

/* ---------- 12. Responsive --------------------------------------------- */

@media (max-width: 900px) {
  :root { --rail-left-w: 240px; }
  .page-main { padding: var(--sp-5) var(--sp-4); }
  .form-grid-2, .form-grid-3 { grid-template-columns: 1fr; }
  /* The base rule is flex+order now too (see .session-card-header comment
     above it -- fixed 2026-08-08 for the same reason this override
     originally existed, reported 2026-08-06). This block only needs to
     add mobile-specific line breaks/widths on top of that, not redefine
     display/wrap/align from scratch. */
  .session-card-header { column-gap: var(--sp-3); }
  .session-date { flex: 1 1 auto; min-width: 0; }
  /* Zero-width forced-break spacers (the flex line-break trick) instead
     of flex-basis:100% directly on the badges -- a flex item with
     flex-basis:100% stretches to fill the whole line, which for a colored
     badge pill meant its background smeared edge-to-edge instead of
     hugging "Failed"/"Completed". order values interleave with the base
     rule's own (id=1, kid=2, date=3, status=4, mode=5, heartbeat=6,
     stats=7) so each logical group gets its own line: id+kid+date, then
     status+mode, then heartbeat, then stats. */
  .session-card-header::before { content: ""; order: 4; flex-basis: 100%; width: 0; }
  .session-status-badge { order: 5; }
  .session-mode-badge { order: 6; }
  .session-card-header::after { content: ""; order: 7; flex-basis: 100%; width: 0; }
  .session-heartbeat { order: 8; flex-basis: 100%; }
  .session-stats {
    order: 9; flex-basis: 100%; flex-wrap: wrap; row-gap: 4px;
    gap: var(--sp-3); justify-content: flex-start; margin-left: 0;
  }
  .alliances-head { flex-direction: column; align-items: stretch; gap: var(--sp-3); }
  .alliances-controls { flex-direction: column; align-items: stretch; }
  .alliances-controls .search-input { width: 100%; }
  .alliances-table { font-size: 11px; }
  /* width:0 in addition to display:none: table-layout:fixed's column-width
     model can still reserve Lv/Avg/Power's declared 90px/80px/140px (from
     the >900px base rule) once the .al-detail colspan=5 row renders --
     display:none hides the cell but doesn't zero its column's width
     contribution in that case, so the count column/bar silently lost
     ~100px (visually disappeared) the moment an alliance row was
     expanded. Reported 2026-08-08. Explicit width:0 removes the
     reservation outright. Power (added later, same treatment) hidden on
     mobile for the same reason Lv/Avg are -- not enough width to show
     five columns without squeezing the name/count columns unreadable. */
  .al-th-lv, .al-th-avg, .al-th-power,
  tr.al-row > td:nth-child(3), tr.al-row > td:nth-child(4), tr.al-row > td:nth-child(5)
    { display: none; width: 0; }
  .al-bar { max-width: 120px; }
  /* table-layout:fixed keeps the name column pinned at its 28% share
     (set for the 4-column desktop layout) even after Lv/Avg are hidden
     above -- reported 2026-08-06: with only ~28% of a narrow mobile
     table to work with, .al-abbr's overflow:ellipsis safety net was
     kicking in on every tag ("[SOS]" rendering as "[SO…"). Widen it now
     that two columns' worth of space is actually free. */
  .al-th-name { width: 55%; }
  /* .al-th-count was width:auto (the base/desktop rule) -- the one truly
     unspecified column left once Lv/Avg get width:0 above. Per the
     table-layout:fixed spec, an unspecified column's width can be
     partly redistributed from a colspanning cell's content width
     (divided across the columns it spans) -- almost certainly why the
     count column/bar collapsed the moment .al-detail-cell (colspan=4,
     containing the member subtable) actually rendered, immediately
     after expanding a row. An explicit width here removes the last
     unspecified column, so there's nothing left to redistribute. */
  .al-th-count { width: 45%; }
}

/* =============================================================================
 * 13. Glass-on-map (v2 layout, activated on body.app-v2)
 *
 * Full-bleed Leaflet map with floating glass cards. See plan:
 * C:\Users\Sbedu\.claude\plans\polymorphic-crunching-piglet.md
 * ========================================================================== */

body.app-v2 {
  overflow: hidden;
  background: #000;
}

body.app-v2 #map,
body.app-v2 .map-full {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  z-index: var(--z-map);
}

/* ---------- Topbar ---------- */

.topbar {
  position: fixed;
  top: var(--sp-3);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  height: var(--topbar-h);
  padding: 0 var(--sp-3);
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  border-radius: var(--card-radius);
  box-shadow: var(--glass-shadow);
  color: var(--text-0);
  z-index: var(--z-topbar);
  font-size: 13px;
  max-width: calc(100vw - var(--sp-5));
}

@supports (backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px)) {
  .topbar { backdrop-filter: blur(14px) saturate(1.3); -webkit-backdrop-filter: blur(14px) saturate(1.3); }
}
@supports not ((backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px))) {
  .topbar { background: var(--glass-bg-opaque); }
}

/* Left-side rail for opening/closing cards. Collapsed to an icon-only
   strip by default, expands on hover -- was always full-width with
   labels, which read as permanently intrusive (screen space it ate
   whether or not you were using it) and, combined with the drag-clamp
   only keeping cards inside the viewport (not clear of the rail),
   made it easy to drag a card's left edge to where the rail would cover
   it. Shrinking the rail's resting footprint shrinks that problem too,
   since cards.js's railClearance() reads the rail's live width (see
   the drag-clamp fix in bindDrag/​_handleResizeImmediate). */
.card-rail {
  position: fixed;
  top: calc(var(--topbar-h) + var(--sp-3) * 2 + 24px);
  left: var(--sp-3);
  z-index: calc(var(--z-topbar) - 1);
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px;
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  border-radius: var(--card-radius);
  box-shadow: var(--glass-shadow);
  width: 44px;
  overflow: hidden;
  transition: width var(--tr-base);
}
.card-rail:hover, .card-rail:focus-within {
  width: 190px;
}
@supports (backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px)) {
  .card-rail { backdrop-filter: blur(14px) saturate(1.3); -webkit-backdrop-filter: blur(14px) saturate(1.3); }
}
.card-rail .rail-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: 6px 10px 6px 8px;
  min-height: 32px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  color: var(--text-2);
  cursor: pointer;
  font-size: 12px;
  text-align: left;
  transition: background var(--tr-fast), color var(--tr-fast), border-color var(--tr-fast);
}
.card-rail .rail-btn:hover { background: rgba(255,255,255,0.06); color: var(--text-0); }
.card-rail .rail-btn.is-active {
  background: rgba(230,184,0,0.12);
  border-color: rgba(230,184,0,0.45);
  color: var(--accent);
}
/* Collapsed: bigger, centered icon, label fully removed from layout (not
   just clipped) -- clipping via the parent's overflow:hidden alone still
   left 1-2 characters of each differently-sized label peeking out past
   the icon, a ragged "zigzag" look since no two labels clip at the same
   letter. Expanded (hover/focus-within): shrink the icon back down and
   left-align icon+label as a normal row. */
.card-rail .rail-ico { width: 20px; text-align: center; font-size: 18px; line-height: 1; flex-shrink: 0; transition: width var(--tr-fast), font-size var(--tr-fast); }
.card-rail .rail-lbl { display: none; white-space: nowrap; }
.card-rail:hover .rail-btn, .card-rail:focus-within .rail-btn { justify-content: flex-start; }
.card-rail:hover .rail-ico, .card-rail:focus-within .rail-ico { width: 16px; font-size: 14px; }
.card-rail:hover .rail-lbl, .card-rail:focus-within .rail-lbl { display: inline; }
.card-rail .rail-sep { height: 1px; background: var(--glass-border); margin: 4px 0; }

/* Rail groups (Scanner / Reports, 2026-08-09): the staff+/superuser-only
   tail of the rail had grown to 9 flat buttons on top of the 10 public
   ones -- these two accordion groups fold the least-frequently-used
   cluster (ops config + analytical reports) behind a single header each,
   expanded inline (not a floating popover) so the exact same mechanism
   works unchanged in both the desktop hover-rail and the mobile bottom
   picker -- no separate mobile positioning logic needed. */
.rail-group { display: flex; flex-direction: column; }
/* Chevron only needs to sit at the far edge once a label is actually
   showing (hover/mobile) -- matches .card-rail .rail-btn's own
   specificity (2 classes) so it wins on source order without !important. */
.card-rail:hover .rail-group-toggle,
.card-rail:focus-within .rail-group-toggle,
body.app-v2.is-mobile .card-rail .rail-group-toggle { justify-content: space-between; }
.rail-group-chevron {
  display: none;
  color: var(--text-3);
  font-size: 10px;
  transition: transform var(--tr-fast);
  flex-shrink: 0;
}
.card-rail:hover .rail-group-chevron,
.card-rail:focus-within .rail-group-chevron,
body.app-v2.is-mobile .rail-group-chevron { display: inline-block; }
.rail-group-toggle.is-group-open .rail-group-chevron { transform: rotate(90deg); }
.rail-group-body { display: flex; flex-direction: column; gap: 4px; margin-top: 2px; }
/* The [hidden] attribute's UA-stylesheet display:none loses to the
   display:flex above (author rules always beat UA rules regardless of
   specificity) -- without this, collapsed groups render expanded. */
.rail-group-body[hidden] { display: none; }
/* .card-rail prefix matches .card-rail .rail-btn's specificity so this
   padding-left actually wins (a bare .rail-subbtn rule would lose to the
   base rule's padding shorthand and never take effect). */
.card-rail .rail-subbtn {
  padding-left: 22px;
  position: relative;
  font-size: 11.5px;
}
/* Connecting rule so nested items read as "belonging to" the group header
   above them, not just indented -- rail-btn's own padding-left (8px) plus
   the extra offset here keeps the line clear of the icon. */
.rail-subbtn::before {
  content: "";
  position: absolute;
  left: 12px;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--glass-border);
}
.card-rail .rail-subbtn .rail-ico { width: 16px; font-size: 13px; }

/* Mobile: rail becomes a bottom picker shown on demand -- always full
   width with labels (no collapse/hover behavior there; touch has no
   hover, and the picker is only ever shown on demand already). */
body.app-v2.is-mobile .card-rail {
  top: auto;
  bottom: 104px;
  left: 50%;
  transform: translateX(-50%) translateY(16px);
  width: auto;
  max-height: calc(100vh - 200px);
  overflow-y: auto;
  overflow-x: visible;
  display: none;
  z-index: calc(var(--z-sheet) + 2);
}
body.app-v2.is-mobile .card-rail.is-mobile-open {
  display: flex;
  transform: translateX(-50%) translateY(0);
}
body.app-v2.is-mobile .card-rail .rail-btn { min-height: 40px; font-size: 13px; justify-content: flex-start; }
/* Touch has no hover -- the desktop collapse/expand-on-hover rules above
   never fire, so force the always-full-size icon + visible label here
   instead of leaving them stuck in the collapsed (icon-only) state. */
body.app-v2.is-mobile .card-rail .rail-ico { width: 16px; font-size: 14px; }
body.app-v2.is-mobile .card-rail .rail-lbl { display: inline; }
/* Hide Leaflet zoom controls on mobile (covered by topbar) */
body.app-v2.is-mobile .leaflet-control-zoom,
body.app-v2.is-mobile .leaflet-top.leaflet-left { display: none; }

.topbar-brand {
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.kingdom-toggle {
  display: inline-flex;
  border: 1px solid var(--glass-border-hi);
  border-radius: var(--r-2);
  height: 22px;
  overflow: hidden;
  flex-shrink: 0;
}
.kid-btn {
  padding: 0 8px;
  height: 100%;
  min-width: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--ff-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.02em;
  background: transparent;
  color: var(--text-2);
  transition: background var(--tr-fast), color var(--tr-fast);
}
.kid-btn.is-active { background: var(--accent-soft); color: var(--accent); }
.kid-btn:not(.is-active):hover { background: rgba(255, 255, 255, 0.06); color: var(--text-0); }
.kid-label-short { display: none; }
/* Compact kingdom picker: unconditional on mobile (app.css mobile block
   below), and on desktop/tablet when kingdom.js's _syncCompactMode()
   measures the pill row as not actually fitting the topbar (reported
   2026-08-10: 8 kingdoms clipped the language toggle off-screen at
   1440px) -- .is-compact is added to #kingdom-selector in that case. */
.kid-select {
  display: none;
  width: 68px;
  height: 22px;
  padding: 0 2px;
  border: 1px solid var(--glass-border-hi);
  border-radius: var(--r-2);
  background: transparent;
  color: var(--text-0);
  font-family: var(--ff-mono);
  font-size: 10px;
  font-weight: 600;
}
.kid-select option { color: #111; }
.kingdom-toggle.is-compact .kid-btn { display: none; }
.kingdom-toggle.is-compact .kid-select { display: inline-block; }
.topbar-sep {
  width: 1px;
  height: 20px;
  background: var(--glass-border);
}
.topbar-nav {
  display: flex;
  gap: 2px;
}
.topbar-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  height: 30px;
  padding: 0 var(--sp-2);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-2);
  color: var(--text-1);
  font-size: 14px;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--tr-fast), color var(--tr-fast), border-color var(--tr-fast);
}
.topbar-btn:hover { background: rgba(255, 255, 255, 0.06); color: var(--text-0); }
.topbar-btn.is-active {
  background: var(--accent-soft);
  color: var(--accent);
  border-color: rgba(212, 168, 90, 0.35);
}
/* Topbar next-scan countdown — replaces the old free-text search input */
.topbar-countdown {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 10px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid rgba(212, 168, 90, 0.25);
  border-radius: var(--r-2);
  color: var(--text-1);
  font-family: var(--ff-mono);
  font-size: 12px;
  line-height: 1;
  white-space: nowrap;
  flex-shrink: 0;
}
.topbar-countdown[hidden] { display: none; }
.topbar-countdown-ico { opacity: 0.7; }
.topbar-countdown-label { color: var(--text-2); }
.topbar-countdown strong { color: var(--accent); font-weight: 600; }
.topbar-status {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--text-2);
  font-size: 11px;
  font-family: var(--ff-mono);
  white-space: nowrap;
}
.topbar-status .pulse {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-3);
}
.topbar-status .pulse.is-live { background: var(--ok); box-shadow: 0 0 8px var(--ok); animation: pulse 1.6s ease-out infinite; }
@keyframes pulse {
  0%   { opacity: 1;   transform: scale(1); }
  70%  { opacity: 0.3; transform: scale(1.4); }
  100% { opacity: 1;   transform: scale(1); }
}

/* ---------- Topbar auth ---------- */

.topbar-auth {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: 8px;
}
.topbar-link {
  color: inherit;
  text-decoration: none;
  padding: 4px 10px;
  border-radius: 4px;
  font-size: 13px;
  /* Button reset for the logout form variant */
  background: none;
  border: none;
  cursor: pointer;
  font: inherit;
  line-height: inherit;
}
.topbar-link:hover {
  background: rgba(255, 255, 255, 0.08);
}
.topbar-logout-form {
  display: inline;
  margin: 0;
  padding: 0;
}
.topbar-user {
  opacity: 0.7;
  font-size: 13px;
}

/* ---------- Cards ---------- */

.card-layer {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: var(--z-card);
}
/* .card-layer's own position+z-index makes it a stacking context: a
   card's z-index only orders it among its sibling cards, it can never
   escape above the topbar/rail on its own no matter how high (see
   .is-maximized below). Raising the whole layer while any card is
   maximized is what actually lets it cover them -- see
   core/cards.js:syncBodyMaximizedClass(). */
body.has-maximized-card .card-layer { z-index: var(--z-card-max); }
/* All glass-card selectors are scoped under .card-layer so they don't clash
   with the legacy `.card` primitive used inside settings/history/etc. */
.card-layer > .card { pointer-events: auto; }
.card-layer > .card * { pointer-events: auto; }

/* Neutralize legacy id-scoped positioning when the element is a v2 card */
body.app-v2 .card-layer > .card#live-updates-panel {
  position: absolute;
  right: auto;
  top: auto;
  max-height: none;
  width: auto;
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  border-radius: var(--card-radius);
  box-shadow: var(--glass-shadow);
  z-index: auto;
}
body.app-v2 .card-layer > .card#live-updates-panel.is-hidden { display: none; }
/* Use flex column (not block) so the inner .card-body keeps the flex:1
   sizing required for overflow:auto to produce a working scrollbar. */
body.app-v2 .card-layer > .card#live-updates-panel:not(.is-hidden) {
  display: flex;
  flex-direction: column;
}
/* On mobile the legacy-id card must flow in the sheet, not stay absolutely positioned */
body.app-v2.is-mobile .card-layer > .card#live-updates-panel {
  position: static !important;
  left: auto !important;
  top: auto !important;
  width: auto !important;
  height: auto !important;
}

.card-layer > .card {
  position: absolute;
  min-width: 200px;
  min-height: 80px;
  max-width: calc(100vw - var(--sp-4));
  max-height: calc(100vh - var(--sp-4));
  display: flex;
  flex-direction: column;
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  border-radius: var(--card-radius);
  box-shadow: var(--glass-shadow);
  color: var(--text-0);
  font-size: 12px;
  overflow: hidden;
  transition: box-shadow var(--tr-base), border-color var(--tr-base);
  padding: 0; /* neutralize legacy .card padding */
}
/* Hide cards until the engine has positioned them — prevents a flash at (0,0). */
.card-layer > .card:not(.is-ready) { visibility: hidden; }

@supports (backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px)) {
  .card-layer > .card { backdrop-filter: blur(14px) saturate(1.2); -webkit-backdrop-filter: blur(14px) saturate(1.2); }
}
@supports not ((backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px))) {
  .card-layer > .card { background: var(--glass-bg-opaque); }
}

.card-layer > .card.is-focus {
  border-color: var(--glass-border-hi);
  box-shadow: var(--glass-shadow-hi), 0 0 0 1px rgba(212, 168, 90, 0.25);
}
.card-layer > .card.is-focus > .card-header > .card-title {
  color: var(--accent);
}
.card-layer > .card.is-pinned .card-header { cursor: default; }
.card-layer > .card.is-dragging {
  transition: none;
  user-select: none;
  box-shadow: var(--glass-shadow-hi);
}
/* Maximize: fills the viewport, above the topbar (300) and card-rail
   (299) which cards otherwise always sit under -- !important is required
   because inline styles (left/top/width/height from applyRect(), z-index
   from focus()) normally win the cascade over a class selector; nothing
   else in this file relies on beating an inline style, so scoping it
   this tightly is deliberate rather than a habit to repeat elsewhere.
   Un-maximizing just removes the class -- the underlying inline rect was
   never touched, so it's already correct underneath. */
.card-layer > .card.is-maximized {
  inset: 0 !important;
  width: auto !important;
  height: auto !important;
  max-width: 100vw;
  max-height: 100vh;
  border-radius: 0;
  z-index: var(--z-card-max) !important;
}
.card-layer > .card.is-maximized > .card-resize { display: none; }
/* Maximize doesn't apply on mobile (already a full bottom sheet) --
   hide the button so it isn't a dead control. */
body.app-v2.is-mobile .card-layer > .card .card-action[data-act="maximize"] { display: none; }
.card-layer > .card.is-hidden { display: none; }

.card-layer > .card > .card-header {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  height: var(--card-header-h);
  padding: 0 var(--sp-2) 0 var(--sp-3);
  border-bottom: 1px solid var(--glass-border);
  cursor: grab;
  user-select: none;
  touch-action: none;
  background: rgba(0, 0, 0, 0.15);
  flex-shrink: 0;
}
.card-layer > .card.is-dragging > .card-header { cursor: grabbing; }
.card-layer > .card > .card-header > .card-title {
  flex: 1;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-1);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* reset legacy .card-title block styles */
  margin: 0;
  padding: 0;
  border: none;
  background: transparent;
}
.card-layer > .card > .card-header > .card-actions {
  display: flex;
  gap: 2px;
  flex-shrink: 0;
}
.card-layer > .card .card-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: var(--r-1);
  color: var(--text-2);
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--tr-fast), color var(--tr-fast);
}
.card-layer > .card .card-action:hover { background: rgba(255, 255, 255, 0.08); color: var(--text-0); }
.card-layer > .card .card-action.is-active { color: var(--accent); }

.card-layer > .card > .card-body {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: var(--sp-3);
}
.card-layer > .card > .card-body::-webkit-scrollbar { width: 6px; }
.card-layer > .card > .card-body::-webkit-scrollbar-thumb { background: var(--glass-border-hi); border-radius: 3px; }

.card-layer > .card > .card-resize {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 14px;
  height: 14px;
  cursor: nwse-resize;
  touch-action: none;
  opacity: 0.5;
}
.card-layer > .card > .card-resize::after {
  content: "";
  position: absolute;
  right: 3px;
  bottom: 3px;
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--text-2);
  border-bottom: 2px solid var(--text-2);
}
.card-layer > .card.is-pinned > .card-resize { display: none; }

/* ---------- Minimized chips (dock) ---------- */

.card-dock {
  position: fixed;
  display: flex;
  gap: var(--sp-2);
  padding: var(--sp-2);
  z-index: calc(var(--z-card) + 10);
  pointer-events: none;
}
.card-dock > * { pointer-events: auto; }
.card-dock.is-bottom {
  bottom: var(--sp-3);
  left: 50%;
  transform: translateX(-50%);
  flex-direction: row;
}

.card-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  height: 28px;
  padding: 0 var(--sp-3);
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  border-radius: 14px;
  box-shadow: var(--glass-shadow);
  color: var(--text-1);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  cursor: pointer;
  transition: background var(--tr-fast), color var(--tr-fast), border-color var(--tr-fast);
}
.card-chip:hover { color: var(--text-0); border-color: var(--glass-border-hi); }

@supports (backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px)) {
  .card-chip { backdrop-filter: blur(12px) saturate(1.2); -webkit-backdrop-filter: blur(12px) saturate(1.2); }
}
@supports not ((backdrop-filter: blur(12px)) or (-webkit-backdrop-filter: blur(12px))) {
  .card-chip { background: var(--glass-bg-opaque); }
}

/* ---------- Bottom sheet (mobile) ---------- */

body.app-v2.is-mobile .card-dock { display: none; }

/* Mobile mode: .card-layer transforms into a bottom sheet.
   Cards inside lose absolute positioning and become a stacked list. */
body.app-v2.is-mobile .card-layer {
  inset: auto 0 0 0;
  top: auto;
  height: 70vh;
  max-height: 70vh;
  background: var(--glass-bg-opaque);
  border-top: 1px solid var(--glass-border);
  border-top-left-radius: 14px;
  border-top-right-radius: 14px;
  box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.5);
  transform: translateY(calc(100% - 48px));
  transition: transform var(--tr-base);
  pointer-events: auto;
  overflow-y: auto;
  padding: 8px;
  padding-top: 20px;
  z-index: var(--z-sheet);
  touch-action: pan-y;
  cursor: pointer;
}
body.app-v2.is-mobile .card-layer.is-sheet-open {
  transform: translateY(0);
}
/* Sheet drag handle at the very top */
body.app-v2.is-mobile .card-layer::before {
  content: "";
  position: sticky;
  top: 0;
  display: block;
  width: 40px;
  height: 4px;
  margin: 4px auto 8px;
  background: var(--glass-border-hi);
  border-radius: 2px;
  flex-shrink: 0;
}
body.app-v2.is-mobile .card-layer > .card {
  position: static;
  left: auto !important;
  top: auto !important;
  width: auto !important;
  height: auto !important;
  max-width: none;
  max-height: none;
  margin: 0 0 8px 0;
  flex-shrink: 0;
  pointer-events: auto;
  visibility: visible;
}
body.app-v2.is-mobile .card-layer > .card.is-hidden { display: none; }
body.app-v2.is-mobile .card-layer > .card > .card-header { cursor: default; }
body.app-v2.is-mobile .card-layer > .card > .card-resize { display: none; }
/* Sheet toggle button sits above the sheet when it's closed */
.sheet-toggle {
  display: none;
  position: fixed;
  left: 50%;
  bottom: 56px;
  transform: translateX(-50%);
  padding: 6px 14px;
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  border-radius: 16px;
  box-shadow: var(--glass-shadow);
  color: var(--text-0);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  cursor: pointer;
  z-index: calc(var(--z-sheet) + 1);
}
body.app-v2.is-mobile .sheet-toggle { display: inline-flex; }
body.app-v2.is-mobile .card-layer.is-sheet-open ~ .sheet-toggle { display: none; }

.bottom-sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 70vh;
  display: flex;
  flex-direction: column;
  background: var(--glass-bg-opaque);
  border-top: 1px solid var(--glass-border);
  border-radius: 14px 14px 0 0;
  box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.5);
  color: var(--text-0);
  z-index: var(--z-sheet);
  transform: translateY(calc(100% - 48px));
  transition: transform var(--tr-base);
}
.bottom-sheet.is-open { transform: translateY(0); }
.bottom-sheet-handle {
  display: flex;
  justify-content: center;
  padding: 10px 0 6px;
  cursor: grab;
  touch-action: none;
}
.bottom-sheet-handle::after {
  content: "";
  width: 40px;
  height: 4px;
  background: var(--glass-border-hi);
  border-radius: 2px;
}
.bottom-sheet-tabs {
  display: flex;
  gap: var(--sp-1);
  padding: 0 var(--sp-3) var(--sp-2);
  overflow-x: auto;
  border-bottom: 1px solid var(--glass-border);
}
.bottom-sheet-tab {
  flex-shrink: 0;
  padding: 6px 10px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-2);
  color: var(--text-2);
  font-family: inherit;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  cursor: pointer;
}
.bottom-sheet-tab.is-active {
  background: var(--accent-soft);
  color: var(--accent);
  border-color: rgba(212, 168, 90, 0.35);
}
.bottom-sheet-body {
  flex: 1;
  overflow: auto;
  padding: var(--sp-3);
}

/* Mobile-only topbar tweaks */
/* Language toggle inside the topbar — override sidebar-tuned defaults. */
.topbar .lang-toggle {
  border: 1px solid var(--glass-border-hi);
  border-radius: var(--r-2);
  height: 26px;
  overflow: hidden;
  flex-shrink: 0;
}
.topbar .lang-btn {
  padding: 0 8px;
  height: 100%;
  min-width: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--text-1);
  background: transparent;
}
.topbar .lang-btn.is-active {
  background: var(--accent-soft);
  color: var(--accent);
}
.topbar .lang-btn:not(.is-active):hover {
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-0);
}

body.app-v2.is-mobile .topbar {
  left: var(--sp-2);
  right: var(--sp-2);
  transform: none;
  max-width: calc(100vw - var(--sp-4));
  gap: var(--sp-2);
  padding: 0 var(--sp-2);
  overflow: hidden;
}
body.app-v2.is-mobile .topbar-brand span:not(.topbar-kid) { display: none; }
/* Even the short-form pill row (bare "1259", "1343", ...) doesn't scale --
   3+ kingdoms pushed the language toggle almost entirely off-screen
   (reported 2026-08-10; topbar has overflow:hidden and no wrap). Switch to
   a single fixed-width <select> on mobile instead: constant width
   regardless of kingdom count, and the OS's own picker UI handles any
   number of options with no custom dropdown/overflow logic needed. */
body.app-v2.is-mobile .kingdom-toggle .kid-btn { display: none; }
body.app-v2.is-mobile .kid-select { display: inline-block; }
/* On mobile keep the countdown label visible (the icon alone wasn't obvious
   enough — users couldn't tell what the value referred to) but tighten the
   chip and drop the icon to save horizontal space. */
body.app-v2.is-mobile .topbar-countdown {
  padding: 0 6px;
  font-size: 11px;
  gap: 4px;
}
body.app-v2.is-mobile .topbar-countdown-ico { display: none; }
body.app-v2.is-mobile .topbar-coord { display: none; }
body.app-v2.is-mobile .topbar-sep { display: none; }
body.app-v2.is-mobile #btn-reset-layout { display: none; }
body.app-v2.is-mobile .topbar-status {
  font-size: 10px;
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* On narrow phones the topbar-status is compressed; the scan-badge was
   getting clipped past the ellipsis. Drop the less urgent "last scan" date
   on mobile so the blinking scan / live badges always show, and pin the
   badges themselves so flex can't shrink them away. #topbar-kingdom (full
   "Kingdom 1259" label) is dropped too -- redundant now that .kid-select
   already shows the current kingdom compactly at the start of the bar,
   and it doesn't fit here regardless (was clipping mid-word). */
body.app-v2.is-mobile .topbar-status #topbar-last-scan,
body.app-v2.is-mobile .topbar-status #topbar-kingdom,
body.app-v2.is-mobile .topbar-status .topbar-status-sep {
  display: none;
}
body.app-v2.is-mobile .topbar-status .scan-badge,
body.app-v2.is-mobile .topbar-status .live-badge {
  flex-shrink: 0;
}
body.app-v2.is-mobile .topbar .lang-toggle { flex-shrink: 0; }

/* ---------- Tablet snap mode (768 - 1279) ---------- */

@media (min-width: 768px) and (max-width: 1279px) {
  body.app-v2.is-tablet .card {
    /* Positions managed by cards.js snap logic — no CSS constraints needed. */
  }
}


/* ---------- Scanner accounts card (staff-only) ---------- */
.scanner-accounts-page { display:flex; flex-direction:column; gap:var(--sp-3); }
.sa-head { display:flex; align-items:center; justify-content:space-between; gap:var(--sp-3); }
.sa-title { font-size:1.1rem; margin:0; }
.sa-status { font-size:.85rem; color:var(--text-2); }
.sa-list { display:flex; flex-direction:column; gap:var(--sp-2); }
.sa-row {
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  padding: var(--sp-2);
}
.sa-row-head {
  display:flex; align-items:center; justify-content:space-between;
  gap:var(--sp-2); flex-wrap:wrap;
}
.sa-row-label { display:flex; align-items:center; gap:var(--sp-1); font-weight:600; }
.sa-row-host { font-family: var(--ff-mono); }
.sa-badge {
  display:inline-block; padding:1px 6px; border-radius:10px;
  font-size:.7rem; line-height:1.2;
  background:var(--accent,#4a8fff); color:#0a0a0a;
}
/* WCAG AA fix 2026-07-16: -ok/-warn were 3.24:1/2.92:1 with white text
   at .7rem (needs 4.5:1) -- darkened, same hue. -err was already 5.44:1. */
.sa-badge-ok { background:#2f8355; color:#fff; }
.sa-badge-warn { background:#9b6a2b; color:#fff; }
.sa-badge-err { background:#c0392b; color:#fff; }
.sa-row-meta {
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
  gap:var(--sp-2); margin:var(--sp-2) 0 0; padding:0;
  font-size:.85rem;
}
.sa-row-meta > div { display:flex; flex-direction:column; gap:2px; }
.sa-row-meta dt { color:var(--text-2); margin:0; font-size:.75rem; text-transform:uppercase; letter-spacing:.04em; }
.sa-row-meta dd { margin:0; font-family:var(--ff-mono); }
.sa-row-form { padding-top:var(--sp-2); border-top:1px dashed var(--line); margin-top:var(--sp-2); }
.sa-form { display:flex; flex-direction:column; gap:var(--sp-2); }
.sa-form .field { display:flex; flex-direction:column; gap:4px; }
.sa-form .field label { font-size:.8rem; color:var(--text-2); }
.sa-form input, .sa-form textarea {
  background:var(--bg-3); border:1px solid var(--line);
  color:var(--text,#eee); border-radius:6px; padding:6px 8px;
}
.sa-form textarea { font-family:var(--ff-mono); font-size:.78rem; word-break:break-all; }
.sa-form .field-row { display:grid; grid-template-columns:1fr 1fr; gap:var(--sp-2); }
.sa-form-actions { display:flex; flex-wrap:wrap; gap:var(--sp-1); }
.sa-form-msg {
  padding:6px 8px; border-radius:6px; font-size:.85rem;
  background:var(--bg-3); border:1px solid var(--line);
}
.sa-msg-ok   { border-color:#3aa169; color:#3aa169; }
.sa-msg-err  { border-color:#c4424a; color:#c4424a; }
.sa-msg-warn { border-color:#c98a3a; color:#c98a3a; }
.sa-msg-info { color:var(--text-2); }
@media (max-width: 640px) {
  .sa-row-meta { grid-template-columns:1fr; }
  .sa-form .field-row { grid-template-columns:1fr; }
}

/* ---------- Alliance activity report card (staff-only) ----------
   "Signal beacon" heat theme: activity intensity reads as watch-fire
   brightness (ember -> gold -> flare), echoing the game's own rally-
   beacon vocabulary instead of a generic heatmap gradient. Reuses the
   app's real tokens (--accent is already this gold) plus a few local
   ramp stops scoped to .aa-page since they're specific to this card. */
.aa-page {
  display:flex; flex-direction:column; gap:var(--sp-3);
  --aa-ember: #3d2a16; --aa-hot: #ff9d3d; --aa-flare: #ffd88a;
  --aa-cell-off: var(--bg-3);
  /* Cool, unambiguously-different-from-warm-amber highlight for the
     beacon chart's top-3 hours -- a taller bar already reads "hotter"
     within the existing ember/hot/flare ramp, so distinguishing the
     top-3 needs a different hue entirely, not just more of the same
     gradient. */
  --aa-peak: #52d6e0;
}
.aa-controlbar {
  display:flex; align-items:flex-end; gap:var(--sp-3); flex-wrap:wrap;
  background:var(--bg-2); border:1px solid var(--line); border-radius:var(--r-3);
  padding:var(--sp-3);
}
.aa-field { display:flex; flex-direction:column; gap:4px; }
.aa-field label { font-size:.68rem; font-weight:600; letter-spacing:.06em; text-transform:uppercase; color:var(--text-2); }
.aa-field input {
  background:var(--bg-3); border:1px solid var(--line-strong,var(--line));
  color:var(--text-0); border-radius:var(--r-2); padding:6px 9px; font-size:.85rem;
}
.aa-field input:focus { outline:2px solid var(--accent); outline-offset:1px; }
#aa-abbr { width:110px; }
#aa-days { width:64px; font-family:var(--ff-mono); }
.aa-runbtn { align-self:flex-end; }
.aa-statgroup { display:flex; gap:var(--sp-5,20px); margin-left:auto; flex-wrap:wrap; }
.aa-stat { text-align:right; }
.aa-stat-n { font-family:var(--ff-mono); font-variant-numeric:tabular-nums; font-size:1.15rem; font-weight:700; line-height:1; }
.aa-stat-l { font-size:.62rem; letter-spacing:.06em; text-transform:uppercase; color:var(--text-2); margin-top:2px; }

.aa-status { font-size:.85rem; color:var(--text-2); min-height:1.2em; }
.aa-caveat {
  display:flex; gap:var(--sp-2); align-items:flex-start;
  background:var(--accent-soft); border:1px solid var(--line); border-radius:var(--r-2);
  padding:var(--sp-2) var(--sp-3); margin-top:var(--sp-3); font-size:.78rem; color:var(--text-1); line-height:1.5;
}
.aa-caveat-ic { color:var(--accent); font-weight:800; flex-shrink:0; }
.aa-warn { color:#c98a3a; }

/* Bear Rally report -- one .brr-event per real-world event window,
   reusing .aa-page/.aa-controlbar/.aa-field/.aa-status/.aa-caveat/
   .aa-statgroup for the shared chrome (see _bear_rally_report_card.html)
   and adding just the event-list/roster layout this report needs. */
.brr-events { display:flex; flex-direction:column; gap:var(--sp-3); margin-top:var(--sp-3); }
.brr-event {
  background:var(--bg-2); border:1px solid var(--line); border-radius:var(--r-3);
  overflow:hidden;
}
.brr-event-head {
  display:flex; align-items:baseline; gap:var(--sp-2); flex-wrap:wrap;
  padding:var(--sp-2) var(--sp-3); border-bottom:1px solid var(--line);
}
.brr-event-title { font-weight:700; font-size:.9rem; }
.brr-roster-head-row, .brr-roster-row {
  display:grid; grid-template-columns:1.1fr 100px 70px 44px 50px 1fr;
  gap:var(--sp-2); align-items:center; padding:4px var(--sp-3);
}
.brr-roster-head-row {
  font-size:.62rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  color:var(--text-3,var(--text-2));
}
.brr-roster-row { font-size:.8rem; border-top:1px solid var(--line); }
.brr-roster-row .brr-nick { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; cursor:pointer; width:fit-content; }
.brr-roster-row .brr-nick:hover { color:var(--accent); text-decoration:underline; }
.brr-roster-row .brr-fid { color:var(--text-2); font-variant-numeric:tabular-nums; }
.brr-roster-row .brr-abbr { color:var(--text-2); }
.brr-roster-row .brr-lv { color:var(--text-2); font-variant-numeric:tabular-nums; }
.brr-roster-row .brr-hourcount { color:var(--text-2); font-variant-numeric:tabular-nums; text-align:center; }
.brr-roster-row .brr-peak {
  color:var(--text-2); font-variant-numeric:tabular-nums;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.brr-group-head {
  display:flex; align-items:baseline; gap:var(--sp-2);
  padding:6px var(--sp-3) 3px; border-top:1px solid var(--line);
  background:var(--bg-3);
}
.brr-group-abbr { font-weight:700; font-size:.78rem; color:var(--accent); }

/* Below 560px the grid's 3 fixed-px columns (100+70+44=214px) left almost
   nothing for the two flexible ones (nick/peak, ~35px each on a ~320px
   card) -- both ellipsized down to 3-4 characters. Reported 2026-08-06.
   Same stacked-card fix as the Alliance Activity roster: header row
   hidden (doesn't apply once rows aren't columnar), nick+alliance tag on
   line 1, id/level/peak-hours as a labelled line 2. */
@media (max-width: 560px) {
  .brr-roster-head-row { display:none; }
  .brr-roster-row {
    display:flex; flex-wrap:wrap; align-items:baseline;
    column-gap:8px; row-gap:2px; padding:6px var(--sp-3);
  }
  .brr-roster-row .brr-nick { order:1; flex:1 1 auto; min-width:0; font-weight:600; }
  .brr-roster-row .brr-abbr { order:2; flex:0 0 auto; }
  .brr-roster-row .brr-fid {
    order:3; flex-basis:100%; font-size:.7rem;
  }
  .brr-roster-row .brr-fid::before { content:"ID "; color:var(--text-3,var(--text-2)); }
  .brr-roster-row .brr-lv { order:4; flex:0 0 auto; font-size:.7rem; }
  .brr-roster-row .brr-lv::before { content:"Lv "; color:var(--text-3,var(--text-2)); }
  .brr-roster-row .brr-hourcount { order:5; flex:0 0 auto; font-size:.7rem; }
  .brr-roster-row .brr-hourcount::before { content:"×Hr "; color:var(--text-3,var(--text-2)); }
  .brr-roster-row .brr-peak {
    order:6; flex:0 0 auto; font-size:.7rem;
    overflow:visible; text-overflow:clip; white-space:normal;
  }
  .brr-roster-row .brr-peak::before { content:"Peak "; color:var(--text-3,var(--text-2)); }
}

/* Player Intel card (_player_intel_card.html / pages/player-intel.js) --
   same list-with-header-row pattern as .brr-roster-*, own column widths. */
#pi-uid-input { width:260px; font-family:var(--ff-mono); }
.pi-status-row { display:flex; align-items:center; justify-content:space-between; gap:var(--sp-2); }
.pi-note { font-size:.75rem; color:var(--text-2); line-height:1.5; }
.pi-list-head, .pi-row {
  display:grid; grid-template-columns:110px 1.2fr 110px 90px 24px;
  gap:var(--sp-2); align-items:center; padding:4px var(--sp-3);
}
.pi-list-head {
  font-size:.62rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  color:var(--text-3,var(--text-2));
}
.pi-row { font-size:.8rem; border-top:1px solid var(--line); }
.pi-row .pi-uid { color:var(--text-2); font-variant-numeric:tabular-nums; }
.pi-row .pi-nick { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pi-row .pi-power {
  color:var(--text-2); font-variant-numeric:tabular-nums;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.pi-row .pi-status { text-transform:capitalize; }
.pi-row .pi-status-failed { color:#c9503a; }
.pi-row .pi-status-done { color:#4caf7d; }
.pi-row .pi-status-running { color:var(--aa-hot,#ff9d3d); }
.pi-row .pi-error { color:#c9503a; cursor:help; }

/* Activity status tiers -- active / trending_inactive / inactive, see
   map/services/alliance_activity.py:_activity_status. */
.aa-stat-active .aa-stat-n { color:#4caf7d; }
.aa-stat-trending .aa-stat-n { color:#c98a3a; }
.aa-stat-inactive .aa-stat-n { color:var(--text-3,var(--text-2)); }

.aa-sect-head { display:flex; align-items:baseline; justify-content:space-between; margin:var(--sp-4) 0 var(--sp-2); gap:var(--sp-2); flex-wrap:wrap; }
.aa-sect-title { font-size:.72rem; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:var(--text-2); }
.aa-sect-hint { font-size:.72rem; color:var(--text-3,var(--text-2)); }
.aa-sect-head-right { display:flex; align-items:center; gap:var(--sp-3); }
.aa-downloadbtn {
  background:var(--bg-3); border:1px solid var(--line-strong,var(--line)); color:var(--text-1);
  border-radius:var(--r-2); padding:3px 9px; font-size:.7rem; font-weight:600; letter-spacing:.02em;
  cursor:pointer; display:inline-flex; align-items:center; gap:4px;
}
.aa-downloadbtn:hover { background:var(--bg-4,var(--bg-3)); color:var(--text-0); border-color:var(--accent); }
.aa-downloadbtn:disabled { opacity:.4; cursor:default; }
.aa-downloadbtn:disabled:hover { background:var(--bg-3); color:var(--text-1); border-color:var(--line-strong,var(--line)); }

/* ---- Alliance-wide beacon chart ---- */
.aa-beacon-panel { background:var(--bg-2); border:1px solid var(--line); border-radius:var(--r-3); padding:var(--sp-3) var(--sp-3) var(--sp-2); }
.aa-beacon-chart { display:flex; align-items:flex-end; gap:3px; height:96px; }
.aa-beacon-col { flex:1 1 0; display:flex; flex-direction:column; align-items:center; justify-content:flex-end; height:100%; position:relative; }
.aa-beacon-fill {
  width:60%; min-height:2px; border-radius:2px 2px 1px 1px;
  background:linear-gradient(to top, var(--accent-dim,var(--accent)), var(--accent) 55%, var(--aa-hot) 85%, var(--aa-flare));
  transition:filter .12s;
}
.aa-beacon-col:hover .aa-beacon-fill { filter:brightness(1.2); }
.aa-beacon-col .aa-hr-label { font-size:.58rem; color:var(--text-2); margin-top:4px; font-family:var(--ff-mono); }
.aa-beacon-col.is-now .aa-hr-label { color:var(--accent); font-weight:700; }
/* Top-3 busiest hours -- a distinct cool color rather than a brighter/
   taller version of the existing warm ramp, since a taller bar already
   reads "hotter" within that gradient on its own. Declared after .is-now
   so peak takes priority on the rare hour that's both "now" and top-3. */
.aa-beacon-col.is-peak-hour .aa-beacon-fill {
  background:linear-gradient(to top, color-mix(in srgb, var(--aa-peak) 55%, transparent), var(--aa-peak));
}
.aa-beacon-col.is-peak-hour .aa-hr-label { color:var(--aa-peak); font-weight:700; }
.aa-beacon-tip {
  position:absolute; bottom:100%; margin-bottom:5px; left:50%; transform:translateX(-50%);
  background:var(--glass-bg,rgba(18,20,26,.92)); border:1px solid var(--glass-border,rgba(255,255,255,.1));
  color:var(--text-0); padding:3px 6px; border-radius:5px; font-size:.65rem; font-family:var(--ff-mono);
  white-space:nowrap; opacity:0; pointer-events:none; transition:opacity .1s; z-index:3;
}
.aa-beacon-col:hover .aa-beacon-tip { opacity:1; }

/* ---- Top-3 spotlight ---- */
.aa-spotlight { display:grid; grid-template-columns:repeat(3,1fr); gap:var(--sp-2); }
.aa-spot-card { background:var(--bg-2); border:1px solid var(--line); border-radius:var(--r-3); padding:var(--sp-2) var(--sp-3); position:relative; overflow:hidden; }
.aa-spot-card::before { content:""; position:absolute; top:0; left:0; right:0; height:2px; background:linear-gradient(90deg,var(--accent),var(--aa-flare)); }
.aa-spot-rank { font-family:var(--ff-mono); font-size:.66rem; font-weight:700; color:var(--accent); letter-spacing:.04em; }
.aa-spot-nick { font-size:.92rem; font-weight:700; margin:2px 0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; cursor:pointer; width:fit-content; }
.aa-spot-nick:hover { color:var(--accent); text-decoration:underline; }
.aa-spot-meta { font-size:.68rem; color:var(--text-2); margin-bottom:var(--sp-2); }
.aa-spot-meta .aa-lvchip { display:inline-block; background:var(--bg-3); color:var(--text-1); border-radius:4px; padding:1px 5px; font-family:var(--ff-mono); font-size:.62rem; margin-right:4px; }
.aa-rally-badge { display:inline-block; background:rgba(230,184,0,0.14); color:var(--accent); border-radius:4px; padding:1px 5px; font-family:var(--ff-mono); font-size:.62rem; margin-left:2px; }
.aa-mini-strip { display:flex; gap:1.5px; height:26px; align-items:flex-end; }
.aa-mini-cell { flex:1 1 0; border-radius:2px 2px 0 0; min-height:2px; background:var(--aa-cell-off); }
.aa-spot-foot { display:flex; justify-content:space-between; margin-top:4px; font-size:.64rem; color:var(--text-2); font-family:var(--ff-mono); }
.aa-spot-foot .aa-peak { color:var(--accent); font-weight:700; }

/* ---- Roster ---- */
/* overflow-x:auto (not overflow:hidden) -- the roster grid has a
   min-width below so a card resized narrower than the full column set
   scrolls horizontally instead of silently clipping "Last seen" etc. */
.aa-roster { background:var(--bg-2); border:1px solid var(--line); border-radius:var(--r-3); overflow-x:auto; }
.aa-roster-head-row, .aa-row {
  display:grid; grid-template-columns:26px 1fr 52px 72px 56px 64px 190px 130px 82px;
  gap:var(--sp-2); align-items:center; padding:5px var(--sp-3);
  min-width:880px;
}
.aa-roster-head-row {
  font-size:.62rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--text-3,var(--text-2));
  border-bottom:1px solid var(--line); position:sticky; top:0; background:var(--bg-2); z-index:2;
}
.aa-roster-body { max-height:320px; overflow-y:auto; }
.aa-row { border-bottom:1px solid var(--line); font-size:.78rem; }
.aa-row:hover { background:var(--bg-3); }
.aa-row.is-idle { opacity:.5; }
.aa-row.is-trending { opacity:.8; }
.aa-row .aa-rk { font-family:var(--ff-mono); color:var(--text-3,var(--text-2)); font-size:.74rem; }
.aa-row.is-top .aa-rk { color:var(--accent); font-weight:700; }
.aa-row .aa-nick { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; cursor:pointer; width:fit-content; }
.aa-row .aa-nick:hover { color:var(--accent); text-decoration:underline; }
.aa-row .aa-lv { font-family:var(--ff-mono); color:var(--text-2); font-size:.72rem; text-align:right; }
.aa-row .aa-marches { font-family:var(--ff-mono); font-variant-numeric:tabular-nums; text-align:right; color:var(--text-2); }
.aa-row .aa-rally { font-family:var(--ff-mono); font-variant-numeric:tabular-nums; text-align:right; color:var(--text-2); }
.aa-row .aa-score { font-family:var(--ff-mono); font-variant-numeric:tabular-nums; text-align:right; font-weight:700; }
.aa-row.is-idle .aa-score, .aa-row.is-trending .aa-score { font-weight:500; }
.aa-status-dot {
  display:inline-block; width:6px; height:6px; border-radius:50%;
  margin-right:5px; vertical-align:middle;
}
.aa-status-dot.aa-status-active { background:#4caf7d; }
.aa-status-dot.aa-status-trending_inactive { background:#c98a3a; }
.aa-status-dot.aa-status-inactive { background:var(--text-3,var(--text-2)); }
.aa-strip { display:flex; gap:1.5px; height:20px; align-items:flex-end; }
.aa-cell { flex:1 1 0; border-radius:1.5px 1.5px 0 0; min-height:2px; background:var(--aa-cell-off); position:relative; }
.aa-cell:hover::after {
  content:attr(data-tip); position:absolute; bottom:100%; left:50%; transform:translateX(-50%); margin-bottom:4px;
  background:var(--glass-bg,rgba(18,20,26,.92)); border:1px solid var(--glass-border,rgba(255,255,255,.1));
  color:var(--text-0); padding:2px 6px; border-radius:4px; font-size:.62rem; font-family:var(--ff-mono);
  white-space:nowrap; z-index:5;
}
.aa-row .aa-peak { font-family:var(--ff-mono); font-size:.7rem; color:var(--accent); text-align:right; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.aa-row .aa-last { font-family:var(--ff-mono); font-size:.68rem; color:var(--text-3,var(--text-2)); text-align:right; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.aa-row.is-idle .aa-peak, .aa-row.is-idle .aa-last { color:var(--text-3,var(--text-2)); }

/* Below 560px the 9 fixed-px grid columns (366px+ before gaps) simply
   don't fit a phone's ~340-390px width -- the old fix here just dropped
   min-width:auto and shrank the pixel columns a bit, which still left
   more fixed width than available: the 1fr nick track got squeezed to
   0px and .aa-nick's own width:fit-content then painted right on top of
   .aa-lv next to it (reported 2026-08-06, confirmed via getBoundingClientRect
   showing .aa-nick/.aa-lv literally overlapping). Switched to a genuine
   2-line stacked card per row instead of trying to keep 9 columns legible
   at that width -- same .aa-row DOM/classes, no JS changes, just
   flex+order to reflow line 1 (rank, nick, score) and line 2 (level,
   marches, rally, peak) without the header row (which doesn't make sense
   once rows aren't columnar). The hourly-pattern sparkline (.aa-strip,
   24 tiny bars) and "Last seen" are dropped -- neither is legible at this
   width and .aa-peak already surfaces the same "when are they active"
   info as readable text. */
@media (max-width: 560px) {
  .aa-roster-head-row { display:none; }
  .aa-row { min-width:auto; }
  .aa-row {
    display:flex; flex-wrap:wrap; align-items:baseline;
    column-gap:6px; row-gap:2px; padding:8px var(--sp-3);
  }
  .aa-row .aa-rk { order:1; flex:0 0 auto; }
  .aa-row .aa-nick {
    order:2; flex:1 1 auto; min-width:0; width:auto;
    font-size:.85rem; font-weight:600; color:var(--text-0);
  }
  .aa-row .aa-score { order:3; flex:0 0 auto; }
  /* flex-basis:100% forces a wrap point in a flex-wrap:wrap row -- this
     is what pushes everything from here on (order 4+) onto line 2,
     without needing a separate wrapper element around them. */
  .aa-row .aa-lv {
    order:4; flex-basis:100%; text-align:left;
    font-size:.7rem; color:var(--text-2);
  }
  .aa-row .aa-lv::before { content:"Lv "; color:var(--text-3,var(--text-2)); }
  .aa-row .aa-marches, .aa-row .aa-rally, .aa-row .aa-peak {
    order:5; flex:0 0 auto; text-align:left;
    font-size:.7rem; color:var(--text-2);
  }
  .aa-row .aa-marches::after { content:" marches"; color:var(--text-3,var(--text-2)); }
  .aa-row .aa-rally::before { content:"🚩"; }
  .aa-row .aa-strip, .aa-row .aa-last { display:none; }
  .aa-spotlight { grid-template-columns:1fr; }
  .aa-statgroup { margin-left:0; }
}

/* ---------- March direction arrow (features/map/march-arrow.js) ----------
   Shared by the static march layer (loaders.js) and the live push overlay
   (live-overlay.js) -- a small triangle at the target end of the dashed
   march line, rotated to the march's actual bearing. Base orientation
   (0deg) points up; JS sets the per-marker rotate() to the real bearing. */
.ks-march-arrow-icon { background: transparent; border: 0; }
.ks-march-arrow-tri {
  width: 0; height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-bottom: 8px solid; /* color set inline per march_type */
  /* Pivot at the tip (top-center, where the left/right wedges meet) --
     matches iconAnchor in march-arrow.js so the tip stays pinned exactly
     on the march coordinate at every rotation angle instead of swinging
     off to one side. */
  transform-origin: 50% 0%;
  /* Halo so the arrowhead stays readable over ANY background --
     castle/hall/trap/banner artwork spans a wide range of mid-tone hues
     (sampled from city.png: blues ~#4a6ca0, greys ~#707070, golds
     ~#c8a050) that no single flat march-type color can hold WCAG 3:1
     contrast against simultaneously (verified: every one of the 21
     march-type colors fails against at least one of those). A halo
     solves it for all of them at once, and for anything drawn under a
     march line in the future, instead of re-tuning colors per artwork.
     Color is set inline per march_type (march-arrow.js's haloFor()) --
     dark colors get a light halo and vice versa, since a single fixed
     halo color made the palette's own dark entries (e.g. Rally Join's
     indigo) unreadable against an equally-dark map background; this
     `filter` declaration is just the structural half (stacked twice: a
     single drop-shadow reads as a soft, half-opacity smudge, doubling it
     compounds to a solid-looking outline without a blur radius large
     enough to blur the tip's sharp point), always overridden by the
     inline style's color. Applied here (computed in the triangle's own
     local space) rather than on transform: rotate() rotates the
     already-haloed shape as one unit, so the halo stays centered at
     every bearing. */
  filter: drop-shadow(0 0 1px rgba(5,5,10,.9)) drop-shadow(0 0 1px rgba(5,5,10,.9));
}

/* ---------- Live march overlay (push events) ---------- */
.ks-live-march-arc {
  /* Sottile pulse opacity per attirare l'occhio sulla marcia appena
     arrivata. Stile linea (weight, dasharray) gestito da JS — qui
     animiamo solo l'opacità per non sovrascrivere lo stile statico. */
  animation: ks-live-march-pulse 1.4s ease-in-out infinite;
}
@keyframes ks-live-march-pulse {
  0%, 100% { stroke-opacity: 0.95; }
  50%      { stroke-opacity: 0.45; }
}
.ks-subscribe-pin {
  background: transparent;
  border: 0;
  text-align: center;
  font-size: 18px;
  line-height: 22px;
  filter: drop-shadow(0 0 4px rgba(0,0,0,0.7));
}
.live-toggle {
  display:flex; align-items:center; gap:6px;
  font-size:.85rem; margin-bottom:var(--sp-2);
}
#live-march-type-filter {
  width: 100%;
  margin-bottom: var(--sp-2);
}

/* ---------- Live listener daemon control panel ---------- */
.sa-daemon {
  background: var(--bg-3);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  padding: var(--sp-2);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.sa-daemon-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-2); flex-wrap: wrap;
}
.sa-daemon-state {
  display: inline-flex; align-items: center; gap: 6px;
  font-weight: 600;
}
.sa-daemon-dot {
  display: inline-block; width: 10px; height: 10px; border-radius: 50%;
  background: #888;
  box-shadow: 0 0 6px currentColor;
}
.sa-daemon.is-running .sa-daemon-dot,
.sa-daemon-state.is-running .sa-daemon-dot { background: #3aa169; }
.sa-daemon-actions { display: flex; gap: var(--sp-1); flex-wrap: wrap; }
.sa-daemon-msg {
  padding: 6px 8px; border-radius: 6px; font-size: .85rem;
  background: var(--bg-2);
  border: 1px solid var(--line);
}

/* Camps layer (⛺ marker) */
.camp-icon {
  font-size: 16px;
  text-align: center;
  color: #66bb6a;
  filter: drop-shadow(0 0 1px rgba(0,0,0,0.6));
  line-height: 22px;
}

/* Rally Called marker row in marches table — slave-aggregator, persists
   alongside the real master row once it's observed (see
   MarchListView._attach_rally_called_rows). */
.march-row-rally-called {
  background: rgba(158, 158, 158, 0.07);
  border-left: 2px dashed rgba(158, 158, 158, 0.6);
  font-style: italic;
}
.march-row-rally-called em { font-weight: 600; color: #bdbdbd; }

/* Event target (march_type=13) under attack by an alliance that doesn't
   currently hold the tile (MapSpecialTile.alliance_abbr) — see
   MarchListView._attach_event_target_holders. Flashes to draw the eye,
   same visual language as .ks-live-march-arc's pulse. */
.march-row-contested {
  border-left: 2px solid var(--danger);
  animation: march-row-contested-flash 1.6s ease-in-out infinite;
}
@keyframes march-row-contested-flash {
  0%, 100% { background: rgba(194, 106, 90, 0.05); }
  50%      { background: rgba(194, 106, 90, 0.28); }
}
.march-row-contested:hover { animation-play-state: paused; }
.march-contested-flag { color: var(--danger); font-weight: 600; }

/* ---------- KvK Battles card -------------------------------------------- */

.kvk-page { display: flex; flex-direction: column; gap: var(--sp-4); }

.kvk-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--sp-4);
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--line);
}
.kvk-title {
  font-size: 24px;
  font-weight: 600;
  letter-spacing: -0.02em;
  margin: 0 0 var(--sp-2) 0;
}
.kvk-summary {
  font-size: 12px;
  color: var(--text-2);
}
.kvk-summary strong {
  color: var(--text-0);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.kvk-refresh-btn {
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  color: var(--text-1);
  font-size: 16px;
  line-height: 1;
  width: 32px;
  height: 32px;
  cursor: pointer;
  transition: background var(--tr-fast), color var(--tr-fast), transform var(--tr-fast);
  flex-shrink: 0;
}
.kvk-refresh-btn:hover { background: var(--bg-2); color: var(--accent); }
.kvk-refresh-btn.is-spinning { animation: kvk-refresh-spin 0.8s linear infinite; color: var(--accent); }
@keyframes kvk-refresh-spin { to { transform: rotate(360deg); } }

/* 6 columns (Season/Date/Opponent/Role/Prep/Castle) don't fit a phone
   viewport without either hiding Prep or Castle -- unlike the alliances
   table's Lv/Avg, both of those are the whole point of this card, so
   scroll the table horizontally instead of dropping either one. */
.kvk-list { overflow-x: auto; }

.kvk-table {
  width: 100%;
  min-width: 560px;
  border-collapse: collapse;
  background: var(--bg-1);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  overflow: hidden;
  font-size: 12px;
}
.kvk-table th {
  text-align: left;
  font-size: 10px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-2);
  padding: 8px 14px;
  border-bottom: 1px solid var(--line-strong);
  background: var(--bg-0);
  white-space: nowrap;
}
.kvk-table td {
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}
.kvk-table tr:last-child td { border-bottom: 0; }
.kvk-table tr:hover td { background: var(--bg-2); }
.kvk-season-cell { color: var(--text-0); font-weight: 500; }
.kvk-opponent-cell {
  font-family: var(--ff-mono);
  color: var(--text-1);
  font-variant-numeric: tabular-nums;
}

.kvk-badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.03em;
  white-space: nowrap;
}
.kvk-badge-attacker { background: rgba(194, 106, 90, 0.16); color: #d98a76; }
.kvk-badge-defender { background: rgba(90, 140, 194, 0.16); color: #7fabe0; }
.kvk-badge-won  { background: rgba(110, 180, 120, 0.18); color: #7fcf8c; }
.kvk-badge-lost { background: rgba(158, 158, 158, 0.16); color: var(--text-2); }
.kvk-captured-tag {
  color: var(--accent);
  margin-left: 4px;
  font-size: 11px;
  cursor: help;
}

.kvk-stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-5);
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line);
}
.kvk-stat { display: flex; flex-direction: column; gap: 3px; }
.kvk-stat-label {
  font-size: 10px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-2);
}
.kvk-stat-value {
  font-family: var(--ff-mono);
  font-size: 15px;
  font-weight: 600;
  color: var(--text-0);
  font-variant-numeric: tabular-nums;
}
.kvk-stat-rate {
  font-family: var(--ff-sans);
  font-size: 11px;
  font-weight: 400;
  color: var(--text-2);
  margin-left: 2px;
}

@media (max-width: 900px) {
  .kvk-head { flex-direction: column; align-items: stretch; gap: var(--sp-3); }
  .kvk-refresh-btn { align-self: flex-end; }
  .kvk-table { font-size: 11px; }
  .kvk-table th, .kvk-table td { padding: 8px 10px; }
  .kvk-stats { gap: var(--sp-4); }
}
