@charset "utf-8";
/* ==========================================================================
   The Experiment™ — styles.css
   Hardcore DayZ Livonia server frontend.

   Contents
     01  Tokens (dark default + light override + manual toggle)
     02  Reset & base
     03  Utilities
     04  Controls: buttons, fields, badges, pills
     05  Chrome: header, drawer, bottom nav, footer
     06  Notices: banners, toasts, states, skeletons
     07  Home: hero, status strip, features, CTA
     08  Shop: layout, sidebar, toolbar, cards, kit contents
     09  Item dialog
     10  Account
     11  Server info & map
     12  Responsive breakpoints (360 / 480 / 768 / 1024 / 1280 / 1600 / 1920)
     13  Motion, forced colours, print

   Breakpoints are mobile-first. Layout also uses container queries where the
   component, not the viewport, is what actually matters.
   ========================================================================== */

/* ==========================================================================
   01  TOKENS
   ========================================================================== */
:root {
  color-scheme: dark;

  /* --- STACKING ORDER ----------------------------------------------------
     One scale, named for what sits at each level, so "what is on top of what"
     is a question with a written answer.

     The panel previously had twenty z-index values chosen one at a time
     (1, 2, 3, 60, 65, 70, 90, 100, 150, 200, -1, -2). Every one was correct
     against whatever it was tested beside, and collectively they decided
     nothing — which is how a drawer ends up under a sticky header, or a map
     marker over a modal.

     Rules:
       - Nothing outside this block sets a raw z-index.
       - A new layer gets a NAME here, not a number at the call site.
       - The gaps are deliberate: room to insert without renumbering. */
  --z-under:    -2;   /* the page wash and hero photo, under even the textures */
  --z-beneath:  -1;   /* decorative textures, behind the content */
  --z-base:      0;   /* normal document flow */
  --z-raised:   10;   /* cards lifted on hover, map markers */
  --z-sticky:   20;   /* sticky table headings, section toolbars */
  --z-nav:      30;   /* the area rail and status bar */
  --z-drawer:   40;   /* the selected-player / detail drawer */
  --z-overlay:  50;   /* scrims behind modals and mobile nav */
  --z-modal:    60;   /* dialogs, confirmations */
  --z-popover:  70;   /* menus and pickers launched FROM a modal */
  --z-toast:    80;   /* transient messages, above all of it */
  --z-skip:     90;   /* the skip-to-content link: reachable over anything */

  /* --- LAYOUT WIDTHS -----------------------------------------------------
     The rail is fixed so the main area's width is predictable at every
     breakpoint; a rail that resizes with content makes every table below it
     reflow when a label changes. */
  --rail-w:        232px;
  --rail-w-narrow: 68px;    /* icons only, before the rail collapses entirely */
  --drawer-w:      380px;
  --main-max:      1440px;

  /* --- surfaces: dark charcoal base -------------------------------------- */
  --bg-0:        #080a09;
  --bg-1:        #0c0f0d;
  --bg-2:        #121715;
  --bg-3:        #18201c;
  --bg-4:        #202a24;
  --bg-inset:    #0a0d0b;

  --line:        #26302a;
  --line-2:      #35423a;
  --line-hi:     #4a5a50;

  /* --- ink ---------------------------------------------------------------- */
  --text:        #e7ede9;
  --text-2:      #b6c3bb;
  --text-3:      #8d9b93;
  --text-inv:    #08120c;

  /* --- identity ----------------------------------------------------------- */
  --forest-deep: #16301f;
  --forest:      #2c5a3b;
  --forest-lit:  #3d7c51;
  --accent:      #63bd7c;
  --accent-2:    #82d197;
  --accent-ink:  #06140b;

  --steel:       #8b999f;
  --steel-deep:  #55636a;
  --mist:        #a4bcc6;
  /* Lifted from #d75c53: that measured 4.39:1 on --bg-3, just under WCAG AA. */
  --danger:      #e26a61;
  --danger-deep: #7d2b26;
  --amber:       #dfa63f;
  --amber-deep:  #6f4d13;

  /* --- tier ramp ---------------------------------------------------------- */
  --tier-free:       #9aa8a1;
  --tier-basic:      #5f9c6b;
  --tier-standard:   #79a9c6;
  --tier-advanced:   #d9a441;
  --tier-elite:      #e26a61;
  --tier-legendary:  #e8c15a;

  /* --- spacing scale -------------------------------------------------------
     One scale, referenced by the density layer below. Components that use
     these instead of literal rem values get compact mode for free. */
  --space-1: .25rem;
  --space-2: .45rem;
  --space-3: .7rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2.2rem;

  /* --- risk ramp -------------------------------------------------------------
     Risk as FIRST-CLASS tokens rather than re-used status colours, so "this
     is dangerous" and "this failed" can stop sharing a hue. The ramp matches
     the Command Centre's four tiers exactly. */
  --risk-read:     var(--steel);
  --risk-moderate: var(--amber);
  --risk-high:     var(--danger);
  --risk-critical: #b03edd;

  /* --- shape & depth ------------------------------------------------------ */
  --r-xs: 4px;
  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 14px;
  --r-xl: 20px;
  --r-pill: 999px;

  --shadow-1: 0 1px 2px rgb(0 0 0 / .5);
  --shadow-2: 0 6px 18px -6px rgb(0 0 0 / .65), 0 2px 5px -2px rgb(0 0 0 / .5);
  --shadow-3: 0 24px 60px -18px rgb(0 0 0 / .8), 0 6px 18px -8px rgb(0 0 0 / .6);
  /* Upward-casting elevation for bottom sheets (mobile map drawer). */
  --shadow-up: 0 -14px 40px -12px rgb(0 0 0 / .7);
  --ring: 0 0 0 2px var(--bg-1), 0 0 0 4px var(--accent);

  /* --- rhythm ------------------------------------------------------------- */
  --pad-x: clamp(1rem, 4vw, 2.5rem);
  --gap: clamp(.75rem, 2vw, 1.25rem);
  --wrap: 1280px;
  --header-h: 60px;
  --bottom-nav-h: 62px;
  --tap: 44px;

  /* --- type --------------------------------------------------------------- */
  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
          "Noto Sans", sans-serif;
  --font-mono: ui-monospace, "Cascadia Mono", "SFMono-Regular", Menlo, Consolas,
               "Liberation Mono", monospace;

  --fs-2xs:  .66rem;  /* the floor, for chrome labels only — never body text */
  --fs-xs:   .72rem;
  --fs-sm:   .82rem;
  --fs-base: .95rem;
  --fs-md:   1.05rem;
  --fs-lg:   clamp(1.15rem, .95rem + .7vw, 1.4rem);
  --fs-xl:   clamp(1.4rem, 1.1rem + 1.3vw, 2rem);
  --fs-2xl:  clamp(1.8rem, 1.2rem + 2.6vw, 3rem);
  --fs-hero: clamp(2.4rem, 1.2rem + 6vw, 5.2rem);

  /* --- texture: pure CSS + one inline SVG grain (no network request) ------- */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
  --grain-opacity: .05;
}

/* Light theme — via system preference, unless the user picked a theme. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme-locked]) {
    color-scheme: light;
    --bg-0:        #dfe4df;
    --bg-1:        #eef1ee;
    --bg-2:        #ffffff;
    --bg-3:        #f5f7f4;
    --bg-4:        #e8ece7;
    --bg-inset:    #e4e9e4;

    --line:        #d3dad3;
    --line-2:      #bcc6bd;
    --line-hi:     #9aa79c;

    --text:        #121a15;
    --text-2:      #39473f;
    --text-3:      #56655c;
    --text-inv:    #ffffff;

    --forest-deep: #cfe0d3;
    --forest:      #2a6b41;
    --forest-lit:  #1f5734;
    --accent:      #1c6b39;
    --accent-2:    #14512a;
    --accent-ink:  #ffffff;

    --steel:       #566268;
    --steel-deep:  #7d888d;
    --mist:        #3f5b66;
    --danger:      #a5342c;
    --danger-deep: #f3d6d3;
    --amber:       #85570a;
    --amber-deep:  #f6e6c4;

    --tier-free:       #5c6a62;
    --tier-basic:      #2a6b41;
    --tier-standard:   #1f5b7d;
    --tier-advanced:   #85570a;
    --tier-elite:      #a5342c;
    --tier-legendary:  #7a5a08;

    --shadow-1: 0 1px 2px rgb(20 30 24 / .1);
    --shadow-2: 0 6px 18px -8px rgb(20 30 24 / .22), 0 2px 5px -3px rgb(20 30 24 / .18);
    --shadow-3: 0 24px 60px -20px rgb(20 30 24 / .3), 0 6px 18px -10px rgb(20 30 24 / .22);
    --shadow-up: 0 -14px 40px -12px rgb(20 30 24 / .28);
    --ring: 0 0 0 2px var(--bg-1), 0 0 0 4px var(--accent);
    --grain-opacity: .035;
  }
}

/* Manual override — always wins over the media query. */
:root[data-theme="light"] {
  color-scheme: light;
  --bg-0:#dfe4df; --bg-1:#eef1ee; --bg-2:#ffffff; --bg-3:#f5f7f4; --bg-4:#e8ece7; --bg-inset:#e4e9e4;
  --line:#d3dad3; --line-2:#bcc6bd; --line-hi:#9aa79c;
  --text:#121a15; --text-2:#39473f; --text-3:#56655c; --text-inv:#ffffff;
  --forest-deep:#cfe0d3; --forest:#2a6b41; --forest-lit:#1f5734;
  --accent:#1c6b39; --accent-2:#14512a; --accent-ink:#ffffff;
  --steel:#566268; --steel-deep:#7d888d; --mist:#3f5b66;
  --danger:#a5342c; --danger-deep:#f3d6d3; --amber:#85570a; --amber-deep:#f6e6c4;
  --tier-free:#5c6a62; --tier-basic:#2a6b41; --tier-standard:#1f5b7d;
  --tier-advanced:#85570a; --tier-elite:#a5342c; --tier-legendary:#7a5a08;
  --shadow-1: 0 1px 2px rgb(20 30 24 / .1);
  --shadow-2: 0 6px 18px -8px rgb(20 30 24 / .22), 0 2px 5px -3px rgb(20 30 24 / .18);
  --shadow-3: 0 24px 60px -20px rgb(20 30 24 / .3), 0 6px 18px -10px rgb(20 30 24 / .22);
  --shadow-up: 0 -14px 40px -12px rgb(20 30 24 / .28);
  --grain-opacity: .035;
}

:root[data-theme="dark"] { color-scheme: dark; }

/* ==========================================================================
   02  RESET & BASE
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scrollbar-color: var(--line-hi) var(--bg-1);
}

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--bg-1);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--fs-base);
  line-height: 1.6;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-wrap: break-word;
  padding-bottom: calc(var(--bottom-nav-h) + env(safe-area-inset-bottom, 0px));
}

/* Ambient depth: a cold mist wash at the top, forest bloom low-left, grain over. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: var(--z-under);
  pointer-events: none;
  background:
    radial-gradient(120% 70% at 50% -10%, color-mix(in srgb, var(--mist) 12%, transparent) 0%, transparent 60%),
    radial-gradient(90% 60% at 8% 105%, color-mix(in srgb, var(--forest) 22%, transparent) 0%, transparent 65%),
    linear-gradient(180deg, var(--bg-0) 0%, var(--bg-1) 38%, var(--bg-1) 100%);
}
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: var(--z-beneath);
  pointer-events: none;
  background-image: var(--grain);
  opacity: var(--grain-opacity);
  mix-blend-mode: overlay;
}

h1, h2, h3, h4 {
  margin: 0 0 .5em;
  line-height: 1.15;
  font-weight: 700;
  letter-spacing: -.015em;
  text-wrap: balance;
}
p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

a { color: var(--accent); text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent); text-underline-offset: 3px; }
a:hover { text-decoration-color: currentColor; }

img, svg { display: block; max-width: 100%; }
svg { fill: none; }

button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }

code, kbd, samp { font-family: var(--font-mono); font-size: .9em; }
code {
  background: var(--bg-inset);
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  padding: .08em .38em;
  color: var(--text-2);
}

table { border-collapse: collapse; width: 100%; }

hr { border: 0; border-top: 1px solid var(--line); margin: 1.5rem 0; }

::selection { background: color-mix(in srgb, var(--accent) 40%, transparent); color: var(--text); }

/* Focus: always visible, never removed. */
:focus-visible {
  outline: none;
  box-shadow: var(--ring);
  border-radius: var(--r-sm);
}
.skip-link {
  position: fixed;
  top: .5rem; left: .5rem;
  z-index: var(--z-skip);
  transform: translateY(-160%);
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 700;
  padding: .7rem 1.1rem;
  border-radius: var(--r-md);
  text-decoration: none;
  transition: transform .16s ease;
}
.skip-link:focus-visible { transform: translateY(0); }

/* ==========================================================================
   03  UTILITIES
   ========================================================================== */
.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--pad-x);
}
.wrap.narrow { max-width: 46rem; }

.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/*
 * hidden means hidden.
 *
 * The browser default is `[hidden] { display: none }` at the very bottom of
 * the cascade, so ANY class that sets a display beats it. This sheet sets
 * `display: flex` and `display: grid` on dozens of components, and the result
 * was a slow drip of the same bug: `.view[hidden]`, `.mobile-drawer[hidden]`,
 * `.cat-items[hidden]`, `.search-clear[hidden]`, `.catalog-progress[hidden]`,
 * `.kit-contents[hidden]`, `.load-more-wrap[hidden]` — seven separate patches,
 * each added the day somebody found an element that would not hide.
 *
 * The eighth was the staff-area header: hiding it from a signed-out visitor
 * did nothing, so a closed door still carried an enabled "Search players,
 * orders, events" box. One rule closes the whole class. Anything that must
 * stay visible while carrying the attribute was a bug in the first place.
 */
[hidden] { display: none !important; }

/* The staff-area gate: a refused visitor's shell (header actions, rail,
   toggle, crumbs) is hidden by THIS class, never by the hidden attribute —
   hidden on the rail belongs to the mobile Sections toggle. */
.adm-gated { display: none !important; }

/* The one deliberate exception to [hidden]: at desktop the Sections toggle
   does not exist, so a rail left collapsed on a phone must reappear when the
   window widens — with the toggle gone there would be no way to open it.
   The gate class still wins, because a refused visitor is refused at every
   width. */
@media (min-width: 900px) {
  .adm-nav[hidden]:not(.adm-gated) { display: flex !important; }
}

/* Set while a drawer, the filter sheet or the item modal is open. */
body.no-scroll { overflow: hidden; }

.view { display: block; padding-block: clamp(1.5rem, 4vw, 2.75rem) clamp(2.5rem, 6vw, 4.5rem); }
#view-home { padding-top: 0; }

.tm { font-size: .5em; vertical-align: super; letter-spacing: 0; opacity: .8; }

.page-title { font-size: var(--fs-2xl); margin-bottom: .25rem; }
.page-sub { color: var(--text-2); max-width: 62ch; margin-bottom: clamp(1.25rem, 3vw, 2rem); }

.section-title {
  display: flex; align-items: center; gap: .55rem;
  font-size: var(--fs-lg);
  margin-bottom: 1rem;
}
.section-icon { width: 1.15em; height: 1.15em; color: var(--accent); flex: none; }
.section-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem;
}
.section-head .section-title { margin-bottom: 0; }

.panel {
  background: linear-gradient(180deg, var(--bg-2), var(--bg-3));
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: clamp(1rem, 2.5vw, 1.6rem);
  box-shadow: var(--shadow-1);
}
.panel-title {
  display: flex; align-items: center; gap: .55rem; flex-wrap: wrap;
  font-size: var(--fs-md);
  margin-bottom: .75rem;
}
.panel-icon { width: 1.2em; height: 1.2em; color: var(--accent); flex: none; }
.panel-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; flex-wrap: wrap; }
.panel-head .panel-title { margin-bottom: .75rem; }
.panel-lede { color: var(--text-2); font-size: var(--fs-sm); }
.panel-note {
  margin-top: 1rem;
  padding-top: .85rem;
  border-top: 1px dashed var(--line);
  color: var(--text-3);
  font-size: var(--fs-sm);
}

/* ==========================================================================
   04  CONTROLS
   ========================================================================== */
.btn {
  --btn-bg: var(--bg-3);
  --btn-fg: var(--text);
  --btn-line: var(--line-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  min-height: var(--tap);
  padding: .6rem 1.1rem;
  border: 1px solid var(--btn-line);
  border-radius: var(--r-md);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: var(--fs-sm);
  font-weight: 650;
  letter-spacing: .01em;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color .15s ease, border-color .15s ease, transform .12s ease, color .15s ease;
}
.btn svg { width: 1.15em; height: 1.15em; flex: none; }
.btn:hover { background: var(--bg-4); border-color: var(--line-hi); }
.btn:active { transform: translateY(1px); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .5; pointer-events: none; }

.btn-primary {
  --btn-bg: linear-gradient(180deg, var(--forest-lit), var(--forest));
  --btn-fg: #f2fff5;
  --btn-line: color-mix(in srgb, var(--accent) 50%, transparent);
  box-shadow: var(--shadow-1);
}
/* Hover keeps the resting dark gradient: the near-white label on the bright
   accent measures about 2.2:1 in the dark theme, so the border alone carries
   the hover cue. */
.btn-primary:hover { border-color: var(--accent); }

.btn-ghost { --btn-bg: transparent; --btn-line: var(--line-2); }
.btn-ghost:hover { --btn-bg: var(--bg-3); }

.btn-quiet {
  --btn-bg: transparent;
  --btn-line: transparent;
  --btn-fg: var(--text-2);
}
.btn-quiet:hover { --btn-bg: var(--bg-3); --btn-fg: var(--text); --btn-line: var(--line); }

.btn-danger { --btn-bg: transparent; --btn-fg: var(--danger); --btn-line: color-mix(in srgb, var(--danger) 45%, transparent); }
.btn-danger:hover { --btn-bg: color-mix(in srgb, var(--danger) 14%, transparent); }

.btn-sm { min-height: 38px; padding: .38rem .75rem; font-size: var(--fs-xs); }
.btn-block { width: 100%; }

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  flex: none;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--text-2);
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
.icon-btn svg { width: 22px; height: 22px; }
.icon-btn:hover { background: var(--bg-3); color: var(--text); border-color: var(--line); }

/* --- fields ------------------------------------------------------------- */
.field { margin-bottom: 1rem; }
.field label { display: block; font-size: var(--fs-sm); font-weight: 650; margin-bottom: .35rem; }
.field-help, .field-note { color: var(--text-3); font-size: var(--fs-xs); margin: .35rem 0 0; }
.field-note { margin-top: 1rem; line-height: 1.55; }

input[type="text"], input[type="search"], input[type="number"], .select {
  width: 100%;
  min-height: var(--tap);
  padding: .55rem .8rem;
  background: var(--bg-inset);
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  color: var(--text);
  transition: border-color .15s ease, background-color .15s ease;
}
input::placeholder { color: var(--text-3); }
input:hover, .select:hover { border-color: var(--line-hi); }
input[type="search"]::-webkit-search-cancel-button { display: none; }

.select {
  appearance: none;
  padding-right: 2.2rem;
  cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, var(--text-3) 50%),
                    linear-gradient(135deg, var(--text-3) 50%, transparent 50%);
  background-position: calc(100% - 1.05rem) 55%, calc(100% - .68rem) 55%;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}

.check-row {
  display: flex;
  align-items: center;
  gap: .6rem;
  min-height: var(--tap);
  padding: .15rem .1rem;
  font-size: var(--fs-sm);
  color: var(--text-2);
  cursor: pointer;
}
.check-row input { width: 18px; height: 18px; flex: none; accent-color: var(--accent); cursor: pointer; }
.check-row:hover { color: var(--text); }

input[type="range"] {
  width: 100%;
  accent-color: var(--accent);
  min-height: var(--tap);
  background: transparent;
}

.form-actions { display: flex; gap: .6rem; flex-wrap: wrap; margin-top: .5rem; }
.form-error {
  margin: 0 0 .75rem;
  padding: .55rem .8rem;
  border-radius: var(--r-md);
  border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  color: var(--danger);
  font-size: var(--fs-sm);
}

/* --- badges, pills, tags ------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .12rem .48rem;
  border-radius: var(--r-xs);
  border: 1px solid currentColor;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  line-height: 1.5;
  white-space: nowrap;
}
.badge svg { width: 1em; height: 1em; }

.badge-tier { color: var(--tier-free); background: color-mix(in srgb, currentColor 12%, transparent); }
.badge-tier[data-tier="free"]      { color: var(--tier-free); }
.badge-tier[data-tier="basic"]     { color: var(--tier-basic); }
.badge-tier[data-tier="standard"]  { color: var(--tier-standard); }
.badge-tier[data-tier="advanced"]  { color: var(--tier-advanced); }
.badge-tier[data-tier="elite"]     { color: var(--tier-elite); }
.badge-tier[data-tier="legendary"] { color: var(--tier-legendary); }

.badge-kit { color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.badge-lock { color: var(--amber); background: color-mix(in srgb, var(--amber) 14%, transparent); }

/* One pill anatomy. The status badge (orders + checkout results) is the same
   shape with its own data-status colours below. */
.pill, .status-badge {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .18rem .6rem;
  border-radius: var(--r-pill);
  border: 1px solid color-mix(in srgb, currentColor 45%, transparent);
  background: color-mix(in srgb, currentColor 12%, transparent);
  font-size: var(--fs-xs);
  font-weight: 700;
  white-space: nowrap;
}
.pill svg, .status-badge svg { width: 1em; height: 1em; }
.pill-ok      { color: var(--accent); }
.pill-warn    { color: var(--amber); }
.pill-danger  { color: var(--danger); }
.pill-neutral { color: var(--steel); }
.pill-info    { color: var(--mist); }

/* Status badge colours — the anatomy is shared with .pill above. */
.status-badge[data-status="delivered"]      { color: var(--accent); }
.status-badge[data-status="delivering"]     { color: var(--tier-standard); }
.status-badge[data-status="pending"]        { color: var(--amber); }
.status-badge[data-status="manual_review"]  { color: var(--amber); }
.status-badge[data-status="failed"]         { color: var(--danger); }
.status-badge[data-status="refunded"]       { color: var(--steel); }

.price {
  display: inline-flex;
  align-items: baseline;
  gap: .3rem;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}
.price-value { font-size: 1.05em; }
.price-cur { font-size: .74em; color: var(--text-3); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }
.price-free { color: var(--accent); }

/* ==========================================================================
   05  CHROME
   ========================================================================== */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  background: color-mix(in srgb, var(--bg-1) 88%, transparent);
  backdrop-filter: blur(12px) saturate(140%);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
}
.header-inner {
  display: flex;
  align-items: center;
  gap: .5rem;
  min-height: var(--header-h);
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--pad-x);
}
.header-rule {
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--line-2) 15%, var(--line-hi) 50%, var(--line-2) 85%, transparent);
}

.brand {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin-right: auto;
  padding: .3rem .4rem .3rem 0;
  color: var(--text);
  text-decoration: none;
  min-height: var(--tap);
}
.brand-mark { color: var(--accent); flex: none; }
.brand-text { display: flex; flex-direction: column; line-height: 1.1; min-width: 0; }
.brand-name { font-weight: 800; letter-spacing: -.02em; font-size: 1rem; white-space: nowrap; }
.brand-motto {
  font-size: var(--fs-2xs);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-3);
  white-space: nowrap;
}

