/* République styles — extracted verbatim from republique.html (lines 53-1302). */

/* ─── App Shell Layout ──────────────────────────────────── */

/* Full-viewport flex column — locked to viewport so children scroll independently */
.app-shell {
  display: flex;
  flex-direction: column;
  height: 100%;
}

/* ── Fixed top header (mobile only; hidden on tablet+ via media query) ── */
.app-header {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--rep-line);
  background: linear-gradient(180deg, #2a2e36 0%, #1c1f24 100%);
  position: sticky;
  top: 0;
  z-index: 100;
}

.header-top-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.header-brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex: 1;
  min-width: 0;
}

.header-brand .crest {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  object-fit: cover;
}

.header-title {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.header-app-name {
  color: var(--rep-cream);
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.header-sub {
  color: var(--pico-muted-color);
  font-size: 0.75rem;
  letter-spacing: 0.03em;
}

/* Mobile section-picker dropdown — inline beside title */
.nav-dropdown {
  appearance: none;
  -webkit-appearance: none;
  background: var(--rep-control-bg);
  color: var(--rep-control-text);
  border: 1px solid var(--rep-line);
  padding: 0.35rem 0.55rem;
  border-radius: 6px;
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  max-width: 10rem;
  flex: 0 1 auto;
  min-width: 0;
}

/* Intro toggle button (in header) */
.intro-btn {
  appearance: none;
  -webkit-appearance: none;
  background: var(--rep-control-bg);
  color: var(--rep-cream);
  border: 1px solid var(--rep-line);
  padding: 0.45rem 0.8rem;
  border-radius: 6px;
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  letter-spacing: 0.03em;
}

.intro-btn:hover {
  border-color: var(--rep-amber-soft);
  color: var(--rep-amber);
}

.intro-btn.active {
  background: var(--rep-amber);
  color: var(--rep-on-amber);
  border-color: var(--rep-amber);
}

.intro-btn .chev {
  margin-left: auto;
  transition: transform 0.18s ease;
  opacity: 0.7;
}

/* Intro button in section panel header (desktop) */
.section-intro-btn {
  margin-left: 0.5rem;
  margin-right: 0.5rem;
}

.intro-btn.active .chev {
  transform: rotate(180deg);
}

/* ── Body: sidebar + main content ─────────────────────── */
.app-body {
  display: flex;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

/* ── Sidebar navigation ────────────────────────────────── */
.sidebar-nav {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  width: 200px;
  flex: 0 0 auto;
  align-self: stretch;
  border-right: 1px solid var(--rep-line);
  background: #1a1d23;
  overflow-y: auto;
  padding: 0.25rem 0;
  --nav-link-spacing-vertical: 0;
  row-gap: 0;
}

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.8rem 0.5rem;
  border-bottom: 1px solid var(--rep-line);
  margin-bottom: 0.2rem;
}

.sidebar-crest {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  object-fit: cover;
}

.sidebar-app-name {
  color: var(--rep-cream);
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Nav items */
.nav-item {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.55rem 1rem;
  color: var(--rep-cream);
  text-decoration: none;
  font-size: 0.85rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  border-left: 3px solid transparent;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
  cursor: pointer;
}

.nav-item:hover {
  background: rgba(245, 180, 0, 0.08);
  color: var(--rep-amber);
}

.nav-item.active {
  background: rgba(245, 180, 0, 0.12);
  color: var(--rep-amber);
  border-left-color: var(--rep-amber);
  font-weight: 600;
}

.nav-item i {
  font-size: 1.05rem;
  flex: 0 0 auto;
  width: 1.25rem;
  text-align: center;
}

/* Nav separators (Intro → Resume Game / Edit Game, and above Game Setup) */
.sidebar-nav hr.nav-sep {
  border: none;
  border-top: 1px solid var(--rep-line);
  margin: 0.3rem 0.6rem 0.15rem;
}
.sidebar-nav-compact hr.nav-sep { margin: 0.25rem 0.35rem 0.15rem; }

/* ── Main content area ─────────────────────────────────── */
.main-content {
  flex: 1;
  min-width: 0;
  padding: 1.25rem 1.5rem 2rem;
}

/* ── Desktop status bar — sticky at top of viewport ────── */
.status-bar {
  display: none;
}

@media (min-width: 768px) {
  .status-bar {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.4rem 1.25rem;
    background: var(--rep-paper-2);
    border-bottom: 1px solid var(--rep-line);
    font-size: 0.85rem;
    color: var(--rep-cream);
    letter-spacing: 0.02em;
    position: sticky;
    top: 0;
    z-index: 50;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
  }
}

.status-turn {
  color: var(--rep-amber);
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}

.status-time {
  color: var(--rep-cream);
  font-weight: 700;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.9rem;
  background: #14171c;
  padding: 0.15rem 0.45rem;
  border-radius: 4px;
}

.status-phase {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  font-weight: 600;
  color: var(--rep-amber);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: 0.8rem;
}

.status-player {
  margin-left: auto;
  color: var(--pico-muted-color);
}

/* ── Turn / time / player inline in section header (mobile only) ── */
.header-turn,
.header-time,
.header-player {
  display: none;
}

@media (max-width: 767px) {
  .header-turn {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    color: var(--rep-amber);
    font-weight: 600;
    font-size: 0.72rem;
    white-space: nowrap;
  }

  .header-time {
    display: inline;
    color: var(--rep-cream);
    font-weight: 700;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.78rem;
    background: #14171c;
    padding: 0.1rem 0.35rem;
    border-radius: 4px;
    white-space: nowrap;
  }

  .header-player {
    display: inline;
    margin-left: auto;
    color: var(--pico-muted-color);
    font-size: 0.72rem;
    white-space: nowrap;
  }
}

/* ── Intro card ────────────────────────────────────────── */
.intro-card {
  background: var(--rep-paper-2);
  border: 1px solid var(--rep-line);
  border-radius: var(--pico-border-radius);
  padding: 1.25rem 1.5rem;
  margin-bottom: 1.25rem;
  color: var(--rep-cream);
}

.intro-card h2 {
  color: var(--rep-amber);
  font-size: 1.3rem;
  margin: 0 0 0.75rem;
  letter-spacing: 0.03em;
}

.intro-card h3 {
  color: var(--rep-cream);
  font-size: 1.05rem;
  margin: 1rem 0 0.4rem;
  letter-spacing: 0.02em;
}

.intro-card h4 {
  color: var(--rep-amber);
  font-size: 0.95rem;
  margin: 0.8rem 0 0.3rem;
}

/* ── Section panel (replaces accordion <details>) ──────── */
.section-panel {
  background: var(--rep-paper-2);
  border: 1px solid var(--rep-line);
  border-radius: var(--pico-border-radius);
  overflow: hidden;
}

.section-panel-header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 0.7rem;
  padding: 0.65rem 0.85rem;
  background: linear-gradient(180deg, #262a32, #1f232a);
  border-bottom: 1px solid var(--rep-line);
}

.section-panel-icon {
  color: var(--rep-amber);
  font-size: 1.25rem;
  flex: 0 0 auto;
}

.section-panel-title {
  margin: 0;
  color: var(--rep-cream);
  font-size: 1.1rem;
  font-weight: 600;
  letter-spacing: 0.03em;
}

.section-panel-body {
  padding: 1rem 1.1rem 1.25rem;
}

/* Desktop-only prev/next navigation footer */
.section-panel-footer {
  display: block;
  border-top: 1px solid var(--rep-line);
  padding: 0.5rem 0.75rem;
  background: linear-gradient(180deg, #1f232a, #1a1d23);
  overflow: hidden; /* clearfix for floated prev/next buttons */
}

.phase-nav-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  text-decoration: none;
  color: var(--rep-cream);
  font-size: 0.85rem;
  cursor: pointer;
  transition: color 0.12s;
}

.phase-nav-btn:hover {
  color: var(--rep-amber);
}

.phase-nav-prev {
  float: left;
}

.phase-nav-next {
  float: right;
}

.phase-nav-label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--pico-muted-color);
}

.phase-nav-title {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.phase-nav-chev {
  font-size: 1.1rem;
  opacity: 0.6;
}

/* ── Light mode overrides for new layout components ─────── */
:root[data-theme="light"] .app-header {
  background: linear-gradient(180deg, #fffdf3 0%, #f5efd8 100%);
}

:root[data-theme="light"] .sidebar-nav {
  background: #f5efd8;
}

:root[data-theme="light"] .section-panel-header {
  background: linear-gradient(180deg, #f8f1d8, #f0e7c5);
}

:root[data-theme="light"] .nav-item.active {
  background: rgba(245, 180, 0, 0.15);
}

:root[data-theme="light"] .status-time {
  background: #e8deb8;
  color: #2a2419;
}

:root[data-theme="light"] .header-time {
  background: #e8deb8;
  color: #2a2419;
}

:root[data-theme="light"] .section-panel-footer {
  background: linear-gradient(180deg, #f0e7c5, #e8deb8);
}

/* ── Responsive ────────────────────────────────────────── */

/* Mobile: hide sidebar, show dropdown in header; single-column */
@media (max-width: 767px) {
  .sidebar-nav {
    display: none;
  }

  .app-body {
    flex-direction: column;
  }

  .main-content {
    padding: 0.75rem 0.85rem 1.5rem;
  }

  .intro-card {
    padding: 1rem;
  }

  .section-panel-body {
    padding: 0.8rem 0.8rem 1rem;
  }
}

/* Tablet+: show sidebar, hide mobile header, show next-phase footer */
@media (min-width: 768px) {
  .app-header {
    display: none;
  }

  .nav-dropdown {
    display: none;
  }
}

/* Force dark scheme on every UA — overrides user system preference.
   Lock viewport height so the app shell constrains its children. */
:root,
html,
body {
  color-scheme: dark;
  height: 100%;
}

/* Bridge mount point — must fill body so .app-shell's height:100% resolves.
   overflow-y: auto here (not on .main-content) so scroll position survives
   bridge re-renders which replace innerHTML but leave #app itself intact. */
#app {
  height: 100%;
}

.main-content {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: 1rem 1.25rem 2rem;
}

/* ─── Theme tweaks ──────────────────────────────────────── */
:root {
  /* Accent colour now follows the active Pico theme. The hex
     fallbacks match the original "amber" look so anything that
     loads before Pico still renders correctly. */
  --rep-amber: var(--pico-primary-background, #f5b400);
  --rep-amber-soft: var(--pico-primary-border, #c98a00);
  --rep-amber-hover: var(--pico-primary-hover-background, #ffc424);
  --rep-on-amber: var(--pico-primary-inverse, #1a1a1a);

  --rep-blue: #2e6cb5;

  /* 48.67: the 3D board's sky. Named here rather than in the renderer so the theme carries it, like
     every other colour in this file — the renderer reads these two and invents nothing of its own. The
     horizon is the paper the table stands on, so the board sits in its own surface rather than on a
     colour from nowhere; the zenith is the app's blue. */
  --rep-sky-horizon: var(--rep-paper);
  --rep-sky-top: var(--rep-blue);
  --rep-cloud: #dfe6ef;
  /* The country the table stands in, mixed from the ground the zones already use rather than invented,
     so it moves with the theme the way the sky does. */
  --rep-land: color-mix(in srgb, var(--rep-soil-open-solid) 42%, var(--rep-paper));
  --rep-land-shade: color-mix(in srgb, var(--rep-soil-open-solid) 22%, var(--rep-paper));
  --rep-land-far: var(--rep-sky-horizon);
  --rep-red: #c0392b;
  --rep-cream: #f4ebd0;
  --rep-paper: #1c1f24;
  --rep-paper-2: #23272e;
  --rep-line: #3a3f48;

  /* Control surface — base bg for pip / opt buttons in their
     non-active state. Switched per theme so that active-state
     overrides (.pip-chosen, .opt-btn.active, etc.) win on
     specificity alone, without needing per-mode duplicates. */
  --rep-control-bg: #2a2e36;
  --rep-control-text: var(--rep-cream);

  /* 48.1: the furniture this app draws for itself, one home each. Pico's semantic tokens
     (`--pico-primary-background`, `--pico-muted-color`, …) are used wherever one means the right thing,
     and its named palette (`--pico-color-jade-700`, loaded since 48.2) wherever a *named* colour is what
     is wanted. What is left is the ground a zone shows and the legend marks it with, the amber a counter
     takes under the pointer, and the black its shadows and outlines are drawn in. Values are unchanged
     from the literals they replace — a rename, not a repaint — so the wash and the solid are still two
     greens for one terrain until someone says which is right. */
  --rep-soil-open-wash: rgba(116, 196, 118, 0.20);
  --rep-soil-open-wash-strong: rgba(116, 196, 118, 0.24);
  --rep-soil-open-solid: #4a9d50;
  --rep-soil-open-edge: rgba(60, 140, 64, 0.55);
  --rep-soil-rough-wash: rgba(206, 182, 106, 0.26);
  --rep-soil-rough-wash-strong: rgba(206, 182, 106, 0.30);
  --rep-soil-rough-solid: #b8922f;
  --rep-soil-rough-edge: rgba(150, 120, 40, 0.6);
  --rep-soil-difficult-wash: rgba(122, 86, 56, 0.28);
  --rep-soil-difficult-wash-strong: rgba(122, 86, 56, 0.34);
  --rep-soil-difficult-solid: #9c5a2e;
  --rep-soil-difficult-edge: rgba(90, 55, 30, 0.65);
  --rep-hover-wash: rgba(245, 180, 0, 0.14);
  --rep-shade: rgba(0, 0, 0, 0.4);
  /* 48.15: this turn's state, as a colour — green while the division has not acted, grey once it has.
     The counter's outline reads from here, and it is the *app's* colour rather than the theme's (an
     earlier pass coloured an effect with Pico's primary border, which is blue in the azure theme). Kept
     because whatever the counter's 3D treatment turns out to be, it wants these two colours. */
  --rep-state-ready: rgba(60, 160, 70, 0.9);
  --rep-state-acted: var(--rep-line);
  /* 51.5: engaged — the third state, in the app's own red rather than a new value. */
  --rep-state-engaged: var(--rep-red);
  /* 48.15: how deep the counter's block is per face, and how the two faces are lit — the top face takes the
     light from above, the right face is turned away from it. Both are one number to tune by eye; the
     block's colour itself is the state colour, unchanged. */
  --bf-unit-top: 6px;          /* how tall the top face is */
  --bf-unit-side: 10px;        /* how wide the right face is */
  --bf-unit-top-angle: -59deg;   /* atan(side / top)  = atan(10 / 6)  */
  --bf-unit-side-angle: -31deg;  /* atan(top / side)  = atan(6 / 10)  */
  --bf-unit-light: 0.18;
  --bf-unit-dark: 0.22;
  /* 48.70: how deep a cell is, and how far the block inside it sits down from the cell's top edge. The
     block's top face reaches `--bf-unit-top` above its border box, and the sector view is the tighter of
     the two cells (one division, less slack than a line-view zone), so each view carries its own drop.
     Both are looks to tune by eye, so neither is a number typed into the rule that uses it. */
  --bf-zone-depth: 4.1rem;     /* how deep a cell is */
  /* Measured, not guessed. `.bf-unit` is `rotateX(-28deg)` about its own bottom, so in the board's 3D
     space a block's visual top edge sits about 0.47 of its height above where it is laid out — ~43px for
     the line view's ~91px block, ~26px in a sector. Each view's drop is sized to that lift: enough that
     the block (with the extruded face above it) is inside a sector's top edge, which is where the user saw
     it crossing; in the line view the block's own top edge is inside and the leaning face may cross. */
  --bf-zone-drop: 3.5rem;      /* where the block starts, in the line view */
  --bf-sector-drop: 2rem;      /* ...and in a zoomed sector, where it must clear the face too */
}

.rep-default { color: #22c55e; font-weight: 700; }

body {
  background:
    radial-gradient(1200px 600px at 20% -10%, rgba(245, 180, 0, 0.05), transparent 60%),
    radial-gradient(900px 500px at 110% 110%, rgba(46, 108, 181, 0.07), transparent 55%),
    var(--rep-paper);
}

/* ─── Banner ────────────────────────────────────────────── */
.banner {
  border: 1px solid var(--rep-line);
  background: linear-gradient(180deg, #2a2e36 0%, #1c1f24 100%);
  padding: 1.25rem 1.5rem;
  margin-bottom: 1.25rem;
  border-radius: var(--pico-border-radius);
  display: flex;
  align-items: center;
  gap: 1rem;
}

.banner .crest {
  flex: 0 0 auto;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background:
    conic-gradient(from 0deg,
      var(--rep-blue) 0deg 120deg,
      var(--rep-cream) 120deg 240deg,
      var(--rep-red) 240deg 360deg);
  box-shadow: inset 0 0 0 3px #14171c, 0 0 0 1px var(--rep-line);
  display: grid;
  place-items: center;
  color: #14171c;
  font-weight: 900;
  font-size: 1.4rem;
  letter-spacing: 0.5px;
}

.banner h1 {
  margin: 0;
  font-size: clamp(1.4rem, 3.5vw, 2rem);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--rep-cream);
}

.banner .sub {
  margin: 0;
  color: var(--pico-muted-color);
  font-size: 0.9rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* ─── Accordion (details) ───────────────────────────────── */
.acc {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.acc details {
  border: 1px solid var(--rep-line);
  border-radius: var(--pico-border-radius);
  background: var(--rep-paper-2);
  padding: 0;
  overflow: hidden;
}

.acc details>summary {
  padding: 0.85rem 1rem;
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--rep-cream);
  background: linear-gradient(180deg, #262a32, #1f232a);
  border-bottom: 1px solid transparent;
  transition: border-color 0.15s, background 0.15s;
}

.acc details>summary::-webkit-details-marker {
  display: none;
}

/* Pico v2 paints its own caret via summary::after — suppress so only
   our custom .chev icon shows on the right. */
.acc details>summary::after {
  display: none !important;
}

.acc details[open]>summary {
  border-bottom-color: var(--rep-line);
}

.acc details>summary>.bi {
  color: var(--rep-amber);
  font-size: 1.25rem;
  flex: 0 0 auto;
}

.acc details>summary>.chev {
  margin-left: auto;
  transition: transform 0.18s ease;
  opacity: 0.7;
}

.acc details[open]>summary>.chev {
  transform: rotate(180deg);
}

/* "Rules" link — sits at the top of each expanded section body so
   it only appears when the user opens that accordion. Opens the WTJ
   original rules text for that section in a new tab. */
.rules-btn {
  background: rgba(245, 180, 0, 0.08);
  color: var(--rep-amber);
  border: 1px solid var(--rep-amber-soft);
  padding: 0.22rem 0.6rem;
  border-radius: 6px;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 700;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  line-height: 1;
  margin-bottom: 0.6rem;
}

.rules-btn:hover {
  background: var(--rep-amber);
  color: var(--rep-on-amber);
  border-color: var(--rep-amber);
}

.body {
  padding: 1rem 1.1rem 1.25rem;
}

/* 47.19: the roller element itself. It has never had a rule — an unknown custom element is an inline
   box, so every die took whatever width its table cell or flex row happened to leave it, which is why
   the same component read as crammed on one screen and full width on another. One roller, one
   presentation: it spans the room it is given, shrinks freely, and grows when a row shares it with a
   label (the `auto 1fr` dice-blocks) instead of collapsing to its content. */
rep-d10 {
  display: block;
  width: 100%;
  min-width: 0;
  flex: 1 1 auto;
}

/* ─── Pip strip (manual D10 entry) ──────────────────────── */
.pip-strip {
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  gap: 4px;
  margin: 0.75rem 0;
}

.pip-btn {
  appearance: none;
  border: 1px solid var(--rep-line);
  background: var(--rep-control-bg);
  color: var(--rep-control-text);
  font-weight: 700;
  font-size: 1rem;
  padding: 0.55rem 0;
  border-radius: 6px;
  cursor: pointer;
  transition: transform 0.06s ease, background 0.12s, border-color 0.12s, color 0.12s;
  user-select: none;
  touch-action: manipulation;
}

.pip-btn:hover:not(:disabled) {
  border-color: var(--rep-amber-soft);
  color: var(--rep-amber);
}

/* Dice highlight is pinned to Pumpkin (not theme-following) so the
   roll animation reads clearly against any Pico theme + in both
   light and dark modes. */
.pip-btn.pip-rolling {
  background: #ff7a1a;
  color: #ffffff;
  border-color: #ff7a1a;
  transform: scale(1.08);
  box-shadow: 0 0 0 2px rgba(255, 122, 26, 0.35);
}

.pip-btn.pip-chosen {
  background: #cd6700;
  color: #ffffff;
  border-color: #cd6700;
  box-shadow: 0 0 0 2px rgba(205, 103, 0, 0.35);
}

/* Shaded step — fills the band from the natural roll to the
   modified roll. Darker pumpkin for negative mods (the modified
   result is lower than the natural roll), dark jade for positive
   mods (modified result is higher). Reads as the trail of the
   modifier behind the highlighted natural face. */
.pip-btn.pip-shaded {
  background: #6e3800;
  color: #f0e0c8;
  border-color: #6e3800;
}

.pip-btn.pip-shaded-pos {
  background: #00553f;
  color: #d8efe0;
  border-color: #00553f;
}

/* Skirmish dice-result strips — display-only D10 face row that shows
   multiple rolls at once. Each face that landed gets the same pumpkin
   highlight as a normal chosen roll, plus a stack of small dice icons
   indicating how many times that number came up. */
.dice-result .pip-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 2.6rem;
  cursor: default;
}

.dice-result .pip-btn:hover:not(:disabled) {
  border-color: var(--rep-line);
  color: var(--rep-control-text);
  background: var(--rep-control-bg);
}

.dice-result .pip-btn.pip-rolled {
  background: #cd6700;
  color: #ffffff;
  border-color: #cd6700;
  box-shadow: 0 0 0 2px rgba(205, 103, 0, 0.35);
}

.dice-badge {
  display: flex;
  gap: 1px;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  line-height: 1;
  max-width: 100%;
}

.dice-badge i {
  font-size: 0.6rem;
}

.dice-badge .dice-extra-label {
  margin-left: 2px;
  font-size: 0.62rem;
  font-weight: 800;
  color: #fff7d0;
  letter-spacing: 0.02em;
}

/* Compact pip strip — variants for non-d10 pickers. */
.pip-strip.compact-6 {
  grid-template-columns: repeat(6, 1fr);
}

.pip-strip.compact-7 {
  grid-template-columns: repeat(7, 1fr);
}

.pip-strip.compact-4 {
  grid-template-columns: repeat(4, 1fr);
}

.pip-strip.compact-8 {
  grid-template-columns: repeat(8, 1fr);
}

.pip-strip.compact-9 {
  grid-template-columns: repeat(9, 1fr);
}

.pip-strip.compact-5 {
  grid-template-columns: repeat(5, 1fr);
}

/* Auto-fit: one column per child, all on a single line regardless of
   count. Used by the assault cover strip where the option list grows. */
.pip-strip.compact-fit {
  grid-template-columns: none;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
}

.pip-strip.compact-fit .pip-btn {
  min-width: 0;
  padding-left: 0.35rem;
  padding-right: 0.35rem;
}

/* Morale-state picker — five named buttons (Confident → Demoralized).
   Wider buttons than pip-btn because state names are multi-character.
   Active state pinned to amber to match the "this is the chosen value"
   cue. Used in Orders + Movement (and anywhere else a state picker
   makes sense). */
.morale-strip {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 4px;
  margin: 0.45rem 0 0;
}

.morale-btn {
  appearance: none;
  border: 1px solid var(--rep-line);
  background: var(--rep-control-bg);
  color: var(--rep-control-text);
  padding: 0.55rem 0.3rem;
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.78rem;
  font-weight: 600;
  text-align: center;
  font-family: inherit;
  letter-spacing: 0.01em;
}

.morale-btn:hover:not(.active) {
  border-color: var(--rep-amber-soft);
  color: var(--rep-amber);
}

.morale-btn.active {
  background: var(--rep-amber);
  color: var(--rep-on-amber);
  border-color: var(--rep-amber);
}

.morale-description {
  font-size: 0.85rem;
  color: var(--rep-cream);
  margin: 0.55rem 0 0.3rem;
  padding: 0.55rem 0.75rem;
  background: rgba(245, 180, 0, 0.08);
  border-left: 3px solid var(--rep-amber);
  border-radius: 4px;
  line-height: 1.5;
}

.morale-description strong {
  color: var(--rep-amber);
}

.pip-strip-label {
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--pico-muted-color);
  margin: 0.55rem 0 0.3rem;
}

/* Inline note callout — amber-rule emphasis for rules-context blurbs. */
.note-box {
  background: rgba(245, 180, 0, 0.06);
  border-left: 3px solid var(--rep-amber);
  border-radius: 4px;
  padding: 0.55rem 0.75rem 0.55rem 0.85rem;
  font-size: 0.85rem;
  color: var(--rep-cream);
  line-height: 1.45;
  margin: 0.55rem 0 0.6rem;
}

.note-box i {
  color: var(--rep-amber);
  margin-right: 0.35rem;
}

.note-box strong {
  color: var(--rep-amber);
}

/* Action row: target chip + roll button */
.action-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 0.5rem;
}

.target-chip {
  flex: 1 1 auto;
  min-width: 0;
  background: #14171c;
  border: 1px solid var(--rep-line);
  border-radius: 8px;
  padding: 0.55rem 0.85rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.95rem;
  color: var(--rep-cream);
}

.target-chip strong {
  color: var(--rep-amber);
}

.roll-btn {
  flex: 0 0 auto;
  background: var(--rep-amber);
  color: var(--rep-on-amber);
  border: 1px solid var(--rep-amber);
  font-weight: 700;
  padding: 0.55rem 1.1rem;
  border-radius: 8px;
  cursor: pointer;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: 0.85rem;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.roll-btn:hover {
  background: var(--rep-amber-hover);
}

.roll-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Pass-score pill — the headline number a player needs to roll.
   Pinned to amber (not theme-following) for consistent legibility
   and to read as the "target you need to beat" cue at a glance. */
.pass-pill {
  flex: 0 0 auto;
  background: #f5b400;
  color: #1a1a1a;
  border: 1px solid #f5b400;
  border-radius: 10px;
  padding: 0.3rem 0.85rem;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  line-height: 1;
  box-shadow: 0 0 0 2px rgba(245, 180, 0, 0.25);
}

.pass-pill small {
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.85;
  margin-bottom: 3px;
}

.pass-pill strong {
  font-size: 1.45rem;
  font-weight: 800;
}

.pass-pill.impossible {
  background: #471a1a;
  color: #f87171;
  border-color: #6b2a2a;
  box-shadow: 0 0 0 2px rgba(248, 113, 113, 0.18);
}

/* ─── Result card ───────────────────────────────────────── */
.result-card {
  margin-top: 0.85rem;
  border: 1px solid var(--rep-line);
  border-radius: 8px;
  padding: 0.8rem 0.95rem;
  background: #14171c;
  display: none;
}

.result-card.shown {
  display: block;
}

.result-card .verdict {
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: 0.9rem;
  margin-bottom: 0.35rem;
}

.result-card.pass .verdict {
  color: #4ade80;
}

.result-card.fail .verdict {
  color: #f87171;
}

.result-card.info .verdict {
  color: var(--rep-amber);
}

.result-card .breakdown {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  color: var(--pico-muted-color);
  white-space: pre-wrap;
  line-height: 1.5;
}

.state-transition {
  font-size: 0.95rem;
  font-weight: 600;
  margin: 0.4rem 0;
  color: var(--rep-cream);
}

.state-transition .arrow {
  color: var(--rep-amber);
  margin: 0 0.4em;
}

.state-transition strong {
  color: var(--rep-amber);
}

/* Per-die result chips in skirmish breakdown */
.pass-chip, .hit-2m-chip, .hit-1m-chip, .fail-chip, .miss-chip {
  display: inline-block;
  width: 1.6em;
  height: 1.6em;
  line-height: 1.6em;
  text-align: center;
  border-radius: 4px;
  font-weight: 700;
  font-size: 0.82rem;
  margin: 0 1px;
}
.pass-chip  { background: #1a4a2a; color: #8fdf8f; }
.hit-2m-chip { background: #4a2a0a; color: #f5b400; }
.hit-1m-chip { background: #3a2a0a; color: #d4942a; }
.fail-chip { background: #4a1a1a; color: #f08888; }
.miss-chip { background: #2a2a2a; color: #888; }

.restrictions {
  border-top: 1px dashed var(--rep-line);
  padding-top: 0.55rem;
  margin-top: 0.6rem;
  font-size: 0.85rem;
  color: var(--rep-cream);
  line-height: 1.5;
}

.restrictions strong {
  color: var(--rep-amber);
}

/* Banner for "this unit can't take this action" cases (e.g. a
   demoralized artillery battery is silenced and can't fire). */
.silenced-banner {
  background: rgba(248, 113, 113, 0.12);
  border-left: 3px solid #f87171;
  border-radius: 4px;
  padding: 0.6rem 0.85rem;
  margin: 0.6rem 0;
  font-size: 0.9rem;
  color: #f87171;
  display: none;
  line-height: 1.45;
}

.silenced-banner.shown {
  display: block;
}

.silenced-banner i {
  margin-right: 0.4rem;
}

/* Inside a result-card the state-transition follows the verdict, no
   top margin. */
.result-card .state-transition {
  margin: 0.15rem 0 0.5rem;
}

/* ─── Form layout ───────────────────────────────────────── */
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0.6rem 0.9rem;
  margin-bottom: 0.4rem;
}

.form-grid label {
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--pico-muted-color);
  margin-bottom: 0.2rem;
}

.form-grid select,
.form-grid input {
  margin-bottom: 0;
}

.mod-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.25rem 1rem;
  margin: 0.6rem 0 0.4rem;
}

.mod-list label {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.88rem;
  cursor: pointer;
  color: var(--rep-cream);
  padding: 0.15rem 0;
}

.mod-list input[type="checkbox"] {
  margin: 0;
}

.mod-list .mod-val {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--rep-amber);
  font-weight: 700;
  min-width: 2.2em;
  display: inline-block;
}

.mod-list .mod-val.minus {
  color: #f87171;
}

.mod-list .house-rule {
  display: inline-block;
  margin-left: 0.25rem;
  font-size: 0.65rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #14171c;
  background: #9ca3af;
  padding: 0 0.35rem;
  border-radius: 4px;
  font-weight: 700;
  vertical-align: 1px;
}

/* ─── Reference / chart tables ──────────────────────────── */
.chart-tbl {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
  margin: 0.6rem 0 0.8rem;
}

.chart-tbl th,
.chart-tbl td {
  border: 1px solid var(--rep-line);
  padding: 0.4rem 0.6rem;
  text-align: center;
}

.chart-tbl th {
  background: #262a32;
  color: var(--rep-amber);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.chart-tbl tbody tr:nth-child(odd) {
  background: rgba(255, 255, 255, 0.015);
}

.chart-tbl .num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  font-weight: 600;
}

/* Clickable-row variant — use a chart table as the picker itself
   (one tap = chosen grade). Active row is highlighted in amber. */
.chart-tbl.click-rows tbody tr {
  cursor: pointer;
  transition: background 0.12s;
}

.chart-tbl.click-rows tbody tr:hover:not(.active) td:not(.active) {
  background: rgba(245, 180, 0, 0.10);
}

.chart-tbl.click-rows tbody tr.active td,
.chart-tbl.click-rows tbody tr.active td.row-label {
  background: var(--rep-amber);
  color: var(--rep-on-amber);
  font-weight: 700;
}

/* Assault grid — the Cav-weight column lives inline but is
   hidden unless that side has type = cavalry. JS toggles
   `.show-cav` on the relevant table. */
.assault-side-table:not(.show-cav) th.cav-col,
.assault-side-table:not(.show-cav) td.cav-cell {
  display: none;
}

/* Chart-as-picker — any .grade-cell inside a chart table is a
   clickable selector. Used by Skirmish (Atk + Def cells per row)
   and Rally (Grade + Morale cells per row). */
.chart-tbl tbody td.grade-cell {
  cursor: pointer;
  font-weight: 700;
  transition: background 0.12s;
}

.chart-tbl tbody td.grade-cell:hover:not(.active) {
  background: rgba(245, 180, 0, 0.10);
}

.chart-tbl tbody td.grade-cell.active {
  background: var(--rep-amber);
  color: var(--rep-on-amber);
}

/* Row-linked highlight — when the picker cell in a row is active,
   the partner cell in the same row (column 2) gets a soft amber
   tint so the (Grade ↔ Ratio) or (Class ↔ Target) pairing reads
   as one row. Background only — text colour and weight stay normal
   so it doesn't look washed out against the amber bg. */
.chart-tbl tbody tr.row-linked td:nth-child(2) {
  background: rgba(245, 180, 0, 0.25);
}

/* ─── Army defaults (multi-select allowed + default picker) ────────── */

.army-defaults-tables {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.9rem;
  margin-top: 0.4rem;
}

@media (max-width: 720px) {
  .army-defaults-tables {
    grid-template-columns: 1fr;
  }
}

.army-defaults-tables .adt-title {
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--rep-amber);
  margin-bottom: 0.2rem;
}

.army-defaults-tbl {
  margin: 0;
  font-size: 0.85rem;
}

.army-defaults-tbl td {
  padding: 0.3rem 0.5rem;
}

.army-defaults-tbl td.adt-name {
  text-align: left;
}

.army-defaults-tbl td.adt-item {
  cursor: pointer;
  user-select: none;
  text-align: left;
  font-weight: 700;
  /* 46.14: no background transition here. The highlight fades *in* (see .on)
     but is removed instantly, so a cell that has just been switched off can
     never paint the accent colour for a frame — that transient was what looked
     like "a disabled grade rendered in the wrong colour". */
  transition: color 0.12s;
}

.army-defaults-tbl td.adt-item:hover:not(.on) {
  background: rgba(245, 180, 0, 0.10);
}

.army-defaults-tbl td.adt-item.on {
  background: var(--rep-amber);
  color: var(--rep-on-amber);
  transition: background 0.12s, color 0.12s;
}

.army-defaults-tbl td.adt-item.off {
  background: transparent;
  color: var(--pico-muted-color);
  opacity: 0.6;
}

.army-defaults-tbl td.adt-default {
  cursor: pointer;
  user-select: none;
  width: 22%;
  font-size: 1.05rem;
  color: var(--pico-muted-color);
  transition: background 0.12s;
}

.army-defaults-tbl td.adt-default:hover {
  background: rgba(0, 168, 120, 0.10);
}

.army-defaults-tbl td.adt-default.on {
  color: #00a878;
  font-weight: 700;
}

.army-defaults-tbl td.adt-default.disabled {
  pointer-events: none;
  opacity: 0.3;
}

/* 46.20 — drag handle for reordering battle groups (the ▲/▼ chevrons beside it
   stay as the keyboard/accessibility path). touch-action:none keeps a touch
   drag from scrolling the page instead of reordering. */
.bg-drag-handle {
  background: none;
  border: none;
  color: var(--pico-muted-color);
  cursor: grab;
  padding: 0 0.15rem;
  font-size: 0.85rem;
  line-height: 1;
  touch-action: none;
}

.bg-drag-handle:hover {
  color: var(--pico-primary);
}

.bg-drag-handle:active {
  cursor: grabbing;
}

tr.bg-dragging {
  opacity: 0.45;
}

tr.bg-drop-target td {
  border-top: 2px solid var(--pico-primary);
}

.army-defaults-tables .skirm-defaults {
  grid-column: 1 / -1;
}

/* ─── Morale indicator dots ─────────────────────────────────────── */

/* Morale pip buttons — smaller text so names fit */
.pip-strip.compact-fit .pip-btn {
  font-size: 0.72rem;
  letter-spacing: -0.01em;
}

.morale-dot {
  display: inline-block;
  width: 0.85em;
  height: 0.85em;
  border-radius: 2px;
  margin-right: 0.35em;
  vertical-align: middle;
  position: relative;
  top: -0.05em;
}

/* Confident — Green */
.morale-dot.m0 {
  background: #27ae60;
  border: 1px solid #111;
}

/* Disordered — Blue */
.morale-dot.m1 {
  background: #3498db;
  border: 1px solid #111;
}

/* Rattled — Yellow */
.morale-dot.m2 {
  background: #f1c40f;
  border: 1px solid #111;
}

/* Shaken — Red */
.morale-dot.m3 {
  background: #c0392b;
  border: 1px solid #111;
}

/* Broken — Black */
.morale-dot.m4 {
  background: #1a1a1a;
  border: 1px solid #666;
}

/* Demoralized — Black */

.morale-btn {
  text-align: left;
}

.morale-btn-row .opt-btn {
  text-align: left;
}

.chart-tbl td.morale-cell {
  text-align: left;
}

/* Hue slider for the custom palette — a horizontal strip with the
   full hue spectrum as its track. Drag to live-update the active
   player's background or accent without the OS colour-picker dialog. */
.hue-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 24px;
  border-radius: 6px;
  outline: none;
  cursor: pointer;
  background: linear-gradient(to right,
      hsl(0, 90%, 50%), hsl(30, 90%, 50%), hsl(60, 90%, 50%), hsl(90, 90%, 50%),
      hsl(120, 90%, 50%), hsl(150, 90%, 50%), hsl(180, 90%, 50%), hsl(210, 90%, 50%),
      hsl(240, 90%, 50%), hsl(270, 90%, 50%), hsl(300, 90%, 50%), hsl(330, 90%, 50%),
      hsl(360, 90%, 50%));
  border: 1px solid var(--rep-line);
}

.hue-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 28px;
  background: var(--rep-paper);
  border: 2px solid var(--rep-cream);
  border-radius: 3px;
  cursor: grab;
  box-shadow: 0 0 0 1px var(--rep-shade);
}

.hue-slider::-moz-range-thumb {
  width: 14px;
  height: 28px;
  background: var(--rep-paper);
  border: 2px solid var(--rep-cream);
  border-radius: 3px;
  cursor: grab;
  box-shadow: 0 0 0 1px var(--rep-shade);
}

/* House-rules toggle table — full-width chart-tbl variant with each
   row click-to-toggle. Active rows get a soft jade tint so it reads
   as "applied" at a glance. */
.house-rules-table tbody tr {
  cursor: pointer;
  transition: background 0.12s;
}

.house-rules-table tbody tr:hover:not(.hr-active) {
  background: rgba(245, 180, 0, 0.08);
}

.house-rules-table tbody tr.hr-active {
  background: rgba(0, 134, 108, 0.18);
}

.house-rules-table td.hr-state {
  text-align: center;
  font-size: 1.05rem;
  color: var(--rep-cream);
}

.house-rules-table tr.hr-active td.hr-state {
  color: #00a878;
}

.house-rules-table td.hr-name {
  font-weight: 700;
}

.house-rules-table td.hr-desc {
  font-size: 0.78rem;
  opacity: 0.88;
  line-height: 1.3;
}

/* Scale rulers in Settings — visual ground-scale comparison. */
.scale-rulers-box {
  border: 1px solid var(--rep-line);
  border-radius: 8px;
  padding: 0.7rem 0.85rem 0.5rem;
  background: var(--rep-paper-2);
  margin-top: 0.5rem;
  width: 100%;
}

.ruler {
  margin-bottom: 0.55rem;
  width: 100%;
}

.ruler-label {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--rep-amber);
  margin-bottom: 0.15rem;
}

.ruler-marks {
  position: relative;
  height: 14px;
  width: 100%;
}

.ruler-marks .r-label {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  font-size: 0.66rem;
  font-weight: 700;
  color: var(--rep-cream);
  white-space: nowrap;
  line-height: 14px;
}

.ruler-bar {
  display: flex;
  width: 100%;
  height: 24px;
  border: 1px solid var(--rep-line);
  border-radius: 3px;
  overflow: hidden;
}

.r-seg {
  border-right: 1px solid rgba(0, 0, 0, 0.25);
  min-width: 0;
}

.r-seg:last-child {
  border-right: none;
}

.r-tick {
  background: var(--rep-paper);
  color: var(--rep-cream);
}

.rb-white {
  background: #f6f1e2;
  color: #1a1a1a;
}

.rb-red {
  background: #c0392b;
  color: #fff;
}

/* Per-accordion header */
.acc-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--rep-line);
  border-radius: 6px;
  background: var(--rep-paper-2);
  margin-bottom: 0.8rem;
  flex-wrap: wrap;
}

.acc-header-meta {
  display: flex;
  gap: 0.7rem;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  flex-wrap: wrap;
}

.acc-header-meta .acc-turn,
.acc-header-meta .acc-time {
  color: var(--rep-amber);
}

.acc-header-meta .acc-player {
  color: var(--rep-cream);
}

.acc-header .rules-btn {
  margin-bottom: 0;
}

/* Doctrine banner */
.doctrine-banner {
  font-size: 0.8rem;
  color: var(--rep-amber);
  margin-bottom: 0.6rem;
  font-style: italic;
}

/* Hors de Combat fate description */
.hc-fate {
  margin-top: 0.45rem;
  font-style: italic;
  font-weight: 400;
  font-size: 0.92rem;
  opacity: 0.92;
  line-height: 1.4;
}

/* Non-applicable cell */
.chart-tbl tbody td.na-cell {
  color: var(--pico-muted-color);
  opacity: 0.5;
  font-style: italic;
  cursor: default;
  text-align: center;
}

/* Change matrix (Orders) */
.change-matrix td.cm-same {
  background: rgba(255, 255, 255, 0.04);
  color: var(--pico-muted-color);
  font-style: italic;
  font-size: 0.78rem;
}

.change-matrix td.cm-free {
  background: rgba(74, 222, 128, 0.18);
  font-weight: 700;
  font-size: 0.78rem;
  text-transform: lowercase;
  letter-spacing: 0.05em;
}

.change-matrix td.cm-roll {
  background: rgba(245, 180, 0, 0.28);
  font-weight: 800;
  font-size: 0.78rem;
  letter-spacing: 0.06em;
}

.change-matrix td.cm-forbidden {
  background: rgba(248, 113, 113, 0.12);
  color: #f87171;
  font-weight: 700;
  font-size: 0.78rem;
  letter-spacing: 0.06em;
}

.change-matrix th {
  background: #262a32;
}

.change-matrix td.row-label {
  text-align: right;
  padding-right: 0.7rem;
}

/* Panic grid */
.panic-grid tbody td[data-react] {
  cursor: pointer;
  transition: background 0.12s;
  font-weight: 700;
}

.panic-grid tbody td[data-react]:hover:not(.active-cell) {
  background: rgba(245, 180, 0, 0.10);
}

.panic-grid tbody tr.active-row td.row-label {
  background: rgba(245, 180, 0, 0.18);
  color: var(--rep-amber);
}

.panic-grid tbody td.pass-col {
  font-weight: 600;
  color: var(--pico-muted-color);
  cursor: default;
  background: transparent;
}

.panic-grid tbody td.active-cell {
  background: var(--rep-amber);
  color: var(--rep-on-amber);
}

.chart-tbl td.row-label {
  text-align: left;
  font-weight: 600;
  color: var(--rep-cream);
  background: #1f232a;
}

.chart-note {
  font-size: 0.82rem;
  color: var(--pico-muted-color);
  font-style: italic;
  margin: 0.2rem 0 0.6rem;
}

/* Two-column for sequence list */
.seq-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.6rem 1rem;
  counter-reset: seq-phase-num;
}

.seq-phase {
  border: 1px solid var(--rep-line);
  border-radius: 6px;
  padding: 0.55rem 0.7rem;
  background: #1f232a;
  counter-increment: seq-phase-num;
}

.seq-phase h4 {
  margin: 0 0 0.25rem;
  font-size: 0.78rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--rep-amber);
  display: flex;
  align-items: center;
  gap: 0.45rem;
}

.seq-phase h4::before {
  content: counter(seq-phase-num);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5em;
  height: 1.5em;
  border-radius: 50%;
  background: var(--rep-amber);
  color: #1a1a1a;
  font-weight: 800;
  font-size: 0.85em;
  letter-spacing: 0;
  flex-shrink: 0;
}

.seq-phase ul {
  margin: 0;
  padding-left: 1.05rem;
  font-size: 0.86rem;
  color: var(--rep-cream);
}

.seq-phase ul li {
  margin: 0.05rem 0;
}

.seq-phase ul li a {
  color: var(--rep-cream);
  text-decoration: none;
  border-bottom: 1px dotted transparent;
  transition: color 0.12s, border-color 0.12s;
}

.seq-phase ul li a:hover {
  color: var(--rep-amber);
  border-bottom-color: var(--rep-amber);
}

.seq-phase ul li a::after {
  content: ' ↗';
  font-size: 0.85em;
  opacity: 0.4;
  letter-spacing: 0;
}

.seq-phase ul li a:hover::after {
  opacity: 1;
}

/* Skirmish log */
.skirmish-log {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  background: #0f1216;
  border: 1px solid var(--rep-line);
  border-radius: 6px;
  padding: 0.6rem 0.75rem;
  max-height: 280px;
  overflow-y: auto;
  margin-top: 0.6rem;
}

.skirmish-log .row {
  padding: 0.1rem 0;
  border-bottom: 1px dotted #2a2e36;
}

.skirmish-log .row:last-child {
  border-bottom: none;
}

.skirmish-log .ok {
  color: #4ade80;
}

.skirmish-log .miss {
  color: var(--pico-muted-color);
}

.skirmish-log .bad {
  color: #f87171;
}

.skirmish-log .flee {
  color: var(--rep-amber);
}

.tally {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.6rem 0 0.2rem;
}

.tally .pill {
  background: #1f232a;
  border: 1px solid var(--rep-line);
  padding: 0.3rem 0.65rem;
  border-radius: 999px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  color: var(--rep-cream);
}

.tally .pill strong {
  color: var(--rep-amber);
}

/* ─── Army Notes ────────────────────────────────────────── */
.army-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.85rem;
}

.army-tab {
  appearance: none;
  border: 1px solid var(--rep-line);
  background: #1f232a;
  color: var(--rep-cream);
  padding: 0.4rem 0.85rem;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  letter-spacing: 0.02em;
}

.army-tab:hover {
  border-color: var(--rep-amber-soft);
  color: var(--rep-amber);
}

.army-tab.active {
  background: var(--rep-amber);
  color: var(--rep-on-amber);
  border-color: var(--rep-amber);
}

.flag-icon {
  width: 2.1rem;   /* 1.4 x height (33.6px at 24px) */
  height: 1.5rem;  /* double the old 12px swatch */
  border-radius: 2px;
  display: inline-block;
  vertical-align: -0.3rem;
  border: 1px solid rgba(0, 0, 0, 0.35);
  overflow: hidden;
  flex: 0 0 auto;
  background: #444;
}

.army-panel h3 {
  margin: 0.6rem 0 0.25rem;
  color: var(--rep-cream);
  font-size: 1.05rem;
  letter-spacing: 0.02em;
}

.army-overview {
  color: var(--rep-cream);
  font-size: 0.92rem;
  margin: 0.1rem 0 0.55rem;
  line-height: 1.45;
}

.army-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.2rem 0 0.6rem;
}

