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

/* Design tokens live in tokens.css (loaded first in index.html).
   Aesthetic brief in .impeccable.md (TailAdmin dark admin). */

body {
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--ink-dim);
  height: 100vh;
  font-size: var(--fs-md);
  line-height: var(--lh-normal);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Lucide icons — svg elements replace <i data-lucide="..."> at runtime.
   Control size + stroke + color from the parent so icons inherit context. */
svg[data-lucide],
.lucide { width: 1em; height: 1em; stroke-width: 2; flex-shrink: 0; }

/* ══ Topbar ══════════════════════════════════════════════════════
   Sits above the sidebar + content row. Holds global search + utility
   buttons. Taller than before (56px) to match modern admin rhythm. */
#topbar {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  padding: 0 var(--space-lg);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-md);
  position: sticky;
  top: 0;
  z-index: 200;
  height: var(--topbar-h);
  flex-shrink: 0;
}
.logo {
  font-size: var(--fs-md); font-weight: var(--fw-bold);
  letter-spacing: -0.015em; white-space: nowrap; color: var(--ink);
}
.logo span { color: var(--accent-mark); }
.tb-right { display: flex; align-items: center; gap: var(--space-xs); flex-shrink: 0; }

.tb-search-wrap { position: relative; }
.tb-search {
  background: var(--bg-sunken); border: 1px solid var(--line); border-radius: var(--r-md);
  color: var(--ink); padding: 8px 12px 8px 36px; font-size: var(--fs-sm); width: 280px;
  outline: none;
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              width var(--dur-fast) var(--ease-out);
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%239CA3AF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='11' cy='11' r='8'/><path d='m21 21-4.35-4.35'/></svg>");
  background-repeat: no-repeat;
  background-position: 12px 50%;
}
.tb-search::placeholder { color: var(--ink-faint); }
.tb-search:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
  width: 340px;
}
.tb-search-dd {
  top: calc(100% + 6px); right: 0; left: auto;
  min-width: 340px; max-width: 440px; z-index: 400;
  background: var(--surface-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
}
.ac-type-ip { background: var(--signal-warn-bg); color: var(--signal-warn); }
.ac-type-check { background: var(--accent-soft); color: var(--accent-mark); }

/* Top-bar utility buttons (Logs, Info, Lang, Back link). One consistent
   ghost-button style — subtle at rest, blue outline on hover. */
.lang-btn, .ip-toggle {
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  color: var(--ink-dim);
  padding: 6px 12px;
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  cursor: pointer;
  text-decoration: none;
  display: inline-flex; align-items: center; gap: 6px;
  line-height: 1;
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.lang-btn:hover, .ip-toggle:hover {
  color: var(--ink); border-color: var(--line-strong); background: var(--surface-hover);
}
.ip-toggle.active {
  color: var(--accent-mark); border-color: var(--accent);
  background: var(--accent-soft);
}

/* ══ Sidebar ═════════════════════════════════════════════════════
   260px wide. Card-like surface with icon + label rows, collapsible
   groups. Active item: blue-tinted bg + blue text; no side-stripe
   >1px (see .impeccable.md anti-refs). */
#sidenav {
  width: var(--sidebar-w);
  background: var(--surface);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  overflow: hidden;
  z-index: 500;
}
.sn-header {
  display: flex; align-items: center; gap: var(--space-sm);
  padding: 0 var(--space-lg);
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
  height: var(--topbar-h);     /* aligns with topbar */
  box-sizing: border-box;
}
/* Version chip next to the EterniaLogs brand. Empty until JS populates;
   collapses cleanly when /api/version fails. */
.app-version {
  font-family: var(--font-mono); font-size: var(--fs-xs);
  color: var(--ink-faint); white-space: nowrap;
  padding: 1px 6px; border-radius: 999px;
  border: 1px solid var(--line); background: transparent;
  cursor: help;
}
.app-version:empty { display: none; }
.app-version:hover { color: var(--ink-dim); border-color: var(--ink-faint); }
/* Flash the chip when a deploy lands (started_at changed). Two-second
   pulse to surface the version change without being a distraction. */
@keyframes app-version-flash-anim {
  0%   { background: color-mix(in srgb, #10B981 35%, transparent); }
  100% { background: transparent; }
}
.app-version-flash {
  animation: app-version-flash-anim 2s ease-out;
  color: var(--ink); border-color: #10B981;
}
.sn-nav {
  display: flex; flex-direction: column;
  padding: var(--space-md) var(--space-sm) var(--space-lg);
  overflow-y: auto;
  flex: 1;
  min-height: 0;
}
.sn-group { display: flex; flex-direction: column; margin-top: var(--space-md); }
.sn-group:first-child { margin-top: 0; }
.sn-group-header {
  all: unset;
  display: flex; align-items: center; justify-content: space-between;
  cursor: pointer;
  padding: 10px 16px 8px;
  color: var(--ink-faint);
  font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  text-transform: uppercase; letter-spacing: var(--tracking-label);
  user-select: none;
  transition: color var(--dur-fast) var(--ease-out);
}
.sn-group-header:hover { color: var(--ink-dim); }
.sn-chevron {
  width: 14px; height: 14px;
  color: var(--ink-faint);
  stroke-width: 2;
  transition: transform var(--dur-fast) var(--ease-out);
}
.sn-group.collapsed .sn-chevron { transform: rotate(-90deg); }
.sn-group.collapsed .sn-group-items { display: none; }
.sn-group-items { display: flex; flex-direction: column; gap: 2px; }

/* Nav items. Flex row: icon + label. Active = surface-raised bg + white
   text (TailAdmin pattern — no blue tint on passive nav). */
.tb-item {
  display: flex; align-items: center; gap: 14px;
  padding: 11px 16px; color: var(--ink-dim); text-decoration: none;
  font-size: var(--fs-md); font-weight: var(--fw-medium);
  border-radius: var(--r-md);
  transition: color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
  white-space: nowrap;
  line-height: 1.2;
}
.tb-item svg[data-lucide],
.tb-item > svg {
  width: 20px; height: 20px;
  color: var(--ink-faint);
  stroke-width: 2;
  flex-shrink: 0;
  transition: color var(--dur-fast) var(--ease-out);
}
.tb-item:hover { color: var(--ink); background: var(--surface-raised); }
.tb-item:hover svg { color: var(--ink); }
/* TailAdmin active state: bg-graydark (surface-raised) + white text + white
   icon. NOT a blue tint — the primary color is reserved for buttons. */
.tb-item.active {
  color: var(--ink);
  background: var(--surface-raised);
  font-weight: var(--fw-semibold);
}
.tb-item.active svg { color: var(--ink); }

/* ══ Content area ════════════════════════════════════════════════
   Body bg visible here; views render inside. Max content width keeps
   dense tables readable on ultrawide monitors. */
#app-wrap { display: flex; flex: 1; min-height: 0; overflow: hidden; }
#content {
  flex: 1;
  padding: var(--space-lg);
  min-width: 0;
  overflow-y: auto;
  height: 100%;
  background: var(--bg);
}
.view { display: none; }
.view.active { display: block; }
.view h1 {
  font-family: var(--font-sans);
  font-size: var(--fs-xl); font-weight: var(--fw-semibold);
  margin: 0 0 var(--space-2xs);
  letter-spacing: var(--tracking-tight);
  color: var(--ink);
}
.view-desc {
  font-size: var(--fs-sm);
  color: var(--ink-faint);
  margin-bottom: var(--space-lg);
  line-height: var(--lh-normal);
  max-width: 72ch;
}
.section-title {
  font-size: var(--fs-lg); font-weight: var(--fw-semibold);
  margin: var(--space-lg) 0 var(--space-sm);
  color: var(--ink);
  letter-spacing: var(--tracking-tight);
}

/* ══ Card — the default container ══════════════════════════════ */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-lg);
  margin-bottom: var(--space-md);
}
.card-hd {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: var(--space-md);
  gap: var(--space-md);
}
.card-title {
  font-size: var(--fs-md); font-weight: var(--fw-semibold);
  color: var(--ink); margin: 0;
  letter-spacing: var(--tracking-tight);
}

/* ══ Dashboard ═══════════════════════════════════════════════════
   Landing view. Grids + panels feed from /api/dashboard. */


/* Retention KPI tiles — D1 / D7 / D30 / Stickiness / DAU-WAU-MAU.
   Value colour follows GameAnalytics benchmark bands (gain ≥ top,
   accent ≥ median, warn below median). */
.dash-retkpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-md);
}
.dash-retkpi-tile {
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--space-sm) var(--space-md);
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}
.dash-retkpi-tile .label {
  font-size: 11px; color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--fw-semibold);
}
.dash-retkpi-tile .value {
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  color: var(--ink); line-height: 1.1;
  margin-top: 2px;
}
.dash-retkpi-tile .value.accent  { color: var(--accent-mark); }
.dash-retkpi-tile .value.sig-gain { color: var(--signal-gain); }
.dash-retkpi-tile .value.sig-warn { color: var(--signal-warn); }
.dash-retkpi-tile .sub {
  font-size: var(--fs-xs); color: var(--ink-faint);
  margin-top: 2px;
}

/* Global timeframe pills at top of the dashboard. Affect every trend
   chart and the period-delta tiles so "weekly vs monthly" comparisons
   are one click away. */
.dash-tf-bar {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin-bottom: var(--space-md);
}
.dash-tf-pills {
  display: inline-flex;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 2px;
  gap: 0;
}
.dash-tf-pill {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--ink-faint);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  padding: 6px 14px;
  border-radius: 999px;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
  transition: background 120ms ease, color 120ms ease;
}
.dash-tf-pill:hover { color: var(--ink); }
.dash-tf-pill.is-active {
  background: var(--accent-mark);
  color: #fff;
}
.dash-tf-compare {
  margin-left: auto;
  font-size: var(--fs-sm);
  color: var(--ink-faint);
}
.dash-tf-compare input[type=checkbox] { margin-right: 6px; }