.primary-nav { display: none; }
.primary-nav-list { display: flex; align-items: center; gap: .15rem; list-style: none; margin: 0; padding: 0; }
.nav-link {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  min-height: var(--tap);
  padding: .4rem .7rem;
  border-radius: var(--r-md);
  color: var(--text-2);
  font-size: var(--fs-sm);
  font-weight: 620;
  text-decoration: none;
  white-space: nowrap;
  transition: color .15s ease, background-color .15s ease;
}
.nav-link svg { width: 18px; height: 18px; flex: none; opacity: .85; }
.nav-link:hover { color: var(--text); background: var(--bg-3); }
.nav-link[aria-current="page"] {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.nav-link[aria-current="page"] svg { opacity: 1; }

.header-actions { display: flex; align-items: center; gap: .15rem; flex: none; }

/* --- mobile drawer ------------------------------------------------------- */
/* One scrim serves both the mobile drawer and the shop's filter sidebar. */
.drawer-scrim, .sidebar-scrim {
  position: fixed; inset: 0; z-index: var(--z-overlay);
  background: rgb(0 0 0 / .6);
  backdrop-filter: blur(2px);
  animation: fade-in .18s ease;
}
.mobile-drawer {
  position: fixed;
  inset-block: 0;
  inset-inline-end: 0;
  z-index: var(--z-modal);
  width: min(21rem, 86vw);
  display: flex;
  flex-direction: column;
  background: var(--bg-2);
  border-inline-start: 1px solid var(--line-2);
  box-shadow: var(--shadow-3);
  animation: slide-in-right .2s cubic-bezier(.2,.7,.3,1);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
/* The head and title are shared with the shop's filter sidebar; the sidebar's
   own quirks live beside its block in section 08. */
.drawer-head, .sidebar-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: .5rem; padding: .6rem .7rem .6rem 1.1rem;
  border-bottom: 1px solid var(--line);
}
.drawer-title, .sidebar-title { font-weight: 750; letter-spacing: .1em; text-transform: uppercase; font-size: var(--fs-xs); color: var(--text-3); }
.drawer-list { list-style: none; margin: 0; padding: .5rem; overflow-y: auto; flex: 1; }
.drawer-link {
  display: flex; align-items: center; gap: .75rem;
  min-height: 52px;
  padding: .5rem .8rem;
  border-radius: var(--r-md);
  color: var(--text);
  font-weight: 620;
  text-decoration: none;
}
.drawer-link svg { width: 20px; height: 20px; color: var(--text-3); flex: none; }
.drawer-link:hover { background: var(--bg-3); }
.drawer-link[aria-current="page"] { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); }
.drawer-link[aria-current="page"] svg { color: var(--accent); }
.drawer-foot { padding: 1rem 1.2rem; border-top: 1px solid var(--line); }
.drawer-note { color: var(--text-3); font-size: var(--fs-xs); margin: 0; }

/* --- bottom nav (phones) ------------------------------------------------- */
.bottom-nav {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  /* Same layer as the header; later in the DOM, so it wins their ties. */
  z-index: var(--z-nav);
  display: grid;
  /* SIX tabs (Home, Shop, Cart, Account, Rules, Map). This was repeat(5,1fr)
     while the markup had six links, so "Map" wrapped onto a lonely second row
     on every phone. Derived from the content, so a future seventh tab cannot
     silently re-break it. */
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  background: color-mix(in srgb, var(--bg-2) 94%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-top: 1px solid var(--line-2);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.bottom-link {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .15rem;
  min-height: var(--bottom-nav-h);
  padding: .35rem .2rem;
  color: var(--text-3);
  font-size: var(--fs-2xs);
  font-weight: 650;
  letter-spacing: .02em;
  text-decoration: none;
  position: relative;
}
.bottom-link svg { width: 22px; height: 22px; }
/* The cart count, floated over the trolley icon. Same .nav-count pill as the
   desktop badge, repositioned — the phone badge must exist or a cart full of
   paid-for lines looks empty from the nav (see the boot comment in app.js). */
.bottom-link .nav-count {
  position: absolute;
  top: .2rem;
  left: calc(50% + .35rem);
  margin-inline-start: 0;
}
.bottom-link[aria-current="page"] { color: var(--accent); }
.bottom-link[aria-current="page"]::before {
  content: "";
  position: absolute;
  top: 0; left: 50%;
  width: 26px; height: 2px;
  transform: translateX(-50%);
  background: var(--accent);
  border-radius: 0 0 3px 3px;
}

/* --- footer -------------------------------------------------------------- */
.site-footer {
  margin-top: clamp(2rem, 6vw, 4rem);
  border-top: 1px solid var(--line);
  background: linear-gradient(180deg, transparent, color-mix(in srgb, var(--bg-0) 60%, transparent));
  padding-block: clamp(1.5rem, 4vw, 2.5rem);
}
.footer-inner { display: grid; gap: 1.1rem; }
.footer-brand { display: flex; align-items: center; gap: .7rem; }
.footer-brand .brand-mark { color: var(--accent); }
.footer-name { margin: 0; font-weight: 800; letter-spacing: -.02em; }
.footer-motto { margin: 0; font-size: var(--fs-xs); letter-spacing: .16em; text-transform: uppercase; color: var(--text-3); }
.footer-nav { display: flex; flex-wrap: wrap; gap: .25rem 1.1rem; }
.footer-nav a { color: var(--text-2); font-size: var(--fs-sm); text-decoration: none; min-height: 34px; display: inline-flex; align-items: center; }
.footer-nav a:hover { color: var(--accent); }
.footer-note, .footer-build { margin: 0; color: var(--text-3); font-size: var(--fs-xs); max-width: 70ch; }
.footer-build { font-family: var(--font-mono); }

/* ==========================================================================
   06  NOTICES, STATES, SKELETONS
   ========================================================================== */
/* Earlier in the DOM than the header, so a tie would put it underneath. */
.banner-region { position: relative; z-index: calc(var(--z-nav) + 1); }
.banner {
  display: flex;
  align-items: flex-start;
  gap: .7rem;
  /* Full-bleed band, but keep the text aligned to the same gutter as .wrap on
     wide screens (>--wrap) instead of flush against the raw viewport edge. */
  padding-block: .7rem;
  padding-inline: max(var(--pad-x), calc((100% - var(--wrap)) / 2 + var(--pad-x)));
  font-size: var(--fs-sm);
  border-bottom: 1px solid color-mix(in srgb, currentColor 35%, transparent);
  background: color-mix(in srgb, currentColor 14%, var(--bg-1));
}
.banner > svg { width: 20px; height: 20px; flex: none; margin-top: .1rem; }
.banner-body { flex: 1; min-width: 0; color: var(--text); }
.banner-body strong { color: currentColor; }
.banner-dismiss { margin-inline-start: auto; flex: none; width: 34px; height: 34px; }
.banner-dismiss svg { width: 18px; height: 18px; }
.banner-warn   { color: var(--amber); }
.banner-danger { color: var(--danger); }
.banner-info   { color: var(--mist); }

.noscript {
  margin: 0;
  padding-block: 1rem;
  padding-inline: max(var(--pad-x), calc((100% - var(--wrap)) / 2 + var(--pad-x)));
  background: color-mix(in srgb, var(--amber) 16%, var(--bg-1));
  border-bottom: 1px solid color-mix(in srgb, var(--amber) 45%, transparent);
  color: var(--text);
  font-size: var(--fs-sm);
}
.noscript p { margin: .35rem 0 0; max-width: 70ch; color: var(--text-2); }

.toast-region {
  position: fixed;
  z-index: var(--z-toast);
  /* Shown as a manual popover (see app.js toast()) so toasts fired from an open
     modal <dialog> paint in the top layer, above the dialog's ::backdrop rather
     than smeared beneath it. Reset the popover UA box (inset:0 + margin:auto +
     border/padding/background) back to a transparent, bottom-anchored strip. */
  inset: auto;
  margin: 0;
  border: 0;
  padding: 0;
  background: transparent;
  overflow: visible;
  inset-inline: .75rem;
  bottom: calc(var(--bottom-nav-h) + env(safe-area-inset-bottom, 0px) + .75rem);
  display: flex;
  flex-direction: column;
  gap: .5rem;
  pointer-events: none;
}
.toast-region:popover-open { display: flex; }
.toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: .6rem;
  padding: .7rem .85rem;
  border-radius: var(--r-md);
  border: 1px solid color-mix(in srgb, currentColor 45%, transparent);
  background: var(--bg-3);
  box-shadow: var(--shadow-2);
  font-size: var(--fs-sm);
  animation: toast-in .2s cubic-bezier(.2,.7,.3,1);
}
.toast > svg { width: 18px; height: 18px; flex: none; margin-top: .15rem; }
.toast-text { color: var(--text); flex: 1; min-width: 0; }
.toast-ok { color: var(--accent); }
.toast-err { color: var(--danger); }
.toast-info { color: var(--mist); }
/* Only error toasts carry one — they announce as alerts and live longer, so
   they also need a way to be closed on purpose. */
.toast-dismiss { flex: none; width: 28px; height: 28px; color: var(--text-2); }
.toast-dismiss svg { width: 14px; height: 14px; }
.toast.is-leaving { animation: toast-out .18s ease forwards; }

/* A quiet button in its armed "really?" state — the Empty-the-cart second
   click. Colour is the whole signal; the button stays where it was. */
.btn-danger-text { color: var(--danger) !important; }

/* --- generic states ------------------------------------------------------ */
.state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: .5rem;
  padding: clamp(1.75rem, 6vw, 3.5rem) 1.25rem;
  border: 1px dashed var(--line-2);
  border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--bg-2) 60%, transparent);
}
.state h3 { margin: 0; font-size: var(--fs-md); }
.state p { margin: 0; color: var(--text-2); max-width: 46ch; font-size: var(--fs-sm); }
.state-icon { width: 34px; height: 34px; color: var(--text-3); }
.state-error .state-icon { color: var(--danger); }
.state-empty .state-icon { color: var(--steel); }
.state-actions { display: flex; gap: .5rem; flex-wrap: wrap; justify-content: center; margin-top: .5rem; }
.state-detail { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-3); word-break: break-word; }

.empty-page { min-height: 46vh; justify-content: center; }

/* --- skeletons ----------------------------------------------------------- */
.skeleton {
  position: relative;
  overflow: hidden;
  background: var(--bg-3);
  border-radius: var(--r-sm);
  color: transparent;
}
.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--line-hi) 45%, transparent), transparent);
  animation: shimmer 1.4s infinite;
}
.sk-card {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--bg-2);
  padding: 1rem;
  display: grid;
  gap: .6rem;
}
.sk-line { height: .7rem; border-radius: var(--r-xs); }
.sk-line.w-40 { width: 40%; } .sk-line.w-60 { width: 60%; }
.sk-line.w-75 { width: 75%; } .sk-line.w-90 { width: 90%; }
.sk-block { height: 3.2rem; border-radius: var(--r-md); }

.spinner {
  width: 1.1em; height: 1.1em;
  border: 2px solid color-mix(in srgb, currentColor 30%, transparent);
  border-top-color: currentColor;
  border-radius: 50%;
  animation: spin .7s linear infinite;
  flex: none;
}

@keyframes shimmer { 100% { transform: translateX(100%); } }
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes fade-in { from { opacity: 0; } }
@keyframes slide-in-right { from { transform: translateX(100%); } }
@keyframes slide-in-left { from { transform: translateX(-100%); } }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px) scale(.98); } }
@keyframes toast-out { to { opacity: 0; transform: translateY(6px) scale(.98); } }
@keyframes pulse-dot { 50% { opacity: .35; } }

/* ==========================================================================
   07  HOME
   ========================================================================== */
.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-block: clamp(3rem, 12vw, 7.5rem) clamp(2.5rem, 8vw, 5rem);
  border-bottom: 1px solid var(--line);
}
/* The generated hero photograph. It sits BELOW a scrim (.hero-texture) so all
   contrast is measured against the scrim rather than against a photograph we
   cannot control. image-set lets a browser take the WebP and fall back to JPEG
   without a <picture> element, which keeps this purely decorative layer out of
   the accessibility tree entirely. */
.hero-photo {
  position: absolute;
  inset: 0;
  z-index: var(--z-under);
  background-image: image-set(
    url("assets/img/hero-forest-bunker.webp") type("image/webp"),
    url("assets/img/hero-forest-bunker-1376.jpg") type("image/jpeg")
  );
  background-image: -webkit-image-set(
    url("assets/img/hero-forest-bunker.webp") type("image/webp"),
    url("assets/img/hero-forest-bunker-1376.jpg") type("image/jpeg")
  );
  background-size: cover;
  background-position: center 38%;
  /* Fades out downward so the copy below never sits on busy pixels. */
  mask-image: linear-gradient(180deg, #000 0%, #000 45%, transparent 92%);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 45%, transparent 92%);
}
@media (max-width: 767.98px) {
  /* Phones get the small variant; the 1376 file is wasted bandwidth there. */
  .hero-photo {
    background-image: image-set(
      url("assets/img/hero-forest-bunker.webp") type("image/webp"),
      url("assets/img/hero-forest-bunker-640.jpg") type("image/jpeg")
    );
  }
}

.hero-texture {
  position: absolute;
  inset: 0;
  z-index: var(--z-beneath);
  background:
    /* Scrim first: this is what guarantees the AA contrast of the hero copy,
       independent of whatever the photograph happens to be doing. */
    linear-gradient(180deg,
      color-mix(in srgb, var(--bg-1) 62%, transparent) 0%,
      color-mix(in srgb, var(--bg-1) 78%, transparent) 55%,
      var(--bg-1) 100%),
    radial-gradient(70% 90% at 78% 12%, color-mix(in srgb, var(--mist) 16%, transparent), transparent 62%),
    radial-gradient(85% 100% at 12% 92%, color-mix(in srgb, var(--forest) 40%, transparent), transparent 60%),
    repeating-linear-gradient(115deg,
      transparent 0 22px,
      color-mix(in srgb, var(--line) 40%, transparent) 22px 23px);
}
.hero-inner { position: relative; }

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  margin: 0 0 1rem;
  padding: .3rem .8rem .3rem .6rem;
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--bg-2) 70%, transparent);
  font-size: var(--fs-xs);
  font-weight: 650;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-2);
}
.eyebrow-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
  animation: pulse-dot 2.4s ease-in-out infinite;
}

.hero-title {
  font-size: var(--fs-hero);
  font-weight: 850;
  letter-spacing: -.045em;
  line-height: .92;
  margin: 0 0 .25rem;
  background: linear-gradient(170deg, var(--text) 25%, color-mix(in srgb, var(--mist) 75%, var(--text)) 70%, var(--steel) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.hero-motto {
  margin: 0 0 1.1rem;
  font-size: clamp(.85rem, .7rem + .55vw, 1.15rem);
  font-weight: 700;
  letter-spacing: clamp(.14em, .5vw, .34em);
  text-transform: uppercase;
  color: var(--accent);
}
.hero-lede { max-width: 54ch; color: var(--text-2); font-size: var(--fs-md); margin-bottom: 1.6rem; }
.hero-cta { display: flex; flex-wrap: wrap; gap: .6rem; margin-bottom: clamp(1.75rem, 5vw, 3rem); }

.hero-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 1px;
  margin: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--line);
  overflow: hidden;
  max-width: 52rem;
}
.hero-fact { background: color-mix(in srgb, var(--bg-2) 88%, transparent); padding: .8rem 1rem; }
.hero-fact dt { font-size: var(--fs-xs); letter-spacing: .1em; text-transform: uppercase; color: var(--text-3); }
.hero-fact dd { margin: .15rem 0 0; font-weight: 750; font-size: var(--fs-md); font-variant-numeric: tabular-nums; }

/* --- status strip -------------------------------------------------------- */
.status-section { margin-top: clamp(1.75rem, 5vw, 3rem); }
/* Five cards: `auto-fit` leaves an orphan at most widths, so the column count is
   stepped explicitly to land on 1 / 2 / 3 / 5. The two-column step still strands
   a fifth card, so there — and only there — the last card spans the row. */
.status-strip {
  display: grid;
  grid-template-columns: 1fr;
  gap: .7rem;
}
@media (min-width: 520px)  { .status-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 520px) and (max-width: 899.98px) {
  .status-card:last-child { grid-column: 1 / -1; }
}
@media (min-width: 900px)  { .status-strip { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 900px) and (max-width: 1279.98px) {
  /* 5 cards in a 3-col grid would leave a lopsided empty track bottom-right;
     span the last one across the two trailing columns to fill it, mirroring
     the 2-col patch above. */
  .status-card:last-child { grid-column: span 2; }
}
@media (min-width: 1280px) { .status-strip { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
.status-card {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  gap: .1rem .7rem;
  align-items: center;
  padding: .85rem 1rem;
  border: 1px solid var(--line);
  border-inline-start: 3px solid var(--tone, var(--line-hi));
  border-radius: var(--r-md);
  background: linear-gradient(180deg, var(--bg-2), var(--bg-3));
  box-shadow: var(--shadow-1);
  min-height: 74px;
}
.status-card[data-tone="ok"]      { --tone: var(--accent); }
.status-card[data-tone="warn"]    { --tone: var(--amber); }
.status-card[data-tone="danger"]  { --tone: var(--danger); }
.status-card[data-tone="neutral"] { --tone: var(--steel-deep); }
.status-card[data-tone="info"]    { --tone: var(--mist); }
.status-ico { grid-row: 1 / 3; width: 26px; height: 26px; color: var(--tone, var(--text-3)); }
.status-label { font-size: var(--fs-xs); letter-spacing: .1em; text-transform: uppercase; color: var(--text-3); min-width: 0; overflow-wrap: anywhere; }
.status-value { font-weight: 750; font-size: var(--fs-md); font-variant-numeric: tabular-nums; line-height: 1.25; min-width: 0; overflow-wrap: anywhere; }
/* When the stale flag chip is present it floats top-right; reserve room for it
   on the label AND value rows so a long label/value never runs under it.
   :has scopes the padding to only the cards that actually show the chip. */
.status-card:has(.status-stale) .status-label,
.status-card:has(.status-stale) .status-value { padding-inline-end: 4.75rem; }
.status-sub { grid-column: 2; font-size: var(--fs-xs); color: var(--text-3); }
.status-stale {
  position: absolute;
  top: .55rem; inset-inline-end: .6rem;
  display: inline-flex; align-items: center; gap: .25rem;
  padding: .05rem .4rem;
  border-radius: var(--r-xs);
  border: 1px solid color-mix(in srgb, var(--amber) 50%, transparent);
  background: color-mix(in srgb, var(--amber) 15%, transparent);
  color: var(--amber);
  font-size: var(--fs-2xs);
  font-weight: 750;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.status-foot { margin-top: .75rem; color: var(--text-3); font-size: var(--fs-xs); max-width: 80ch; }

/* --- features ------------------------------------------------------------ */
.features { margin-top: clamp(2rem, 6vw, 3.5rem); }
.feature-grid {
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 17.5rem), 1fr));
}
.feature-card {
  display: flex;
  flex-direction: column;
  gap: .5rem;
  padding: clamp(1rem, 2.5vw, 1.4rem);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, var(--bg-2), var(--bg-3));
  box-shadow: var(--shadow-1);
  transition: border-color .18s ease, transform .18s ease;
}
.feature-card:hover { border-color: var(--line-hi); transform: translateY(-2px); }
.feature-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 42px; height: 42px;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--forest) 30%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
  color: var(--accent);
  margin-bottom: .25rem;
}
.feature-icon svg { width: 22px; height: 22px; }
.feature-card h3 { margin: 0; font-size: var(--fs-md); }
.feature-card p { margin: 0; color: var(--text-2); font-size: var(--fs-sm); flex: 1; }
.feature-link {
  display: inline-flex; align-items: center; gap: .35rem;
  margin-top: .5rem;
  min-height: 38px;
  font-size: var(--fs-sm);
  font-weight: 650;
  text-decoration: none;
}
.feature-link svg { width: 1.05em; height: 1.05em; transition: transform .16s ease; }
.feature-link:hover svg { transform: translateX(3px); }

/* --- CTA ----------------------------------------------------------------- */
.cta-panel {
  display: grid;
  gap: 1.25rem;
  margin-top: clamp(2rem, 6vw, 3.5rem);
  padding: clamp(1.25rem, 4vw, 2.5rem);
  border: 1px solid var(--line-2);
  border-radius: var(--r-xl);
  background:
    radial-gradient(120% 140% at 100% 0%, color-mix(in srgb, var(--forest) 34%, transparent), transparent 62%),
    linear-gradient(180deg, var(--bg-2), var(--bg-3));
}
.cta-copy h2 { font-size: var(--fs-xl); }
.cta-copy p { color: var(--text-2); max-width: 62ch; margin: 0; }
.cta-actions { display: flex; flex-wrap: wrap; gap: .6rem; align-content: center; }

/* ==========================================================================
   08  SHOP
   ========================================================================== */
.shop-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
}
.shop-head .page-sub { margin-bottom: 0; }
.shop-layout { display: block; position: relative; }

/* --- sidebar ------------------------------------------------------------- */
/* The scrim, head and title share one rule with the mobile drawer — see
   .drawer-scrim and .drawer-head in section 05. */
.shop-sidebar {
  position: fixed;
  inset-block: 0;
  inset-inline-start: 0;
  z-index: var(--z-modal);
  width: min(20rem, 88vw);
  display: none;
  flex-direction: column;
  background: var(--bg-2);
  border-inline-end: 1px solid var(--line-2);
  box-shadow: var(--shadow-3);
  animation: slide-in-left .2s cubic-bezier(.2,.7,.3,1);
}
.shop-sidebar.is-open { display: flex; }
/* Inside the sidebar's flex column the head must not shrink. */
.sidebar-head { flex: none; }
/* An h2, so the filters own their own subtree of the outline — hence margin: 0. */
.sidebar-title { margin: 0; }
.sidebar-scroll { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 1rem 1.1rem; }
.sidebar-foot { flex: none; padding: .8rem 1.1rem calc(.8rem + env(safe-area-inset-bottom, 0px)); border-top: 1px solid var(--line); }

.filter-block + .filter-block { margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px solid var(--line); }
.filter-title {
  font-size: var(--fs-xs);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-3);
  margin: 0 0 .6rem;
}

.chip-row { display: flex; flex-wrap: wrap; gap: .35rem; }
.chip {
  display: inline-flex; align-items: center; gap: .3rem;
  min-height: 36px;
  padding: .25rem .65rem;
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--text-2);
  font-size: var(--fs-xs);
  font-weight: 650;
  letter-spacing: .03em;
  transition: background-color .14s ease, color .14s ease, border-color .14s ease;
}
.chip:hover { border-color: var(--line-hi); color: var(--text); }
.chip[aria-pressed="true"] {
  background: color-mix(in srgb, var(--chip-tone, var(--accent)) 18%, transparent);
  border-color: color-mix(in srgb, var(--chip-tone, var(--accent)) 60%, transparent);
  color: var(--chip-tone, var(--accent));
}
.chip::before {
  content: "";
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--chip-tone, var(--text-3));
  flex: none;
}