.army-meta .pill {
  background: #1f232a;
  border: 1px solid var(--rep-line);
  padding: 0.28rem 0.7rem;
  border-radius: 999px;
  font-size: 0.78rem;
  color: var(--rep-cream);
}

.army-meta .pill i {
  color: var(--rep-amber);
  margin-right: 0.3rem;
}

.army-notes-list {
  margin: 0.2rem 0 0.6rem 1.1rem;
  padding: 0;
  font-size: 0.85rem;
  color: var(--rep-cream);
}

.army-notes-list li {
  margin: 0.12rem 0;
}

.army-tbl-wrap {
  overflow-x: auto;
}

.army-tbl {
  width: 100%;
  min-width: 480px;
}

/* ─── Dice pair (two D10 strips) ─────────────────────────── */
.dice-pair {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  margin: 0.6rem 0 0.4rem;
}

.dice-block {
  /* 47.19: the label used to sit in a `9em` column beside the strip, so the ten faces only ever got the
     rest of the card — which is what "crammed up like it is not even using the same component" was. The
     label is a caption on its own line, and the roller below it spans the card, the same as the deploy
     die does. */
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.25rem;
}

.dice-label {
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--pico-muted-color);
  font-weight: 700;
}

.dice-block .pip-strip {
  margin: 0;
  flex: 1;
}

