/* ---- fonts -------------------------------------------------------------
   Self-hosted IBM Plex, copied from the Google Fonts stylesheet this page
   used to link. Living here rather than in their own file keeps it to zero
   extra requests, and removes a render-blocking stylesheet on a third-party
   origin from in front of first paint.

   Only the latin and latin-ext subsets are vendored. Google also serves
   cyrillic and vietnamese; a browser only downloads a subset whose
   unicode-range matches text actually on screen, so dropping them costs
   nothing here and degrades to the system font if an uploaded league ever
   carries those scripts. Keep the unicode-range lines intact -- strip them
   and the browser loads every face instead of the one it needs.

   font-display: swap is carried over deliberately: text paints immediately
   in the fallback face rather than staying invisible while these load.
   ------------------------------------------------------------------------ */

/* latin-ext */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(./vendor/fonts/plex-mono-400-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(./vendor/fonts/plex-mono-400-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* IBM Plex Mono ships a 500 weight and it is deliberately not vendored here:
   nothing pairs --type-data with font-weight: 500. Every bold in this app
   routes to --type-label 600 instead (see .history-row__upset), so a mono 500
   face would never be requested. Re-add both subsets alongside the .woff2
   files if a --type-data rule ever wants it. */

/* latin-ext */
@font-face {
  font-family: 'IBM Plex Sans Condensed';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(./vendor/fonts/plex-sans-condensed-400-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'IBM Plex Sans Condensed';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(./vendor/fonts/plex-sans-condensed-400-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
  font-family: 'IBM Plex Sans Condensed';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(./vendor/fonts/plex-sans-condensed-500-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'IBM Plex Sans Condensed';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(./vendor/fonts/plex-sans-condensed-500-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* latin-ext */
@font-face {
  font-family: 'IBM Plex Sans Condensed';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(./vendor/fonts/plex-sans-condensed-600-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'IBM Plex Sans Condensed';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(./vendor/fonts/plex-sans-condensed-600-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---- tokens ------------------------------------------------------------ */

:root {
  /* Surfaces. Land sits lighter than the surround so the nation shape reads
     as a cut sheet laid on a table, the way a plate map does. */
  --sheet:    #E4E7E6;
  --land:     #F3F1EB;
  --hairline: #BCB7AB;   /* county borders */
  --rule:     #6C6B62;   /* state borders  */
  --ink:      #22201C;

  --leader:   #5C5A52;

  /* Hover/active surface a step brighter than --land. Its own token because
     it used to just be a hardcoded #fff, which stayed bright white and broke
     the picture in dark mode. */
  --surface-strong: #fff;

  --type-label: "IBM Plex Sans Condensed", "Helvetica Neue", sans-serif;
  --type-data:  "IBM Plex Mono", ui-monospace, monospace;

  --step: 8px;
}

/* Same relative-contrast relationships as the light set, inverted: land a
   touch lighter than sheet, rule/leader mid-contrast, ink at the extreme.
   Set via data-theme (written by the inline script in <head>, before this
   stylesheet paints) rather than a prefers-color-scheme media query alone,
   so an explicit toggle can override the OS setting either direction. */
:root[data-theme="dark"] {
  --sheet:    #1B1A17;
  --land:     #242220;
  --hairline: #3A3730;
  --rule:     #8B8778;
  --ink:      #EDEAE2;
  --leader:   #A9A493;
  --surface-strong: #2E2B26;
}

/* ---- shell ------------------------------------------------------------- */

* { box-sizing: border-box; }

/* The UA rule [hidden] { display: none } loses to any ID or class selector that
   sets display. #failure sets display: grid, so without this the panel is
   permanently on screen. Global so the next hidden element cannot repeat it. */
[hidden] { display: none !important; }

html, body {
  height: 100%;
  margin: 0;
  background: var(--sheet);
  color: var(--ink);
  font-family: var(--type-label);
  /* The page never scrolls. Badges and the tooltip are position: fixed, so a
     scrolling body would slide them off the map they are anchored to. */
  overflow: hidden;
}

body {
  display: grid;
  grid-template-columns: minmax(212px, 15%) 1fr;
  grid-template-areas: "panel map";
}

#map {
  grid-area: map;
  display: block;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  cursor: grab;
  touch-action: none;
}

#map:active { cursor: grabbing; }

/* ---- panel ------------------------------------------------------------- */

#panel {
  grid-area: panel;
  z-index: 20;              /* above the badge overlay, which spans the viewport */
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow-y: auto;
  padding: 20px 16px 16px;
  background: var(--sheet);
  border-right: 1px solid var(--rule);
}

/* theme toggle ------------------------------------------------------------ */

/* Fixed to the viewport, not the panel, so it stays in the browser window's
   upper-right corner regardless of the panel/map grid. z-index above
   #failure (40) so theme switching still works even if the map fails to
   load. #about-popup shares this z-index and comes later in the DOM, so it
   would cover this button on stacking order alone -- it gives up
   pointer-events to avoid that. Keep the two in sync. */
#theme-toggle {
  position: fixed;
  top: 14px;
  right: 14px;
  z-index: 45;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  font-size: 15px;
  line-height: 1;
  color: var(--rule);
  background: var(--land);
  border: 1px solid var(--rule);
  border-radius: 50%;
  cursor: pointer;
}

#theme-toggle:hover { background: var(--surface-strong); }
#theme-toggle:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

#theme-toggle[aria-pressed="true"] {
  color: var(--ink);
  background: var(--surface-strong);
  border-color: var(--ink);
}

/* panel modes -------------------------------------------------------------- */

/* Invisible to layout: children become direct flex items of #panel, exactly
   as if this wrapper weren't there. That means the existing margin-top-based
   spacing on .panel__block/.panel__head needs no changes, and the global
   [hidden] { display: none !important; } rule is sufficient on its own to
   hide a whole mode -- !important always wins over `contents`. */
.panel-mode { display: contents; }

.mode-nav {
  display: flex;
  padding: 3px;
  margin-bottom: 16px;
  background: var(--land);
  border: 1px solid var(--rule);
  border-radius: 999px;
}

/* Full stadium radius, deliberately distinct from the softer 12px corners
   every other panel button uses (.region-set, .panel-btn) -- this is the one
   control that governs everything else, not a peer among the sections. */
.mode-tab {
  flex: 1;
  padding: 6px 0;
  font-family: var(--type-label);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--rule);
  background: none;
  border: 0;
  border-radius: 999px;
  cursor: pointer;
}

.mode-tab:hover { color: var(--ink); }
.mode-tab:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

.mode-tab[aria-pressed="true"] {
  color: var(--ink);
  background: var(--surface-strong);
}

/* region set switcher -------------------------------------------------- */

.region-sets {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  margin-bottom: 18px;
  padding: 10px 0px;
  border-bottom: 1px solid var(--hairline);
  border-top: 1px solid var(--hairline);
}

.region-set {
  padding: 6px 8px;
  font-family: var(--type-label);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--rule);
  background: var(--land);
  border: 1px solid var(--rule);
  border-radius: 12px;
  cursor: pointer;
}

.region-set:hover { background: var(--surface-strong); }
.region-set:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

.region-set[aria-pressed="true"] {
  color: var(--ink);
  background: var(--surface-strong);
  border-color: var(--ink);
}

/* Uploaded sets share the same 3-column grid as the defaults, just with a
   dashed border so they read as distinct at a glance. */
.region-set--uploaded {
  border-style: dashed;
}

/* Forces the first uploaded button specifically onto column 1, which bumps
   it (and the uploaded group after it) to a fresh row if the last default
   button didn't already end one — uploads never share a row with defaults. */
.region-set--uploaded-first {
  grid-column-start: 1;
}

.panel__head h1 {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.18em;
  line-height: 1.4;
  text-transform: uppercase;
}

#tally {
  margin: 6px 0 0;
  font-family: var(--type-data);
  font-size: 10px;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  color: var(--rule);
}

#league-note {
  margin: 4px 0 0;
  font-family: var(--type-label);
  font-size: 11px;
  line-height: 1.4;
  color: var(--rule);
}

.panel__block {
  margin-top: 22px;
}

.panel__block-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin: 0 0 8px;
  padding-bottom: 5px;
  border-bottom: 1px solid var(--hairline);
}

.panel__label {
  margin: 0;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--rule);
}