.price-filter { display: grid; gap: .1rem; }
.price-out {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.cat-nav { display: grid; gap: .1rem; }
.cat-group { border-top: 1px solid var(--line); padding-top: .35rem; margin-top: .35rem; }

/*
 * A top-level shelf ("Popular") — a category with no group, shown beside
 * "All items" rather than inside an accordion. It gets the accent that the
 * groups do not, because it is where the shop opens and the eye should land
 * there first.
 */
.cat-btn-shelf .cat-name { font-weight: 600; }
.cat-btn-shelf svg { color: var(--accent); }
.cat-group:first-child { border-top: 0; margin-top: 0; padding-top: 0; }
.cat-group-btn {
  display: flex; align-items: center; gap: .5rem; width: 100%;
  min-height: 42px;
  padding: .4rem .5rem;
  border: 0; border-radius: var(--r-sm);
  background: transparent;
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 750;
  text-align: start;
}
.cat-group-btn:hover { background: var(--bg-3); }
.cat-group-btn .chev { width: 16px; height: 16px; color: var(--text-3); transition: transform .18s ease; margin-inline-start: auto; }
.cat-group-btn[aria-expanded="true"] .chev { transform: rotate(180deg); }
.cat-group-btn .cat-count { font-size: var(--fs-xs); color: var(--text-3); font-weight: 600; font-variant-numeric: tabular-nums; }
.cat-group-btn > svg:first-child { width: 17px; height: 17px; color: var(--accent); flex: none; }

.cat-items { display: grid; gap: .05rem; padding-inline-start: .3rem; }
.cat-btn {
  display: flex; align-items: center; gap: .5rem; width: 100%;
  min-height: 40px;
  padding: .3rem .55rem;
  border: 0; border-radius: var(--r-sm);
  border-inline-start: 2px solid transparent;
  background: transparent;
  color: var(--text-2);
  font-size: var(--fs-sm);
  text-align: start;
}
.cat-btn > svg { width: 16px; height: 16px; color: var(--text-3); flex: none; }
.cat-btn .cat-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cat-btn .cat-count { font-size: var(--fs-xs); color: var(--text-3); font-variant-numeric: tabular-nums; }
.cat-btn:hover { background: var(--bg-3); color: var(--text); }
.cat-btn[aria-pressed="true"] {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-inline-start-color: var(--accent);
  color: var(--accent);
  font-weight: 700;
}
.cat-btn[aria-pressed="true"] > svg, .cat-btn[aria-pressed="true"] .cat-count { color: var(--accent); }
.cat-btn .lock { color: var(--amber); width: 13px; height: 13px; }

/* --- toolbar ------------------------------------------------------------- */
.shop-main { min-width: 0; container-type: inline-size; container-name: shopmain; }

.shop-toolbar {
  display: grid;
  gap: .6rem;
  grid-template-columns: 1fr;
  margin-bottom: .85rem;
}
.search-field { position: relative; display: flex; align-items: center; }
.search-icon {
  position: absolute;
  inset-inline-start: .75rem;
  width: 18px; height: 18px;
  color: var(--text-3);
  pointer-events: none;
}
/* The icon sat on top of the placeholder because the generic control rule
   `input[type="text"], input[type="search"], …` has specificity (0,1,1) and
   sets the `padding` SHORTHAND, which resets padding-inline. A bare
   `.search-input` (0,1,0) therefore lost, and the text started at .8rem —
   directly under the icon. This selector is (0,2,2) so it wins outright, and
   it uses the longhand so it cannot be reset by a later shorthand. */
.search-field > input[type="search"].search-input {
  padding-inline: 2.4rem 2.6rem;
  /* Safari draws its own decorations inside the padding box, which would
     re-introduce an overlap at the trailing edge. */
  -webkit-appearance: none;
  appearance: none;
}

.search-clear {
  position: absolute;
  inset-inline-end: .3rem;
  width: 36px; height: 36px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-3);
}
.search-clear:hover { background: var(--bg-3); color: var(--text); }
.search-clear svg { width: 16px; height: 16px; }
.sort-field { position: relative; display: flex; align-items: center; }
.sort-icon { position: absolute; inset-inline-start: .75rem; width: 18px; height: 18px; color: var(--text-3); pointer-events: none; }
.sort-field .select { padding-inline-start: 2.4rem; }

.active-filters { display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: .6rem; }
.active-filters:empty { display: none; }
.filter-chip {
  display: inline-flex; align-items: center; gap: .35rem;
  min-height: 32px;
  padding: .15rem .3rem .15rem .6rem;
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  background: var(--bg-3);
  color: var(--text-2);
  font-size: var(--fs-xs);
  font-weight: 650;
}
.filter-chip b { color: var(--text); font-weight: 750; }
.filter-chip .x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px;
  border: 0; border-radius: 50%;
  background: transparent;
  color: var(--text-3);
}
.filter-chip .x:hover { background: var(--bg-4); color: var(--danger); }
.filter-chip .x svg { width: 12px; height: 12px; }

.result-count { margin: 0 0 .75rem; color: var(--text-3); font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.result-count strong { color: var(--text); }

.catalog-progress {
  display: flex; align-items: center; gap: .55rem;
  margin-bottom: .75rem;
  padding: .5rem .75rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--bg-2);
  color: var(--text-3);
  font-size: var(--fs-xs);
}
.progress-track { flex: 1; height: 4px; border-radius: var(--r-pill); background: var(--bg-4); overflow: hidden; min-width: 60px; }
.progress-fill { height: 100%; background: var(--accent); border-radius: inherit; transition: width .25s ease; }

/* --- card grid ----------------------------------------------------------- */
.card-grid {
  display: grid;
  gap: .75rem;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 16.5rem), 1fr));
  align-content: start;
}

.item-card {
  container-type: inline-size;
  container-name: card;
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, var(--bg-2), var(--bg-3));
  box-shadow: var(--shadow-1);
  transition: border-color .16s ease, transform .16s ease, box-shadow .16s ease;
  overflow: hidden;
}
.item-card:hover { border-color: var(--line-hi); transform: translateY(-2px); box-shadow: var(--shadow-2); }
.item-card:focus-within { border-color: var(--accent); }
.item-card[data-restricted="true"] { border-color: color-mix(in srgb, var(--amber) 32%, var(--line)); }

.card-top { display: flex; gap: .7rem; padding: .85rem .9rem .6rem; align-items: flex-start; }
.card-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex: none;
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--forest) 26%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--accent);
}
.card-ico svg { width: 20px; height: 20px; }
/* A real photo gets a WIDE slot, not the icon's square.
   `cover` inside a 38px square did to an M4 exactly what you would expect: it
   threw away both ends and showed the middle of the receiver, which is the one
   part of a rifle that identifies nothing. DayZ renders are landscape for
   weapons and portrait for gear, so the slot is wide and the fit is `contain`:
   the whole object is always visible, whatever its shape, and the background
   fills the rest instead of the picture being cropped to fit. */
.card-ico-photo {
  padding: 0; overflow: hidden;
  background: var(--bg-inset, var(--forest));
  width: 72px; height: 44px;
}
.card-ico-photo .card-photo {
  width: 100%; height: 100%;
  object-fit: contain;
  display: block;
}
@media (max-width: 599.98px) {
  /* Narrower cards on a phone — keep the photo wide but a little shorter so the
     title still has room beside it. */
  .card-ico-photo { width: 60px; height: 38px; }
}
.item-card[data-restricted="true"] .card-ico {
  background: color-mix(in srgb, var(--amber) 16%, transparent);
  border-color: color-mix(in srgb, var(--amber) 35%, transparent);
  color: var(--amber);
}
.card-headings { min-width: 0; flex: 1; }
/* The title IS the link now — it dresses as a heading, underlining on
   hover/focus so pointer users still get the affordance. */
.card-name-link { color: inherit; text-decoration: none; }
.card-name-link:hover, .card-name-link:focus-visible { text-decoration: underline; }

.card-name {
  margin: 0;
  font-size: var(--fs-base);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -.01em;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.card-cat {
  margin: .2rem 0 0;
  font-size: var(--fs-xs);
  color: var(--text-3);
  letter-spacing: .04em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.card-badges { display: flex; flex-wrap: wrap; gap: .3rem; padding: 0 .9rem .5rem; }
.card-meta {
  display: flex; align-items: center; justify-content: space-between; gap: .6rem;
  padding: .5rem .9rem;
  margin-top: auto;
  border-top: 1px solid var(--line);
  background: color-mix(in srgb, var(--bg-inset) 55%, transparent);
}
.card-count { font-size: var(--fs-xs); color: var(--text-3); font-variant-numeric: tabular-nums; }
.card-actions { display: flex; gap: .4rem; padding: .6rem .9rem .85rem; }
.card-actions .btn { flex: 1; padding-inline: .6rem; }

.kit-toggle {
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  width: 100%;
  min-height: 40px;
  padding: .45rem .9rem;
  border: 0;
  border-top: 1px solid var(--line);
  background: transparent;
  color: var(--text-2);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  text-align: start;
}
.kit-toggle:hover { background: var(--bg-4); color: var(--text); }
.kit-toggle .chev { width: 16px; height: 16px; transition: transform .18s ease; flex: none; }
.kit-toggle[aria-expanded="true"] { color: var(--accent); }
.kit-toggle[aria-expanded="true"] .chev { transform: rotate(180deg); }

.kit-contents {
  border-top: 1px solid var(--line);
  background: var(--bg-inset);
  max-height: 15rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.kit-list { list-style: none; margin: 0; padding: .4rem 0; }
.kit-list li {
  display: flex; align-items: baseline; justify-content: space-between; gap: .6rem;
  padding: .22rem .9rem;
  font-size: var(--fs-xs);
  border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
}
.kit-list li:last-child { border-bottom: 0; }
.kit-class { font-family: var(--font-mono); color: var(--text-2); min-width: 0; overflow-wrap: anywhere; }
.kit-qty { color: var(--accent); font-weight: 750; font-variant-numeric: tabular-nums; flex: none; }
.kit-empty { padding: .6rem .9rem; color: var(--text-3); font-size: var(--fs-xs); }

/* Container query: give the card a wider two-column head when it has room. */
@container card (min-width: 21rem) {
  .card-top { padding-block: 1rem .7rem; }
  .card-ico { width: 44px; height: 44px; }
  .card-ico svg { width: 23px; height: 23px; }
  /* A PHOTO slot stays landscape and grows with the extra room — this rule
     used to square it back to 44×44 and undo the whole-weapon fit. */
  .card-ico-photo { width: 88px; height: 52px; }
  .card-name { font-size: var(--fs-md); }
}

.load-more-wrap { display: flex; justify-content: center; margin-top: 1.25rem; }
.grid-sentinel { height: 1px; }

/* ==========================================================================
   09  ITEM DIALOG
   ========================================================================== */
.item-dialog {
  width: min(46rem, 100vw);
  max-width: 100%;
  max-height: 100dvh;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text);
  overflow: visible;
}
.item-dialog::backdrop { background: rgb(0 0 0 / .72); backdrop-filter: blur(3px); }
.dialog-inner {
  position: relative;
  max-height: 100dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--bg-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-3);
  animation: dialog-in .2s cubic-bezier(.2,.7,.3,1);
}
@keyframes dialog-in { from { opacity: 0; transform: translateY(14px) scale(.985); } }
.dialog-close {
  position: absolute;
  top: .55rem; inset-inline-end: .55rem;
  z-index: var(--z-raised);
  background: color-mix(in srgb, var(--bg-3) 85%, transparent);
  border-color: var(--line);
}

.dlg-head {
  padding: 1.25rem clamp(1rem, 4vw, 1.75rem) 1rem;
  border-bottom: 1px solid var(--line);
  background:
    radial-gradient(120% 160% at 100% 0%, color-mix(in srgb, var(--forest) 30%, transparent), transparent 60%),
    linear-gradient(180deg, var(--bg-3), var(--bg-2));
  border-radius: var(--r-xl) var(--r-xl) 0 0;
}
.dlg-eyebrow { display: flex; align-items: center; gap: .45rem; color: var(--text-3); font-size: var(--fs-xs);
  letter-spacing: .1em; text-transform: uppercase; margin: 0 2.5rem .4rem 0; }
.dlg-eyebrow svg { width: 15px; height: 15px; color: var(--accent); flex: none; }
.dlg-title { margin: 0 2.5rem .6rem 0; font-size: var(--fs-xl); overflow-wrap: anywhere; }
.dlg-badges { display: flex; flex-wrap: wrap; gap: .35rem; }

.dlg-body { padding: clamp(1rem, 4vw, 1.75rem); display: grid; gap: 1.1rem; }
/* The product hero in the detail dialog: a wide 16:9 frame that crops to fill,
   so kits of any source aspect sit uniformly above the facts. */
/* The detail hero: the whole item, as big as it will go.
   `cover` here cropped a long weapon's barrel and stock straight off the frame
   — the picture you open an item to LOOK at. `contain` keeps every render
   complete whatever its proportions, and the padded dark frame makes the
   letterboxing read as a display case rather than a mistake. */
.dlg-photo {
  aspect-ratio: 16 / 9; border-radius: var(--r-lg); overflow: hidden;
  border: 1px solid var(--line); background: #0f1712;
  padding: .6rem;
}
.dlg-photo-img {
  width: 100%; height: 100%;
  object-fit: contain;
  display: block;
  /* Renders are small source files; let the browser scale them smoothly rather
     than sharpening compression noise when the hero is bigger than the file. */
  image-rendering: auto;
}
/* Small caption under a fact value (tier meaning, "per one", reference #). */
.dlg-fact-note { display: block; margin-top: .15rem; color: var(--text-2); font-weight: 400; font-size: var(--fs-xs); }
.dlg-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 1px;
  margin: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--line);
  overflow: hidden;
}
.dlg-facts > div { background: var(--bg-3); padding: .6rem .8rem; }
.dlg-facts dt { font-size: var(--fs-xs); letter-spacing: .08em; text-transform: uppercase; color: var(--text-3); }
.dlg-facts dd { margin: .1rem 0 0; font-weight: 700; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }

.dlg-section-title {
  display: flex; align-items: center; justify-content: space-between; gap: .6rem;
  font-size: var(--fs-xs); letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-3); margin: 0 0 .5rem;
}
.contents-table-wrap {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
  max-height: 22rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.contents-table { font-size: var(--fs-sm); }
.contents-table th, .contents-table td { text-align: start; padding: .45rem .8rem; border-bottom: 1px solid var(--line); }
.contents-table thead th {
  position: sticky; top: 0;
  background: var(--bg-3);
  font-size: var(--fs-xs); letter-spacing: .08em; text-transform: uppercase; color: var(--text-3);
  z-index: var(--z-raised);
}
.contents-table tbody tr:last-child td { border-bottom: 0; }
.contents-table tbody tr:nth-child(even) { background: color-mix(in srgb, var(--bg-inset) 45%, transparent); }
.contents-table .cls { overflow-wrap: anywhere; }
.contents-table .cls-name { display: block; color: var(--text); font-weight: 600; }
.contents-table .cls-raw {
  display: block; margin-top: .1rem;
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-3, var(--text-2)); opacity: .75;
}
.contents-table .qty { text-align: end; font-variant-numeric: tabular-nums; font-weight: 750; color: var(--accent); width: 5rem; }

.buy-bar {
  display: grid;
  gap: .75rem;
  padding: 1rem clamp(1rem, 4vw, 1.75rem) clamp(1rem, 4vw, 1.75rem);
  border-top: 1px solid var(--line);
  background: color-mix(in srgb, var(--bg-inset) 55%, transparent);
  border-radius: 0 0 var(--r-xl) var(--r-xl);
  position: sticky;
  bottom: 0;
}
.buy-row { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; }
.qty-control {
  display: inline-flex; align-items: center;
  border: 1px solid var(--line-2);
  border-radius: var(--r-md);
  background: var(--bg-inset);
  overflow: hidden;
  flex: none;
}
.qty-control button {
  width: var(--tap); height: var(--tap);
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; background: transparent; color: var(--text-2);
}
.qty-control button:hover:not([disabled]) { background: var(--bg-4); color: var(--text); }
.qty-control button[disabled] { opacity: .35; cursor: not-allowed; }
.qty-control button svg { width: 16px; height: 16px; }
.qty-control input {
  width: 3rem; min-height: var(--tap);
  border: 0; border-inline: 1px solid var(--line);
  border-radius: 0;
  background: transparent;
  text-align: center;
  font-weight: 750;
  font-variant-numeric: tabular-nums;
  padding-inline: 0;
}
.buy-total { margin-inline-start: auto; text-align: end; }
.buy-total .lbl { display: block; font-size: var(--fs-xs); letter-spacing: .08em; text-transform: uppercase; color: var(--text-3); }
.buy-total .val { font-size: var(--fs-lg); font-weight: 800; font-variant-numeric: tabular-nums; }
/* What you have, next to what it costs. Quiet by default; amber only when the
   total is beyond it, because "short by N" is the one version worth reading. */
.buy-balance {
  display: block; margin-top: .15rem;
  font-size: var(--fs-xs); font-variant-numeric: tabular-nums; color: var(--text-3);
}
.buy-balance.is-short { color: var(--amber); font-weight: 600; }

.buy-btn { width: 100%; min-height: 50px; font-size: var(--fs-base); }
.buy-note { margin: 0; font-size: var(--fs-xs); color: var(--text-3); }
/* In-dialog "added to cart" confirmation. Toasts can't be seen while the modal
   item dialog is open (its backdrop is in the top layer above them), so this
   green strip sits directly above the action buttons instead. */
.dlg-confirm {
  display: flex; align-items: center; gap: .5rem;
  margin-bottom: .2rem; padding: .55rem .7rem;
  font-size: var(--fs-sm); font-weight: 650; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line));
  border-radius: var(--r-sm);
}
.dlg-confirm svg { width: 18px; height: 18px; flex: none; }
/* The be-in-game warning earns amber and a border — it is the one note whose
   ignorance costs a player their purchase experience. */
.buy-note-important {
  color: var(--text-2);
  background: color-mix(in srgb, var(--amber) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--amber) 35%, var(--line));
  border-radius: var(--r-sm); padding: .5rem .65rem;
}

.order-result {
  display: grid;
  gap: .5rem;
  padding: .85rem 1rem;
  border-radius: var(--r-md);
  border: 1px solid color-mix(in srgb, currentColor 40%, transparent);
  background: color-mix(in srgb, currentColor 10%, var(--bg-2));
  font-size: var(--fs-sm);
}
.order-result .or-head { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; font-weight: 750; color: var(--text); }
.order-result p { margin: 0; color: var(--text-2); }
.order-result dl { display: grid; grid-template-columns: auto 1fr; gap: .1rem .7rem; margin: 0; font-size: var(--fs-xs); }
.order-result dt { color: var(--text-3); }
.order-result dd { margin: 0; font-family: var(--font-mono); overflow-wrap: anywhere; color: var(--text-2); }
.order-result-ok    { color: var(--accent); }
.order-result-warn  { color: var(--amber); }
.order-result-error { color: var(--danger); }

.locked-note {
  display: flex; gap: .6rem; align-items: flex-start;
  padding: .7rem .85rem;
  border-radius: var(--r-md);
  border: 1px solid color-mix(in srgb, var(--amber) 40%, transparent);
  background: color-mix(in srgb, var(--amber) 10%, transparent);
  font-size: var(--fs-sm);
  color: var(--text-2);
}
.locked-note > svg { width: 18px; height: 18px; color: var(--amber); flex: none; margin-top: .15rem; }

/* ==========================================================================
   10  ACCOUNT
   ========================================================================== */
.account-grid { display: grid; gap: var(--gap); margin-bottom: var(--gap); }
.panel-wide { margin-bottom: var(--gap); }

.link-state {
  display: flex; align-items: flex-start; gap: .6rem;
  margin-bottom: 1rem;
  padding: .7rem .85rem;
  border-radius: var(--r-md);
  border: 1px solid color-mix(in srgb, currentColor 38%, transparent);
  background: color-mix(in srgb, currentColor 10%, transparent);
  font-size: var(--fs-sm);
}
.link-state > svg { width: 18px; height: 18px; flex: none; margin-top: .15rem; }
.link-state .ls-body { color: var(--text-2); min-width: 0; }
.link-state .ls-body strong { color: var(--text); display: block; }
.link-state code { overflow-wrap: anywhere; }
.link-linked { color: var(--accent); }
.link-unlinked { color: var(--amber); }

/* ---- cart ------------------------------------------------------------- */
.cart-panel {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--bg-inset);
  padding: 1rem;
  margin-bottom: 1rem;
}
.cart-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto auto;
  align-items: center;
  gap: .7rem;
  padding: .7rem 0;
  border-bottom: 1px solid var(--line);
}
.cart-line:last-of-type { border-bottom: 0; }
/* A line the server would refuse is dimmed AND excluded from the total, so the
   two signals cannot disagree. The dimming sits on the cells rather than the
   row, because opacity inherits multiplicatively — a child inside a dimmed row
   cannot opt back out — and the blocker's explanation must stay at full
   strength: it is the one part of the row the player has to read. */
.cart-line-blocked .cl-name > :not(.cl-blocker),
.cart-line-blocked .cl-qty,
.cart-line-blocked .cl-price,
.cart-line-blocked .cl-actions { opacity: .68; }
.cart-line .cl-blocker { margin: .2rem 0 0; font-size: var(--fs-xs); color: var(--text-3); }
.cart-line .cl-qty { display: flex; align-items: center; gap: .35rem; }
.cart-qty { width: 4.2rem; text-align: center; font-variant-numeric: tabular-nums; }
/* The item dialog's quantity too: the number floated left of centre. */
#buy-qty { text-align: center; font-variant-numeric: tabular-nums; }
.cart-line .cl-price { font-variant-numeric: tabular-nums; white-space: nowrap; }
.cart-foot {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: .7rem; padding-top: .9rem; margin-top: .3rem; border-top: 1px solid var(--line);
}
.cart-foot-actions { display: flex; flex-wrap: wrap; gap: .5rem; }
.cart-total .lbl { font-size: var(--fs-xs); letter-spacing: .1em; text-transform: uppercase; color: var(--text-3); }
.cart-total .val { display: block; font-size: clamp(1.2rem, 1rem + .8vw, 1.6rem); font-weight: 700;
  font-variant-numeric: tabular-nums; }
.nav-count {
  display: inline-block; min-width: 1.35em; padding: 0 .35em; margin-inline-start: .35em;
  border-radius: var(--r-pill); background: var(--accent); color: var(--accent-ink);
  font-size: var(--fs-xs); line-height: 1.5; text-align: center; font-variant-numeric: tabular-nums;
}
@media (max-width: 40rem) {
  /* Explicit areas so the four cells stack as name-on-top / controls-beneath,
     instead of auto-placing into a lopsided 2×2 (price under the name). */
  .cart-line {
    grid-template-columns: 1fr auto auto;
    grid-template-areas:
      "name  name   name"
      "qty   price  actions";
  }
  .cart-line .cl-name    { grid-area: name; }
  .cart-line .cl-qty     { grid-area: qty; }
  .cart-line .cl-price   { grid-area: price;   justify-self: end; }
  .cart-line .cl-actions { grid-area: actions; justify-self: end; }
}

.balance-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: .7rem; }
.balance-card {
  padding: .9rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--bg-inset);
}
.balance-card .bc-label { display: flex; align-items: center; gap: .4rem; font-size: var(--fs-xs);
  letter-spacing: .1em; text-transform: uppercase; color: var(--text-3); }
.balance-card .bc-label svg { width: 14px; height: 14px; }
.balance-card .bc-value {
  margin-top: .2rem;
  font-size: clamp(1.35rem, 1.1rem + 1vw, 1.9rem);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}
.balance-card .bc-cur { font-size: .55em; color: var(--text-3); font-weight: 650; letter-spacing: .08em; text-transform: uppercase; margin-inline-start: .25rem; }

/* orders: table on wide, cards on narrow */
.orders-table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--r-md); }
.orders-table { font-size: var(--fs-sm); min-width: 44rem; }
.orders-table th, .orders-table td { padding: .6rem .8rem; text-align: start; border-bottom: 1px solid var(--line); vertical-align: top; }
.orders-table thead th {
  background: var(--bg-3);
  font-size: var(--fs-xs); letter-spacing: .08em; text-transform: uppercase; color: var(--text-3);
  white-space: nowrap;
}
.orders-table tbody tr:last-child td { border-bottom: 0; }
.orders-table tbody tr:hover { background: color-mix(in srgb, var(--bg-4) 45%, transparent); }
.orders-table .num { text-align: end; font-variant-numeric: tabular-nums; white-space: nowrap; }
.orders-table .oid { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-3); }
.orders-table .reason { color: var(--text-3); font-size: var(--fs-xs); overflow-wrap: anywhere; }