/* Section subhead */
.sub-hd {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--rep-amber);
  margin: 0.85rem 0 0.35rem;
  border-bottom: 1px dashed var(--rep-line);
  padding-bottom: 0.2rem;
}
/* 51.16: the artillery dialog's own notes read as **sentences**, not as headings — the user's own correction:
   *"The note about cotton wool should be in lowercase not UPPERCASE"*. It was this uppercase transform saying
   that, not the string, so the string was never the thing to edit. The class is on that dialog's notes only, so
   no other screen's headings change with it. */
.sub-hd.arty-note { text-transform: none; letter-spacing: 0; }
.sub-hd.arty-note .arty-who { font-weight: 600; }

.hint {
  font-size: 0.8rem;
  color: var(--pico-muted-color);
  margin: 0.3rem 0 0.2rem;
}
/* 51.17: the tactical phase with nothing in it — the message is the panel's whole body, with the way on under
   it. Pico's own semantic colours, like the rest of the frame's notes. */
.tac-empty { margin: 0.2rem 0 0.6rem; }
.tac-empty p { font-size: 0.82rem; color: var(--pico-muted-color); margin: 0 0 0.6rem; }

footer.footer {
  margin-top: 2rem;
  padding-top: 1rem;
  border-top: 1px solid var(--rep-line);
  color: var(--pico-muted-color);
  font-size: 0.8rem;
  text-align: center;
}