.panel__toggle {
  flex-shrink: 0;
  padding: 0;
  font-family: var(--type-label);
  font-size: 6px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--rule);
  background: none;
  border: 0;
  cursor: pointer;
}

.panel__toggle:hover { color: var(--ink); }
.panel__toggle:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

.panel__block[data-collapsed="true"] .panel__block-body { display: none; }

.panel__empty {
  margin: 0;
  font-size: 11px;
  line-height: 1.55;
  color: var(--rule);
}

/* Full-width panel action, shared so the upload <label> and the download
   <button> read as the same kind of control rather than each carrying its own
   near-identical copy of these declarations. */
.panel-btn {
  display: block;
  width: 100%;
  margin-top: 10px;
  padding: 8px 12px;
  font-family: var(--type-label);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-align: center;
  color: var(--ink);
  background: var(--land);
  border: 1px solid var(--rule);
  border-radius: 12px;
  cursor: pointer;
}

.panel-btn:hover:not(:disabled) { background: var(--surface-strong); }
.panel-btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Undo/Redo share a row rather than each taking the full width .panel-btn
   normally claims -- the row carries the top margin so its children can drop
   theirs and split the space evenly. */
.btn-row {
  display: flex;
  gap: 8px;
  margin-top: 10px;
}

.btn-row .panel-btn {
  width: auto;
  flex: 1;
  margin-top: 0;
}