/* Weekend Snapshot card — period header + 6 comparison tiles. */
.ws-period {
  display: flex; align-items: center; gap: var(--space-md);
  margin-bottom: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  background: var(--bg-sunken);
  border-radius: var(--r-md);
  border: 1px solid var(--line);
}
.ws-period-block { display: flex; flex-direction: column; gap: 2px; }
.ws-period-tag {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--fw-semibold);
  color: var(--ink-faint);
}
.ws-period-dates {
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.ws-period-vs {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
}

.ws-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-sm);
}
.ws-tile {
  padding: var(--space-sm) var(--space-md);
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  display: flex; flex-direction: column; gap: 2px;
}
.ws-tile-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--fw-semibold);
  color: var(--ink-faint);
}
.ws-tile-row {
  display: flex; align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
}
.ws-tile-this {
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.ws-tile-delta {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
}
.ws-tile-delta.sig-gain { color: var(--signal-gain); }
.ws-tile-delta.sig-loss { color: var(--signal-loss); }
.ws-tile-delta.sig-warn { color: var(--signal-warn); }
.ws-tile-prev { font-size: var(--fs-xs); }

/* Peak chart trim: summary tiles above the canvas, weekday baseline strip
   below it. Both styled to feel like a single composite card. */
.dash-peak-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
}
.dash-peak-tile {
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--space-sm) var(--space-md);
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}
.dash-peak-tile .label {
  font-size: 11px; color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--fw-semibold);
}
.dash-peak-tile .value {
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  color: var(--ink); line-height: 1.1;
  margin-top: 2px;
}
.dash-peak-tile .sub { font-size: var(--fs-xs); margin-top: 2px; }

.dash-peak-weekday {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px dashed var(--line);
}
.dash-peak-weekday-title {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  margin-bottom: var(--space-xs);
}
.dash-peak-weekday-row {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--space-xs);
  align-items: end;
}
.dash-peak-weekday-cell {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: var(--space-xs) 4px;
  border-radius: var(--r-sm);
}
.dash-peak-weekday-cell.is-current {
  background: rgba(60,80,224,0.10);
  outline: 1px solid rgba(60,80,224,0.45);
}

/* Bipolar bar — centred axis represents the weekly average. Bar grows
   up if the day is above avg, down if below. Heights are scaled to the
   max absolute deviation across the week so a 12% spread fills the
   visual range instead of looking flat. */
.dash-peak-weekday-bar-bipolar {
  width: 100%; height: 64px;
  display: flex; flex-direction: column;
  align-items: stretch;
  background: rgba(60,80,224,0.04);
  border-radius: 2px;
  overflow: hidden;
  position: relative;
}
.dash-peak-weekday-bar-bipolar .dpwb-up {
  flex: 0 0 auto;
  margin-top: auto;
  background: linear-gradient(to top, #10B981, #34d399);
  min-height: 0;
  transition: height 200ms ease;
}
.dash-peak-weekday-bar-bipolar .dpwb-down {
  flex: 0 0 auto;
  background: linear-gradient(to bottom, #FB5454, #ef4444);
  min-height: 0;
  transition: height 200ms ease;
}
.dash-peak-weekday-bar-bipolar .dpwb-axis {
  flex: 0 0 1px;
  background: var(--ink-faint);
  opacity: 0.5;
}
.dash-peak-weekday-cell.is-above .dash-peak-weekday-delta { color: var(--signal-gain); }
.dash-peak-weekday-cell.is-below .dash-peak-weekday-delta { color: var(--signal-loss); }
.dash-peak-weekday-cell.is-neutral .dash-peak-weekday-delta { color: var(--ink-faint); }

.dash-peak-weekday-val {
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-semibold);
  color: var(--ink);
}
.dash-peak-weekday-delta {
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-semibold);
  margin-top: 1px;
}
.dash-peak-weekday-lab {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  margin-top: 2px;
}

/* External-shard chip row, used as the toggle UI in the Hourly Pattern
   card. Each chip is color-keyed (--chip-color set inline) to its line
   on the chart. Off state dims; on state lights up the dot + text.
   The wrapper row keeps the "Compare other shards:" label flush left
   with the chips wrapping naturally on narrow viewports. */
.dash-shards-chips-row {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  margin: var(--space-sm) 0;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  font-size: var(--fs-xs);
}
.dash-shards-chips {
  display: flex; flex-wrap: wrap; gap: 6px;
}
.dash-shard-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
  font-size: var(--fs-xs);
  color: var(--ink-faint);
  transition: background 0.12s, color 0.12s, border-color 0.12s;
  user-select: none;
}
.dash-shard-chip:hover {
  background: var(--bg);
  border-color: var(--ink-faint);
}
.dash-shard-chip.on {
  color: var(--ink);
  border-color: var(--chip-color, var(--accent-mark));
  background: color-mix(in srgb, var(--chip-color, var(--accent-mark)) 12%, var(--bg-sunken));
}
.dash-shard-chip-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--chip-color, var(--ink-faint));
  opacity: 0.45;
  flex-shrink: 0;
}
.dash-shard-chip.on .dash-shard-chip-dot { opacity: 1; }
.dash-shard-chip-name { font-weight: var(--fw-semibold); }
.dash-shard-chip-count {
  font-variant-numeric: tabular-nums;
  font-size: 10px;
}

/* Currently-online panel — 4 metrics side by side, optional inline
   dual-box examples row underneath. */
.dash-live-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--space-md);
}
.dash-live-metric {
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--space-sm) var(--space-md);
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}
.dash-live-metric .label {
  font-size: 11px; color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--fw-semibold);
}
.dash-live-metric .value {
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  line-height: 1.1;
  margin-top: 2px;
}
.dash-live-metric .value.accent { color: var(--accent-mark); }
.dash-live-metric .sub {
  font-size: var(--fs-xs); color: var(--ink-faint);
  margin-top: 2px;
}
.dash-live-examples {
  grid-column: 1 / -1;
  margin-top: var(--space-sm);
  padding-top: var(--space-sm);
  border-top: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 6px;
}
.dash-live-examples-title {
  font-size: 11px; color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--fw-semibold);
  margin-bottom: 2px;
}
.dash-live-example {
  display: flex; gap: var(--space-sm); align-items: baseline;
  font-size: var(--fs-sm);
  flex-wrap: wrap;
}
.dash-grid-2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: var(--space-md);
  margin-bottom: var(--space-md);
}
.dash-grid-3 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-md);
  margin-bottom: var(--space-md);
}
/* Small-ish cards inside grids shouldn't stack double margin. */
.dash-grid-2 .card, .dash-grid-3 .card { margin-bottom: 0; }

/* Mini list rows inside dashboard cards (risk / rich / flags). */
.dash-list { display: flex; flex-direction: column; gap: 2px; }
.dash-list-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-sm);
  padding: 10px 12px; border-radius: var(--r-md);
  transition: background var(--dur-fast) var(--ease-out);
  min-height: 44px;
}
.dash-list-row:hover { background: var(--surface-hover); }
.dash-list-row .dash-list-main {
  display: flex; flex-direction: column; gap: 2px;
  min-width: 0; flex: 1;
}
.dash-list-row .dash-list-primary {
  font-size: var(--fs-sm); font-weight: var(--fw-medium);
  color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dash-list-row .dash-list-secondary {
  font-size: var(--fs-xs); color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.dash-list-row .dash-list-value {
  font-size: var(--fs-sm); font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}
.dash-list-empty {
  text-align: center; padding: var(--space-lg) var(--space-md);
  color: var(--ink-faint); font-size: var(--fs-sm);
}

/* Heatmap: 24 columns (hours) × 7 rows (days). Cells sized fluidly so
   the grid fills the card. Uses accent-soft at graduated alpha. */
.dash-heatmap {
  display: grid;
  grid-template-columns: 44px repeat(24, 1fr);
  gap: 3px;
  align-items: center;
  overflow-x: auto;
}
.dash-heatmap-label {
  font-size: 11px; color: var(--ink-faint);
  font-weight: var(--fw-medium);
  text-align: right; padding-right: 8px;
}
.dash-heatmap-label.hour { font-size: 10px; text-align: center; padding-right: 0; }
.dash-heatmap-cell {
  aspect-ratio: 1;
  min-width: 14px;
  border-radius: 2px;
  background: var(--bg-sunken);
  transition: transform var(--dur-fast) var(--ease-out),
              outline-color var(--dur-fast) var(--ease-out);
  cursor: default;
  outline: 1px solid transparent;
}
.dash-heatmap-cell:hover {
  transform: scale(1.2);
  outline-color: var(--ink);
  z-index: 1;
  position: relative;
}

/* Floating heatmap tooltip — appended once to <body>, positioned via JS. */
.dash-heatmap-tip {
  position: absolute;
  z-index: 9999;
  display: none;
  background: var(--surface-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  padding: 10px 12px;
  font-size: var(--fs-sm);
  color: var(--ink-dim);
  box-shadow: var(--shadow-lg);
  pointer-events: none;
  min-width: 140px;
}
.dash-heatmap-tip .tip-t {
  font-weight: var(--fw-semibold); color: var(--ink);
  font-size: var(--fs-sm); margin-bottom: 2px;
}
.dash-heatmap-tip .tip-s {
  font-size: var(--fs-xs); color: var(--ink-faint);
  margin-bottom: 6px;
}
.dash-heatmap-tip .tip-v {
  font-size: var(--fs-sm);
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}
.dash-heatmap-tip .tip-v strong { color: var(--accent-mark); font-weight: var(--fw-semibold); }

/* Security signal metric strip. Similar to stat tiles but tighter. */
.dash-security-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--space-md);
}
.dash-sec-tile {
  display: flex; flex-direction: column; gap: 4px;
  padding: var(--space-md);
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}
.dash-sec-tile .label {
  font-size: 11px; color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--fw-semibold);
}
.dash-sec-tile .value {
  font-size: var(--fs-lg); font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.dash-sec-tile.has-signal .value { color: var(--signal-warn); }

/* ── Info Pane ── */
.infopane {
  width: var(--infopane-w);
  background: var(--surface);
  border-left: 1px solid var(--line);
  flex-shrink: 0;
  display: none;
  flex-direction: column;
  font-size: var(--fs-xs);
  overflow-y: auto;
}
.infopane.open { display: flex; }
.infopane-header { padding: 14px 14px 10px; border-bottom: 1px solid var(--line); display: flex; justify-content: space-between; align-items: center; }
.ip-name { font-weight: var(--fw-bold); font-size: var(--fs-sm); }
.ip-close { background: none; border: none; color: var(--ink-dim); font-size: var(--fs-md); cursor: pointer; line-height: 1; }
.ip-body { padding: 12px 14px; overflow-y: auto; flex: 1; }
.ip-placeholder { text-align: center; padding: 40px 10px; font-size: var(--fs-xs); }
.ip-section { margin-bottom: 12px; }
.ip-section-title { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 1px; color: var(--ink-dim); margin-bottom: 4px; }
.ip-stat-row { display: flex; justify-content: space-between; padding: 2px 0; }
.ip-stat-row .label { color: var(--ink-dim); }
.ip-chars { display: flex; flex-wrap: wrap; gap: 3px; }

/* Stat tiles — TailAdmin-style metric cards. Self-contained cards with
   label above value. Wrap on narrow viewports via auto-fit grid. */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-md);
  margin-bottom: var(--space-lg);
}
.stat-card {
  display: flex; flex-direction: column;
  gap: var(--space-2xs);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--space-md) var(--space-lg);
  box-shadow: var(--shadow-sm);
}
.stat-card .label {
  font-size: 11px;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--fw-semibold);
}
.stat-card .value {
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  line-height: 1.15;
  min-width: 0;
  word-break: break-word;
  overflow-wrap: anywhere;
}
/* Strings that aren't a single big number (hostnames, IPs, OS strings)
   wrap at a smaller size — readability over big-typeface drama. */