/* ─── Settings UI ───────────────────────────────────────── */
.settings-section {
  margin-bottom: 1rem;
}

.player-card {
  border: 1px solid var(--rep-line);
  border-radius: 10px;
  padding: 0.85rem 1rem 0.5rem;
  margin-bottom: 1rem;
  background: var(--rep-paper-2);
}

.player-card-title {
  font-size: 0.9rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--rep-amber);
  margin-bottom: 0.7rem;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.show-theme-toggle {
  margin-left: auto;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  color: var(--rep-cream);
  display: flex;
  align-items: center;
  gap: 0.3rem;
  cursor: pointer;
  opacity: 0.85;
}

.show-theme-toggle input[type=checkbox] {
  accent-color: var(--rep-amber);
  cursor: pointer;
}

.theme-swatch-grid {
  display: grid;
  grid-template-columns: repeat(10, 18px);
  gap: 3px;
  margin-top: 0.3rem;
}

.theme-swatch {
  background: var(--swatch-color, #888);
  border: 1px solid var(--rep-line);
  width: 18px;
  height: 18px;
  border-radius: 4px;
  cursor: pointer;
  text-align: center;
  font-family: inherit;
  font-size: 0;
  padding: 0;
}

.theme-swatch.active {
  outline: 2px solid var(--rep-amber);
  outline-offset: 2px;
}

.theme-mode-row {
  display: flex;
  gap: 0.5rem;
  align-items: stretch;
  flex-wrap: wrap;
}

.group-box {
  border: 1px solid var(--rep-line);
  border-radius: 6px;
  padding: 0.45rem 0.55rem;
  flex: 0 0 auto;
}

.theme-mode-row .opt-btn {
  padding: 0.35rem 0.55rem;
  font-size: 0.78rem;
}

.custom-palette-panel {
  margin-top: 0.5rem;
  padding: 0.55rem 0.7rem;
  border: 1px dashed var(--rep-line);
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.02);
}

.grade-cell.grade-disabled,
.grade-cell.disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

.opt-btn.custom-toggle.active {
  background: var(--rep-amber);
  color: #1a1a1a;
  border-color: var(--rep-amber);
}

.btn-row {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 0.3rem;
}

.time-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.35rem;
}

.time-row-label {
  min-width: 5.5rem;
  font-size: 0.78rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--rep-amber);
  font-weight: 700;
}

.time-row-label i {
  margin-right: 0.25rem;
}

.opt-btn {
  background: var(--rep-control-bg);
  color: var(--rep-control-text);
  border: 1px solid var(--rep-line);
  padding: 0.5rem 1rem;
  border-radius: 6px;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: inherit;
  font-size: 0.85rem;
}

.opt-btn:hover {
  border-color: var(--rep-amber-soft);
  color: var(--rep-amber);
}

.opt-btn.active {
  background: var(--rep-amber);
  color: var(--rep-on-amber);
  border-color: var(--rep-amber);
}

.opt-btn.active:hover {
  color: #1a1a1a;
}

/* ─── Light mode palette (minimal first cut) ─────────────── */
:root[data-theme="light"] {
  /* A daylight sky's clouds, not the dark theme's grey ones — the only token here that cannot be mixed
     out of the others, because a cloud is light in both themes and the theme's own light colour is its
     ink. */
  --rep-cloud: #ffffff;
  --rep-paper: #faf6e8;
  --rep-paper-2: #fffdf3;
  --rep-cream: #2a2419;
  --rep-line: #d4ccb8;
  color-scheme: light;
}

:root[data-theme="light"] body {
  background:
    radial-gradient(1200px 600px at 20% -10%, rgba(245, 180, 0, 0.10), transparent 60%),
    var(--rep-paper);
}