/* Muted, non-interactive look for when there's nothing to act on -- used by
   both Download League (before a set loads) and Reset view (at zoom 1). */
.panel-btn:disabled {
  color: var(--hairline);
  border-color: var(--hairline);
  background: none;
  cursor: default;
}

/* The upload control is a <label> wrapping its file input, so focus lands on
   the input inside it rather than on the label itself -- a <label> is not in
   the tab order. That makes :focus-within the only way to draw a focus ring
   on the visible control. */
.upload-btn { position: relative; }
.upload-btn:focus-within { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Hides an element visually while leaving it focusable and exposed to
   assistive tech. NOT interchangeable with the `hidden` attribute: the global
   [hidden] rule sets display: none, and a display: none element is removed
   from the tab order entirely. These file inputs used `hidden`, which is why
   Upload League and Conquest Replay's Load were unreachable by keyboard --
   tab skipped straight past them and :focus-within above could never fire.

   Positioned against .upload-btn (hence the position: relative) rather than
   the viewport, so focusing it doesn't yank the scrollable panel to the top. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* random league ---------------------------------------------------------- */

/* The app's only text input. Monospaced because a seed is data you read back
   and retype, and dashes/word boundaries stay legible in mono. */
.seed-input {
  display: block;
  width: 100%;
  padding: 7px 9px;
  font-family: var(--type-data);
  font-size: 11px;
  color: var(--ink);
  background: var(--land);
  border: 1px solid var(--rule);
  border-radius: 12px;
}

.seed-input::placeholder { color: var(--hairline); }
.seed-input:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Dice stays square, Generate takes the rest of the row. */
.seed-actions {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 6px;
  margin-top: 8px;
}

.seed-roll,
.seed-generate {
  padding: 7px 12px;
  font-family: var(--type-label);
  font-weight: 600;
  color: var(--ink);
  background: var(--land);
  border: 1px solid var(--rule);
  border-radius: 12px;
  cursor: pointer;
}

.seed-roll:hover,
.seed-generate:hover { background: var(--surface-strong); }

.seed-roll:focus-visible,
.seed-generate:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Unicode die glyph rather than an SVG, matching the theme toggle's sun/moon. */
.seed-roll {
  width: 34px;
  padding: 0;
  font-size: 17px;
  line-height: 1;
}

.seed-generate {
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* Generation is synchronous but not instant at 40 teams -- buildRegions()
   runs a full Dijkstra pass -- so the button reports being busy. */
.seed-generate[aria-busy="true"] {
  color: var(--rule);
  cursor: progress;
}

/* The one panel__block-head with no label -- see index.html's comment. This
   overrides the shared space-between so the lone toggle still ends up on the
   right, matching every other section, instead of the left. */
#secrets-head {
  justify-content: flex-end;
}

.secret-row {
  display: flex;
  gap: 8px;
}

/* Same visual language as .seed-roll / .region-set: --land fill, --rule
   border, 12px radius, brightening to --surface-strong on hover. flex: 1
   spreads the four buttons evenly across the row. */
.secret-btn {
  flex: 1;
  padding: 10px 0;
  font-size: 20px;
  line-height: 3;
  color: var(--rule);
  background: var(--land);
  border: 1px solid var(--rule);
  border-radius: 12px;
  cursor: pointer;
}

.secret-btn:hover { background: var(--surface-strong); color: var(--ink); }
.secret-btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* region list ------------------------------------------------------------ */

.regions {
  margin: 0;
  padding: 0;
  list-style: none;
}

.region-row {
  display: grid;
  grid-template-columns: 10px 1fr;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 4px 4px;
  border: 0;
  border-left: 3px solid transparent;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.region-row:hover:not(:disabled) { background: var(--land); }

.region-row[aria-pressed="true"] {
  background: var(--land);
  border-left-color: var(--swatch);
}

.region-row:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: -2px;
}

/* An eliminated team's row in the Teams list: still shown (the roster is
   fixed for the whole war) but no longer zoomable, so it reads like the
   disabled .panel-btn state elsewhere in this panel. pointer-events: none
   is what actually stops the click/hover handlers from firing -- the
   `disabled` attribute alone blocks click and focus but not pointerenter. */
.region-row:disabled {
  color: var(--hairline);
  border-left-color: transparent;
  cursor: default;
  pointer-events: none;
}

.region-row__swatch {
  width: 10px;
  height: 10px;
  background: var(--swatch);
}

.region-row__name {
  font-size: 9px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Conquest Count rows carry a third column. The Teams list stays at two so it
   keeps no trailing gap where a count would have gone. */
.region-row--count { grid-template-columns: 10px 1fr auto; }

/* Mono with tabular-nums, this file's voice for a figure you read back
   (#tally, #tip-meta) -- and it keeps the column from shifting as counts reach
   two digits. No 600 weight: Plex Mono only ships 400/500. */
.region-row__count {
  font-family: var(--type-data);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  color: var(--rule);
}

/* Conquest History lines. Mono because the abbreviated default ("ATL def. CHI")
   is a data readout and fixed width makes a column of them line up; the full-text
   setting wraps in the same face rather than switching fonts mid-log. */
.history-row {
  padding: 3px 4px;
  font-family: var(--type-data);
  font-size: 10px;
  line-height: 1.55;
}

/* Genuinely bold rather than synthetic: --type-data (Plex Mono, the rest of
   this row inherits it) only ships 400/500, so font-weight here would just be
   faked by the browser. Switching this one fragment to --type-label picks up
   its real 600 cut instead. */
.history-row__upset {
  font-family: var(--type-label);
  font-weight: 600;
  letter-spacing: 0.04em;
}

/* Zebra striping so a long log stays scannable row to row. --land rather than
   a new token: it's already this file's "surface a touch off --sheet" color
   (panel rows on hover, the panel itself against the map), so a stripe reads
   as consistent with the rest of the chrome instead of introducing a new hue. */
.history-row:nth-child(even) {
  background: var(--land);
}

/* options ---------------------------------------------------------------- */

/* Breathing room where a full-width action is followed by checkboxes, as in
   Map Controls: without it the button's border butts against the first row. */
.panel-btn + .opt { margin-top: 7px; }

.opt {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 4px;
  font-size: 11px;
  letter-spacing: 0.04em;
  cursor: pointer;
}

.opt input {
  width: 13px;
  height: 13px;
  margin: 0;
  accent-color: var(--ink);
  cursor: pointer;
}

/* No other <select> exists in this app to match -- built from the same
   bordered-field language as .seed-input/.panel-btn. Sits directly below its
   checkbox, disabled state mirroring .panel-btn:disabled. */
.rank-weight {
  display: block;
  width: 100%;
  margin: 4px 0 2px;
  padding: 6px 8px;
  font-family: var(--type-label);
  font-size: 11px;
  color: var(--ink);
  background: var(--land);
  border: 1px solid var(--rule);
  border-radius: 12px;
  cursor: pointer;
}

.rank-weight:disabled {
  color: var(--hairline);
  border-color: var(--hairline);
  background: none;
  cursor: default;
}

.rank-weight:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* layer toggles ---------------------------------------------------------- */

body.off-county-borders .county-border,
body.off-state-borders .state-border,
body.off-badges #badges { display: none; }

/* ---- map layers -------------------------------------------------------- */

/* Only ~6 + N paths total. County and state boundaries are single mesh paths
   rather than 3,143 individual shapes, which keeps the DOM trivial and zoom
   on the compositor. Add a per-county layer here if you ever need hover. */

.land {
  fill: var(--land);
}

.region {
  stroke: none;
  opacity: 0.9;
  transition: opacity 180ms ease;
}

/* Dim the field when one region is picked out. */
#regions[data-active] .region     { opacity: 0.22; }
#regions[data-active] .region--on { opacity: 0.95; }

.county-border,
.state-border,
.nation-border {
  fill: none;
  stroke-linejoin: round;
  stroke-linecap: round;
  /* Constant-weight hairlines at every zoom level. Drop this and swap to
     stroke-width: N/k on the zoom handler if you want borders to thin out
     and reveal detail as you push in instead. */
  vector-effect: non-scaling-stroke;
}

.county-border { stroke: var(--hairline); stroke-width: 0.4; }
.state-border  { stroke: var(--rule);     stroke-width: 0.9; }
.nation-border { stroke: var(--ink);      stroke-width: 0.3; }

/* County hit targets. Never painted, so they cost nothing to rasterize and
   the merged region fills below stay seam-free. pointer-events: all makes an
   unfilled path hit-testable, which the default visiblePainted would not. */
.county {
  fill: none;
  stroke: none;
  pointer-events: all;
  cursor: pointer;
}

/* Dragging the map beats hovering a county. */
#map:active .county { cursor: grabbing; }

/* ---- tooltip ----------------------------------------------------------- */

#tip {
  position: fixed;
  z-index: 30;
  /* Without these the box keeps its static position, which sits below a
     100vh svg, and the translate below pushes it further off screen. */
  left: 0;
  top: 0;
  max-width: 30ch;
  padding: 6px 9px 7px;
  background: var(--land);
  border: 1px solid var(--rule);
  border-bottom: 3px solid var(--tip-accent, var(--rule));
  pointer-events: none;
  will-change: transform;
}

#tip-name {
  display: block;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
}