.stat-card .value.text {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  letter-spacing: -0.01em;
}

/* Churn table — char list shown under the account name. Constrain so a
   30-char hoarder account doesn't blow row height. */
.churn-chars {
  font-size: var(--fs-xs);
  color: var(--ink-faint);
  margin-top: 2px;
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.stat-card .sub {
  font-size: var(--fs-xs);
  color: var(--ink-faint);
}
/* Signal-tinted text utility classes. Named for role (loss/gain/info/warn),
   not hue — keeps the markup semantically clean even if the palette shifts. */
.sig-gain { color: var(--signal-gain); }
.sig-loss { color: var(--signal-loss); }
.sig-warn { color: var(--signal-warn); }
.sig-info { color: var(--signal-info); }
.accent { color: var(--accent-mark); }

/* ── Filters ══════════════════════════════════════════════════════
   Filter bar sits inside or above a table card. Uses surface with a
   subtle border so it reads as "controls" rather than "decorated row". */
.filter-bar {
  display: flex; flex-wrap: wrap; gap: var(--space-xs); align-items: center;
  margin-bottom: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
}
.cb-label {
  font-size: var(--fs-sm); color: var(--ink-dim);
  display: inline-flex; align-items: center; gap: 6px;
  cursor: pointer; user-select: none;
}

input, select, textarea {
  background: var(--bg-sunken); border: 1px solid var(--line);
  border-radius: var(--r-md);
  color: var(--ink);
  padding: 10px 14px;
  font-size: var(--fs-sm);
  font-family: inherit;
  outline: none; min-width: 140px;
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
input::placeholder, textarea::placeholder { color: var(--ink-faint); }
input:focus, select:focus, textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
input[type="checkbox"], input[type="radio"] {
  min-width: 0; width: 16px; height: 16px;
  accent-color: var(--accent);
  cursor: pointer;
}

/* ── Buttons ═══════════════════════════════════════════════════════
   Primary (blue fill) / secondary (ghost with border). Consistent height,
   Inter medium weight, 8px radius. Matches TailAdmin's button system. */
.btn {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--accent); color: #fff; border: 1px solid var(--accent);
  border-radius: var(--r-md);
  padding: 10px 18px;
  font-size: var(--fs-sm); font-weight: var(--fw-medium);
  font-family: inherit;
  cursor: pointer;
  line-height: 1.25;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.btn:hover { background: var(--accent-strong); border-color: var(--accent-strong); }
.btn:focus-visible { box-shadow: 0 0 0 3px var(--accent-soft); outline: none; }
.btn:disabled { background: var(--surface-raised); border-color: var(--line); color: var(--ink-faint); cursor: not-allowed; }
.btn-secondary {
  background: transparent; color: var(--ink-dim);
  border-color: var(--line);
}
.btn-secondary:hover {
  background: var(--surface-hover); color: var(--ink);
  border-color: var(--line-strong);
}

/* ── Tables ═══════════════════════════════════════════════════════ */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
thead th {
  background: var(--bg-sunken);
  padding: 14px 18px;
  text-align: left;
  font-weight: var(--fw-semibold);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--ink-faint);
  position: sticky; top: 0;
  white-space: nowrap;
  border-bottom: 1px solid var(--line);
}
tbody td {
  padding: 14px 18px;
  border-top: 1px solid var(--line);
  white-space: nowrap;
  max-width: 360px;
  overflow: hidden; text-overflow: ellipsis;
  color: var(--ink-dim);
}
tbody tr { transition: background var(--dur-fast) var(--ease-out); }
tbody tr:hover { background: var(--surface-hover); }

/* ── Info cards — TailAdmin "data card" sibling of stat-card.
   stat-card is for one big number; info-card is for a section of dense
   key→value pairs. Same surface/border/shadow tokens, same auto-fit
   grid pattern, just a wider min-tile width because labels need room. */
.info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
  gap: var(--space-md);
  margin-bottom: var(--space-lg);
}
.info-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--space-md) var(--space-lg);
  box-shadow: var(--shadow-sm);
  min-width: 0;
}
.info-card.span-2 { grid-column: span 2; }
.info-card.span-3 { grid-column: span 3; }
.info-card .head {
  font-size: 11px;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--fw-semibold);
  margin: 0 0 var(--space-sm);
  padding-bottom: var(--space-2xs);
  border-bottom: 1px solid var(--line);
}
.info-card dl.kv {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 6px var(--space-md);
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.45;
}
.info-card dl.kv dt {
  color: var(--ink-faint);
  font-weight: var(--fw-normal);
  white-space: nowrap;
}
.info-card dl.kv dd {
  margin: 0;
  min-width: 0;
  color: var(--ink);
  word-break: break-word;
  overflow-wrap: anywhere;
}
.info-card dl.kv dd code {
  font-family: var(--font-mono, monospace);
  font-size: 0.92em;
  color: var(--ink-dim);
  word-break: break-all;
}
.info-card dl.kv dd.mono {
  font-family: var(--font-mono, monospace);
  word-break: break-all;
}
.info-card .meter {
  height: 6px;
  background: var(--bg-sunken);
  border-radius: 3px;
  overflow: hidden;
  margin: 6px 0 10px;
}
.info-card .meter > span { display: block; height: 100%; }

/* ── Telemetry tables: fixed layout, no horizontal scroll ─────────── */
.tel-table-wrap { overflow-x: hidden; }
table.tel-table {
  table-layout: fixed;
  width: 100%;
  max-width: 100%;
}
table.tel-table > thead th,
table.tel-table > tbody > tr.tel-row > td {
  white-space: normal;
  word-break: break-word;
  overflow-wrap: anywhere;
  max-width: none;
  overflow: hidden;
  text-overflow: clip;
  padding: 10px 12px;
  vertical-align: top;
}
/* Detail row: full reset so the rich detail pane can lay itself out
   without inheriting nowrap/max-width:360px/overflow:hidden from the
   global `tbody td` rule. The pane handles its own internal layout. */
table.tel-table > tbody > tr.tel-detail > td {
  white-space: normal !important;
  word-break: normal !important;
  max-width: none !important;
  overflow: visible !important;
  text-overflow: clip !important;
  padding: 4px 14px 14px !important;
  background: var(--bg-sunken);
}

th.sortable { cursor: pointer; user-select: none; }
th.sortable:hover { color: var(--accent-mark); }
th.th-sent { color: var(--signal-warn); }
th.th-recv { color: var(--signal-info); }
th.sortable::after { content: ' \2195'; opacity: .35; font-size: var(--fs-xs); }
th.sortable.asc::after  { content: ' \2191'; opacity: 1; color: var(--accent-mark); }
th.sortable.desc::after { content: ' \2193'; opacity: 1; color: var(--accent-mark); }

.filter-row td { padding: 4px 8px !important; border: none !important; background: var(--surface) !important; }
.col-filter {
  width: 100%; min-width: 60px; max-width: 200px;
  padding: 6px 8px; font-size: var(--fs-xs);
  background: var(--bg-sunken); border: 1px solid var(--line);
  border-radius: var(--r-sm); color: var(--ink);
  cursor: pointer;
}
.col-filter:focus { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }

/* ── Tags / chips ═══════════════════════════════════════════════ */
.tag, .chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 8px; border-radius: var(--r-pill);
  font-size: 11px; font-weight: var(--fw-medium);
  vertical-align: middle;
  line-height: 1.3;
}
.tag-loss, .chip.tag-loss { background: var(--signal-loss-bg); color: var(--signal-loss); }
.tag-warn, .chip.tag-warn { background: var(--signal-warn-bg); color: var(--signal-warn); }
.tag-gain, .chip.tag-gain { background: var(--signal-gain-bg); color: var(--signal-gain); }
.tag-info, .chip.tag-info { background: var(--signal-info-bg); color: var(--signal-info); }
.tag-purple, .chip.tag-purple { background: var(--accent-soft); color: var(--accent-mark); }
.tag-dim, .chip.tag-dim {
  background: rgba(255, 255, 255, 0.06);
  color: var(--ink-faint);
}
.chip {
  background: rgba(255, 255, 255, 0.04);
  color: var(--ink-dim);
  border: 1px solid var(--line);
}