:root[data-theme="light"] .banner {
  background: linear-gradient(180deg, #fffdf3 0%, #f5efd8 100%);
}

:root[data-theme="light"] .acc details>summary {
  background: linear-gradient(180deg, #f8f1d8, #f0e7c5);
}

:root[data-theme="light"] {
  --rep-control-bg: #fffdf3;
  --rep-control-text: #2a2419;
}

:root[data-theme="light"] .target-chip,
:root[data-theme="light"] .result-card,
:root[data-theme="light"] .seq-phase,
:root[data-theme="light"] .tally .pill,
:root[data-theme="light"] .army-meta .pill {
  background: #fffdf3;
  color: #2a2419;
}

:root[data-theme="light"] .chart-tbl th {
  background: #ede5cc;
  color: #5a4a1c;
}

:root[data-theme="light"] .chart-tbl td.row-label {
  background: #f5efd8;
}

:root[data-theme="light"] .skirmish-log {
  background: #fffdf3;
  color: #2a2419;
}

:root[data-theme="light"] .note-box {
  background: var(--rep-hover-wash);
}

/* Tighten on small screens */
@media (max-width: 540px) {
  .body {
    padding: 0.8rem 0.8rem 1rem;
  }

  .pip-btn {
    font-size: 0.9rem;
    padding: 0.5rem 0;
  }

  .target-chip {
    font-size: 0.85rem;
  }
}

/* ── Compact sidebar ─────────────────────────────── */
.sidebar-nav-compact { width:52px; }
.sidebar-nav-compact .sidebar-brand { padding:0.5rem 0.4rem 0.5rem; justify-content:center; }
.sidebar-nav-compact .sidebar-crest { width:24px; height:24px; }
.sidebar-nav-compact .nav-item { justify-content:center; padding:0.45rem 0; font-size:1rem; }
.sidebar-nav-compact .nav-item i { font-size:1.1rem; }

.nav-toggle-btn { background:none; border:none; color:var(--rep-cream); cursor:pointer; padding:0.3rem; margin:0 auto 0.3rem; font-size:0.85rem; opacity:0.6; }
.nav-toggle-btn:hover { opacity:1; }
.sidebar-nav-compact .nav-toggle-btn i { transform:rotate(180deg); }

/* Tighter nav */
.nav-item { font-size:0.75rem; padding:0.3rem 0.8rem; gap:0.4rem; line-height:1.2; }
.sidebar-app-name { font-size:0.78rem; }
.nav-item i { font-size:0.9rem; width:1.05rem; }
.nav-toggle-btn { padding:0.15rem; margin-bottom:0.1rem; }

/* ── Toasts (46.5 save/backup feedback) ─────────────── */
.toast-host { position:fixed; right:0.8rem; bottom:0.8rem; z-index:2000; display:flex; flex-direction:column; gap:0.45rem; max-width:min(22rem, calc(100vw - 1.6rem)); }
.toast { padding:0.6rem 0.85rem; border-radius:8px; border:1px solid var(--pico-muted-border-color); background:var(--pico-card-background-color); color:var(--pico-color); font-size:0.85rem; box-shadow:0 4px 14px rgba(0,0,0,0.35); animation:toast-in 0.25s ease; }
.toast-ok { border-color:var(--pico-ins-color, #2f9e44); }
.toast-ok::before { content:"✓ "; color:var(--pico-ins-color, #2f9e44); }
.toast-err { border-color:var(--pico-del-color); }
.toast-err::before { content:"⚠ "; color:var(--pico-del-color); }
.toast-out { opacity:0; transition:opacity 0.4s ease; }
@keyframes toast-in { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:none; } }

/* ── Restore-from-backup modal (46.5) ─────────────── */
.modal-backdrop { position:fixed; inset:0; z-index:1500; background:rgba(0,0,0,0.55); display:flex; align-items:center; justify-content:center; padding:1rem; }
.modal-card { background:var(--pico-card-background-color); border:1px solid var(--pico-muted-border-color); border-radius:10px; padding:1.2rem 1.4rem; max-width:90vw; box-sizing:border-box; width:100%; box-shadow:0 8px 30px rgba(0,0,0,0.4); }
.modal-card h4 { margin:0 0 0.5rem; }
.modal-game { font-weight:600; margin:0.2rem 0 0.4rem; }
.modal-note { margin:0 0 1rem; font-size:0.85rem; color:var(--pico-muted-color); }
.modal-actions { display:flex; gap:0.5rem; justify-content:flex-end; }

/* ── Backup panel on Load Game (46.5) ─────────────── */
.backup-panel { border:1px solid var(--pico-muted-border-color); border-radius:10px; padding:0.8rem 1rem 1rem; margin-bottom:1.4rem; background:var(--pico-card-sectioning-background-color); }
.backup-panel h3 { margin:0 0 0.4rem; font-size:1.05rem; }
.backup-panel .hint { margin:0 0 0.7rem; font-size:0.82rem; }
.backup-actions { display:flex; flex-wrap:wrap; gap:0.5rem; }
.opt-ghost { background:transparent; }
/* 46.53: the National Settings block is a card (an <article>, which Pico styles);
   keep it compact and aligned with the player card's spacing. */
.card.adv-tuning { margin: 0.7rem 0; padding: 0.7rem 0.9rem; }
.card.adv-tuning .adv-tuning-btn { margin-bottom: 0.4rem; }

/* 47.15: the battlefield pseudo-map — the table, not a plan of the ground. Two halves:
   your army below, the opposing army above it facing down, so their left wing is drawn
   over your right. Each half is its own three-column grid, so the columns line up
   across the 180° rotation; which column a zone takes is set inline by the renderer
   (that is what mirrors the opposing half), and the advance strips meet in the middle. */
.bf-map {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 0.9rem;
  /* 47.24: the surface's children keep their own 3D positions instead of being flattened into
     it — which is what lets a half (or a zone) be lifted toward the viewer on its own. */
  transform-style: preserve-3d;
  /* 47.24: the pivot is the REAR (top) edge, so the rear edge of the board stays put and the
     space the projection costs opens up BELOW the board instead of above it. Measured: 16px above
     (the stage's padding only) and 156px below on the line view, 16px / 62px on the sector —
     against 319px and 132px ABOVE with the near-edge pivot. It halves the cost too: with
     `perspective-origin` at the stage's bottom, pinning the rear edge lets the near edge grow
     toward the viewer rather than the far edge shrinking away, so the board projects 569px of its
     702px instead of 426px. No lift here on purpose — with the top pinned, lifting the board would
     only put the band back at the top. */
  transform-origin: center top;
  /* Adjust angle: 20deg to 35deg usually works best. */
  /* Adjust angle: 20deg to 35deg usually works best. */
  /* 47.24: 28deg — back up from 20deg, for more depth. The near edge stays flush because the scale
     is re-derived from the same depth: with the rear edge as the pivot the near half is magnified by
     P/(P - h*sin(theta)), and at 28deg on the line view's 702px board that is 1.197x (h*sin28 =
     329px of depth into a 2000px eye), so the scale is 1/1.197 = 0.835. The counters cannot come
     out magnified whatever these two are set to: they sit between the near edge and the rear, so
     their own k is at most the near edge's, and dividing by the near edge's k leaves them at or
     under their layout size. */
  /* 47.24: and the scale itself, applied OUTSIDE the rotation — a uniform scale of the whole
     projection, which leaves the rear edge narrower than the near one (the intended look) and
     keeps the counters' aspect, where a width-only scale would stretch them. */
  transform: scale(0.835) rotateX(28deg);
  /* Animate the tilt instead of snapping it when the angle changes. */
  transition: transform 0.3s ease;
  /* Gives the board physical depth. */
  box-shadow: 0 30px 40px rgba(0, 0, 0, 0.45);
}
.bf-map.bf-sector {
  /* 47.24: the sector's board is one division's, so it is shorter and its near edge is magnified
     less — 1.068x against the line view's 1.197x at this angle — and so takes less compensation:
     `scale(0.936)`, i.e. 1/1.068. Same rule as the line view, same reason: the scale is the
     reciprocal of the near-edge magnification, so the near edge lands on the content width. */
  transform: scale(0.936) rotateX(28deg);
}
.bf-half {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 3px;
  /* Preserve 3D through the children so their transforms anchor in the board's 3D space, not in
     the half's own flattened plane. (`.bf-zone` carries the same declaration.) */
  transform-style: preserve-3d;
}
.bf-half-label {
  grid-column: 1 / -1;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  opacity: 0.6;
}
/* 48.13: the side a sector belongs to, in Pico's own palette — jade-700 for the player, and for the
   opposing half the user's own pick, pumpkin-850 (`#372004`), rather than the brighter red this
   started on. Both are 0,2,0 and stated after `.bf-zone`, so they win outright — the zone-level soil
   tints that used to paint over them are gone with this, and so is the neutral grey a *mixed* zone
   showed: a sector's ground is now its side, always.
   The values are Pico's `--pico-color-jade-700` (#015234) and `--pico-color-pumpkin-850` (#372004).
   The palette is linked in www/index.html, but it ships as utility classes
   (`.pico-background-jade-700`) rather than root variables — measured:
   `getPropertyValue('--pico-color-jade-700')` on <html> is empty even with the file loaded — so the
   two values are named here. */
.bf-half-own .bf-zone,
.bf-map.bf-sector .bf-zone { background: #015234; }
.bf-half-enemy .bf-zone { background: #372004; }
/* 48.6: the opposing half faces down the table, so its divisions stack the other way up: the first
   division is the one at the front — nearest this player — and it goes *below* the second, the
   mirror of our own layout. The zone's label and backdrop are absolute, so reversing the flow
   moves the rectangles and nothing else. */
.bf-half-enemy .bf-zone { flex-direction: column-reverse; }
/* 47.18: the ground. Coarse terrain is a division's own area value (Open / Rough /
   Difficult, `deploy_terrain`), so the colour sits on the rectangle — and on the zone when
   everything in it agrees. A zone that disagrees is striped by its stacked rectangles. */
.bf-soil-open { background: var(--rep-soil-open-wash); }
.bf-soil-rough { background: var(--rep-soil-rough-wash); }
.bf-soil-difficult { background: var(--rep-soil-difficult-wash); }
.bf-soil-mixed { background: rgba(128, 128, 128, 0.08); }
/* A rectangle's own ground has to beat `.bf-unit`'s base background, so it is stated at
   higher specificity rather than relying on where it sits in this file — the ground is the
   point of the map, not a decoration on it. */
.bf-unit.bf-soil-open {
  background: var(--rep-soil-open-wash-strong);
  border-left: 4px solid var(--rep-soil-open-edge);
}
.bf-unit.bf-soil-rough {
  background: var(--rep-soil-rough-wash-strong);
  border-left: 4px solid var(--rep-soil-rough-edge);
}
.bf-unit.bf-soil-difficult {
  background: var(--rep-soil-difficult-wash-strong);
  border-left: 4px solid var(--rep-soil-difficult-edge);
}
.bf-zone.bf-soil-open,
.bf-zone.bf-soil-rough,
.bf-zone.bf-soil-difficult { border-style: solid; }
/* 48.2: the zone-level terrain *tint* is gone: a sector's ground is its side (jade-700 or red-700,
   above), and the terrain shows on the rectangles. The border-style rule stays — it was the one part
   of this that never needed to fight the fill. */
/* 47.24: the key has to work as a key. On the dark panel — the default palette, `:root[data-theme
   ="light"]` is the override — the three tints composite to dE76 10.9 (Open/Rough), 13.6
   (Open/Difficult) and 12.1 (Rough/Difficult): not enough to tell apart, and lightening the chips
   to fix that would put light text on a light swatch. So each chip carries the same colour bar the
   rectangles carry on their left edge, in solid colour here so the panel cannot wash it out —
   distinctness from the accent, contrast untouched. The board needs nothing: its rectangles
   already wear their soil colour on that edge, which is how a Rough division reads as Rough. */
.bf-legend .bf-chip { border-left-width: 4px; }
.bf-legend .bf-chip.bf-soil-open { border-left-color: var(--rep-soil-open-solid); }
.bf-legend .bf-chip.bf-soil-rough { border-left-color: var(--rep-soil-rough-solid); }
.bf-legend .bf-chip.bf-soil-difficult { border-left-color: var(--rep-soil-difficult-solid); }
.bf-legend {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.35rem 0 0.6rem;
  font-size: 0.68rem;
}
.bf-chip {
  padding: 0.1rem 0.45rem;
  border-radius: 4px;
  border: 1px solid var(--rep-line, rgba(128, 128, 128, 0.45));
  font-weight: 600;
}
.bf-zone {
  position: relative;
  display: flex;
  /* 47.15/47.18: divisions sharing a position stack — one under the other — so the
     zone runs down the cross axis, not along the row. */
  flex-direction: column;
  /* 47.40: the rectangle sits in the middle of its cell. Plainest in the advance strip,
     which spans the whole half; in a wing cell it only nudges, because the cell is barely
     wider than a division's own box. */
  align-items: center;
  gap: 0.3rem;
  /* 48.70: deeper cells, and the block sits inside the top edge rather than poking through it — the drop
     has to cover the block's own top face (--bf-unit-top) with air to spare. */
  min-height: var(--bf-zone-depth);
  padding: var(--bf-zone-drop) 0.4rem 0.4rem;
  border: 1px dashed var(--rep-line, rgba(128, 128, 128, 0.45));
  border-radius: 5px;
  background: rgba(128, 128, 128, 0.05);
  /* Preserve 3D through the children so their transforms anchor in the board's 3D space, not in
     the zone's own flattened plane. */
  transform-style: preserve-3d;
}
/* The two advance strips face each other across the middle of the map. */
.bf-zone-advance { border-style: solid; }
.bf-zone-label {
  position: absolute;
  top: 0.3rem;
  /* 47.40: the cell's caption is centred with the boxes it describes, not pinned to the
     top-left — `left: 0; right: 0` makes the absolute box span the whole cell so the text
     centres across it. */
  left: 0;
  right: 0;
  text-align: center;
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  opacity: 0.55;
}
/* 48.18: each sector's caption moves to its own side's edge — right for ours, left for theirs — so the
   centred division boxes stop printing over it. */
.bf-half-own .bf-zone-label { text-align: right; padding-right: 0.5rem; }
.bf-half-enemy .bf-zone-label { text-align: left; padding-left: 0.5rem; }
.bf-unit {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.05rem;
  min-width: 8.5rem;
  max-width: 14rem;
  padding: 0.3rem 0.45rem;
  padding-right: 1.7rem; /* 47.40: room for the nation flag in the top right */
  position: relative;    /* 47.40: the corner badges position against the rectangle */
  border-radius: 4px;
  border: 1px solid var(--rep-amber-soft, rgba(245, 180, 0, 0.4));
  background: rgba(245, 180, 0, 0.06);
  color: inherit;
  font: inherit;
  line-height: 1.15;
  text-align: left;
  cursor: pointer;
  /* 47.24: the counter stands proud of the board — a few pixels of Z, not a floating card. */
  /* 47.24: and it faces the player: the -28deg cancels the board's own pitch, so the card stands
     up off its base (hence the bottom-centre origin above) instead of lying flat on the table.
     This angle is the negated `.bf-map` angle and has to move with it. A mismatch shows up as a
     width ratio that no longer matches the height ratio — when the board went to 20deg and this
     was left at 28deg, counters read 0.94 across against 0.97 down. */
  transform: rotateX(-28deg) translateZ(1px);
  /* 47.24: the counter's own shadow — `drop-shadow`, so it follows the card's silhouette (and the
     flag and glyphs on it) rather than the box, and lands on the board below it. */
  filter: drop-shadow(0 6px 6px var(--rep-shade));
  /* 47.24: inert for as long as that shadow lives on this same element — a `filter` is a grouping
     property, so it flattens the children whatever this says (measured: a 20px-lifted child moves
     0.0px with the filter on and 6.3px with it off, while the computed value reads `preserve-3d`
     either way). It starts doing work the moment the shadow moves to a wrapper. */
  transform-style: preserve-3d;
  /* Its own base is the pivot: a counter stands on its edge, not through its middle. */
  transform-origin: bottom center;
}

/* 47.24's contact band under the card used to live on `::after`; that pseudo is the block's right face
   now (48.15). Nothing is lost: the card's own `filter: drop-shadow(...)` casts the contact shadow, and
   because the faces are its children it now follows the whole block rather than the card alone. */
.bf-unit:hover { background: var(--rep-hover-wash); }
/* Read, not commanded: the opposing side's rectangles are not buttons. */
.bf-unit-enemy {
  border-color: var(--rep-line, rgba(128, 128, 128, 0.45));
  background: rgba(128, 128, 128, 0.08);
  cursor: default;
  /* 48.15: the opposing counters are the same block — the extrusion follows their own state colour
     (the same two, since a spent enemy division is spent too). */
}
/* 48.15: one `box-shadow` replaces another, so the open popup's inset ring has to name the block's
   edge alongside itself or the open counter alone would lose its thickness. */
.bf-unit-open { box-shadow: inset 0 0 0 2px var(--rep-amber, #f5b400); }
/* 47.40 / 48.5: this turn's state, as a thick border — green while the division has not acted, grey
   once it has. An outline rather than a border, so it cannot collide with the terrain border-left or
   the popup's inset box-shadow above. Grey, not red: red is this board's "engaged" (the battle group
   boxes use exactly that red), and a spent division is spent, not in trouble. The grey is this
   file's own `--rep-line` idiom, as used for every other line on the board: Pico's `--pico-muted-
   color` and `--pico-muted-border-color` both resolve to *white* in this app's theme, which would
   have shouted louder than the red did. */
/* 48.15: the counter as a solid block. The card is the front face; this is the rest of the block — the
   band above it and the band to its right — so the front and back edges connect, instead of a second
   rectangle peeking out from behind (which is all an offset copy of the box can ever look like).

   **Two plain bands, and deliberately no `clip-path`.** A one-piece silhouette drawn with
   `clip-path: polygon(...)` was measured *dead* on this element: `getComputedStyle` reported the polygon
   exactly as written and not one pixel of it ever painted — sampled above, right and inside the band,
   the board and the zone, never the block — with or without a `translateZ`. A plain positioned band on
   the same pseudo paints perfectly (`(59,147,63)`, the expected blend). So the L is painted as two
   non-repeating gradient layers, sized and placed at the card's own top and right edges.

   The colour is the card's own outline colour — green while the division has not acted, grey once it has
   — and `pointer-events: none` keeps hit-testing exactly as it was, so the map's backdrop still takes
   the taps that used to leave a sector. The card's `filter` means the drop-shadow follows the whole
   block, and because it is one element per counter the pitch costs nothing. */
/* 48.15: the counter as a real extrusion — two **sheared** faces, so the block has a top and a
   right-hand side whose edges run diagonally from the card's own corners and meet at a point.

   The geometry, with the card as the front face (W x H) and the depth vector (d, -d) — out to the right
   and up, d = `--bf-unit-depth`:

     top face   (0,0) (W,0) (W+d,-d) (d,-d)      a parallelogram: the card's width, sheared right
     right face (W,0) (W+d,-d) (W+d,H-d) (W,H)   the card's height, sheared up
     silhouette (0,0) (d,-d) (W+d,-d) (W+d,H-d) (W,H) (0,H) — a hexagon, meeting at the point (W+d,-d)

   Both are ordinary rectangles under a single skew — `skewX(-45deg)` about the bottom-left for the top
   face, `skewY(-45deg)` about the top-left for the right face — which is why this needs no `clip-path`
   (a clipped silhouette on this element was measured dead: it computed correctly and painted nothing) and
   no extra markup. The shared edge (W,0)->(W+d,-d) is the same line in both faces, so they join with no
   seam and the outer corner is a point rather than a square.

   The colour is the card's own state colour — green while the division has not acted, grey once it has —
   from the same two tokens the outline uses, so the block and the border cannot disagree. */
/* The two faces are anchored by *length*, not by `100%`: `bottom: 100%` and `left: 100%` resolve against
   a box a few pixels larger than the card, which measured as the block floating ~5px clear of the card —
   a visible seam. `top: calc(-1 * d)` and `right: calc(-1 * d)` put each face's joining edge exactly on
   the card's own edge, which is what makes the silhouette one shape. */
.bf-unit::before {
  content: "";
  position: absolute;
  pointer-events: none;
  top: calc(-1 * var(--bf-unit-top));
  left: 0;
  width: 100%;
  height: var(--bf-unit-top);
  transform-origin: bottom left;
  transform: skewX(var(--bf-unit-top-angle));
  /* The lit face: the state colour with a wash of light over it. */
  background-image:
    linear-gradient(rgba(255, 255, 255, var(--bf-unit-light)), rgba(255, 255, 255, var(--bf-unit-light))),
    linear-gradient(var(--bf-block), var(--bf-block));
}
.bf-unit::after {
  content: "";
  position: absolute;
  pointer-events: none;
  top: 0;
  right: calc(-1 * var(--bf-unit-side));
  width: var(--bf-unit-side);
  height: 100%;
  transform-origin: top left;
  transform: skewY(var(--bf-unit-side-angle));
  /* The face turned away: the same colour, shaded. */
  background-image:
    linear-gradient(rgba(0, 0, 0, var(--bf-unit-dark)), rgba(0, 0, 0, var(--bf-unit-dark))),
    linear-gradient(var(--bf-block), var(--bf-block));
}
.bf-unit-ready::before, .bf-unit-ready::after { --bf-block: var(--rep-state-ready); }
.bf-unit-acted::before, .bf-unit-acted::after { --bf-block: var(--rep-state-acted); }

.bf-unit-ready { outline: 3px solid var(--rep-state-ready); outline-offset: -3px; }
.bf-unit-acted { outline: 3px solid var(--rep-state-acted); outline-offset: -3px; }
/* 51.5: a division in contact wears a **red band along the bottom edge** of its rectangle. An inset shadow
   rather than a border, so a division coming into contact cannot move the board's layout by a pixel — and the
   readiness outline is drawn over it, so a division still reads ready/acted at a glance as well as engaged.
   The band is **cased in the block's own dark wash** (`--bf-unit-dark`, the transparent black the 3D counter's
   flag backing is drawn with) because a player can pick a red theme: a red band on a red plate would otherwise
   vanish, and the casing is what guarantees the stripe reads whatever the nation's colour is. `-open` (47.40)
   already spends the inset ring, so the two are composed rather than overwritten. */
.bf-unit-engaged { box-shadow: inset 0 -6px 0 0 var(--rep-state-engaged), inset 0 -9px 0 0 rgba(0, 0, 0, var(--bf-unit-dark)); }
.bf-unit-engaged.bf-unit-open { box-shadow: inset 0 0 0 2px var(--rep-amber, #f5b400), inset 0 -6px 0 0 var(--rep-state-engaged), inset 0 -9px 0 0 rgba(0, 0, 0, var(--bf-unit-dark)); }
/* 47.40: the corner badges and the facing triangle — nation flag top right, one morale box
   per battle group bottom right (own rectangles only, see `unitBadges`), and a triangle at
   the centre top for your own divisions and centre bottom for the enemy, pointing the way
   each line faces: both point at the middle of the map, where the two lines meet. */
.bf-unit-facing {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: 0.7rem;
  height: 0.45rem;
  background: currentColor;
  opacity: 0.6;
}
.bf-unit-facing-up { top: 0.07rem; clip-path: polygon(50% 0, 100% 100%, 0 100%); }
.bf-unit-facing-down { bottom: 0.07rem; clip-path: polygon(0 0, 100% 0, 50% 100%); }
.bf-unit-flag { position: absolute; top: 0.15rem; right: 0.2rem; line-height: 0; }
.bf-unit-flag .flag-icon { width: 1.19rem; height: 0.85rem; vertical-align: 0; }
.bf-unit-morale {
  position: absolute;
  bottom: 0.12rem;
  right: 0.25rem;
  font-size: 0.55rem;
  line-height: 1;
}
/* Every rectangle now has something in the bottom band — a facing triangle (both halves) or
   morale boxes — so the space is reserved once, for all of them. */
.bf-unit { padding-bottom: 0.95rem; }

/* 47.24: the map's perspective stage. `.bf-map-stage` is the viewport — it establishes the
   viewing distance — and `.bf-map` is the surface that tilts inside it. The hint above and the
   legend below are outside the stage, so they stay flat. */
.bf-map-stage {
  /* Lower = more dramatic tilt, higher = flatter/subtler. */
  /* 47.24: raised from 1000px together with the 20deg angle, because with the rear edge as the
     pivot the near half is magnified by 1/(1 - d/P), where d is its depth toward the viewer
     (h*sin(theta), times how far down the board the element sits). At 1000px and a 702px board
     that put the own-side counters at 1.18x across; at 2000px they measure 1.08x across and
     1.10x down, which is the layout size to the eye. The board still tilts by the same 20deg —
     this only moves the eye further off, so the depth cue is gentler and the band below the board
     shrinks with it (156px -> 74px on the line view). */
  perspective: 2000px;
  /* View from near player's baseline looking forward. */
  perspective-origin: 50% 100%;
  /* Prevent 3D edges from clipping. */
  overflow: visible;
  /* 47.24: 1rem on top so the board's rear edge is not cramped against the hint above it, and
     0.5rem below — the space under the board comes from the projection itself now (the pivot is
     the rear edge), so this padding is just breathing room, not the band. */
  padding: 1rem 2rem 0.5rem;
  /* 47.24: containing block for the zoom's stage-wide backdrop (`.bf-backdrop-stage`), so the
     table around the board is a click target as well as the board itself. */
  position: relative;
}
/* 47.24: the stage no longer clips, but the panel and the app body above it do (`.section-panel`,
   `.app-body`) — a tilted map would still be cut off at their edges. Un-clip those two, but only
   on screens that actually hold a stage, so every other screen keeps the layout it has. */
.section-panel:has(.bf-map-stage),
.app-body:has(.bf-map-stage) {
  overflow: visible;
}

/* ── 47.24: the Command map ───────────────────────────────────────────────
   Two extra lines on a rectangle (the ground it stands in, uncased, and where it is in the
   deployment sequence), and the zoomed sector below. */
.bf-unit-terrain { font-size: 0.62rem; letter-spacing: 0.02em; opacity: 0.85; }
/* 48.68: what the division did this turn — "Rallied", "New Orders — Probe" — or "Action Available" while
   it has not acted. The ready/acted outline already carries the state; this is the sentence beside it. */
.bf-unit-action { font-size: 0.62rem; font-weight: 700; letter-spacing: 0.02em; }
.bf-unit-action-free { font-weight: 600; opacity: 0.7; color: var(--pico-muted-color); }
.bf-unit-terrain-from { opacity: 0.6; }
.bf-unit-status { font-size: 0.62rem; font-weight: 600; color: var(--pico-muted-color); }

/* The sector: one zone across the whole map, the division in it, then its battle groups. */
.bf-sector .bf-zone-sector { grid-column: 1 / -1; display: block; text-align: center;
  padding-top: var(--bf-sector-drop); min-height: var(--bf-zone-depth); }
.bf-sector-unit { margin: 0.15rem auto 0.55rem; text-align: left; }
.bf-sector-groups { justify-content: center; align-items: stretch; }
/* 47.24: the group row stands a pixel proud of the board, the same trick the counters use. Without it
   the zone's flat backdrop is level with these boxes in the board's 3D space, and the sort put the
   backdrop in front — so a tap meant for a box landed on the backdrop and read as "go back". */
.bf-sector-groups { transform: translateZ(1px); }
/* 47.24: in a zoomed sector a battle group box is a control — it opens that group's engagement
   editor — and the division's own box is the third way out of the zoom. Both say so with a cursor,
   since neither looks like a button. */
.bf-sector-groups .bf-gcard { cursor: pointer; }
.bf-sector-unit { cursor: pointer; }

/* One battle group's box. Green while it is not engaged, red once it is. */
.bf-gcard {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.06rem;
  min-width: 9.5rem;
  max-width: 15rem;
  padding: 0.35rem 0.5rem;
  border: 2px solid rgba(60, 160, 70, 0.85);
  border-radius: 5px;
  background: rgba(128, 128, 128, 0.06);
  text-align: left;
  /* 47.24: battle group boxes stand proud too, a touch lower than the division they belong to.
     */
  transform: translateZ(6px);
  filter: drop-shadow(0 6px 6px var(--rep-shade));
}
.bf-gcard-engaged { border-color: rgba(192, 57, 43, 0.9); }
/* 50.15: a group that is **not on the table** — its division has not deployed, so it is part of the march
   column, not a formed unit on the ground — reads as washed-out grey whatever else is true of it. The
   green/red border above says what the group is doing (unengaged / engaged); this says where it *is*, and
   the user's point is that those cannot both be true of a column on the road. The colour comes from Pico's
   own muted pair rather than an invented hex. */
.bf-gcard.bf-gcard-offtable {
  border-style: dashed;
  border-color: var(--rep-line, rgba(128, 128, 128, 0.55));
  background: rgba(128, 128, 128, 0.14);
  opacity: 0.75;
}
.bf-gcard-arm {
  position: absolute;
  top: 0.25rem;
  left: 0.35rem;
  /* 47.24: the unit-type icon — musket, horseshoe or field gun — rather than an INF/CAV/ART chip.
     Stroked in `currentColor`, so it takes the card's own text colour. */
  line-height: 0;
  opacity: 0.85;
}
.unit-icon { width: 6rem; height: 6rem; display: inline-block; vertical-align: middle; object-fit: contain; border-radius: 4px; }
/* 48.11: the chips were sized as glyph marks (0.95rem) when they were stroke SVGs; a **photograph** at that
   size is a smudge, so they are sized to be read now — and `inline-block` with the text's middle, because a
   block image inside a table cell pushed the group's name onto the next line. The panel header is where the
   chip is the point, so it carries the largest of them. */
.cmd-group-hd .unit-icon { width: 8.8rem; height: 8.8rem; }
/* the unit-versus-unit blocks — battery → target, attacker → defender — and Movement's tapped-group popup */
.ar-who .unit-icon, .as-who .unit-icon, .bf-tapped .unit-icon { width: 8rem; height: 8rem; }
/* the board's own group box keeps a smaller mark: that card is a fixed size and the chip shares it with the
   name, the flag and the two numbers it is read for. */
.bf-gcard-arm .unit-icon { width: 1.3rem; height: 1.3rem; }
.bf-gcard-flag { position: absolute; top: 0.2rem; right: 0.3rem; line-height: 0; }
.bf-gcard-flag .flag-icon { width: 1.19rem; height: 0.85rem; vertical-align: 0; }
.bf-gcard-name { margin-top: 0.9rem; font-size: 0.78rem; font-weight: 600; }
.bf-gcard-units { font-size: 0.72rem; font-weight: 600; }
.bf-gcard-line { font-size: 0.66rem; opacity: 0.8; }
/* 47.24: the two numbers the box is read for, given their own weight rather than the shared line
   style the formation, grade and skirmish fields share. */
.bf-gcard-hits { font-size: 0.72rem; font-weight: 700; }
.bf-gcard-morale { font-size: 0.7rem; font-weight: 600; }
/* 50.15: what each of the group panel's controls is for — "Formation", "Engagement", "Terrain" — because
   three bare selects in a row say nothing about themselves. */
.cmd-field-label {
  display: block;
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  opacity: 0.7;
  margin-bottom: 0.1rem;
}
.bf-gcard-eng { font-size: 0.62rem; letter-spacing: 0.04em; text-transform: uppercase; opacity: 0.7; }
/* 48.27: the panic a battle group's combat results have dealt it this turn — the reaction, in the
   colour the palette already gives a warning. */
.bf-gcard-panic { font-size: 0.7rem; font-weight: 600; color: var(--pico-del-color); }

/* The sector's card above the map, and the card the action buttons will land in. */
.cmd-card {
  position: relative;
  margin-bottom: 0.6rem;
  padding: 0.6rem 0.7rem 0.5rem;
  border: 1px solid var(--pico-muted-border-color);
  border-radius: 8px;
  background: var(--pico-card-background-color);
}
.cmd-back { margin-bottom: 0.35rem; }
.cmd-cell { font-size: 0.62rem; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; opacity: 0.6; }
.cmd-div { font-size: 1.05rem; font-weight: 600; }
.cmd-stats { font-size: 0.75rem; color: var(--pico-muted-color); }
.cmd-actions {
  margin-top: 0.7rem;
  padding: 0.6rem 0.7rem;
  border: 1px dashed var(--pico-muted-border-color);
  border-radius: 8px;
  text-align: center;
}
/* 47.24: the division's action buttons. Real buttons, with their real enable rules — a disabled
   one is a rule the player can see, not a missing feature. */
.cmd-btns { display: flex; flex-wrap: wrap; gap: 0.4rem; justify-content: center; }
.cmd-btns .opt-btn { margin: 0; }
.cmd-btns .opt-btn[disabled] { opacity: 0.45; }
/* 47.24: the rally roll lines the Rally button puts in front of the player — one per group with a
   die showing, and the single button that fires every die still waiting. */
.cmd-rally {
  margin-top: 0.6rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--pico-muted-border-color);
  text-align: center;
}
.cmd-rally-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  margin: 0.3rem 0;
}
.cmd-rally-name { font-size: 0.72rem; font-weight: 600; }
/* 47.24: what the rally die has to reach — the figure the player rolls against, beside the die. */
.cmd-rally-target { font-size: 0.74rem; font-weight: 700; }
.cmd-rally .opt-btn { margin: 0; }
/* 48.3: the division's terrain, in the same box as the action buttons rather than tucked inside
   deploy or march — it is state, not an action, so it stays usable after the division has acted. */
.cmd-terrain {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  margin-top: 0.5rem;
  padding-top: 0.45rem;
  border-top: 1px dashed var(--pico-muted-border-color);
}
.cmd-terrain-label {
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.7;
}
/* 47.24: the engagement editor the group boxes open — march's own table, so it needs only the card's
   spacing rather than a second set of styles. */
.cmd-engage {
  margin-top: 0.6rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--pico-muted-border-color);
  text-align: left;
}
/* 47.24: the deploy/march popup and the orders popup, rendered in the card rather than over the map;
   march's carries its own `.bf-popup` styling, so this only adds the card's spacing. */
.cmd-deploy, .cmd-orders {
  margin-top: 0.6rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--pico-muted-border-color);
  text-align: center;
}
.cmd-deploy .bf-popup, .cmd-orders .bf-popup { position: static; box-shadow: none; }
/* 47.24: the die row for an order that needs a roll — the Orders screen's own controls, shown here
   because the card has no table below the map for them to live in. */
.cmd-orderroll {
  margin-top: 0.6rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--pico-muted-border-color);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.4rem;
}
/* 47.24: the same, for a deploy roll: march's own controls, rendered in the card. */
.cmd-deployroll {
  margin-top: 0.6rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--pico-muted-border-color);
  text-align: center;
}
/* 47.24: one battle group's panel, opened by clicking its box in the sector — its full details and its
   battalions' formation diagrams, so the box does not have to carry everything. */
.cmd-group {
  margin-top: 0.6rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--pico-muted-border-color);
  text-align: left;
}
.cmd-group-hd { display: flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; }
/* 48.11: the chip's size for this header is set once, with the other chip sizes — a second rule here used to
   shrink it to 0.85rem and would have won the cascade, keeping the header's photograph a smudge. */
.cmd-group-sub { opacity: 0.75; font-size: 0.72rem; }
.cmd-group-line { font-size: 0.68rem; opacity: 0.85; margin-top: 0.15rem; }
.cmd-group-panic { color: var(--pico-del-color); font-weight: 600; opacity: 1; }
/* 51.25: the group's trail — the table of what happened to it, oldest first. Pico's own semantic colours,
   like the lines above it, so a card reads as one thing rather than as a widget bolted onto it. */
.cmd-group-trail { width: 100%; margin-top: 0.35rem; border-collapse: collapse; font-size: 0.68rem; }
.cmd-group-trail th, .cmd-group-trail td { padding: 0.08rem 0.35rem; text-align: left; border: 0; }
.cmd-group-trail th { color: var(--pico-muted-color); font-weight: 500; }
.cmd-group-trail tbody td { border-top: 1px solid var(--pico-muted-border-color); }
.cmd-group-trail th:nth-child(1), .cmd-group-trail th:nth-child(2),
.cmd-group-trail td:nth-child(1), .cmd-group-trail td:nth-child(2) {
  width: 1%; white-space: nowrap; color: var(--pico-muted-color);
}
.cmd-group-diagrams {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.45rem 0;
}
/* 47.24: the rolled march — the three base distances in the current scale, the die with its bonus,
   the distance rolled, and the ground the column ends in. */
/* The diagram rows are emoji, so they are laid out on their own lines without any wrapping of ours. */
.fdg {
  line-height: 1;
  font-size: 1.05rem;
  padding: 0.25rem 0.35rem;
  border: 1px solid var(--rep-line, rgba(128, 128, 128, 0.45));
  border-radius: 4px;
  /* 51.29: the open-order line is **twice as wide** as the others — a whole base's gap between the bases — so
     the host scrolls rather than clipping it into a shape the player would misread. */
  overflow-x: auto;
}
.fdg-line { display: block; white-space: pre; }
/* 51.29: and the two rows of an open order are staggered by these two offsets — a couple of pixels each way,
   which is what makes a line of glyphs read as jagged rather than ruled. */
.fdg-up { position: relative; top: -2px; }
.fdg-down { position: relative; top: 2px; }
/* 48.42: blocks stacked in a column are spaced so the gap between them reads — about half a block. */
.fdg-line + .fdg-line { margin-top: 0.5em; }
/* 48.38: the square is drawn as a single base inside an empty box. */
.fdg-box { border: 2px solid #000; border-radius: 0; padding: 0.45rem 0.7rem; }
/* 48.39: the battle group whose panel is open — a ring *outside* the engagement border, in the blue
   of the formation squares (Pico's blue-500, the value the 🟦 reads as). */
.bf-gcard-selected { outline: 3px solid #3c71f7; outline-offset: 2px; }
.cmd-march {
  margin-top: 0.6rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--pico-muted-border-color);
  text-align: center;
}
.cmd-march-dist { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; font-size: 0.68rem; }
.cmd-march-opt { opacity: 0.65; }
.cmd-march-here { opacity: 1; font-weight: 700; }
.cmd-march-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  margin-top: 0.45rem;
}
.cmd-march-result { font-weight: 600; }
.bf-unit-id { font-size: 0.62rem; font-weight: 700; opacity: 0.7; }
/* 48.17: the corps sits above the division's own name. */
.bf-unit-corps { font-size: 0.66rem; letter-spacing: 0.03em; opacity: 0.75; }
/* 48.43: a single group's engagement picker in its panel. */
.cmd-group-engage { margin-top: 0.4rem; }
/* 48.84: the same group's change-formation picker. */
.cmd-group-formation { margin-top: 0.4rem; }
/* 48.48: the ground the group stands in. */
.cmd-group-terrain { margin-top: 0.3rem; font-size: 0.75rem; }
.bf-unit-name { font-size: 0.78rem; font-weight: 600; }
.bf-unit-orders {
  font-size: 0.67rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  opacity: 0.8;
}
.bf-popup {
  /* Down a column, `flex-basis` is the vertical axis — stretch across instead. */
  align-self: stretch;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-top: 0.15rem;
  padding: 0.35rem;
  border: 1px solid var(--rep-line, rgba(128, 128, 128, 0.45));
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.18);
  /* 47.24: the popup sits up off the counter it belongs to, so it casts like one. */
  filter: drop-shadow(0 6px 6px var(--rep-shade));
}
.bf-popup-btn { font-size: 0.7rem; padding: 0.2rem 0.45rem; }

/* 47.15: the corps group row in the orders table — the tree, without repeating the
   corps name on every division row. */
.corps-group-row td {
  background: rgba(128, 128, 128, 0.1);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  opacity: 0.85;
  padding-top: 0.3rem;
  padding-bottom: 0.3rem;
}
/* 47.20: the division block inside a corps group — the second level of the tree, so the
   battle-group rows below it do not have to repeat which division they belong to. */
.division-group-row td {
  background: rgba(128, 128, 128, 0.05);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  opacity: 0.9;
  padding-top: 0.22rem;
  padding-bottom: 0.22rem;
}

/* 47.20: the zoomed view on Movement — a division's groups inside its own rectangle, laid
   out right to left so the leading brigade is on the right, as units deployed in life.
   The backdrop is the map's background: it sits under the units, so a click that misses
   every rectangle is its own event (which is what "click the background to zoom out"
   needs — a handler on the zone itself would fire for unit clicks too). */
.bf-backdrop {
  position: absolute;
  inset: 0;
  z-index: 0;
  cursor: pointer;
}
.bf-zone-label { z-index: 1; }
.bf-unit,
.bf-group,
.bf-popup { position: relative; z-index: 1; }
.bf-groups {
  display: flex;
  flex-direction: row-reverse;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.3rem;
  margin-top: 0.35rem;
  width: 100%;
}
.bf-group {
  /* 47.24: so a badge or a shadow can hang off the box itself, the way the division's does. */
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.05rem;
  padding: 0.28rem 0.45rem;
  border-radius: 4px;
  border: 1px solid var(--rep-amber-soft, rgba(245, 180, 0, 0.4));
  background: rgba(0, 0, 0, 0.18);
  color: inherit;
  font: inherit;
  line-height: 1.15;
  text-align: left;
  cursor: pointer;
  /* 47.24: a battle group box stands on the counter face, so it casts like one too. */
  filter: drop-shadow(0 6px 6px var(--rep-shade));
}
.bf-group-open { box-shadow: inset 0 0 0 2px var(--rep-amber, #f5b400); }
.bf-group-idle { opacity: 0.45; font-size: 0.66rem; padding: 0.28rem 0.45rem; }

/* 48.23/48.24: the schnitzel fire block, for skirmishers and for guns. */
.sk-fire, .ar-fire { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin-top: 0.5rem; }
.sk-who, .ar-who { font-weight: 600; font-size: 0.8rem; }
.sk-n { display: flex; align-items: center; gap: 0.3rem; font-size: 0.75rem; }
/* What the app read off the two groups. */
.ar-facts { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.ar-chip { font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.05em;
  padding: 0.1rem 0.35rem; border: 1px solid var(--pico-muted-border-color); border-radius: 3px; opacity: 0.85; }
/* 50.22: the automatic bombardment's report — what the engaged batteries did for the player, as a list of
   shots rather than a paragraph about them. */
.ar-auto { list-style: none; margin: 0.2rem 0 0.6rem; padding: 0; font-size: 0.78rem; }
.ar-auto li { padding: 0.12rem 0 0.12rem 0.5rem; border-left: 2px solid var(--rep-soil-difficult-edge);
  margin-bottom: 0.15rem; }
.sk-done { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.75; }

/* 48.26: the schnitzel assault block. */
.as-fire { margin-top: 0.5rem; }
.as-who { font-weight: 600; font-size: 0.8rem; }
.as-facts { font-size: 0.7rem; opacity: 0.8; margin-bottom: 0.4rem; }

/* ── 48.67: the `command3d` experiment ─────────────────────────────────────────
   The canvas host, its HUD and the toggle on the Command screen. Everything here is
   chrome around the scene; the board's own colours are read out of the stylesheet by
   `www/command3d.js` (the `--rep-soil-*` and `--rep-state-*` tokens above), so this
   block states no palette of its own. */
.bv-toggle-row { display: flex; justify-content: flex-end; margin-bottom: 0.35rem; }
.bv-toggle { font-size: 0.72rem; padding: 0.2rem 0.6rem; }
.bv-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  min-height: 260px;
  border: 1px solid var(--pico-muted-border-color);
  border-radius: 6px;
  overflow: hidden;
  background: var(--pico-background-color);
  /* Orbiting is a drag: the page must not scroll underneath it. */
  touch-action: none;
  /* 51.4: and the browser must not answer a **long press** with its own gesture. On iOS a press held on a
     canvas raises the selection callout and hands the touch to the system, which is why the ruler's held
     finger never arrived there — `touch-action` alone does not suppress that. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
.bv-canvas { display: block; width: 100%; height: 100%; }
.bv-hud { position: absolute; top: 0.35rem; left: 0.35rem; display: flex; align-items: center; gap: 0.35rem;
  pointer-events: none; flex-wrap: wrap; max-width: calc(100% - 0.7rem); }
.bv-hud > * { pointer-events: auto; }
.bv-hud-btn { font-size: 0.68rem; padding: 0.15rem 0.45rem; }
.bv-hud-btn.active { outline: 2px solid var(--rep-state-ready); }
/* 51.4: the measuring stick's own layer — an SVG over the canvas, taking no pointer of its own (the
   board's input is the canvas, and the mode is the board's state). The dash is `stroke-dasharray` rather
   than a 3D line, so the figure under it stays text and reads at any zoom; both take Pico's own primary
   rather than a colour of their own. */
.bv-measure { position: absolute; inset: 0; display: none; pointer-events: none; }
.bv-measure.on { display: block; }
.bv-measure-svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.bv-measure-line { stroke: var(--pico-color-sand-800, #32302b); stroke-width: 2; stroke-dasharray: 7 5; }
/* 52.9: the legs a click has already committed — the same stroke as the live leg, quieter, so the route reads as
   one path with the part being aimed standing out. */
.bv-measure-trail { fill: none; stroke: var(--pico-color-sand-800, #32302b); stroke-width: 2;
  stroke-dasharray: 7 5; opacity: 0.55; }
/* 52.9: the route's total, beside the Measure button that opens the mode — waypoints, yards, and the tabletop
   measure. A tool label rather than a chip on the board, so it does not compete with the leg's own figure. */
/* 52.14: the received-but-unactivated order on the division's info line - the second order word. */
.gp-i-activating { color: var(--rep-amber); font-weight: 600; margin-left: 0.4rem; }
/* 52.15: an ungraded reserve is the state that stops a division leaving its sector, so its word is the control. */
.gp-i-orders-ungraded { background: none; border: 0; padding: 0; font: inherit;
  color: var(--rep-amber); cursor: pointer; text-decoration: underline dotted; }
/* 52.8: the door to the division's own history. */
.gp-i-history { background: none; border: 0; padding: 0 0.2rem; font: inherit; color: inherit;
  cursor: pointer; opacity: 0.7; }
.gp-i-history:hover { opacity: 1; }

.bv-measure-summary { font: 500 0.66rem/1.2 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #fff; background: var(--pico-color-sand-800, #32302b); border-radius: 4px; padding: 0.1rem 0.4rem;
  white-space: nowrap; }
/* 52.9: the trip in leagues, on the HUD's next line if the waypoints figure has taken the first — a league being an
   hour of marching, which is the measure a column is actually planned in. */
.bv-measure-leagues { font: 600 0.68rem/1.2 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #fff; background: var(--pico-color-sand-800, #32302b); border-radius: 4px; padding: 0.1rem 0.4rem;
  white-space: nowrap; }
/* 52.9: one chip per leg, so the committed legs keep their figures as the trail does. */
.bv-measure-chips { position: absolute; inset: 0; }
.bv-measure-dot { fill: var(--pico-color-sand-800, #32302b); }
.bv-measure-chip {
  position: absolute; transform: translate(-50%, -50%); white-space: nowrap;
  font: 500 0.7rem/1.3 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #fff; background: var(--pico-color-sand-800, #32302b);
  border-radius: 4px; padding: 0.1rem 0.4rem; font-weight: 600;
}

/* 50.5: the tactical phase's panel — the divisions in the fight and the acting division's groups. The rule
   the colours carry is the user's own: a division that can fight is green (`--rep-state-ready`, the token
   the board's own ready border uses), one with nothing to do this phase is grey. The selected groups take
   the app's existing chosen-row treatment (`tr.active`), so a multi-selection reads the way every other
   picker in the app reads. */
.tac-panel { display: flex; flex-direction: column; gap: 0.45rem; }
.tac-div-row { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.tac-div { font-size: 0.72rem; padding: 0.2rem 0.5rem; background: none; color: inherit;
  border: 1px solid var(--pico-muted-border-color); }
.tac-div.tac-ready { border-color: var(--rep-state-ready); color: var(--rep-state-ready); }
.tac-div.tac-idle { opacity: 0.5; }
.tac-div.tac-here { outline: 2px solid var(--rep-state-ready); }
.tac-tbl .tac-when { text-align: left; font-size: 0.7rem; }
.tac-row.tac-acts td:first-child { color: var(--rep-state-ready); }
.tac-none { display: none; }
.bv-pad { display: none; }

/* 48.88's follow-up: the battle group's editor is a modal over the map — wider than the restore dialog
   because it carries the formation diagram and the group's own pickers, and scrollable because on a phone
   it is taller than the screen. It leaves the top of the board visible on purpose: the map is what the
   group is being read against. */
.cmd-group-modal { align-items: flex-start; padding-top: 4rem; }
/* The dismiss strip fills the viewport behind the card, and the card is positioned above it: a click that
   lands outside the fields hits the strip and closes the modal, and one that lands on them does not. */
.modal-dismiss { position: absolute; inset: 0; }
/* 50.34: a card has to out-stack the backdrop's dismiss layer. The dismiss is `position: absolute` and an
   unpositioned card paints *under* it, so every click on the card's own buttons lands on the dismiss instead —
   which is what made Save Game do nothing at all (found by pressing it with a real pointer event rather than
   `element.click()`). The group modal carried its own `position: relative` for the same reason; this is the
   general rule, and it applies to every card in a `.modal-backdrop`. */
.modal-backdrop > .modal-card { position: relative; z-index: 1; }
.cmd-group-modal .modal-card { position: relative; z-index: 1; max-width: 32rem; width: 100%; max-height: 78vh; overflow-y: auto; }
.cmd-group-modal .cmd-group { margin-top: 0; }

/* 48.88: the overlay's attribution, directly under the canvas the tiles are drawn on — "clearly visible"
   is the licence's own wording, so the credit is stated rather than tucked away. The controls live on the
   map card instead (`new_game`'s map editor), with the board they describe. */
.ng-map-geo { width: 14ch; }
.ng-map-3d-btn { font-size: 0.68rem; padding: 0.15rem 0.5rem; margin-left: auto; }
.ng-map-3d { margin-top: 0.6rem; }
.bv-credit { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.15rem 0.4rem;
  margin-top: 0.3rem; font-size: 0.68rem; color: var(--pico-muted-color); }
.bv-credit a { color: inherit; }
.bv-credit-sep { opacity: 0.6; }
.bv-map-note { margin-left: auto; font-style: italic; }

/* 48.81: a unit on the pushing orders (Probe / Assault / Exploit) must spend at least half of its
   allowance, so the figure is printed in Pico's own alarm colour to say so — the number itself is
   the unit's move, unchanged, and the assault-orders bonus is already in it. */
.opt-btn.mv-attack { color: var(--pico-del-color); }

/* ── 48.73/48.75: the map editor in the wizard's settings step ──────────────────
   Controls only — the block's own summary line says what is inside it, and no prose
   is written into the UI. Colours are Pico's, including the fault line's alarm red. */
.ng-map { margin: 0.6rem 0 0.9rem; }
.ng-map-toggle { width: 100%; display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; }
.ng-map-sum { opacity: 0.7; font-weight: 400; }
.ng-map-sec { margin-top: 0.6rem; padding-top: 0.5rem; border-top: 1px dashed var(--pico-muted-border-color); }
.ng-map-row { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.ng-map-f { display: flex; align-items: center; gap: 0.25rem; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em; }
/* 48.84: the numbers are what the row is *for*, and at 6ch with the browser's own spinner sitting on
   top of them the value was the one thing you could not read. The spinner goes, the field gets room. */
.ng-map-f input { width: 8ch; font-size: 0.8rem; margin: 0; appearance: textfield; -moz-appearance: textfield; }
.ng-map-f input::-webkit-outer-spin-button,
.ng-map-f input::-webkit-inner-spin-button { -webkit-appearance: none; appearance: none; margin: 0; }
/* 48.88: the coordinates are the longest values on the card — `-0.12780` is eight characters before the
   sign is counted — so they get their own width rather than the 8ch the measurements use, and their own
   spinner reset: the plain `.ng-map-f input` rules above are more specific than a bare class, which is
   what kept them at 8ch with the arrows sitting on the digits. */
.ng-map-f input.ng-map-geo { width: 14ch; appearance: textfield; -moz-appearance: textfield; text-transform: none; }
.ng-map-f input.ng-map-geo::-webkit-outer-spin-button,
.ng-map-f input.ng-map-geo::-webkit-inner-spin-button { -webkit-appearance: none; appearance: none; margin: 0; }
.ng-map-name { flex: 1 1 12ch; min-width: 10ch; font-size: 0.8rem; }
.ng-map-sector { margin-top: 0.45rem; }
.ng-map-tick { margin-top: 0.25rem; }
.ng-map-tick .opt-btn { font-size: 0.66rem; padding: 0.1rem 0.4rem; }
.ng-map-fault { margin-top: 0.3rem; font-size: 0.72rem; color: var(--pico-del-color); }
/* 48.84: the expanded editor is a card, with the board drawn across its top — never more than half the
   page tall, so the fields below it stay on screen while the picture is being read. */
.ng-map-card { padding: 0.8rem 0.9rem; }
/* 48.73: the picture is the editor now — every sector is edited through it — so it takes almost the whole
   page: nearly full height, the card's full width, and the bands carry the state the rows used to. */
.ng-map-pic { display: block; width: 100%; height: auto; max-height: 88vh; margin: 0 auto 0.7rem; }
.ng-map-band-t { font-size: 11px; font-weight: 600; letter-spacing: 0.05em; }
.ng-map-band-d { font-size: 9px; fill: var(--pico-muted-color, currentColor); opacity: 0.85; }
/* 48.73: neither line is a target — the label was fixed first, and the *details* had the same fault: on the
   far half's shorter bands they also spill past their band, so a click on the very text a player is reading
   landed on the wrong sector (or on nothing). */
.ng-map-band-t, .ng-map-band-d { pointer-events: none; }
.ng-map-band { fill: var(--pico-card-sectioning-background-color); stroke: var(--pico-muted-border-color); stroke-width: 0.7; }
.ng-map-band-them { fill: var(--pico-background-color); }
/* 48.73: a band is the way into its sector's modal, and says so on hover and while open. The strokes use
   tokens that resolve in *every* theme — `--pico-primary` does not (Pico defines it in the light scope
   only), and an invalid `stroke` declaration falls back to `none`, which is why the centre line below had
   never painted; `--rep-amber` carries its own fallback in the palette above. */
.ng-map-band { cursor: pointer; }
.ng-map-band:hover { stroke: var(--rep-amber); stroke-width: 1.6; }
.ng-map-band-open { stroke: var(--rep-amber); stroke-width: 1.8; }
/* 48.73: the sector rows are one line each — name, what a division arrives on, and the two ground lists
   — so the labels are the small uppercase ones the fields use, and each chip row can wrap under them. */
.ng-map-sector { align-items: center; gap: 0.35rem; }
.ng-map-edit { padding: 0.1rem 0.45rem; font-size: 0.8rem; line-height: 1; }
.ng-map-band-t { font-size: 9px; fill: var(--pico-muted-color, currentColor); text-transform: uppercase; letter-spacing: 0.04em; }
/* 48.73: the label is not a target. Without this the text sits over its band and takes the pointer, so the
   hover highlight (and the click that opens the modal) died the moment the pointer was over the name. */
.ng-map-band-t { pointer-events: none; }
.ng-map-band-t-them { font-size: 8px; opacity: 0.75; }
.ng-map-centre { stroke: var(--pico-muted-color, currentColor); stroke-width: 1.2; stroke-dasharray: 7 4; }

/* ── 50.20: the gameplay frame ─────────────────────────────────────────────────
   Game play is three bands on one screen: the info strip and the global buttons along the
   top, the board filling everything between, and the phase's context buttons pinned at the
   bottom. Nothing here scrolls — that is the point of the frame, and it is also what stops
   a scroll gesture and the board's own orbit from fighting over the same drag. */
.gp-shell {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--pico-background-color);
  /* iPhone home-bar / notch: the frame is the screen, so it pads itself rather than the page. */
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

.gp-top {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.6rem;
  padding: 0.35rem 0.6rem;
  border-bottom: 1px solid var(--rep-line);
  background: linear-gradient(180deg, #2a2e36 0%, #1c1f24 100%);
  z-index: 2;
}

.gp-info {
  display: flex;
  align-items: center;
  gap: 0.25rem 0.6rem;
  flex: 1 1 auto;
  min-width: 0;
  flex-wrap: wrap;
  font-size: 0.8rem;
  color: var(--rep-cream);
  letter-spacing: 0.02em;
}

.gp-turn { color: var(--rep-amber); font-weight: 700; display: inline-flex; align-items: center; gap: 0.3rem; }
.gp-time { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700; background: #14171c; padding: 0.1rem 0.4rem; border-radius: 4px; }
.gp-phase { font-weight: 600; opacity: 0.9; }
.gp-player { display: inline-flex; align-items: center; gap: 0.3rem; }

.gp-globals { display: flex; align-items: center; gap: 0.35rem; }

.gp-btn {
  appearance: none;
  -webkit-appearance: none;
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 0.35rem 0.7rem;
  border-radius: 6px;
  border: 1px solid var(--rep-line);
  background: var(--rep-control-bg);
  color: var(--rep-cream);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  white-space: nowrap;
}
.gp-btn:hover:not(:disabled) { border-color: var(--rep-amber-soft); color: var(--rep-amber); }
.gp-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.gp-btn-global { font-size: 0.75rem; padding: 0.3rem 0.6rem; }
.gp-btn-done { background: var(--rep-amber); color: var(--rep-on-amber); border-color: var(--rep-amber); }
.gp-btn-done:hover:not(:disabled) { background: var(--rep-amber-soft); color: var(--rep-on-amber); }

/* The centre is the board, and the board is whatever room is left over. The credits and the legend come
   out of `renderBoard` after the stage, so the band is a column: the stage takes the room, they keep
   their own line — attribution is a licence condition and must not be clipped away. */
.gp-centre {
  flex: 1 1 auto;
  min-height: 0;
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
/* 50.20: a pretzel game has no order of battle, so the centre holds the phase's own page instead of the
   canvas — and a page has to be reachable, so *that* band is the one thing that scrolls. The frame's
   bands stay where they are; the page moves under them. */
.gp-centre:not(:has(.bv-stage)) {
  overflow-y: auto;
}
.gp-centre .bv-stage {
  flex: 1 1 auto;
  width: 100%;
  height: auto;
  min-height: 0;
  aspect-ratio: auto;
  border: 0;
  border-radius: 0;
}
.gp-centre .bv-credit,
.gp-centre .bf-legend {
  flex: 0 0 auto;
  padding: 0 0.5rem 0.2rem;
}

.gp-bar {
  flex: 0 0 auto;
  min-height: 2.7rem;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.6rem calc(0.4rem + env(safe-area-inset-bottom));
  border-top: 1px solid var(--rep-line);
  background: var(--rep-paper-2);
  overflow-x: auto;
  z-index: 2;
}
.gp-bar-note { font-size: 0.75rem; color: var(--pico-muted-color); }
.gp-btn-action { flex: 0 0 auto; }

/* The phase's panel, over the board it is read against. */
.gp-modal { align-items: flex-end; padding: 0; }
.gp-modal-card {
  position: relative;
  z-index: 1;
  width: 100%;
  /* 51.7: 90% of the viewport rather than a fixed 46rem — the user's reason: *"some of the entry fields get
     truncated if its too narrow"*. `border-box` because the *card* is what should measure 90%: with the default
     content-box the padding and border sat outside the cap and the card came out at 93% of the window. The card
     is `width: 100%` inside a full-width backdrop, so these two lines are what decide how wide it gets. (The
     nested `.cmd-group-modal .modal-card` keeps its own cap: it lives *inside* a card rather than over the page,
     so 90vw there would overflow its own container.) */
  box-sizing: border-box;
  max-width: 90vw;
  max-height: 82vh;
  overflow-y: auto;
  margin: 0 auto;
  border-radius: 10px 10px 0 0;
}
.gp-modal-head { display: flex; align-items: center; gap: 0.5rem; position: sticky; top: 0; background: var(--pico-card-background-color, var(--rep-paper-2)); padding-bottom: 0.3rem; }
.gp-modal-title { font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; font-size: 0.85rem; }
.gp-modal-x { margin-left: auto; appearance: none; background: none; border: 0; color: inherit; cursor: pointer; font-size: 1rem; padding: 0.2rem 0.4rem; }
/* 50.27: the battle group's neighbours, centred on the card between a title that is always short and an X
   that stays hard right — centred off the head rather than laid out between them, or the X's wider hit box
   would drag the pair off centre. Only a group panel renders it. */
.gp-modal-nav { position: absolute; left: 50%; transform: translateX(-50%); display: flex; gap: 0.15rem; }
/* 50.27: the pair reads in the **current player's colour** — the swatch the app paints that player with
   everywhere else (`theme.swatch_hex()`, handed in as `--gp-nav-ink` by `renderGroupNav`). It falls back to
   the card's own ink so the head is still readable if that variable ever goes missing. */
.gp-nav-btn { appearance: none; background: none; border: 0; color: var(--gp-nav-ink, currentColor); cursor: pointer; font-size: 1rem; line-height: 1; padding: 0.2rem 0.45rem; border-radius: 6px; }
.gp-nav-btn:hover:not(:disabled) { background: color-mix(in srgb, currentColor 12%, transparent); }
.gp-nav-btn:disabled { opacity: 0.35; cursor: default; }
/* 47.32: the **Rules Manual** is the one panel whose body is a document rather than a short block, and an
   empty one collapsed the bottom sheet to a sliver against the bottom edge of the screen — the reporter's
   "why does the rules menu appear at the bottom of the page". The sheet stays a sheet (it is the frame's own
   shape, and a manual is long), but it now has a floor to hold the page it is going to be. */
.gp-modal-card:has(.rules-manual) { min-height: 62vh; }

/* The board stays visible behind the card on a phone — it is what the panel is about. */
.gp-modal .modal-dismiss { background: transparent; }

@media (max-width: 480px) {
  .gp-info { font-size: 0.72rem; }
  .gp-btn { font-size: 0.72rem; padding: 0.3rem 0.5rem; }
  .gp-modal-card { max-height: 76vh; }
}

/* 50.20: the frame is the screen, so the page behind it neither scrolls nor shows a margin — the
   leftover body margin is the difference between "no scrolling" and eight pixels of it. */
html:has(.gp-shell),
body:has(.gp-shell) {
  overflow: hidden;
  margin: 0;
  height: 100%;
}

/* ── 50.20: the info line ──────────────────────────────────────────────────────
   Between the board and the action bar: what is selected, in one row, and the board's ground key at
   the far end. The action row above it keeps nothing but actions, which is the user's ruling — info
   moves with the selection, actions stay where the thumb expects them. */
.gp-infoline {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 0.55rem;
  width: 100%;
  margin: 0;
  padding: 0.3rem 0.75rem;
  border: 0;
  border-top: 1px solid var(--rep-line);
  background: var(--rep-paper-2);
  color: var(--pico-muted-color);
  font-family: inherit;
  font-size: 0.8rem;
  line-height: 1.35;
  letter-spacing: 0.02em;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  cursor: pointer;
}
.gp-infoline:hover { background: var(--rep-control-bg); }
.gp-infoline:focus-visible { outline: 2px solid var(--rep-amber); outline-offset: -2px; }
/* The selected division's name leads; everything after it is separated by a middot rather than by its own
   markup, so adding a field to the summary costs nothing here. */
.gp-i-name { color: var(--rep-cream); font-weight: 700; letter-spacing: 0.03em; }
.gp-i-fields {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  gap: 0.55rem;
  min-width: 0;
  overflow: hidden;
}
.gp-i-fields > span + span::before { content: "\00b7"; margin-right: 0.55rem; opacity: 0.45; }
.gp-i-hint { color: var(--pico-muted-color); }
.gp-i-ground { color: var(--rep-cream); }
/* 50.32: the info line sets the division's ground in place — the ground word is a button, and pressing it opens
   the grounds this sector allows as buttons beside it. Buttons, not a select: this row's select change never
   reached Zig while the same handler took one from a panel. */
/* Each ground button wears the ground's own colour: the soil palette the board's zones and rectangles read,
   so the word and the ground it names cannot be read two ways. The ground's slug is the class, and the three
   rules below only map that slug onto the palette. */
.gp-ground-open { --gp-ground-wash: var(--rep-soil-open-wash); --gp-ground-strong: var(--rep-soil-open-wash-strong);
  --gp-ground-edge: var(--rep-soil-open-edge); }
.gp-ground-rough { --gp-ground-wash: var(--rep-soil-rough-wash); --gp-ground-strong: var(--rep-soil-rough-wash-strong);
  --gp-ground-edge: var(--rep-soil-rough-edge); }
.gp-ground-difficult { --gp-ground-wash: var(--rep-soil-difficult-wash);
  --gp-ground-strong: var(--rep-soil-difficult-wash-strong); --gp-ground-edge: var(--rep-soil-difficult-edge); }
.gp-i-ground-btn { appearance: none; background: none; border: 0; padding: 0 0.15rem; margin: 0; color: inherit;
  font: inherit; font-weight: 700; cursor: pointer; }
/* the row's word is the ground it stands on, so it carries that ground's wash and edge, not the amber marker */
.gp-i-ground-btn[class*="gp-ground-"] { background: var(--gp-ground-wash); border-radius: 4px;
  text-decoration: underline dotted var(--gp-ground-edge); text-underline-offset: 2px; }
.gp-i-ground-open { box-shadow: inset 0 0 0 1px var(--rep-line); }
.gp-i-ground-opt { appearance: none; border: 1px solid var(--gp-ground-edge); background: var(--gp-ground-wash);
  color: var(--rep-control-text); font: inherit; font-size: 0.72rem; font-weight: 600; padding: 0.1rem 0.35rem;
  margin-left: 0.25rem; border-radius: 4px; cursor: pointer; }
.gp-i-ground-opt:hover { background: var(--gp-ground-strong); }
.gp-i-ground-sel { display: none; }
/* the card's own copy of the same picker keeps its width (the inline style it used to carry) */
.cmd-terrain select { max-width: 12ch; }
.gp-i-orders { font-weight: 600; }
.gp-soil-key { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 0.3rem; padding-left: 0.6rem; }
.gp-soil-key::before { content: none; }
.gp-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  display: inline-block;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.35);
}
.gp-dot-open { background: var(--rep-soil-open-solid); }
.gp-dot-rough { background: var(--rep-soil-rough-solid); }
.gp-dot-difficult { background: var(--rep-soil-difficult-solid); }

/* 50.20: the "divisions still to act" confirmation — Done in Command, with a division or more left to act.
   The existing modal shape, sized like a question rather than a panel. */
.gp-confirm { max-width: 30rem; }
.gp-confirm-list { display: flex; flex-wrap: wrap; gap: 0.25rem 0.5rem; margin-bottom: 0.5rem; }
.gp-wait-name {
  font-weight: 700;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--rep-cream);
  background: var(--rep-control-bg);
  border: 1px solid var(--rep-line);
  border-radius: 6px;
  padding: 0.15rem 0.45rem;
}
.gp-confirm-why { font-size: 0.8rem; color: var(--pico-muted-color); margin: 0; }