#tip-meta {
  display: block;
  margin-top: 2px;
  font-family: var(--type-data);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  color: var(--rule);
}

/* ---- badges ------------------------------------------------------------ */

/* Fixed layer in client pixel space. Anchors are projected out of viewBox
   units through getScreenCTM(), so badges hold their size at any zoom. */
#badges {
  position: fixed;
  inset: 0;
  pointer-events: none;
}

/* Each badge is a zero-size anchor point at the region centroid; the plate
   centers itself on it. Nothing scales with zoom. */
.badge {
  position: fixed;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  pointer-events: none;
  will-change: transform;
}

.badge__label {
  position: absolute;
  left: 0;
  top: 0;
  padding: 5px 10px 6px;
  background: color-mix(in srgb, var(--land) 93%, transparent);
  border: 1px solid var(--leader);
  border-radius: 12px;
  font-size: 8px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: auto;
  cursor: pointer;
  transform: translate(-50%, -50%);
  transition: background-color 160ms ease, transform 160ms ease;
}

.badge__label:hover,
.badge:focus-visible .badge__label {
  background: var(--land);
  transform: translate(-50%, -50%) scale(1.06);
  outline: none;
}

.badge:focus-visible .badge__label {
  box-shadow: 0 0 0 2px var(--sheet), 0 0 0 4px var(--ink);
}