.results-info { font-size: var(--fs-xs); color: var(--ink-faint); margin-bottom: var(--space-sm); }
.row-flagged { background: var(--signal-loss-bg) !important; }

/* Pages view — each .page submission renders as a standard .info-card
 * with the meta, status, question, and reply as kv rows. No bespoke
 * card shell or signal-tinted backgrounds; the existing primitives
 * carry all the layout. (Previous version invented .pg-card-* classes
 * that misused --signal-info-bg / --signal-gain-bg as background paint
 * for "question side" / "reply side" — semantic state tokens used for
 * mere layout intent. Removed in favour of composing .info-card.) */
.pg-list {
  display: flex; flex-direction: column;
  gap: var(--space-sm);
}
.pg-empty {
  padding: var(--space-lg);
  text-align: center;
  font-style: italic;
}
/* Preserve newlines + word-wrap inside dl.kv dd for the long-form
 * page text and reply. The dl.kv base style strips whitespace, which
 * collapses player-typed line breaks. */
.pg-text {
  white-space: pre-wrap;
  display: inline-block;
}

/* Kufur incident detail — chat transcript section. The card shell
 * comes from .info-card; this only styles the transcript-specific
 * pieces: scrollable container, line grid (role-chip + speaker + msg),
 * and the table-cell reset for the expanding row in the incident list.
 * No row-tinted backgrounds — role is carried by a compact .chip
 * before the speaker so the signal colors retain their semantic
 * meaning (state, not "who said it"). */
.kf-transcript {
  display: flex; flex-direction: column; gap: 2px;
  margin-top: var(--space-sm);
  padding: var(--space-sm); border-radius: var(--r-md);
  background: var(--bg-sunken);
  font-family: var(--font-mono); font-size: var(--fs-sm);
  max-height: 60vh; overflow-y: auto;
}
.kf-line {
  display: grid;
  grid-template-columns: max-content 220px minmax(0, 1fr);
  gap: var(--space-sm);
  align-items: baseline;
  padding: 4px 6px; border-radius: var(--r-sm);
}
.kf-line:hover { background: var(--surface-hover); }
.kf-speaker { color: var(--ink-faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.kf-msg     { white-space: pre-wrap; word-break: break-word; color: var(--ink-dim); }
tr.kf-row:hover { background: var(--surface-hover); }
tr.kf-detail > td { padding: 0; }

/* ── Trade / Vendor-Sale flow cards ── */
.sort-bar {
  display: flex; gap: 6px; align-items: center;
  margin: 0 0 var(--space-md);
  font-size: var(--fs-sm);
  color: var(--ink-faint);
}
.sort-pill {
  background: var(--surface); border: 1px solid var(--line);
  color: var(--ink-dim);
  border-radius: var(--r-pill);
  padding: 4px 12px; cursor: pointer;
  font-size: var(--fs-xs); font-weight: var(--fw-medium);
  font-family: inherit;
  line-height: 1.4;
  transition: color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.sort-pill:hover { color: var(--ink); border-color: var(--line-strong); background: var(--surface-hover); }
.sort-pill.active {
  background: var(--accent-soft); color: var(--accent-mark);
  border-color: var(--accent);
}

/* Trade/Vendor/Transfer feed — unified container */
.trade-feed { display: flex; flex-direction: column; gap: 2px; }

/* ── Directional transfer row ──────────────────────────────────
   Reads left→right like a sentence:
     [Date] [Giver] → [Items] [Taker] [Tags] [</>]
   Grid lays columns so dates + names align vertically across the whole feed,
   and the items column flex-grows to absorb slack. Each row is one line.   */
.xfer {
  display: grid;
  /* All columns are rigid minmax-bounded so names stay aligned regardless
     of how many flags or items land on an individual row. tags column is
     fixed-width so an extra flag doesn't shove the taker column around. */
  grid-template-columns:
    [date] 120px
    [giver] minmax(140px, 200px)
    [arrow] 20px
    [items] minmax(240px, 1fr)
    [taker] minmax(140px, 200px)
    [tags] 200px
    [raw] 34px;
  gap: 10px;
  align-items: center;
  padding: 6px 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  transition: border-color .1s;
}
.xfer:hover { border-color: color-mix(in oklch, var(--signal-info) 50%, transparent); }

.xfer-date {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-dim);
  white-space: nowrap;
}

/* Giver/taker are flex columns — use align-items (not text-align) so the
   char + acct lines pack to the correct side. text-align does nothing on
   flex-item positioning; this was the actual alignment bug. */
.xfer-giver, .xfer-taker {
  display: flex; flex-direction: column; gap: 1px;
  min-width: 0;
}
.xfer-giver { align-items: flex-end; text-align: right; }
.xfer-taker { align-items: flex-start; text-align: left; }
.xfer-char {
  font-weight: var(--fw-semibold);
  color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 100%;
}
.xfer-giver .xfer-char { color: var(--signal-info); }
.xfer-taker .xfer-char { color: var(--signal-gain); }
.xfer-acct {
  font-size: var(--fs-xs);
  color: var(--ink-dim);
  max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.xfer-arrow {
  color: var(--accent-mark);
  font-size: var(--fs-md);
  text-align: center;
  opacity: .75;
  user-select: none;
}
.xfer-items {
  display: flex; gap: 3px; flex-wrap: wrap;
  align-items: center;
  min-width: 0;  /* critical: let items wrap/shrink instead of overflowing */
}
.xfer-tags {
  display: flex; gap: 3px; flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  min-width: 0;
}
.xfer-raw { justify-self: center; }

/* ── Ranked leaderboard rows (Top Sellers / Top Buyers) ──
   Mirrors the transfer grammar: rank | who | amount | count.
   Columns align vertically so positions are easy to read at a glance. */
.rank-row {
  display: grid;
  grid-template-columns: [rank] 24px [who] 1fr [amt] auto [count] auto;
  align-items: center; gap: 10px;
  padding: 5px 10px;
  border-radius: var(--r-md);
  border: 1px solid transparent;
  font-size: var(--fs-sm);
}
.rank-row:hover {
  background: var(--surface-hover); border-color: var(--line);
}
.rank-num {
  font-family: var(--font-mono);
  font-size: var(--fs-xs); color: var(--ink-dim);
  text-align: right; user-select: none;
}
.rank-row:nth-child(1) .rank-num { color: var(--accent-mark); font-weight: var(--fw-bold); }
.rank-row:nth-child(2) .rank-num { color: var(--signal-info); }
.rank-row:nth-child(3) .rank-num { color: var(--signal-gain); }
.rank-who { font-weight: var(--fw-semibold); }
.rank-amt { font-size: var(--fs-xs); white-space: nowrap; }
.rank-count { justify-self: end; }

/* ── Stat strip (tight inline metric chips) ──
   Used above transaction feeds to show totals without a big stat-card block. */
.stat-strip {
  display: flex; gap: 6px; flex-wrap: wrap;
  margin-bottom: 8px;
}
.stat-chip {
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 4px 10px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
}
.stat-chip .stat-num { font-weight: var(--fw-bold); font-variant-numeric: tabular-nums; }
.stat-chip .stat-lbl { color: var(--ink-dim); font-size: var(--fs-xs); }

/* Pair view header: back button + interleaved title with arrow */
.pair-header {
  display: flex; align-items: center; gap: 12px; margin-bottom: 10px;
}
.pair-header h1 { font-size: var(--fs-md); }
.pair-header h1 .xfer-arrow { margin: 0 6px; }

/* Summary-style aside above revenue deposits (vendor tab). Flattened —
   was a bordered box wrapping its chips (nested-card anti-pattern). Now
   just a tight strip of chips with no outer container styling. */
.vs-chars-summary {
  margin-bottom: var(--space-sm);
  font-size: var(--fs-xs);
  color: var(--ink-dim);
}
.vs-chars-summary .chip {
  margin-right: 4px; padding: 2px 8px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-pill); font-size: var(--fs-xs);
}

/* Narrow screens: stack columns vertically. Giver label, then items, then
   taker. Keeps the directional reading but without horizontal scrolling. */
@media (max-width: 1100px) {
  .xfer {
    grid-template-columns: [date] 110px [main] 1fr [raw] auto;
    grid-template-areas:
      "date  main  raw"
      ".     tags  .";
    row-gap: 4px;
  }
  .xfer-date { grid-area: date; }
  .xfer-raw { grid-area: raw; }
  .xfer-tags { grid-area: tags; justify-content: flex-start; }
  .xfer-giver, .xfer-arrow, .xfer-items, .xfer-taker {
    grid-area: main;
    display: inline-flex; align-items: baseline; vertical-align: baseline;
  }
  .xfer-giver { text-align: left; }
}

/* Player-view two-level tab bar.
   Top row (pl-group-tabs): section nav — underline indicator style so it
   reads as primary navigation within the player card, not a second filter
   row. The hairline parent border doubles as the shared baseline.
   Bottom row (pl-sub-tabs): standard app pills — matches merged views. */
.pl-group-tabs {
  display: flex; gap: 0;
  margin: 0 0 var(--space-md);
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
.pl-group-tabs .sub-tab {
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  margin-bottom: -1px;
  padding: 10px 16px 11px;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--ink-faint);
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.pl-group-tabs .sub-tab:hover { color: var(--ink); background: transparent; }
.pl-group-tabs .sub-tab.active {
  color: var(--accent-mark);
  background: transparent;
  border-bottom-color: var(--accent);
  font-weight: var(--fw-semibold);
}
.pl-sub-tabs { margin: 0 0 var(--space-md); flex-wrap: wrap; }
.pl-sub-tabs:empty { display: none; margin: 0; }
.pl-sub-tabs .sub-tab .tab-count,
.pl-group-tabs .sub-tab .tab-count {
  margin-left: 4px; font-weight: var(--fw-normal); color: var(--ink-faint);
  font-size: var(--fs-xs); font-variant-numeric: tabular-nums;
}
.pl-sub-tabs .sub-tab.active .tab-count { color: rgba(255,255,255,0.75); }
.pl-group-tabs .sub-tab.active .tab-count { color: var(--accent-mark); opacity: .75; }
.pl-pane { animation: none; }  /* no fade — switching should be instant */

/* UID badges — inline small monospace id pills for full traceability */
.uid-badge {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-dim);
  background: color-mix(in oklch, var(--ink-dim) 8%, transparent);
  padding: 0 4px;
  border-radius: var(--r-sm);
  margin-left: 2px;
  vertical-align: middle;
  letter-spacing: .02em;
}
.uid-badge:hover { color: var(--accent-mark); background: var(--accent-soft); cursor: pointer; }

/* Raw-log trigger button — shows source log line(s) in a modal */
.raw-btn {
  background: transparent;
  border: 1px solid var(--line);
  color: var(--ink-dim);
  font-family: var(--font-mono);
  font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  padding: 1px 6px;
  border-radius: var(--r-sm);
  cursor: pointer;
  margin-left: 4px;
}
.raw-btn:hover { color: var(--signal-info); border-color: var(--signal-info); background: color-mix(in oklch, var(--signal-info) 10%, transparent); }

/* Raw log modal content — monospace with line numbers, highlights source line */
#modal-body.modal-body, .modal-body {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 70vh;
  overflow-y: auto;
}
.raw-line { display: flex; gap: 10px; padding: 1px 4px; }
/* Highlighted raw-log source line — full-width subtle bg tint replaces
   the banned 2px side stripe. The tint alone is enough to pull the eye
   when skimming; no decoration needed. */
.raw-line-hl { background: var(--accent-soft); border-radius: var(--r-sm); }
.raw-lineno { color: var(--ink-dim); min-width: 50px; text-align: right; user-select: none; }
.raw-text { flex: 1; color: var(--ink); }

/* ── Pagination ── */
.pagination {
  display: flex; gap: 6px; margin-top: var(--space-md);
  justify-content: center; flex-wrap: wrap;
}
.page-btn {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); color: var(--ink-dim);
  padding: 6px 12px; font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  cursor: pointer; font-family: inherit;
  transition: all var(--dur-fast) var(--ease-out);
}
.page-btn:hover { background: var(--surface-hover); color: var(--ink); border-color: var(--line-strong); }
.page-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.page-btn:disabled { opacity: .4; cursor: default; }