.orders-cards { display: grid; gap: .6rem; }
.order-card {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--bg-inset);
  padding: .8rem .9rem;
  display: grid;
  gap: .45rem;
}
.order-card .oc-head { display: flex; align-items: flex-start; justify-content: space-between; gap: .6rem; }
.order-card .oc-name { font-weight: 700; overflow-wrap: anywhere; }
.order-card dl { display: grid; grid-template-columns: auto 1fr; gap: .15rem .8rem; margin: 0; font-size: var(--fs-xs); }
.order-card dt { color: var(--text-3); }
.order-card dd { margin: 0; text-align: end; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.order-card .oid { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--text-3); }

/* The per-line breakdown of a cart order, opened on demand. A cart's parent
   row can only say "Cart of 2 item(s)"; this is where the two kits are
   actually named, priced and given their own delivery status. */
.order-breakdown { margin-top: .4rem; }
.order-lines { display: flex; flex-direction: column; gap: .3rem; margin-top: .4rem; }
.order-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: .5rem;
  padding: .35rem .5rem;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--bg-2);
  font-size: var(--fs-xs);
}
.order-line .ol-name { overflow-wrap: anywhere; }
.order-line .ol-price { font-variant-numeric: tabular-nums; color: var(--text-2); }
@media (max-width: 519.98px) {
  /* Three columns will not fit a narrow phone without truncating the item
     name, which is the one thing this panel exists to show. */
  .order-line { grid-template-columns: minmax(0, 1fr); }
  .order-line .ol-price { text-align: start; }
}

/* ==========================================================================
   11  SERVER INFO & MAP
   ========================================================================== */
.info-grid { display: grid; gap: var(--gap); }

.schedule-table { font-size: var(--fs-sm); margin-bottom: 1rem; }
.schedule-table th, .schedule-table td { padding: .55rem .7rem; text-align: start; border-bottom: 1px solid var(--line); }
.schedule-table thead th { font-size: var(--fs-xs); letter-spacing: .08em; text-transform: uppercase; color: var(--text-3); }
.schedule-table tbody th { font-weight: 650; color: var(--text-2); }
.schedule-table tbody tr:last-child th, .schedule-table tbody tr:last-child td { border-bottom: 0; }

.rule-list { margin: 0; padding-inline-start: 1.15rem; display: grid; gap: .45rem; color: var(--text-2); font-size: var(--fs-sm); }
.rule-list li::marker { color: var(--accent); }
.rule-list-num { padding-inline-start: 1.35rem; }
.rule-list strong { color: var(--text); }

/* --- Base Ping -----------------------------------------------------------
   One stacked block per concern (state, bases, Trusted teammates, alerts,
   coverage) so the panel reads top to bottom on a phone without a horizontal
   scroll, which is where most players will actually be when they check it. */
.bp-block { padding: 1rem 0; border-top: 1px solid var(--line); }
.bp-block:first-child { padding-top: 0; border-top: 0; }
.bp-heading {
  margin: 0 0 .5rem;
  font-size: var(--fs-sm);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-3);
}
.bp-status { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
/* A delivery problem is the one thing on this panel a player must not scroll
   past, so it is tinted rather than left to look like another list. */
.bp-failures { border-inline-start: 3px solid var(--amber); padding-inline-start: .8rem; }
.bp-block .form-actions { margin-top: .5rem; flex-wrap: wrap; }
.bp-block .link-form { margin-top: .9rem; }

.restart-clock {
  display: grid;
  gap: .2rem;
  padding: .8rem 1rem;
  margin-bottom: 1rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--bg-inset);
}
.restart-clock .rc-label { font-size: var(--fs-xs); letter-spacing: .1em; text-transform: uppercase; color: var(--text-3); }
.restart-clock .rc-value { font-size: var(--fs-lg); font-weight: 800; font-variant-numeric: tabular-nums; }
.restart-clock .rc-note { font-size: var(--fs-xs); color: var(--text-3); }

/* The static map panel that lived here was replaced by the live map drawn by
   map.js — see the lm-* section near the end of this file. */

/* ==========================================================================
   12  RESPONSIVE BREAKPOINTS
   ========================================================================== */

/* --- ≥480px: large phones ------------------------------------------------ */
@media (min-width: 480px) {
  .shop-toolbar { grid-template-columns: 1fr auto; }
  .sort-field .select { min-width: 13rem; }
  .toast-region { inset-inline: auto .9rem; width: min(24rem, calc(100vw - 1.8rem)); }
  .card-actions .btn { flex: none; }
  .card-actions .btn:first-child { flex: 1; }
}

/* --- ≥768px: tablet / iPad ----------------------------------------------- */
@media (min-width: 768px) {
  :root { --header-h: 64px; }

  body { padding-bottom: 0; }
  .bottom-nav { display: none; }
  .toast-region { bottom: 1rem; }

  /*
   * The header nav stays COLLAPSED here.
   *
   * It used to open up at 768px, and it did not fit: the brand plus seven
   * links measured about 870px against a 753px viewport, so every page on a
   * portrait tablet — public and staff alike — scrolled 170px sideways. The
   * offender was the site header, which is why it showed up identically on
   * all eighteen admin screens and looked like an admin-layout problem.
   *
   * The drawer is already built and already reachable from the toggle, so a
   * tablet gets the same menu a phone does until there is genuinely room for
   * the bar. See the 1024px block.
   */

  .account-grid { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }
  .info-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .info-grid > section:nth-child(4) { grid-column: 1 / -1; }

  .cta-panel { grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
  .footer-inner { grid-template-columns: auto 1fr; align-items: center; }
  .footer-nav { justify-self: end; }
  .footer-note, .footer-build { grid-column: 1 / -1; }

  .buy-bar { grid-template-columns: auto 1fr; align-items: center; }
  .buy-bar .buy-row { grid-column: 1 / -1; }
  .buy-note { grid-column: 1 / -1; }

  .orders-cards { display: none; }
  .orders-table-wrap { display: block; }
}

/* --- <768px: mobile-only bits ------------------------------------------- */
@media (max-width: 767.98px) {
  .orders-table-wrap { display: none; }
  .orders-cards { display: grid; }
  /* Bottom sheet on phones: `margin-block: auto 0` pins the modal to the bottom
     edge, which is where a thumb actually is. */
  .item-dialog { width: 100vw; max-width: 100vw; margin-block: auto 0; margin-inline: 0; }
  .dialog-inner { border-radius: var(--r-xl) var(--r-xl) 0 0; min-height: 88dvh; }
  .dlg-head { border-radius: var(--r-xl) var(--r-xl) 0 0; }
  .buy-bar { border-radius: 0; padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px)); }
  .hero-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* --- ≥1024px: small laptop — shop sidebar becomes a column --------------- */
@media (min-width: 1024px) {
  /*
   * NOW the header bar fits. Measured, not guessed: the brand block plus the
   * seven links come to roughly 870px, so anything narrower than this pushes
   * the page sideways.
   */
  .primary-nav { display: block; }
  .nav-toggle { display: none; }

  .shop-layout {
    display: grid;
    grid-template-columns: 16.5rem minmax(0, 1fr);
    gap: 1.5rem;
    align-items: start;
  }
  .shop-sidebar {
    position: sticky;
    top: calc(var(--header-h) + 1rem);
    inset-block: auto;
    inset-inline: auto;
    z-index: var(--z-raised);
    display: flex;
    width: auto;
    max-height: calc(100dvh - var(--header-h) - 2rem);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-1);
    animation: none;
    background: linear-gradient(180deg, var(--bg-2), var(--bg-3));
  }
  .sidebar-head, .sidebar-close { display: none; }
  .sidebar-scroll { padding-top: 1.15rem; }
  .shop-filter-toggle { display: none; }
  .sidebar-scrim { display: none; }
  .info-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* --- ≥1280px: laptop ----------------------------------------------------- */
@media (min-width: 1280px) {
  :root { --wrap: 1280px; }
  .shop-layout { grid-template-columns: 18rem minmax(0, 1fr); gap: 1.75rem; }
  .card-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 17.5rem), 1fr)); gap: .9rem; }
  .hero-lede { font-size: 1.1rem; }
  .info-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .info-grid > section:nth-child(4) { grid-column: auto; }
}

/* --- ≥1600px ------------------------------------------------------------- */
@media (min-width: 1600px) {
  :root { --wrap: 1480px; --pad-x: 2.75rem; }
  .shop-layout { grid-template-columns: 19rem minmax(0, 1fr); }
  /* Tighter minimum here on purpose: the results column is wide enough for a
     fourth column, and three 340px cards read as sparse rather than premium. */
  .card-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 15.5rem), 1fr)); }
}

/* --- ≥1920px: big desktop ------------------------------------------------ */
@media (min-width: 1920px) {
  :root { --wrap: 1680px; --fs-base: 1rem; }
  .card-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 15.5rem), 1fr)); gap: 1rem; }
  .feature-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .hero-facts { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* --- container query on the results column ------------------------------- */
@container shopmain (min-width: 55rem) {
  .shop-toolbar { grid-template-columns: minmax(0, 1fr) 15rem; }
}
@container shopmain (max-width: 26rem) {
  .card-meta { flex-wrap: wrap; }
}

/* --- pointer-coarse OR narrow: bigger targets ----------------------------
   Width fallback so a small fine-pointer window (split-screen laptop, DevTools
   responsive without touch emulation) also gets touch-sized controls. */
@media (pointer: coarse), (max-width: 599.98px) {
  :root { --tap: 46px; }
  .cat-btn { min-height: 44px; }
  .chip { min-height: 40px; }
  .banner-dismiss, .search-clear { min-width: 44px; min-height: 44px; }
  .footer-nav a { min-height: 44px; }
  .feature-link { min-height: 44px; }
  /* min-* (not width/height) so it overrides the fixed 28px close button. */
  .toast-dismiss { min-width: 44px; min-height: 44px; }
  .filter-chip { min-height: 44px; }
  /* The hit area grows through the box, not the glyph — the icon stays 12px. */
  .filter-chip .x { min-width: 44px; min-height: 44px; padding: .25rem; }
}

/* --- very short viewports ------------------------------------------------ */
@media (max-height: 520px) and (min-width: 768px) {
  .hero { padding-block: 2.5rem 2rem; }
  .shop-sidebar { max-height: 78dvh; }
}

/* ==========================================================================
   13  MOTION, FORCED COLOURS, PRINT
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .feature-card:hover, .item-card:hover { transform: none; }
  .skeleton::after { display: none; }
  .spinner { border-top-color: transparent; border-right-color: currentColor; }
}

@media (prefers-contrast: more) {
  :root { --line: #4a5a50; --line-2: #6a7d71; --text-2: #d4ded8; --text-3: #b3c0b8; }
  .btn { border-width: 2px; }
}

@media (forced-colors: active) {
  /* The gradient-clipped hero headline would vanish, so hand it back a colour. */
  .hero-title { background: none; color: CanvasText; -webkit-text-fill-color: CanvasText; }
  .item-card, .panel, .status-card, .feature-card { border: 1px solid CanvasText; }
  .badge, .pill, .status-badge, .chip { border: 1px solid CanvasText; }
  .skeleton::after { display: none; }
  :focus-visible { outline: 3px solid Highlight; box-shadow: none; }
}

@media print {
  body { background: #fff; color: #000; padding: 0; }
  body::before, body::after { display: none; }
  .site-header, .bottom-nav, .mobile-drawer, .drawer-scrim, .shop-sidebar,
  .toast-region, .banner-region, .shop-toolbar, .load-more-wrap, .item-dialog { display: none !important; }
  .item-card, .panel { break-inside: avoid; border: 1px solid #999; }
}

/* ===================================================================
   Category banner — generated artwork above the shop results.
   The image is decorative; the label on the scrim carries the meaning.
   Contrast is measured against the scrim, never against the photograph.
   =================================================================== */
.group-banner {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  margin-bottom: 1rem;
  /* Short and wide so it frames the results instead of pushing them away.
     Capped in px so it cannot dominate a tall phone screen. */
  aspect-ratio: 6 / 1;
  min-height: 92px;
  max-height: 160px;
}
.group-banner img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 45%;
  z-index: var(--z-beneath);
}
.group-banner-scrim {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: .15rem;
  padding: 0 clamp(1rem, 4vw, 2rem);
  /* Solid at the left where the text sits, clearing to show the art. */
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--bg-1) 92%, transparent) 0%,
    color-mix(in srgb, var(--bg-1) 76%, transparent) 45%,
    color-mix(in srgb, var(--bg-1) 28%, transparent) 100%
  );
}
.group-banner-eyebrow {
  margin: 0;
  font-size: var(--fs-xs);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-3);
}
.group-banner-title {
  margin: 0;
  font-size: clamp(1.25rem, 3.5vw, 1.9rem);
  font-weight: 800;
  letter-spacing: -.01em;
  color: var(--text);
}
@media (max-width: 767.98px) {
  .group-banner { aspect-ratio: 4 / 1; min-height: 80px; }
  /* On a narrow screen the art has no room to read, so the scrim goes
     near-solid and the label stays legible. */
  .group-banner-scrim {
    background: linear-gradient(
      90deg,
      color-mix(in srgb, var(--bg-1) 94%, transparent) 0%,
      color-mix(in srgb, var(--bg-1) 62%, transparent) 100%
    );
  }
}
@media (prefers-reduced-motion: no-preference) {
  .group-banner img { transition: transform .6s ease; }
  .group-banner:hover img { transform: scale(1.03); }
}

/* ===================================================================
   ADMIN AREA
   Same tokens as the rest of the site — this is one application, not a
   second panel bolted on.
   =================================================================== */
.adm-wrap { padding-block: clamp(1.25rem, 3vw, 2rem) 4rem; }
/* Thin, quiet scrollbars inside the panel's own scrolling boxes (tables,
   the rail, drawers) — the browser default draws a bright gutter through
   every wide table. Firefox reads the two-value shorthand. */
.adm-wrap ::-webkit-scrollbar { width: 10px; height: 10px; }
.adm-wrap ::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: var(--r-pill); }
.adm-wrap ::-webkit-scrollbar-thumb:hover { background: var(--text-3); }
.adm-wrap ::-webkit-scrollbar-track { background: transparent; }
.adm-wrap * { scrollbar-width: thin; scrollbar-color: var(--line-2) transparent; }
.adm-head {
  display: flex; flex-wrap: wrap; gap: 1rem;
  align-items: flex-end; justify-content: space-between;
  /* Ruled off from the content: the page header is chrome, the cards are
     work, and a hairline keeps the two planes from bleeding together. */
  margin-bottom: 1.25rem; padding-bottom: 1rem;
  border-bottom: 1px solid var(--line);
}
.adm-title { font-size: clamp(1.5rem, 4vw, 2.1rem); font-weight: 800; margin: .2rem 0 0; letter-spacing: -.01em; }
.adm-head-actions { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }

/* The live chip: proof the panel is updating itself, and how. The dot pulses
   only while a live transport is up — a paused panel must not look alive. */
.adm-live {
  display: inline-flex; align-items: center; gap: .4rem;
  font-size: .72rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-3); white-space: nowrap; user-select: none;
}
.adm-live-dot { width: .5rem; height: .5rem; border-radius: 50%; background: var(--text-3); }
.adm-live-sse .adm-live-dot,
.adm-live-poll .adm-live-dot { background: var(--accent); animation: adm-live-pulse 2s ease-in-out infinite; }
.adm-live-sse, .adm-live-poll { color: var(--accent); }
@keyframes adm-live-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .35; }
}
@media (prefers-reduced-motion: reduce) { .adm-live-dot { animation: none; } }
.adm-search-field { min-width: min(320px, 70vw); }

/* =====================================================================
   THE ADMIN SHELL
   A slim INTENT RAIL on the left — six icon-over-label buttons, always the
   same six places — and one column beside it: header, tab row, work. The
   rail never expands; which screen of an intent is the tab row's job,
   directly above the content it switches. Two levels, two places.

   `min-width: 0` on the flexible columns is the load-bearing declaration: a
   grid item defaults to min-width:auto, so ONE wide table forces the whole
   grid wider than the viewport and the page scrolls sideways.
   ===================================================================== */
.adm-shell {
  display: grid;
  grid-template-columns: 5.25rem minmax(0, 1fr);
  gap: 1.1rem;
  align-items: start;
}
.adm-col { min-width: 0; }

/* The rail. Sticky as a column so all six intents stay reachable under a
   long table; on a phone it turns into a horizontal, scrollable strip that
   sticks under the site header — always visible, never behind a toggle. */
.adm-rail {
  display: flex; flex-direction: column; gap: .3rem;
  /* Clear the sticky site header (--header-h) plus a gap — pinning at just
     16px let the rail slide UP INTO the header band while scrolling, and with
     the same z-index as the header the rail painted over the brand. */
  position: sticky; top: calc(var(--header-h) + var(--space-4));
  z-index: var(--z-nav);
  padding: .35rem;
  border: 1px solid var(--line); border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--bg-2) 92%, transparent);
  max-height: calc(100vh - 2rem);
  overflow-y: auto;
}
.adm-rail .adm-density-toggle { margin-top: auto; }

@media (max-width: 899.98px) {
  .adm-shell { display: block; }
  .adm-rail {
    flex-direction: row; overflow-x: auto; overflow-y: hidden;
    max-height: none; margin-bottom: .9rem;
    top: calc(var(--header-h) + .4rem);
    background: color-mix(in srgb, var(--bg-1) 92%, transparent);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    overscroll-behavior-x: contain;
  }
  .adm-rail .adm-density-toggle { margin-top: 0; margin-left: auto; }
  /* The area rail becomes a sticky horizontal strip right under the header on
     mobile. The command-queue filter bar is also sticky and pins ~6px higher,
     so the rail (z-nav) painted over its lower edge. Drop the filter bar below
     the rail's rendered height so both stay fully visible while scrolling. */
  .adm-cmdqueue-filters { top: calc(var(--header-h) + .4rem + 3.4rem); }
}

/* The work region: main column plus the optional selection drawer. */
.adm-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4);
  align-items: start;
  position: relative;
}
.adm-layout > .adm-main { min-width: 0; }
@media (min-width: 1280px) {
  .adm-layout.has-drawer { grid-template-columns: minmax(0, 1fr) var(--drawer-w); }
}
/* The Livonia survey texture, behind the staff area only. A fixed-attachment
   pseudo-element at low opacity: cards sit on their own opaque surfaces, so
   the texture lives in the gutters and never under text. Removed entirely
   when the reader asked for more contrast or less data — it is decoration,
   and decoration loses every argument. */
.adm-layout::before {
  content: '';
  position: fixed; inset: 0; z-index: var(--z-beneath);
  background: url('assets/img/texture-livonia.webp') repeat;
  background-size: 512px 512px;
  opacity: .22;
  pointer-events: none;
}
@media (prefers-contrast: more), (prefers-reduced-data: reduce) {
  .adm-layout::before { content: none; }
}

/* The tab row: the open area's screens, ruled off like real tabs. */
.adm-tabs-row {
  display: flex; gap: .15rem; align-items: stretch;
  overflow-x: auto; overscroll-behavior-x: contain;
  border-bottom: 1px solid var(--line);
  margin-bottom: 1.1rem;
}
.adm-tabs-row[hidden] { display: none; }

/*
 * PLAYER IDENTITY COLOURS. One of twelve muted discs per player, hashed from
 * their uid, identical on every screen. The letter comes from data-letter via
 * ::before so it never enters textContent. All twelve backgrounds were picked
 * dark enough for white text at AA.
 */
.adm-monogram {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; border-radius: 50%; color: #fff; font-weight: 750;
  width: 1.5rem; height: 1.5rem; font-size: .7rem;
  vertical-align: middle; margin-inline-end: .4rem;
}
.adm-monogram::before { content: attr(data-letter); }
.adm-monogram-md { width: 2.4rem; height: 2.4rem; font-size: 1.05rem; }
/* The real Discord picture, overlaid on the coloured initial: while it
   loads (or if it fails and is removed) the monogram shows through. */