/* ---- chrome ------------------------------------------------------------ */

/* Reset view carries no styles of its own: it's a full-width panel action, so
   it uses .panel-btn like the Upload/Download buttons. The #reset id is still
   the JS hook (wireZoom's click handler, onZoom's enable/disable). */

/* Leads the Map Controls section, so the spacing goes below it. */
#hint {
  margin: 0 0 2px;
  font-size: 10px;
  line-height: 1.5;
  letter-spacing: 0.04em;
  color: var(--rule);
}

#failure {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 6px;
  padding: 24px;
  text-align: center;
  background: var(--sheet);
}

.failure__head {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.failure__body {
  max-width: 56ch;
  margin: 0;
  font-family: var(--type-data);
  font-size: 12px;
  line-height: 1.6;
  color: var(--ink);
  text-align: left;
}

.failure__body--quiet {
  font-family: var(--type-label);
  font-size: 11px;
  color: var(--rule);
  text-align: center;
}

#failure code {
  font-family: var(--type-data);
  font-size: 12px;
}

/* ---- war --------------------------------------------------------------- */

/* Centered on the map, not on the viewport. Sharing #map's grid area is what
   makes that free: at the 860px breakpoint the areas flip from "panel map" to
   "map" / "panel" and the card follows, with no offsets to maintain. z-index
   applies to grid items without position, and beats #badges (fixed, z-index
   auto) as well as #tip (30). No scrim and no shadow -- nothing in this app
   dims the map, it sits on it, the way #tip and #failure do. */