/* ── Player card ── */
.player-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: var(--space-lg);
  box-shadow: var(--shadow-sm);
}
.player-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 14px; flex-wrap: wrap; gap: 12px; }
.player-header h2 { font-size: var(--fs-md); }
.player-stats { display: flex; gap: 16px; flex-wrap: wrap; }
.player-stat { text-align: center; }
.player-stat .num { font-size: var(--fs-md); font-weight: var(--fw-bold); }
.player-stat .lbl { font-size: var(--fs-xs); color: var(--ink-dim); text-transform: uppercase; }
.player-details { display: flex; gap: 12px; margin-bottom: 14px; align-items: flex-start; flex-wrap: wrap; }
.player-details > .detail-section { flex: 1 1 320px; min-width: 0; }
.detail-section h3 { font-size: var(--fs-xs); color: var(--ink-dim); margin-bottom: 4px; text-transform: uppercase; letter-spacing: .5px; }
.pl-trade-stats { font-size: var(--fs-xs); color: var(--ink-dim); margin: -2px 0 8px; padding: 6px 10px; background: var(--surface-hover); border-radius: var(--r-md); }

/* ── Player header bar ─────────────────────────────────────── */
.pl-sticky {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  padding: var(--space-md) var(--space-lg);
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--space-md); flex-wrap: wrap;
}
.pl-sticky-main { display: flex; align-items: center; gap: var(--space-sm); flex-wrap: wrap; }
.pl-sticky-name {
  font-size: var(--fs-lg); font-weight: var(--fw-semibold);
  margin: 0; white-space: nowrap; color: var(--ink);
  letter-spacing: var(--tracking-tight);
}
.pl-sticky-chars {
  display: flex; gap: 4px; flex-wrap: wrap; align-items: center;
}
/* Collapsible disguise group. The toggle chip is muted/dashed to read as
   "more here if you want it" without competing with real character chips. */
.pl-sticky-chars .chip-toggle {
  cursor: pointer;
  background: transparent;
  border-style: dashed;
  color: var(--ink-faint);
  transition: color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.pl-sticky-chars .chip-toggle:hover {
  color: var(--ink-dim); border-color: var(--line-strong);
}
.pl-sticky-chars .chip-toggle.active {
  background: var(--surface-hover);
  color: var(--ink);
  border-style: solid;
}
.pl-disguise-chips {
  display: inline-flex; flex-wrap: wrap; gap: 4px;
  padding-left: 4px;
}
.pl-sticky-gold {
  display: flex; gap: var(--space-xs); flex-wrap: wrap;
}
.pl-gold-chip {
  display: flex; flex-direction: column; align-items: flex-start;
  padding: 6px 12px;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  min-width: 90px;
}
.pl-gold-chip .val {
  font-weight: var(--fw-semibold); font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.pl-gold-chip .lbl {
  font-size: 10px; color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--fw-medium);
}

/* The player-card (attached below pl-sticky) needs its top corners
   squared to share a seam with the sticky header. */
.pl-sticky + .player-card {
  border-top: none;
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

/* Guild memberships strip — character → guild mapping, always visible
   below the sticky header + above the analysis/tabs block. Compact rows
   so even a 5-guild account doesn't eat vertical real estate. */
.pl-guilds {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-sm);
  padding: 10px var(--space-lg);
  background: var(--bg-sunken);
  border-left: 1px solid var(--line);
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-sm);
}
.pl-guilds-label {
  font-size: 11px; color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--fw-semibold);
  margin-right: 4px;
}
.pl-guild-entry {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}
.pl-guild-char  { color: var(--ink); font-weight: var(--fw-semibold); }
.pl-guild-arrow { color: var(--ink-faint); }
.pl-guild-name  { color: var(--ink-dim); }
.pl-guild-abbrev {
  color: var(--ink-faint); font-family: var(--font-mono);
  font-size: var(--fs-xs);
}
.pl-guild-role {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--fw-bold);
  padding: 1px 6px;
  border-radius: var(--r-sm);
}
.pl-guild-role.master  { background: var(--signal-warn-bg); color: var(--signal-warn); }
.pl-guild-role.coowner { background: var(--signal-info-bg); color: var(--signal-info); }

/* If there's a guild strip between sticky and player-card, the card's top
   corners need to stay square (strip already has bottom border). */