.adm-monogram { position: relative; overflow: hidden; }
.adm-monogram-img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; border-radius: 50%;
}
.adm-hue-0  { background: #7a4d9d; }
.adm-hue-1  { background: #3d6b8f; }
.adm-hue-2  { background: #2f7d6d; }
.adm-hue-3  { background: #8f6b2f; }
.adm-hue-4  { background: #9d4d5e; }
.adm-hue-5  { background: #4d7a9d; }
.adm-hue-6  { background: #4f7a33; } /* was #5e8f3d — 3.8:1 on white, under AA */
.adm-hue-7  { background: #8f3d6b; }
.adm-hue-8  { background: #6b5e9d; }
.adm-hue-9  { background: #257777; } /* was #2f8f8f — 3.9:1 on white, under AA */
.adm-hue-10 { background: #8a5c31; } /* was #9d6b3d — borderline on white, lifted clear of AA */
.adm-hue-11 { background: #5e6b8f; }
/* Both classes, for specificity: playerLink() always emits them together and
   the .adm-player-name rule later in the sheet would otherwise win the
   display battle, leaving the monogram to sit on the text baseline. */
.adm-player-name.adm-player-link { display: inline-flex; align-items: center; }

/* The coordinate readout while picking: tabular digits in a quiet chip, wide
   enough that "12800, 12800" does not push the Reset button around. */
.adm-coord-readout {
  min-width: 8.5rem; text-align: center;
  font-variant-numeric: tabular-nums; font-size: var(--fs-sm);
  color: var(--text-2);
}

/* A table row that opens something: hand cursor and a hover the zebra cannot
   provide, so "click the person" is discoverable without a hint label. */
.adm-row-click { cursor: pointer; }
.adm-row-click:hover td { background: color-mix(in srgb, var(--accent) 10%, var(--bg-3)); }

/* The Needs-a-person list: each row is one thing waiting on a human, as a
   full-width link with a tone edge. Amber = waiting, red = a strong alt
   signal. All-clear renders a single green line instead. */
.adm-attention-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.adm-attention {
  display: block; padding: .6rem .75rem;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  border-inline-start-width: 3px; text-decoration: none; color: var(--text);
  background: var(--bg-3);
}
.adm-attention:hover { background: color-mix(in srgb, var(--accent) 8%, var(--bg-3)); }
.adm-attention-warn { border-inline-start-color: var(--amber); }
.adm-attention-bad { border-inline-start-color: var(--danger); }
.adm-attention-detail { color: var(--danger); font-weight: 700; }
.adm-allclear { margin: 0; display: flex; align-items: center; gap: .4rem; }

/* ---------------------------------------------------------------------------
   THE OVERVIEW HERO. Full-bleed Livonia watchtower banner; the greeting and
   the shift verdict sit in the image's calm dark left side. The text is
   always light-on-dark regardless of theme, because it sits on the IMAGE,
   not on the page — the veil guarantees the contrast the theme cannot.
--------------------------------------------------------------------------- */
.adm-hero {
  position: relative; overflow: hidden; border-radius: var(--r-lg);
  min-height: clamp(150px, 22vw, 230px);
  display: flex; align-items: flex-end;
  border: 1px solid var(--line);
  /* The image's own palette, as paint. A 404'd banner then degrades to a
     plain dark card the light-on-dark text still reads on — in BOTH themes —
     instead of a gradient smear over the page background. */
  background: #0f1712;
}
.adm-hero-img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: 72% 42%;
}
.adm-hero-veil {
  position: absolute; inset: 0;
  background:
    linear-gradient(90deg, rgba(7, 12, 9, .84) 0%, rgba(7, 12, 9, .58) 42%, rgba(7, 12, 9, .12) 78%, rgba(7, 12, 9, 0) 100%),
    linear-gradient(0deg, rgba(7, 12, 9, .62) 0%, rgba(7, 12, 9, 0) 55%);
}
.adm-hero-text { position: relative; padding: 1rem 1.25rem 1.05rem; max-width: 40rem; }
.adm-hero-text .adm-hello-line { margin: 0; color: #f2f5f2; font-size: 1.25rem; font-weight: 800; text-shadow: 0 1px 8px rgba(0,0,0,.5); }
.adm-hero-text .adm-hello-sub { margin: .2rem 0 0; color: #cfd8cf; font-size: .84rem; text-shadow: 0 1px 6px rgba(0,0,0,.5); }
.adm-hero-verdict {
  display: inline-block; margin-top: .65rem; padding: .3rem .7rem;
  border-radius: var(--r-pill); font-size: .8rem; font-weight: 700;
  backdrop-filter: blur(2px);
}
.adm-hero-verdict-ok { background: rgba(28, 107, 57, .55); color: #d9f2e1; border: 1px solid rgba(120, 200, 150, .45); }
.adm-hero-verdict-warn { background: rgba(158, 106, 3, .5); color: #ffe9bd; border: 1px solid rgba(230, 180, 80, .5); }
/* Data saver: the banner is decoration; the words are the content. With the
   image and veil gone the surface is the THEME's, so every hardcoded
   on-image colour — text and verdict chips alike — hands over to tokens;
   the pale chip text was unreadable on the light theme's card. */
@media (prefers-reduced-data: reduce) {
  .adm-hero-img, .adm-hero-veil { display: none; }
  .adm-hero { min-height: 0; background: var(--bg-3); }
  .adm-hero-text .adm-hello-line, .adm-hero-text .adm-hello-sub { color: var(--text); text-shadow: none; }
  .adm-hero-text .adm-hello-sub { color: var(--text-3); }
  .adm-hero-verdict-ok { background: transparent; color: var(--accent); border-color: var(--accent); }
  .adm-hero-verdict-warn { background: transparent; color: var(--amber); border-color: var(--amber); }
}

/* NEXT ACTIONS on the hub: big, labelled, thumbable targets. */
.adm-quickgrid {
  display: grid; gap: .6rem;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
}
.adm-quick {
  display: flex; align-items: center; gap: .55rem;
  min-height: 3.1rem; padding: .55rem .8rem;
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--shadow-1);
  color: var(--text); font-weight: 650; font-size: var(--fs-sm);
  text-decoration: none;
}
.adm-quick:hover { border-color: var(--line-hi); background: var(--bg-3); }
.adm-quick-ic { width: 18px; height: 18px; flex: 0 0 auto; color: var(--accent); }

/* Attention count on an area button. Red on purpose: it means "a person is
   waited on", never merely "there is data". Sits at the row's end so the
   label stays aligned with chip-less rows. */
.adm-nav-count {
  margin-inline-start: auto; flex: 0 0 auto;
  min-width: 1.25rem; padding: 0 .3rem; text-align: center;
  font-size: var(--fs-xs); font-weight: 750; line-height: 1.25rem;
  color: #fff; background: var(--danger); border-radius: var(--r-pill);
}

/* The Overview greeting lines. Their container is the hero banner
   (.adm-hero, above) — the old .adm-hello greeting-card wrapper is gone. */
.adm-hello-line { margin: 0; font-size: var(--fs-lg); font-weight: 700; }
.adm-hello-sub { margin: .15rem 0 0; font-size: var(--fs-sm); color: var(--text-2); }

/* --- the six intents on the rail ------------------------------------ */
/*
 * One muted hue per intent, the same everywhere that intent shows itself:
 * icon tint, active wash and active marker. Colour answers "where am I"
 * before the words are read; the WORD is always beside it, so colour is
 * never the only carrier. Scoped to the rail so the hue set cannot leak
 * into the rest of the app.
 */
.adm-rail .adm-nav-area { --area-hue: var(--accent); }
.adm-rail [data-area="now"]        { --area-hue: #5b9dd9; }
.adm-rail [data-area="players"]    { --area-hue: #52ad72; }
.adm-rail [data-area="moderation"] { --area-hue: #d96a6a; }
.adm-rail [data-area="server"]     { --area-hue: #c9913f; }
.adm-rail [data-area="store"]      { --area-hue: #a98ede; }
.adm-rail [data-area="system"]     { --area-hue: #3aa8a0; }

/* A rail button: icon over a short label, roomy enough for a thumb. */
.adm-rail .adm-nav-area {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: .3rem;
  width: 100%; min-height: 3.4rem; padding: .55rem .2rem .5rem;
  border: 0; border-radius: var(--r-md); background: transparent;
  color: var(--text-2); font: inherit; cursor: pointer;
}
.adm-rail .adm-nav-area svg { width: 20px; height: 20px; color: var(--area-hue); }
.adm-rail-label {
  font-size: .66rem; font-weight: 650; letter-spacing: .02em;
  line-height: 1.1; text-align: center;
}
.adm-rail .adm-nav-area:hover {
  background: color-mix(in srgb, var(--area-hue) 9%, transparent);
  color: var(--text);
}
/* The open intent: a hue wash PLUS a marker bar, so which one is open does
   not depend on distinguishing two dark tints. */
.adm-rail .adm-nav-area.is-open {
  background: color-mix(in srgb, var(--area-hue) 14%, transparent);
  color: var(--text);
}
.adm-rail .adm-nav-area.is-open::before {
  content: ''; position: absolute; inset-block: .5rem; left: 0;
  width: 3px; border-radius: 2px; background: var(--area-hue);
}
/* The attention count pins to the button's corner in the column layout. */
.adm-rail .adm-nav-count { position: absolute; top: .2rem; right: .25rem; margin: 0; }

@media (max-width: 899.98px) {
  .adm-rail .adm-nav-area { min-width: 4.4rem; width: auto; flex: 0 0 auto; }
  .adm-rail .adm-nav-area.is-open::before {
    inset-block: auto 0; left: .5rem; right: .5rem; width: auto; height: 3px;
  }
}

/* --- breadcrumb + purpose ------------------------------------------- */
.adm-head-titles { min-width: 0; }
.adm-crumbs {
  display: flex; align-items: center; gap: .4rem;
  font-size: var(--fs-xs); color: var(--text-3);
  margin-bottom: .15rem;
}
.adm-crumb-sep { opacity: .6; }
.adm-crumb-here { color: var(--text-2); }
.adm-purpose {
  margin: .2rem 0 0; color: var(--text-3); font-size: var(--fs-sm);
  max-width: 68ch;
}

/* --- profile menu ---------------------------------------------------- */
.adm-profile { position: relative; }
/* An UNSIZED <svg><use> defaults to 300x150 — the SVG spec's fallback — which
   is exactly what the owner saw: a "manager icon" filling a third of the
   header. Every inline icon in the panel gets a hard cap so no future
   unclassed svg can balloon again; the few larger art slots override it. */
.adm-profile-btn svg, .adm-nav-btn svg, .adm-tab svg { width: 16px; height: 16px; flex: 0 0 auto; }
.adm-profile-avatar { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; flex: 0 0 auto; }
.adm-profile-btn {
  display: inline-flex; align-items: center; gap: .4rem;
  /* Compact on purpose — the owner's first note on the redesign was that
     this chip was "way too big". It is a status indicator with a menu, not
     a primary action; it should read at a glance and stay out of the way. */
  min-height: 32px; padding: .2rem .65rem;
  border: 1px solid var(--line); border-radius: var(--r-pill);
  background: var(--bg-2); color: var(--text-2); font: inherit; cursor: pointer;
  font-size: var(--fs-sm);
}
.adm-profile-btn:hover { background: var(--bg-3); color: var(--text); }
.adm-profile-role { font-size: var(--fs-xs); letter-spacing: .02em; }
.adm-profile-menu {
  position: absolute; right: 0; top: calc(100% + .35rem);
  min-width: 220px; padding: .3rem;
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--bg-2); box-shadow: var(--shadow-3);
  /* A popover, so it sits above the drawer and the sticky rail but below a
     modal it might have launched. */
  z-index: var(--z-popover);
}
.adm-profile-menu[hidden] { display: none; }
.adm-profile-item {
  display: flex; align-items: center; gap: .55rem; width: 100%;
  min-height: var(--tap); padding: .5rem .6rem;
  border: 0; border-radius: var(--r-sm); background: transparent;
  color: var(--text-2); font: inherit; text-align: left; cursor: pointer;
}
.adm-profile-item:hover { background: var(--bg-3); color: var(--text); }

/* =====================================================================
   MAP TOOLBAR AND DRAWER
   The toolbar sits ABOVE the map in normal flow. Controls floating over a
   map cover the thing they control, and on a phone they cover most of it.
   ===================================================================== */
.adm-map-toolbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  margin-bottom: .6rem;
}
.adm-map-search { flex: 1 1 220px; min-width: 0; }
.adm-map-toolbar .btn { min-height: var(--tap); }

/* Coordinate mode is a MODE, so it looks like one while it is on: a pressed
   button and a crosshair over the map. A mode with no visible state is a mode
   somebody forgets is on. */
.adm-map-coord.is-on {
  background: color-mix(in srgb, var(--accent) 24%, transparent);
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.adm-map-stage.is-picking { cursor: crosshair; }
.adm-map-stage.is-picking::after {
  content: 'Click the map to pick a spot';
  position: absolute; left: 50%; bottom: .6rem; transform: translateX(-50%);
  padding: .3rem .7rem; border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--bg-1) 88%, transparent);
  border: 1px solid var(--accent);
  font-size: var(--fs-xs); color: var(--text-2);
  pointer-events: none;      /* never intercepts the click it describes */
  z-index: var(--z-raised);
}
.adm-map-filter-note { display: block; margin-top: .4rem; font-size: var(--fs-xs); color: var(--text-3); }

/* The detail drawer. A grid column at desktop (see .adm-layout.has-drawer),
   an overlay below that — three columns leave the middle too narrow to read. */
.adm-map-drawer {
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--bg-2); padding: var(--space-4);
  /* Clear the sticky header, matching the intent rail. */
  position: sticky; top: calc(var(--header-h) + var(--space-4));
  max-height: calc(100vh - var(--header-h) - 2rem); overflow-y: auto;
  z-index: var(--z-drawer);
}
.adm-map-drawer[hidden] { display: none; }
@media (max-width: 1279.98px) {
  .adm-map-drawer {
    position: fixed; inset: auto 0 0 0;
    max-height: 70vh; border-radius: var(--r-md) var(--r-md) 0 0;
    box-shadow: var(--shadow-up);
  }
}
@media (max-width: 767.98px) {
  /* Below 768px the bottom-nav is visible; lift the sheet to sit ON it rather
     than covering its tap targets (768-1279px keeps bottom:0 — nav is hidden). */
  .adm-map-drawer {
    bottom: calc(var(--bottom-nav-h) + env(safe-area-inset-bottom, 0px));
    max-height: calc(70vh - var(--bottom-nav-h));
  }
}

/* =====================================================================
   RESPONSIVE TABLES
   Below the breakpoint a table becomes a stack of cards, each row a card
   and each cell labelled by its column. The label comes from data-label,
   which the renderers already set — so this is a presentation change, not
   a second markup path that could drift from the first.
   ===================================================================== */
@media (max-width: 767.98px) {
  .adm-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .adm-table, .adm-table tbody, .adm-table tr, .adm-table td { display: block; width: 100%; }
  .adm-table tr {
    border: 1px solid var(--line); border-radius: var(--r-sm);
    margin-bottom: .4rem; padding: .3rem .5rem; background: var(--bg-2);
  }
  .adm-table td {
    display: grid; grid-template-columns: minmax(0, 42%) minmax(0, 1fr);
    gap: .5rem; padding: .25rem 0; border: 0;
  }
  .adm-table td::before {
    content: attr(data-label);
    color: var(--text-3); font-size: var(--fs-xs);
    /* Wraps rather than truncating: a clipped label is a cell nobody can
       identify, which is worse than a taller row. */
    overflow-wrap: anywhere;
  }
  .adm-table td:last-child { border-bottom: 0; }
}

/* (Table wrap + motion rules live with the table styles further down; the
   duplicates that used to sit here let the two copies drift apart.) */
.adm-nav-btn {
  display: flex; align-items: center; gap: .6rem;
  padding: .55rem .7rem; min-height: var(--tap);
  border: 0; border-radius: var(--r-sm); background: transparent;
  color: var(--text-2); font: inherit; text-align: left; cursor: pointer;
  width: 100%;
}
.adm-nav-btn:hover { background: var(--bg-3); color: var(--text); }
.adm-nav-btn[aria-current="true"] {
  background: color-mix(in srgb, var(--forest) 30%, transparent);
  color: var(--text); font-weight: 650;
}
.adm-nav-btn svg { width: 18px; height: 18px; flex: none; }

/* In the TAB ROW the same buttons read as tabs: flat, underlined when open,
   never full-width. The current screen is the one whose underline meets the
   row's rule — shape carries the state, colour only seconds it. */
.adm-tabs-row .adm-nav-btn {
  width: auto; flex: 0 0 auto;
  padding: .55rem .85rem; margin-bottom: -1px;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  border-bottom: 2px solid transparent;
  white-space: nowrap; font-weight: 600;
}
.adm-tabs-row .adm-nav-btn:hover { background: color-mix(in srgb, var(--bg-3) 70%, transparent); }
.adm-tabs-row .adm-nav-btn[aria-current="true"] {
  background: transparent;
  border-bottom-color: var(--accent);
  color: var(--text);
}

.adm-main { min-width: 0; display: grid; gap: 1rem; }
/*
 * THE SURFACE SYSTEM, redesigned.
 *
 * The panel used to be flat: bg-2 rectangles on a bg-1 page, hairline
 * borders, no elevation, card titles set exactly like body text but bolder.
 * Everything was one plane, so nothing framed anything and every screen
 * read as a wall. The redesign is three moves, applied consistently:
 *
 *   1. Cards float — a real shadow and a faint top sheen, so content sits ON
 *      the page instead of being drawn into it.
 *   2. Every card opens with a HEADER BAND — full-width tinted strip, title
 *      set as a small-caps kicker — so scanning a screen is reading a list
 *      of labelled drawers, not parsing undifferentiated text.
 *   3. Numbers live in raised tiles with an accent top edge, because counts
 *      are what an operator's eye hunts first.
 *
 * All tokens, no images, both themes for free.
 */
.adm-card {
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: linear-gradient(180deg, color-mix(in srgb, var(--bg-3) 45%, var(--bg-2)) 0, var(--bg-2) 3.5rem);
  padding: 1rem;
  box-shadow: var(--shadow-1);
  /*
   * A GRID ITEM'S DEFAULT MINIMUM IS ITS CONTENT, NOT ZERO.
   *
   * `.adm-main` already carries `min-width: 0`, which stops the COLUMN being
   * forced wide — but every card inside it is a grid item too, with the same
   * `min-width: auto` default. So a card holding a wide table refused to
   * shrink below that table's width and pushed straight through its own
   * container: at 768px the Server screen was 745px of card inside a 692px
   * column, and the whole page scrolled sideways by the difference.
   *
   * Setting it here rather than on the one screen that showed the symptom,
   * because every card is capable of holding something wide and this is the
   * rule, not a patch. Wide content still scrolls inside its own container.
   */
  min-width: 0;
}
.adm-card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: .75rem;
  /* The header BAND: spans the card edge to edge, tinted, ruled off. */
  margin: -1rem -1rem .85rem;
  padding: .6rem 1rem;
  background: color-mix(in srgb, var(--bg-3) 55%, transparent);
  border-bottom: 1px solid var(--line);
  border-radius: var(--r-md) var(--r-md) 0 0;
}
.adm-card-title {
  margin: 0;
  /* A kicker, not a heading: the CONTENT is the point of a card, and a label
     set in small caps frames it without competing with it. */
  font-size: var(--fs-xs); font-weight: 750;
  text-transform: uppercase; letter-spacing: .12em;
  color: var(--text-2);
}
.adm-grid { display: grid; gap: .75rem; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.adm-stat {
  background: var(--bg-3); border-radius: var(--r-sm); padding: .7rem .8rem;
  border: 1px solid var(--line);
  border-top: 2px solid color-mix(in srgb, var(--accent) 45%, var(--line-2));
  box-shadow: var(--shadow-1);
}
.adm-stat dt { font-size: var(--fs-xs); letter-spacing: .08em; text-transform: uppercase; color: var(--text-3); }
/* fs-lg, not fs-xl: these tiles carry TEXT as often as numbers — a Discord
   @name, a position, a place phrase — and the extra-large weight made those
   read like dashboard KPIs and overflow their tile. overflow-wrap lets a long
   value wrap instead of pushing past the edge; still bold and prominent. */
.adm-stat dd { margin: .2rem 0 0; font-size: var(--fs-lg, 1.1rem); font-weight: 750; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; min-width: 0; }

/* --- the four states ------------------------------------------------ */
.adm-skeleton { display: grid; gap: .5rem; }
.adm-skeleton-row {
  height: 1.1rem; border-radius: var(--r-sm);
  background: linear-gradient(90deg, var(--bg-3) 25%, var(--bg-inset) 37%, var(--bg-3) 63%);
  background-size: 400% 100%;
}
@media (prefers-reduced-motion: no-preference) {
  .adm-skeleton-row { animation: adm-shimmer 1.4s ease infinite; }
  @keyframes adm-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
}
.adm-empty, .adm-error {
  display: grid; gap: .35rem; justify-items: start;
  padding: 1rem; border-radius: var(--r-sm); background: var(--bg-3);
}
.adm-error { border-left: 3px solid var(--danger); }
.adm-empty-icon { width: 20px; height: 20px; color: var(--text-3); }
/* Opt-in brand art on the few empties an operator meets often. */
.adm-empty-art { width: 160px; height: auto; border-radius: var(--r-md); opacity: .9; }
@media (prefers-reduced-data: reduce) { .adm-empty-art { display: none; } }
.adm-empty-title { margin: 0; font-weight: 650; }
/* The reason is the point of the empty state, so it must never be decorative. */
.adm-empty-reason { margin: 0; color: var(--text-2); font-size: var(--fs-sm); max-width: 60ch; }

.adm-chip {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .15rem .5rem; border-radius: var(--r-pill);
  font-size: var(--fs-xs); font-variant-numeric: tabular-nums;
}
.adm-chip-fresh { background: color-mix(in srgb, var(--forest) 40%, transparent); color: var(--text); }
.adm-chip-stale { background: color-mix(in srgb, var(--amber) 30%, transparent); color: var(--text); }
.adm-chip-unknown { background: var(--bg-3); color: var(--text-3); }

/* Risk, on the SAME four-tier ramp as the Command Centre. A provider restart
   and a game command of the same risk must not be different colours, or the
   colour stops meaning risk and starts meaning which screen you are on. */
.adm-chip.risk-low,
.adm-chip.risk-read     { background: color-mix(in srgb, var(--risk-read) 30%, transparent); color: var(--text); }
.adm-chip.risk-moderate { background: color-mix(in srgb, var(--risk-moderate) 30%, transparent); color: var(--text); }
.adm-chip.risk-high     { background: color-mix(in srgb, var(--risk-high) 30%, transparent); color: var(--text); }
.adm-chip.risk-critical { background: color-mix(in srgb, var(--risk-critical) 35%, transparent); color: var(--text); }

/* Under prefers-contrast, colour alone is never the signal: the label is
   already the word "critical", so the chip gains an outline rather than
   relying on a hue somebody may not distinguish. */
@media (prefers-contrast: more) {
  .adm-chip.risk-low, .adm-chip.risk-read,
  .adm-chip.risk-moderate, .adm-chip.risk-high, .adm-chip.risk-critical {
    background: transparent; outline: 2px solid currentColor; outline-offset: -2px;
  }
}

/* --- map ------------------------------------------------------------ */
.adm-map-stage {
  position: relative; width: 100%;
  /* The one source of the square stage: the box has height before the image
     loads and before any telemetry arrives. */
  aspect-ratio: 1 / 1;
  overflow: hidden; border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--bg-inset);
}
.adm-map-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.adm-map-layers { position: absolute; inset: 0; pointer-events: none; }
.adm-map-layer { position: absolute; inset: 0; }
/* A graticule so the stage stays usable if the image ever fails. */
.adm-map-fallback {
  background-image:
    repeating-linear-gradient(0deg, transparent 0 9.99%, color-mix(in srgb, var(--line-2) 60%, transparent) 10%),
    repeating-linear-gradient(90deg, transparent 0 9.99%, color-mix(in srgb, var(--line-2) 60%, transparent) 10%);
}
.adm-map-fallback-note {
  position: absolute; inset-inline: 0; bottom: .5rem; text-align: center;
  margin: 0; font-size: var(--fs-xs); color: var(--text-3);
}
.adm-marker {
  position: absolute; width: 9px; height: 9px; margin: -4.5px 0 0 -4.5px;
  border-radius: 50%; pointer-events: auto;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .55);
}
.adm-marker-kills { background: var(--danger); }
.adm-marker-deaths { background: var(--text-3); }
.adm-marker-builds { background: var(--amber); }
/* Shape distinguishes the staff-only layers from event dots — colour is
   reinforced, never the only carrier. */
.adm-marker-players { background: var(--accent); width: 11px; height: 11px; margin: -5.5px 0 0 -5.5px; }
.adm-marker-incidents {
  background: var(--danger); border-radius: 2px;
  width: 11px; height: 11px; margin: -5.5px 0 0 -5.5px;
  outline: 2px solid color-mix(in srgb, var(--danger) 45%, transparent); outline-offset: 2px;
}
.adm-marker-vehicles {
  background: var(--amber); border-radius: 2px; transform: rotate(45deg);
}
.adm-zone {
  position: absolute; border-radius: 50%;
  transform: translate(-50%, -50%);
  border: 2px solid color-mix(in srgb, var(--danger) 70%, transparent);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
}
.adm-layer-note {
  position: absolute; left: .5rem; bottom: .5rem;
  font-size: var(--fs-xs); color: var(--text-3);
  background: color-mix(in srgb, var(--bg-1) 80%, transparent);
  padding: .1rem .4rem; border-radius: var(--r-sm);
}
/* Notes stack instead of covering one another when several layers are dark.
   The ladder covers EIGHT rungs because the map has eight layers: with only
   three, the day four layers went dark at once, notes four-plus would pile
   onto the first rung again. */
.adm-map-layer:nth-child(2) .adm-layer-note { bottom: 1.7rem; }
.adm-map-layer:nth-child(3) .adm-layer-note { bottom: 2.9rem; }
.adm-map-layer:nth-child(4) .adm-layer-note { bottom: 4.1rem; }
.adm-map-layer:nth-child(5) .adm-layer-note { bottom: 5.3rem; }
.adm-map-layer:nth-child(6) .adm-layer-note { bottom: 6.5rem; }
.adm-map-layer:nth-child(7) .adm-layer-note { bottom: 7.7rem; }
.adm-map-layer:nth-child(8) .adm-layer-note { bottom: 8.9rem; }

/* --- heatmap cells ---------------------------------------------------- */
.adm-heat-cell {
  position: absolute; background: var(--danger);
  pointer-events: auto; border-radius: 1px;
}

/* --- base claims -------------------------------------------------------- */
/* Circles at claimed radius. Status carried by border style AND the label:
   solid = approved, dashed = pending, dotted+warn = overlap review. */
.adm-base {
  position: absolute; border-radius: 50%;
  transform: translate(-50%, -50%);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  border: 2px solid var(--accent);
  padding: 0; cursor: pointer; pointer-events: auto;
}
.adm-base-pending { border-style: dashed; opacity: .75; }
.adm-base-overlap_review {
  border-style: dotted; border-color: var(--amber);
  background: color-mix(in srgb, var(--amber) 10%, transparent);
}

/* --- density ---------------------------------------------------------------
   One attribute on the root element, set by the toggle in the staff header
   and remembered per device. Compact tightens the read surfaces — tables,
   cards, nav — and leaves TOUCH TARGETS alone: buttons keep their size
   because density is about scanning more rows, not about making controls
   harder to hit. */
:root[data-density="compact"] .adm-card { padding: .6rem; }
/* The header band pulls out to the card edge with negative margins sized to
   the DEFAULT 1rem padding; in compact the card padding shrinks to .6rem, so
   the un-adjusted -1rem margin over-pulled and the band sat proud of the card,
   overlapping the content below it. Match the band's pull to compact padding. */
:root[data-density="compact"] .adm-card-head { margin: -.6rem -.6rem .6rem; }
:root[data-density="compact"] .adm-table th,
:root[data-density="compact"] .adm-table td { padding: .25rem .45rem; }
:root[data-density="compact"] .adm-nav-btn { padding-block: .3rem; }
:root[data-density="compact"] .adm-vitalrow { padding-block: .1rem; }
:root[data-density="compact"] .adm-gear-toggle,
:root[data-density="compact"] .adm-gear-leaf { padding-block: .12rem; }

/* --- the command drawer --------------------------------------------------- */
/* One chip above the section body on every staff screen: the half-built
   command follows the operator instead of dying with the page. */
.adm-drawer { margin-bottom: .75rem; }
.adm-drawer-chip {
  display: flex; flex-wrap: wrap; align-items: center; gap: .6rem;
  padding: .45rem .7rem; border-radius: var(--r-sm);
  background: var(--bg-3); border: 1px dashed var(--line);
}
.adm-drawer-label { font-weight: 650; font-size: var(--fs-sm); }
.adm-drawer-params {
  font-size: var(--fs-xs); color: var(--text-3);
  font-variant-numeric: tabular-nums;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 40ch;
}

/* --- simple / advanced ------------------------------------------------------
   Simple is the default: required fields only. The disclosure carries a count
   so "there is more here" is legible without opening it. */
.adm-advanced-toggle {
  display: inline-flex; align-items: center; gap: .4rem;
  font: inherit; font-size: var(--fs-sm); color: var(--text-2);
  background: none; border: 0; padding: .35rem 0; cursor: pointer;
}
.adm-advanced-toggle::before {
  content: '▸'; color: var(--text-3); transition: transform .12s ease;
}
.adm-advanced-toggle[aria-expanded="true"]::before { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  .adm-advanced-toggle::before { transition: none; }
}
.adm-advanced-panel {
  border-left: 1px solid var(--line);
  padding-left: var(--space-3); margin: var(--space-1) 0 var(--space-2);
}

/* --- command presets ------------------------------------------------------ */
.adm-preset-row {
  display: flex; flex-wrap: wrap; gap: .4rem; margin: .4rem 0 .7rem;
}

/* --- nav groups --------------------------------------------------------- */
.adm-nav-group {
  display: flex; align-items: center; gap: .45rem;
  margin: .9rem 0 .25rem; padding: 0 .6rem;
  font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-3);
}
.adm-nav-group:first-child { margin-top: .2rem; }
/* Decorative Higgsfield emblem. Circular crop hides the square cell; a
   failed load collapses to nothing because the heading text IS the heading. */
.adm-nav-badge {
  /* 22px, up from 18: the emblems are the rail's identity and were reading
     as bullets. A faint ring seats each one on its row. */
  width: 22px; height: 22px; border-radius: 50%;
  object-fit: cover; flex: 0 0 auto; opacity: .95;
  box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--area-hue) 40%, transparent);
}
.adm-nav-area.is-open .adm-nav-badge { opacity: 1; box-shadow: 0 0 0 2px var(--area-hue); }

/* --- connections ----------------------------------------------------------
   Four states, and "not configured" is deliberately NEUTRAL rather than red:
   an unset variable is a decision nobody has made, not a fault, and colouring
   it as a fault sends staff hunting an outage that does not exist. */
.adm-conn-list { display: flex; flex-direction: column; gap: var(--space-3); margin-top: var(--space-2); }
.adm-conn {
  border: 1px solid var(--line); border-left-width: 3px;
  border-radius: var(--r-sm); padding: var(--space-3);
  background: var(--bg-2);
}
.adm-conn-connected { border-left-color: var(--accent); }
.adm-conn-degraded { border-left-color: var(--amber); }
.adm-conn-offline { border-left-color: var(--danger); }
.adm-conn-not_configured { border-left-color: var(--line-2); }
.adm-conn-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.adm-conn-name { font-weight: 650; }
.adm-conn-age { margin-left: auto; font-size: var(--fs-xs); color: var(--text-3); }
.adm-conn-diag { margin: var(--space-2) 0 0; font-size: var(--fs-sm); color: var(--text-2); }
.adm-conn-detail { margin-top: var(--space-2); }

/* The Overview strip: six chips, each linking to its diagnostic. */
.adm-healthstrip {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.adm-health-chip {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .3rem .6rem; border-radius: var(--r-sm);
  border: 1px solid var(--line); background: var(--bg-2);
  font-size: var(--fs-xs); color: var(--text-2); text-decoration: none;
}
.adm-health-chip:hover { background: var(--bg-3); }
.adm-health-name { font-weight: 650; color: var(--text); }
.adm-health-state { color: var(--text-3); }
.adm-health-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-3); flex: 0 0 auto; }
.adm-health-connected .adm-health-dot { background: var(--accent); }
.adm-health-degraded .adm-health-dot { background: var(--amber); }
.adm-health-offline  .adm-health-dot { background: var(--danger); }
.adm-health-offline { border-color: color-mix(in srgb, var(--danger) 45%, var(--line)); }

/* --- the TEST checklist --------------------------------------------------- */
/*
 * `.adm-checklist-item`, NOT `.adm-check`. Three unrelated components had
 * claimed that one name: this checklist row, the shop's filter LABEL (a flex
 * row of box + text), and a raw checkbox INPUT sized to 1rem square. The
 * cascade let the last one win, so the shop's filter labels collapsed into
 * 16px squares stacked on each other — the "glitched buttons on top of each
 * other" under the category filters. One component, one class.
 */
.adm-checklist { list-style: none; margin: var(--space-3) 0 0; padding: 0; }
.adm-checklist-item {
  display: flex; align-items: flex-start; gap: var(--space-2);
  padding: var(--space-2) 0; border-bottom: 1px solid var(--line);
}
.adm-checklist-item:last-child { border-bottom: 0; }
.adm-check-mark { flex: 0 0 auto; color: var(--text-3); font-weight: 700; }
.adm-checklist-item.is-done .adm-check-mark { color: var(--accent); }
.adm-check-label { font-weight: 600; }
/* The done/not-yet badge keeps to the right edge. (This selector said
   .adm-check for a while — the checklist's OLD name, by then owned by the
   checkbox label — so the badge alignment was silently lost and checkbox
   labels grew a stray auto margin.) */
.adm-checklist-item > :last-child { margin-left: auto; flex: 0 0 auto; }

/* --- split views ---------------------------------------------------------
   On a wide screen, a list and the record opened from it sit side by side —
   picking the next case without losing the current one is the whole point
   of a queue screen. Below the breakpoint it stacks, exactly as before. */
@media (min-width: 1200px) {
  .adm-split { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 4fr); gap: 1rem; align-items: start; }
  /* The opened record keeps itself in view while the list scrolls. */
  .adm-split > :last-child { position: sticky; top: calc(var(--header-h) + .75rem); max-height: calc(100vh - var(--header-h) - 1.5rem); overflow-y: auto; }
}

/* --- touch ---------------------------------------------------------------
   A coarse pointer gets the 44px floor on every control that matters. Only
   under (pointer: coarse): mouse users keep the denser targets, and compact
   mode remains about ROWS, never about controls. */
@media (pointer: coarse) {
  .btn, .adm-nav-btn, .adm-console-item, .adm-palette-row, .adm-layer-toggle,
  .adm-gear-toggle, .adm-advanced-toggle, .adm-tab, .adm-linkbtn {
    min-height: 44px;
  }
  .adm-input, select.adm-input { min-height: 44px; }
}

/* --- alt cases ---------------------------------------------------------- */
/* A row that opens something behaves like a control. */
.adm-row-openable { cursor: pointer; }
.adm-row-openable:hover td { background: var(--bg-3); }
.adm-inline-form {
  display: flex; flex-wrap: wrap; gap: .5rem; align-items: center;
  margin: .5rem 0;
}
.adm-inline-form .adm-input { flex: 1 1 14rem; min-width: 0; }

/* --- layer legend / toggles ------------------------------------------- */
/*
 * The layer menu, as a menu. It used to be a bare row of chips floating
 * under the map with nothing naming what they were; now it sits in the same
 * boxed bar as the toolbar's controls, opens with the word LAYERS, and an
 * off layer dims AND hollows its dot, so on/off is readable without
 * comparing opacities.
 */
.adm-map-legend {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem;
  margin-top: .6rem; padding: .5rem .6rem;
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--r-md);
}
.adm-legend-label {
  font-size: var(--fs-xs); font-weight: 750; letter-spacing: .12em;
  text-transform: uppercase; color: var(--text-3); margin-inline-end: .3rem;
}
.adm-layer-toggle {
  display: inline-flex; align-items: center; gap: .4rem;
  font: inherit; font-size: var(--fs-xs); color: var(--text-2);
  background: var(--bg-3); border: 1px solid var(--line);
  border-radius: var(--r-pill); padding: .25rem .6rem; cursor: pointer;
}
.adm-layer-toggle[aria-pressed="true"] { border-color: color-mix(in srgb, currentColor 30%, var(--line)); }
.adm-layer-toggle[aria-pressed="false"] { opacity: .5; }
.adm-layer-toggle[aria-pressed="false"] .adm-layer-dot { background: transparent !important; border: 1.5px solid var(--text-3); }
.adm-layer-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--text-3); flex: 0 0 auto; }
.adm-layer-toggle-kills .adm-layer-dot { background: var(--danger); }
.adm-layer-toggle-deaths .adm-layer-dot { background: var(--text-3); }
.adm-layer-toggle-builds .adm-layer-dot { background: var(--amber); }
.adm-layer-toggle-heat .adm-layer-dot { background: var(--danger); border-radius: 2px; }
.adm-layer-toggle-bases .adm-layer-dot { background: transparent; border: 2px solid var(--accent); }
.adm-layer-toggle-incidents .adm-layer-dot { background: var(--danger); border-radius: 2px; }
.adm-layer-toggle-vehicles .adm-layer-dot { background: var(--amber); border-radius: 2px; transform: rotate(45deg); }
.adm-layer-toggle-players .adm-layer-dot { background: var(--accent); }

/* --- tables --------------------------------------------------------- */
/* Wide content scrolls INSIDE its own box. The page never scrolls sideways;
   a table does. (Also why the headings are NOT sticky: position: sticky
   cannot escape a scroll container, so inside this wrap it silently never
   stuck. Dead behaviour removed rather than left to mislead.) */
.adm-table-wrap { overflow-x: auto; max-width: 100%; }
.adm-table thead th {
  background: var(--bg-2);
}
/* The commands filter row follows too: filtering a list you cannot see the
   controls for is a scroll-up tax on every query. */
.adm-cmdqueue-filters {
  position: sticky;
  top: var(--header-h);
  z-index: var(--z-sticky);
  background: var(--bg-2);
  padding-block: .35rem;
}
.adm-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.adm-table th, .adm-table td { text-align: left; padding: .55rem .7rem; border-bottom: 1px solid var(--line); }
.adm-table th { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .07em; color: var(--text-3); }
/* Zebra plus hover: the stripes carry the eye across a wide row, the hover
   answers "which row am I about to click". Even rows only, faint on purpose —
   at full strength stripes shout louder than the data. */
.adm-table tbody tr:nth-child(even) { background: color-mix(in srgb, var(--bg-3) 38%, transparent); }
.adm-table tbody tr:hover { background: color-mix(in srgb, var(--accent) 8%, var(--bg-3)); }
.adm-table td { font-variant-numeric: tabular-nums; }
.adm-badge { padding: .1rem .45rem; border-radius: var(--r-sm); font-size: var(--fs-xs); }
.adm-badge-ok { background: color-mix(in srgb, var(--forest) 40%, transparent); }
.adm-badge-warn { background: color-mix(in srgb, var(--amber) 30%, transparent); }
.adm-badge-bad { background: color-mix(in srgb, var(--danger) 28%, transparent); }
/* (The phone layout for .adm-table lives in ONE place — the ≤767.98px block
   under RESPONSIVE TABLES. A second copy here, with a different data-label
   format and the deprecated clip property, used to fight it below 600px.) */

/* --- standing notices ----------------------------------------------- */
/* A limit staff must not discover by being surprised: that a ban is queued
   rather than confirmed, that the admission registers cannot be written from
   here, that Smart Operations never acts by itself, that outbound delivery is
   switched off. */
.adm-banner {
  display: flex; gap: .65rem; align-items: flex-start;
  padding: .7rem .85rem; border-radius: var(--r-md);
  border: 1px solid var(--line); background: var(--bg-2);
  margin-bottom: 1rem;
}
.adm-banner-warn {
  border-color: color-mix(in srgb, var(--amber) 55%, var(--line));
  background: color-mix(in srgb, var(--amber) 10%, var(--bg-2));
}
/* `bad` is used for a fact about the player rather than a limit of the panel —
   "permanently banned, confirmed". It had no rule at all, so the one banner
   that most needed to stop the reader looked exactly like a neutral note. */
.adm-banner-bad {
  border-color: color-mix(in srgb, var(--danger) 55%, var(--line));
  background: color-mix(in srgb, var(--danger) 10%, var(--bg-2));
}
.adm-banner-info {
  border-color: color-mix(in srgb, var(--accent, var(--forest)) 45%, var(--line));
}
.adm-banner-icon { width: 1.1rem; height: 1.1rem; flex: none; margin-top: .1rem; color: var(--text-2); }
.adm-banner-title { margin: 0; font-weight: 600; font-size: var(--fs-sm); }
.adm-banner-text { margin: .2rem 0 0; font-size: var(--fs-sm); color: var(--text-2); }

.adm-badge-neutral { background: var(--bg-3); }

/* --- count chips ----------------------------------------------------- */
/* The ONE definition. A second .adm-chiprow further down used to half-override
   this one property-by-property; these values are the merged result. */
.adm-chiprow { display: inline-flex; flex-wrap: wrap; gap: .3rem; margin: .6rem 0 0; }
.adm-countchip {
  display: inline-flex; align-items: baseline; gap: .35rem;
  padding: .25rem .55rem; border-radius: var(--r-sm);
  background: var(--bg-3); border: 1px solid var(--line);
}
.adm-countchip-n { font-variant-numeric: tabular-nums; font-weight: 600; }
.adm-countchip-k { font-size: var(--fs-xs); color: var(--text-3); }

/* --- the honesty panels ---------------------------------------------- */
/* Used for "what this cannot see". Long prose, so it reads as a list of
   explanations rather than a table of failures. */
.adm-reasons { margin: .5rem 0 0; display: grid; gap: .5rem; }
.adm-reasons dt { font-size: var(--fs-sm); font-weight: 600; }
.adm-reasons dd { margin: .1rem 0 0; font-size: var(--fs-sm); color: var(--text-2); }

/* --- command catalogue ----------------------------------------------- */
.adm-cmd {
  display: inline-block; font-family: var(--font-mono);
  font-size: var(--fs-xs); padding: .1rem .35rem;
  background: var(--bg-3); border-radius: var(--r-sm);
}
.adm-cmd-desc { display: block; margin-top: .2rem; font-size: var(--fs-xs); color: var(--text-3); }

/* --- player monitor -------------------------------------------------- */
.adm-player-name { display: block; font-weight: 600; }
.adm-player-uid {
  display: block; font-size: var(--fs-xs); color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

/* A bar is only drawn where a maximum was actually reported. Everything
   else is a number or the words "not measured" — never an empty bar, which
   would read as a confident zero. */
.adm-vital { display: flex; align-items: center; gap: .4rem; min-width: 6.5rem; }
.adm-bar {
  flex: 1; height: .45rem; min-width: 2.5rem;
  background: var(--bg-3); border-radius: var(--r-pill); overflow: hidden;
}
.adm-bar-fill { display: block; height: 100%; border-radius: var(--r-pill); }
.adm-bar-ok { background: var(--forest); }
.adm-bar-warn { background: var(--amber); }
.adm-bar-bad { background: var(--danger); }
.adm-vital-n { font-size: var(--fs-xs); font-variant-numeric: tabular-nums; }
.adm-vital-raw { font-size: var(--fs-sm); font-variant-numeric: tabular-nums; color: var(--text-2); }
.adm-vital-none {
  font-size: var(--fs-xs); color: var(--text-3); font-style: italic;
  border-bottom: 1px dotted var(--text-3); cursor: help;
}

/* The vitals CLUSTER: six icon+bar+value minis in one table cell — the old
   panel's language, and the cure for the twelve-column monitor that
   overlapped itself the first night it met a real player. */
.adm-vits {
  display: grid;
  grid-template-columns: repeat(3, minmax(5.2rem, 1fr));
  gap: .2rem .7rem;
  min-width: 17rem;
}
.adm-vit { display: flex; align-items: center; gap: .3rem; cursor: help; }
.adm-vit-ic { display: inline-flex; flex: none; }
.adm-vit-ic svg { width: 13px; height: 13px; fill: currentColor; display: block; }
.adm-vit-bar { flex: 1; min-width: 1.8rem; height: .35rem; }
.adm-vit-n {
  font-size: var(--fs-xs); font-variant-numeric: tabular-nums;
  min-width: 2.2ch; text-align: right;
}
.adm-vit-ok { color: var(--forest); }
.adm-vit-warn { color: var(--amber); }
.adm-vit-bad { color: var(--danger); }
.adm-vit-none { color: var(--text-3); }
.adm-vit .adm-vit-n { color: var(--text); }
.adm-vit-none .adm-vit-n { color: var(--text-3); }
@media (max-width: 720px) {
  .adm-vits { grid-template-columns: repeat(2, minmax(5.2rem, 1fr)); min-width: 0; }
}
.adm-flagrow { display: flex; flex-wrap: wrap; gap: .25rem; }

/* One-click "undo this status" buttons under the Right-now flags. Amber-edged
   so they read as a correction, not an ordinary action; wrap on a phone. */
.adm-status-fixes { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .6rem; }
.adm-status-fix {
  border: 1px solid color-mix(in srgb, var(--amber) 45%, transparent);
  background: color-mix(in srgb, var(--amber) 10%, var(--bg-2));
  color: var(--text); border-radius: var(--r-sm);
  padding: .3rem .6rem; font-size: var(--fs-sm); cursor: pointer;
  max-width: 100%;
}
.adm-status-fix:hover:not([disabled]) { background: color-mix(in srgb, var(--amber) 18%, var(--bg-2)); }
.adm-status-fix strong { color: var(--amber); }
.adm-status-fix[disabled] { opacity: .55; cursor: not-allowed; }
/* Care suggestions are restorative, so they read GREEN — a distinct meaning
   from the amber "undo a state" strip they sit beside. Same shape, same
   disabled treatment; only the tone differs so an operator can tell "fix a
   vital" from "reverse a status" at a glance. */
.adm-care-fix {
  border-color: color-mix(in srgb, var(--tier-basic, #4caf50) 50%, transparent);
  background: color-mix(in srgb, var(--tier-basic, #4caf50) 10%, var(--bg-2));
}
.adm-care-fix:hover:not([disabled]) { background: color-mix(in srgb, var(--tier-basic, #4caf50) 18%, var(--bg-2)); }
.adm-care-fix strong { color: var(--tier-basic, #4caf50); }

/* --- player profile -------------------------------------------------- */
.adm-player-link { color: inherit; text-decoration: none; border-bottom: 1px solid transparent; }
.adm-player-link:hover, .adm-player-link:focus-visible {
  border-bottom-color: currentColor; text-decoration: none;
}

.adm-profile-head {
  display: flex; align-items: center; gap: .85rem; flex-wrap: wrap;
  margin-bottom: 1rem;
}
.adm-back { flex: none; }
.adm-profile-name { margin: 0; font-size: var(--fs-lg); }
.adm-profile-uid {
  font-size: var(--fs-xs); color: var(--text-3); font-variant-numeric: tabular-nums;
}

.adm-tabs {
  display: flex; gap: .15rem; flex-wrap: wrap;
  border-bottom: 1px solid var(--line); margin-bottom: 1rem;
}
.adm-tab {
  appearance: none; background: none; border: 0; cursor: pointer;
  padding: .5rem .75rem; color: var(--text-2);
  font: inherit; font-size: var(--fs-sm);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.adm-tab:hover { color: var(--text); }
.adm-tab[aria-selected="true"] {
  /* 2px underline to match the area .adm-tabs-row active indicator, so the two
     admin tab strips read identically. */
  color: var(--text); font-weight: 600; border-bottom-color: var(--accent);
}
/* "Moderation ⟨3⟩" — how much waits behind a tab, before it is opened. */
.adm-tab-count {
  display: inline-block; margin-left: .4rem; padding: 0 .4rem;
  border-radius: var(--r-pill); background: var(--bg-3);
  font-size: var(--fs-xs); font-weight: 600; color: var(--text-2);
  line-height: 1.4;
}

/* One vital per row, so the reason for an unmeasured reading has room to be
   read rather than being hidden in a tooltip. */
.adm-vitals { display: grid; gap: .5rem; margin: .5rem 0; }
.adm-vitalrow {
  display: grid; grid-template-columns: 8rem minmax(6rem, 14rem) 1fr;
  gap: .75rem; align-items: center;
}
.adm-vitalrow-label { font-size: var(--fs-sm); color: var(--text-2); }
.adm-vitalrow-why { font-size: var(--fs-xs); color: var(--text-3); }
@media (max-width: 599.98px) {
  .adm-vitalrow { grid-template-columns: 1fr; gap: .15rem; }
}

.adm-gearlist {
  list-style: none; margin: .5rem 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: .35rem;
}
.adm-gearlist li {
  padding: .2rem .5rem; border-radius: var(--r-sm);
  background: var(--bg-3); border: 1px solid var(--line);
  font-size: var(--fs-sm);
}

/* --- the structured gear tree ------------------------------------------- */
/* Nesting is carried by an indent AND a rule line, so the shape survives at
   small sizes where indentation alone stops being legible. */
.adm-gear-tree { list-style: none; margin: .4rem 0 0; padding: 0; }
.adm-gear-tree .adm-gear-tree {
  margin-left: .55rem; padding-left: .7rem;
  border-left: 1px solid var(--line);
}

.adm-gear-node { margin: .15rem 0; }

/* The expander and the leaf row look identical on purpose: whether a thing
   happens to contain something else should not change how it reads. */
.adm-gear-toggle, .adm-gear-leaf {
  display: flex; align-items: baseline; gap: .5rem;
  width: 100%; text-align: left;
  padding: .25rem .45rem; border-radius: var(--r-sm);
  background: none; border: 1px solid transparent;
  color: inherit; font: inherit;
}
.adm-gear-toggle { cursor: pointer; }
.adm-gear-toggle:hover { background: var(--bg-3); }

/* A disclosure triangle that rotates. Never the only cue — aria-expanded
   carries the state for anything that is not looking at the glyph. */
.adm-gear-toggle::before {
  content: '▸'; flex: 0 0 auto; color: var(--text-3);
  transition: transform .12s ease;
}
.adm-gear-toggle[aria-expanded="true"]::before { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
  .adm-gear-toggle::before { transition: none; }
}
/* Keeps the leaf's text aligned with an expandable row's text. */
.adm-gear-leaf::before { content: ''; flex: 0 0 auto; width: 1ch; }

.adm-gear-label { display: flex; flex-wrap: wrap; align-items: baseline; gap: .45rem; min-width: 0; }
.adm-gear-class { font-weight: 600; overflow-wrap: anywhere; }
.adm-gear-slot {
  font-size: var(--fs-xs); color: var(--text-3);
  padding: .05rem .35rem; border-radius: var(--r-sm); background: var(--bg-3);
}
.adm-gear-summary { font-size: var(--fs-sm); color: var(--text-2); font-variant-numeric: tabular-nums; }

/* Long, and only interesting when you are chasing one specific item. */
.adm-gear-id {
  font-size: var(--fs-xs); color: var(--text-3);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  overflow-wrap: anywhere;
}
.adm-gear-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
  margin-left: 1.5rem; margin-block: .1rem .25rem;
}
/* An item the game server is removing right now: still visible (nothing
   vanishes before the game confirms), visibly on its way out. */
.adm-gear-removing { opacity: .45; }
.adm-gear-removing .adm-gear-class { text-decoration: line-through; }

/* --- the live pulse: the profile's breathing dashboard head (goal 105) -- */
.adm-pulse { display: grid; gap: .8rem; }
.adm-pulse-head { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }
.adm-pulse-who { display: flex; flex-direction: column; min-width: 0; }
.adm-pulse-name { font-size: var(--fs-lg); font-weight: 700; overflow-wrap: anywhere; }
.adm-pulse-sub { font-size: var(--fs-xs); color: var(--text-3); overflow-wrap: anywhere; }
.adm-pulse-online {
  margin-left: auto; padding: .2rem .6rem; border-radius: 999px;
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .06em;
}
.adm-pulse-online.is-on { background: color-mix(in srgb, var(--ok, #2e9e5b) 18%, transparent); color: var(--ok, #2e9e5b); }
.adm-pulse-online.is-off { background: color-mix(in srgb, var(--text-3) 16%, transparent); color: var(--text-3); }
.adm-pulse-where {
  display: flex; flex-wrap: wrap; gap: .35rem 1rem; align-items: baseline;
  font-size: var(--fs-sm); color: var(--text-2);
}
.adm-pulse-place { font-weight: 600; }
.adm-pulse-coords { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--text-3); font-size: var(--fs-xs); }
.adm-pulse-kv span:first-child { color: var(--text-3); }
.adm-pulse-tiles {
  display: grid; gap: .55rem;
  grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
}
.adm-pulse-tile {
  display: grid; gap: .25rem; padding: .55rem .65rem;
  border: 1px solid var(--line-1, rgba(128,128,128,.25)); border-radius: .55rem;
  background: color-mix(in srgb, var(--text-1) 3%, transparent);
}
.adm-pulse-tile-label { font-size: var(--fs-xs); color: var(--text-3); text-transform: uppercase; letter-spacing: .05em; }
.adm-pulse-tile-value { font-size: var(--fs-md); font-weight: 700; font-variant-numeric: tabular-nums; }
.adm-pulse-track { height: 5px; border-radius: 3px; background: color-mix(in srgb, var(--text-3) 18%, transparent); overflow: hidden; }
.adm-pulse-fill { height: 100%; border-radius: 3px; background: var(--accent, #4f8ff0); transition: width .5s ease; }
.adm-pulse-fill-health { background: #2e9e5b; }
.adm-pulse-fill-blood { background: #c0392b; }
.adm-pulse-fill-water { background: #3498db; }
.adm-pulse-fill-energy { background: #e67e22; }
.adm-pulse-fill-shock { background: #8e44ad; }

/* --- the quick actions strip ------------------------------------------- */
.adm-quickbar {
  display: grid; gap: .5rem; margin: .8rem 0 1rem;
  grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
}
.adm-quickbtn {
  display: flex; flex-direction: column; align-items: center; gap: .3rem;
  padding: .7rem .5rem; border-radius: .6rem; cursor: pointer;
  border: 1px solid var(--line-1, rgba(128,128,128,.25));
  background: color-mix(in srgb, var(--text-1) 4%, transparent);
  color: var(--text-1); font: inherit;
}
.adm-quickbtn:hover:not([disabled]) { border-color: var(--accent, #4f8ff0); background: color-mix(in srgb, var(--accent, #4f8ff0) 10%, transparent); }
.adm-quickbtn[disabled] { opacity: .45; cursor: not-allowed; }
.adm-quickbtn-icon { font-size: 1.3rem; line-height: 1; }
.adm-quickbtn-label { font-size: var(--fs-xs); font-weight: 600; }

/* --- the vehicle spawner (goal 105) ------------------------------------ */
.adm-vspawn { max-width: 46rem; }
.adm-vspawn-grid {
  display: grid; gap: .6rem; margin-block: .8rem;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  max-height: 55vh; overflow-y: auto;
}
.adm-vspawn-card {
  display: flex; flex-direction: column; gap: .3rem; padding: .5rem;
  border: 1px solid var(--line-1, rgba(128,128,128,.25)); border-radius: .6rem;
  background: color-mix(in srgb, var(--text-1) 3%, transparent);
  color: var(--text-1); font: inherit; cursor: pointer; text-align: left;
}
.adm-vspawn-card:hover { border-color: var(--accent, #4f8ff0); background: color-mix(in srgb, var(--accent, #4f8ff0) 8%, transparent); }
/* The FULL vehicle, its own shape: contain, never crop-to-square. */
.adm-vspawn-imgwrap {
  aspect-ratio: 16 / 9; display: grid; place-items: center;
  border-radius: .4rem; overflow: hidden;
  background: color-mix(in srgb, var(--text-3) 8%, transparent);
}
.adm-vspawn-img { max-width: 100%; max-height: 100%; object-fit: contain; }
.adm-vspawn-noimg { font-size: 2rem; opacity: .5; }
.adm-vspawn-name { font-weight: 700; font-size: var(--fs-sm); }
.adm-vspawn-meta { font-size: var(--fs-xs); color: var(--text-3); }
.adm-vspawn-heroimg { max-width: 100%; max-height: 12rem; object-fit: contain; display: block; margin-inline: auto; }
.adm-vspawn-confirm { display: grid; gap: .5rem; }
.adm-vspawn-advanced { margin-top: .6rem; border-top: 1px dashed var(--line-1, rgba(128,128,128,.3)); padding-top: .6rem; }
.adm-vspawn-advanced summary { cursor: pointer; font-size: var(--fs-xs); color: var(--text-3); }

@media (max-width: 640px) {
  .adm-pulse-tiles { grid-template-columns: repeat(2, 1fr); }
  .adm-quickbar { grid-template-columns: repeat(3, 1fr); }
  .adm-vspawn-grid { grid-template-columns: repeat(2, 1fr); max-height: 50vh; }
}

.adm-gear-caveats { margin-top: .9rem; }
.adm-gear-caveats .adm-gearlist { flex-direction: column; }

/* --- command console -------------------------------------------------- */
.adm-console { display: grid; grid-template-columns: minmax(13rem, 18rem) 1fr; gap: 1rem; }
@media (max-width: 899.98px) { .adm-console { grid-template-columns: 1fr; } }

.adm-console-list {
  display: flex; flex-direction: column; gap: .2rem;
  max-height: 32rem; overflow-y: auto;
  border: 1px solid var(--line); border-radius: var(--r-md); padding: .35rem;
  padding-right: .25rem;
}
.adm-console-item {
  display: flex; align-items: center; gap: .4rem; flex-wrap: wrap;
  appearance: none; background: none; border: 0; cursor: pointer;
  font: inherit; font-size: var(--fs-sm); text-align: left;
  padding: .4rem .5rem; border-radius: var(--r-sm); color: var(--text);
}
.adm-console-item:hover { background: var(--bg-3); }
.adm-console-item[aria-selected="true"] {
  background: color-mix(in srgb, var(--forest) 22%, transparent);
  outline: 1px solid color-mix(in srgb, var(--forest) 50%, transparent);
}
.adm-console-name { font-family: var(--font-mono); font-size: var(--fs-xs); }
.adm-console-detail { min-width: 0; }

/* --- typed command forms ---------------------------------------------- */
.adm-cmdform-head { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: .35rem; }
.adm-cmdform-title { margin: 0; font-size: var(--fs-md); font-family: var(--font-mono); }
/* Grid, not block: label, control and help each get their OWN row. As inline
   flow, the help text started on the same line as the input and wrapped
   around it — helper sentences split around the control they explain. */
.adm-field { display: grid; justify-items: start; margin: .7rem 0; }
.adm-field > .field-help { margin-top: .3rem; }
.adm-field-label {
  display: flex; align-items: baseline; gap: .4rem;
  font-size: var(--fs-sm); font-weight: 600; margin-bottom: .2rem;
}
.adm-req { font-size: var(--fs-xs); font-weight: 400; color: var(--amber); }
.adm-input {
  width: 100%; max-width: 26rem; box-sizing: border-box;
  padding: .45rem .6rem; font: inherit; font-size: var(--fs-sm);
  color: var(--text); background: var(--bg-1);
  border: 1px solid var(--line); border-radius: var(--r-sm);
}
/* Focus is the ONE global ring — no per-component outlines doubling it. */
/* The bound the SERVER enforces, shown next to the box that must respect it. */
.adm-field-bound { display: block; margin-top: .15rem; font-size: var(--fs-xs); color: var(--text-3); }
.adm-cmdform-actions { display: flex; gap: .5rem; flex-wrap: wrap; margin: 1rem 0 .5rem; }
/* The DENSE small button is an admin decision, so it is scoped to the admin
   surfaces (the panel plus its body-mounted overlays). Unscoped, this rule
   was rewriting the PUBLIC site's .btn-sm — cart, base-ping and shop filter
   buttons all shrank below a usable tap target. 2.25rem is the floor a thumb
   can still land on; the public 38px default stays untouched elsewhere. */
:is(#view-admin, .adm-modal-overlay) .btn-sm {
  padding: .2rem .5rem; font-size: var(--fs-xs);
  min-height: 2.25rem;
}
/*
 * Tappable controls need a target a thumb can actually hit.
 *
 * Keyed on BOTH a coarse pointer and a narrow viewport. `pointer: coarse`
 * alone is not enough: a narrow window on a desktop still reports a fine
 * pointer, so the rule silently never applied and the controls stayed 22px —
 * which is exactly how this was missed until it was measured.
 *
 * `display: inline-flex` is not decoration. min-height does nothing to an
 * inline element, and the player-name link is an <a>.
 */
@media (pointer: coarse), (max-width: 599.98px) {
  /* The dense 36px shrink is an admin decision — scope .btn-sm to admin so the
     PUBLIC small buttons (shop filter toggle, reset filters, refresh, cart
     footer) are NOT dragged below the touch target. */
  :is(#view-admin, .adm-modal-overlay) .btn-sm,
  .adm-action, .adm-console-item, .adm-palette-row, .adm-tab,
  .adm-linkbtn, .adm-player-link {
    min-height: 2.25rem;
    display: inline-flex;
    align-items: center;
  }
  .adm-console-item, .adm-palette-row { display: flex; }
  /* Public small buttons must still meet the full touch target. */
  .btn-sm { min-height: var(--tap); display: inline-flex; align-items: center; }
}

.adm-preview, .adm-cmdresult { margin-top: .75rem; }
.adm-preview-inner {
  border: 1px dashed var(--line); border-radius: var(--r-md); padding: .7rem .85rem;
}
.adm-preview-effect { margin: 0 0 .4rem; font-size: var(--fs-sm); font-weight: 600; }
.adm-engine-msg {
  margin: .3rem 0; font-family: var(--font-mono);
  font-size: var(--fs-xs); color: var(--text-2);
}
.adm-status-head { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: .4rem; }
.adm-linkbtn {
  appearance: none; background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; font-size: var(--fs-sm); color: var(--text);
  font-family: var(--font-mono);
  border-bottom: 1px solid var(--line);
}
.adm-linkbtn:hover, .adm-linkbtn:focus-visible { border-bottom-color: currentColor; }

/* --- confirmation ------------------------------------------------------ */
/* Deliberately not window.confirm: the operator must see the exact effect,
   and for the dangerous tiers must type the command name. Enter is never the
   confirm key — a reflex is not a decision. */
.adm-modal-overlay {
  position: fixed; inset: 0; z-index: var(--z-modal);
  display: flex; align-items: center; justify-content: center; padding: 1rem;
  background: color-mix(in srgb, #000 62%, transparent);
}
.adm-modal {
  width: min(34rem, 100%); max-height: 85vh; overflow-y: auto;
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 1.1rem 1.2rem;
  box-shadow: var(--shadow-3);
}
.adm-modal-high, .adm-modal-critical {
  border-color: color-mix(in srgb, var(--danger) 60%, var(--line));
}
.adm-modal-title { margin: 0 0 .4rem; font-size: var(--fs-md); }
.adm-modal-effect {
  margin: 0 0 .8rem; font-size: var(--fs-sm); color: var(--text);
  padding: .55rem .7rem; border-radius: var(--r-sm);
  background: var(--bg-3); border-left: 3px solid var(--amber);
}
.adm-modal-actions { display: flex; gap: .5rem; justify-content: flex-end; margin-top: 1rem; }
/* Solid danger is the ADMIN treatment (a destructive confirm should look like
   one). Scoped, because unscoped it repainted the public site's quiet
   outline .btn-danger — e.g. the account page's unlink button. */
:is(#view-admin, .adm-modal-overlay) .btn-danger {
  background: var(--danger); color: #fff;
  border: 1px solid color-mix(in srgb, var(--danger) 70%, #000);
}
.btn-danger[disabled], .btn-primary[disabled] { opacity: .45; cursor: not-allowed; }

/* --- receipts ---------------------------------------------------------- */
.adm-toasts {
  position: fixed; right: 1rem; bottom: 1rem; z-index: var(--z-toast);
  display: flex; flex-direction: column; gap: .5rem;
  max-width: min(24rem, calc(100vw - 2rem));
  /* The HOST catches nothing. It is a shrink-to-fit column that lives above
     the bottom nav on a phone, so with default pointer-events the gaps
     between stacked receipts — and the whole box while a receipt is on
     screen — swallowed taps meant for the tabs underneath. The receipts
     themselves take pointer-events back below, exactly as .toast-region and
     .toast already do on the public side. */
  pointer-events: none;
}
@media (max-width: 767.98px) {
  /* Below 768px the public bottom nav is on screen, and the admin route never
     hides it. A receipt pinned 1rem off the raw viewport bottom therefore
     lands ON the tab row: the right-hand tabs always, and Cart and Shop too
     as soon as a message is long or carries a detail line, because the host
     is right-anchored and shrink-to-fit. There is no dismiss handler on a
     receipt, so an intercepted tap is not delayed, it is lost — for the full
     five seconds, or nine on an error. Lift the column to sit ON the nav, the
     same measurement .adm-map-drawer uses. Above 768px the nav is gone and
     plain bottom: 1rem is correct, so this stays inside the query. */
  .adm-toasts { bottom: calc(var(--bottom-nav-h) + env(safe-area-inset-bottom, 0px) + .75rem); }
}
.adm-toast {
  pointer-events: auto;
  display: flex; gap: .5rem; align-items: flex-start;
  padding: .6rem .75rem; border-radius: var(--r-md);
  background: var(--bg-2); border: 1px solid var(--line);
  box-shadow: var(--shadow-2);
}
.adm-toast-ok { border-color: color-mix(in srgb, var(--forest) 60%, var(--line)); }
.adm-toast-err { border-color: color-mix(in srgb, var(--danger) 60%, var(--line)); }
/* The action was taken but did not fully land — a ban recorded here that the
   game server has not confirmed. Neither green nor red would be true. */
.adm-toast-warn { border-color: color-mix(in srgb, var(--amber) 60%, var(--line)); }
.adm-toast-icon { width: 1.05rem; height: 1.05rem; flex: none; margin-top: .1rem; }
.adm-toast-msg { margin: 0; font-size: var(--fs-sm); }
.adm-toast-detail { margin: .2rem 0 0; font-size: var(--fs-xs); color: var(--text-3); }

@media (prefers-reduced-motion: no-preference) {
  .adm-toast { animation: adm-toast-in .18s ease-out; }
  @keyframes adm-toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
}

/* --- kit studio -------------------------------------------------------- */
.adm-studio { display: grid; grid-template-columns: minmax(14rem, 20rem) 1fr; gap: 1rem; }
@media (max-width: 899.98px) { .adm-studio { grid-template-columns: 1fr; } }
.adm-studio-side { display: flex; flex-direction: column; gap: .5rem; min-width: 0; }
/* A blocked class is marked in place, next to the item it breaks — a count at
   the top tells an operator nothing they can act on. */
.adm-row-bad td { background: color-mix(in srgb, var(--danger) 9%, transparent); }
.adm-presetgrid {
  display: grid; gap: .6rem; margin-top: .5rem;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
}
.adm-preset { padding: .6rem .7rem; border: 1px solid var(--line); border-radius: var(--r-md); }
.adm-preset-name { margin: 0 0 .15rem; font-weight: 600; font-size: var(--fs-sm); }
.adm-preset-items {
  margin: .35rem 0 0; font-size: var(--fs-xs); color: var(--text-3);
  font-family: var(--font-mono); word-break: break-word;
}

/* --- quick actions ----------------------------------------------------- */
.adm-actionbar { display: flex; flex-wrap: wrap; gap: .25rem; }
.adm-action { display: inline-flex; align-items: center; gap: .3rem; white-space: nowrap; }
.adm-action svg { width: .85rem; height: .85rem; }
/* Disabled rather than hidden: an operator who cannot see a control learns
   nothing; one who sees it greyed out with a reason learns the rule. */
.adm-action[disabled] { opacity: .4; cursor: not-allowed; }
.adm-action-high { border-color: color-mix(in srgb, var(--danger) 45%, var(--line)); }
@media (max-width: 599.98px) {
  .adm-actionbar { gap: .35rem; }
  .adm-action { padding: .35rem .55rem; }
}

/* --- command palette --------------------------------------------------- */
.adm-palette-overlay { align-items: flex-start; padding-top: 10vh; }
.adm-palette {
  width: min(40rem, 100%); background: var(--bg-2);
  border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--shadow-3); overflow: hidden;
}
.adm-palette-input {
  max-width: none; border: 0; border-radius: 0; font-size: var(--fs-md);
  padding: .8rem 1rem; background: transparent;
}
/* (No outline:none here. The global :focus-visible ring applies; a local
   removal was a landmine waiting for the day the global rule changes.) */
.adm-palette-status {
  margin: 0; padding: .25rem 1rem .5rem; border-bottom: 1px solid var(--line);
}
.adm-palette-list { max-height: 55vh; overflow-y: auto; }
.adm-palette-row {
  display: flex; align-items: center; gap: .5rem; width: 100%;
  appearance: none; background: none; border: 0; cursor: pointer;
  font: inherit; font-size: var(--fs-sm); text-align: left;
  padding: .5rem 1rem; color: var(--text);
}
.adm-palette-row:hover, .adm-palette-row.is-active { background: var(--bg-3); }
/* Row + star share a line; the highlight lives on the wrap so the star is
   inside it. The star is its own control and never navigates. */
.adm-palette-rowwrap { display: flex; align-items: center; }
.adm-palette-rowwrap.is-active, .adm-palette-rowwrap:hover { background: var(--bg-3); }
.adm-palette-rowwrap .adm-palette-row { flex: 1 1 auto; }
.adm-palette-star {
  appearance: none; background: none; border: 0; cursor: pointer;
  font: inherit; font-size: var(--fs-sm); color: var(--text-3);
  padding: .5rem .8rem; flex: 0 0 auto;
}
.adm-palette-star.is-fav { color: var(--amber); }
.adm-palette-title { font-weight: 600; }
.adm-palette-context {
  margin-left: auto; font-size: var(--fs-xs); color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 45%;
}

/* A marker that can be opened says so by behaving like a control. */
.adm-marker-live { appearance: none; padding: 0; border: 0; cursor: pointer; }
/* Hover affordance only — keyboard focus gets the global ring. */
.adm-marker-live:hover {
  outline: 2px solid var(--text); outline-offset: 1px;
}

/* --- one player's path --------------------------------------------------- */
/* The SVG fills the stage and shares the marker layer's coordinate space, so
   the line and the dots cannot drift apart. It never takes pointer events:
   a path drawn over the map must not swallow clicks on the markers under it. */
.adm-trail {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; overflow: visible;
}
.adm-trail-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: .45;
  stroke-linejoin: round;
  stroke-linecap: round;
  opacity: .85;
}
.adm-trail-dot { fill: var(--accent); opacity: .7; }
/* Start and end are the two the eye needs. Shape and fill carry the
   difference, not colour alone. */
.adm-trail-start { fill: var(--bg-1); stroke: var(--accent); stroke-width: .5; opacity: 1; }
.adm-trail-end   { fill: var(--accent); opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  .adm-trail-end { animation: adm-trail-pulse 2.4s ease-in-out infinite; }
}
@keyframes adm-trail-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .45; }
}

/* --- economy ------------------------------------------------------------- */
/* Signed figures. Colour is a reinforcement, never the only carrier: the sign
   is printed too, so this reads correctly without colour vision. */
.adm-num { font-variant-numeric: tabular-nums; font-weight: 650; }
.adm-num-up { color: var(--accent); }
.adm-num-down { color: var(--danger); }

.adm-subhead {
  margin: 1.1rem 0 .5rem; font-size: var(--fs-sm); font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--text-3);
}
.adm-subhead:first-child { margin-top: 0; }

/* The innocent explanations under a pattern list. Deliberately normal reading
   size — small print under a list of names is how a caveat gets skipped. */
.adm-note-list {
  margin: .6rem 0 0; padding-left: 1.1rem; color: var(--text-2);
  font-size: var(--fs-sm); line-height: 1.55;
}
.adm-note-list li { margin: .25rem 0; }

.adm-card-note {
  font-size: var(--fs-xs); color: var(--text-3);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

.adm-form { display: flex; flex-direction: column; gap: .7rem; max-width: 34rem; }
.adm-form .btn { align-self: flex-start; }

.adm-statement:empty { display: none; }

/* --- shop editor --------------------------------------------------------- */
.adm-textarea {
  font-family: var(--font-mono, ui-monospace, "Cascadia Code", Consolas, monospace);
  font-size: var(--fs-sm); line-height: 1.5; resize: vertical; min-height: 7rem;
}

.adm-filterrow {
  display: grid; gap: .7rem; margin: .7rem 0;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}

.adm-checkrow { display: flex; flex-wrap: wrap; gap: .5rem 1.1rem; margin: .6rem 0; }
.adm-check { display: inline-flex; align-items: center; gap: .4rem; font-size: var(--fs-sm); }
.adm-check input { accent-color: var(--accent); width: 1rem; height: 1rem; }

.adm-btnrow { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .7rem; }

/*
 * The player directory's filter row. Six dropdowns and a Clear button, wrapping
 * onto as many lines as the width allows — on a phone that is one per line,
 * which is right: these are read one at a time, not scanned as a block.
 *
 * The selects are allowed to shrink but not below 10rem, because the labels
 * ("Seen in the last 30 days") are the whole point — a filter row squeezed to
 * "Seen in…" is a row of riddles.
 */
.adm-filters { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-top: .6rem; }
.adm-filters .adm-filter-sel { flex: 1 1 10rem; min-width: 10rem; max-width: 16rem; width: auto; }
.adm-filters .btn { margin-inline-start: auto; }

.adm-shop-name { font-weight: 650; }
.adm-shop-sub { font-size: var(--fs-xs); color: var(--text-3); margin-top: .15rem; }

.adm-preview:empty { display: none; }
.adm-preview { margin-top: .8rem; }

.adm-shop-detail:empty, .adm-shop-results:empty { display: none; }

/* The editor is a form, not a table: one column on a phone, two where there
   is room. Contents spans both, because a class list wrapped at 20 characters
   is unreadable. */
@media (min-width: 60rem) {
  .adm-form { max-width: none; display: grid; gap: .7rem 1.2rem; grid-template-columns: 1fr 1fr; }
  .adm-form > p, .adm-form > .adm-btnrow, .adm-form > .adm-preview,
  .adm-form > .adm-checkrow { grid-column: 1 / -1; }
  .adm-form > label:has(textarea) { grid-column: 1 / -1; }
}

/* --- order log ----------------------------------------------------------- */
.adm-muted { color: var(--text-3); font-style: italic; }
.adm-input-sm { max-width: 9rem; display: inline-block; }

/* --- staff access ------------------------------------------------------- */
/* The closed door and the sign-in prompt. Centred and quiet: this is not an
   error the visitor caused, and a wall of red would say it was. */
.adm-gate {
  max-width: 34rem; margin: 3rem auto; padding: 1.6rem;
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--r-md); text-align: center;
}
.adm-gate h2 { margin: 0 0 .6rem; font-size: var(--fs-lg); }
.adm-gate p { color: var(--text-2); line-height: 1.6; margin: 0 0 1.1rem; }

.account-staff {
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: 1.1rem 1.2rem; margin-bottom: 1.4rem;
}
.account-staff h3 { margin: 0 0 .4rem; font-size: var(--fs-md); }
.account-staff p { color: var(--text-2); margin: 0 0 .8rem; line-height: 1.55; }

/* Held and absent are distinguished by a word as well as a colour — the list
   has to be readable without colour vision. */
.perm-list { list-style: none; padding: 0; margin: 0 0 .8rem; display: grid; gap: .3rem;
             grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
.perm-list li { font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.perm-held { color: var(--accent); }
.perm-absent { color: var(--text-3); }
.muted-note { font-size: var(--fs-xs); color: var(--text-3); }

/* --- live Livonia map (public / player) --------------------------------- */
.lm-wrap { display: grid; gap: 1.2rem; grid-template-columns: 1fr; }
@media (min-width: 60rem) { .lm-wrap { grid-template-columns: minmax(0,1fr) 20rem; } }

.lm-figure { position: relative; }
/* The stage is a square: Livonia is 12800×12800, so the aspect ratio must be
   1:1 or every marker is stretched off its true position. */
.lm-stage {
  position: relative; width: 100%; aspect-ratio: 1 / 1;
  background: linear-gradient(180deg, var(--bg-2), var(--bg-3));
  border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--shadow-1);
  overflow: hidden;
}
.lm-grid {
  position: absolute; inset: 0;
  background-image:
    linear-gradient(to right, color-mix(in srgb, var(--line) 60%, transparent) 1px, transparent 1px),
    linear-gradient(to bottom, color-mix(in srgb, var(--line) 60%, transparent) 1px, transparent 1px);
  /* 3200 m divisions over 12800 = every 25%. */
  background-size: 25% 25%;
}
.lm-axis { position: absolute; display: flex; justify-content: space-between;
  font-size: var(--fs-xs); color: var(--text-3); font-variant-numeric: tabular-nums; }
.lm-axis-x { left: 0; right: 0; bottom: 2px; padding: 0 2px; }
.lm-axis-z { top: 2px; bottom: 2px; left: 2px; flex-direction: column; }
.lm-layers { position: absolute; inset: 0; }

.lm-marker { position: absolute; transform: translate(-50%, -50%);
  background: none; border: none; padding: 0; cursor: default; }
.lm-dot { display: block; width: 9px; height: 9px; border-radius: 50%;
  border: 2px solid var(--bg-1); box-shadow: 0 0 0 1px var(--text-3); background: var(--text-2); }
.lm-place .lm-dot { background: var(--accent); }
.lm-zone-approved .lm-dot { background: var(--tier-basic); }
.lm-zone-draft .lm-dot { background: var(--amber); }
.lm-label { position: absolute; left: 12px; top: -4px; white-space: nowrap;
  font-size: var(--fs-xs); color: var(--text-2); pointer-events: none;
  /* A fully-opaque chip so a label that lands over a neighbour's dot or text
     actually occludes it instead of the two smearing together (a translucent
     chip lets the lower text bleed through), plus a width cap with ellipsis so
     a long place name cannot run across half the map. The nearer marker sits on
     top via its own stacking, so the top label stays readable. */
  max-width: 8.5rem; overflow: hidden; text-overflow: ellipsis;
  padding: 0 .25rem; border-radius: var(--r-xs);
  background: var(--bg-1); }
.lm-marker:hover .lm-label, .lm-marker:focus-within .lm-label {
  z-index: 5; overflow: visible; max-width: none; }
/* A marker in the right third of the map hangs its label to the LEFT of the
   dot, so a player near the east edge does not have their name run off the
   screen — the exact spill this fixed on a 375px phone. It expands leftward
   into the map on hover, which always has room. */
.lm-label-left { left: auto; right: 12px; text-align: right; }
.lm-label small { display: block; color: var(--text-3); font-variant-numeric: tabular-nums; }

.lm-heat { position: absolute; transform: translate(-50%, -50%);
  background: color-mix(in srgb, var(--danger) 30%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 50%, transparent); border-radius: 3px; }

.lm-side { background: linear-gradient(180deg, var(--bg-2), var(--bg-3));
  border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--shadow-1); padding: 1rem 1.1rem; }
.lm-side-title { margin: 0 0 .6rem; font-size: var(--fs-md); }
.lm-facts div { display: flex; justify-content: space-between; gap: 1rem;
  padding: .25rem 0; border-bottom: 1px solid var(--line); font-size: var(--fs-sm); }
.lm-facts dt { color: var(--text-3); } .lm-facts dd { margin: 0; }
.lm-self { margin-top: 1rem; padding-top: .8rem; border-top: 1px solid var(--line); }
.lm-self h3, .lm-legend h3 { margin: 0 0 .3rem; font-size: var(--fs-sm); text-transform: uppercase;
  letter-spacing: .06em; color: var(--text-3); }
.lm-stale { font-size: var(--fs-xs); color: var(--amber); }
.lm-muted { color: var(--text-3); font-size: var(--fs-sm); line-height: 1.5; }
.lm-legend { margin-top: 1rem; padding-top: .8rem; border-top: 1px solid var(--line); }
.lm-loading { color: var(--text-3); padding: 2rem; text-align: center; }
.lm-offline { background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 1.6rem; text-align: center; }
.lm-warn { background: color-mix(in srgb, var(--amber) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--amber) 45%, transparent);
  border-radius: var(--r-sm); padding: .6rem .8rem; font-size: var(--fs-sm); margin: 0 0 .8rem; }

/* ======================================================================
   THE PLAYER SCREEN
   Finding somebody by name, and acting on them.
   ====================================================================== */

/* --- the name picker ---------------------------------------------------
   The dropdown is positioned against a RELATIVE shell rather than the card,
   so it hangs below the box wherever the box happens to sit — inside a
   modal, in a toolbar, or at the top of the page. */
.adm-picker { display: block; max-width: 34rem; }
.adm-picker-shell { position: relative; }
.adm-picker-input { width: 100%; }
.adm-picker-list {
  position: absolute; inset-inline: 0; top: calc(100% + .25rem);
  /* Above the drawer but below a modal: the picker can be opened from a
     panel, and a modal opened from the picker must still cover it. */
  z-index: var(--z-drawer);
  margin: 0; padding: .25rem; list-style: none;
  max-height: 17rem; overflow-y: auto;
  background: var(--bg-2); border: 1px solid var(--line-2);
  border-radius: var(--r-md); box-shadow: var(--shadow-2);
}
.adm-picker-row {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  padding: .45rem .55rem; border-radius: var(--r-sm); cursor: pointer;
}
.adm-picker-row:hover, .adm-picker-row.is-active { background: var(--bg-4); }
.adm-picker-name { font-weight: 600; }
.adm-picker-seen { font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap; }
.adm-picker-status { margin: .35rem 0 0; min-height: 1.1em; }

/* --- the actions on one player -----------------------------------------
   Three columns on a wide screen — what it is called, what it does, whether
   it can be undone — because the second and third are the ones that stop a
   mistake and a tooltip hides both. They stack on a phone. */
.adm-paction-group { display: flex; flex-direction: column; gap: .1rem; }
.adm-paction-row {
  display: grid; grid-template-columns: minmax(9rem, 12rem) 1fr minmax(0, 14rem);
  gap: .5rem 1rem; align-items: baseline;
  padding: .5rem 0; border-bottom: 1px solid var(--line);
}
.adm-paction-row:last-child { border-bottom: 0; }
.adm-paction { justify-self: start; width: 100%; }
.adm-paction-effect { font-size: var(--fs-sm); color: var(--text-2); }
.adm-paction-undo { font-size: var(--fs-xs); color: var(--text-3); }
.adm-profile-actions { margin-top: 1.2rem; }
.adm-section-heading {
  margin: 0 0 .5rem; font-size: var(--fs-sm); text-transform: uppercase;
  letter-spacing: .06em; color: var(--text-3);
}

/* --- what the player is told, inside the action form -------------------
   Set apart from the action's own inputs, because it is a different
   decision: what to do, and then who is told about it. */
.adm-modal-wide { width: min(42rem, 100%); }
.adm-modal-subtitle { margin: 0 0 .5rem; font-size: var(--fs-sm); }
.adm-prompt-block {
  margin-top: 1rem; padding: .8rem .9rem;
  background: var(--bg-3); border: 1px solid var(--line);
  border-left: 3px solid var(--accent); border-radius: var(--r-sm);
}
/* --- standing flag ------------------------------------------------------ */
.adm-profile-discord { display: block; font-size: var(--fs-sm); opacity: .7; }
.adm-flag-badge { margin-inline-start: .25rem; }
.adm-flag-set { margin-inline-start: .35rem; }
.adm-flag-option {
  display: flex; align-items: center; gap: .6rem; width: 100%;
  text-align: left; padding: .55rem .6rem; margin: .2rem 0;
  border: 1px solid var(--line); border-radius: .5rem;
  background: transparent; color: inherit; cursor: pointer;
}
.adm-flag-option:hover { border-color: var(--accent); }
.adm-flag-option.is-current { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); }
.adm-flag-option .field-help { margin: 0; }

/* --- analytics ---------------------------------------------------------- */
/* The unicode sparkline: monospace so every block is one column wide, and
   larger than body text because eight brightness levels at 13px is a smudge. */
.adm-sparkline {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 1.05rem; letter-spacing: .05em; margin: .35rem 0;
  white-space: pre; overflow-x: auto;
}
.adm-group-line { margin: .3rem 0; font-size: var(--fs-sm); }

/* --- bot settings groups (Discord screen) ------------------------------ */
/*
 * A group of related settings inside one card: a heading, one line of help,
 * then the controls. Bordered on the left rather than boxed, so three groups
 * in a card read as sections of one thing, not three more cards.
 */
.adm-subcard {
  border-left: 2px solid var(--line);
  padding: .25rem 0 .25rem .85rem;
  margin: .75rem 0;
}
.adm-subcard h3 { margin: 0 0 .15rem; font-size: var(--fs-sm); }
.adm-subcard > .field-help { margin-top: 0; }
.adm-setting-row { margin: .5rem 0; }
.adm-setting-label { margin: 0 0 .2rem; font-size: var(--fs-sm); }

.adm-checkline {
  display: flex; align-items: center; gap: .5rem;
  padding: .25rem 0; font-size: var(--fs-sm); cursor: pointer;
}
/* The raw checkbox INPUT inside a checkline — see the note on the checklist
   above for why this no longer squats on `.adm-check`. */
.adm-checkline input[type="checkbox"] { width: 1rem; height: 1rem; accent-color: var(--accent); }
.adm-modal-error {
  margin: .6rem 0 0; font-size: var(--fs-sm); color: var(--danger);
}

@media (max-width: 720px) {
  .adm-paction-row { grid-template-columns: 1fr; gap: .2rem; }
  .adm-paction { width: auto; }
}

/* --- the commands screen: search, shelves, and what a command is ------- */
.adm-console-side { display: flex; flex-direction: column; gap: .6rem; min-width: 0; }
.adm-console-search { width: 100%; }
/* Group headings pin to the top of the scrolling list, so an operator who
   is deep in "Vehicles" can still read which shelf they are on. The solid
   background stops the items showing through as they slide underneath. */
.adm-console-group {
  position: sticky; top: -0.35rem; z-index: 1;
  margin: .9rem 0 .35rem; padding: .35rem 0 .25rem;
  background: var(--bg-2);
  box-shadow: 0 .35rem .35rem -.35rem color-mix(in srgb, var(--bg-1) 65%, transparent);
}
.adm-console-group:first-child { margin-top: 0; }
.adm-console-group-name {
  margin: 0; font-size: var(--fs-xs); text-transform: uppercase;
  letter-spacing: .07em; color: var(--text-3);
}
.adm-console-group-blurb { margin: .15rem 0 0; font-size: var(--fs-xs); color: var(--text-3); }
.adm-console-name { font-weight: 600; }

/* What it is aimed at, and what to do if it was a mistake — above the
   fields, because both are decisions made before typing. */
.adm-cmdform-facts {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .2rem .8rem;
  margin: .7rem 0 1rem; padding: .6rem .75rem;
  background: var(--bg-3); border-radius: var(--r-sm);
  font-size: var(--fs-sm);
}
.adm-cmdform-facts dt { color: var(--text-3); white-space: nowrap; }
.adm-cmdform-facts dd { margin: 0; }
.adm-cmdform-permanent { color: var(--danger); font-weight: 600; }
.adm-cmd-label { font-weight: 600; display: block; }

/* A rescue: what it does, who it is for, and the button. */
.adm-rescue {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 22rem) auto;
  gap: .6rem 1rem; align-items: end;
  padding: .7rem 0; border-bottom: 1px solid var(--line);
}
.adm-rescue:last-of-type { border-bottom: 0; }
.adm-rescue .adm-picker { max-width: none; }

@media (max-width: 899.98px) {
  .adm-rescue { grid-template-columns: 1fr; align-items: stretch; }
  .adm-console-list { max-height: 22rem; }
}

/* --- your record, on the account page ---------------------------------- */
.panel-subtitle { margin: 1.2rem 0 .4rem; font-size: var(--fs-sm); }
.lb-list { margin: 0; padding-left: 1.4rem; display: grid; gap: .25rem; }
.lb-list li { display: flex; justify-content: space-between; gap: 1rem; }
.lb-name { min-width: 0; overflow-wrap: anywhere; }
.lb-kills { font-variant-numeric: tabular-nums; color: var(--text-3); }
/* You, in the list. A row you cannot find is a leaderboard that does not
   answer the question people open it to ask. */
.lb-you { font-weight: 700; }
.lb-you .lb-kills { color: var(--accent); }

/* --- several players, chosen one at a time ----------------------------- */
.adm-multipicker { display: grid; gap: .5rem; }
.adm-chips { display: flex; flex-wrap: wrap; gap: .35rem; min-height: 1.6rem; align-items: center; }
/* A REMOVABLE token, not a status chip. This used to be named .adm-chip,
   which collided with the freshness/risk chip of the same name defined
   earlier — and because this block came later, the freshness colours were
   silently dead everywhere. One component, one class. */
.adm-chip-token {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .2rem .3rem .2rem .55rem; border-radius: var(--r-pill);
  background: var(--bg-4); border: 1px solid var(--line-2); font-size: var(--fs-sm);
}
.adm-chip-x {
  border: 0; background: transparent; color: var(--text-3); cursor: pointer;
  font-size: 1rem; line-height: 1; padding: 0 .25rem; border-radius: var(--r-pill);
}
/* A saved-view chip: the view button plus its delete control. */
.adm-viewchip { display: inline-flex; align-items: center; gap: .1rem; }

/* --- the live-players bulk bar ----------------------------------------- */
.adm-bulkbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: .45rem;
  padding: .45rem .6rem; margin-bottom: .6rem;
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line));
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--accent) 7%, var(--bg-2));
}
.adm-bulkbar-empty { border-style: dashed; border-color: var(--line); background: transparent; }
.adm-bulkcount { font-size: var(--fs-sm); margin-inline-end: .3rem; }
/* Gather: a labelled cluster of anchor buttons, set off from the row actions
   with its own hairline so "bring the others onto X" reads as one control
   rather than more loose buttons. Wraps on a phone instead of overflowing. */
.adm-bulk-gather {
  display: flex; flex-wrap: wrap; align-items: center; gap: .35rem;
  padding-inline-start: .55rem; margin-inline-start: .2rem;
  border-inline-start: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line));
}
.adm-bulk-gather-label { font-size: var(--fs-xs); color: var(--text-3); }

/* Economy tuning editor: a responsive grid of labelled number fields. Each
   row keeps its label and input together and the grid reflows to one column
   on a phone, so no field ever runs off the screen. */
.adm-tune-grid {
  display: grid; gap: .5rem .9rem;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}
.adm-tune-row { display: flex; flex-direction: column; gap: .2rem; min-width: 0; }
.adm-tune-key { font-size: var(--fs-xs); color: var(--text-2); overflow-wrap: anywhere; }
.adm-tune-input { width: 100%; }
.adm-tune-actions { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-bottom: .6rem; }
/* Match the resting height of the full .btn beside it so the row isn't a stubby
   field floating next to a taller button. Scoped here (not on base .adm-input)
   so the .adm-inline-form rows that pair with 36px btn-sm keep their fit. */
.adm-tune-actions .adm-input { flex: 1 1 16rem; min-width: 0; min-height: var(--tap); }
.adm-tune-outcome:empty { display: none; }

/* --- the money screen on a player's profile ---------------------------- */
/* Built phone-first. Nothing here goes past three columns before it stacks,
   and every direction cue is a glyph or a word as well as a colour: this
   panel is read in daylight on a phone by people who may not see the red. */
.adm-money-window { display: flex; flex-wrap: wrap; gap: .4rem; margin: .5rem 0 .8rem; }
.adm-money-window .btn[aria-pressed="true"] {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--text-1);
}
.adm-money-amt { display: inline-flex; align-items: baseline; gap: .28rem; font-variant-numeric: tabular-nums; }
.adm-money-glyph { font-size: .7em; line-height: 1; }
.adm-money-fig { font-weight: 700; }
.adm-money-word { font-size: var(--fs-xs); color: var(--text-3); text-transform: uppercase; letter-spacing: .06em; }
/* Blended TOWARDS the theme's own text colour rather than used raw: --forest
   is a background tint elsewhere on this panel and is too dark to read as
   text on the dark theme. The mix keeps the green/red reading in both themes
   while the glyph and the sign carry the meaning on their own regardless. */
.adm-money-in { color: color-mix(in srgb, var(--forest) 55%, var(--text)); }
.adm-money-out { color: color-mix(in srgb, var(--danger) 55%, var(--text)); }
.adm-money-flat { color: var(--text-3); }

.adm-money-headline {
  display: grid; gap: .5rem; margin: .6rem 0 .4rem;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
}
.adm-money-head-cell {
  display: flex; flex-direction: column; gap: .15rem; min-width: 0;
  padding: .5rem .65rem; border: 1px solid var(--line); border-radius: var(--r-sm, 8px);
  background: var(--bg-3);
}
.adm-money-head-key { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .08em; color: var(--text-3); }

.adm-money-groups { display: flex; flex-direction: column; gap: .4rem; margin: .5rem 0; }
.adm-money-group { padding: .5rem .65rem; border: 1px solid var(--line); border-radius: var(--r-sm, 8px); }
.adm-money-group-top { display: flex; flex-wrap: wrap; gap: .4rem .8rem; align-items: baseline; justify-content: space-between; }
.adm-money-group-label { font-weight: 650; overflow-wrap: anywhere; }
.adm-money-group-detail { margin: .2rem 0 0; font-size: var(--fs-xs); color: var(--text-3); overflow-wrap: anywhere; }
.adm-money-more, .adm-money-pager { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin: .5rem 0; }
.adm-money-rounds-sum { margin: .2rem 0; font-size: var(--fs-sm); }
.adm-money-bigwin { margin: .2rem 0 .4rem; font-size: var(--fs-sm); font-weight: 650; }
.adm-money-sum { margin: .1rem 0 .6rem; }
.adm-money-correct .adm-tune-actions { margin-bottom: .2rem; }
/*
 * The "this is already waiting on an answer" warning, and the one checkbox
 * that overrides it. Empty on every ordinary correction, so it carries no
 * margin of its own — an invisible gap above the movements list would read as
 * a layout bug on the ninety-nine presses that have nothing pending.
 */
.adm-money-pending:not(:empty) { margin: .5rem 0 .2rem; display: flex; flex-direction: column; gap: .4rem; }
.adm-tune-problems { margin: .3rem 0 0; padding-inline-start: 1.1rem; color: var(--danger); font-size: var(--fs-sm); }
.adm-tune-problems li { overflow-wrap: anywhere; }
.adm-selectcell { width: 2rem; }
.adm-selectcell input[type="checkbox"] {
  width: 1.05rem; height: 1.05rem; accent-color: var(--accent);
  /* A selection tap target is the ROW's job on phones; on desktop the box
     itself is comfortably clickable at this size. */
}
.adm-chip-x:hover { color: var(--danger); background: var(--bg-2); }

/* --- one base, with the decision it needs ------------------------------ */
.adm-basecard {
  padding: .85rem 0; border-bottom: 1px solid var(--line);
  display: grid; gap: .6rem; min-width: 0;
}
.adm-basecard:last-child { border-bottom: 0; }
.adm-basecard-head { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.adm-basecard-name { margin: 0; font-size: var(--fs-md); }
.adm-basecard-head .btn { margin-left: auto; }

@media (max-width: 599.98px) {
  .adm-basecard-head .btn { margin-left: 0; }
}

/* --- who is selected, above the half-built command --------------------- */
.adm-drawer-selected { border-left: 3px solid var(--accent); }
.adm-drawer-who { font-weight: 700; }

/* --- kits a member can claim, on their account ------------------------- */
.kit-access-list { margin: 0; padding: 0; list-style: none; display: grid; gap: .3rem; }
.kit-access-list li { display: flex; justify-content: space-between; gap: 1rem;
  padding: .3rem 0; border-bottom: 1px solid var(--line); }
.kit-access-list li:last-child { border-bottom: 0; }
.kit-access-name { min-width: 0; overflow-wrap: anywhere; }
.kit-access-state { color: var(--text-3); font-size: var(--fs-sm); white-space: nowrap; }
.kit-access-gone .kit-access-name { text-decoration: line-through; color: var(--text-3); }

/* Position cell: the place name leads, the raw coordinates are the subtitle.
   A number pair is only useful with a map open; a town name is useful now. */
.adm-pos-place { display: block; }
.adm-pos-coords {
  display: block;
  font-size: var(--fs-xs);
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

/* ===== Goal 106: unified history, Call Admin, settings text ============ */

/* --- the history kind chips ------------------------------------------- */
.adm-hist-chips { display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: .6rem; }
.adm-hist-chip {
  border: 1px solid var(--line); background: transparent; color: var(--text-2);
  border-radius: 999px; padding: .18rem .65rem; font-size: var(--fs-sm);
  cursor: pointer; transition: border-color .12s ease, color .12s ease;
}
.adm-hist-chip:hover { border-color: var(--accent); color: var(--text-1); }
.adm-hist-chip.is-on {
  border-color: var(--accent); color: var(--accent); font-weight: 600;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

/* --- Call Admin queue cards ------------------------------------------- */
.adm-help-queue { display: grid; gap: .7rem; }
.adm-help-card {
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: var(--radius, 8px); padding: .7rem .8rem; display: grid; gap: .45rem;
}
.adm-help-card[data-status="claimed"] { border-left-color: var(--warn, #d7a44a); }
.adm-help-head { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; }
.adm-help-when { margin-left: auto; color: var(--text-3); font-size: var(--fs-xs); white-space: nowrap; }
.adm-help-reason { margin: 0; font-style: italic; color: var(--text-1); overflow-wrap: anywhere; }

/* --- the multi-line settings value (welcome text) ---------------------- */
.adm-setting-text { width: 100%; resize: vertical; min-height: 4.2em; margin-bottom: .4rem; }

@media (max-width: 599.98px) {
  .adm-help-when { margin-left: 0; width: 100%; }
}

/* --- the member-facing Call Admin form --------------------------------- */
.calladmin-form { display: grid; gap: .55rem; max-width: 34rem; }
@media (min-width: 700px) {
  .calladmin-form { grid-template-columns: minmax(10rem, .6fr) 1fr auto; align-items: start; }
  .calladmin-form .field-note { grid-column: 1 / -1; }
}