#war-popup {
  grid-area: map;
  place-self: center;
  z-index: 35;
  position: relative;   /* anchors .war-close only */

  /* Fixed width rather than shrink-to-fit: consecutive battles swap through
     team names of wildly different lengths, and an auto-width card would
     jitter with every battle -- and relayout the grid it sits in. */
  width: min(360px, 86%);
  display: grid;
  gap: 10px;
  justify-items: center;
  padding: 18px 20px 16px;
  text-align: center;
  background: var(--land);
  border: 1px solid var(--rule);
  border-radius: 12px;
}

/* Unicode glyph rather than an SVG, and the icon-button box #theme-toggle
   establishes. Borderless because it sits inside a bordered card. */
.war-close {
  position: absolute;
  top: 8px;
  right: 8px;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 0;
  font-family: var(--type-label);
  font-size: 15px;
  line-height: 1;
  color: var(--rule);
  background: none;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
}

.war-close:hover { color: var(--ink); background: var(--surface-strong); }
.war-close:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Same "no scrim" convention as #war-popup and #tip -- centered on the whole
   viewport rather than the map's grid area, since About isn't about a
   battle and the button that opens it lives on the Build side. Above
   #failure (40) so it would still win in the (very unlikely) case both were
   ever shown at once. */
#about-popup {
  position: fixed;
  inset: 0;
  z-index: 45;
  display: grid;
  place-items: center;
  padding: 24px;
  /* This element paints nothing -- it is inset: 0 purely to center the card.
     Without this it is an invisible sheet over the whole viewport that eats
     every click: it shares z-index 45 with #theme-toggle and comes later in
     the DOM, so it was covering the one control that is supposed to work no
     matter what (see the #theme-toggle comment above), plus the entire panel
     and map underneath. There is no backdrop-to-dismiss behavior to preserve
     -- wireAbout() closes on the X and on Escape only -- so letting clicks
     pass straight through costs nothing. The card re-enables them for itself. */
  pointer-events: none;
}

.about-card {
  pointer-events: auto;
  position: relative;   /* anchors .about-close only */
  width: min(420px, 100%);
  max-height: 80vh;
  overflow-y: auto;
  padding: 20px 22px;
  background: var(--land);
  border: 1px solid var(--rule);
  border-radius: 12px;
}

.about-card__head {
  margin: 0 0 10px;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.about-card__body p {
  margin: 0 0 10px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--ink);
}

.about-card__body p:last-child { margin-bottom: 0; }

/* ---- secret reveal ------------------------------------------------------ */

/* Same grid-item placement as #war-popup rather than #about-popup's
   fixed inset: 0. That matters here: this card is its own dismiss target, and
   a full-viewport wrapper would have to give up pointer-events and hand them
   back to the child, which is a trap to re-derive every time. Occupying only
   its own box means it can just be clickable.

   z-index above #war-popup (35) rather than equal to it: a war can still be
   running with its card up when you switch to Build and press a suit, and for
   its two seconds this should win rather than depend on DOM order. Still below
   #failure (40) -- a dead map outranks an Easter egg. */
#secret-popup {
  grid-area: map;
  place-self: center;
  z-index: 38;
  cursor: pointer;
  transition: opacity 180ms ease;
}

/* Driven by data-visible rather than a class so the two states are greppable
   next to the JS that sets them. The element is also `hidden` once the fade
   finishes -- opacity alone would leave an invisible click target centered on
   the map. */