.pl-guilds + .player-card {
  border-top: none;
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

/* ── Item tooltip ── */
.tag[title] { cursor: help; }
.item-bag {
  position: absolute; z-index: 400; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: 10px; max-width: 350px; box-shadow: var(--shadow-overlay);
  display: flex; flex-wrap: wrap; gap: 4px; line-height: 1.8;
}

.risk-badge { display: inline-block; padding: 2px 8px; border-radius: var(--r-pill); font-size: var(--fs-xs); font-weight: var(--fw-bold); min-width: 28px; text-align: center; }
.risk-high { background: var(--signal-loss-bg); color: var(--signal-loss); }
.risk-med  { background: var(--signal-warn-bg); color: var(--signal-warn); }
.risk-low  { background: var(--signal-gain-bg); color: var(--signal-gain); }

.items-hover { cursor: help; }
.items-hover[title] { border-bottom: 1px dashed var(--ink-dim); }
.chip.disguise { opacity: .35; font-style: italic; text-decoration: line-through; }
.acct-link { cursor: pointer; text-decoration: none; color: var(--signal-info); font-weight: var(--fw-medium); }
.acct-link:hover { color: var(--accent-mark); text-decoration: underline; }
.clickable { cursor: pointer; }
.clickable:hover { color: var(--accent-mark); }
.dim { color: var(--ink-dim); }

/* Driver analysis rollup chips + alert row highlighting */
.drv-rollup { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.drv-rollup-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border: 1px solid var(--line); border-radius: var(--r-pill);
  background: var(--surface); cursor: pointer; font-size: var(--fs-xs);
  transition: border-color .15s, background .15s;
}
.drv-rollup-chip:hover { border-color: var(--accent-mark); background: var(--accent-soft); }
.drv-rollup-chip strong { color: var(--ink); }
tr.row-alert { background: var(--signal-loss-bg); }
tr.row-alert:hover { background: color-mix(in oklch, var(--signal-loss) 16%, transparent); }

/* Autocomplete dropdown */
.autocomplete-dropdown {
  display: none; position: absolute; top: 100%; left: 0; right: 0; z-index: 300;
  background: var(--surface); border: 1px solid var(--line); border-top: none;
  border-radius: 0 0 var(--r-md) var(--r-md); max-height: 300px; overflow-y: auto;
  box-shadow: var(--shadow-overlay);
}
.autocomplete-dropdown.open { display: block; }
.ac-item {
  padding: 8px 12px; cursor: pointer; border-bottom: 1px solid var(--line);
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
}
.ac-item:hover, .ac-item.selected { background: var(--surface-hover); }
.ac-name { font-weight: var(--fw-semibold); }
.ac-chars { font-size: var(--fs-xs); color: var(--ink-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ac-type { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .5px; padding: 2px 5px; border-radius: var(--r-sm); flex-shrink: 0; }
.ac-type-acct { background: var(--signal-info-bg); color: var(--signal-info); }
.ac-type-char { background: var(--signal-gain-bg); color: var(--signal-gain); }
code { background: var(--bg-sunken); padding: 1px 4px; border-radius: var(--r-sm); font-size: var(--fs-xs); color: var(--ink-dim); }

/* ── Sub-tabs ── */
/* ── Sub-tabs — in-view pill nav ══════════════════════════════════
   Sits above a card's body (or inside a card's header). Rounded pills
   with soft hover; active = blue fill + white text. */
.sub-tabs {
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-bottom: var(--space-md);
}
.sub-tab {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  color: var(--ink-dim);
  padding: 9px 16px;
  font-size: var(--fs-sm); font-weight: var(--fw-medium);
  font-family: inherit;
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 8px;
  line-height: 1.25;
  transition: color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.sub-tab:hover { color: var(--ink); background: var(--surface-hover); border-color: var(--line-strong); }
.sub-tab.active {
  background: var(--accent); color: #fff;
  border-color: var(--accent);
}

/* ── Alert ── */
.alert-box {
  background: var(--signal-loss-bg);
  border: 1px solid color-mix(in oklch, var(--signal-loss) 40%, transparent);
  border-radius: var(--r-md);
  padding: var(--space-xs) var(--space-sm);
  margin-bottom: var(--space-sm);
  font-size: var(--fs-xs);
  line-height: var(--lh-normal);
}

/* ── Check chain ── */
.chain-cell { white-space: normal; max-width: 400px; line-height: 1.7; }
.chain-arrow { color: var(--accent-mark); font-weight: var(--fw-bold); padding: 0 2px; }

/* Check detail card */
.ck-detail-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
  padding: 18px; margin-bottom: 14px;
}
.ck-detail-card .ck-uid { font-family: monospace; font-size: var(--fs-sm); color: var(--accent-mark); }
.ck-detail-card .ck-amount { font-size: var(--fs-xl); font-weight: var(--fw-bold); color: var(--signal-warn); margin: 4px 0 12px; }
.ck-lifecycle { display: flex; align-items: stretch; gap: 0; margin: 12px 0; flex-wrap: wrap; }
/* Lifecycle step — flattened. Was a bordered box inside .ck-detail-card
   (nested-card anti-pattern). The .ck-arrow elements between steps
   provide visual separation; each step is just padded text now. */
.ck-step {
  flex: 1; min-width: 140px;
  padding: var(--space-sm) var(--space-md);
  display: flex; flex-direction: column; gap: var(--space-2xs);
  position: relative;
}
.ck-step-label { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 1px; color: var(--ink-dim); }
.ck-step-value { font-weight: var(--fw-semibold); }
.ck-step-time { font-size: var(--fs-xs); color: var(--ink-dim); }
.ck-arrow { display: flex; align-items: center; padding: 0 8px; font-size: var(--fs-lg); color: var(--accent-mark); font-weight: var(--fw-bold); }
.ck-flag { margin-top: 8px; }

/* ── Drill-down summary ── */
.dd-summary { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; padding: 12px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md); }
.dd-stat { text-align: center; min-width: 70px; }
.dd-stat .num { font-size: var(--fs-md); font-weight: var(--fw-bold); }
.dd-stat .lbl { font-size: var(--fs-xs); color: var(--ink-dim); text-transform: uppercase; }

/* ── Activity feed ── */
.feed-day { margin-bottom: 4px; }
.feed-day-header {
  font-size: var(--fs-xs); font-weight: var(--fw-bold); color: var(--accent-mark); padding: 10px 0 4px;
  border-bottom: 1px solid var(--line); margin-bottom: 2px; letter-spacing: .5px;
}
.feed-line {
  display: flex; align-items: baseline; gap: var(--space-xs);
  padding: 4px var(--space-xs); margin: 1px 0;
  font-size: var(--fs-sm); border-radius: var(--r-sm);
  flex-wrap: wrap; line-height: 1.55;
}
/* Signal label replaces the banned border-left stripe. Leading OUT/IN/PVP/
   OFFBOOK in mono reads as an evidence tag — works in monochrome printout,
   accessible to screen readers, not color-alone. */
.feed-line::before {
  flex: 0 0 56px;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-label);
  user-select: none;
}
.feed-line:hover { background: var(--surface-hover); }
.feed-ts { color: var(--ink-dim); font-size: var(--fs-xs); font-family: var(--font-mono); min-width: 52px; flex-shrink: 0; }
.feed-who { font-weight: var(--fw-medium); }
.feed-arrow-out { color: var(--signal-warn); font-size: var(--fs-md); font-weight: var(--fw-bold); }
.feed-arrow-in  { color: var(--signal-info); font-size: var(--fs-md); font-weight: var(--fw-bold); }
.feed-out          { background: var(--signal-warn-bg); }
.feed-out::before  { content: 'OUT'; color: var(--signal-warn); }
.feed-in           { background: var(--signal-info-bg); }
.feed-in::before   { content: 'IN';  color: var(--signal-info); }
.feed-pvp          { background: var(--signal-loss-bg); }
.feed-pvp::before  { content: 'PVP'; color: var(--signal-loss); }
/* Offbook is an OUT/IN variant — source-order-later wins at equal specificity,
   so OFFBOOK label overrides OUT/IN while the direction-tinted bg is preserved */
.feed-offbook::before { content: 'OFFBOOK'; color: var(--signal-loss); letter-spacing: 0.04em; }
.feed-icon { font-size: var(--fs-sm); }
.feed-bal {
  margin-left: auto; font-size: var(--fs-xs); font-weight: var(--fw-semibold); padding: 1px 6px;
  border-radius: var(--r-sm); background: var(--bg-sunken); flex-shrink: 0; font-family: monospace;
}
.feed-sent { color: var(--ink-dim); font-size: var(--fs-xs); }

/* ── Log panel ── */
.log-panel {
  position: absolute; top: 46px; right: 100px; z-index: 300;
  background: var(--surface); border: 1px solid var(--line); border-radius: 0 0 var(--r-md) var(--r-md);
  width: 340px; box-shadow: var(--shadow-overlay);
}
.log-panel-header { padding: 10px 14px; border-bottom: 1px solid var(--line); display: flex; justify-content: space-between; align-items: center; font-size: var(--fs-sm); }
.log-panel-body { max-height: 400px; overflow-y: auto; padding: 8px; }
.log-panel-footer { padding: 8px 14px; border-top: 1px solid var(--line); display: flex; gap: 6px; justify-content: flex-end; }
.log-upload-row { padding: 8px 14px; border-top: 1px solid var(--line); display: flex; gap: 10px; align-items: center; }
.log-upload-status { font-size: var(--fs-xs); color: var(--ink-dim); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.log-file-item { display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: var(--r-md); font-size: var(--fs-xs); cursor: pointer; }
.log-file-item:hover { background: var(--surface-hover); }
.log-file-item input { flex-shrink: 0; }
.log-file-date { font-weight: var(--fw-semibold); min-width: 85px; }
.log-file-name { color: var(--ink-dim); font-size: var(--fs-xs); flex: 1; }
.log-file-size { color: var(--ink-dim); font-size: var(--fs-xs); }
.log-file-loaded { color: var(--signal-gain); font-size: var(--fs-xs); }

/* ── Context menu ── */
.ctx-menu {
  display: none; position: fixed; z-index: 500;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
  min-width: 200px; padding: 4px 0;
  box-shadow: var(--shadow-overlay);
}
.ctx-menu.open { display: block; }
.ctx-item {
  padding: 7px 14px; font-size: var(--fs-xs); cursor: pointer; display: flex; align-items: center; gap: 8px;
}
.ctx-item:hover { background: var(--surface-hover); }
.ctx-item .ctx-icon { font-size: var(--fs-sm); width: 18px; text-align: center; flex-shrink: 0; }
.ctx-item .ctx-label { flex: 1; }
.ctx-item .ctx-hint { font-size: var(--fs-xs); color: var(--ink-dim); }
.ctx-sep { height: 1px; background: var(--line); margin: 4px 0; }

/* Raw-log drawer (shared #modal element, now a right-side panel instead
   of a centered overlay). Impeccable flagged modals as lazy; a drawer
   lets the user keep context on the main UI while reading the raw log
   that produced a given event. No backdrop dimming — the drawer is its
   own visual surface. The existing JS (click-#modal-to-dismiss) still
   works for clicks directly on the drawer wrapper. */
.modal-overlay {
  position: fixed;
  top: 0; right: 0;
  width: min(640px, 50vw);
  height: 100vh;
  background: var(--bg);
  z-index: 999;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-overlay);
  animation: drawer-slide-in var(--dur-base) var(--ease-out);
}
@keyframes drawer-slide-in {
  from { transform: translateX(100%); }
  to   { transform: translateX(0); }
}
.modal {
  flex: 1; width: 100%;
  background: transparent; border: none;
  border-left: 1px solid var(--line);
  border-radius: 0;
  max-width: none; max-height: none;
  overflow: hidden;
  display: flex; flex-direction: column;
}
.modal-header {
  display: flex; justify-content: space-between; align-items: center;
  padding: var(--space-sm) var(--space-md);
  border-bottom: 1px solid var(--line);
  background: var(--bg-sunken);
}
.modal-header h2 {
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  color: var(--ink-dim);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-family: var(--font-mono);
}
.modal-close {
  background: none; border: none; color: var(--ink-dim);
  font-size: var(--fs-lg); cursor: pointer;
  padding: 0 var(--space-xs);
  transition: color var(--dur-instant) var(--ease-out);
}
.modal-close:hover { color: var(--ink); }
.modal-body {
  padding: var(--space-md);
  overflow-y: auto;
  font-size: var(--fs-xs);
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-all;
  flex: 1;
}
.modal-body .hl {
  background: var(--accent-soft);
  display: block;
  margin: 0 calc(-1 * var(--space-md));
  padding: 0 var(--space-md);
}

/* ── Scrollbar ── */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line); border-radius: var(--r-sm); }
::-webkit-scrollbar-thumb:hover { background: var(--line-strong); }

