/* Primary UI typeface — Rajdhani, a geometric/technical sans that reads as
   more "futuristic HUD" than Exo 2 while staying highly legible at small
   sizes. Share Tech Mono stays a SEPARATE, deliberate choice for fixed-width
   numeric readouts (combat damage numbers, countdown timers, admin
   migration filenames, etc.) — it is NOT part of this swap. */
@import url('https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;500;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap');

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

:root {
  --bg:          #090c07;
  --bg-sidebar:  #0c0f08;
  --bg-card:     #0e1209;
  --bg-card2:    #121709;
  --border:      #1c2512;
  --border-hi:   #2e3e18;
  --accent:      #8da020;
  --accent-hi:   #aac424;
  --gold:        #c9a42c;
  --gold-hi:     #e8c030;
  --purple:      #6530b8;
  --purple-hi:   #9060e0;
  --text:        #c8d2be;
  --text-muted:  #8e9e7e;
  --text-bright: #edf3e6;
  --green-ok:    #40c020;
  --sidebar-w:   218px;
  --header-h:    52px;
  --bg-input:    #0b0e08;
  --red:         #c03020;
  --red-bg:      #c0302015;
}

html, body {
  min-height: 100%;
  font-family: 'Rajdhani', sans-serif;
  font-size: 15px;
  font-weight: 400;
  background: var(--bg);
  color: var(--text);
  /* Light text on this UI's near-black background renders heavier/blurrier
     under default subpixel anti-aliasing — pronounced with Rajdhani's tight,
     tall letterforms at small label sizes, where it reads as smudged-together
     characters. Force grayscale AA so strokes stay crisp instead of fuzzy. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* Form controls don't inherit font-family from body by default in most
   browsers — set it explicitly so buttons/inputs/selects/textareas actually
   render in Rajdhani instead of the OS UI font. */
body, button, input, select, textarea {
  font-family: 'Rajdhani', sans-serif;
}

/* Base link styling — themed accent instead of the browser default blue/purple (and no
 * purple "visited" state). Wrapped in :where() so the ENTIRE rule contributes ZERO
 * specificity (0,0,0): it still colors every bare <a>, but ANY scoped style overrides it
 * automatically — base AND :hover. This is the genuinely "low specificity" default the
 * comments below assume. Previously the literal `a:link, a:visited` selectors were (0,1,1)
 * and silently beat single-class button rules (0,1,0) — e.g. a `.travel-btn-go` link got
 * accent-green text on its accent-green background and the label vanished. (Author origin
 * still beats the UA :visited purple regardless of specificity, so the visited state is
 * covered too.) Any button-styled <a> with a class that sets `color` now wins on its own. */
:where(a, a:link, a:visited) { color: var(--accent); text-decoration: none; }
:where(a:hover) { color: var(--accent-hi); }

/* Inline player-name links used across pages */
a.profile-link {
  color: inherit; text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color .13s, border-color .13s;
}
a.profile-link:hover { color: var(--accent-hi); border-bottom-color: var(--accent-hi); }

/* ─── Keyboard focus ───────────────────────────────────────────────────────
 * One global focus ring for keyboard users. The file had ZERO :focus-visible
 * rules while stripping outlines in two places, so tabbing through the app gave
 * either no visible focus at all or a ring at 12.5% alpha that is invisible on
 * the near-black --bg. That matters more now that the combat controls are real
 * <button>s and actually reachable by Tab.
 *
 * :focus-visible (not :focus) means mouse clicks never show the ring — only
 * keyboard/AT navigation does, so this costs nothing visually for mouse players.
 * --accent-hi is redefined per theme further down, so the ring follows the
 * player's chosen theme automatically. */
:focus-visible {
  outline: 2px solid var(--accent-hi);
  outline-offset: 2px;
  border-radius: 2px;
}

/* ─── App shell ─── */
/* 100dvh tracks the *visible* viewport so the layout doesn't sit behind the
   mobile address bar (100vh is the larger, chrome-inclusive height). */
.app { display: flex; height: 100vh; height: 100dvh; overflow: hidden; }

/* ─────────────────────────────────────
   SIDEBAR
───────────────────────────────────── */
.sidebar {
  width: var(--sidebar-w); flex-shrink: 0;
  background: var(--bg-sidebar); border-right: 1px solid var(--border);
  display: flex; flex-direction: column; overflow-y: auto; overflow-x: hidden;
}
.sidebar::-webkit-scrollbar { width: 3px; }
.sidebar::-webkit-scrollbar-thumb { background: var(--border-hi); border-radius: 2px; }

.sidebar-brand { display: block; padding: 1.4rem 1.2rem 1.1rem; border-bottom: 1px solid var(--border); user-select: none; text-decoration: none; }

.brand-top { display: flex; align-items: center; gap: 0.5rem; justify-content: center;}
.brand-void { font-size: 2.1rem; font-weight: 700; letter-spacing: 0.18em; color: var(--text-bright); line-height: 1; text-shadow: 0 0 24px #8da02033; }
.brand-icon { display: flex; align-items: center; opacity: 0.6; }
.brand-divider { display: flex; align-items: center; gap: 0.4rem; margin: 0.35rem 0; }
.brand-line { flex: 1; height: 1px; background: linear-gradient(90deg, transparent, var(--accent), transparent); }
.brand-diamond { font-size: 0.5rem; color: var(--accent); }
.brand-frontier { font-size: 0.7rem; letter-spacing: 0.38em; color: var(--accent); text-transform: uppercase; font-weight: 600; text-align: center;}

/* ── Sidebar wallet ──────────────────────────────────────────────────────────
   Currencies relocated out of the top header (see includes/header.php). A compact
   icon + value + unit stack that fills otherwise-empty sidebar space. The value
   spans carry .coins/.shards/.premium so the Daily-Bonus claim handler updates
   them live (same classes the old header tiles used). */
.sidebar-wallet {
  display: flex; flex-direction: column; gap: 0.12rem;
  padding: 0.55rem 0.7rem; margin: 0; border-bottom: 1px solid var(--border);
}
.swallet-row {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.28rem 0.5rem; border-radius: 4px;
  text-decoration: none; color: var(--text-bright);
  border: 1px solid transparent; transition: background 0.15s ease, border-color 0.15s ease;
}
/* Only the linked row (Premium -> shop) reacts to hover; the plain rows are static. */
a.swallet-row:hover { background: #ffffff05; border-color: var(--border-hi); }
.swallet-ico { width: 16px; text-align: center; font-size: 0.82rem; flex-shrink: 0; filter: drop-shadow(0 0 5px currentColor); }
.swallet-val {
  flex: 1; font-family: 'Share Tech Mono', monospace; font-weight: 700;
  font-size: 0.93rem; letter-spacing: 0.02em; color: var(--text-bright);
  text-align: right; line-height: 1;
  font-variant-numeric: tabular-nums;
}
/* Brief glow when the value changes (toggled by the daily-bonus handler reuse). */
.swallet-val { transition: color 0.25s ease, text-shadow 0.25s ease; }
.swallet-unit {
  width: 30px; flex-shrink: 0; font-size: 0.62rem; font-weight: 700;
  letter-spacing: 0.08em; color: var(--text-muted); text-transform: uppercase;
}

/* Nav */
.sidebar-nav { flex: 1; padding: 0.4rem 0; }
.nav-section  { margin-top: 0.25rem; }
/* The global link color is :where()-wrapped (zero specificity, top of this file), so a
   bare `.nav-item` (0,1,0) already outranks it for the neutral default — no need to spell
   out :link/:visited the way this rule used to. */
.nav-item {
  display: flex; align-items: center; gap: 0.7rem; padding: 0.58rem 1.2rem;
  /* Three tiers of emphasis: FADED by default (--text-muted) so the menu sits
     quietly, brightening toward white on hover, and only the ACTIVE page wears the
     theme accent (green/amber) — so the screen you're on is the one thing that pops. */
  color: var(--text-muted); text-decoration: none; font-size: 1rem; font-weight: 600;
  /* Letter-spacing trimmed from 0.08em when the font-size grew from 0.82rem
     to 1rem (15px, per the Rajdhani sizing pass) so uppercase labels like
     "Communication" keep fitting the 218px sidebar on one line. */
  letter-spacing: 0.03em; text-transform: uppercase; border-left: 2px solid transparent;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: color 0.13s, background 0.13s, border-color 0.13s;
}
.nav-item:hover  { color: var(--text-bright); background: #ffffff04; border-left-color: var(--border-hi); }
.nav-item.active { color: var(--accent-hi); background: #8da02015; border-left-color: var(--accent); }
.nav-item svg { flex-shrink: 0; }
.nav-fa-icon { width: 16px; text-align: center; flex-shrink: 0; font-size: 0.8rem; }

/* Sidebar nav groups (expandable submenus) */
.nav-item-end { display: flex; align-items: center; gap: 0.5rem; margin-left: auto; }
.nav-group-toggle { font-size: 0.65rem; color: var(--text-muted); transition: transform 0.2s ease; flex-shrink: 0; }
.nav-group.open .nav-group-toggle { transform: rotate(180deg); }

/* Blinking attention dot — shown on Communication when there's an unread
   mailbox message or a pending PvP challenge (see includes/header.php). */
.nav-attn-dot {
  width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
  background: #ff4040; box-shadow: 0 0 6px 1px #ff404088;
  animation: nav-attn-blink 1.1s ease-in-out infinite;
}
@keyframes nav-attn-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
.nav-submenu {
  display: flex; flex-direction: column; max-height: 0; overflow: hidden;
  transition: max-height 0.22s ease; background: #00000020;
}
.nav-group.open .nav-submenu { max-height: 260px; }
/* The Admin Panel carries far more entries (~20+) than any game menu, so the 260px
   cap clips most tools out of reach. Give it a much taller open state and let it
   scroll inside the sidebar instead of overflowing off-screen. A thin styled
   scrollbar keeps it from looking like an accidental clip. */
.nav-group-admin_tools.open .nav-submenu {
  max-height: min(70vh, 620px); overflow-y: auto;
  scrollbar-width: thin; scrollbar-color: var(--border-hi) transparent;
}
.nav-group-admin_tools.open .nav-submenu::-webkit-scrollbar { width: 7px; }
.nav-group-admin_tools.open .nav-submenu::-webkit-scrollbar-thumb {
  background: var(--border-hi); border-radius: 4px;
}
/* Bare class outranks the :where()-wrapped global link color — see the note on .nav-item. */
.nav-sub-item {
  display: flex; align-items: center; gap: 0.6rem; padding: 0.46rem 1.2rem 0.46rem 2.3rem;
  /* Submenu rows are secondary, so they sit a touch dimmer than top-level items
     (var(--text-muted)) but still aren't accent-tinted until active/hover. */
  color: var(--text-muted); text-decoration: none; font-size: 0.93rem; font-weight: 600;
  /* Letter-spacing trimmed from 0.05em alongside the size bump (0.74rem ->
     0.93rem, ~14px) so long entries like "Underground Casino" still ellipsis
     gracefully instead of pushing the row wider. */
  letter-spacing: 0.03em; text-transform: uppercase; border-left: 2px solid transparent;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: color 0.13s, background 0.13s, border-color 0.13s;
}
.nav-sub-item .nav-fa-icon { font-size: 0.7rem; opacity: 0.75; }
/* Transient "hacked" look for the player's own callsign while it Matrix-decodes
   into a birthday greeting (see the time-limited surprise script in header.php,
   shown only to Justin's accounts). Terminal-green mono, like the homepage
   hero/down-page hacks. Purely cosmetic + time-limited. */
.player-name.bday-hack {
  color: #34ff8f; text-shadow: 0 0 5px rgba(0, 255, 123, 0.35);
  font-family: 'Share Tech Mono', monospace;
}
.nav-sub-item:hover  { color: var(--text-bright); background: #ffffff04; border-left-color: var(--border-hi); }
.nav-sub-item.active { color: var(--accent-hi); background: #8da02015; border-left-color: var(--accent); }

/* Sidebar global search — quick-jump box for finding a player or alliance
   (clan) by name (see includes/header.php + api/search_global.php). The
   "SEARCH" banner artwork is a purely decorative label sitting above a
   plain text input, so typed text stays readable instead of overlapping
   the artwork; the dropdown of matches opens beneath the input. */
.sidebar-search { position: relative; padding: 0 1.2rem 0.6rem; margin-top: 0.2rem; }
.sidebar-search-banner { display: block; width: 100%; border-radius: 4px; margin-bottom: 0.5rem; }
.sidebar-search-input {
  display: block; width: 100%; padding: 0.5rem 0.7rem;
  background: #00000040; border: 1px solid var(--border-hi); border-radius: 4px;
  color: var(--text-bright); font-family: 'Rajdhani', sans-serif; font-size: 0.74rem;
  font-weight: 500; letter-spacing: 0.02em;
}
.sidebar-search-input::placeholder { color: var(--text-muted); }
/* Same specificity caveat as .form-input:focus below — (0,2,0) beats the global
 * :focus-visible rule, so this needs its own indicator. A border-colour change
 * alone was too subtle to read as focus, so it gets the matching accent ring. */
.sidebar-search-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-hi); }
.sidebar-search-results {
  position: absolute; left: 1.2rem; right: 1.2rem; top: 100%; margin-top: 0.3rem;
  background: rgba(10,14,8,0.97); border: 1px solid var(--border-hi); border-radius: 6px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.6); max-height: 260px; overflow-y: auto;
  display: none; z-index: 210;
}
.sidebar-search-results.open { display: block; }
.sidebar-search-result {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  padding: 0.5rem 0.7rem; color: var(--text); text-decoration: none; font-size: 0.78rem;
  border-bottom: 1px solid var(--border);
}
.sidebar-search-result:last-child { border-bottom: none; }
.sidebar-search-result:hover { background: #ffffff08; color: var(--accent-hi); }
.sidebar-search-result-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-search-result-tag {
  flex-shrink: 0; font-size: 0.6rem; letter-spacing: 0.08em; text-transform: uppercase;
  padding: 0.15rem 0.4rem; border-radius: 3px; font-weight: 700;
}
.sidebar-search-result-tag.player { background: #8da02022; color: var(--accent-hi); }
.sidebar-search-result-tag.clan   { background: #d0484822; color: #e07a7a; }
.sidebar-search-empty { padding: 0.6rem 0.7rem; font-size: 0.74rem; color: var(--text-muted); }

/* Sidebar status — same small-uppercase legibility pass as the header stats:
 * these were 0.62-0.68rem at weight 400 (~9-10px against the 15px root), which is
 * below where condensed Rajdhani holds up in muted grey. */
.sidebar-status { padding: 0.9rem 1.2rem; border-top: 1px solid var(--border); font-size: 0.72rem; font-weight: 500; letter-spacing: 0.09em; text-transform: uppercase; color: var(--text-muted); }
.status-row    { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.55rem; }
.status-online { color: var(--green-ok); font-weight: 700; text-decoration: none; cursor: pointer; }
.status-online:hover { text-decoration: underline; }
.status-label { font-size: 0.7rem; font-weight: 600; letter-spacing: 0.08em; color: var(--text-muted); margin-top: 0.1rem; }
.status-time  { font-family: 'Share Tech Mono', monospace; font-size: 1.1rem; color: var(--text); line-height: 1; }
.status-date  { font-size: 0.72rem; font-weight: 500; color: var(--text-muted); margin-top: 0.2rem; }

/* ─────────────────────────────────────
   MAIN WRAPPER
───────────────────────────────────── */
.main-wrapper { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }

/* ─────────────────────────────────────
   TOP HEADER
───────────────────────────────────── */
.top-header {
  height: var(--header-h); background: var(--bg-sidebar); border-bottom: 1px solid var(--border);
  display: flex; align-items: center; padding: 0 1.25rem; gap: 0.5rem; flex-shrink: 0;
}

.menu-btn {
  background: none; border: none; color: var(--text-muted); cursor: pointer; padding: 0.3rem;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; width: 34px; height: 34px; flex-shrink: 0;
}
.menu-btn:hover { color: var(--text); }

.ham-bar {
  display: block; width: 18px; height: 1.5px; background: currentColor; border-radius: 1px;
  transform-origin: center; pointer-events: none;
  transition: transform 0.24s ease, opacity 0.2s ease, width 0.2s ease;
}
.menu-btn.active .ham-bar:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
.menu-btn.active .ham-bar:nth-child(2) { opacity: 0; width: 0; }
.menu-btn.active .ham-bar:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }

.header-stats { display: flex; align-items: center; flex: 1; gap: 0; }
.hstat { display: flex; align-items: center; gap: 0.5rem; padding: 0 1rem; border-right: 1px solid var(--border); }
.hstat:first-child { padding-left: 0; }
.hstat-icon  { font-size: 1rem; flex-shrink: 0; line-height: 1; }
.hstat-text  { display: flex; flex-direction: column; gap: 1px; }
/* Muted uppercase micro-label — same legibility pass as .hstat-sub below: weight
 * 500 -> 600 so the condensed strokes survive at this size, and line-height off 1
 * so the caps aren't clipped tight against the value underneath. */
.hstat-label { font-size: 0.74rem; font-weight: 600; letter-spacing: 0.08em; color: var(--text-muted); text-transform: uppercase; line-height: 1.15; }
.hstat-value { font-size: 0.95rem; font-weight: 500; color: var(--text-bright); line-height: 1; letter-spacing: 0.02em; }
.hstat-value.coins   { color: var(--accent-hi); }
.hstat-value.shards  { color: var(--purple-hi); }
.hstat-value.premium { color: var(--gold); }
.hstat-value.avail   { color: var(--accent-hi); }
.hstat-value.active  { color: var(--gold); }

/* ── Rank tile (leftmost header stat: "level / power" + insignia) ──────────────
 * Relative so the insignia can pin to the tile's top-right corner, and padded a
 * little on the right so the badge never collides with the "/ power" number.
 * The tile is compact by design (no bar, short numbers) so it costs the row far
 * less width than the Health/Stamina tiles beside it. */
.hstat-rank { position: relative; outline: none; }
/* The "/" between Skill Rank and Power Level — dimmer and tighter than the two
 * numbers so "5 / 20" reads as one value, not two stats jammed together. */
.hstat-rank .hstat-value  { display: flex; align-items: baseline; gap: 0.18rem; }
.hstat-rank .hstat-sep    { color: var(--text-muted); font-weight: 400; }
.hstat-rank .js-power     { color: var(--accent-hi); }

/* Insignia badge. Every shape is a clip-path on the empty .rank-pip spans, so
 * PHP (military_rank_insignia_html) and JS (main.js rank refresh) only ever have
 * to emit the right COUNT of pips and set data-shape — no SVG, no images.
 *   chevron -> stacked bottom-up (like sleeve stripes)
 *   bar / diamond / star -> laid out side by side
 * Colour comes from data-tier via currentColor, so all pips of a rank match. */
.rank-insignia {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 2px; line-height: 0; color: var(--text-muted);
}
/* The insignia stands IN for this tile's icon — it occupies the same slot the
 * fa-heart-pulse / fa-bolt glyphs do on the neighbouring tiles, left of the
 * numbers. The dark plate + hairline border makes it read as a worn BADGE rather
 * than as decoration on the "LEVEL / POWER" label beside it, and the fixed
 * min-width/height keeps the plate from resizing as pip shapes change between
 * ranks (which would jitter the whole row's alignment on promotion). */
.rank-insignia-icon {
  flex: none;
  min-width: 26px; min-height: 22px; padding: 2px 3px;
  background: rgba(0,0,0,0.32); border: 1px solid var(--border);
  border-radius: 3px;
}
.rank-insignia[data-shape="chevron"] { flex-direction: column-reverse; gap: 1px; }
.rank-pip { display: block; background: currentColor; flex: none; }

/* Chevron: a "^" cut out of a small block, stacked like sleeve stripes. */
.rank-insignia[data-shape="chevron"] .rank-pip {
  width: 14px; height: 4px;
  clip-path: polygon(50% 0, 100% 100%, 78% 100%, 50% 40%, 22% 100%, 0 100%);
}
/* Bar: a plain vertical stripe (junior-officer style, worn side by side). */
.rank-insignia[data-shape="bar"] .rank-pip { width: 3px; height: 13px; border-radius: 1px; }
/* Diamond: rotated square, field-officer style. */
.rank-insignia[data-shape="diamond"] .rank-pip {
  width: 12px; height: 12px;
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
}
/* Star: five-point, flag-officer style. */
.rank-insignia[data-shape="star"] .rank-pip {
  width: 13px; height: 13px;
  clip-path: polygon(50% 0, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
}

/* Keyboard focus: the tile is tabbable (it owns the rank tooltip), so give it a
 * visible ring — .hstat-rank kills the default outline to avoid a boxy focus rect
 * on mouse click, and this puts a deliberate one back for keyboard users. */
.hstat-rank:focus-visible { box-shadow: inset 0 0 0 1px var(--border-hi); border-radius: 3px; }

/* Tier colours — a visible promotion cue as you climb: dull steel while
 * enlisted, warming through amber/gold, ending on void purple at flag rank. */
.rank-insignia[data-tier="enlisted"] { color: #9aa88c; }
.rank-insignia[data-tier="nco"]      { color: var(--accent-hi); }
.rank-insignia[data-tier="officer"]  { color: var(--gold); filter: drop-shadow(0 0 3px rgba(212,175,55,0.35)); }
.rank-insignia[data-tier="command"]  { color: var(--purple-hi); filter: drop-shadow(0 0 4px rgba(144,96,224,0.45)); }

/* Rank tooltip reuses the .hstat-tooltip panel below verbatim — including its
 * position: fixed. That is NOT decorative: `.header-stats` is overflow:hidden on
 * desktop (see the ≥992px block) so an absolutely-positioned panel hanging below
 * the row would be clipped away. Fixed positioning escapes the clip, and main.js
 * sets the top/left from the tile's bounding rect on hover/focus. */

/* Region tile is a link into the Zone Map. Inherit the plain-stat look (no link
 * underline/colour) but give a subtle hover cue + the "Travel to zone" sub-line. */
.hstat-link { text-decoration: none; color: inherit; cursor: pointer; transition: background 0.15s ease; }
.hstat-link:hover { background: rgba(255,255,255,0.04); }

/* ── Action tile (a stat tile that is a <button>) ──
   Used by the Unspent XP badge: XP the player has earned from a bounty, mission
   or quest but not yet distributed across their attributes. Clicking it opens the
   allocator dialog (assets/js/reward-box.js binds .js-xp-pending globally), so it
   is a real button rather than a link — it does not navigate anywhere.
   Resets the browser's button chrome so it lays out identically to the .hstat
   divs beside it, then pulses so a fresh reward is noticed without being loud. */
.hstat-action {
  appearance: none; font: inherit; text-align: left; color: inherit; cursor: pointer;
  background: none; border: 0; border-right: 1px solid var(--border);
  transition: background 0.15s ease;
}
.hstat-action:hover { background: rgba(46,207,207,0.07); }
.hstat-action:focus-visible { outline: 2px solid var(--accent-hi); outline-offset: -2px; }
.hstat-action .hstat-value { color: #2ecfcf; font-family: 'Share Tech Mono', monospace; font-weight: 700; }
.hstat-action .hstat-icon  { animation: hstat-xp-pulse 2.6s ease-in-out infinite; }
@keyframes hstat-xp-pulse {
  0%, 100% { text-shadow: none; }
  50%      { text-shadow: 0 0 10px #2ecfcf; }
}
/* Nothing outstanding — the tile leaves the layout entirely rather than sitting
   there reading zero. !important is required: the ≤991 dropdown sets
   `#header-stats .hstat { display: flex !important }` to force every tile visible
   in the collapsed panel, and an empty badge must stay hidden even there. */
.hstat-action.is-empty { display: none !important; }
@media (prefers-reduced-motion: reduce) {
  .hstat-action .hstat-icon { animation: none; }
}
.hstat-link:hover .hstat-value.js-region { text-decoration: underline; }
/* Legibility pass: this was 0.58rem/400 which, against the 15px root, rendered at
 * ~8.7px. Rajdhani is condensed with thin strokes, so uppercase at that size turned
 * to mush — contrast was never the issue (accent-hi on the sidebar is ~9:1), stroke
 * weight and x-height were. Bumped to ~10px at 600, with more tracking (condensed
 * uppercase needs air between letters to stay separable) and line-height off 1. */
.hstat-sub {
  font-size: 0.68rem; font-weight: 600; letter-spacing: 0.09em;
  color: var(--accent-hi); text-transform: uppercase; line-height: 1.2; margin-top: 2px;
}

/* Daily Bonus stat is a <button> (clickable when available) but needs to look
 * identical to the plain .hstat divs around it — strip the native button chrome. */
.hstat-daily-bonus {
  background: none; border: 0; border-right: 1px solid var(--border);
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.hstat-daily-bonus:disabled { cursor: default; }
.hstat-daily-bonus:not(:disabled):hover .hstat-value.avail { text-decoration: underline; }

/* Hover/focus tooltip on the Daily Bonus stat — explains what the reward was
 * (already claimed) or will be (still available) without needing to click it.
 * JS (header.php) re-parents this onto <body> and toggles .visible, rather than
 * a CSS :hover rule — `.header-stats` has overflow:hidden (so a long stats row
 * can't push the player menu off-screen) which would otherwise clip it, since
 * overflow clipping applies to descendants regardless of position scheme. */
.hstat-tooltip {
  position: fixed; width: 230px; padding: 0.65rem 0.8rem; border-radius: 8px;
  background: rgba(10,14,8,0.97); border: 1px solid var(--border-hi);
  box-shadow: 0 8px 24px rgba(0,0,0,0.6);
  font-size: 0.72rem; line-height: 1.45; color: var(--text-muted);
  font-weight: 400; text-transform: none; letter-spacing: normal; white-space: normal;
  opacity: 0; pointer-events: none; transition: opacity 0.12s ease;
  z-index: 9000;
}
.hstat-tooltip.visible { opacity: 1; }
/* display:block so the title still owns its own line when the tooltip body is
 * built from inline <span>s (the rank tile) rather than block elements. */
.hstat-tooltip-title {
  display: block;
  font-size: 0.78rem; font-weight: 700; color: var(--gold);
  letter-spacing: 0.02em; margin-bottom: 0.3rem;
}
.hstat-tooltip .reward-line { display: block; margin-top: 0.3rem; color: var(--accent-hi); font-weight: 600; }
.hstat-tooltip strong { color: var(--text-bright); }

/* ── Stats wrapper / collapse toggle ──
   On desktop the wrapper is display:contents so .header-stats lays out exactly
   as the inline flex:1 row it always was, and the toggle pill is hidden. Both
   flip at the tablet breakpoint (<=991px) — see the media query below. */
.header-stats-wrap { display: contents; }
.stats-toggle {
  display: none; align-items: center; gap: 0.4rem;
  background: var(--bg-card2); border: 1px solid var(--border-hi); border-radius: 3px;
  color: var(--text); font-family: inherit; font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; padding: 0.42rem 0.7rem; cursor: pointer;
  transition: border-color 0.13s, color 0.13s;
}
.stats-toggle > i { color: var(--accent-hi); }
.stats-toggle:hover { border-color: var(--accent-hi); color: var(--text-bright); }

.xp-wrap      { display: flex; flex-direction: column; gap: 3px; }
/* Header vitals bars mirror the in-combat player-card HUD bars
   (assets/css/combat-ftl.css .cft-bar / .cft-bar-fill / .cft-bar-seg): a taller,
   dark-inset rounded track holding a glowing colored fill, overlaid by fine
   "segment" notches so the top-bar vitals read the same as the PvE/PvP cards.
   The fill stays width-driven by its inline style + the .js-*-bar hooks, so no
   header markup or JS change is needed — the notch overlay is a ::after pseudo
   on the track, painted above the fill (the track clips the fill's glow inward,
   exactly like .cft-bar's overflow:hidden). */
.xp-bar-track { position: relative; height: 11px; width: 78px; background: #0a0f0b;
  border: 1px solid #1d2c22; border-radius: 4px; overflow: hidden; }
.xp-bar-fill  { height: 100%; background: var(--accent); border-radius: 3px;
  box-shadow: 0 0 7px var(--accent); transition: width 0.4s ease; }
/* Segment notches: thin dark bands every ~11px, matching .cft-bar-seg's look. */
.xp-bar-track::after { content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image: repeating-linear-gradient(90deg, transparent 0 9px, #05080a 9px 11px); }
.hp-bar-fill   { background: #d04848; box-shadow: 0 0 7px rgba(208,72,72,0.6); }
.stam-bar-fill { background: #e0a93a; box-shadow: 0 0 7px rgba(224,169,58,0.6); }
.void-bar-fill { background: #9060e0; box-shadow: 0 0 7px rgba(144,96,224,0.6); }
.rested-xp-bar-fill { background: #8aa8e8; box-shadow: 0 0 7px rgba(138,168,232,0.6); }

/* ── Segmented HUD bar (canonical, reusable) ──────────────────────────────────
   The site-wide progress / stat bar style, matching the in-combat player cards
   (assets/css/combat-ftl.css .cft-bar). EVERY value bar in the game — XP, health,
   stamina, void, shields, quest/mission progress, meters, cooldowns — uses this
   look: a dark inset rounded track holding a glowing colored fill, overlaid by
   fine "segment" notches. NEW bars should use these classes directly:
       <div class="vf-bar"><span class="vf-bar-fill" style="width:60%"></span></div>
   Recolor a fill by overriding its background + a matching glow (the .hp/.xp/…
   modifier pattern above). Existing feature bars replicate this SAME treatment in
   their own CSS files so page-CSS load order stays correct — search the repo for
   "segment notches" to find them. The notch pitch (9px lit / 2px gap, over a
   #0a0f0b track with a #1d2c22 edge) is the canon: keep it identical everywhere
   so every bar reads as one system. */
.vf-bar { position: relative; height: 11px; background: #0a0f0b; border: 1px solid #1d2c22;
  border-radius: 4px; overflow: hidden; display: block; }
.vf-bar-fill { display: block; height: 100%; border-radius: 3px; background: var(--accent);
  box-shadow: 0 0 7px var(--accent); transition: width 0.4s ease; }
.vf-bar::after { content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image: repeating-linear-gradient(90deg, transparent 0 9px, #05080a 9px 11px); }

/* ── Shared toast (window.VF.toast) ──
   A single bottom-centre notification reused for brief, styled in-app messages
   (e.g. "Your health is already full.") in place of window.alert(). */
.vf-toast {
  position: fixed; left: 50%; bottom: 30px; transform: translateX(-50%) translateY(12px);
  z-index: 11000; max-width: 90vw;
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.7rem 1.1rem; border-radius: 8px;
  background: rgba(10,14,8,0.97); border: 1px solid var(--border-hi); border-left-width: 3px;
  color: var(--text-bright); font-size: 0.85rem; font-weight: 600; letter-spacing: 0.02em;
  box-shadow: 0 8px 28px rgba(0,0,0,0.6);
  opacity: 0; pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
}
.vf-toast.show       { opacity: 1; transform: translateX(-50%) translateY(0); }
.vf-toast-info       { border-left-color: var(--accent); }
.vf-toast-success    { border-left-color: var(--accent-hi); }
.vf-toast-error      { border-left-color: #d04848; }
.vf-toast::before {
  font-family: 'Font Awesome 6 Free'; font-weight: 900; font-size: 0.95rem; flex-shrink: 0;
}
.vf-toast-info::before    { content: '\f05a'; color: var(--accent-hi); }   /* circle-info */
.vf-toast-success::before { content: '\f058'; color: var(--accent-hi); }   /* circle-check */
.vf-toast-error::before   { content: '\f06a'; color: #d04848; }            /* circle-exclamation */

/* ── Requirements pop-up (window.VFReqPopup) ──
   Blocks an equip / quick-slot assignment when the character doesn't meet an item's
   stat/skill requirements, listing every requirement met (green) or unmet (red) with
   the character's current level. Built and shown by quickslots.js; one reused node is
   appended to <body>. z-index sits above the item colorbox (10000) and toast (11000). */
.vf-req-overlay {
  position: fixed; inset: 0; z-index: 12000;
  display: none; align-items: center; justify-content: center;
  background: rgba(2, 4, 2, 0.72); padding: 1rem;
}
.vf-req-overlay.open { display: flex; }
.vf-req-box {
  position: relative; width: 100%; max-width: 340px;
  max-height: 90vh; overflow-y: auto;
  padding: 1rem 1.1rem 1.1rem; border-radius: 10px;
  background: rgba(10, 14, 8, 0.98); border: 1px solid var(--border-hi);
  box-shadow: 0 12px 48px rgba(0, 0, 0, 0.75);
}
.vf-req-close {
  position: absolute; top: 6px; right: 8px;
  width: 24px; height: 24px; padding: 0; line-height: 22px;
  border: none; border-radius: 4px; background: rgba(0, 0, 0, 0.45);
  color: var(--text-muted); font-size: 18px; cursor: pointer;
  transition: color 0.12s, background 0.12s;
}
.vf-req-close:hover { color: var(--text-bright); background: rgba(0, 0, 0, 0.7); }
.vf-req-title {
  display: flex; align-items: center; gap: 0.45rem;
  font-size: 0.95rem; font-weight: 700; letter-spacing: 0.03em;
  color: #FF8A7A; margin-bottom: 0.4rem; padding-right: 1.2rem;
}
.vf-req-title i { color: #F5C542; }
.vf-req-sub {
  font-size: 0.78rem; color: var(--text-muted); margin-bottom: 0.7rem; line-height: 1.4;
}
.vf-req-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.4rem; }
.vf-req-row {
  display: flex; align-items: center; gap: 0.45rem;
  font-size: 0.82rem; font-weight: 600; letter-spacing: 0.02em;
}
.vf-req-row i { flex-shrink: 0; width: 1rem; text-align: center; }
.vf-req-met   { color: #7CFF9E; }
.vf-req-unmet { color: #FF8A7A; }
.vf-req-have  { color: var(--text-muted); font-weight: 400; font-style: italic; }
.vf-req-ok {
  display: block; width: 100%; margin-top: 0.95rem; padding: 0.5rem 0.6rem;
  font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  border-radius: 4px; border: 1px solid var(--accent-hi);
  background: var(--accent-hi); color: var(--bg-card); cursor: pointer;
  transition: filter 0.12s;
}
.vf-req-ok:hover { filter: brightness(1.12); }

.server-time-bar {
  display: flex; align-items: center; gap: 0.5rem; margin-left: auto;
  padding: 0.35rem 0.85rem; background: var(--bg-card2); border: 1px solid var(--border-hi);
  border-radius: 4px; font-family: 'Share Tech Mono', monospace; font-size: 0.78rem;
  color: var(--text-bright); flex-shrink: 0;
}
.server-time-bar i { color: var(--accent-hi); }
.server-time-date { color: var(--text-muted); letter-spacing: 0.04em; }
.server-time-tz   { color: var(--accent-hi); font-weight: 700; letter-spacing: 0.06em; }

.player-menu-wrap { position: relative; }

.header-player {
  display: flex; align-items: center; gap: 0.6rem; padding: 0.25rem 0.6rem;
  border: 1px solid var(--border); border-radius: 3px; cursor: pointer; user-select: none;
  transition: border-color 0.13s;
}
.header-player:hover,
.header-player.active { border-color: var(--border-hi); background: #ffffff03; }

.player-avatar {
  width: 34px; height: 34px; border-radius: 2px; background: var(--bg-card2);
  border: 1px solid var(--border-hi); overflow: hidden; flex-shrink: 0;
}

.avatar-img { width: 100%; height: 100%; object-fit: contain; image-rendering: pixelated; display: block; }

.player-info   { display: flex; flex-direction: column; gap: 1px; }
.player-name   { font-size: 0.88rem; font-weight: 700; color: var(--text-bright); letter-spacing: 0.06em; line-height: 1; }
.player-class  { font-size: 0.62rem; color: var(--text-muted); letter-spacing: 0.06em; text-transform: uppercase; }
.player-chevron { color: var(--text-muted); font-size: 0.7rem; display: inline-block; transition: transform 0.2s ease; }
.header-player.active .player-chevron { transform: rotate(180deg); }

/* ─────────────────────────────────────
   PLAYER DROPDOWN
───────────────────────────────────── */
.player-dropdown {
  position: absolute; top: calc(100% + 9px); right: 0; width: 306px;
  background: var(--bg-card); border: 1px solid var(--border-hi); border-radius: 3px;
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.72), 0 0 0 1px #8da02012;
  z-index: 500; display: flex; flex-direction: row;
  /* Scroll instead of clipping when the menu is taller than the viewport
     (e.g. mobile landscape). overflow-x stays hidden to preserve corner radius. */
  max-height: calc(100vh - var(--header-h) - 18px);
  max-height: calc(100dvh - var(--header-h) - 18px);
  overflow-y: auto; overflow-x: hidden;
  opacity: 0; transform: translateY(-6px) scale(0.98); pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
}
/* Quick-slot hotbar: a horizontal row sitting in the panel flow, below the
   Level/XP blocks. (It was once a vertical left rail; a row reads as a hotbar
   and keeps the menu a single clean column.) */
.pd-slotbar {
  display: flex; flex-direction: row; gap: 6px;
  padding: 0.55rem 1rem; background: var(--bg-card2);
  border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
}
.pd-slot {
  /* Equal-width square slots that share the row. min-width:0 lets them shrink to
     fit narrow phones; max-width stops 5 slots from ballooning on a wide menu. */
  flex: 1 1 0; min-width: 0; max-width: 54px; aspect-ratio: 1 / 1; background: #0a0e07;
  border: 1px solid var(--border); border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; position: relative; transition: border-color 0.12s, background 0.12s;
}
.pd-slot:hover { border-color: var(--accent); background: #111a07; }
/* Slot number sits top-left; the owned-stack count (.qs-qty) sits bottom-right. */
.pd-slot-num {
  font-size: 0.48rem; color: var(--text-muted); letter-spacing: 0.04em;
  position: absolute; top: 2px; left: 3px; line-height: 1;
}
/* Stack count: distinct gold so it reads separately from the muted slot number. */
.qs-qty {
  font-size: 0.5rem; color: var(--gold-hi); font-weight: 700; letter-spacing: 0.02em;
  position: absolute; bottom: 2px; right: 3px; line-height: 1; pointer-events: none;
}

/* ── Quick-slot states (shared by the dropdown bar and the inventory bar) ── */
.pd-slot.filled { border-color: var(--accent); background: #111a07; }
.pd-slot .qs-icon { font-size: 1rem; color: var(--accent-hi); pointer-events: none; }
/* Item artwork shown in place of the FontAwesome icon when an item has one. */
.pd-slot .qs-img { width: 78%; height: 78%; object-fit: contain; pointer-events: none; }
.pd-slot.qs-drop { border-color: var(--accent-hi); background: #16210a; box-shadow: 0 0 0 2px #8da02033; }
/* Requirement gate: a slotted item whose stat/skill requirements the character
 * doesn't meet gets a red transparency overlay so it reads as "owned but unusable".
 * pointer-events:none keeps the slot fully interactive (clear ×, click-to-use/open
 * all still work); the red border reinforces the locked state. Set server-side on
 * each bar and kept in sync by quickslots.js across assigns/rearranges. */
.pd-slot.qs-locked { border-color: #b3402f; }
.pd-slot.qs-locked::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: rgba(190, 45, 30, 0.42); pointer-events: none; z-index: 2;
}
/* ── Durability wear states (includes/durability.php → durability_state) ──
 * WORN (at or below DURABILITY_WARN_PCT = 10% of the copy's ceiling): an AMBER
 * overlay — the item still works, but it is one or two swings from snapping.
 * BROKEN (0 durability): a RED overlay plus the .qs-broken-mark glyph on top, and
 * the item cannot be used at all until it is repaired.
 *
 * Exactly ONE of qs-worn / qs-locked / qs-broken is ever set on a tile (the renderer
 * picks by precedence broken > requirement-lock > worn) because all three paint the
 * same ::after layer. These rules sit AFTER .qs-locked so that, if a stale/JS-toggled
 * tile ever carries two of them, the wear state still wins on equal specificity. */
.pd-slot.qs-worn { border-color: #c9971f; }
.pd-slot.qs-worn::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: rgba(214, 163, 39, 0.38); pointer-events: none; z-index: 2;
}
.pd-slot.qs-broken { border-color: #d04848; }
.pd-slot.qs-broken::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: rgba(190, 45, 30, 0.52); pointer-events: none; z-index: 2;
}
/* The broken glyph rides ABOVE the red overlay (z-index 3 vs the overlay's 2) and is
   click-through, so the slot stays draggable/clearable exactly as before. */
.pd-slot .qs-broken-mark {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  font-size: 0.95rem; color: #ffdede; text-shadow: 0 1px 3px rgba(0,0,0,0.9);
  pointer-events: none; z-index: 3;
}
/* Dim the item art under a broken overlay so the glyph reads clearly. */
.pd-slot.qs-broken .qs-img,
.pd-slot.qs-broken .qs-icon { opacity: 0.45; filter: grayscale(0.6); }
/* Filled slots can be dragged to another slot to rearrange the bar. */
.pd-slot.filled { cursor: grab; }
.pd-slot.dragging { opacity: 0.5; cursor: grabbing; }
.qs-clear {
  position: absolute; top: 1px; right: 2px;
  width: 13px; height: 13px; padding: 0; line-height: 11px;
  border: none; border-radius: 3px; background: rgba(0,0,0,0.55); color: #d98a6a;
  font-size: 12px; cursor: pointer; display: none;
}
.pd-slot.filled:hover .qs-clear { display: block; }
.pd-panel { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.player-dropdown.open { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
.player-dropdown::before {
  content: ''; position: absolute; top: -5px; right: 22px; width: 9px; height: 9px;
  background: var(--bg-card2); border-left: 1px solid var(--border-hi); border-top: 1px solid var(--border-hi);
  transform: rotate(45deg);
}

/* Centred character card: thought bubble + class GIF stacked, then name/class
   below. A row layout broke here — the class GIF loads at its native 256px and
   shoved the name/class clean off the right edge of the 306px menu. */
.pd-head { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 0.45rem; padding: 0.85rem 1rem; border-bottom: 1px solid var(--border); background: var(--bg-card2); }
/* Cap the GIF so it can never blow out the menu width again. */
.pd-head-avatar  { flex-shrink: 0; width: 128px; max-width: 100%; border-radius: 2px; overflow: hidden; line-height: 0; }
.pd-head-avatar .avatar-img { width: 100%; height: auto; }
.pd-head-name    { font-size: 0.92rem; font-weight: 700; color: var(--text-bright); letter-spacing: 0.04em; line-height: 1.2; }
.pd-head-class   { font-size: 0.63rem; color: var(--accent); text-transform: uppercase; letter-spacing: 0.1em; margin-top: 0.15rem; }

/* Avatar column: stacks thought bubble above the class GIF */
.pd-avatar-col { display: flex; flex-direction: column; align-items: center; flex-shrink: 0; }

/* Thought bubble and its trailing dots */
.pd-think-wrap  { display: flex; flex-direction: column; align-items: center; margin-bottom: -10px; max-width: 100%; }
/* nowrap is fine now the head is centred (bubble has the full panel width), but
   cap + ellipsis guards against an over-long flavour line on a narrow phone. */
.pd-think-bubble { background: #fff; border: 1px solid #ccc; border-radius: 10px; padding: 3px 8px; font-size: 0.82rem; color: #111; white-space: nowrap; letter-spacing: 0.04em; max-width: 100%; overflow: hidden; text-overflow: ellipsis; transition: opacity 0.3s; }
.pd-think-dots  { display: flex; flex-direction: column; align-items: center; gap: 2px; margin-top: 3px; }
/* Three dots shrink as they approach the avatar, giving the classic thought-bubble tail */
.pd-think-dots span { display: block; background: #ccc; border-radius: 50%; }
.pd-think-dots span:nth-child(1) { width: 5px; height: 5px; }
.pd-think-dots span:nth-child(2) { width: 3px; height: 3px; }
.pd-think-dots span:nth-child(3) { width: 2px; height: 2px; }

.pd-stats { display: grid; grid-template-columns: repeat(2, 1fr); border-bottom: 1px solid var(--border); }
.pd-stats.pd-stats-single { grid-template-columns: 1fr; }
.pd-stat  { padding: 0.6rem 0.5rem; text-align: center; border-right: 1px solid var(--border); }
.pd-stat:last-child { border-right: none; }
.pd-stat-label { font-size: 0.56rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-muted); margin-bottom: 0.18rem; }
/* Flex (not the plain text line it used to be) because the Level/Power row now
 * carries the military rank insignia to the LEFT of the numbers, exactly like the
 * header rank tile — the badge is a flex box itself and needs to sit on the same
 * centre line as the digits rather than on the text baseline. */
.pd-stat-val   { display: flex; align-items: center; justify-content: center; font-size: 0.9rem; font-weight: 700; color: var(--text-bright); font-family: 'Share Tech Mono', monospace; letter-spacing: 0.02em; }
.pd-stat-val .pd-stat-sep { color: var(--text-muted); margin: 0 0.35rem; font-weight: 400; }
/* Match the header tile: the badge is a plate to the left of the pair, and the
 * Power number carries the accent colour so the two readouts look identical. */
.pd-stat-val .rank-insignia-icon { margin-right: 0.45rem; }
.pd-stat-val .js-pd-power { color: var(--accent-hi); }
/* The dropdown has no hover tooltip (the header tile does), so the rank NAME is
 * printed under the numbers instead — otherwise the pips are unreadable here. */
.pd-stat-rank { margin-top: 0.2rem; font-size: 0.56rem; text-transform: uppercase; letter-spacing: 0.09em; color: var(--text-muted); }
.pd-coins   { color: var(--accent-hi); }
.pd-shards  { color: var(--purple-hi); }
.pd-premium { color: var(--gold); }

.pd-xp-wrap   { padding: 0.7rem 1rem; border-bottom: 1px solid var(--border); }
.pd-xp-labels { display: flex; justify-content: space-between; font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-muted); margin-bottom: 0.38rem; }
.pd-xp-pct    { color: var(--accent-hi); }
/* Segmented HUD bar — see the canonical .vf-bar in this file. */
.pd-xp-track  { position: relative; height: 11px; background: #0a0f0b; border: 1px solid #1d2c22; border-radius: 4px; overflow: hidden; }
.pd-xp-fill   { height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent-hi)); border-radius: 3px; box-shadow: 0 0 7px #aac42466; }
.pd-xp-track::after { content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image: repeating-linear-gradient(90deg, transparent 0 9px, #05080a 9px 11px); }
.pd-xp-sub    { font-size: 0.6rem; color: var(--text-muted); margin-top: 0.3rem; letter-spacing: 0.03em; }

/* ── Character class switcher (inside the player dropdown) ──────────────── */
.pd-chars { padding: 0.35rem 0; border-top: 1px solid var(--border); }
.pd-section-label {
  padding: 0.3rem 1rem 0.2rem;
  font-size: 0.6rem; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--text-muted);
}

/* Collapsible section header (shared by "My Characters" and "Settings"): a
   full-width clickable row that expands the body below. The caret flips when
   open. Any .pd-collapse wrapper + .pd-collapse-toggle + .pd-collapse-body gets
   the same behaviour (toggled by js/main.js). */
.pd-collapse-toggle {
  display: flex; align-items: center; gap: 0.5rem;
  width: 100%; padding: 0.32rem 1rem 0.28rem;
  background: none; border: 0; font-family: inherit; cursor: pointer;
  text-align: left; transition: background 0.12s;
}
.pd-collapse-toggle:hover { background: #ffffff07; }
.pd-collapse-toggle .pd-section-label { padding: 0; flex: 0 0 auto; }
/* Active-callsign hint on the My Characters header, right-aligned so the
   collapsed state still reads who you're playing. */
.pd-chars-active {
  flex: 1 1 auto; min-width: 0; text-align: right;
  font-size: 0.72rem; font-weight: 700; color: var(--gold-hi);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* margin-left:auto pins the caret to the right edge whether or not the header
   has a middle element (My Characters has the callsign; Settings does not). */
.pd-collapse-caret {
  flex: 0 0 auto; margin-left: auto; font-size: 0.62rem; color: var(--text-muted);
  transition: transform 0.18s ease;
}
.pd-collapse-toggle[aria-expanded="true"] .pd-collapse-caret { transform: rotate(180deg); }
.pd-collapse-toggle[aria-expanded="true"] .pd-chars-active { color: var(--text-muted); }

/* Body is collapsed (max-height 0) until .pd-collapse-open is set by the toggle. */
.pd-collapse-body { max-height: 0; overflow: hidden; transition: max-height 0.22s ease; }
.pd-collapse.pd-collapse-open .pd-collapse-body { max-height: 360px; }

.pd-char-row {
  display: flex; align-items: center; gap: 0.5rem;
  width: 100%; padding: 0.45rem 1rem;
  background: none; border: 0; font-family: inherit; cursor: pointer;
  text-align: left; transition: background 0.12s;
}
.pd-char-row:not(:disabled):hover { background: #ffffff07; }
.pd-char-row:disabled { cursor: default; }
.pd-char-name {
  font-size: 0.82rem; font-weight: 700; color: var(--text-bright); flex-shrink: 0;
}
.pd-char-meta {
  font-size: 0.7rem; color: var(--text-muted); letter-spacing: 0.03em;
  flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pd-char-badge {
  margin-left: auto; flex-shrink: 0;
  font-size: 0.58rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; padding: 0.12rem 0.4rem; border-radius: 99px;
  background: rgba(255, 200, 50, 0.12); color: var(--gold-hi);
  border: 1px solid rgba(255, 200, 50, 0.35);
}
.pd-char-active .pd-char-name { color: var(--gold-hi); }
.pd-char-create {
  display: flex; align-items: center; gap: 0.55rem;
  padding: 0.42rem 1rem;
  font-size: 0.78rem; font-weight: 600; letter-spacing: 0.04em;
  color: var(--accent-hi); text-decoration: none;
  transition: background 0.12s, color 0.12s;
}
.pd-char-create:hover { background: #ffffff07; color: var(--text-bright); }
.pd-char-create i { color: var(--text-muted); font-size: 0.7rem; }

.pd-actions { padding: 0.3rem 0; border-top: 1px solid var(--border); }
.pd-action  {
  display: flex; align-items: center; gap: 0.65rem; padding: 0.55rem 1rem;
  font-size: 0.8rem; font-weight: 600; letter-spacing: 0.04em;
  color: var(--text); text-decoration: none; transition: background 0.12s, color 0.12s;
}
.pd-action i { color: var(--text-muted); transition: color 0.12s; }
.pd-action:hover { background: #ffffff07; color: var(--text-bright); }
.pd-action:hover i { color: var(--accent); }
.pd-action-danger:hover   { background: #c0302012; color: #e06050; }
.pd-action-danger:hover i { color: #c03020; }
/* The Text Mode toggle is a <button> wearing the .pd-action look — strip the
   native button chrome so it sits flush with the anchor rows above/below it. */
button.pd-action { width: 100%; background: none; border: 0; font-family: inherit; text-align: left; cursor: pointer; }
.pd-textmode-state { margin-left: auto; font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-muted); }
button.pd-action[aria-pressed="true"] .pd-textmode-state,
button.pd-action[aria-pressed="true"] i { color: var(--accent); }

/* ─────────────────────────────────────
   PAGE CONTENT
───────────────────────────────────── */
/* Extra bottom padding clears the fixed comm-link FAB (bottom-right, 52px + a
   0.75rem inset ≈ 64px tall) so the last row of page content is never hidden
   under it. Kept in sync across the responsive padding overrides below. */
.page-content { flex: 1; overflow-y: auto; padding: 1rem 1rem 4.5rem; }
.page-content::-webkit-scrollbar { width: 5px; }
.page-content::-webkit-scrollbar-thumb { background: var(--border-hi); border-radius: 3px; }

/* ── Buttons ── */
.btn {
  display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.58rem 1.4rem;
  font-family: inherit; font-size: 1rem; font-weight: 700;
  /* Letter-spacing trimmed from 0.12em alongside the 0.78rem -> 1rem size
     bump so longer uppercase labels don't push buttons wider or wrap. */
  letter-spacing: 0.06em;
  text-transform: uppercase; cursor: pointer; border-radius: 2px; text-decoration: none;
  transition: all 0.13s; border: none;
}
.btn-primary { background: var(--accent); color: #080a06; border: 1px solid var(--accent-hi); }
.btn-primary:hover { background: var(--accent-hi); box-shadow: 0 0 18px #8da02044; }
.btn-ghost  { background: transparent; color: var(--text); border: 1px solid var(--border-hi); }
.btn-ghost:hover { border-color: var(--text-muted); color: var(--text-bright); }
.btn-sm { padding: 0.35rem 0.9rem; font-size: 0.87rem; }

/* Disabled buttons read as inert — greyed out, no hover, not clickable. */
.btn:disabled,
.btn[disabled] {
  opacity: 0.35; cursor: not-allowed;
  background: var(--bg-card2); color: var(--text-muted);
  border-color: var(--border); box-shadow: none;
  pointer-events: none;
}

/* ── Table scroll wrapper ── */
/* Wrap wide tables so they scroll horizontally on narrow screens instead of
   forcing the whole page to overflow. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ── Grid rows ── */
.card-row { display: grid; gap: 0.9rem; }
.cols-4   { grid-template-columns: repeat(4, 1fr); }
.cols-3   { grid-template-columns: 1.1fr 1.9fr 1.1fr; }
.cols-2   { grid-template-columns: repeat(2, 1fr); }
.cols-1   { grid-template-columns: 1fr; }

/* ── Base card ── */
.card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 3px; padding: 0.9rem; display: flex; flex-direction: column; gap: 0.7rem; transition: border-color 0.15s; }
.card:hover { border-color: var(--border-hi); }
.card-title {
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.15em; text-transform: uppercase;
  color: var(--accent); display: flex; align-items: center; justify-content: space-between;
}
.card-title svg { color: var(--text-muted); cursor: pointer; }
.card-title svg:hover { color: var(--text); }

/* ─────────────────────────────────────
   SIDEBAR BACKDROP
───────────────────────────────────── */
.sidebar-backdrop { display: none; position: fixed; inset: 0; background: rgba(0, 0, 0, 0.6); z-index: 199; cursor: pointer; }
.sidebar-backdrop.active { display: block; }

/* ─────────────────────────────────────
   RESPONSIVE BREAKPOINTS
───────────────────────────────────── */

@media (min-width: 992px) {
  .menu-btn { display: none; }
  /* Safety net: the stats row is flex:1, so without this it grows to its content
     width and shoves the clock + player menu (and its dropdown) off the right edge
     on narrower desktops. min-width:0 lets it shrink; overflow:hidden clips a stat
     at worst instead of pushing the player menu off-screen. The breakpoint trims
     below handle the clean whole-stat hiding so this rarely has to clip anything. */
  .header-stats { min-width: 0; overflow: hidden; }
}

/* Progressive trim of the stats row so it always fits and the player menu never
   gets pushed off-screen. Tile order is: 1 Rank (level/power), 2 Unspent XP,
   3 Health, 4 Stamina, 5 Void (class-gated — absent for non-void classes, which
   shifts everything after it up one), 6 Rested XP, 7 Region.
   The Rank tile is narrow (two short numbers, no progress bar), so the first
   threshold keeps five tiles where it used to keep four; the new 1360px step
   then drops back to four.
   Unspent XP is counted here even when it is empty — it stays in the DOM and is
   hidden by .is-empty rather than being omitted, precisely so these nth-child
   positions do not shift underneath the player as they earn and spend XP. */
@media (max-width: 1475px) {
  .header-stats .hstat:nth-child(n+7) { display: none; }
}
@media (max-width: 1360px) {
  .header-stats .hstat:nth-child(n+6) { display: none; }
}

@media (max-width: 1200px) {
  .cols-4 { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 991px) {
  .sidebar { position: fixed; left: 0; top: 0; height: 100%; z-index: 200; transform: translateX(-100%); transition: transform 0.28s cubic-bezier(0.25, 0.46, 0.45, 0.94); }
  .sidebar.open { transform: translateX(0); box-shadow: 8px 0 48px rgba(0, 0, 0, 0.8); }
  .sidebar-status { display: none; }
  .sidebar-brand  { padding: 0.8rem 1.1rem; }
  .brand-void     { font-size: 1.7rem; }
  .nav-item       { padding: 0.44rem 1.1rem; }
  .main-wrapper   { width: 100%; }

  /* ── Collapse the stats row into a "Stats" dropdown ──
     The wrapper becomes a positioned flex item (flex:1 keeps the player menu
     pushed to the right edge, like the old full-width row did), the toggle pill
     appears, and .header-stats turns into an absolutely-positioned panel under
     the toggle. #header-stats (an ID) outranks the .header-stats .hstat:nth-child
     trim rules in the smaller breakpoints, so EVERY stat shows in the dropdown. */
  .header-stats-wrap { display: block; flex: 1; position: relative; }
  .stats-toggle { display: inline-flex; }

  #header-stats {
    position: absolute; top: calc(100% + 9px); left: 0; z-index: 510;
    flex-direction: column; align-items: stretch; flex: none; gap: 0;
    width: min(290px, calc(100vw - 1.5rem));
    padding: 0.35rem;
    background: var(--bg-card); border: 1px solid var(--border-hi); border-radius: 6px;
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.72), 0 0 0 1px #8da02012;
    opacity: 0; transform: translateY(-6px); pointer-events: none;
    transition: opacity 0.16s ease, transform 0.16s ease;
  }
  #header-stats.open { opacity: 1; transform: none; pointer-events: auto; }

  /* Show every stat, full width, as stacked rows (overrides the inline trim). */
  #header-stats .hstat {
    display: flex !important; width: 100%; padding: 0.55rem 0.6rem;
    border-right: 0; border-bottom: 1px solid var(--border);
  }
  #header-stats .hstat:first-child { padding-left: 0.6rem; }
  #header-stats .hstat:last-child  { border-bottom: 0; }
  #header-stats .xp-bar-track { width: 100%; }
}

@media (max-width: 767px) {
  .page-content { padding: 0.7rem 0.7rem 4.5rem; }
  .top-header   { padding: 0 0.75rem; }
  .server-time-bar { display: none; }
  /* Phones keep the two most-glanced readouts — Rank (level/power) and Health —
     plus Unspent XP, which sits between them and is an ACTION the player needs to
     be able to reach on a phone, not just a readout. Everything else drops so the
     header fits. nth-child selector keeps specificity equal to the ≤991 rule above
     so this reliably wins by source order. */
  .header-stats .hstat:nth-child(n+4) { display: none; }
  .cols-3 { grid-template-columns: 1fr; }
  .cols-2 { grid-template-columns: 1fr; }
  .sidebar-brand { padding: 0.65rem 1rem; }
  .brand-void    { font-size: 1.5rem; }
  .nav-item      { padding: 0.36rem 1rem; font-size: 0.93rem; gap: 0.6rem; }
  .btn           { padding: 0.48rem 0.9rem; font-size: 0.93rem; }
}

/* ─────────────────────────────────────
   COLOUR THEMES
   Override only the background / accent variables; utility colours (green-ok,
   gold, red, purple for void shards) are intentionally left unchanged so badges,
   bars, and error states stay legible across all themes.
───────────────────────────────────── */

/* Burgundy — deep wine, blood-red accent */
[data-theme="burgundy"] {
  --bg:         #090507;
  --bg-sidebar: #0c0608;
  --bg-card:    #10070a;
  --bg-card2:   #160a0d;
  --border:     #280e18;
  --border-hi:  #481828;
  --accent:     #b82040;
  --accent-hi:  #d83050;
  --text:       #d0bec2;
  --text-muted: #9c7e82;
  --text-bright:#ede6e8;
  --bg-input:   #0c0608;
}

/* Gray — cold steel, industrial */
[data-theme="gray"] {
  --bg:         #080808;
  --bg-sidebar: #0c0c0c;
  --bg-card:    #101010;
  --bg-card2:   #141414;
  --border:     #1e1e1e;
  --border-hi:  #2e2e2e;
  --accent:     #8898a8;
  --accent-hi:  #a8bac8;
  --text:       #c0c8d0;
  --text-muted: #808890;
  --text-bright:#e0e8f0;
  --bg-input:   #0c0c0c;
}

/* Purple — void energy, cosmic */
[data-theme="purple"] {
  --bg:         #07050f;
  --bg-sidebar: #0a0714;
  --bg-card:    #0e0a1c;
  --bg-card2:   #130d22;
  --border:     #1c1535;
  --border-hi:  #2c2050;
  --accent:     #8050d8;
  --accent-hi:  #a070f8;
  --text:       #bdb8d5;
  --text-muted: #8578a8;
  --text-bright:#e5e0f5;
  --bg-input:   #0a0714;
}

/* Cyberpunk — deep teal night, hot-magenta accent.
   Follows the dark pattern of the other themes: a near-black teal-tinted backdrop
   with the neon teal (#0a9fa7) reserved for borders/highlights and hot magenta
   (#c10577) as the action accent. */
[data-theme="cyberpunk"] {
  --bg:         #02100f;
  --bg-sidebar: #04211f;
  --bg-card:    #052a28;
  --bg-card2:   #073431;
  --border:     #0a504c;
  --border-hi:  #0a9fa7;
  --accent:     #c10577;
  --accent-hi:  #e8208f;
  --text:       #c2dedc;
  --text-muted: #7ba8a4;
  --text-bright:#e6f6f4;
  --bg-input:   #04211f;
}

/* Amber — warm rust/amber salvage-dock glow, matches the game's own art
   palette (docs/pixellab-art-pipeline.md's "warm amber light"). */
[data-theme="amber"] {
  --bg:         #0c0904;
  --bg-sidebar: #100c05;
  --bg-card:    #140f06;
  --bg-card2:   #1a1308;
  --border:     #2c2010;
  --border-hi:  #4a3318;
  --accent:     #d8862c;
  --accent-hi:  #f5a542;
  --text:       #d8c8b0;
  --text-muted: #a08860;
  --text-bright:#f5ecd8;
  --bg-input:   #100c05;
}

/* Frost — icy cyan coolant glow (the "frost-cyan" accent used for cold/
   coolant mobs in the art pipeline), distinct from Cyberpunk's magenta. */
[data-theme="frost"] {
  --bg:         #050a0c;
  --bg-sidebar: #081012;
  --bg-card:    #0a1418;
  --bg-card2:   #0e1a20;
  --border:     #16323c;
  --border-hi:  #1c5468;
  --accent:     #2da8c8;
  --accent-hi:  #4ccdf0;
  --text:       #bcdce4;
  --text-muted: #6a98a4;
  --text-bright:#e0f4fa;
  --bg-input:   #081012;
}

/* Crimson — bright ember red-orange, distinct from Burgundy's deep wine. */
[data-theme="crimson"] {
  --bg:         #0c0504;
  --bg-sidebar: #100706;
  --bg-card:    #160908;
  --bg-card2:   #1c0c0a;
  --border:     #341210;
  --border-hi:  #5c1c18;
  --accent:     #d8302c;
  --accent-hi:  #f8453c;
  --text:       #d8c0bc;
  --text-muted: #a07a76;
  --text-bright:#f5e4e0;
  --bg-input:   #100706;
}

/* Sea — deep sea-green with a soft chartreuse accent. Follows the dark pattern
   of the other themes: a near-black green backdrop with the requested sea green
   (#07553B) reserved as the card/panel tone and lime (#CED46A) as the accent. */
[data-theme="sea"] {
  --bg:         #030f0a;
  --bg-sidebar: #04170f;
  --bg-card:    #07553B;
  --bg-card2:   #086345;
  --border:     #0e7d5a;
  --border-hi:  #14996e;
  --accent:     #CED46A;
  --accent-hi:  #dde388;
  --text:       #cfe4d8;
  --text-muted: #8fb3a1;
  --text-bright:#eef7f0;
  --bg-input:   #04170f;
}

/* Slate — cool blue-gray, distinct from Gray's neutral steel. */
[data-theme="slate"] {
  --bg:         #07090c;
  --bg-sidebar: #0a0d10;
  --bg-card:    #0d1116;
  --bg-card2:   #11161c;
  --border:     #1c2530;
  --border-hi:  #2c3c4e;
  --accent:     #5a7ea0;
  --accent-hi:  #7aa0c8;
  --text:       #c0c8d2;
  --text-muted: #7a8a98;
  --text-bright:#e4eaf0;
  --bg-input:   #0a0d10;
}

.site-copyright {
  text-align: center;
  font-size: 0.68rem;
  color: var(--text-muted);
  letter-spacing: 0.06em;
  padding: 1rem 1rem 0.5rem;
  border-top: 1px solid var(--border);
  margin-top: 2rem;
}

@media (max-width: 368px) {
  .page-content   { padding: 0.5rem 0.5rem 4.5rem; }
  .top-header     { padding: 0 0.55rem; gap: 0.3rem; }
  .header-stats .hstat { padding: 0 0.5rem; }
  .cols-4         { grid-template-columns: 1fr; }
  .sidebar        { width: 200px; }
  .sidebar-brand  { padding: 0.5rem 0.85rem; }
  .brand-void     { font-size: 1.3rem; }
  .brand-frontier { letter-spacing: 0.18em; }
  .brand-divider  { margin: 0.2rem 0; }
  .nav-item       { padding: 0.28rem 0.85rem; font-size: 0.72rem; gap: 0.5rem; }
  .nav-fa-icon    { font-size: 0.72rem; }
  .card           { padding: 0.65rem; gap: 0.5rem; }
  .card-row       { gap: 0.5rem; }
  /* On the narrowest phones, anchor the menu to the VIEWPORT instead of the
     (off-centre, right-aligned) trigger. A near-full-width menu anchored
     right:0 to the trigger hung ~7px off the left screen edge and clipped the
     slot rail. The header is static at top:0, so a fixed menu sits just under
     it with an even 8px gutter on both sides. */
  .player-dropdown {
    position: fixed; top: var(--header-h); left: 8px; right: 8px; width: auto;
    max-height: calc(100vh - var(--header-h) - 16px);
    max-height: calc(100dvh - var(--header-h) - 16px);
  }
  /* The little pointer arrow can't track the trigger once we're viewport-anchored. */
  .player-dropdown::before { display: none; }
}

/* ─────────────────────────────────────
   TOUCH & SMALL-SCREEN ERGONOMICS
───────────────────────────────────── */

/* Inputs must be ≥16px or iOS Safari force-zooms the viewport on focus
   and never zooms back out. */
@media (max-width: 767px) {
  input, select, textarea,
  .form-input, .s-input, .admin-input,
  .buyin-row input[type="number"], .party-search input { font-size: 16px; }
}

/* Bump primary controls to the 44px touch-target minimum on touch devices,
   without affecting mouse/desktop density. */
@media (max-width: 991px) and (pointer: coarse) {
  .menu-btn { width: 44px; height: 44px; }
}

/* ═════════════════════════════════════════════════════════════
   PRE-AUTH SHARED
   Starfield, nebula base, nav bar, page body, fadeIn
═════════════════════════════════════════════════════════════ */

#stars { position: fixed; inset: 0; pointer-events: none; z-index: 0; }

.nebula { position: fixed; border-radius: 50%; filter: blur(85px); pointer-events: none; z-index: 0; }

.page-nav {
  position: relative; z-index: 10;
  display: flex; align-items: center; justify-content: space-between;
  padding: 1rem 2rem; border-bottom: 1px solid var(--border); background: var(--bg);
}
.nav-brand { font-size: 0.9rem; font-weight: 700; letter-spacing: 0.25em; text-transform: uppercase; text-decoration: none; color: var(--text-muted); }
/* Space the wordmark as VOID FRONTIER (the accent half gets a small leading
   gap) so the brand reads as two words. Set on the shared component so /index,
   /beta-signup and /login all match. */
.nav-brand span { color: var(--accent); margin-left: 0.4em; }
.nav-back,
.nav-link {
  display: inline-flex; align-items: center; gap: 0.4rem;
  font-size: 0.78rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-muted); text-decoration: none; transition: color 0.13s;
}
.nav-back:hover,
.nav-link:hover { color: var(--accent-hi); }

.page-body {
  position: relative; z-index: 2;
  min-height: calc(100vh - 57px);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 2.5rem 1.5rem;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ─────────────────────────────────────
   AUTH CARD CHROME (login, signup, admin tools)
───────────────────────────────────── */

.card-header { padding: 1.5rem 1.75rem 1.25rem; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 1rem; }
.card-label  { font-size: 0.62rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.1rem; }
.card-body   { padding: 1.5rem 1.75rem; }
.card-footer { padding: 1rem 1.75rem; border-top: 1px solid var(--border); font-size: 0.74rem; color: var(--text-muted); text-align: center; }
.card-footer a { color: var(--accent); text-decoration: none; }
.card-footer a:hover { color: var(--accent-hi); }

/* ─────────────────────────────────────
   AUTH FORMS — shared across login, signup, create_character
───────────────────────────────────── */

.alert-error {
  display: flex; align-items: center; gap: 0.55rem; padding: 0.65rem 0.75rem;
  background: #c0302015; border: 1px solid #c0302044; border-radius: 2px; font-size: 0.8rem; color: #e06050;
}

.divider-row {
  display: flex; align-items: center; gap: 0.75rem;
  font-size: 0.65rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted);
}
.divider-row::before, .divider-row::after { content: ''; flex: 1; height: 1px; background: var(--border); }

.form-row    { display: grid; gap: 1rem; margin-bottom: 1rem; }
.form-row-2  { grid-template-columns: 1fr 1fr; }
.form-group  { display: flex; flex-direction: column; gap: 0.35rem; }
.form-label  { font-size: 0.7rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-muted); }
.form-label .req { color: var(--accent); }
.form-label .optional { color: var(--text-muted); font-weight: 400; text-transform: none; letter-spacing: normal; opacity: 0.7; }

.form-input {
  width: 100%; background: var(--bg-input); border: 1px solid var(--border-hi); border-radius: 2px;
  color: var(--text); padding: 0.62rem 0.75rem; font-family: inherit; font-size: 0.88rem;
  transition: border-color 0.13s, box-shadow 0.13s; appearance: none;
}
.form-input::placeholder { color: var(--border-hi); }
/* The glow was #8da02020 — 12.5% alpha, effectively invisible on the near-black
 * --bg, so a focused input was almost indistinguishable from an unfocused one.
 * Now a solid 2px ring in the themeable accent, which follows the border-radius
 * (an outline would not).
 *
 * SPECIFICITY NOTE: `.form-input:focus` is (0,2,0) and therefore beats the global
 * `:focus-visible` rule at (0,1,0) — the global ring does NOT reach inputs. This
 * box-shadow IS the focus indicator here, for mouse and keyboard alike, so it has
 * to stay visible on its own. Don't lower its alpha assuming the global rule is
 * backing it up. */
.form-input:focus  { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-hi); }
.form-input.error  { border-color: var(--red); box-shadow: 0 0 0 2px #c0302018; }
textarea.form-input { resize: vertical; min-height: 90px; }
select.form-input  { cursor: pointer; }
select.form-input option { background: #111609; }

.input-wrap { position: relative; }
.input-icon {
  position: absolute; left: 0.75rem; top: 50%; transform: translateY(-50%);
  color: var(--text-muted); font-size: 0.8rem; pointer-events: none;
}
.input-wrap .form-input { padding-left: 2.2rem; }

.password-wrap { position: relative; }
.toggle-pw {
  position: absolute; right: 0.75rem; top: 50%; transform: translateY(-50%);
  background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 0.8rem; transition: color 0.13s;
}
.toggle-pw:hover { color: var(--accent); }

.form-error { font-size: 0.7rem; color: var(--red); display: flex; align-items: center; gap: 0.3rem; }

.submit-btn {
  width: 100%; padding: 0.8rem 2rem; background: var(--accent); color: #080a06;
  border: 1px solid var(--accent-hi); border-radius: 2px; font-family: inherit; font-size: 1rem;
  /* Letter-spacing trimmed from 0.14em alongside the 0.9rem -> 1rem size bump. */
  font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase;
  cursor: pointer; transition: all 0.15s; display: flex; align-items: center; justify-content: center; gap: 0.5rem;
}
.submit-btn:hover  { background: var(--accent-hi); box-shadow: 0 0 20px #aac42444; }
.submit-btn:active { transform: translateY(1px); }

@media (max-width: 560px) {
  .page-nav    { padding: 0.85rem 1rem; }
  .page-body   { padding: 1.5rem 1rem; }
  .card-header { padding: 1.1rem 1.25rem 1rem; }
  .card-body   { padding: 1.25rem; }
  .card-footer { padding: 0.85rem 1.25rem; }
  .form-row-2  { grid-template-columns: 1fr; }
  /* Landing + signup carry an emblem + a right-side link in the nav; tighten the
     wordmark on phones so the brand never crowds the Login/Back link. */
  .pg-landing .nav-brand, .pg-signup .nav-brand { letter-spacing: 0.12em; font-size: 0.82rem; }
}

/* Ultra-narrow phones: collapse the nav's right-side link to its icon so the
   brand + link can never collide (the icon keeps the title tooltip). */
@media (max-width: 340px) {
  .nav-link-label { display: none; }
}

/* ── Party invite overlay (global — footer.php renders this on every page) ── */
.party-invite-overlay { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(5, 8, 4, 0.7); z-index: 1000; }
.party-invite-overlay.show { display: flex; }
.party-invite-box     { background: var(--bg-card); border: 1px solid var(--border-hi); border-radius: 4px; padding: 1.4rem 1.6rem; text-align: center; min-width: 280px; }
.party-invite-title   { font-size: 1rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent-hi); margin-bottom: 0.6rem; }
.party-invite-msg     { font-size: 0.9rem; color: var(--text-bright); margin-bottom: 1rem; }
.party-invite-actions { display: flex; justify-content: center; gap: 0.6rem; }

/* ── Glossary term: ICE (Intrusion Countermeasures Electronics) ──
   Any hacking surface wraps the jargon term "ICE" in <abbr class="ice-term">.
   Style it as a defined term — a dotted underline + help cursor — so players
   know it's explained on hover, alongside the spelled-out text next to it. */
abbr.ice-term {
  text-decoration: underline dotted var(--accent-hi, #8fd14f);
  text-underline-offset: 2px;
  cursor: help;
  letter-spacing: 0.02em;
}