#secret-popup[data-visible="false"] { opacity: 0; }

.secret-card {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin: 0;
  max-width: min(320px, 86%);
  padding: 14px 20px;
  font-family: var(--type-label);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-align: center;
  color: var(--ink);
  background: var(--land);
  border: 1px solid var(--rule);
  border-radius: 12px;
}

/* Deliberately the same 20px / var(--rule) as .secret-btn, on the same
   var(--land) background, so the glyph reads as the button that was pressed
   rather than as decoration. flex: none keeps it at full size when a longer
   message wraps beside it. */
.secret-card__suit {
  flex: none;
  font-size: 20px;
  line-height: 1;
  letter-spacing: 0;
  color: var(--rule);
}

.war-popup__kicker {
  margin: 0;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--rule);
}

.war-bout {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 10px;
  width: 100%;
}

.war-team {
  display: grid;
  grid-template-columns: 10px 1fr;
  align-items: center;
  gap: 7px;
  min-width: 0;
}

/* The left slot mirrors the right rather than repeating it: the swatch trails
   the name and the text runs right-aligned, so the two names read as
   converging toward the "vs" in the middle. Column order follows DOM order,
   which is why war-team-a's markup lists its label before its swatch while
   war-team-b's does the opposite -- this rule doesn't reorder anything, it
   only resizes the columns to fit a trailing 10px swatch instead of a
   leading one. */
#war-team-a {
  grid-template-columns: 1fr 10px;
}

#war-team-a .war-team__label {
  text-align: right;
}

/* Same 10px chip as .region-row__swatch, off the same --swatch property, so a
   team reads identically here and in the Teams list. Earns its keep when two
   teams share a name. */
.war-team__swatch {
  width: 10px;
  height: 10px;
  background: var(--swatch);
}

.war-team__label {
  display: grid;
  gap: 1px;
  text-align: left;
}

.war-team__city,
.war-team__nickname {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  overflow-wrap: anywhere;
}

/* A name with no space in it (nothing to split into a city) has no city line
   to show. Collapsing it rather than leaving an empty row is what keeps a
   one-word name from showing a blank line above the nickname. */
.war-team__city:empty { display: none; }

/* The trailing side of whichever way the flicker currently leans is muted;
   the leading side is left at its default color, no override needed. Both
   the flicker and the final reveal always set one slot to "loss", so this one
   rule covers both. */
.war-team[data-outcome="loss"] .war-team__city,
.war-team[data-outcome="loss"] .war-team__nickname { color: var(--rule); }

.war-vs {
  font-family: var(--type-data);
  font-size: 10px;
  color: var(--rule);
}

/* One team left, so the whole bout row (both slots, the swatch, "vs") goes
   away rather than just war-team-b -- .war-result already states the
   champion's name at the card's one headline size, and the bout's smaller
   city/nickname lines underneath it would only repeat that. */
#war-popup[data-state="champion"] .war-bout { display: none; }

/* The .failure__head treatment -- the one headline size this app has. The
   min-height reserves the line so the card cannot jump when a result lands. */
.war-result {
  min-height: 1.4em;
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.war-detail {
  min-height: 1.5em;
  margin: 0;
  font-family: var(--type-data);
  font-size: 10px;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  color: var(--rule);
}

/* #war-stop carries no styles of its own: it's a full-width action, so it uses
   .panel-btn like Reset view and the Upload/Download buttons. */

/* ---- responsive + motion ----------------------------------------------- */

@media (max-width: 860px) {
  body {
    grid-template-columns: 1fr;
    /* Map takes what is left; the panel is capped and scrolls inside itself so
       the body still never scrolls. */
    grid-template-rows: minmax(0, 1fr) auto;
    grid-template-areas: "map" "panel";
  }

  #panel {
    max-height: 46vh;
    padding: 14px 16px 16px;
    border-right: 0;
    border-top: 1px solid var(--rule);
  }

  .panel__block { margin-top: 16px; }

  .badge__label { padding: 4px 8px 5px; font-size: 11px; letter-spacing: 0.1em; }
  #hint { display: none; }

  /* The map row is short here, so the card gives some of its padding back. */
  #war-popup { padding: 14px 16px 12px; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 1ms !important; }
}