/* Responsive — the app targets power users on laptops and desktops, so
   breakpoints prioritize keeping density readable rather than shrinking
   to phone scale. Order: widest -> narrowest so later rules override. */

/* ≤1100px: hide the persistent info pane (it consumes 280px and the user
   can still open it via the topbar toggle). Infopane content is never
   critical path. */
@media (max-width: 1100px) {
  .infopane { display: none !important; }
  .ip-toggle { display: none !important; }
}

/* ≤900px: tighten content padding and sidebar items. Sidebar stays
   persistent (no auto-hide) — collapse individual groups to manage space. */
@media (max-width: 900px) {
  .tb-item { padding: 5px 10px; font-size: var(--fs-xs); }
  #content { padding: var(--space-sm); }
  #sidenav { width: 180px; }
}

/* ≤768px (tablet): reduce topbar chrome and let filter bars stack
   cleanly. Modals become full-screen drawers. Stats strips wrap earlier. */
@media (max-width: 768px) {
  #topbar { padding: 0 var(--space-sm); height: 44px; }
  .tb-search { width: 180px; }
  .tb-search:focus { width: 220px; }
  .filter-bar {
    flex-direction: column; align-items: stretch;
    padding: var(--space-xs) var(--space-sm);
    gap: var(--space-xs);
  }
  .filter-bar input, .filter-bar select { width: 100%; min-width: 0; }
  .filter-bar > label, .filter-bar > div { width: 100%; }
  .stats-grid { gap: var(--space-xs) var(--space-md); }
  .sub-tabs { flex-wrap: wrap; }
  /* Drawer (raw-log + modals) fills the whole viewport — no room for
     a side panel that leaves content visible. */
  .modal-overlay {
    width: 100vw; max-width: none;
    border-left: none;
  }
  .modal { border-left: none; }
  /* On tablets the 12vh offset pushes the palette too low relative to
     the now-smaller viewport; tighten to ~6vh so it still sits comfortably
     in the upper half. */
  .palette { top: clamp(var(--space-md), 6vh, 48px); }
}

/* ≤600px: keep the logo visible but small; everything else inherits
   the 768px tablet layout. Phone is an edge case (investigators don't
   triage on phones) so we don't invest heavily here. */
@media (max-width: 600px) {
  .logo { font-size: var(--fs-sm); }
  #topbar .tb-right { gap: var(--space-2xs); }
  .tb-search { width: 140px; }
  .tb-search:focus { width: 160px; }
  /* Hide the Lifecycle backlink on phone — reachable via menu. */
  .tb-right a.ip-toggle { display: none; }
  .view h1 { font-size: var(--fs-md); }
  .section-title { font-size: var(--fs-sm); margin-top: var(--space-md); }
}

/* ── Ingest status banner ── */
.ingest-banner {
  position: sticky;
  top: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 18px;
  background: var(--surface);
  border-bottom: 1px solid var(--accent);
  font-size: var(--fs-sm);
  color: var(--ink);
}
.ingest-banner.is-error { border-bottom-color: var(--signal-loss); background: var(--signal-loss-bg); }
.ingest-banner.is-ready { border-bottom-color: var(--signal-gain); background: var(--signal-gain-bg); }
.ingest-spinner {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid color-mix(in oklch, var(--ink) 25%, transparent);
  border-top-color: var(--accent-mark);
  animation: ingest-spin 0.8s linear infinite;
  flex: 0 0 auto;
}
.ingest-banner.is-error .ingest-spinner,
.ingest-banner.is-ready .ingest-spinner { display: none; }
.ingest-elapsed { margin-left: auto; opacity: 0.65; font-variant-numeric: tabular-nums; }
@keyframes ingest-spin { to { transform: rotate(360deg); } }

/* Bank store grouped rows — parent row is clickable, children reveal on toggle */
.bs-table tr.bs-group { cursor: pointer; }
.bs-table tr.bs-group:hover { background: var(--surface-hover); }
.bs-table tr.bs-group.open { background: var(--accent-soft); }
.bs-table .bs-caret { display: inline-block; width: 1em; color: var(--ink-dim); font-size: 0.9em; }
.bs-table tr.bs-child td { border-top: 1px dashed color-mix(in oklch, var(--ink-dim) 30%, transparent); font-size: 0.92em; }

/* Glossary term — dotted underline cues hoverable help without shouting.
   CSS tooltip + the native title attribute (set by applyI18n) give a belt-
   and-suspenders a11y story: the title attr works everywhere, the styled
   tooltip looks correct when the cursor is on the term. Apply the class
   inline (e.g. on sub-tab buttons, filter options, column headers) together
   with data-term="<glossary-key>"; the i18n layer wires up the title. */
.glossary-term { cursor: help; position: relative; }
/* Dotted underline is the "hoverable help" cue — but only on inline-text
   usages. Buttons, pills, and nav items already have their own affordance
   (border + hover state), so adding a dotted underline would fight that. */
.glossary-term:not(button):not(a):not(.sub-tab):not(.tb-item):not([role="button"]) {
  text-decoration: underline dotted var(--ink-faint);
  text-underline-offset: 3px;
}
.glossary-term:hover,
.glossary-term:focus-visible {
  text-decoration-color: var(--accent-mark);
}
/* Styled tooltip bubble — only renders if data-term is set and the native
   title attr was populated. We render from the title attr (not data-term)
   so the bubble content matches the a11y-announced text exactly. */
.glossary-term[title]::after {
  content: attr(title);
  position: absolute;
  bottom: calc(100% + var(--space-2xs));
  left: 50%;
  transform: translateX(-50%);
  background: var(--surface-raised);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--space-xs) var(--space-sm);
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  font-weight: var(--fw-normal);
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
  line-height: 1.5;
  width: max-content;
  max-width: 320px;
  box-shadow: var(--shadow-overlay);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-out), visibility var(--dur-fast) var(--ease-out);
  z-index: 600;
}
.glossary-term[title]:hover::after,
.glossary-term[title]:focus-visible::after { opacity: 1; visibility: visible; }

/* Glossary view — the dedicated Help entry. Listed alphabetically, each
   term gets its full definition + a "seen in" hint so users can retrace
   to where they encountered it. */
.glossary-list {
  display: flex; flex-direction: column;
  gap: var(--space-lg);
  max-width: 72ch;  /* readable line length for definitions */
}
.glossary-entry { display: flex; flex-direction: column; gap: var(--space-2xs); }
.glossary-entry h2 {
  font-family: var(--font-serif);
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-tight);
}
.glossary-entry .gl-short {
  color: var(--ink-dim);
  font-size: var(--fs-sm);
  font-style: italic;
}
.glossary-entry .gl-full {
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
}
.glossary-jump {
  display: flex; flex-wrap: wrap; gap: var(--space-2xs);
  margin-bottom: var(--space-md);
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--line);
}
.glossary-jump a {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-dim);
  text-decoration: none;
  padding: 2px var(--space-xs);
  border-radius: var(--r-sm);
}
.glossary-jump a:hover { color: var(--accent-mark); background: var(--accent-soft); }

/* Error toast — bottom-right, non-modal. Dismisses automatically (see
   showError in app.js) but is click-dismissible via the X button. Uses
   signal-loss tinted background so it clearly reads as a failure state. */
.error-banner {
  position: fixed;
  right: var(--space-md);
  bottom: var(--space-md);
  left: auto;
  z-index: 1000;
  display: flex; align-items: flex-start; gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  min-width: 280px;
  max-width: 480px;
  background: var(--signal-loss-bg);
  border: 1px solid var(--signal-loss);
  border-radius: var(--r-md);
  color: var(--ink);
  font-size: var(--fs-xs);
  line-height: var(--lh-normal);
  box-shadow: var(--shadow-overlay);
  opacity: 0;
  transform: translateY(var(--space-sm));
  visibility: hidden;
  transition: opacity var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out),
              visibility var(--dur-base) var(--ease-out);
}
.error-banner.show { opacity: 1; transform: translateY(0); visibility: visible; }
.error-banner .error-msg {
  flex: 1; min-width: 0;
  font-family: var(--font-mono);
  word-break: break-word;
}
.error-banner .error-close {
  flex: 0 0 auto;
  background: none; border: none; color: var(--ink-dim);
  font-size: var(--fs-md); line-height: 1; cursor: pointer;
  padding: 0 var(--space-2xs);
}
.error-banner .error-close:hover { color: var(--signal-loss); }
/* Success variant — reuses the banner skeleton but tinted green.
   Triggered by showSuccess() / showToast(_, {variant:'success'}). */
.error-banner.is-success {
  background: var(--signal-gain-bg);
  border-color: var(--signal-gain);
}
.error-banner.is-success .error-close:hover { color: var(--signal-gain); }
/* .uid-badge click-to-copy: the hover rule above already covers visual;
   just ensure pointer cursor at rest too (was only set on :hover before,
   which was misleading — nothing signalled affordance until hover). */
.uid-badge { cursor: pointer; }

/* Command palette (Cmd/Ctrl+K). Native <dialog> handles Escape + focus
   trap. Positioned top-center with a dimmed backdrop. Option rows are
   role=option with data-selected for keyboard navigation. */
/* Anchored near the top instead of vertically centered — native <dialog>
   defaults to inset:0 + margin:auto (both axes centered), which put the
   palette mid-screen. Setting top to a specific value and bottom:auto
   pins it to the top third; left:0/right:0 + margin:auto keeps horizontal
   centering. */
/* Scope display:flex to [open] — without this, the rule overrode the UA
   default `dialog:not([open]) { display: none }` and the palette stayed
   visible all the time instead of only appearing on Cmd/Ctrl+K. */
