:root {
  color-scheme: light dark;
  --bg: #fff;
  --text: #111;
  --muted: #888;
  --border: #ddd;
  --border-subtle: #eee;
  --input-bg: #fff;
  --circle-bg: #f3f3f3;
}

/* System preference is the default signal (no explicit choice stored)... */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #16171d;
    --text: #e7e7ea;
    --muted: #9a9fae;
    --border: #33353f;
    --border-subtle: #2a2c35;
    --input-bg: #1e2027;
    --circle-bg: #22242c;
  }
}

/* ...but an explicit toggle choice (stamped as data-theme on <html>) always wins. */
:root[data-theme="dark"] {
  --bg: #16171d;
  --text: #e7e7ea;
  --muted: #9a9fae;
  --border: #33353f;
  --border-subtle: #2a2c35;
  --input-bg: #1e2027;
  --circle-bg: #22242c;
}
/* `color-scheme` controls native form-control rendering independent of
   the variables above — pin it to the explicit choice too, otherwise an
   explicit "Light" pick still renders dark-styled native controls on a
   dark-OS system (learned the hard way in notes/, see its style.css). */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }

* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; color: var(--text); background: var(--bg); }
.topnav {
  padding: 0.75rem 1.5rem;
  border-bottom: 1px solid var(--border-subtle);
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.topnav a { text-decoration: none; font-weight: 600; color: var(--text); }
.topnav-brand { display: flex; align-items: center; gap: 0.5rem; }
.nav-logo { height: 1.5rem; width: auto; display: block; }

.theme-toggle { display: flex; gap: 0.25rem; }
.theme-toggle button {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.2rem 0.45rem;
  font-size: 0.9rem;
  line-height: 1.4;
  color: var(--text);
  cursor: pointer;
}
.theme-toggle button.active {
  background: var(--border-subtle);
  border-color: var(--muted);
}

/* 914px = 2 * 425px .pair-row + 1rem gap (16px) + this container's own
   1.5rem padding on each side (48px) — sized to exactly fit two rows
   with zero leftover slack, tested directly in devtools alongside
   .pair-row's width. */
.container { max-width: 914px; margin: 0 auto; padding: 1.5rem; }
.container.narrow { max-width: 480px; }

.hint { color: var(--muted); font-size: 0.9rem; }
.error { color: #ef4444; }

.button {
  display: inline-block;
  padding: 0.4rem 0.8rem;
  background: #2563eb;
  color: #fff;
  border-radius: 4px;
  text-decoration: none;
  border: none;
  font-size: 0.9rem;
  cursor: pointer;
}
.button.secondary { background: #6b7280; }
.button.danger { background: #b91c1c; }

.badge {
  display: inline-block;
  font-size: 0.7rem;
  padding: 0.1rem 0.4rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--muted);
}

.empty { color: var(--muted); }

form { display: flex; flex-direction: column; gap: 0.75rem; }
label { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.9rem; }
input[type="text"], input[type="password"] {
  padding: 0.4rem;
  font-size: 1rem;
  background: var(--input-bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 4px;
}

/* --- Admin --- */

.list-header { display: flex; justify-content: space-between; align-items: center; gap: 1rem; }

.tracker-list { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 0.75rem; }
.tracker-list li { padding: 0.75rem; border: 1px solid var(--border-subtle); border-radius: 8px; }
.tracker-list-main { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
.tracker-list code { font-size: 0.85rem; }
.tracker-list .meta { color: var(--muted); font-size: 0.78rem; white-space: nowrap; }
.tracker-preview { margin: 0.4rem 0 0.6rem; }

/* --- Tracker page --- */

.tracker-header { display: flex; justify-content: space-between; align-items: center; gap: 1rem; margin-bottom: 0.25rem; }
.tracker-actions { display: flex; align-items: center; gap: 0.75rem; }

.pair-rows { display: flex; flex-wrap: wrap; gap: 1rem; margin-top: 1.5rem; }
.pair-row {
  /* One fixed width (425px), not a min/max range or a percentage
     calc() of the container — deliberately: calc(50% - ...) combined
     with a min-width floor is exactly what caused the original
     squeeze/overlap bug (percentage flex-basis vs. a fixed floor
     interacts inconsistently at in-between container widths), and once
     that was fixed, left unwanted trailing slack after the delete
     button (a wider max-width than the content actually needs,
     whenever the container was near its own max-width). Since
     .container itself is already capped at a fixed max-width (below),
     there's no real container-width range left where a percentage would
     behave any differently from a flat number anyway. flex-shrink: 0
     means this is a true fixed size — it never shrinks/grows regardless
     of how much room .pair-rows has, and .pair-rows' own flex-wrap
     cleanly drops a row to the next line the moment two of these plus
     their gap no longer fit, with no ambiguous in-between range at all.
     425px was directly tested in devtools, not derived from a formula —
     confirmed to leave the delete button evenly spaced between the
     result circle and the card edge. */
  flex: 0 0 425px;
  width: 425px;
  display: flex;
  /* flex-start, not center: each slot's circle is always its first
     element, but parent slots have gender/info controls below the
     circle while the result slot is just the bare circle. Centering by
     each box's own total height sinks the shorter result circle below
     the parents' — flex-start keeps every circle's top edge aligned. */
  align-items: flex-start;
  gap: 0.75rem;
  padding: 1rem;
  border: 1px solid var(--border-subtle);
  border-radius: 8px;
}
.op, .row-delete { align-self: center; flex-shrink: 0; }
.op { font-size: 1.25rem; color: var(--muted); }

.parent-slot { display: flex; flex-direction: column; align-items: center; gap: 0.35rem; flex-shrink: 0; }
.pal-label {
  /* Fixed height for exactly two lines, always reserved — regardless of
     whether the current name is empty, one line, or wraps to two —
     so picking a longer/shorter Pal never shifts the circle below it
     (or anything else in the row) up or down. */
  width: 90px;
  height: 2.4em;
  line-height: 1.2em;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.72rem;
  text-align: center;
  overflow: hidden;
  overflow-wrap: break-word;
  color: var(--text);
}
.parent-controls { display: flex; align-items: center; gap: 0.35rem; }

.pal-circle {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  border: 2px solid var(--border);
  background-color: var(--circle-bg);
  background-repeat: no-repeat;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  font-size: 1.5rem;
  padding: 0;
}
.pal-circle.empty { color: var(--muted); }
.pal-circle.small { width: 40px; height: 40px; font-size: 1rem; cursor: default; }
.pal-circle.result-circle { cursor: default; }

.pal-circle-wrap { position: relative; }
.pal-clear-btn {
  position: absolute;
  top: -4px;
  right: -4px;
  width: 18px;
  height: 18px;
  border: none;
  background-color: transparent;
  background-repeat: no-repeat;
  padding: 0;
  cursor: pointer;
  opacity: 0.85;
}
.pal-clear-btn:hover { opacity: 1; }

/* Bottom-left corner overlap, mirroring .pal-clear-btn's top-right one
   above — a small gender indicator on the circle itself, replacing the
   old full-width bar below it. Fill (solid for a parent's fixed gender,
   conic-gradient for the child's real probability) is set inline per
   instance in public/tracker.js, not via a fixed set of tier classes,
   since it's computed data rather than an enum. The border matches the
   page background so it reads as a clean cutout against the Pal sprite
   behind it, whatever color that happens to be. */
.gender-pie {
  position: absolute;
  bottom: -4px;
  left: -4px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1px solid var(--bg);
}

/* Read-only summary of a parent's assigned passives (set via the "i"
   panel) — same colored skill_rank-arrow mask as the picker's chips,
   just smaller, with the full name + description in the native title
   tooltip rather than shown as text (there's no room for it here). */
.parent-passive-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem;
  justify-content: center;
  align-content: flex-start;
  max-width: 90px;
  margin-top: 0.15rem;
  /* Reserves exactly 1 badge row up front (badges are a fixed 14px —
     see .parent-passive-badge below — so this is exact, not a rounded
     guess). All 4 possible badges actually fit on one row at this
     max-width (4*14px + 3 gaps ≈ 61px < 90px), so 1 row is enough — no
     need to reserve for a 2nd row that in practice never appears.
     Always rendered now (see buildParentSlot) so picking the first
     passive doesn't grow this row and shift the controls below it. */
  min-height: 14px;
}
/* Dark backdrop, light theme only, behind the whole row of badges — not
   per-badge — unlike .skill-chip-badge (in the picker), which already
   sits on its own dark background (.skill-chip's gradient), these
   badges sit directly on the page's own card background. The lighter
   tier colors (basic's near-white #e4fafe especially) nearly disappeared
   against a light theme's white page background without this.
   :not(:empty) — the container above is always rendered to reserve
   space even with zero passives set, but with none it's genuinely
   DOM-empty (the forEach loop appends nothing), so an unconditional
   background here showed as a stray dark bar under a parent with no
   passives assigned at all, not just a colorless reserved gap. Bare
   :root is this file's light-theme default (see the :root/@media block
   near the top), so this backdrop is visible by default and only turned
   off under the dark-theme conditions below. No padding here —
   .parent-passive-badges' 14px min-height already exactly matches a
   badge's own 14px height, so any padding would grow the container the
   moment the first badge appears (reintroducing the size-jump these
   badges were already fixed to avoid). */
.parent-passive-badges:not(:empty) {
  border-radius: 4px;
  background: rgba(17, 17, 17, 0.85);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .parent-passive-badges:not(:empty) { background: transparent; }
}
:root[data-theme="dark"] .parent-passive-badges:not(:empty) { background: transparent; }
/* Matches .skill-chip-badge's size (see below) — these used to be 18px,
   larger than the picker's own badge, which read as inconsistent once
   both were visible side by side. No border-radius here — the badge's
   own shape is entirely the masked icon glyph (see applyIconMask in
   tracker.js), not a box background, so rounding the box itself has no
   visible effect; .parent-passive-badges' own rounded-square backdrop
   above is what actually reads as "rounded square, not circle." */
.parent-passive-badge { width: 14px; height: 14px; flex-shrink: 0; cursor: default; }
/* Same colors as each tier's text (.skill-chip[data-tier]), not a
   separate palette — that's how the game itself colors these. */
.parent-passive-badge[data-tier="basic"] { background-color: #e4fafe; }
.parent-passive-badge[data-tier="mid"] { background-color: #ffd83f; }
.parent-passive-badge[data-tier="high"] { background-color: #68ffd8; }
/* Flipped vertically — the icon set only ships one (upward) arrow
   direction, no separate downward set, so negative ranks reuse the
   same arrow-count textures pointed the other way, matching how the
   game itself shows negative-rank passives (confirmed against a
   user-provided screenshot: downward red chevrons, not upward ones). */
.parent-passive-badge[data-tier="bad"] { background-color: #ff3e3f; transform: scaleY(-1); }

.result-slot { display: flex; flex-direction: column; align-items: center; gap: 0.35rem; flex-shrink: 0; }
/* Same reserved height as .parent-passive-badges (14px, always
   rendered) at the same position in the column — keeps the "Special
   combo" badge lined up with a parent's passive badges instead of the
   result column running shorter and the badge trailing below the
   circle. */
.result-badge-row { min-height: 14px; display: flex; align-items: center; }

.gender-toggle { display: flex; gap: 0.2rem; }
.gender-btn {
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
}
.gender-btn[data-gender="male"].active { background: #2563eb; color: #fff; border-color: #2563eb; }
.gender-btn[data-gender="female"].active { background: #db2777; color: #fff; border-color: #db2777; }

.info-btn {
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: 0.8rem;
  font-style: italic;
  line-height: 1;
}

.row-delete {
  /* No margin-left: auto (removed) — that pinned this button to the
     row's far-right edge by turning ALL of the row's leftover width
     (now that .pair-row has a reliable min-width and can legitimately
     be wider than its content in a 2-column layout) into one big margin
     immediately before the button, instead of that space being spent
     anywhere else. Sitting at its normal flex position — same 0.75rem
     gap as everything else in the row — keeps it right next to the
     result circle regardless of how much slack the row has. */
  width: 1.8rem;
  height: 1.8rem;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.row-delete:hover { color: #ef4444; border-color: #ef4444; }

/* --- Popovers (Pal picker, skill panel) and result tooltip --- */

.popover {
  position: absolute;
  z-index: 20;
  width: 320px;
  max-height: 400px;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.75rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
}
/* .popover's own `display: flex` has the same specificity as the
   browser's built-in `[hidden] { display: none }` rule, and an author
   rule always wins over that regardless of source order — so without
   this, the `hidden` attribute (used by tracker.js to open/close both
   popovers) is silently ignored and the popover never actually hides. */
.popover[hidden] { display: none; }

.pal-info-popover {
  position: absolute;
  /* Higher than .popover's z-index: 20, not just equal — this element
     comes earlier in the DOM than #skill-panel (see tracker.ejs), so at
     equal z-index the skill panel (later in DOM) would paint on top of
     it instead of the other way around. This hover popover should
     always be visible above whatever it's anchored to, regardless of
     DOM order. */
  z-index: 30;
  max-width: 220px;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.5rem;
  /* Always dark, not var(--bg) — same reasoning as .skill-chip's own
     permanently-dark background: a tier-colored name (showPassiveInfo
     sets it inline per entry, e.g. near-white #e4fafe for basic) needs
     a dark backdrop to read at all, and that has nothing to do with
     which theme the rest of the site happens to be in. Following the
     site's light/dark toggle made basic-tier names nearly invisible in
     light mode specifically. */
  background: #1a1a1a;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  pointer-events: none; /* hover-only — never intercepts the mouseleave that hides it */
}
.pal-info-popover[hidden] { display: none; }
/* Only the name is real visible text — Steam's overlay browser doesn't
   render native `title` tooltips, so this is the one piece of info
   that has to work there. Element/work-type icons stay icon + `title`
   only (reverted per feedback), not spelled out as visible text. */
/* Fixed light colors, not var(--text)/var(--muted) — those follow the
   site's theme toggle, which would go dark-on-dark against this
   popover's now-permanently-dark background in light mode. */
.pal-info-name { font-weight: 600; font-size: 0.85rem; color: #f0f0f0; }
/* Smaller than .pal-info-name and not bold — this is meta info (a
   Pal's types, on hover of its name label), not another name; showing
   it the same size as the name it's attached to read as too prominent. */
.pal-info-name.pal-info-type-text { font-size: 0.65rem; font-weight: 400; color: #9199a3; }
.pal-info-description { font-size: 0.75rem; color: #9199a3; white-space: pre-line; }
.pal-info-elements { display: flex; gap: 0.3rem; }
.pal-info-icon { width: 24px; height: 24px; flex-shrink: 0; }
.pal-info-work-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.3rem; }
.pal-info-work-item { display: flex; align-items: center; gap: 0.2rem; }
.pal-info-work-level { font-size: 0.75rem; color: #9199a3; }
/* Wider than the base .popover (320px, shared with the Pal picker) so
   longer passive names have more room before wrapping to a second line
   inside their chip. */
#skill-panel { width: 420px; }
.popover input[type="text"] {
  padding: 0.4rem;
  font-size: 0.9rem;
  background: var(--input-bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 4px;
  flex-shrink: 0;
}
.popover > .hint { flex-shrink: 0; }

.pal-picker-grid {
  overflow-y: auto;
  overflow-x: hidden;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.4rem;
}
.pal-grid-item {
  /* min-width: 0 overrides the flex/grid-item default of min-width:auto,
     which otherwise refuses to shrink an item below its unbroken text's
     intrinsic width — without it, one long/unwrapped name blows the
     column (and the whole grid) wider than the popover, forcing
     horizontal scroll instead of the text just wrapping. */
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
  padding: 0.4rem;
  border: none;
  background: transparent;
  color: var(--text);
  cursor: pointer;
  border-radius: 4px;
  font-size: 0.7rem;
  text-align: center;
}
.pal-grid-item span:last-child { overflow-wrap: break-word; word-break: break-word; max-width: 100%; }
.pal-grid-item:hover { background: var(--border-subtle); }

.skill-selected-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  /* align-content: start — without it, a grid's row tracks stretch by
     default to fill any leftover space in the container. With only 1
     row of chips (1-2 selected) and the min-height below reserving 2
     rows' worth of space, that single row (and the chips in it) was
     stretching to fill it, roughly doubling their height; a 2nd row
     (3-4 selected) split that same slack across both rows instead,
     landing near-correct by comparison. This keeps every row (and every
     chip) at its natural content height regardless of row count. */
  align-content: start;
  gap: 0.4rem;
  margin-bottom: 0.5rem;
  /* Reserves 2 rows' worth of height (not 1) at all times — even with
     0-2 chips selected — so going from 2 to 3 selected doesn't grow the
     panel either; computed from the chip's actual box (line-height
     0.875rem + 0.2rem padding + 0.125rem border ≈ 1.2rem per row, times
     2 rows plus the 0.4rem gap between them). */
  min-height: 2.8rem;
  /* .popover is a max-height flex column; without this, the flex
     algorithm shrinks this grid's box once it grows to 2 rows (3rd/4th
     chip selected), but the chips themselves still render at full size
     and spill out past the shrunk box, overlapping the search input
     that follows it. Only .skill-panel-list (already independently
     scrollable) should be allowed to shrink. */
  flex-shrink: 0;
}
.skill-selected-empty { grid-column: 1 / -1; color: var(--muted); font-size: 0.78rem; }

.skill-panel-list {
  overflow-y: auto;
  max-height: 220px;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.4rem;
}

/* Rarity-tier chip colors deliberately match the game's own fixed
   in-game colors rather than the site's light/dark theme vars — the
   game doesn't reskin these per-theme either, so a hardcoded dark
   backdrop + tier text color reads correctly regardless of site theme.
   Every chip shares the same dark base gradient (matching the game's
   own dark UI panel that every passive row sits on) — mid/high layer an
   extra tier-colored gradient on top of it; basic/bad don't, since
   neither has a real background tint in-game, just colored text. */
.skill-chip {
  display: flex;
  align-items: center;
  /* space-between, not the default flex-start — a short passive name
     otherwise leaves the badge sitting right next to the text instead
     of anchored to the chip's right edge (where it reads as "floating
     toward the middle" once the chip is wider than its content, which
     it usually is inside the 2-column grid). */
  justify-content: space-between;
  gap: 0.5rem;
  min-width: 0;
  border: 1px solid rgba(0, 0, 0, 0.15);
  border-radius: 6px;
  padding: 0.1rem 0.2rem;
  /* 0.7rem, not the ratio-matched ~0.85rem the in-game screenshot
     measurement originally suggested (see git history) — the longest
     real passive names ("Heart of the Immovable King", "Remarkable
     Craftsmanship") wrapped to 2 lines at that size. Checked by
     rendering every long name at increasing sizes against this chip's
     actual available text width (~164px, after the badge/gap/padding/
     border) — 0.7rem (11px) is what it takes for the single worst case
     to fit on one line with room to spare, fitting is prioritized over
     the exact in-game ratio here. */
  font-size: 0.7rem;
  line-height: 1.25;
  text-align: left;
  color: #e4fafe;
  background-image: linear-gradient(#11111188, #111111ff);
  cursor: pointer;
  overflow-wrap: break-word;
}
/* The real in-game skill_rank arrow icons (icons.png, 0-5 arrows),
   shown white/uncolored in the source art and colorized here per rank
   tier via a CSS mask — background-color shows through the icon's own
   shape (set via mask-image/mask-position/mask-size in tracker.js's
   applyIconMask), same idea as the game tinting them at runtime. */
.skill-chip-badge {
  /* Matches the real in-game icon-to-chip-width ratio: measured the
     "Reload Master" chevron badge directly off a screenshot at ~13x11px
     within a ~180px chip (~7% of chip width) — 32px here was roughly
     2x too large. ~195px per chip in our own layout puts the
     ratio-matched size at ~14px. */
  flex: 0 0 14px;
  width: 14px;
  height: 14px;
}
/* Same colors as each tier's text (.skill-chip[data-tier]), not a
   separate palette — that's how the game itself colors these. */
.skill-chip-badge[data-tier="basic"] { background-color: #e4fafe; }
.skill-chip-badge[data-tier="mid"] { background-color: #ffd83f; }
.skill-chip-badge[data-tier="high"] { background-color: #68ffd8; }
/* Flipped vertically — see .parent-passive-badge[data-tier="bad"]'s
   comment above, same reasoning applies here. */
.skill-chip-badge[data-tier="bad"] { background-color: #ff3e3f; transform: scaleY(-1); }
.skill-chip-name { min-width: 0; overflow-wrap: break-word; }
/* basic tier: no color overlay, just the shared dark base + default
   text color already set on .skill-chip above — confirmed by sampling
   two real rank-1 passive rows ("Hooligan", "Dragonkiller") directly
   from a user-provided in-game screenshot: both read as plain
   near-white text (~#e4fafe) with no background tint at all. */

/* mid tier: unlike high, no *horizontal two-tone* gradient — but it
   does have a real background tint (confirmed by sampling "Reload
   Master" at ~#37340f vs. a basic-tier row's neutral ~#1c2326 in the
   same screenshot), not just colored text. That composite works out
   almost exactly to the tier's own text color layered at ~15% opacity
   over the shared dark base, so it's expressed as one flat wash rather
   than a second distinct color like high tier's overlay. */
.skill-chip[data-tier="mid"] {
  background-image: linear-gradient(#ffd83f26, #ffd83f26), linear-gradient(#11111188, #111111ff);
  color: #ffd83f;
}
/* Gradient + text color for the top tier (rank 4-5), not a flat color —
   a subtle green-to-blue sheen layered over the shared dark base, plus
   a bright mint-green text color, both sourced from a wiki's own site
   CSS (not extracted from the game directly) as a stand-in for the real
   in-game look pending in-game confirmation. */
.skill-chip[data-tier="high"] {
  background-image: linear-gradient(to right, #59bb653d, #5958cc7a), linear-gradient(#11111188, #111111ff);
  color: #68ffd8;
}
/* bad tier: no background tint (confirmed directly by the user,
   matching a pixel-sampled "Brittle" row from an in-game screenshot —
   its background was just the plain dark panel, only the text/border/
   icon were red), text color sampled from that same row (~#ff3e3f). */
.skill-chip[data-tier="bad"] { color: #ff3e3f; }
.skill-chip.selected { outline: 2px solid #2563eb; outline-offset: 1px; }
.skill-chip.chip-disabled { opacity: 0.35; cursor: default; pointer-events: none; }
.skill-chip.chip-remove:hover { outline: 2px solid #b91c1c; outline-offset: 1px; }

.popover-actions { display: flex; gap: 0.5rem; justify-content: flex-end; flex-shrink: 0; }

/* Compact, read-only presentation for pinning as a Steam overlay while
   playing — see public/tracker.js's applyViewMode(). Toggled by a
   client-side-only preference (no server route), so everything here is
   driven off this one attribute rather than conditional server/JS
   render logic. */
#view-mode-bar {
  /* Fixed to the top of the viewport, not normal page flow — it used to
     sit after the rows specifically so a cropped/resized pinned window
     could just not show it, but since it already fades out after a few
     seconds of no mouse movement (see public/tracker.js's
     resetViewModeFade), it's only ever briefly in the way regardless of
     where it sits, and fixed-to-top means it's also never affected by
     rows reflowing/wrapping below it (or a scrollbar appearing) as the
     window is resized. Moving the mouse anywhere on the page brings it
     right back. */
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.5rem 0;
  transition: opacity 0.4s ease;
  opacity: 1;
}
#view-mode-bar.faded { opacity: 0; pointer-events: none; }
/* Same equal-specificity fix as .popover[hidden] above (a non-"none"
   base `display` on this element ties with the browser's built-in
   `[hidden] { display: none }` rule at the same specificity, and source
   order alone doesn't reliably decide the winner) — without this,
   toggling the `hidden` attribute would silently do nothing visually. */
#view-mode-bar[hidden] { display: none; }
/* One visually unified pill (shared background, no gap/rounding between
   the 3 segments) instead of 3 separate-looking buttons — a subtle
   inset shadow on each segment's left edge is the only thing hinting
   they're independently clickable. */
.zoom-group {
  display: flex;
  border-radius: 4px;
  overflow: hidden; /* clips each segment's own corners to the group's rounded ends */
  background: #6b7280;
}
.zoom-group button {
  background: none;
  border: none;
  color: #fff;
  font-size: 0.9rem;
  padding: 0.4rem 0.7rem;
  cursor: pointer;
}
.zoom-group button + button { box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.25); }
#zoom-level { font-size: 0.8rem; min-width: 3.5ch; text-align: center; }

html[data-view-mode="1"] .topnav,
html[data-view-mode="1"] .tracker-header,
html[data-view-mode="1"] .bookmark-hint,
html[data-view-mode="1"] .pal-label,
html[data-view-mode="1"] .pal-clear-btn,
html[data-view-mode="1"] .row-delete,
html[data-view-mode="1"] .gender-toggle,
html[data-view-mode="1"] .info-btn,
html[data-view-mode="1"] .parent-passive-badges,
/* .parent-controls only ever holds .gender-toggle/.info-btn (both
   already hidden above), so it'd otherwise sit as a pointless empty
   reserved-height gap in view mode — hiding it too actually shrinks
   each parent slot's height here, instead of just looking empty. */
html[data-view-mode="1"] .parent-controls,
/* Hidden alongside .parent-passive-badges, not just for its own sake —
   this is the *only* other thing reserving a badge-row's worth of
   height in the result column (see buildResultSlot), so leaving it
   visible while the parent side's badge row disappears would misalign
   the "Special combo" badge against the parent's (now-collapsed)
   passive-badge row. */
html[data-view-mode="1"] .result-badge-row {
  display: none;
}
html[data-view-mode="1"] .pal-circle:not(.result-circle) { cursor: default; }

/* "Hide results" (view mode only) — parents-only, no child/operators.
   Deliberately not persisted (see applyHideResults in tracker.js): a
   plain attribute, reset every time view mode is entered, not a second
   localStorage preference. */
html[data-view-mode="1"][data-hide-results="1"] .op,
html[data-view-mode="1"][data-hide-results="1"] .result-slot {
  display: none;
}

/* Compact spacing pass — first-pass best-effort, likely to need further
   tightening once actually seen in a real overlay window. */
html[data-view-mode="1"] .container { padding-top: 0.5rem; }
html[data-view-mode="1"] .pair-rows { gap: 0.5rem; margin-top: 0.5rem; }
html[data-view-mode="1"] .pair-row { gap: 0.4rem; }

/* Full page width, rows sized to their (now much smaller, label-less)
   content instead of forced into 50%-width columns — lets as many rows
   as fit lay out in one line, so zooming out in the overlay puts every
   tracked pair across a single row at the top of the screen instead of
   the normal 2-per-line editable layout. */
html[data-view-mode="1"] .container { max-width: none; }
/* Overrides the base rule's fixed width: 425px too (not just flex/
   max-width) — otherwise every row would stay pinned at that edit-mode
   size even with its (now much smaller, label-less) content, instead of
   shrinking to fit. */
html[data-view-mode="1"] .pair-row { flex: 0 0 auto; width: auto; }