.palette {
  width: min(560px, 92vw);
  max-height: 70vh;
  top: clamp(var(--space-lg), 12vh, 120px);
  bottom: auto;
  left: 0; right: 0;
  margin: 0 auto;
  padding: 0;
  background: var(--surface-raised);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-overlay);
  overflow: hidden;
  flex-direction: column;
}
.palette[open] { display: flex; }
.palette::backdrop {
  background: color-mix(in oklch, var(--bg), transparent 40%);
  backdrop-filter: blur(2px);
}
.palette-input {
  background: transparent;
  border: none; border-bottom: 1px solid var(--line);
  color: var(--ink);
  padding: var(--space-sm) var(--space-md);
  font-size: var(--fs-sm);
  font-family: var(--font-sans);
  outline: none;
  width: 100%;
}
.palette-input::placeholder { color: var(--ink-faint); }
.palette-list {
  list-style: none; margin: 0; padding: var(--space-2xs) 0;
  overflow-y: auto;
  flex: 1;
}
.palette-list li {
  display: flex; align-items: center; gap: var(--space-sm);
  padding: var(--space-xs) var(--space-md);
  font-size: var(--fs-sm);
  cursor: pointer;
  color: var(--ink);
}
.palette-list li .pal-group {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  min-width: 72px;
}
.palette-list li .pal-kbd {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-dim);
}
.palette-list li[aria-selected="true"] { background: var(--accent-soft); }
.palette-list li:hover { background: var(--surface-hover); }
.palette-list li.empty {
  color: var(--ink-faint); font-style: italic; cursor: default;
  justify-content: center;
}
.palette-footer {
  display: flex; gap: var(--space-md); justify-content: center;
  padding: var(--space-xs) var(--space-md);
  border-top: 1px solid var(--line);
  background: var(--bg-sunken);
  font-size: var(--fs-xs);
  color: var(--ink-faint);
}
.palette-footer kbd {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 0 var(--space-2xs);
  margin: 0 2px;
  color: var(--ink-dim);
}

/* Items / spawner copy button */
.copy-btn {
  background: var(--surface); color: var(--ink); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: var(--space-2xs) var(--space-sm);
  font-family: var(--font-mono); font-size: var(--fs-xs); cursor: pointer;
  transition: background 0.1s, border-color 0.1s;
}
.copy-btn:hover { background: var(--bg-sunken); border-color: var(--accent); }
.copy-btn:active { transform: translateY(1px); }

/* Topbar chips: in-flight indicator + freshness pill (Sys.3 loading UX) */
.topbar-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px; border-radius: 999px;
  font-size: var(--fs-xs); font-family: var(--font-mono);
  border: 1px solid var(--line); background: var(--surface); color: var(--ink-dim);
  white-space: nowrap; line-height: 1.5; cursor: default;
}
.topbar-chip-inflight { color: var(--ink); border-color: var(--accent); }
.topbar-chip-fresh { cursor: pointer; }
.topbar-chip-fresh:hover { border-color: var(--ink-dim); }
.fresh-live    { color: #10B981; border-color: #10B98166; background: #10B98115; }
.fresh-stale   { color: #FFA70B; border-color: #FFA70B66; background: #FFA70B15; }
.fresh-down    { color: #FB5454; border-color: #FB545466; background: #FB545415; }
.fresh-no-data { color: var(--ink-faint); }
.fresh-unreachable { color: #FB5454; border-color: #FB545466; }

/* Honeypot live-mode flash animation — new trips fade in highlighted */
@keyframes hp-flash {
  0%   { background: var(--signal-loss); }
  100% { background: transparent; }
}
.hp-live-flash { animation: hp-flash 1.2s ease-out; }

/* Watch view — tail-f style live event console */
.watch-console {
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  height: calc(100vh - 280px);
  min-height: 400px;
  overflow-y: auto;
  padding: var(--space-xs) var(--space-sm);
}
.watch-empty { padding: var(--space-md); text-align: center; }
.watch-line {
  display: grid;
  grid-template-columns: 110px 150px 1fr;
  gap: var(--space-sm);
  padding: 3px var(--space-2xs);
  border-bottom: 1px solid color-mix(in srgb, var(--line) 50%, transparent);
  white-space: nowrap;
  overflow: hidden;
  cursor: pointer;
}
.watch-line:hover { background: color-mix(in srgb, var(--surface) 50%, transparent); }
.watch-ts { color: var(--ink-faint); }
.watch-type {
  font-weight: 600; padding: 0 6px; border-radius: var(--r-sm);
  text-align: center; font-size: 10px; line-height: 18px;
}
.wt-neutral { background: color-mix(in srgb, var(--ink-faint) 25%, transparent); color: var(--ink-dim); }
.wt-session { background: color-mix(in srgb, #259AE6 25%, transparent); color: #7BC8F6; }
.wt-money   { background: color-mix(in srgb, #FFA70B 28%, transparent); color: #FFD27D; }
.wt-warn    { background: color-mix(in srgb, #FFA70B 50%, transparent); color: #FFE6B0; }
.wt-loss    { background: color-mix(in srgb, #FB5454 40%, transparent); color: #FFB3B3; }
.watch-summary { overflow: hidden; text-overflow: ellipsis; color: var(--ink-dim); }
.watch-summary strong { color: var(--ink); font-weight: 600; }
.watch-summary a { color: var(--accent); text-decoration: none; }
.watch-summary a:hover { text-decoration: underline; }
.watch-summary .tag { padding: 0 4px; border-radius: var(--r-sm); font-size: 10px; }
.tag-money { background: color-mix(in srgb, #FFA70B 30%, transparent); color: #FFD27D; }
.watch-raw-row {
  background: var(--bg-sunken);
  padding: var(--space-sm) var(--space-md);
  border-bottom: 1px solid var(--line);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  white-space: normal;
}
.watch-raw-meta { font-size: 11px; margin-bottom: 4px; color: var(--ink-faint); }
.watch-raw-line {
  margin: 0; padding: var(--space-xs);
  background: var(--surface); border-radius: var(--r-sm);
  white-space: pre-wrap; word-break: break-all;
  color: var(--ink);
}
.watch-raw-payload { margin-top: var(--space-xs); }
.watch-raw-payload summary { cursor: pointer; padding: 4px 0; }
.watch-raw-payload pre {
  margin: 4px 0 0 0; padding: var(--space-xs);
  background: var(--surface); border-radius: var(--r-sm);
  max-height: 240px; overflow: auto; white-space: pre-wrap;
}
.watch-raw-loading { padding: var(--space-xs); }

/* Raw Audit view — row coloring by parse status. The "missed" tint is
   the silent-drop signal an operator scans for; matched stays close to
   default so the eye is drawn to anomalies. */
.audit-row-missed       td { background: color-mix(in srgb, #FB5454 8%, transparent); }
.audit-row-partial      td { background: color-mix(in srgb, #FFA70B 6%, transparent); }
.audit-row-not_indexed  td { background: color-mix(in srgb, var(--ink-faint) 4%, transparent); }
.tag-pos { background: color-mix(in srgb, #10B981 30%, transparent); color: #10B981; }
.sig-pos { color: #10B981; }

/* Live status tree — IP→Account→Char nested rows. Multibox IPs (≥2
   accounts on one IP) get a left-border accent so they're spottable
   while scrolling. */
.live-tree { display: flex; flex-direction: column; gap: var(--space-md); }
.live-ip-block {
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--surface); padding: var(--space-sm) var(--space-md);
}
.live-ip-multibox { border-left: 3px solid #FFA70B; }
.live-row {
  display: flex; align-items: center; gap: 6px;
  padding: 3px 0; line-height: 1.5;
  font-size: var(--fs-sm);
  flex-wrap: wrap;
}
.live-row-ip { font-size: var(--fs-md); }
.live-row-acct { padding-left: 20px; }
.live-row-char { padding-left: 44px; }
.live-bullet { color: var(--ink-faint); font-family: var(--font-mono); width: 16px; flex-shrink: 0; }
.live-ipaddr { font-size: var(--fs-md); color: var(--ink); }
.live-acctname { color: var(--ink); }
.live-acctname a { color: inherit; text-decoration: none; border-bottom: 1px dotted var(--ink-faint); }
.live-acctname a:hover { border-bottom-color: var(--ink); color: var(--accent); }
.live-charname { color: var(--ink); margin-right: 4px; }
.live-meta { font-size: var(--fs-xs); color: var(--ink-faint); }
.live-row-ip .live-meta { margin-left: auto; }
.live-country {
  font-size: var(--fs-xs); padding: 1px 6px; border-radius: 999px;
  background: var(--bg-sunken); color: var(--ink-dim);
  font-family: var(--font-mono);
}
.live-chip {
  font-size: var(--fs-xs); padding: 0 6px; border-radius: 999px;
  background: var(--bg-sunken); color: var(--ink-dim);
  white-space: nowrap;
}
.live-chip-guild { background: color-mix(in srgb, var(--accent) 15%, var(--surface)); color: var(--accent); }
.live-chip-hp    { background: color-mix(in srgb, #FB5454 15%, transparent); color: #FB5454; }
.live-chip-ac    { background: color-mix(in srgb, #FFA70B 18%, transparent); color: #FFD27D; }
.live-chip-pos   { background: color-mix(in srgb, #10B981 12%, transparent); color: #10B981; }
.live-chip-pos-stale { background: color-mix(in srgb, #10B981 6%, transparent); color: color-mix(in srgb, #10B981 70%, var(--ink-faint)); }
.live-chip-act   { background: color-mix(in srgb, var(--ink) 8%, transparent); color: var(--ink); max-width: 360px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.audit-raw {
  margin: 0; padding: 4px 6px;
  background: var(--bg-sunken); border-radius: var(--r-sm);
  font-family: var(--font-mono); font-size: var(--fs-xs);
  white-space: pre-wrap; word-break: break-all;
  color: var(--ink);
  max-width: 800px;
}
@keyframes watch-flash-anim {
  0%   { background: color-mix(in srgb, #3C50E0 60%, transparent); }
  100% { background: transparent; }
}
.watch-flash { animation: watch-flash-anim 1.2s ease-out; }
