﻿/* ===================================================================
   WARMONGER — Phase 1 scaffold
   Single file. No frameworks. No build tools.
   This file owns: landscape lock + rotate prompt, the cube-coordinate
   hex grid (10x18), and the Army/Combat zoom states.
   Game data (units/characters/stats) is loaded later — none here yet.
   =================================================================== */

/* ---- Design tokens (from VISUAL_BRIEF.md) ---- */
:root {
  --bg-void:        #08080f;
  --bg-panel:       #0d0d1a;
  --bg-panel-mid:   #12121f;

  --gold-bright:    #f5d87c;
  --gold-mid:       #c9a84c;
  --gold-dim:       #8a6e2a;

  --glow-white:     rgba(255, 255, 255, 0.9);
  --glow-gold:      rgba(245, 216, 124, 0.6);
  --glow-gold-soft: rgba(201, 168, 76, 0.2);

  --human-blue:     #2a5c9a;
  --human-glow:     #4a8fd4;
  --orc-green:      #2a7a3a;
  --orc-glow:       #4aad5a;

  --blood-red:      #8b1a1a;
  --blood-bright:   #c42020;
  --fire-orange:    #d4622a;

  --text-primary:   #f0e8d8;
  --text-secondary: #a89878;
  --text-muted:     #5a5468;
  --text-gold:      #f5d87c;

  --font-display: 'Cinzel Decorative', serif;
  --font-body:    'Cinzel', serif;
  --font-data:    'JetBrains Mono', monospace;

  /* board geometry — single source of truth, flip COLS/ROWS here */
  --hex-stroke: rgba(138, 110, 42, 0.45);
}

* { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
html, body { width: 100%; height: 100%; overflow: hidden; background: var(--bg-void); }
body {
  font-family: var(--font-body);
  color: var(--text-primary);
  position: fixed; inset: 0;
  touch-action: none;
}

/* =========================  THEMED SCROLLBARS  =========================
   Aged brass thumb riding a dark recessed channel — replaces the stark white
   OS-default bar that clashed with the wood/parchment skin (Si 2026-07-20).
   Loaded by BOTH index.html and home.html, so it themes every page at once.
   Per-element bars that deliberately opt out with `scrollbar-width:none` (init
   track, faction bar, map scroll, shop body …) keep their own higher-specificity
   rule and stay hidden — this only dresses the ones that were already showing. */
* { scrollbar-width: thin; scrollbar-color: #9c7c30 rgba(8, 8, 14, 0.55); }
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track {
  background: linear-gradient(90deg, #05050a, #14110a);
  border: 1px solid rgba(138, 110, 42, 0.30);
  box-shadow: inset 1px 0 3px rgba(0, 0, 0, 0.6);
}
::-webkit-scrollbar-thumb {
  border-radius: 6px;
  border: 1px solid #3d3011;
  background: linear-gradient(180deg, #d9b45a 0%, #9c7c30 46%, #6d5620 100%);
  box-shadow: inset 0 1px 0 rgba(245, 216, 124, 0.55), inset 0 -2px 3px rgba(0, 0, 0, 0.45);
}
::-webkit-scrollbar-thumb:hover {
  background: linear-gradient(180deg, #f5d87c 0%, #c9a84c 46%, #8a6e2a 100%);
  border-color: #5a4718;
}
::-webkit-scrollbar-thumb:active {
  background: linear-gradient(180deg, #c9a84c 0%, #8a6e2a 46%, #5f4a1c 100%);
}
::-webkit-scrollbar-button { display: none; height: 0; width: 0; }
::-webkit-scrollbar-corner { background: #05050a; }

/* =========================  ROTATE PROMPT  ========================= */
#rotate-prompt {
  display: none;
  position: fixed; inset: 0; z-index: 1000;
  background: var(--bg-void);
  flex-direction: column; align-items: center; justify-content: center;
  gap: 28px; text-align: center; padding: 24px;
}
@media (orientation: portrait) and (max-width: 1199px) {
  #rotate-prompt { display: flex; }
  #app { display: none; }
}
.rotate-icon {
  width: 72px; height: 72px;
  border: 3px solid var(--gold-mid);
  border-radius: 12px;
  animation: rotate-hint 2.2s ease-in-out infinite;
  position: relative;
}
.rotate-icon::after {
  content: '';
  position: absolute; inset: 14px;
  border: 2px dashed var(--gold-dim);
  border-radius: 4px;
}
@keyframes rotate-hint {
  0%, 40%   { transform: rotate(0deg); }
  60%, 100% { transform: rotate(-90deg); }
}
.rotate-logo {
  width: min(70vw, 360px); height: auto; object-fit: contain;
  filter: drop-shadow(0 4px 16px rgba(139, 26, 26, 0.7));
}
.rotate-msg { font-family: var(--font-body); color: var(--text-secondary); font-size: 1.1rem; }

/* ============================  APP SHELL  ==========================
   Exactly three layout layers: strip (fixed) / battlefield (flex:1) /
   action bar (fixed). Everything else is an absolute overlay on the
   battlefield with zero layout cost. Safe-area insets respected. */
#app {
  position: fixed; inset: 0; display: flex; flex-direction: column;
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
  box-sizing: border-box;
}

/* Top row: phases · initiative tracker · turn */
#initiative-strip {
  position: absolute; top: 0; left: 0; right: 0;
  height: 60px;
  background: url('assets/ui/wooden_panels/top_middle.webp') center / 122% 122% no-repeat;
  display: flex; align-items: stretch; z-index: 20;
}
/* Mana pools (Winds of Magic / Channeling) — boxed corner counters just below the initiative strip. Player left,
   enemy right. A row of shrunk mana-stones = the side's current pool. Only rendered when a caster is on the board. */
.mana-pool {
  position: fixed; top: 62px; z-index: 22;                 /* tucked directly under the init track (Si) */
  display: flex; flex-direction: row; align-items: center; gap: 5px;
  padding: 2px 8px 2px 7px;
  background: rgba(14,14,19,0.86); border: 1px solid var(--gold-mid, #b8963e); border-radius: 7px;
  box-shadow: 0 3px 12px rgba(0,0,0,0.5);
  pointer-events: none; user-select: none;
}
.mana-pool--player { left: 14%; }                          /* pulled well in off the sides (Si) */
.mana-pool--enemy  { right: 14%; }
.mana-label { font: 600 8px/1.15 system-ui, sans-serif; letter-spacing: 0.06em; text-transform: uppercase; color: #cdb87a; max-width: 42px; text-align: center; }
/* crystals pile as an overlapping heap (Si) — each absolutely centred + a per-index offset/rotation from the JS */
.mana-pile { position: relative; width: 30px; height: 26px; flex: 0 0 auto; }
.mana-stone { position: absolute; left: 50%; top: 50%; width: 16px; height: 16px; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.55)); }
.mana-count { position: absolute; right: 1px; bottom: 1px; font: 700 10px/1 system-ui, sans-serif; color: #f2e8cc; text-shadow: 0 1px 2px #000, 0 0 3px #000; }
.mana-empty { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); color: #6b6353; font: 12px/1 system-ui, sans-serif; }
/* Responsive (narrow screens): shrink the pools right down and pull them in even further. */
@media (max-width: 820px) {
  .mana-pool { padding: 1px 5px; gap: 3px; border-radius: 5px; }
  .mana-pool--player { left: 10%; }
  .mana-pool--enemy  { right: 10%; }
  .mana-label { font-size: 9px; max-width: 44px; }        /* was 6.5px/34px — readable now (Si), pile widened to suit */
  .mana-pile { width: 28px; height: 24px; }               /* was 24×22 — room for the bigger count */
  .mana-stone { width: 14px; height: 14px; }
  .mana-count { font-size: 10px; }                        /* was 8.5px */
}

/* Dispel window (Si): a COMPACT panel, not a board-covering modal — the defender can still read the battlefield and
   judge the threat, then throw any number of pool dice at it. Invisible backdrop pauses input without hiding the board. */
/* black wall behind the spell UI (the usual scrim, Si) — dims the board but leaves it readable behind the compact panel */
.dispel-backdrop { position: fixed; inset: 0; z-index: 120; background: rgba(0,0,0,0.55); }
.dispel-panel {
  position: fixed; top: 70px; left: 50%; transform: translateX(-50%); z-index: 121;
  width: 340px; max-width: 92vw; padding: 12px 15px 14px;
  /* the usual dark wall texture (Si) — layered over a dark fill so it reads even if the image is slow/absent */
  background:  url('assets/ui/web_assets/generic_background_dark_covers.webp') top / 171% no-repeat, rgba(16,14,22,0.96);
  border: 3px solid #9a7bc8; border-radius: 10px;
  box-shadow: 0 6px 26px rgba(0,0,0,0.6), 0 0 18px rgba(150,110,220,0.28);
  color: #eadfbf; font: 13px/1.35 system-ui, sans-serif;
  display: flex; flex-direction: column; gap: 7px; text-align: center;
}
.dp-head { font-size: 13px; }
.dp-bolt { color: #b98bff; }
.dp-roll, .dp-head b { color: #d7b3ff; }
.dp-danger { font-size: 12px; color: #e0c98a; font-style: italic; }
.dp-free { font-size: 11px; color: #7fd6ff; text-shadow: 0 0 6px rgba(80,190,255,.55); }
.dp-free b { color: #cdefff; }
.dp-dice { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 2px; }
.dp-lbl { font-size: 12px; color: #cdb87a; }
/* +/- steppers reuse the square wooden burger-bar art (Si) with a dark carved glyph */
.dp-step {
  width: 30px; height: 30px; border: none; cursor: pointer; padding: 0;
  background: url('assets/ui/wooden_panels/top_right_burger_bar.webp') center / 100% 100% no-repeat;
  color: #2a1206; font: 700 17px/1 var(--font-body, system-ui); text-shadow: 0 1px 0 rgba(255,220,150,0.35);
  transition: filter 140ms ease;
}
.dp-step:hover:not(:disabled) { filter: brightness(1.1); }
.dp-step:disabled { filter: grayscale(1); cursor: default; }
.dp-n { min-width: 18px; font-size: 16px; color: #fff; }
.dp-pool { font-size: 13px; color: #ffffff; }
.dp-need { font-size: 11px; color: #ffffff; }
.dp-btns { display: flex; gap: 10px; align-items: center; justify-content: center; margin-top: 4px; }
.dp-go {
  background: url('assets/ui/wooden_panels/message_back.webp') center / 100% 100% no-repeat;
  border: none; color: #ffffff; text-shadow: 0 1px 0 rgba(255,240,200,0.4);
  font-family: var(--font-body, system-ui); font-size: 0.82rem; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase;
  width: 130px; height: 44px; cursor: pointer; transition: filter 0.15s;
}
.dp-go:hover:not(:disabled) { filter: brightness(1.1); }
.dp-go:disabled { filter: grayscale(0.6) brightness(0.7); opacity: 0.6; cursor: default; }
/* "Let it through" = the usual wooden CTA (same art as Continue / End Activation, Si) */
.dp-cta {
  background: url('assets/ui/wooden_panels/bottom_right_end_activation.webp') center / 100% 100% no-repeat;
  border: none; color: #2a1c0c; text-shadow: 0 1px 0 rgba(255,240,200,0.4);
  font-family: var(--font-body, system-ui); font-size: 0.72rem; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  width: 150px; height: 44px; cursor: pointer; transition: filter 0.15s;
}
.dp-cta:hover { filter: brightness(1.08); }
.dp-result { font-weight: 700; font-size: 13px; }
.dp-win  { color: #7cc47c; }
.dp-lose { color: #e0806e; }
/* Dice-roll drama (Si) — YOUR rolls stay on the LEFT (the target to beat), the opponent's land on the RIGHT. Dice
   tumble then settle, best highlighted; auto-advances, no confirm CTA. A transparent blocker keeps the board visible. */
#dice-block { position: fixed; inset: 0; z-index: 119; background: transparent; }
.dice-side {
  position: fixed; top: 42%; transform: translateY(-50%); z-index: 121;
  width: auto; min-width: 118px; max-width: 40vw; padding: 9px 11px 10px; text-align: center;
  background: url('assets/ui/web_assets/generic_background_dark_covers.webp') top / 140% no-repeat, rgba(16,14,22,0.96);
  border: 1px solid #9a7bc8; border-radius: 9px; box-shadow: 0 4px 18px rgba(0,0,0,0.6), 0 0 14px rgba(150,110,220,0.22);
  color: #eadfbf; font: 13px/1.3 system-ui, sans-serif;
}
.dice-side--left  { left: 12px; }
.dice-side--right { right: 12px; }
.ds-title { font-size: 11px; color: #d7b3ff; font-weight: 700; margin-bottom: 3px; max-width: 160px; }
.ds-need { font-size: 10.5px; color: #a89ec0; margin-bottom: 4px; }
.dice-row { display: flex; gap: 8px; justify-content: center; margin: 3px 0 2px; flex-wrap: wrap; }
.die {
  width: 38px; height: 38px; display: flex; align-items: center; justify-content: center;
  background: #241f36; border: 2px solid #7a6a9a; border-radius: 8px;
  font: 700 21px/1 var(--font-body, system-ui); color: #eadfbf;
  box-shadow: 0 2px 6px rgba(0,0,0,0.5);
}
.die--set { animation: dieLand 0.22s ease; }
.die--best { border-color: #ffd24a; color: #ffe9a0; box-shadow: 0 0 12px rgba(255,210,74,0.6); }
@keyframes dieLand { 0% { transform: scale(1.3); } 100% { transform: scale(1); } }
.dice-out { font-size: 13px; color: #cdb87a; min-height: 16px; margin-top: 3px; }
/* Player spell casting — reuses the dispel panel; a spell menu (titles), a detail table, and the commit modal. */
.sp-menu, .sp-detail { width: 320px; }
.sp-list { display: flex; flex-direction: column; gap: 6px; margin: 2px 0; }
.sp-title {
  width: 100%; padding: 11px 14px; cursor: pointer; text-align: center;
  /* genericbar is a bar shape made to stretch across long text without distorting (Si) */
  background: url('assets/ui/wooden_panels/genericbar.webp') center / 100% 100% no-repeat;
  border: none; color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,0.7);
  font-family: var(--font-body, system-ui); font-size: 0.9rem; font-weight: 700; letter-spacing: 0.04em;
  transition: filter 0.15s;
}
.sp-title:hover:not(:disabled) { filter: brightness(1.12); }
.sp-title:disabled { filter: grayscale(1); cursor: default; }
.sp-lock { font-size: 0.7rem; opacity: 0.8; text-transform: none; }

/* ── THE ACTION PANEL (Si) ─────────────────────────────────────────────────────────────────────────────────
   "Action > modal name > Press button to use. It can have the description on it too. And the usual little x."
   The Action button used to fire the ability the instant it was clicked — unnamed, unexplained. This is the
   same chrome as the spell panel (deliberately: it's the same kind of decision), but each row is a CARD: the
   ability's name, what it actually does, its cooldown, and a Use button. A cooling ability is greyed and shown
   WITH its remaining turns rather than hidden — the wait is information. */
.ap-panel { position: relative; }
.ap-close {
  position: absolute; top: 6px; right: 8px; z-index: 2;
  width: 24px; height: 24px; line-height: 1;
  background: transparent; border: 1px solid rgba(200,170,90,0.35); border-radius: 4px;
  color: #cdb87a; font-size: 13px; cursor: pointer; transition: all 0.12s;
}
.ap-close:hover { background: rgba(200,170,90,0.16); color: #f5d87c; border-color: rgba(200,170,90,0.7); }
.ap-item {
  display: flex; flex-direction: column; gap: 3px; text-align: left;
  width: 100%; padding: 10px 12px; cursor: pointer;
  background: rgba(40,32,24,0.62);
  border: 1px solid rgba(180,140,60,0.42); border-left: 3px solid var(--gold-mid); border-radius: 5px;
  color: #eadfbf; transition: filter 0.14s, background 0.14s;
}
.ap-item:hover:not(:disabled) { background: rgba(60,48,30,0.78); filter: brightness(1.08); }
.ap-item:disabled { filter: grayscale(0.85) brightness(0.72); cursor: default; }
.ap-name {
  font-family: var(--font-body, system-ui); font-size: 0.9rem; font-weight: 700;
  color: var(--gold-bright); letter-spacing: 0.03em;
}
.ap-desc { font-size: 0.72rem; line-height: 1.4; color: #cdc0a4; overflow-wrap: anywhere; }
.ap-meta { font-family: var(--font-data); font-size: 0.62rem; color: var(--text-muted); letter-spacing: 0.04em; }
/* The whole card IS the button, but the "Use" tag was just floating gold text — awkward, and it doesn't read as
   tappable on mobile (Si). Give it the same little wooden plank the CLOSE CTA wears, so it clearly looks like the
   thing you press. Still a <span> inside the clickable card — pure affordance, no nested button. */
.ap-use {
  align-self: flex-end; margin-top: 6px;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 96px; padding: 8px 16px; text-align: center;
  background: url('assets/ui/wooden_panels/bottom_right_end_activation.webp') center / 100% 100% no-repeat;
  color: #2a1c0c; text-shadow: 0 1px 0 rgba(255,240,200,0.4);
  font-family: var(--font-body); font-size: 0.66rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em;
}
.ap-item:hover:not(:disabled) .ap-use { filter: brightness(1.1); }
.ap-item:disabled .ap-use { filter: grayscale(0.75) brightness(0.72); }
.sp-combat { font-size: 12px; font-weight: 700; display: flex; align-items: center; justify-content: center; gap: 6px; }
.sp-combat-ic { height: 20px; width: auto; }
.sp-yes { color: #7cc47c; }
.sp-no  { color: #e0b060; }
.sp-ic { width: 13px; height: 13px; vertical-align: -2px; margin: 0 1px; }
.sp-tiers { width: 100%; border-collapse: collapse; font-size: 11.5px; margin: 3px 0; }
.sp-tiers th { color: #cdb87a; font-weight: 600; text-transform: uppercase; font-size: 9.5px; letter-spacing: 0.05em; padding: 2px 4px; border-bottom: 1px solid rgba(180,140,60,0.4); }
.sp-tiers th img { width: 16px; height: 16px; vertical-align: middle; }
.sp-tiers td { padding: 3px 4px; border-bottom: 1px solid rgba(255,255,255,0.06); color: #eadfbf; }
.sp-tiers td:first-child { color: #d7b3ff; font-weight: 600; }
/* pulsing ring on the casting unit so the defender can find the source while deciding */
.dispel-source { fill: none; stroke: #b98bff; stroke-width: 2.5; filter: drop-shadow(0 0 5px #9a6bff); animation: dispelPulse 1s ease-in-out infinite; }
@keyframes dispelPulse { 0%, 100% { opacity: 0.35; } 50% { opacity: 1; } }

/* Summon teleport-in (Si: come on WHITE, then slowly resolve into the real shape; the teleport should look magical). */
.unit--summoning .unit-sprite { animation: summonReveal 1s ease-out; }
@keyframes summonReveal {
  0%   { filter: brightness(0) invert(1) drop-shadow(0 0 6px #bfe6ff); opacity: 0; }
  18%  { filter: brightness(0) invert(1) drop-shadow(0 0 10px #dcf2ff); opacity: 1; }   /* pure white silhouette */
  60%  { filter: brightness(0) invert(1) drop-shadow(0 0 8px #bfe6ff); opacity: 1; }    /* held white */
  100% { filter: none; opacity: 1; }                                                     /* real shape */
}
/* Electric burst around the teleport hex: a soft flash + jagged crackling arcs. */
.summon-fx { pointer-events: none; }
.summon-flash { fill: rgba(214, 240, 255, 0.8); animation: summonFlashFade 0.55s ease-out forwards; }
@keyframes summonFlashFade { 0% { opacity: 0.9; } 100% { opacity: 0; } }
.summon-arc {
  fill: none; stroke: #e2f5ff; stroke-width: 2.2; stroke-linecap: round;
  filter: drop-shadow(0 0 3px #74caff); animation: summonArc 0.5s steps(4) forwards;
}
@keyframes summonArc { 0%,100% { opacity: 0; } 25% { opacity: 1; } 50% { opacity: 0.45; } 75% { opacity: 1; } }
#phase-mini {
  flex: 0 0 auto;
  align-self: flex-start;
  position: relative;
  z-index: 3;
  height: 71px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  padding: 0 14px;
  background: url('assets/ui/wooden_panels/top_left.webp') calc(-73% - 115px) bottom / auto 139% no-repeat;
}
#phase-mini .phase-tab {
  font-family: var(--font-body);
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #aeaeae;
  padding: 5px 7px;
  border-radius: 2px;
  line-height: 1.1;
  margin-top: -4px;
}
#phase-mini .phase-tab--active {
  color: var(--gold-bright); background: rgba(201,168,76,0.14);
  font-weight: 600;
  box-shadow: inset 2px 0 0 var(--gold-mid);
}
#init-slots {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 6px;
  overflow-x: auto; padding: 0 10px;
  scrollbar-width: none; -ms-overflow-style: none;   /* no scrollbar on the init track */
}
#init-slots::-webkit-scrollbar { display: none; height: 0; width: 0; }

/* Middle: battlefield (fills ALL remaining space; min-height:0 avoids flex overflow) */
#battlefield {
  flex: 1 1 auto; min-height: 0; position: relative; overflow: hidden;
  background:
    radial-gradient(ellipse at 50% 40%, #10101e 0%, var(--bg-void) 90%);
  cursor: grab;
}
/* Chance Encounter (#22 storm scenario): steady ambient dim over the whole field (mood), present all battle. */
#battlefield.storm-dim::before {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 4;
  background: radial-gradient(ellipse at 50% 40%, rgba(28,34,58,0.24) 0%, rgba(14,16,32,0.42) 100%);
}
/* The lightning strobe — a single bright-dark-bright-dark beat over the field, then gone. */
.storm-flash { position: absolute; inset: 0; pointer-events: none; z-index: 72; background: #e2ebff; opacity: 0; }
.storm-flash.strobe { animation: stormStrobe 700ms ease-out; }
@keyframes stormStrobe {
  0% { opacity: 0; } 6% { opacity: 0.92; } 16% { opacity: 0.08; }
  30% { opacity: 0.78; } 46% { opacity: 0.05; } 60% { opacity: 0.5; } 100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .storm-flash.strobe { animation-duration: 300ms; }
  @keyframes stormStrobe { 0% { opacity: 0; } 30% { opacity: 0.5; } 100% { opacity: 0; } }
}
/* The energy bolt on the struck hex — additive glow + a dramatic scale-in and flicker, then gone. */
.lightning-bolt {
  pointer-events: none;
  mix-blend-mode: screen;
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: lightningStrike 0.9s ease-out forwards;
}
@keyframes lightningStrike {
  0%   { opacity: 0;    transform: scale(0.65); }
  8%   { opacity: 1;    transform: scale(1.18); }
  20%  { opacity: 0.2;  transform: scale(1.1); }
  32%  { opacity: 1;    transform: scale(1.02); }
  46%  { opacity: 0.35; }
  60%  { opacity: 1;    transform: scale(1); }
  100% { opacity: 0;    transform: scale(1.06); }
}
@media (prefers-reduced-motion: reduce) {
  .lightning-bolt { animation-duration: 0.4s; }
  @keyframes lightningStrike { 0% { opacity: 0; } 30% { opacity: 0.9; } 100% { opacity: 0; } }
}
/* Sparse storm rain — thin wind-blown streaks over the battlefield. Per-drop vars set in JS (ensureRainLayer). */
.storm-rain { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 6; }
.rain-drop {
  position: absolute; top: 0; width: 1px; height: var(--len, 20px);
  background: linear-gradient(to bottom, rgba(198,214,255,0) 0%, rgba(198,214,255,0.9) 100%);
  opacity: var(--op, 0.25);
  animation: rainFall var(--dur, 0.8s) linear var(--delay, 0s) infinite;
}
/* Storm #12 downpour — thicker, brighter streaks on top of the higher drop count set in JS. */
.storm-rain--heavy .rain-drop {
  width: 1.8px;
  background: linear-gradient(to bottom, rgba(210,224,255,0) 0%, rgba(222,232,255,1) 100%);
}
@keyframes rainFall {
  from { transform: translate3d(0, -20vh, 0) rotate(12deg); }
  to   { transform: translate3d(7vh, 110vh, 0) rotate(12deg); }   /* full-height fall; slight sideways = wind */
}
@media (prefers-reduced-motion: reduce) { .storm-rain { display: none; } }
/* Winter — soft falling snow: round white flakes drifting down slowly with a gentle sideways sway. No assets. */
.snow-layer { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 6; }
.snow-flake {
  position: absolute; top: 0; border-radius: 50%;
  width: var(--sz, 4px); height: var(--sz, 4px);
  background: radial-gradient(circle, rgba(255,255,255,0.95) 0%, rgba(236,244,255,0.55) 70%, rgba(236,244,255,0) 100%);
  opacity: var(--op, 0.8);
  animation: snowFall var(--dur, 9s) linear var(--delay, 0s) infinite,
             snowSway var(--sway-dur, 3s) ease-in-out var(--delay, 0s) infinite alternate;
}
@keyframes snowFall { from { transform: translateY(-6vh); } to { transform: translateY(110vh); } }
@keyframes snowSway { from { margin-left: calc(var(--sway, 8px) * -1); } to { margin-left: var(--sway, 8px); } }
@media (prefers-reduced-motion: reduce) { .snow-layer { display: none; } }
/* Desert — sweltering heat: the battlemap softly drifts out of focus now and then (a slow blur shimmer). Only the
   background image blurs; units, terrain and the grid stay sharp on top. */
.board-bg.heat-haze { animation: heatShimmer 9s ease-in-out infinite; will-change: filter; }
@keyframes heatShimmer {
  0%, 42% { filter: blur(0); }
  56%     { filter: blur(2.4px); }
  72%     { filter: blur(0); }
  100%    { filter: blur(0); }
}
@media (prefers-reduced-motion: reduce) { .board-bg.heat-haze { animation: none; } }
#battlefield.dragging { cursor: grabbing; }
#board { width: 100%; height: 100%; display: block; }

/* ---- Deployment panel (replaces action bar during deploy phase) ---- */
.deploy-panel {
  position: absolute; bottom: 0; left: 0; right: 0; z-index: 10;
  /* A solid DARK backing so the strip reads as UI, not board — the black text used to sit straight on the grass and
     was easy to miss, and it must be plain that you can't deploy over this band (Si 2026-07-23). */
  background: linear-gradient(180deg, rgba(10,11,16,0.9), rgba(6,6,10,0.94));
  border-top: 2px solid var(--gold-dim);
  box-shadow: 0 -4px 16px rgba(0,0,0,0.55);
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 8px 12px 10px; pointer-events: all;
}
.deploy-hd { display: flex; justify-content: space-between; align-items: center; width: 100%; }
.deploy-title { font-family: var(--font-display); font-size: 0.72rem; color: #f6f1e6; letter-spacing: 0.06em; text-shadow: 0 1px 3px rgba(0,0,0,0.9); }
.deploy-prog  { font-size: 0.62rem; color: #d9d0bf; text-shadow: 0 1px 2px rgba(0,0,0,0.9); }
.deploy-roster {
  display: flex; flex-direction: row; gap: 6px; flex-wrap: wrap;
  justify-content: center; width: 100%;
}
/* Scout phase — "Begin the battle" ends it early (Scout is MAY, not must). Sits in the deploy panel next to the
   note, so the whole pre-battle flow keeps one home. */
.deploy-scout-done {
  /* On a WOODEN BOARD so it reads as a real, tappable button — the old faint tinted outline was easy to miss (Si). */
  background: url('assets/ui/wooden_panels/genericbar.webp') center / 100% 100% no-repeat;
  border: 1px solid var(--border-gold); border-radius: 4px;
  padding: 7px 22px; cursor: pointer; pointer-events: all;
  color: var(--gold-bright); font-family: var(--font-display); font-size: 0.74rem; letter-spacing: 0.06em;
  text-shadow: 0 1px 2px rgba(0,0,0,0.9); box-shadow: 0 2px 7px rgba(0,0,0,0.55);
  transition: filter 120ms, transform 120ms;
}
.deploy-scout-done:hover { filter: brightness(1.16); transform: translateY(-1px); }
/* DEPLOY CHIT — a tangible wooden unit card (Si: deployment is the first thing a new player sees; Pete didn't find
   it obvious). Wood plank + a unit picture + name, with a loud gold SELECTED state so there's never any doubt which
   unit you're placing. */
.deploy-card {
  position: relative;
  background: url('assets/ui/wooden_panels/message_back.webp') center / 100% 100% no-repeat;
  border: 2px solid var(--border-gold); border-radius: 5px;
  padding: 6px 12px 6px 6px; cursor: pointer; pointer-events: all;
  display: flex; align-items: center; gap: 8px;
  color: #f4e4c1; font-size: 0.68rem;
  transition: transform 120ms, box-shadow 120ms, border-color 120ms;
}
.deploy-card:hover { transform: translateY(-2px); box-shadow: 0 4px 14px rgba(0,0,0,0.5); }
.dcard-pic {
  width: 40px; height: 40px; flex: 0 0 40px; border-radius: 4px;
  background-color: rgba(0,0,0,0.35); background-size: 150% 150%; background-position: center 30%;
  background-repeat: no-repeat; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.5);
}
.dcard-pic--none { background: radial-gradient(circle at 50% 40%, #6b5a3a, #2e2618); }
/* War-machine chit shows the unit PORTRAIT (the machine), not a zoomed token frame — so fit the whole painting,
   centred, rather than the sprite-tuned 150% crop above (Si 2026-07-21: "show the machine, not the people"). */
.dcard-pic--machine { background-size: cover; background-position: center center; }
.dcard-txt { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; }
/* Vanguard reposition panel — instruction text (no button; dropping the unit auto-starts the battle) */
.vanguard-note { flex-basis: 100%; color: #f2ede2; font-size: 0.66rem; line-height: 1.3; opacity: 1; margin-bottom: 2px; text-shadow: 0 1px 2px rgba(0,0,0,0.85); }
.deploy-card--sel {
  border-color: var(--gold-bright);
  box-shadow: 0 0 0 3px rgba(245,216,124,0.6), 0 0 22px rgba(245,216,124,0.75);
  animation: deploySelPulse 1.3s ease-in-out infinite;
}
@keyframes deploySelPulse {
  0%,100% { box-shadow: 0 0 0 3px rgba(245,216,124,0.5), 0 0 16px rgba(245,216,124,0.6); }
  50%     { box-shadow: 0 0 0 4px rgba(245,216,124,0.85), 0 0 30px rgba(245,216,124,0.95); }
}
.dcard-name { font-family: var(--font-display); font-size: 0.74rem; color: #f7e9c6; text-shadow: 0 1px 2px rgba(0,0,0,0.9); letter-spacing: 0.02em; }
.deploy-card--sel .dcard-name { color: var(--gold-bright); }
.dcard-size { font-size: 0.6rem; color: #cdbb92; }
/* The loud "SELECTED" flag — big, bold, yellow, so a first-time player knows exactly what they're about to place. */
.dcard-sel {
  position: absolute; top: -11px; right: -8px;
  background: var(--gold-bright); color: #201400;
  font-family: var(--font-display); font-weight: 900; font-size: 0.6rem; letter-spacing: 0.08em;
  padding: 2px 8px; border-radius: 10px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.6), 0 0 12px rgba(245,216,124,0.7);
  transform: rotate(-4deg);
}

/* DEPLOYMENT phase title — prominent banner across battlefield top */
.deploy-banner {
  position: absolute; top: 14%; left: 50%;
  transform: translateX(-50%);
  z-index: 4; pointer-events: none;
  font-family: var(--font-display); font-size: 2.6rem; font-weight: 900;
  color: var(--gold-bright); letter-spacing: 0.22em;
  text-shadow:
    0 0 60px rgba(180,140,60,0.7),
    0 0 20px rgba(180,140,60,0.5),
    0 2px 6px rgba(0,0,0,0.95);
  opacity: 1;
  white-space: nowrap; user-select: none;
}
/* SCOUT PHASE is its own step, not the tail of deployment (Si) — so it wears BLUE rather than the deployment gold.
   Same glow shape, hue-shifted, so it reads as the same furniture in a different mode. */
.deploy-banner--scout .ds-scout-title { display: block; }
/* The scout instruction rides IN the heading now (Si) — persistent, blue, clearly the secondary line. */
.deploy-banner--scout .ds-scout-sub {
  display: block; margin-top: 12px;
  font-size: 1.15rem; font-weight: 700; letter-spacing: 0.12em;
  color: #cfe4ff;
  text-shadow: 0 0 24px rgba(70,140,235,0.55), 0 2px 6px rgba(0,0,0,0.95);
}
.deploy-banner--scout {
  color: #8fc4ff;
  font-size: 3.5rem;          /* bigger than deployment gold — it's the phase heading now the redundant title is gone (Si) */
  letter-spacing: 0.26em;
  text-shadow:
    0 0 60px rgba(70,140,235,0.7),
    0 0 20px rgba(70,140,235,0.55),
    0 2px 6px rgba(0,0,0,0.95);
}

/* BATTLE-START BOSS SPOTLIGHT — a named enemy leader revealed so the player feels the pressure (Si). Dark card,
   blood-red menace accent, the leader's portrait beside his name; slides up onto the battlefield then out. */
.boss-reveal {
  position: absolute; left: 50%; bottom: 17%;
  transform: translate(-50%, 28px) scale(0.94);
  z-index: 30; pointer-events: none;
  display: flex; align-items: stretch; gap: 16px;
  padding: 13px 28px 13px 13px;
  background: linear-gradient(100deg, rgba(30,10,10,0.96), rgba(14,10,16,0.94));
  border: 1px solid rgba(180,60,60,0.5); border-left: 4px solid var(--blood-bright);
  border-radius: 10px;
  box-shadow: 0 12px 44px rgba(0,0,0,0.72), 0 0 46px rgba(150,30,30,0.34);
  opacity: 0;
  transition: opacity 380ms ease, transform 440ms cubic-bezier(.2,.9,.3,1.25);
}
.boss-reveal.is-in { opacity: 1; transform: translate(-50%, 0) scale(1); }
.boss-reveal__art {
  /* Full card height (Si 2026-07-21: "can the picture be bigger, pretty much full height"). align-self stretches it
     to the card's inner height; min-height sets the floor so the whole card grows to give the art real presence. */
  flex: 0 0 128px; width: 128px; align-self: stretch; min-height: 128px; border-radius: 8px;
  background-size: cover; background-position: center 20%;
  border: 2px solid rgba(200,80,80,0.6); box-shadow: inset 0 0 22px rgba(0,0,0,0.6);
}
.boss-reveal__txt { display: flex; flex-direction: column; justify-content: center; gap: 3px; }
.boss-reveal__eyebrow {
  font-family: var(--font-body); font-size: 0.6rem; letter-spacing: 0.24em; text-transform: uppercase; color: #d98a8a;
}
.boss-reveal__name {
  font-family: var(--font-display); font-size: 1.95rem; font-weight: 900; line-height: 1; color: #f4d7a0;
  text-shadow: 0 2px 10px rgba(0,0,0,0.9), 0 0 24px rgba(180,40,40,0.5);
}
.boss-reveal__role { font-family: var(--font-body); font-size: 0.72rem; letter-spacing: 0.04em; color: #c9b48a; }
/* The boss's taunt (Si) — fades in a BEAT after the name lands, so the reveal reads name → line, not all at once. */
.boss-reveal__taunt {
  font-family: var(--font-body); font-style: italic; font-size: 0.84rem; line-height: 1.3;
  color: #ecd0a6; max-width: 250px; margin-top: 5px; text-shadow: 0 1px 6px rgba(0,0,0,0.85);
  opacity: 0; transform: translateY(4px); transition: opacity 420ms ease 480ms, transform 420ms ease 480ms;
}
.boss-reveal.is-in .boss-reveal__taunt { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  .boss-reveal { transition: opacity 200ms ease; }
  .boss-reveal.is-in { transform: translate(-50%, 0) scale(1); }
  .boss-reveal__taunt { transition: opacity 200ms ease; transform: none; }
}

/* CHARACTER BARKS — Pratchett-style speech bubbles (BARKS brief, Si). A SCREEN-SPACE overlay (position:fixed), sized in
   pixels and clamped to the viewport by showBark — so it stays a readable, on-screen size at ANY camera zoom (Si: the
   old world-space bubble ballooned huge and floated off the top of the screen when combat zoomed in). Floats up, fades;
   never interactive. */
.bark-fixed {
  position: fixed; z-index: 46; pointer-events: none;
  opacity: 0; transform: translateY(8px);
  transition: opacity 0.28s ease, transform 0.28s ease;
}
.bark-fixed.is-in  { opacity: 1; transform: translateY(0); }
.bark-fixed.is-out { opacity: 0; transform: translateY(-12px); }
/* Traditional comic (Beano-style) speech bubble: white, bold black outline, rounded, with a tail that points DOWN
   at the character it belongs to (Si 2026-07-20: "more traditional and just above them… the sort of beano style").
   showBark centres the bubble horizontally on the unit and sits it ~20px above, so a bottom-centre tail drops
   straight onto the speaker. */
.bark-bubble {
  position: relative;
  max-width: 250px;
  background: #ffffff; color: #16130d;
  font-family: var(--font-body, Georgia, serif); font-size: 15px; font-weight: 700; line-height: 1.24; font-style: normal;
  padding: 10px 16px; border-radius: 21px; border: 2.5px solid #17130c;
  box-shadow: 0 4px 13px rgba(0, 0, 0, 0.42);
  text-align: center; text-wrap: balance;
}
/* the tail — two stacked triangles: a black one (outline) behind a slightly shorter white one (fill), so the tail
   reads as white with a black edge, matching the bubble. Both hang below the bubble's bottom edge. */
.bark-bubble::before, .bark-bubble::after {
  content: ''; position: absolute; left: 50%; width: 0; height: 0; border-style: solid;
}
.bark-bubble::before {                                   /* outline (black), behind */
  bottom: -18px; margin-left: -12px;
  border-width: 18px 12px 0 12px; border-color: #17130c transparent transparent transparent;
}
.bark-bubble::after {                                    /* fill (white), overlaps the bubble border to hide the seam */
  bottom: -10px; margin-left: -8px;
  border-width: 13px 8px 0 8px; border-color: #ffffff transparent transparent transparent;
}
@media (prefers-reduced-motion: reduce) { .bark-fixed { transition: opacity 0.28s ease; transform: none !important; } }

/* Hidden enemy token (deployment fog-of-war) */
.unit--hidden .unit-body { fill: #1a0d2a; stroke: #7744bb; opacity: 0.85; }
.unit-hidden-glyph { fill: #9955dd; font-size: 26px; font-weight: 700; font-family: var(--font-display); }

/* Bottom action bar — floats over the battlefield (grass visible behind planks) */
#action-bar {
  position: absolute; bottom: 0; left: 0; right: 0;
  height: 80px;
  background: none;
  pointer-events: none;
  z-index: 10;
}
/* Base planks — each 55%, bottom-anchored, overlap in centre */
#action-bar::before {
  content: ''; position: absolute; left: 0; bottom: 0; right: 45%; height: 70px;
  background: url('assets/ui/wooden_panels/bottom_left.webp') left bottom / 100% 100% no-repeat;
  z-index: 0;
}
#action-bar::after {
  content: ''; position: absolute; right: 0; bottom: 0; left: 45%; height: 70px;
  background: url('assets/ui/wooden_panels/bottom_right.webp') right bottom / 100% 100% no-repeat;
  z-index: 0;
}
#action-bar .hint {
  font-family: var(--font-body); color: var(--text-muted);
  font-size: 0.78rem; letter-spacing: 0.12em; text-transform: uppercase;
}

/* ---- battlefield ground ---- */
.board-bg { pointer-events: none; }
/* darken the grass a touch so the gold grid + tokens pop (texture still reads through) */
.board-tint { fill: rgba(8, 14, 6, 0.32); pointer-events: none; }

/* ---- terrain overlay (beneath interactive hexes + units) ---- */
.hex-terrain { stroke: none; pointer-events: none; opacity: 0.5; }  /* soft wash under the decal art */
.hex-terrain-decal { pointer-events: none; }   /* no filter — drop-shadow re-composites every animation frame */
/* category outline ringing the hex edge — colour set inline per terrain */
.hex-terrain-border {
  fill: none; stroke-width: 3; pointer-events: none;
}
.hex-terrain-glyph {
  font-size: 20px; fill: rgba(255, 255, 255, 0.32);
  text-anchor: middle; dominant-baseline: central;
  pointer-events: none; user-select: none;
}

/* ---- terrain info popup (bottom-left, above the unit name; purely informational) ---- */
/* Bottom-left corner: LASTING EFFECTS (status) stacked directly above the TERRAIN blurb — one anchored column. */
.corner-info {
  position: absolute; left: 12px; bottom: 74px; z-index: 30;
  display: flex; flex-direction: column; gap: 6px; align-items: flex-start;
  pointer-events: none;
}
.terrain-info {
  max-width: 230px; pointer-events: none;
  background: rgba(10, 8, 5, 0.9); border: 1px solid var(--gold-dim);
  border-radius: 6px; padding: 6px 10px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.55);
  display: flex; flex-direction: column; gap: 2px;
}
/* Status block reads as a unit condition, not ground — a faintly cooler border sets it apart from terrain. */
.status-info { border-color: rgba(120, 150, 175, 0.5); }
.terrain-info .ti-name {
  font-family: var(--font-display); font-size: 0.82rem; color: var(--gold-bright); letter-spacing: 0.04em;
  display: flex; align-items: center; gap: 6px;
}
.terrain-info .ti-dot {
  width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.6);
}
.wiki-terrain-swatch {
  display: inline-block; width: 11px; height: 11px; border-radius: 3px;
  margin-right: 7px; vertical-align: -1px; box-shadow: 0 0 0 1px rgba(0,0,0,0.5);
}
.terrain-info .ti-eff { display: flex; flex-direction: column; gap: 3px; margin-top: 2px; }
.terrain-info .ti-line {
  display: flex; align-items: center; gap: 6px;
  font-family: var(--font-data); font-size: 0.7rem; color: var(--text-secondary); line-height: 1.2;
}
.terrain-info .ti-ic { width: 15px; height: 15px; object-fit: contain; flex-shrink: 0; }
.terrain-info .ti-ic--none { display: inline-block; width: 15px; flex-shrink: 0; }  /* align text when no icon */
.terrain-info .ti-ic--eye   { color: var(--text-secondary); }
.terrain-info .ti-ic--flame { color: #e8863a; }
/* Notation tint: green = helps the unit stood here, red = hurts it (colour by BENEFIT, not raw sign). */
.ti-good { color: #7cc47c; }
.ti-bad  { color: #e0806e; }

/* ---- hex visuals ---- */
/* Grid line colour is theme-driven (setBoardBackground sets --grid-stroke on #board); gold is the default. */
.hex { fill: transparent; stroke: var(--grid-stroke, rgba(247, 228, 162, 0.6)); stroke-width: 1.25; transition: fill 120ms ease;
       filter: var(--hex-filter, drop-shadow(0 0 0.6px rgba(0,0,0,0.85))); }
.hex--deploy-top    { fill: rgba(42, 122, 58, 0.10); }   /* enemy (orc) edge, top */
.hex--deploy-bottom { fill: rgba(42, 92, 154, 0.12); }   /* player (human) edge, bottom */
/* The place-here zone SHIMMERS (Si: "maybe have sparkles on the deployment area") — a twinkling green so a first-time
   player can't miss where their units go. Spatial cue, so animating it is fair game (unlike ambient tints). */
.hex--deploy-valid  { fill: rgba(60,170,100,0.20); stroke: rgba(90,225,140,0.85); stroke-width: 2; animation: deployTwinkle 1.8s ease-in-out infinite; }
@keyframes deployTwinkle {
  0%,100% { fill: rgba(60,170,100,0.14); stroke: rgba(90,220,140,0.6); }
  50%     { fill: rgba(90,220,150,0.34); stroke: rgba(150,255,190,1); }
}
@media (prefers-reduced-motion: reduce) { .hex--deploy-valid { animation: none; } }
/* Vanguard swap: the first-picked unit's hex, waiting for its swap partner */
.hex--swap-sel { fill: rgba(245,216,124,0.22); stroke: var(--gold-bright); stroke-width: 3; }
/* Hold the Line: the centre band you must control at the buzzer. Colour tracks who holds it right now —
   gold when you're winning the ground, red when the enemy has more presence there. */
.hex--hold-zone { stroke-width: 2; stroke-dasharray: 5 4; animation: holdPulse 2.4s ease-in-out infinite; }
.hex--hold-held { fill: rgba(230,190,90,0.14);  stroke: rgba(245,216,124,0.75); }
.hex--hold-lost { fill: rgba(180,40,40,0.16);   stroke: rgba(214,70,60,0.8); }
@keyframes holdPulse { 0%,100% { stroke-opacity: 0.55; } 50% { stroke-opacity: 1; } }
/* FOUR ARMIES — the contested CENTRE zone (blue, marked hexes) + the running leaderboard panel. */
.hex--centre-zone { fill: rgba(74,143,212,0.20) !important; stroke: rgba(126,178,255,0.6); stroke-width: 1.75; stroke-dasharray: 5 4; animation: holdPulse 2.4s ease-in-out infinite; }
#four-armies-board { position: fixed; top: 96px; left: 10px; z-index: 45; background: url('assets/ui/web_assets/generic_background_dark_covers.webp') top / 230% no-repeat, rgba(14,12,18,0.96); border: 1px solid var(--gold-mid); border-radius: 8px; padding: 8px 11px 9px; min-width: 156px; pointer-events: none; box-shadow: 0 4px 18px rgba(0,0,0,0.6); }
.fab-title { font-family: var(--font-display, var(--font-body)); font-size: 11px; letter-spacing: 0.07em; text-transform: uppercase; color: var(--gold-bright); text-align: center; margin-bottom: 5px; }
.fab-row { display: flex; align-items: center; gap: 9px; padding: 2px 0; font-family: var(--font-body); font-size: 13px; }
.fab-rank { width: 14px; text-align: center; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.fab-name { flex: 1; font-weight: 600; }
.fab-score { font-variant-numeric: tabular-nums; font-weight: 700; color: #f0e6c8; }
.fab-you .fab-name    { color: #7fb2ff; }
.fab-orc .fab-name    { color: #1dfc60; }
.fab-human .fab-name  { color: #b25cff; }
.fab-beast .fab-name  { color: #e0a94a; }
/* Last Stand (Breakthrough): the enemy back-edge row you must punch a unit through to. */
.hex--breakthrough { fill: rgba(80,190,120,0.16); stroke: rgba(110,225,150,0.8); stroke-width: 2;
  stroke-dasharray: 6 4; animation: holdPulse 2s ease-in-out infinite; }
/* Burn it Down: each supply-depot hex — amber to take, green while a unit's on it, charred once razed. */
.hex--raze      { fill: rgba(220,140,40,0.20); stroke: rgba(240,170,70,0.9); stroke-width: 2.4;
  stroke-dasharray: 5 3; animation: holdPulse 1.8s ease-in-out infinite; }
.hex--raze-held { fill: rgba(90,200,120,0.22); stroke: rgba(120,235,150,0.95); stroke-width: 2.6; }
.hex--raze-done { fill: rgba(40,34,30,0.5);    stroke: rgba(90,80,70,0.7);     stroke-width: 2; }
/* The clutter-goods sprite sitting on the depot; dims + desaturates once razed. */
.raze-goods { pointer-events: none; filter: drop-shadow(0 1px 1.5px rgba(0,0,0,0.6)); }
.raze-goods--razed { opacity: 0.4; filter: grayscale(0.8) brightness(0.6); }
/* Find the Precious: dig-site hexes — amber undug, faded once dug empty, gold pulse for the found relic. */
.hex--dig       { fill: rgba(200,165,70,0.16); stroke: rgba(220,190,90,0.8); stroke-width: 2;
  stroke-dasharray: 4 4; animation: holdPulse 2.2s ease-in-out infinite; }
.hex--dig-empty { fill: rgba(30,28,24,0.4);    stroke: rgba(110,105,95,0.5); stroke-width: 1.5; }
.hex--dig-relic { fill: rgba(245,210,90,0.28);  stroke: rgba(255,225,110,1);  stroke-width: 3;
  animation: holdPulse 1.1s ease-in-out infinite; }
.dig-goods { pointer-events: none; filter: drop-shadow(0 1px 1.5px rgba(0,0,0,0.6)); }
.dig-goods--empty { opacity: 0.35; filter: grayscale(0.9) brightness(0.55); }
.dig-goods--relic { filter: drop-shadow(0 0 4px rgba(255,220,110,0.95)); }
/* Objective Target Marker (SCENARIOS_BRIEF) — a bobbing gold arrow over a themed-secondary target hex/unit,
   plus a soft gold tint on the hex itself. Shared component (Find the Path, Signal Fire, Silence the Watchers …). */
.hex--objective { fill: rgba(245,205,90,0.16); stroke: rgba(247,214,110,0.9); stroke-width: 2.4;
  stroke-dasharray: 4 4; animation: holdPulse 1.6s ease-in-out infinite; }
.obj-marker { fill: #ffd24a; stroke: #6b4e12; stroke-width: 1.2; pointer-events: none;
  filter: drop-shadow(0 1px 1.5px rgba(0,0,0,0.6)); transform-box: fill-box; transform-origin: 50% 50%;
  animation: objBob 1.1s ease-in-out infinite; }
@keyframes objBob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-3px); } }
.reduce-motion .obj-marker, .reduce-motion .hex--objective { animation: none; }
/* Pursuit / Assassination: fleeing target hex + label. GOLD so the kill target is unmistakable (Si). */
.hex--escape { fill: rgba(245,205,90,0.22); stroke: rgba(247,214,110,0.98); stroke-width: 2.6;
  stroke-dasharray: 4 4; animation: holdPulse 1.6s ease-in-out infinite; }
.escape-badge {
  font-family: var(--font-display); font-weight: 700; font-size: 8.5px; fill: #ffd76a;
  letter-spacing: 0.06em; paint-order: stroke; stroke: #000; stroke-width: 1.4px; stroke-linejoin: round; pointer-events: none;
}
/* Escort ESCAPE LINE — the whole safe home edge the runners dash for (Si 2026-07-23: "the entire bottom row, not just
   one hex"). A soft friendly-BLUE wash spanning the strip; NO per-hex stroke (a stroked grid across a full-width band
   is clutter — an ambient tint reads as "your ground, get them here"). Distinct from the GOLD objective/kill markers. */
.hex--homeline { fill: rgba(96,168,232,0.15); animation: holdPulse 2.2s ease-in-out infinite; }
.reduce-motion .hex--homeline { animation: none; }
/* Find the Precious dig-roll panel (reuses the morale-test panel) — the found / not-found result art.
   Full-bleed banner directly under the title bar: no padding/margin, touches the header and both side edges
   (the panel's overflow:hidden clips it cleanly). */
.dig-test-panel .dtp-result-img {
  display: block;
  width: 100%; margin: 0; padding: 0;
  animation: digImgIn 260ms ease-out;
}
@keyframes digImgIn { from { opacity: 0; } to { opacity: 1; } }
/* Treacherous Ground: collapse warning tint — reddens and pulses faster as the countdown ticks toward zero. */
.collapse-warn { pointer-events: none; stroke: none; }
.collapse-warn--3 { fill: rgba(210,150,40,0.16); animation: collapseWarn 2.2s ease-in-out infinite; }
.collapse-warn--2 { fill: rgba(220,110,30,0.22); animation: collapseWarn 1.4s ease-in-out infinite; }
.collapse-warn--1 { fill: rgba(210,40,30,0.32);  animation: collapseWarn 0.8s ease-in-out infinite; }
@keyframes collapseWarn { 0%,100% { opacity: 0.45; } 50% { opacity: 1; } }
/* The countdown numeral — deliberately tiny, with a dark outline so it stays legible over units/terrain. */
.collapse-timer-num {
  font-family: var(--font-display); font-weight: 700; font-size: 7px;
  paint-order: stroke; stroke: #000; stroke-width: 1.4px; stroke-linejoin: round; pointer-events: none;
}
.collapse-timer-num--3 { fill: #ffd27a; }
.collapse-timer-num--2 { fill: #ff9d4a; }
.collapse-timer-num--1 { fill: #ff5a44; }
/* Deploy tint is cleared at battle start by removing the classes (see beginBattle) — a CSS
   override here would outrank the shooting/vision overlays that share `fill` on these hexes. */
.hex:hover          { fill: rgba(245, 216, 124, 0.07); }
.hex--selected {
  fill: rgba(245, 216, 124, 0.18); stroke: var(--gold-bright); stroke-width: 2;
  /* SVG equivalent of box-shadow 0 0 105px 45px rgba(252,255,46,.9) — layered to fake spread */
  filter:
    drop-shadow(0 0 8px  rgba(252, 255, 46, 0.9))
    drop-shadow(0 0 20px rgba(252, 255, 46, 0.8))
    drop-shadow(0 0 40px rgba(252, 255, 46, 0.5));
}

/* ---- unit tokens (Phase 2: one unit = one hex, per ruling C3) ---- */
.unit            { cursor: pointer; }
.unit-body       { stroke: var(--gold-mid); stroke-width: 1.5; }
.unit--human .unit-body { fill: var(--human-blue); }
.unit--orc   .unit-body { fill: var(--orc-green); }
/* rescue captive — a pale, non-combatant token, gold-ringed so it reads as "protect this" */
.unit--npc .unit-body { fill: #d8cfa8; stroke: var(--gold-bright); stroke-width: 2.5; }
.unit--enemy .unit-ring { fill: none; stroke: var(--blood-bright); stroke-width: 2; opacity: 0.85; }
.unit-label  { font-family: var(--font-body); font-size: 12px; font-weight: 600;
               fill: var(--text-primary); text-anchor: middle; dominant-baseline: middle;
               pointer-events: none; user-select: none; }
.unit-count  { font-family: var(--font-data); font-size: 11px; font-weight: 700;
               fill: var(--gold-bright); text-anchor: middle; pointer-events: none; user-select: none; }
.unit--selected .unit-body { stroke: var(--gold-bright); stroke-width: 3;
               filter: drop-shadow(0 0 5px var(--glow-gold)); }
/* active unit — strong pulsing glow so it's obvious which unit is acting/moving */
.unit--active .unit-body {
  stroke: var(--gold-bright); stroke-width: 3;
  filter: drop-shadow(0 0 7px var(--glow-gold)) drop-shadow(0 0 14px var(--glow-gold-soft));
  animation: active-pulse 1.3s ease-in-out infinite;
}
@keyframes active-pulse { 0%, 100% { stroke: var(--gold-mid); } 50% { stroke: #fff; } }
/* outer glow on the active unit's hex (its "space") */
.hex--active {
  fill: rgba(245, 216, 124, 0.12);
  stroke: var(--gold-bright); stroke-width: 2.5;
  filter: drop-shadow(0 0 8px var(--glow-gold));
}
/* ACTIVE-UNIT BEACON (Si): the same gold as the active hex, but BROADER — a larger pulsing gold hex-ring around the
   unit it's your turn to move, plus a bobbing chevron above it. Pure transform/opacity animation (GPU-cheap, no
   per-frame filter repaint). No board dim — the field stays fully readable. */
.active-beacon-ring {
  fill: none; stroke: var(--gold-bright); stroke-width: 2.5;
  filter: drop-shadow(0 0 6px var(--glow-gold));
  transform-box: fill-box; transform-origin: center;
  animation: active-ring-pulse 1.4s ease-in-out infinite;
}
@keyframes active-ring-pulse {
  0%, 100% { transform: scale(1.05); opacity: 0.40; }
  50%      { transform: scale(1.20); opacity: 0.90; }
}
.active-beacon-arrow {
  fill: none; stroke: var(--gold-bright); stroke-width: 4; stroke-linecap: round; stroke-linejoin: round;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.65));
  transform-box: fill-box; transform-origin: center;
  animation: active-arrow-bob 0.95s ease-in-out infinite;
}
@keyframes active-arrow-bob {
  0%, 100% { transform: translateY(-1px); }
  50%      { transform: translateY(4px); }
}
@media (prefers-reduced-motion: reduce) {
  .active-beacon-ring { animation: none; transform: scale(1.12); opacity: 0.7; }
  .active-beacon-arrow { animation: none; }
}
.unit-sprite { filter: drop-shadow(0 1px 1.5px rgba(0,0,0,0.55)); }
/* commander (hero) at the front of a unit — gold rim so they read as prominent */
.unit-commander { filter: drop-shadow(0 1px 1.5px rgba(0,0,0,0.6)) drop-shadow(0 0 3px rgba(255,214,120,0.9)); }
/* war-machine body (static) + siege shot VFX */
.unit-machine { filter: drop-shadow(0 1px 2px rgba(0,0,0,0.6)); pointer-events: none; }
/* PERFORMANCE (Si): the per-model drop-shadow re-composites every animation frame AND every zoom re-raster — the
   single biggest paint cost with a full board. Strip it during the two moments that actually chug, when the shadow
   is invisible anyway: (a) while the camera is flying (zoom/pan re-rasters the whole board every frame), and (b)
   during a combat spotlight, on the dimmed non-spotlight units sitting under the 62% black overlay. The two
   combatants keep their shadow; everything idle keeps it too. Frees the main thread so combat numbers paint. */
#board.cam-flying .unit-sprite,
#board.cam-flying .unit-commander,
#board.cam-flying .unit-machine,
#board.spot-dim .unit:not(.unit--spotlight) .unit-sprite,
#board.spot-dim .unit:not(.unit--spotlight) .unit-commander,
#board.spot-dim .unit:not(.unit--spotlight) .unit-machine { filter: none; }
.siege-rock, .siege-impact, .siege-dust { pointer-events: none; }
/* dust puff drifts upward and fades after impact */
.siege-dust {
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: siege-dust-rise 1100ms ease-out forwards;
}
@keyframes siege-dust-rise {
  from { opacity: 0.9; transform: translateY(0) scale(0.9); }
  to   { opacity: 0;   transform: translateY(-32px) scale(1.3); }
}
/* impact spatter fades out as it settles */
.siege-impact {
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: siege-impact-fade 320ms ease-out forwards;
}
@keyframes siege-impact-fade {
  from { opacity: 1; transform: scale(0.85); }
  to   { opacity: 0; transform: scale(1.15); }
}
/* machine recoil jolt on firing */
.machine-recoil { transform-box: fill-box; animation: machine-recoil 300ms ease-out; }
@keyframes machine-recoil {
  0%   { transform: translateY(0); }
  35%  { transform: translateY(5px); }
  100% { transform: translateY(0); }
}
/* Bolt Thrower — a fast, heavy black bolt line whipping to the target (huge arrow strike), then a quick fade. */
.bolt-line { pointer-events: none; stroke: #0b0b0e; stroke-width: 3.6; stroke-linecap: round; filter: drop-shadow(0 0 2px rgba(0,0,0,0.7)); }
.bolt-line--fade { animation: boltFade 180ms ease-out forwards; }

/* Stakes — planted defensive palisade (Stakes activated keyword) */
#stakes-layer .stakes-marker line { stroke: #7a4f26; stroke-width: 2.4; stroke-linecap: round; filter: drop-shadow(0 1px 1px rgba(0,0,0,0.6)); }

/* Cannon / Mortar explosion on the target hex — a frame sequence, glowing warm */
.cannon-blast { pointer-events: none; filter: drop-shadow(0 0 7px rgba(255,150,50,0.65)); }
/* Mortar shell — the borrowed trebuchet rock, recoloured near-black + metallic (not stone) */
.mortar-shell { filter: grayscale(1) brightness(0.32) contrast(1.35) drop-shadow(0 1px 2px rgba(0,0,0,0.7)); }

/* Charge dust — a punchy burst kicked up behind a cavalry/chariot on a full charge */
@keyframes chargeDust {
  0%   { opacity: 0;    transform: scale(0.45); }
  22%  { opacity: 0.9;  }
  100% { opacity: 0;    transform: scale(1.75); }
}
.charge-dust { pointer-events: none; transform-box: fill-box; transform-origin: center; animation: chargeDust 1.6s ease-out forwards; }
.reduce-motion .charge-dust { animation: none; opacity: 0.6; }
@keyframes boltFade { from { opacity: 1; } to { opacity: 0; } }
/* SPELL PAYOFF (Si) — a one-shot burst over the target that a spell LANDED. Pops in, holds, fades. The elemental
   spells (Fireball / Lightning Storm) get a matching glow. */
.magic-fx { pointer-events: none; transform-box: fill-box; transform-origin: center; animation: magicFx 1.3s ease-out forwards; }
.magic-fx--fire   { filter: drop-shadow(0 0 9px rgba(255,140,30,0.6)); }
.magic-fx--energy { filter: drop-shadow(0 0 9px rgba(120,170,255,0.65)); }
/* Ally-magic vs enemy-magic reads at a glance: GOOD is a warm gold-green blessing, BAD a sickly violet curse. The
   glow is on the drop-shadow (cheap, one-shot) not a per-frame filter — these are transient, not the persistent
   sprite shadows the arch rule forbids. */
.magic-fx--good { filter: drop-shadow(0 0 8px rgba(150,225,120,0.7)); }
.magic-fx--bad  { filter: drop-shadow(0 0 8px rgba(180,90,220,0.75)); }
.reduce-motion .magic-fx { animation: magicFxFade 1.1s ease-out forwards; }
/* Pop in, a little shimmer on the hold (Si), then fade. The two mid-hold wobbles are the "shimmer about". */
@keyframes magicFx {
  0%   { opacity: 0;    transform: scale(0.45) rotate(-6deg); }
  18%  { opacity: 1;    transform: scale(1.10) rotate(0deg); }
  40%  { opacity: 0.92; transform: scale(1.0) rotate(1.5deg); }
  60%  { opacity: 0.98; transform: scale(1.04) rotate(-1.5deg); }
  100% { opacity: 0;    transform: scale(1.2) rotate(0deg); }
}
@keyframes magicFxFade { 0% { opacity: 0; } 20% { opacity: 0.95; } 100% { opacity: 0; } }
/* FRONT INDICATOR — chevron ">" showing which way every unit faces. Yellow = friendly (good),
   red = enemy (bad). A key directional cue; the dark glow keeps it off the gold grid lines. */
/* The front-facing BAND (Si 2026-07-23) — a faction-coloured rect masked to the front_facing.png hatched shape,
   standing on the hex's front edge UNDER the unit. `fill` is the faction colour (no stroke/filter — it's a filled
   mask shape, and per-model filters are out for perf). */
.unit-front {
  fill: var(--gold-bright);
  opacity: 0.9;
}
/* FACING-BAND 4-WAY PALETTE — deep & equally "dulled" so none vibrates on the green board (Si 2026-07-23: the old
   neon #ff1515 "hurt my eyes"; wants a richer, saturated red for the enemy, and the four-way battle equally dulled —
   "a bull brown and a dulled blue"). Enemy = deep blood red. (Rings keep their own brighter side-marker system.) */
.unit--enemy .unit-front { fill: #c1121e; }
/* Beasts (neutral "Triple Threat" faction) — earthy brown body + amber ring/facing, distinct from the player's
   blue, the orcs' green and the red enemy markers. Placed after the enemy rules so it wins on the shared ring. */
.unit--beast .unit-body  { fill: #8a6d4a; }
.unit--beast .unit-ring  { stroke: #e0a94a; }
.unit--beast .unit-front { fill:#8f5a2a; }   /* bull brown — dulled to match the deep-red enemy band (Si 2026-07-23) */
/* RIVAL HUMAN faction (Battle of Four Armies — the enemy human army). A VIOLET ring + facing so an enemy human block
   never blends with the PLAYER's blue humans (identical body) OR the orcs' red ring (Si 2026-07-22: "for back humans,
   make sure they have a different hex outline colour"). Body stays human-blue; only the outline marks the side. Placed
   after the enemy rules so it wins the shared ring, exactly like the beasts. Four sides now read at a glance: player
   (blue body / gold), rival humans (blue body / VIOLET ring), orcs (green / red ring), beasts (brown / amber ring). */
.unit--enemy.unit--human .unit-ring  { stroke: #b25cff; }
.unit--enemy.unit--human .unit-front { fill:#4d729e; }   /* dulled blue — the rival human band, muted to match the 4-way set (Si 2026-07-23; ring stays violet so it never blends with the player's blue) */
/* AT FIRST LIGHT — the friendly AI ally (npc:'ally') fights alongside you. A GREEN chevron/ring/body sets it apart from
   your own units (blue body / gold facing) and the enemy (red), so the reinforcement battle reads at a glance. Placed
   after .unit--npc (its gold body, line ~846) so green wins on the shared body. */
.unit--ally .unit-body  { fill: #2f6d47; stroke: #43d17a; }
.unit--ally .unit-ring  { stroke: #43d17a; }
.unit--ally .unit-front { fill:#43d17a; }

/* grab-and-drag to rotate — live ghost chevron at the previewed facing */
.turn-ghost {
  fill: none; stroke: var(--gold-bright); stroke-width: 3;
  stroke-linejoin: round; stroke-linecap: round; opacity: 0.95;
  filter: drop-shadow(0 0 5px var(--glow-gold)); pointer-events: none;
}
.turn-ghost--enemy { stroke: var(--blood-bright); }
.hex-coord {
  font-family: var(--font-data); font-size: 9px;
  fill: var(--text-muted); text-anchor: middle; dominant-baseline: middle;
  pointer-events: none; user-select: none;
}

/* ---- floating controls ---- */
.ctl-btn {
  position: absolute; z-index: 20;
  background: rgba(8, 8, 15, 0.85);
  border: 1px solid var(--gold-mid);
  color: var(--text-primary);
  font-family: var(--font-body); font-size: 0.74rem;
  letter-spacing: 0.08em; text-transform: uppercase;
  padding: 9px 14px; border-radius: 2px; cursor: pointer;
  box-shadow: 0 0 4px var(--glow-gold), inset 0 0 4px rgba(201,168,76,0.1);
  transition: all 160ms ease;
}
.ctl-btn:hover { border-color: var(--gold-bright); color: var(--gold-bright); }
#zoom-toggle { bottom: 12px; right: 4px; }

/* unit info — right-edge slide-in drawer (hidden until a unit is selected) */
#readout {
  position: absolute; top: 78px; right: 0; z-index: 25;
  width: 238px; max-height: calc(100% - 96px); overflow-y: auto;
  font-family: var(--font-data); font-size: 0.8rem; line-height: 1.5;
  color: var(--text-secondary);
  background: rgba(8, 8, 15, 0.92);
  border: 1px solid var(--gold-mid); border-right: none;
  border-radius: 4px 0 0 4px;
  padding: 12px 14px;
  box-shadow: -2px 0 14px rgba(0,0,0,0.5), inset 0 0 10px var(--glow-gold-soft);
  transform: translateX(100%); transition: transform 240ms ease;
}
#readout.show { transform: translateX(0); }
#readout b { color: var(--gold-bright); }

/* ---- unit info card: glanceable stat-icon grid (replaces the word-salad line) ---- */
.uc-head { margin-bottom: 9px; line-height: 1.25; }
.uc-name { font-family: var(--font-display); font-weight: 700; color: var(--gold-bright);
  font-size: 0.95rem; letter-spacing: 0.02em; }
.uc-type { color: var(--text-muted); font-size: 0.72rem; text-transform: capitalize; }
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.stat-box {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-width: 42px; min-height: 44px; padding: 4px; gap: 3px;
  background: rgba(13, 13, 26, 0.6); border: 1px solid var(--gold-dim); border-radius: 3px;
  cursor: pointer; transition: border-color 120ms ease;
}
.stat-box:hover { border-color: var(--gold-mid); }
.stat-box__icon { width: 18px; height: 18px; opacity: 0.92; pointer-events: none; }
.stat-box__value { font-family: var(--font-data); font-weight: 700; font-size: 0.95rem;
  color: var(--text-primary); line-height: 1; pointer-events: none; }
.stat-box__value small { font-size: 0.6rem; color: var(--text-muted); font-weight: 600; }
.stat-box--warn { border-color: var(--fire-orange); box-shadow: 0 0 6px rgba(212, 98, 42, 0.4); }
.stat-box--warn .stat-box__value { color: var(--fire-orange); }
.uc-sub { font-family: var(--font-body); font-size: 0.6rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: #ffffff; margin: 9px 0 5px; }
/* Spells on the unit-info card — open info for BOTH sides + the otherwise-hidden per-spell cooldown (Si 2026-07-23). */
.uc-spell { background: rgba(8,8,15,0.5); border: 1px solid var(--gold-dim); border-radius: 5px; padding: 7px 10px; margin-bottom: 7px; }
.uc-spell-head { display: flex; align-items: center; flex-wrap: wrap; gap: 5px 8px; margin-bottom: 3px; }
.uc-spell-name { font-family: var(--font-body); font-weight: 700; color: #d7b3ff; font-size: 0.82rem; }
.uc-spell-when { font-size: 0.62rem; color: var(--text-muted); font-style: italic; }
.uc-spell-cd { font-size: 0.58rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; padding: 1px 7px; border-radius: 9px; }
.uc-spell-cd--ready { color: #6fe3a0; background: rgba(12,80,22,0.5); border: 1px solid rgba(30,140,55,0.5); }
.uc-spell-cd--wait  { color: #ffb877; background: rgba(120,60,10,0.5); border: 1px solid rgba(200,120,30,0.55); }
.uc-spell-wiz { color: var(--text-muted); font-size: 0.82em; text-transform: none; letter-spacing: 0; }
.uc-spell-tiers { margin: 2px 0 0; }
.ucard-nameline { display: flex; flex-direction: column; gap: 3px; margin-bottom: 14px; }
.ucard-xp-row { display: flex; align-items: center; gap: 7px; margin-top: 2px; }
.ucard-rank-badge { width: 20px; height: 20px; object-fit: contain; image-rendering: pixelated; flex-shrink: 0; }
.ucard-xp-label { font-family: var(--font-data); font-size: 0.72rem; color: var(--text-muted); }
.ucard-modelcount { font-family: var(--font-data); font-size: 0.82rem; color: var(--text-secondary); margin-left: auto; white-space: nowrap; }
.ucard-modelcount--low { color: var(--warn-orange, #d97706); }
.uc-keywords { margin-top: 9px; display: flex; flex-wrap: wrap; gap: 4px; }
.kw { font-family: var(--font-body); font-size: 0.64rem; color: var(--text-secondary);
  background: rgba(201, 168, 76, 0.10); border: 1px solid var(--gold-dim);
  border-radius: 10px; padding: 1px 7px; }
.stat-tip { min-height: 14px; margin-top: 9px; font-family: var(--font-body);
  font-size: 0.68rem; color: var(--gold-bright); }

/* ---- movement range (Phase 3) ---- */
.hex--move {
  fill: rgba(255, 255, 255, 0.28);
  stroke: #ffdd9c; stroke-width: 1.5;
}
.hex--move:hover { fill: rgba(74, 143, 212, 0.45); }
/* armed (previewed) move destination — tap again to confirm */
.hex--move-target {
  fill: rgba(245, 216, 124, 0.34) !important;
  stroke: var(--gold-bright); stroke-width: 2.5;
}
/* WAYPOINT MOVEMENT: an intermediate committed turn-point in a planned chain (the end wears .hex--move-target). */
.hex--waypoint {
  fill: rgba(245, 216, 124, 0.20) !important;
  stroke: var(--gold-mid); stroke-width: 2;
}
/* WAYPOINT MOVEMENT — heads-up: the planned route crosses an already-visible hazard (fire / collapsing ground).
   A soft pulsing warning ring on that one hex — a spatial cue, so it earns a stroke (Si's subtle-cues rule). */
.hex--path-hazard {
  fill: rgba(240, 100, 45, 0.15) !important;
  stroke: #f0863a; stroke-width: 2.5;
  animation: path-hazard-pulse 1.15s ease-in-out infinite;
}
@keyframes path-hazard-pulse { 0%, 100% { stroke-opacity: 0.45; } 50% { stroke-opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .hex--path-hazard { animation: none; stroke-opacity: 0.85; } }
/* REAR-HIT PREVIEW (Si 2026-07-22: "a subtle rear-arc tint on the defender… 'I'm in their back' before I commit").
   While a move destination is armed, any defender whose REAR that hex sits in wears a soft crimson tint. A spatial
   cue marking ONE specific foe, so it earns a light stroke; kept low + warm so it never shouts like a shoot target
   (which it never coexists with — rear-hit is a MOVEMENT-phase cue, shoot targets a shooting-phase one). */
.hex--rear-hit {
  fill: rgba(208, 64, 40, 0.24) !important;
  stroke: rgba(255, 122, 74, 0.6); stroke-width: 2;
  animation: rear-hit-pulse 1.4s ease-in-out infinite;
}
@keyframes rear-hit-pulse { 0%, 100% { stroke-opacity: 0.4; } 50% { stroke-opacity: 0.95; } }
@media (prefers-reduced-motion: reduce) { .hex--rear-hit { animation: none; stroke-opacity: 0.8; } }
/* shoot range zone — long range (outer half): amber tint signals +1 to-hit penalty */
.hex--shoot-range {
  fill: rgba(83, 106, 54, 0.22);
  stroke: rgba(35, 59, 4, 0.22); stroke-width: 1;
}
/* shoot range zone — short range (inner half): green-yellow, no penalty */
.hex--shoot-range-close {
  fill: rgba(160, 230, 20, 0.28);
  stroke: rgba(140, 210, 10, 0.50); stroke-width: 1.5;
}
/* shoot range zone — the HERO'S extended reach (Si: a ranged commander like Amblin out-ranges his regiment).
   A GOLD warning band beyond the unit's own range — distinct from the unit's green short/amber long and from the
   red target/blind — so the player reads three tiers at a glance and sees the hexes only the hero can pick off. */
.hex--shoot-hero {
  fill: rgba(228, 164, 56, 0.24);
  stroke: rgba(244, 196, 92, 0.62); stroke-width: 1.75;
}
/* ---- BLIND FIRE: in range, but the shooter CANNOT SEE the hex ----------------------------------------
   Only a Marksman/Volley unit ever sees these. It may take the shot, but it is a lob over a hill: on a
   D10 of 1–6 the shell DRIFTS to a random adjacent hex and lands on whatever is there — including its own
   army. That is a far worse shot than the long-range +1, so it must not wear the same green.
   Red = blind. Short/long split exactly as the green pair does: brighter = closer. */
.hex--shoot-blind-close {
  fill: rgba(214, 68, 52, 0.26);
  stroke: rgba(232, 96, 72, 0.55); stroke-width: 1.5;
  stroke-dasharray: 5 3;
}
.hex--shoot-blind {
  fill: rgba(138, 34, 30, 0.22);
  stroke: rgba(168, 52, 44, 0.42); stroke-width: 1;
  stroke-dasharray: 5 3;
}
/* in-range enemy with blocked LOS or wrong arc */
.hex--shoot-blocked {
  fill: rgba(120, 20, 20, 0.22) !important;
  stroke: rgba(200, 50, 50, 0.50); stroke-width: 1.5;
  stroke-dasharray: 4 3;
}
/* valid shooting target highlight */
.hex--shoot {
  fill: rgba(196, 32, 32, 0.30) !important;
  stroke: var(--blood-bright); stroke-width: 2;
}
/* …and a valid target the shooter cannot SEE: the shot may drift. Dashed = you are firing blind. */
.hex--shoot--blind {
  stroke-dasharray: 5 3;
  stroke: rgba(255, 138, 96, 0.95);
}
/* SPELL TARGET — a legal hex to cast at, after the roll has set the range (Si's range-lit picker). Arcane violet
   so it never reads as a shoot (red) or an objective (gold); a soft pulse invites the tap. */
.hex--spell-target {
  fill: rgba(150, 90, 220, 0.30) !important;
  stroke: rgba(196, 148, 255, 0.95); stroke-width: 2.4;
  animation: spellTargetPulse 1.1s ease-in-out infinite;
}
@keyframes spellTargetPulse { 0%,100% { stroke-opacity: 0.6; } 50% { stroke-opacity: 1; } }
.reduce-motion .hex--spell-target { animation: none; }
@media (prefers-reduced-motion: reduce) { .hex--spell-target { animation: none; } }
/* ---- MAGIC RESIST AURA — anti-magic ground around a wizard ----------------------------------------
   Persistent, not selection-gated: the ward is terrain as far as the magic game is concerned, and you must
   be able to read the map without poking at units. Kept very light — it underlays everything else and must
   never fight the move/shoot overlays for attention. Your wards are blue, theirs violet, so where an
   archmage's ward laps an enemy caster you can SEE the lap. */
/* A WHISPER, NOT A SHOUT. Only units that PROJECT a ward light anything — `Magic Resist Aura X`, i.e. the
   wizards. A unit with plain self-MR (Stormrunners, Trolls, King Johann) tints nothing; his resistance is
   personal and there is no ground to show.
   Kept deliberately faint. I first gave every warded hex a coloured STROKE too, which lit up the grid like a
   Christmas tree — and ?demo=all, which stands EVERY wizard in the game shoulder to shoulder, turned half the
   board purple. A real battle has one or two casters, and the ward should read as a faint stain on the ground
   you notice when you look for it — not as a UI overlay competing with the move and shoot ranges. */
/* Si: "players should still be looking at the GRASS, with a hint of magic colouring to it."
   So the ward is a tint ON the terrain, never a layer OVER it. If you find yourself reading the ward before
   you read the ground, it is too strong. */
.hex--ward     { fill: rgba(88, 152, 232, 0.08); }   /* your ward — a cold blue breath on the grass */
.hex--ward-foe { fill: rgba(168, 104, 228, 0.08); }  /* theirs — violet                             */
/* ---- unit vision overlay (shown when a unit is selected) ---- */
/* line of sight (front arc, unobstructed) — faint blue wash */
.hex--los { fill: rgba(106, 135, 111, 0.3); pointer-events: none; }
/* weapon range — long half: amber (signals the +1 to-hit) */
.hex--weaprange { fill: rgba(210, 150, 60, 0.18); pointer-events: none; }
/* weapon range — short half: warmer amber-gold, no penalty */
.hex--weaprange-close { fill: rgba(220, 120, 50, 0.24); pointer-events: none; }
/* ---- weapon range the shooter CANNOT SEE (Marksman / Volley only) ------------------------------------
   He may fire here, but it is a blind lob: on a D10 of 1–6 the shell DRIFTS to a random adjacent hex and
   lands on whatever is there — including his own army. A far worse shot than the long-range +1, so it gets
   RED rather than amber, split short/long exactly as the amber pair is. The dashes say "you are guessing". */
.hex--weaprange-blind-close {
  fill: rgba(214, 68, 52, 0.26); pointer-events: none;
  stroke: rgba(240, 110, 85, 0.55); stroke-width: 1.5; stroke-dasharray: 5 3;
}
.hex--weaprange-blind {
  fill: rgba(138, 34, 30, 0.22); pointer-events: none;
  stroke: rgba(180, 60, 50, 0.40); stroke-width: 1; stroke-dasharray: 5 3;
}
/* ENEMY MOVEMENT REACH (Si 2026-07-19: "a faint outline where the enemy can move to… each hex an outer white
   colour rather than toning them"). A near-hollow WHITE RING per reachable hex — a fill would clash with the
   weapon-range / LOS TINTS this overlays, so movement is read by its outline, shooting by its colour. Defined
   after the shoot rules so the white edge wins where a hex is both reachable and in range. */
.hex--enemy-reach {
  fill: rgba(255, 255, 255, 0.05);
  stroke: rgba(255, 255, 255, 0.9); stroke-width: 2; pointer-events: none;
}
/* SCOUT-PHASE SPOTLIGHT (Si): a dark scrim over the whole board, with the active scout's reachable hexes redrawn
   BRIGHT above it — so during scouting only the scout and where it can go are lit. Blue to match the SCOUT PHASE
   banner. The scrim/highlights are pointer-events:none so taps still reach the board (move a scout, inspect a foe). */
.scout-scrim { fill: rgba(0, 0, 0, 0.52); pointer-events: none; }   /* moderate dim — enemies stay readable through it; the reach hexes are punched clear by the mask (Si) */
.scout-reach-hi {
  fill: rgba(120, 205, 255, 0.32); stroke: rgba(185, 230, 255, 0.98); stroke-width: 2.5;
  pointer-events: none; animation: scout-reach-pulse 1.6s ease-in-out infinite;
}
@keyframes scout-reach-pulse {
  0%, 100% { fill: rgba(120, 205, 255, 0.24); }
  50%      { fill: rgba(140, 215, 255, 0.42); }
}
@media (prefers-reduced-motion: reduce) { .scout-reach-hi { animation: none; } }
.move-path {
  fill: none; stroke: var(--gold-bright); stroke-width: 3;
  stroke-dasharray: 7 6; stroke-linecap: round; opacity: 0.9; pointer-events: none;
}
.move-path-end {
  fill: rgba(245, 216, 124, 0.25); stroke: var(--gold-bright);
  stroke-width: 2; pointer-events: none;
}
.unit-facing-ghost { fill: var(--gold-bright); opacity: 0.4; pointer-events: none; }
.facing-arrow--armed .facing-tri {
  fill: var(--gold-bright); filter: drop-shadow(0 0 7px var(--glow-gold));
}
.unit-mp {
  font-family: var(--font-data); font-size: 11px; font-weight: 700;
  fill: var(--gold-bright); text-anchor: middle; pointer-events: none; user-select: none;
}
.unit-mp-bg { fill: rgba(8,8,15,0.85); stroke: var(--gold-mid); stroke-width: 1; }

/* ---- initiative strip slots ---- */
.init-slot {
  flex: 0 0 auto; height: 45px; width: 45px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 6px; overflow: hidden;
  border: 2px solid transparent;
  background: rgba(12,12,20,0.55);
  transition: all 160ms ease; position: relative; cursor: pointer;
}
.init-slot .is-portrait {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.init-slot .is-fallback {
  display: none; align-items: center; justify-content: center;
  font-family: var(--font-body); font-size: 0.62rem; font-weight: 600;
  color: var(--text-secondary); white-space: nowrap;
}
.init-slot.is-human { border-color: #9bbfec; box-shadow: 0 0 6px rgba(42,92,154,0.6); }
.init-slot.is-orc   { border-color: #1dfc60; box-shadow: 0 0 6px rgba(196,32,32,0.6); }
.init-slot--active {
  height: 56px; width: 56px;
  border-width: 2.5px;
  box-shadow: 0 0 10px var(--glow-gold), 0 0 20px var(--glow-gold-soft);
}
.init-slot--active.is-human { border-color: var(--gold-bright); box-shadow: 0 0 10px var(--glow-gold), 0 0 20px var(--glow-gold-soft); }
.init-slot--active.is-orc   { border-color: var(--gold-bright); box-shadow: 0 0 10px var(--glow-gold), 0 0 20px var(--glow-gold-soft); }
.init-slot--spent { opacity: 0.35; }
/* Waited units are NOT dimmed (Si): they're still coming, so they read at full strength like everyone else.
   The WAIT divider already says where they sit in the order — fading them on top of that just looked broken. */
.init-slot--engaged { box-shadow: 0 0 0 2px #c42020 inset, 0 0 6px rgba(196,32,32,0.5); }

/* waited divider — vertical line with label between normal and waited units */
.init-divider {
  flex: 0 0 auto; width: 2px; align-self: stretch;
  background: var(--gold-mid); opacity: 0.55;
  position: relative; margin: 0 6px;
}
.init-divider__label {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  background: var(--bg-deep, #08080f); color: var(--gold-mid);
  font-family: var(--font-body); font-size: 0.5rem; font-weight: 700;
  letter-spacing: 0.1em; padding: 2px 3px; white-space: nowrap;
}

/* ---- init scroll arrows ---- */
.init-arrow {
  flex: 0 0 auto; width: 28px; align-self: stretch;
  background: rgba(8,8,15,0.6); border: none; color: var(--gold-mid);
  font-size: 1.3rem; cursor: pointer; transition: color 140ms ease;
  height: 50px; margin-top: 4px;
}
.init-arrow:hover { color: var(--gold-bright); }

/* ---- round overlay buttons (zoom magnifier, info "i") ---- */
.round-btn {
  position: absolute; z-index: 22;
  width: 44px; height: 44px; border-radius: 50%;
  background: rgba(8,8,15,0.85); border: 1px solid var(--gold-mid);
  color: var(--text-primary); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 5px var(--glow-gold-soft); transition: all 150ms ease;
}
.round-btn:hover, .round-btn.active { border-color: var(--gold-bright); color: var(--gold-bright); }

/* ---- right-side button cluster — vertically centered on the battlefield ---- */
.side-btns {
  position: absolute;
  right: -10px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  z-index: 22;
}

/* ---- info panel button — wooden carved "i" art ---- */
.btn-info {
  display: block;
  width: 44px;
  height: 130px;
  background: url('assets/ui/wooden_panels/i_panel.webp') center / contain no-repeat;
  border: none;
  color: transparent;
  cursor: pointer;
  transition: filter 150ms ease;
}
.btn-info:hover { filter: brightness(1.15); }
.btn-info.active { filter: brightness(1.2) drop-shadow(0 0 4px rgba(245,216,124,0.5)); }

/* ---- log panel button — sits directly below the "i" panel ---- */
.btn-log {
  display: block;
  width: 42px; height: 80px;
  background: url('assets/ui/wooden_panels/log_panel.webp') center / contain no-repeat;
  border: none; color: transparent; cursor: pointer;
  transition: filter 150ms ease;
}
.btn-log:hover { filter: brightness(1.15); }
.btn-log.active { filter: brightness(1.2) drop-shadow(0 0 4px rgba(245,216,124,0.5)); }

/* ---- log drawer ---- */
/* Si wants the log to be a FEATURE, not a footnote — it's where the whole fight is told. Wider, taller. */
.log-drawer {
  position: absolute; z-index: 21;
  top: 50%; right: 34px;
  width: 340px; max-height: min(60vh, 420px);
  background: rgba(10, 8, 5, 0.92);
  border: 1px solid var(--gold-dim); border-radius: 6px 0 0 6px;
  display: flex; flex-direction: column;
  transform: translateX(calc(100% + 34px)) translateY(-50%); opacity: 0; pointer-events: none;
  transition: transform 220ms ease, opacity 180ms ease;
  overflow: hidden;
}
.log-drawer.open { transform: translateX(0) translateY(-50%); opacity: 1; pointer-events: all; }
.log-drawer-head {
  font-family: var(--font-display); font-size: 0.72rem; letter-spacing: 0.1em;
  color: var(--gold-bright); text-transform: uppercase;
  padding: 7px 12px 5px; border-bottom: 1px solid var(--gold-dim); flex-shrink: 0;
}
/* min-height: 0 IS LOAD-BEARING. A flex item defaults to `min-height: auto`, which refuses to shrink below its
   content — so this never becomes a real scroll container, it just grows, and scrollTop/scrollHeight lie. That is
   why the log would not keep pace with the fight (Si). It went unnoticed before only because the old log
   PREPENDED — newest at the top, so nothing ever had to scroll to be seen. */
.log-drawer-body {
  overflow-y: auto; flex: 1 1 auto; min-height: 0;
  padding: 8px 8px 10px;
  display: flex; flex-direction: column; gap: 7px;
  scroll-behavior: auto;   /* jump, don't glide — a smooth scroll cannot keep up with a fast combat */
  /* body { touch-action: none } kills touch/trackpad scrolling across the WHOLE page — it exists to stop the
     board panning when you drag it. But it also means the log cannot be dragged: "I cant scroll on it".
     Hand vertical scrolling back, but only inside this panel. */
  touch-action: pan-y;
  overscroll-behavior: contain;   /* and don't let a flick at the end of the log scroll the page behind it */
}
/* ── THE BOXES MUST NOT SHRINK ─────────────────────────────────────────────────────────────────────────────
   THIS is why "the tabs just want to climb on top of eachother" and why Geralt's Double Strike line was never
   visible. The body is a flex column, so every box is a flex ITEM — and a flex item defaults to `flex-shrink: 1`.
   So once the fight overflowed the drawer, flex did not scroll it: it SQUASHED the boxes to fit. And .log-group
   carries `overflow: hidden` (for its rounded corners), so a squashed box silently CLIPPED its own contents. The
   log wasn't losing the lines, it was cropping them off the bottom of each post.
   Pin every box to its natural height. Then the content genuinely overflows, and the container genuinely scrolls. */
.log-drawer-body > * { flex: 0 0 auto; }
.log-entry {
  display: flex; gap: 8px; align-items: flex-start;
  padding: 6px 10px;
  font-family: var(--font-body); font-size: 0.74rem; color: var(--text-secondary);
  min-width: 0;   /* a flex child defaults to min-width:auto and refuses to shrink — that is how text escapes the box */
}
.log-turn { font-family: var(--font-data); font-size: 0.66rem; color: var(--text-muted); flex-shrink: 0; padding-top: 2px; }
.log-msg  { color: var(--text-primary); line-height: 1.4; flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }

/* ── ONE BOXED STORY PER EVENT (Si: "headline, then the news of what happened") ────────────────────────────
   A whole melee is a single bordered block: the headline says who is fighting whom, and every strike, save and
   morale line for that fight sits inside it. Newest at the BOTTOM — pushLog appends, and the drawer sticks to
   the bottom unless you have scrolled up to read something. */
.log-group {
  border: 1px solid rgba(138,110,42,0.42);
  border-left: 3px solid var(--gold-mid);
  border-radius: 4px;
  background: rgba(28, 20, 10, 0.55);
  overflow: hidden;   /* nothing escapes the box */
}
.log-group-head {
  display: flex; gap: 8px; align-items: baseline;
  padding: 6px 10px;
  background: rgba(138,110,42,0.16);
  border-bottom: 1px solid rgba(138,110,42,0.3);
  font-family: var(--font-display); font-size: 0.7rem; letter-spacing: 0.04em;
  color: var(--gold-bright);
}
.lg-title { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; line-height: 1.35; }
.lg-vs    { color: var(--text-muted); font-family: var(--font-body); font-size: 0.66rem; }
.log-group-body { padding: 4px 0 6px; }
/* Inside a story the rows are plain — the BOX is the frame, so boxing each line again would just be noise. */
.log-group .log-entry { padding: 3px 10px; }
.log-group .log-entry + .log-entry { border-top: 1px solid rgba(255,255,255,0.04); }

/* A standalone event (a shot, a rally, a rout) is its own small box — combatLogHtml already gives it a
   headline + rows, so it is a one-paragraph story and reads as one. */
.log-entry--combat:not(.log-group .log-entry) {
  border: 1px solid rgba(138,110,42,0.3);
  border-left: 3px solid rgba(138,110,42,0.65);
  border-radius: 4px;
  background: rgba(28, 20, 10, 0.4);
}

/* ---- structured combat log ---- */
.lc-head {
  font-family: var(--font-body); font-size: 0.78rem; font-weight: 600;
  color: var(--gold-bright); margin-bottom: 4px;
}
.lc-tag { font-size: 0.66rem; color: var(--gold-mid); font-weight: 400; }
/* Area-fire keyword chip (Volley / Explode) — a warmer amber so a barrage tag reads as a KEYWORD in effect, not an outcome. */
.lc-tag--area { color: #d8a24e; letter-spacing: 0.03em; }
.lc-pen { font-size: 0.66rem; color: var(--text-muted); }
/* Crit / Normal sub-blocks within one strike (Si: crits are resolved first, so the log details them as their own
   block above the ordinary hits). A hairline rule sets each block apart; the crit header wears the gold. */
.lc-subhead {
  font-family: var(--font-body); font-size: 0.6rem; font-weight: 700; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--text-muted);
  margin: 5px 0 2px; padding-top: 4px; border-top: 1px solid rgba(255,255,255,0.07);
}
.lc-subhead--crit { color: var(--gold-bright); border-top-color: rgba(201,168,76,0.35); }
.lc-row {
  display: flex; align-items: baseline; gap: 6px; margin-bottom: 2px; flex-wrap: wrap;
}
/* Stat icon before each resolution line (COMBAT_ICONS brief) — sized to the label, centred not baseline-aligned. */
.lc-ic { width: 13px; height: 13px; flex: 0 0 auto; object-fit: contain; align-self: center; opacity: 0.92; }
/* THE OVERFLOW (Si: "the occasional word goes off the console").
   This was `flex-shrink: 0` — the label was a flex item FORBIDDEN from shrinking, so a long line ("drift roll
   (1–6 scatters), the shot goes wide") took its full max-content width and ran straight off the edge of the
   drawer. The dice could wrap; the words could not. Let it shrink, and let it wrap. */
.lc-lbl {
  font-family: var(--font-body); font-size: 0.8rem; font-weight: 600;
  color: var(--text-primary);
  flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere;
}
.lc-dice {
  font-family: var(--font-data); font-size: 0.6rem; font-weight: 700;
  display: flex; flex-wrap: wrap; gap: 2px; align-items: center;
}
.lc-die {
  display: inline-flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; border-radius: 3px;
  line-height: 1; box-sizing: border-box;   /* the faction faces carry a 1px border — keep every die the same size */
}
/* FACTION DICE (Si). The humans roll blue, the orcs roll green — and a die belongs to whoever ROLLED it, so the
   save row carries the DEFENDER's dice. That lets tone mean one thing and one thing only, on every row:

       bright and shiny = this roll SUCCEEDED for the one who rolled it
       dark and dull    = it failed him

   Hue tells you whose dice these are; tone tells you how they went. Same base colour within a faction, but far
   enough apart in tone to read at a glance in a scrolling feed — a lit die and a dead one, not two shades of green.
   (The bright face is deliberately doing three things at once: it lightens, it saturates, and it glows. A colour
   difference alone would not survive the log's small type at 14px.) */
.lc-die--human.is-good {
  background: linear-gradient(180deg, #6fb4ff, #2f74d6);
  color: #f2f8ff; border: 1px solid #8cc6ff;
  box-shadow: 0 0 5px rgba(90,170,255,0.55), inset 0 1px 0 rgba(255,255,255,0.45);
  text-shadow: 0 1px 1px rgba(0,0,0,0.45);
}
.lc-die--human.is-bad {
  background: #16202e; color: #47576d; border: 1px solid #22303f;
}
.lc-die--orc.is-good {
  background: linear-gradient(180deg, #8fdc55, #3f9c2c);
  color: #f4ffe9; border: 1px solid #a9e97a;
  box-shadow: 0 0 5px rgba(130,215,85,0.55), inset 0 1px 0 rgba(255,255,255,0.40);
  text-shadow: 0 1px 1px rgba(0,0,0,0.45);
}
.lc-die--orc.is-bad {
  background: #18240f; color: #4d5c3c; border: 1px solid #24331a;
}
/* Nothing was rolled at all — no armour to save with. Not a failure, an absence. */
.lc-die--none { background: #2a2a2a; color: var(--text-muted); border: 1px solid #383838; }

/* THE INITIAL TO-HIT POOL (Si: "the initial roll should have ALL dice including crits in there… the big pool of
   dice is missing") — the WHOLE roll stated once at the top, before the attacks are separated into their own
   resolves below. Set apart as THE roll of the strike. */
.lc-pool {
  margin: 3px 0 6px; padding: 4px 7px;
  background: rgba(255,255,255,0.045); border-radius: 3px;
  border-left: 3px solid rgba(255,255,255,0.20);
}
.lc-pool .lc-lbl { font-weight: 700; }
/* A crit die GLOWS gold within the pool so the eye finds it among the ordinary dice ("…including crits"). Two
   classes deep so it wins over the faction .is-good/.is-bad face and swaps its glow for the gold ring. */
.lc-die--crit.is-good, .lc-die--crit.is-bad {
  border-color: var(--gold-bright);
  box-shadow: 0 0 0 2px var(--gold-bright), 0 0 7px rgba(201,168,76,0.85);
}
/* SEPARATED RESOLVES (Si: "separate the attacks… the resolves tabbed or inside their own borders"). Each group —
   crits first, then normal hits — sits in its own tabbed, bordered box so its wound→save→deaths chain reads as one. */
.lc-resolve {
  margin: 0 0 6px 8px; padding: 3px 9px 4px;
  border-left: 2px solid rgba(255,255,255,0.14); border-radius: 2px;
  background: rgba(255,255,255,0.025);
}
.lc-resolve--crit { border-left-color: rgba(201,168,76,0.5); background: rgba(201,168,76,0.05); }
.lc-resolve .lc-subhead { border-top: none; padding-top: 0; margin-top: 1px; }   /* the box border already sets it apart */
/* "N hits from the roll" — a back-reference to the pool above, not a fresh roll, so it's quieter than a full row. */
.lc-from .lc-lbl { color: var(--text-muted); font-weight: 600; font-size: 0.72rem; }

/* FACTION-TINTED UNIT NAMES (Si). Humans blue, orcs green — the same hues as their dice, so a blue name and a
   blue die read as the same side at a glance and the log stops being one undifferentiated blur. Brighter than the
   dice faces because this is text on a dark field, not a filled pill. */
.lc-name { font-weight: 700; }
.lc-name--human { color: #7fbcff; }
.lc-name--orc   { color: #9ee06f; }
.lc-deaths {
  font-family: var(--font-body); font-size: 0.88rem; font-weight: 700;
  color: var(--blood-bright); margin-top: 4px;
}
.lc-no-kills {
  font-family: var(--font-body); font-size: 0.66rem;
  color: var(--text-muted); margin-top: 3px; font-style: italic;
}
/* HITS ARE NOW LANDING ON THE HERO. The screen of bodies is gone and the wounds are going on HIM — the one moment
   in a fight where a wargamer slows the dice down and says where they're going (Si). Gold, boxed and set apart
   from the ordinary red death count, because it is a different KIND of fact: not how many died, but who is
   taking them now. */
.lc-alloc {
  font-family: var(--font-body); font-size: 0.8rem; font-weight: 700;
  color: var(--gold-bright); margin-top: 5px; padding: 4px 8px;
  background: rgba(201,168,76,0.10);
  border-left: 3px solid var(--gold-mid); border-radius: 2px;
}
.lc-alloc b { color: #fff3cf; }

/* ancient scroll = objectives modal trigger — sits below the top bar, above the "i" */
.scroll-btn {
  display: block;
  width: 40px; height: 40px; padding: 0; border: none; background: none; cursor: pointer;
  filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.55)); transition: transform 140ms ease;
  margin-right: 12px;
}
.scroll-btn img { width: 100%; height: 100%; object-fit: contain; display: block; pointer-events: none; }
.scroll-btn:hover { transform: scale(1.08); }
/* objectives modal body */
.obj-block { border: 1px solid var(--gold-dim); border-radius: 5px; background: rgba(8, 8, 15, 0.45);
  padding: 12px 14px; margin-bottom: 12px; }
.obj-block.obj-primary { border-color: var(--gold-mid); }
/* scenario header — no border/box, reads as the modal's intro paragraph above the objectives */
.obj-scenario { padding: 2px 4px; margin-bottom: 18px; }
.obj-scenario .obj-name { font-size: 1.2rem; margin-top: 2px; }
.obj-scenario .obj-desc { font-style: italic; }
.obj-label { font-family: var(--font-body); font-size: 0.66rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--text-muted); }
.obj-name { font-family: var(--font-display); font-weight: 700; color: var(--gold-bright);
  font-size: 1.05rem; margin: 3px 0 5px; }
.obj-desc { font-family: var(--font-body); color: var(--text-secondary); font-size: 0.86rem; line-height: 1.45; }
/* THE STORY PRIZE — a rescued hero. Reads as a REWARD, not a chore (Si: "missing all the artwork and shiny"):
   his FACE in a gold-ringed medallion, a warm gilt card, and a slow shimmer sweeping across. A nameless captive
   stays a plain .obj-block; only a NAMED hero rescue earns this. */
.obj-block.obj-prize {
  display: flex; align-items: center; gap: 14px;
  position: relative; overflow: hidden;
  border-color: var(--gold-bright);
  background: linear-gradient(135deg, rgba(58, 44, 12, 0.55) 0%, rgba(16, 12, 20, 0.5) 55%);
  box-shadow: 0 0 0 1px rgba(212, 175, 55, 0.3), inset 0 0 26px rgba(212, 175, 55, 0.14);
}
.obj-block.obj-prize::after {   /* the shimmer — a soft gold band drifting across the card */
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(115deg, transparent 34%, rgba(255, 231, 158, 0.16) 48%, transparent 60%);
  background-size: 250% 100%; animation: objPrizeShimmer 5s ease-in-out infinite;
}
.obj-prize-portrait {
  flex: 0 0 76px; width: 76px; height: 76px; border-radius: 50%;
  object-fit: cover; object-position: 50% 20%;   /* face-biased crop */
  border: 2px solid var(--gold-bright);
  box-shadow: 0 0 15px rgba(212, 175, 55, 0.55), 0 2px 7px rgba(0, 0, 0, 0.6);
}
.obj-prize-portrait--relic { border-radius: 10px; object-position: center; }   /* a magic item is a square relic, not a face */
.obj-prize-body { flex: 1 1 auto; min-width: 0; position: relative; z-index: 1; }
.obj-block.obj-prize .obj-label { color: var(--gold-mid); }
@keyframes objPrizeShimmer { 0% { background-position: 185% 0; } 60%, 100% { background-position: -85% 0; } }
@media (prefers-reduced-motion: reduce) { .obj-block.obj-prize::after { animation: none; } }
/* top-right grey stone panel — fixed proportions, holds the turn rock + burger, bleeds off
   the top & right edges (overhangs the wooden beam beneath). No stretching: auto width keeps aspect. */
.cap-right {
  flex: 0 0 auto; align-self: flex-start; position: relative; z-index: 3;
  height: 70px;   /* overhangs the 60px bar a few px, same as the left cap */
  display: flex; align-items: center; gap: 4px; padding: 0 8px 0 18px;
  background: url('assets/ui/wooden_panels/top_right.webp') calc(129% + 114px) bottom / auto 135% no-repeat;
}
.cap-right > * { position: relative; z-index: 1; }
/* turn "rock" sign — replaces the gold circle */
.turn-counter {
  flex: 0 0 auto; align-self: center;
  width: 74px; height: 52px; border: none; border-radius: 0; box-shadow: none;
  background: url('assets/ui/wooden_panels/top_right_turn.webp') center / contain no-repeat;
  display: flex; flex-direction: column; align-items: center; justify-content: center; margin-top: -5px;
}
.turn-counter .tc-label {
  font-family: var(--font-display); font-size: 0.46rem; letter-spacing: 0.1em;
  color: #4c3200; text-transform: uppercase;
}
.turn-counter .tc-num {
  font-family: var(--font-display); font-weight: 700; font-size: 1.25rem;
  color: #4c3200;
  line-height: 1;
}
/* Time-pressure scenarios: the buzzer turn burns red so "final turn" reads at a glance. */
.turn-counter .tc-num--final { color: #a11; animation: tcFinal 1s ease-in-out infinite; }
@keyframes tcFinal { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } }

/* ---- action bar layout ---- */
.ab-left {
  position: absolute; left: 0; bottom: 0; height: 64px;
  padding: 0 14px;
  display: flex; flex-direction: column; justify-content: center; gap: 2px;
  min-width: 0;
  pointer-events: auto; z-index: 3;
}
/* name line: rank badge + unit name */
.ab-nameline { display: flex; align-items: center; gap: 5px; }
/* stat-card page toggle (unit ⇄ commander) — a small gold pill next to the name */
.ab-page {
  background: rgba(201,168,76,0.16); border: 1px solid var(--gold-dim);
  color: var(--gold-bright); font-family: var(--font-body); font-size: 0.6rem; font-weight: 600;
  letter-spacing: 0.03em; padding: 2px 8px; border-radius: 4px; cursor: pointer;
  margin-left: 6px; white-space: nowrap; flex-shrink: 0; transition: background 0.12s;
}
.ab-page:hover { background: rgba(201,168,76,0.3); }
.ab-rank-icon { width: 28px; height: 28px; object-fit: contain; image-rendering: pixelated; flex-shrink: 0; }
.ab-left .ab-unit { font-family: var(--font-body); font-weight: 600; color: var(--text-gold); font-size: 0.90rem;
  line-height: 1; text-shadow: 1px 1px #000;}
.ab-left .ab-sub  { font-family: var(--font-data); font-size: 0.82rem; color: #ffffff; }
/* XP bar */
.ab-xp-bar { display: flex; align-items: center; gap: 5px; min-width: 0;  margin-top: -6px; }
.xp-bar-track {
  flex: 1; height: 6px; min-width: 60px; max-width: 200px;
  background: rgba(255,255,255,0.15); border-radius: 3px; overflow: hidden;
}
.xp-bar-fill { height: 100%; background: var(--gold-mid); border-radius: 3px; transition: width 0.4s ease; }
.xp-bar-label { font-family: var(--font-data); font-size: 0.65rem; background: rgba(0,0,0,0.55); padding: 1px 5px;
  border-radius: 10px; color: #ffffff; white-space: nowrap; flex-shrink: 0; }
/* glanceable icon stats — one row of eight */
.ab-stats { display: flex; flex-wrap: nowrap; gap: 5px; align-items: center; }
.ab-stat {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  cursor: pointer;
  background-color: rgba(0, 0, 0, 0.6);
  padding: 1px 2px;
  border-radius: 1px;
  border: solid 1px #000;
}
.ab-stat img { width: 13px; height: 13px; opacity: 0.92; pointer-events: none; }
.ab-stat b { font-family: var(--font-data); font-weight: 700; font-size: 0.72rem; color: var(--text-primary); pointer-events: none; }
.ab-stat--warn b { color: var(--fire-orange); }
.ab-stat--down  { background: rgba(140, 65, 8,  0.65); border-color: rgba(200, 95, 15, 0.5); }
.ab-stat--crit  { background: rgba(110, 12, 12, 0.75); border-color: rgba(185, 20, 20, 0.55); }
.ab-stat--up    { background: rgba(12,  80, 22, 0.65); border-color: rgba(30, 140, 55, 0.5); }
.ab-stat--down b, .ab-stat--crit b, .ab-stat--up b { color: #fff; }
/* tap-a-stat tooltip (new-player help) */
/* The battle screen's ONE tooltip — stat chips, initiative portraits, terrain hexes (see showTip in game.js).
   Wraps rather than nowraps: a stat label is short, but terrain carries its effect prose too and would otherwise
   run straight off a phone. JS measures it after the content is set, so a max-width is safe. */
.stat-tooltip {
  position: fixed; z-index: 200; pointer-events: none;
  background: rgba(8, 8, 15, 0.96); border: 1px solid var(--gold-mid); border-radius: 4px;
  padding: 5px 10px; font-family: var(--font-body); font-size: 0.72rem; color: var(--gold-bright);
  max-width: min(280px, 88vw); line-height: 1.35; text-align: center;
  box-shadow: 0 2px 10px rgba(0,0,0,0.6);
  opacity: 0; transition: opacity 120ms ease;
}
.stat-tooltip.show { opacity: 1; }
/* Stacked stat tooltip (Si 2026-07-23: "each effect in a row … green and red") — the stat name, then every active
   modifier on its own row, GREEN if it helps / RED if it hurts. */
.stat-tooltip .tip-head { display: block; font-weight: 700; color: var(--gold-bright); margin-bottom: 3px; }
.stat-tooltip .tip-row  { display: block; font-weight: 600; }
.stat-tooltip .tip-row--good    { color: #6fe3a0; }
.stat-tooltip .tip-row--bad     { color: #ff8a72; }
.stat-tooltip .tip-row--neutral { color: #c9bfa6; opacity: 0.85; }

/* ----------  PUSHBACK CHOICE PROMPT  ---------- */
.pushback-prompt {
  display: none; position: absolute; bottom: 85px; left: 50%; transform: translateX(-50%);
  align-items: center; gap: 10px;
  background: rgba(10, 8, 5, 0.92); border: 1px solid var(--gold-mid); border-radius: 5px;
  padding: 8px 16px; z-index: 15; pointer-events: auto; white-space: nowrap;
}
.pushback-prompt.visible { display: flex; }
.pb-label {
  font-family: var(--font-body); font-size: 0.78rem;
  color: var(--text-secondary); margin-right: 4px;
}
.pb-btn {
  font-family: var(--font-body); font-weight: 600; font-size: 0.8rem;
  letter-spacing: 0.06em; text-transform: uppercase; border-radius: 3px;
  min-width: 108px; height: 36px; cursor: pointer; border: 1px solid;
  transition: all 140ms ease;
}
.pb-btn--follow { background: rgba(30,70,140,0.75); border-color: #4a8adf; color: var(--gold-bright); }
.pb-btn--follow:hover { background: rgba(30,70,140,0.95); border-color: var(--gold-bright); }
.pb-btn--hold { background: rgba(40,20,8,0.75); border-color: var(--gold-dim); color: var(--text-secondary); padding: 8px; }
.pb-btn--hold:hover { background: rgba(60,30,10,0.95); border-color: var(--gold-mid); color: var(--text-primary); }
/* Centre cluster — three overlapping plank panels */
.ab-actions {
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  width: 180px;
  height: 120px;
  background: none;
  display: flex;
  align-items: stretch;
  z-index: 4;
  pointer-events: auto;
  margin-bottom: -28px;
}
/* Flanking pale planks — same height as face, stacked behind via z-index */
.ab-actions::before {
  content: ''; position: absolute; inset: 0;
  background:
    url('assets/ui/wooden_panels/bottom_middle_left.webp')  left  bottom / 70px auto no-repeat,
    url('assets/ui/wooden_panels/bottom_middle_right.webp') right bottom / 70px auto no-repeat;
  z-index: 0;
}
/* Front face — overlaps both flanking planks */
.ab-actions::after {
  content: ''; position: absolute; left: 50%; bottom: 0; transform: translateX(-50%);
  width: 55%; height: 100%;
  background: url('assets/ui/wooden_panels/bottom_middle_top.webp') center bottom / 70px auto no-repeat;
  z-index: 1;
}
.act-btn {
  font-family: var(--font-body); font-weight: 600; font-size: 0.8rem;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-primary); background: rgba(8,8,15,0.7);
  border: 1px solid var(--gold-mid); border-radius: 2px;
  min-width: 84px; height: 44px; cursor: pointer; transition: all 140ms ease;
}
.act-btn:hover:not(:disabled) { border-color: var(--gold-bright); color: var(--gold-bright); background: rgba(201,168,76,0.12); }
.act-btn:disabled { opacity: 0.35; cursor: not-allowed; filter: brightness(0.55) saturate(0.4); }
.act-btn--move  { border-color: var(--human-glow); }
.act-btn--charge{ border-color: var(--blood-bright); }
/* PULSE a live Shoot / Action button so a new player notices the unit can still do something (Si 2026-07-23). Gentle
   glow; never on a disabled button; respects reduced-motion. */
.act-btn--pulse:not(:disabled) { animation: act-btn-pulse 1.5s ease-in-out infinite; }
@keyframes act-btn-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(245,216,124,0); }
  50%      { box-shadow: 0 0 9px 1.5px rgba(245,216,124,0.6); border-color: var(--gold-bright); }
}
@media (prefers-reduced-motion: reduce) { .act-btn--pulse { animation: none; } }
/* WAIT / END ACTIVATION wear their own carved frames (art = the button background) */
#action-bar .act-btn--end {
  background: url('assets/ui/wooden_panels/bottom_right_end_activation.webp') center / 100% 100% no-repeat;
  border: none; border-radius: 0; color: #2a1c0c; text-shadow: 0 1px 0 rgba(255,240,200,0.35);
  min-width: 0; width: 116px; height: 50px; padding: 0 10px; font-size: 0.64rem; font-weight: 700;
}
#action-bar .act-btn--end:hover:not(:disabled) { color: #120c04; filter: brightness(1.08); }
#action-bar .act-btn--wait {
  background: url('assets/ui/wooden_panels/bottom_wait.webp') center / 100% 100% no-repeat;
  border: none; border-radius: 0; color: #2a1206; text-shadow: 0 1px 0 rgba(255,220,150,0.4);
  min-width: 0; width: 76px; height: 46px; padding: 0; font-size: 0.64rem; font-weight: 700; margin-right: 6px;
}
#action-bar .act-btn--wait:hover:not(:disabled) { color: #160a02; filter: brightness(1.08); }
/* ranged stat column — sits in the gap left of the centre action buttons */
.ab-ranged {
  position: absolute;
  right: calc(50% + 94px);
  bottom: 0px;
  display: flex;
  flex-direction: column;
  gap: -2px;
  align-items: flex-end;
  z-index: 3;
  pointer-events: auto;
}
.ab-ranged-stat {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: rgba(0,0,0,0.65);
  padding: 1px 8px;
  cursor: pointer;
}
.ab-ranged-stat img { width: 15px; height: 15px; opacity: 1; pointer-events: none; }
.ab-ranged-stat b   { font-family: var(--font-data); font-weight: 700; font-size: 0.68rem; color: var(--text-primary); pointer-events: none; }
/* accuracy worsened by weather (Mist/Storm/Fog) — flag the number red so it reads as a penalty */
.ab-ranged-stat--worse b  { color: var(--blood-bright); }
.ab-ranged-stat--better b { color: #6fe3a0; }   /* a ranged stat BUFFED above base (e.g. Amblin's +1 Range / −1 RHit) — green, matching the melee boon tint (Si 2026-07-23) */
.ab-right {
  position: absolute; right: 0; bottom: 0; height: 64px; padding: 0 14px;
  display: flex; align-items: center; gap: 8px;
  pointer-events: auto; z-index: 3;
}
#ab-portrait {
  position: absolute;
  left: calc(50% + 96px);
  bottom: 7px;
  width: 54px; height: 54px;
  object-fit: contain;
  image-rendering: pixelated;
  z-index: 5;
  pointer-events: none;
  display: none;
  filter: drop-shadow(0 1px 3px rgba(0,0,0,0.7));
}
/* War-machine active-unit icon is the painted PORTRAIT (the machine), not a pixel token — scale it smoothly (Si 2026-07-21). */
#ab-portrait.ab-portrait--machine { image-rendering: auto; }
/* MOVE / SHOOT / CAST — structural */
#btn-move, #btn-shoot, #btn-cast {
  flex: 1; min-width: 0; height: 100%; padding: 0 2px;
  border: none; border-radius: 0; background: transparent;
  font-family: var(--font-body); font-size: 0.8rem; font-weight: 700;
  letter-spacing: 0.00em; text-transform: uppercase;
  display: flex; align-items: center; justify-content: center;
  position: relative; z-index: 2; cursor: pointer;
}
/* MOVE / CAST sit over the pale flanking planks */
#btn-move, #btn-cast {
  color: #2a1c0c;
  text-shadow: 0 1px 0 rgba(255,240,200,0.6);
}
#btn-move:hover:not(:disabled), #btn-cast:hover:not(:disabled) {
  color: #0e0704; background: transparent;
}
/* SHOOT sits over the dark front face */
#btn-shoot {
  color: var(--gold-bright);
  text-shadow: 0 1px 2px rgba(0,0,0,0.8);
}
#btn-shoot:hover:not(:disabled) {
  color: var(--text-primary); background: transparent;
}
/* CAST (magic) nudged 2px right */
#btn-cast { left: -5px; }
/* Disabled CTAs show strikethrough */
#btn-move:disabled, #btn-shoot:disabled, #btn-cast:disabled {
  text-decoration: line-through;
}
#btn-charge { display: none; }   /* charging is automatic (move into contact) */

/* degraded + charged unit markers */
.unit--degraded .unit-body { stroke: var(--fire-orange); stroke-width: 3; }
.unit--charged .unit-body {
  stroke: var(--blood-bright); stroke-width: 3;
  filter: drop-shadow(0 0 4px rgba(196,32,32,0.7));
}
.charge-flash {
  fill: none; stroke: var(--blood-bright); stroke-width: 3; pointer-events: none;
  animation: charge-flash 600ms ease-out forwards;
}
/* BROKEN — the mark carried by any unit that's routed and running (Si: "Its not always obvious which units are
   currently fleeing"). It wasn't: the ONLY cue was the red stroke below, which paints `.unit-body` — the DISC.
   Almost everything renders as a sprite now and has no disc, so for those units the cue drew nothing at all.
   The art does the talking here, so this THROBS rather than waves — it's a broken mark, not a flag, and a rotating
   flutter would read as cloth. Enough motion to catch the eye at 35% of a hex, no more. No CSS filter: those are
   banned on per-model art (they re-composite every frame and cost the zoom its smoothness). */
.unit-broken {
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: unit-broken-throb 1.05s ease-in-out infinite alternate;
}
@keyframes unit-broken-throb {
  from { transform: scale(0.9);  opacity: 0.78; }
  to   { transform: scale(1.12); opacity: 1; }
}
#board.cam-flying .unit-broken,
.spot-dim .unit-broken { animation: none; }   /* same rule as the sprites: nothing animates mid-flight */
@media (prefers-reduced-motion: reduce) { .unit-broken { animation: none; } }
.unit--fleeing .unit-body {
  stroke: var(--blood-bright); stroke-width: 3;
  animation: flee-flash 0.55s ease-in-out infinite alternate;
}
@keyframes flee-flash { from { stroke-opacity: 1; } to { stroke-opacity: 0.25; } }
@keyframes charge-flash {
  0%   { stroke-opacity: 0.9; r: 24px; }
  100% { stroke-opacity: 0;   r: 62px; }
}

/* fast combat (tray off): clash impact + floating casualty numbers */
.clash-flash {
  fill: none; stroke: #fff; stroke-width: 3; pointer-events: none;
  animation: clash-flash 460ms ease-out forwards;
}
@keyframes clash-flash {
  0%   { stroke-opacity: 0.95; stroke: #fff;            r: 10px; }
  55%  { stroke-opacity: 0.8;  stroke: var(--fire-orange); }
  100% { stroke-opacity: 0;    stroke: var(--blood-bright); r: 40px; }
}
.dmg-float {
  fill: var(--blood-bright); font-family: var(--font-data); font-weight: 700;
  font-size: 15px; text-anchor: middle; pointer-events: none;
  paint-order: stroke; stroke: rgba(0,0,0,0.85); stroke-width: 2.5px;
  transform-box: fill-box; transform-origin: center;
  animation: dmg-float 950ms ease-out forwards;
}
@keyframes dmg-float {
  0%   { opacity: 0; transform: translateY(6px); }
  25%  { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-22px); }
}

/* ---- Reduce Motion ---- */
.reduce-motion .dmg-float { animation: none; opacity: 0; }
.reduce-motion .phase-banner { transition: none; }
.reduce-motion .node-dot  { animation: none; }

/* phase announcement banner (centre flash) */
.phase-banner {
  position: absolute; top: 36%; left: 50%; z-index: 40; pointer-events: none;
  transform: translate(-50%, -50%) scale(0.9); opacity: 0;
  padding: 14px 44px; text-align: center;
  /* NO BOX (Si 2026-07-20: "horrible black background" — even the soft vignette still read as one). The title carries
     itself on a blue glow, exactly like the deploy / scout headings, so it floats over the board instead of a box. */
  background: none;
  transition: opacity 200ms ease, transform 200ms ease;
}
.phase-banner.show { opacity: 1; transform: translate(-50%, -50%) scale(1); }
/* "Battle Begins!", "Victory!" and the phase flashes now wear the same big BLUE bold face as the scout heading
   (Si: "use the nice big blue bold font instead"). Turn / morale / rout banners pin their own colour below. */
.phase-banner .pb-title {
  font-family: var(--font-display); font-weight: 900; font-size: 2.1rem;
  color: #8fc4ff; letter-spacing: 0.14em;
  text-shadow:
    0 0 60px rgba(70,140,235,0.7),
    0 0 20px rgba(70,140,235,0.55),
    0 2px 8px rgba(0,0,0,0.95);
}
.phase-banner .pb-sub {
  font-family: var(--font-body); color: #cfe4ff; font-weight: 600;
  font-size: 0.9rem; margin-top: 8px; letter-spacing: 0.05em;
  text-shadow: 0 1px 6px rgba(0,0,0,0.95), 0 0 16px rgba(70,140,235,0.4);
}
/* START-OF-TURN flash (Si): huge like the battle-start banner, very fast pop-in/out. The title's OWN keyframe drives
   the fade, so it doesn't rely on the .show transition; the container is just held visible and centred. */
.phase-banner--turn { opacity: 1; transition: none; transform: translate(-50%, -50%) scale(1); }
.phase-banner--turn .pb-title {
  color: var(--gold-bright);   /* the per-turn flash keeps its GOLD identity — don't inherit the new blue base */
  font-size: 2.7rem; font-weight: 900; letter-spacing: 0.2em;
  text-shadow: 0 0 60px rgba(180,140,60,0.7), 0 0 20px rgba(180,140,60,0.5), 0 2px 6px rgba(0,0,0,0.95);
  animation: turn-flash 950ms cubic-bezier(0.15, 1.5, 0.35, 1) forwards;
}
@keyframes turn-flash {
  0%   { opacity: 0; transform: scale(0.45); }
  14%  { opacity: 1; transform: scale(1.14); }
  28%  { opacity: 1; transform: scale(1); }
  70%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.06); }
}
@media (prefers-reduced-motion: reduce) { .phase-banner--turn .pb-title { animation-duration: 650ms; } }

/* ====  BATTLEFIELD COMBAT FLOATS (FoG2-style result text)  ==== */
.combat-float {
  font-family: var(--font-display); font-weight: 700; font-size: 13px;
  fill: var(--gold-bright); text-anchor: middle; pointer-events: none;
  paint-order: stroke fill; stroke: rgba(0,0,0,0.9); stroke-width: 3px;
  animation: combat-float-up 1.25s ease-out forwards;
}
.combat-float--kill       { fill: var(--blood-bright); font-size: 14px; }
.combat-float--routed     { fill: #ff2222; font-size: 16px; }
.combat-float--no-effect  { fill: #888; font-weight: 400; font-size: 10px; }
.combat-float--init       { fill: #c8a030; font-size: 14px; font-weight: 700; animation: init-appear 1.2s ease-in-out forwards; }
.combat-float--init-enemy { fill: #d04040; font-size: 14px; font-weight: 700; animation: init-appear 1.2s ease-in-out forwards; }
.combat-float--miss       { fill: #777; font-size: 10px; font-weight: 400; }
.combat-float--buff       { fill: #7ab0d4; font-size: 10px; }
.combat-float--morale-hit  { fill: #c060c0; font-size: 11px; font-weight: 400; }
.combat-float--morale-pass { fill: #7ecf7e; font-size: 11px; font-weight: 400; }
.combat-float--stun        { fill: #6fd6f0; font-size: 12px; font-weight: 700; }
/* End-Phase BEAT 1 — "Poison/Fire/Regeneration — …": names the roll about to resolve, before the outcome float.
   Soft gold + a gentle hold so it reads as "here is what's happening", distinct from the coloured RESULT floats. */
.combat-float--end-roll    { fill: #d8c48a; font-size: 11px; font-weight: 600; letter-spacing: 0.02em; }
/* END-PHASE DICE FLOAT (Si: SEE the poison/regen dice, like the magic dice). The dice inside are the log's .lc-die
   faces (faction-tinted, bright = made its roll), bumped a touch for the board; rises + holds via the shared cf-rise. */
.ep-dice-box { display: flex; align-items: center; justify-content: center; height: 100%; animation: cf-rise 1500ms ease-out forwards; }
.ep-dice {
  display: flex; gap: 4px; padding: 4px 8px;
  /* A LIGHT bone tray, not a dark one (Si: "the dice is very dim… show it nice and bright — it's what players are
     actively looking at"). Regen dice are mostly FAILS (dark faces); on a dark tray they vanished. On light, both
     the bright successes and the dark fails read cleanly. */
  background: rgba(240,232,212,0.97); border: 1px solid rgba(30,20,10,0.6); border-radius: 7px;
  box-shadow: 0 3px 10px rgba(0,0,0,0.55);
}
.ep-dice .lc-die { width: 20px; height: 20px; font-size: 12px; }
/* ICON FLOATS (COMBAT_ICONS brief) — HTML-in-foreignObject so a stat ICON can precede each number at equal weight. */
.cf-fo { pointer-events: none; overflow: visible; }
.cf-box {
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  height: 100%; gap: 2px; animation: cf-rise 1.05s ease-out forwards;
}
.cf-line {
  display: inline-flex; align-items: center; gap: 4px; white-space: nowrap;
  font-family: var(--font-display); font-weight: 700; font-size: 13px; line-height: 1;
  color: var(--gold-bright); text-shadow: 0 1px 3px rgba(0,0,0,0.95);
}
.cf-ic { width: 14px; height: 14px; object-fit: contain; flex: 0 0 auto;
  filter: drop-shadow(0 1px 2px rgba(0,0,0,0.9)); }
.cf-n  { font-variant-numeric: tabular-nums; }
.cf-box--miss  .cf-line { color: #8a8a8a; font-weight: 400; font-size: 11px; }
.cf-box--kill  .cf-line { color: var(--blood-bright); font-size: 14px; }
.cf-box--buff  .cf-line { color: #7ab0d4; }
.cf-box--shoot .cf-line { color: #ccd6e0; }
.cf-box--morale-hit  .cf-line { color: #c060c0; }
.cf-box--morale-pass .cf-line { color: #7ecf7e; }
/* Striker label — name over the INIT lightning-icon + value (Si: icon, not the word "Init"). Gold for the player,
   red-tinted for the enemy, matching the old combat-float--init/-enemy hues. */
.cf-box--init       .cf-line { color: #d8b24a; font-weight: 700; }
.cf-box--init-enemy .cf-line { color: #e08b6a; font-weight: 700; }
/* Holds at full opacity for most of the duration (12%–84%), like the original combat-float — the number is readable
   the whole time it's up, then a quick fade. Total time comes from the per-call duration (combat slow, ranged fast). */
@keyframes cf-rise {
  0%   { opacity: 0; transform: translateY(9px)   scale(0.92); }
  12%  { opacity: 1; transform: translateY(0)     scale(1); }
  84%  { opacity: 1; transform: translateY(-12px) scale(1); }
  100% { opacity: 0; transform: translateY(-22px) scale(1); }
}
/* CHARGE! shout — big wavy blue text that pops in and wobbles as it rises. Light variant is smaller/quicker. */
.combat-float--charge, .combat-float--charge-light {
  fill: #46a6ff; letter-spacing: 1px; stroke: rgba(4,22,54,0.9); stroke-width: 3.5px;
  transform-box: fill-box; transform-origin: center;
  animation: charge-shout 1100ms cubic-bezier(0.2, 1.5, 0.4, 1) forwards;
}
.combat-float--charge       { font-size: 23px; }
.combat-float--charge-light { font-size: 15px; animation-duration: 850ms; }
.combat-float--rearcharge   { fill: #6fb8ff; font-size: 10px; font-weight: 700; }
/* ABILITY SHOUT — a hero calling an activated ability (Hold the Line, Waaagh…). Same big wavy pop as the CHARGE!
   shout (Si: "shout it loud, like the Double Strike blue badge") but an arcane cyan with a glow, so a called
   ability reads distinctly from a charge. Reuses the charge-shout keyframe. */
.combat-float--ability {
  fill: #7fd4ff; font-size: 21px; letter-spacing: 1px;
  stroke: rgba(6,28,60,0.92); stroke-width: 3.5px; paint-order: stroke fill;
  filter: drop-shadow(0 0 6px rgba(90,190,255,0.85));
  transform-box: fill-box; transform-origin: center;
  animation: charge-shout 1200ms cubic-bezier(0.2, 1.5, 0.4, 1) forwards;
}
/* ABILITY BURST — a blue arcane pulse radiating from the hero when an activated ability fires (Si: "a sort of spell
   blue effect… letting the user know it happened"). The ring is drawn at its FULL radius and scaled up from a point,
   so it reads as a shockwave out to the aura's edge. */
.ability-burst {
  fill: rgba(90, 180, 255, 0.13);
  stroke: rgba(150, 214, 255, 0.9); stroke-width: 3;
  transform-box: fill-box; transform-origin: center; pointer-events: none;
  animation: ability-burst-pulse 850ms ease-out forwards;
}
@keyframes ability-burst-pulse {
  0%   { opacity: 0;    transform: scale(0.08); }
  22%  { opacity: 0.95; }
  100% { opacity: 0;    transform: scale(1.05); }
}
@media (prefers-reduced-motion: reduce) { .ability-burst { display: none; } }
@keyframes charge-shout {
  0%   { opacity: 0; transform: translateY(6px)   scale(0.35) rotate(-9deg); }
  18%  { opacity: 1; transform: translateY(0)     scale(1.18) rotate(5deg); }
  34%  { opacity: 1; transform: translateY(-3px)  scale(0.98) rotate(-4deg); }
  52%  { opacity: 1; transform: translateY(-8px)  scale(1.06) rotate(3deg); }
  72%  { opacity: 1; transform: translateY(-15px) scale(1)    rotate(-2deg); }
  100% { opacity: 0; transform: translateY(-27px) scale(0.92) rotate(0); }
}
/* Gold stars bursting out the back of a charge, flying to their per-star --tx/--ty offset while spinning + fading. */
.charge-star, .charge-star--light {
  font-size: 15px; fill: #ffe14a; pointer-events: none;
  paint-order: stroke fill; stroke: rgba(120,80,0,0.55); stroke-width: 1px;
  transform-box: fill-box; transform-origin: center;
  animation: charge-star-burst 950ms ease-out forwards;
}
.charge-star--light { font-size: 11px; animation-duration: 700ms; }
@keyframes charge-star-burst {
  0%   { opacity: 0; transform: translate(0,0) scale(0.2) rotate(0); }
  25%  { opacity: 1; transform: translate(calc(var(--tx) * 0.5), calc(var(--ty) * 0.5)) scale(1.15) rotate(calc(var(--rot) * 0.5)); }
  100% { opacity: 0; transform: translate(var(--tx), var(--ty)) scale(0.55) rotate(var(--rot)); }
}
@media (prefers-reduced-motion: reduce) { .charge-star, .charge-star--light { display: none; } }
@keyframes init-appear {
  0%   { opacity: 0; transform: translateY(0); }
  15%  { opacity: 1; transform: translateY(0); }
  85%  { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(0); }
}
@keyframes combat-float-up {
  0%   { opacity: 0;   transform: translateY(4px); }
  18%  { opacity: 1;   transform: translateY(0); }
  65%  { opacity: 1;   transform: translateY(-18px); }
  100% { opacity: 0;   transform: translateY(-30px); }
}

/* ---- COMBAT SPOTLIGHT ----
   A dark SVG <rect> is inserted between non-spotlight and spotlight units in
   renderUnits(), so all units stay at 100% opacity. Only the active fighters
   appear above the overlay; everything else is naturally dimmed behind it.
   The dark overlay dims non-combatants; the unit glow lifts active fighters above it.
   Hex borders then distinguish attacker from defender. */
g.unit.unit--spotlight {
  filter: brightness(1.2);
}

/* Attacker hex — bright gold pulsing stroke only; no fill (keeps units readable) */
polygon.hex--attacker {
  stroke: #f5d87c;
  stroke-width: 5;
  animation: attacker-pulse 1s ease-in-out infinite;
}
@keyframes attacker-pulse {
  0%, 100% { stroke: #f5d87c; }
  50%       { stroke: #ffffff; }
}

/* Defender hex — blood red stroke only */
polygon.hex--defender {
  stroke: #c42020;
  stroke-width: 5;
}

/* Combat rings sit inset at 91% of hex size — thinner stroke so adjacent rings read cleanly */
polygon.hex-combat-ring { stroke-width: 3.5; }

/* ---- COMBAT RESOLUTION CARD ---- */
.combat-res-card {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  z-index: 60;
  width: 90vw; max-width: 480px;
  max-height: 90vh;
  display: flex; flex-direction: column;
  background:
    linear-gradient(rgba(10,8,6,0.72), rgba(10,8,6,0.82)),
    url('assets/ui/web_assets/generic_background_dark_covers.webp') center / cover no-repeat;
  border: 1px solid var(--gold-mid);
  border-radius: 6px;
  font-family: var(--font-body);
  color: var(--text-primary);
  box-shadow: 0 0 50px rgba(0,0,0,0.92), 0 0 18px rgba(201,168,76,0.12);
  overflow: hidden;
}
.crc-title {
  background: rgba(0,0,0,0.35);
  border-bottom: 1px solid rgba(138,110,42,0.4);
  color: var(--gold-bright);
  font-family: var(--font-display);
  font-size: 0.62rem; letter-spacing: 0.22em; text-transform: uppercase;
  text-align: center; padding: 10px 0;
  flex-shrink: 0;
}
/* Two-column portrait layout — scrolls if content overflows */
.crc-columns {
  display: flex; flex: 1; overflow-y: auto; min-height: 0;
}
.crc-col {
  flex: 1; padding: 12px 12px 10px; min-width: 0;
}
.crc-col-header {
  font-size: 0.58rem; font-weight: 700; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--text-secondary); text-align: center; margin-bottom: 10px;
  padding-bottom: 5px; border-bottom: 1px solid rgba(138,110,42,0.2);
}
.crc-col-sep {
  width: 1px; background: rgba(138,110,42,0.3); flex-shrink: 0; margin: 10px 0;
}
.crc-unit-name {
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--gold-bright); margin-bottom: 4px; margin-top: 8px;
}
.crc-unit-name:first-child { margin-top: 0; }
.crc-factor-row {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 0.65rem; color: var(--text-secondary); padding: 1px 0 1px 6px;
}
.crc-factor-val {
  font-family: var(--font-data); color: var(--text-primary);
  font-weight: 600; min-width: 18px; text-align: right;
}
.crc-divider { height: 1px; background: rgba(255,255,255,0.1); margin: 8px 0 4px; }
.crc-total-row {
  display: flex; justify-content: space-between; align-items: baseline;
  font-size: 0.72rem; font-weight: 700; color: var(--text-primary); padding: 2px 0;
  text-transform: uppercase; letter-spacing: 0.06em;
}
.crc-total-val { font-family: var(--font-data); font-size: 1rem; }
.crc-total-val--wins  { color: #5dba6a; }
.crc-total-val--loses { color: var(--blood-bright); }
.crc-total-val--tie   { color: var(--text-secondary); }
/* Sticky bottom: outcome + OK always visible */
.crc-bottom {
  flex-shrink: 0;
  border-top: 1px solid rgba(138,110,42,0.35);
  background: rgba(0,0,0,0.2);
}
.crc-outcome {
  padding: 8px 16px 4px;
  font-size: 0.78rem; text-align: center; color: var(--text-secondary);
}
.crc-outcome strong { color: var(--gold-bright); }
.crc-footer { padding: 8px 20px 14px; display: flex; justify-content: center; }
.crc-ok-btn {
  background: url('assets/ui/wooden_panels/bottom_right_end_activation.webp') center / 100% 100% no-repeat;
  border: none; border-radius: 0;
  color: #2a1c0c; text-shadow: 0 1px 0 rgba(255,240,200,0.4);
  font-family: var(--font-body); font-size: 1rem; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  width: 120px; height: 46px; padding: 0;
  cursor: pointer; transition: filter 0.15s;
}
.crc-ok-btn:hover { filter: brightness(1.08); }

/* ---- VICTORY RESOLUTION SCREEN ---- */
.victory-card {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  z-index: 62; width: 94vw; max-width: 620px; max-height: 90vh;
  display: flex; flex-direction: column;
  background:
    linear-gradient(rgba(10,8,6,0.74), rgba(10,8,6,0.86)),
    url('assets/ui/web_assets/generic_background_dark_covers.webp') center / cover no-repeat;
  border: 1px solid var(--gold-mid); border-radius: 6px;
  box-shadow: 0 0 50px rgba(0,0,0,0.92), 0 0 18px rgba(201,168,76,0.14);
  font-family: var(--font-body); color: var(--text-primary); overflow: hidden;
}
.vc-title {
  font-family: var(--font-display); color: var(--gold-bright);
  font-size: 2.15rem; letter-spacing: 0.24em; text-transform: uppercase;
  text-align: center; padding: 18px 0 4px; flex-shrink: 0;
}
.vc-treasury {
  text-align: center; flex-shrink: 0; padding: 2px 0 0;
}
.vc-treasury-num {
  font-family: var(--font-data); font-size: 2rem; font-weight: 700; color: var(--gold-bright);
  text-shadow: 0 0 16px rgba(201,168,76,0.35); font-variant-numeric: tabular-nums;
}
.vc-treasury-lbl { font-size: 0.9rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-secondary); }
.vc-earned {
  text-align: center; font-size: 0.6rem; letter-spacing: 0.1em; text-transform: uppercase;
  color: #5dba6a; padding: 2px 0 12px; flex-shrink: 0; min-height: 0.9rem;
  border-bottom: 1px solid rgba(138,110,42,0.3);
}
.vc-c-xp { font-variant-numeric: tabular-nums; }
/* Level-up flash — a quick gold pulse; the commander's is bigger (future army-bonus weight). */
@keyframes vc-levelflash { 0% { color: #fff; transform: scale(1.5); text-shadow: 0 0 14px var(--gold-bright); } 100% { color: var(--gold-bright); transform: scale(1); text-shadow: none; } }
@keyframes vc-levelflash-cmd { 0% { color: #fff; transform: scale(1.9); text-shadow: 0 0 22px var(--gold-bright); } 60% { color: var(--gold-bright); } 100% { color: var(--gold-bright); transform: scale(1); text-shadow: none; } }
.vc-levelflash { display: inline-block; animation: vc-levelflash 0.6s ease-out; color: var(--gold-bright); font-weight: 700; }
.vc-levelflash--cmd { display: inline-block; animation: vc-levelflash-cmd 0.85s ease-out; color: var(--gold-bright); font-weight: 700; }
.vc-rout {
  flex-shrink: 0; display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 8px 0; border-bottom: 1px solid rgba(138,110,42,0.3);
}
.vc-rout:empty { display: none; }
.vc-rout-lbl { font-size: 0.62rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted); }
.vc-rout-toggle .vc-opt { font-size: 0.62rem; padding: 4px 10px; }
/* ── BATTLEFIELD RESURRECTION (Si: "healing power. Genuine ressurection"). A pool of free VETERANS the player
   allocates across his units via the ＋ in each Strength cell. Reads as a blessing, not a transaction: a pale
   restorative green, distinct from the gold every *bought* thing on this card uses. The bar is silent unless the
   battle actually earned a pool. */
.vc-rez {
  flex-shrink: 0; display: flex; align-items: baseline; justify-content: center; gap: 8px;
  padding: 8px 0; border-bottom: 1px solid rgba(138,110,42,0.3);
}
.vc-rez:empty { display: none; }
.vc-rez-lbl  { font-size: 0.62rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted); }
.vc-rez-num  { font-family: var(--font-data); font-size: 1.05rem; color: #8fdca0; }
.vc-rez-num--done { color: var(--text-muted); }
.vc-rez-hint { font-size: 0.62rem; color: var(--text-muted); }
/* Field-medic banner (HEALER post-battle restore) — sits above the table, reads as a healing note. */
.vc-medic {
  flex-shrink: 0; text-align: center; padding: 7px 10px;
  font-size: 0.66rem; letter-spacing: 0.04em; color: #8fdca0;
  background: rgba(143,220,160,0.08); border-bottom: 1px solid rgba(138,110,42,0.3);
}
/* AT FIRST LIGHT — the gamble reward block (gold ± a joining unit ± a promotion). Sits with the other prize lines above
   the unit table. A warm amber card; the JACKPOT gets a brighter gold glow to mark the all-three payout. */
.vc-gamble {
  flex-shrink: 0; margin: 10px 12px; padding: 9px 12px;
  border: 1px solid rgba(214,168,74,0.4); border-left: 3px solid #e0a94a;
  border-radius: 5px; background: rgba(48,36,16,0.5);
}
.vc-gamble--jackpot {
  border-color: rgba(255,214,90,0.7); border-left-color: #ffd65a;
  background: rgba(70,52,14,0.6); box-shadow: 0 0 14px rgba(255,214,90,0.25);
}
.vc-gamble-tag {
  font-size: 0.62rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: #f0c869; margin-bottom: 5px;
}
.vc-gamble--jackpot .vc-gamble-tag { color: #ffe27a; }
.vc-gamble-line { font-size: 0.74rem; color: #ece0c4; line-height: 1.5; }
.vc-gamble-line b { color: #ffe9b8; }
.vc-gamble-line--none { color: #b6a985; font-style: italic; }
/* The ＋/− live inside the Strength cell, so they must stay tiny and not shove the number off its column. */
.vc-rez-step { display: inline-flex; gap: 2px; margin-left: 5px; vertical-align: middle; }
.vc-rez-btn {
  font-family: var(--font-data); font-size: 0.72rem; line-height: 1;
  width: 15px; height: 15px; padding: 0; cursor: pointer;
  background: rgba(143,220,160,0.10); color: #8fdca0;
  border: 1px solid rgba(143,220,160,0.45); border-radius: 2px;
  transition: background 0.12s, color 0.12s;
}
.vc-rez-btn:hover  { background: rgba(143,220,160,0.28); color: #d6ffe2; }
.vc-rez-btn:active { background: #8fdca0; color: #16301c; }
.vc-str-n--rez { color: #8fdca0; }   /* this unit has men coming back — the number itself says so */
/* RESURRECTION PICK (Si 2026-07-23) — the whole row is a big tap target, replacing the fiddly +/− stepper. An
   eligible row shows a "＋ raise" prompt and lights on hover; the chosen one wears a green rail + tinted fill. */
.vc-row--rez-pick { cursor: pointer; }
.vc-row--rez-pick:hover { background: rgba(143,220,160,0.10); }
.vc-row--rez-on { background: rgba(143,220,160,0.16); box-shadow: inset 3px 0 0 #8fdca0; }
.vc-rez-cta { margin-left: 6px; font-size: 0.56rem; letter-spacing: 0.04em; text-transform: uppercase; color: #8fdca0; opacity: 0.9; white-space: nowrap; }
.vc-rez-got { margin-left: 6px; font-family: var(--font-data); font-size: 0.82rem; color: #8fdca0; }
/* ── SECONDARY OBJECTIVE VERDICT (Si: "a tiny bit on if you did the secondary objective and what your reward
   for it was (or not)"). Sits ABOVE the unit table — you're told whether you did the thing before you're told
   what everyone earned. The reward is shown either way: knowing what you FORFEITED is what teaches you to go
   for it next time. */
.vc-objective {
  flex-shrink: 0; display: flex; align-items: center; gap: 14px;
  margin: 10px 12px; padding: 9px 12px;
  border: 1px solid rgba(138,110,42,0.35); border-left: 3px solid var(--gold-mid);
  border-radius: 5px; background: rgba(40,32,18,0.4);
}
.vc-objective--done    { border-left-color: #5dba6a; background: rgba(30,54,32,0.34); }
.vc-objective--missed  { border-left-color: var(--blood-bright); background: rgba(54,26,24,0.3); }
/* A SPLIT objective only partly met — amber, between win and loss: you banked part of the prize (Si). */
.vc-objective--partial { border-left-color: #d9a441; background: rgba(58,44,18,0.34); }
.vc-obj-left  { flex: 1 1 auto; min-width: 0; }
.vc-obj-right { flex: 0 0 auto; text-align: right; }
.vc-obj-tag {
  font-size: 0.54rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 2px;
}
.vc-obj-name {
  font-family: var(--font-display); font-size: 0.78rem; color: var(--gold-bright);
  line-height: 1.3; overflow-wrap: anywhere;
}
.vc-obj-task { font-size: 0.64rem; color: var(--text-secondary); line-height: 1.4; overflow-wrap: anywhere; }
.vc-obj-verdict { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.05em; white-space: nowrap; }
.vc-objective--done    .vc-obj-verdict { color: #5dba6a; }
.vc-objective--missed  .vc-obj-verdict { color: var(--blood-bright); }
.vc-objective--partial .vc-obj-verdict { color: #e0b048; }
.vc-obj-prize--partial { color: #e0b048; }
.vc-obj-prize {
  display: block; margin-top: 3px; white-space: nowrap;
  font-family: var(--font-data); font-size: 0.64rem; color: var(--gold-bright);
}
.vc-obj-prize--lost { color: var(--text-muted); text-decoration: line-through; }

/* The whole reward group scrolls together (Si) — reward + bonus + the per-unit heal/gold/xp rows in ONE scroller,
   so a busy screen never squeezes the rows into a 2-line window. Only the Victory!/gold header + Continue stay put. */
.vc-scroll { flex: 1; overflow-y: auto; min-height: 0; }
.vc-table { padding: 6px 12px 10px; }
.vc-row {
  display: grid; grid-template-columns: 1.7fr 0.6fr 0.6fr 0.7fr 0.6fr 0.8fr 1.5fr;
  align-items: center; gap: 4px; padding: 6px 4px;
  border-bottom: 1px solid rgba(255,255,255,0.055); font-size: 0.66rem;
}
.vc-head {
  font-size: 0.54rem; letter-spacing: 0.1em; text-transform: uppercase;
  color: #ffffff; border-bottom: 1px solid rgba(138,110,42,0.3);
}
.vc-row--cmd  { background: rgba(201,168,76,0.06); }
.vc-row--lost { opacity: 0.5; }
.vc-c-name { font-weight: 700; letter-spacing: 0.03em; color: var(--text-primary); }
.vc-row--cmd .vc-c-name { color: var(--gold-bright); }
.vc-c-num { font-family: var(--font-data); text-align: center; color: var(--text-secondary); }
.vc-c-reward { display: flex; justify-content: flex-end; }
.vc-lvlup { color: #5dba6a; font-size: 0.82em; }
.vc-fell, .vc-lost-tag { color: #ffffff; font-size: 0.58rem; text-transform: uppercase; letter-spacing: 0.05em; }
/* A rescued patrol that walked out with you — a GAIN, so it reads green rather than as another casualty line (Si). */
.vc-join-tag { color: #7ddc7d; font-size: 0.58rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700; }
.vc-row--joined { background: rgba(30, 54, 32, 0.28); }
.vc-toggle { display: inline-flex; border: 1px solid var(--gold-dim); border-radius: 4px; overflow: hidden; }
.vc-opt {
  background: transparent; border: none; color: #ffffff;
  font-family: var(--font-data); font-size: 0.6rem; padding: 6px 13px; cursor: pointer; transition: background 0.12s;
  /* Bigger, UNIFORM tap targets (Si 2026-07-20: "make all the hit detections the bigger sized boxes — fiddly on
     mobile"). min-height matches the 2-line Heal button so a gold/xp-only row is just as tall + tappable as a Heal row. */
  min-height: 40px; display: inline-flex; align-items: center; justify-content: center; text-align: center; line-height: 1.15;
}
.vc-opt.on { background: var(--gold-mid); color: #201400; font-weight: 700; }
/* The Heal pick (Si) — a free patch-up in place of gold/xp. Green when chosen, so it reads as "restore", distinct
   from the gold pick, and the three options divide cleanly. */
.vc-opt--heal.on { background: #3f9c2c; color: #f4ffe9; }
.vc-toggle--heal .vc-opt:not(:last-child) { border-right: 1px solid rgba(138,110,42,0.3); }
.vc-footer {
  flex-shrink: 0; padding: 10px 0 16px; display: flex; justify-content: center;
  border-top: 1px solid rgba(138,110,42,0.35); background: rgba(0,0,0,0.2);
}
.vc-continue {
  background: url('assets/ui/wooden_panels/bottom_right_end_activation.webp') center / 100% 100% no-repeat;
  border: none; color: #2a1c0c; text-shadow: 0 1px 0 rgba(255,240,200,0.4);
  font-family: var(--font-body); font-size: 1rem; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  width: 150px; height: 48px; cursor: pointer; transition: filter 0.15s;
}
/* ═══ THE STORY PRIZE — ITS OWN ROW, WITH FANFARE ══════════════════════════════════════════════════════════════
   Si: "the magic item or regiment joining is a small bonus rather than the big prize. They actually deserve their
   own block… their own entire row with their nice artwork… It should be fanfare that you are about to get this
   mega prize!"
   Art left, words right. The frame is a fixed square because every source is 800×800 — character portraits today,
   Si's magic-item art on the same spec — so one frame serves both and item art needs no layout work.
   Used on the reveal card (promise) and the victory card (won / lost), so the two can never disagree. */
/* FULL-BLEED ART (Si: "Can we have the image with no padding, try maximise that space right there, show off the
   lovely artwork"). The tile is flush to the block's own border — no gap, no inner frame, no rounding of its own
   beyond the row's — so the painting IS the left edge of the card rather than a stamp sitting on it. It fills the
   row's full height via align-items: stretch, and the row's height is set by the text beside it. */
.vc-prize {
  flex-shrink: 0; display: flex; align-items: stretch; gap: 0;
  margin: 10px 12px; padding: 0;
  border: 1px solid var(--gold-mid); border-radius: 6px;
  background:
    linear-gradient(90deg, rgba(201,168,76,0.16), rgba(201,168,76,0.03) 62%),
    rgba(12,9,5,0.5);
  position: relative; overflow: hidden;
}
.vp-frame {
  flex: 0 0 auto; width: 104px; align-self: stretch; overflow: hidden;
  background: rgba(0,0,0,0.45);
  /* Only the ROW's rounding, and only on the corners it actually touches. */
  border-radius: 5px 0 0 5px;
  /* The join with the text, and a soft inner lip so an 800×800 painting doesn't end on a hard line. */
  border-right: 1px solid rgba(201,168,76,0.55);
  box-shadow: inset -14px 0 18px -14px rgba(0,0,0,0.85);
}
/* `cover` on a square source in a taller-than-wide box crops the sides; these are centre-composed, so centre it. */
.vp-art { width: 100%; height: 100%; object-fit: cover; object-position: center; display: block; }
/* No art yet (every magic item, until Si draws them) — a gilded sigil, not a broken image. */
.vp-art--missing {
  width: 100%; height: 100%;
  background:
    radial-gradient(circle at 50% 42%, rgba(201,168,76,0.42), rgba(201,168,76,0.06) 68%),
    repeating-linear-gradient(45deg, rgba(201,168,76,0.07) 0 5px, transparent 5px 10px);
}
.vp-art--missing::after {
  content: '✦'; display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%; font-size: 1.7rem; color: var(--gold-bright); opacity: 0.75;
}
/* The words carry the padding now that the art doesn't. min-height keeps the art tile a generous panel even when
   the text is short — otherwise a two-word prize would shrink the painting to a strip. */
.vp-body { flex: 1 1 auto; min-width: 0; padding: 11px 14px; min-height: 78px;
  display: flex; flex-direction: column; justify-content: center; }
.vp-tag {
  font-size: 0.56rem; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--gold-bright); opacity: 0.9;
}
.vp-name {
  font-family: var(--font-display); font-weight: 700; color: var(--gold-bright);
  font-size: 1.12rem; line-height: 1.2; margin: 2px 0 3px; overflow-wrap: anywhere;
}
.vp-desc { font-size: 0.68rem; color: var(--text-secondary); line-height: 1.4; overflow-wrap: anywhere; }
/* WON — the fanfare. A gold sweep crosses the row once, then it just sits there being gold.
   Once, not looping: a permanent shimmer is wallpaper, and this has to land as an EVENT. */
.vc-prize--won { animation: vpArrive 620ms cubic-bezier(0.2, 0.9, 0.25, 1) both; box-shadow: 0 0 18px rgba(201,168,76,0.3); }
.vc-prize--won .vp-frame { border-right-color: var(--gold-bright); }
.vc-prize--won::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(105deg, transparent 38%, rgba(255,236,170,0.5) 50%, transparent 62%);
  transform: translateX(-120%);
  animation: vpSweep 900ms ease-out 340ms both;
}
@keyframes vpArrive {
  from { opacity: 0; transform: scale(0.94); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes vpSweep { to { transform: translateX(120%); } }
/* LOST — you are shown exactly what slipped away. That is the whole point: a miss you can't see costs nothing. */
.vc-prize--lost { border-color: rgba(138,110,42,0.4); background: rgba(12,9,5,0.5); opacity: 0.62; }
.vc-prize--lost .vp-name { color: var(--text-muted); text-decoration: line-through; }
.vc-prize--lost .vp-art  { filter: grayscale(1) brightness(0.6); }
.vc-prize--lost .vp-tag  { color: var(--text-muted); }
/* PROMISE (reveal card) — present, not shouting. It hasn't happened yet. */
.vc-prize--promise { margin: 0 0 12px; }
@media (prefers-reduced-motion: reduce) {
  .vc-prize--won { animation: none; }
  .vc-prize--won::after { display: none; }
}

.vc-continue:hover { filter: brightness(1.08); }
/* CONTINUE IS LOCKED UNTIL EVERY CHOICE IS MADE (Si: "leave the continue button greyed until all options are
   selected… All need to be filled in before continue lights up"). The card asks real questions — XP or gold per
   unit, buy a recruit, spend a resurrection, mercy or slaughter — and it used to let you walk past all of them and
   silently take the defaults. A locked button turns "I didn't notice" into "I have one thing left to do".
   Not `disabled`: a disabled button eats its own hover and tooltip, and this one has to be able to SAY why. */
.vc-continue--locked {
  filter: grayscale(0.85) brightness(0.62);
  cursor: not-allowed; color: rgba(42,28,12,0.6); text-shadow: none;
}
.vc-continue--locked:hover { filter: grayscale(0.85) brightness(0.62); }
/* The one thing left to do, said plainly under the button. */
.vc-footer { flex-direction: column; align-items: center; gap: 5px; }
.vc-lock-hint {
  font-size: 0.62rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--gold-bright); opacity: 0.85; text-align: center; padding: 0 10px;
}
.vc-lock-hint:empty { display: none; }
/* A nudge when you press a locked button — the answer is UP THERE, in the rows. */
@keyframes vcNudge { 0%,100% { transform: translateX(0); } 25% { transform: translateX(-4px); } 75% { transform: translateX(4px); } }
.vc-nudge { animation: vcNudge 260ms ease-in-out; }
@media (prefers-reduced-motion: reduce) { .vc-nudge { animation: none; } }

/* ---- SHORT LANDSCAPE (a phone on its side: 780×360 and friends) ----------------------------------------------
   The card is a flex column with a scrolling REWARD GROUP (`.vc-scroll`) in the middle, which is right — but every
   OTHER part (title, treasury, Continue footer) was a fixed size tuned for a desktop, and on a 360px-tall screen the
   title alone (2.15rem + 18px padding) plus the treasury ate the entire 90vh. The scroller collapsed to nothing and
   the footer was clipped by the card's own `overflow: hidden`, so Continue sat on top of the rows.
   Nothing here restructures the layout; it just makes the chrome give way so the SCROLLING part can do its job. */
@media (max-height: 560px) {
  .victory-card, .defeat-card { max-height: 96vh; width: 96vw; }
  .vc-title      { font-size: 1.25rem; letter-spacing: 0.16em; padding: 8px 0 2px; }
  .vc-treasury   { padding: 0; }
  .vc-treasury-num { font-size: 1.6rem; }
  .vc-treasury-lbl { font-size: 0.7rem; }
  .vc-earned     { padding: 1px 0 5px; font-size: 0.66rem; min-height: 0; }
  .vc-rout, .vc-rez { padding: 4px 0; }
  .vc-objective  { margin: 4px 8px; padding: 5px 9px; gap: 8px; }
  .vc-obj-task   { display: none; }        /* the task was stated all battle; the VERDICT is what this screen adds */
  .vc-obj-name   { font-size: 0.76rem; }
  /* The prize keeps its art — it is the one thing on this card worth the pixels. Still full-bleed, just narrower. */
  .vc-prize      { margin: 4px 8px; }
  .vp-frame      { width: 68px; }
  .vp-body       { padding: 7px 10px; min-height: 54px; }
  .vp-name       { font-size: 0.86rem; margin: 1px 0 2px; }
  .vp-desc       { font-size: 0.6rem; line-height: 1.3; }
  .vp-tag        { font-size: 0.5rem; }
  .vc-table      { padding: 2px 8px 4px; }
  .vc-footer     { padding: 5px 0 7px; }
  .vc-continue   { width: 124px; height: 36px; font-size: 0.8rem; }
  .overlay-close { transform: scale(0.8); transform-origin: top right; }
}
/* Very short (≤ 420px): the last things to go are the ones the scrolling table can show instead. */
@media (max-height: 420px) {
  .vc-title    { font-size: 1.05rem; padding: 5px 0 1px; }
  .vc-treasury-num { font-size: 1.3rem; }
  .vc-rez-hint { display: none; }          /* the number carries it; the sentence is a desktop luxury */
  .vc-objective { margin: 3px 8px; padding: 4px 8px; }
}

/* ---- DEFEAT SCREEN (detail variant of the resolution card) ---- */
.defeat-card { border-color: var(--blood-bright); box-shadow: 0 0 50px rgba(0,0,0,0.94), 0 0 22px rgba(139,26,26,0.34); }
.df-title { color: var(--blood-bright); }
.df-flavour {
  text-align: center; font-size: 0.74rem; line-height: 1.5; color: var(--text-secondary);
  padding: 4px 26px 14px; flex-shrink: 0; border-bottom: 1px solid rgba(138,110,42,0.3);
}
.df-stats { flex: 1; overflow-y: auto; min-height: 0; padding: 12px 22px; display: flex; flex-direction: column; gap: 2px; }
.df-stat {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 7px 6px; border-bottom: 1px solid rgba(255,255,255,0.05); font-size: 0.72rem;
}
.df-stat-lbl { letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); }
.df-stat-val { font-family: var(--font-data); font-size: 0.98rem; color: var(--text-primary); font-variant-numeric: tabular-nums; }
.df-epitaph {
  text-align: center; font-size: 0.62rem; font-style: italic; color: var(--blood-bright);
  opacity: 0.85; padding: 8px 20px 4px; flex-shrink: 0;
}

/* ---- MORALE TEST PANEL ---- */
.morale-test-panel {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  z-index: 61;
  width: min(320px, 86vw);
  background:
    linear-gradient(rgba(10,8,6,0.75), rgba(10,8,6,0.85)),
    url('assets/ui/web_assets/generic_background_dark_covers.webp') center / cover no-repeat;
  border: 1px solid var(--blood-bright);
  border-radius: 6px;
  font-family: var(--font-body);
  color: var(--text-primary);
  box-shadow: 0 0 50px rgba(0,0,0,0.92), 0 0 20px rgba(139,26,26,0.3);
  overflow: hidden; text-align: center;
}
.mtp-header {
  background: var(--blood-red);
  color: #ffc8c8;
  font-family: var(--font-display);
  font-size: 0.65rem; letter-spacing: 0.2em; text-transform: uppercase;
  padding: 8px 0;
}
.mtp-body { padding: 16px 18px; }
.mtp-unit-name {
  font-size: 1rem; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-primary);
}
.mtp-context { font-size: 0.68rem; color: var(--text-muted); margin-top: 3px; font-style: italic; }
.mtp-morale-val { font-size: 0.72rem; color: var(--text-secondary); margin-top: 2px; }
.mtp-morale-val strong { color: var(--gold-bright); }
.mtp-dock { color: #e08a7a; font-weight: 700; }   /* the −N deduction that made the target lower than the card value */
.mtp-need { margin: 11px 0 0; font-size: 0.8rem; color: var(--text-secondary); }
.mtp-need strong { color: var(--gold-bright); font-size: 1.15rem; display: block; margin-top: 3px; }
.mtp-die-wrap {
  margin: 14px auto 4px;
  width: 62px; height: 62px;
  background: rgba(255,255,255,0.04);
  border: 2px solid var(--gold-dim);
  border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.9rem; font-family: var(--font-data);
  color: var(--text-muted);
  transition: border-color 0.25s, color 0.25s;
}
.mtp-die-wrap--rolling { border-color: var(--gold-mid); color: var(--gold-bright); }
.mtp-die-wrap--pass    { border-color: #5dba6a;         color: #5dba6a; }
.mtp-die-wrap--fail    { border-color: var(--blood-bright); color: #ff7070; }
/* 2d10 shown as TWO dice + total (Si: one die of the sum looked like a d20). The row lays the dice out inline; the
   two d10s are a touch smaller and the total is the emphasised result. HEX-SHAPED, not square — "a Hex is a D10"
   (Si): clip-path makes each die a pointy-top hexagon, so the shape itself signals the die type (a square read as
   a d6). No border (clip-path would shear it); the STONE FILL + state tint + glow carry the look instead. */
.mtp-dice-row { display: flex; align-items: center; justify-content: center; gap: 9px; margin: 16px auto 6px; }
.mtp-dice-row .mtp-die-wrap { margin: 0; }
.mtp-die-wrap--d10, .mtp-die-total {
  border: none; border-radius: 0; background: linear-gradient(158deg, rgba(74,64,42,0.92), rgba(40,34,22,0.95));
  color: #d8c9a0;
  clip-path: polygon(50% 0%, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%);
}
.mtp-die-wrap--d10 { width: 52px; height: 56px; font-size: 1.4rem; }
.mtp-die-total     { width: 64px; height: 70px; font-size: 1.85rem; }
/* state tints for the hex dice (the shared --rolling/pass/fail set a border colour that the clip-path hides, so
   re-express each as a fill + glow that follows the hex silhouette via drop-shadow) */
.mtp-die-wrap--d10.mtp-die-wrap--rolling, .mtp-die-total.mtp-die-wrap--rolling {
  background: linear-gradient(158deg, #6b5722, #3a2f10); color: var(--gold-bright);
  filter: drop-shadow(0 0 6px rgba(245,216,124,0.55)); }
.mtp-die-wrap--d10.mtp-die-wrap--pass, .mtp-die-total.mtp-die-wrap--pass {
  background: linear-gradient(158deg, #2d5a34, #16301c); color: #c6ffcf;
  filter: drop-shadow(0 0 7px rgba(93,186,106,0.6)); }
.mtp-die-wrap--d10.mtp-die-wrap--fail, .mtp-die-total.mtp-die-wrap--fail {
  background: linear-gradient(158deg, #642424, #351212); color: #ffcccc;
  filter: drop-shadow(0 0 7px rgba(200,60,60,0.6)); }
.mtp-die-op { font-family: var(--font-display); font-size: 1.3rem; color: var(--text-secondary); font-weight: 700; }
.mtp-result-label { font-size: 1rem; font-weight: 700; letter-spacing: 0.08em; margin-top: 8px; }
.mtp-result-label--pass { color: #5dba6a; }
.mtp-result-label--fail { color: var(--blood-bright); }
.mtp-result-sub { font-size: 0.68rem; color: var(--text-secondary); margin-top: 2px; }
.mtp-footer { padding: 10px 14px 16px; border-top: 1px solid rgba(139,26,26,0.3); display: flex; justify-content: center; }
.mtp-btn {
  font-family: var(--font-body); font-size: 0.7rem; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; cursor: pointer; transition: filter 0.15s;
}
.mtp-roll-btn {
  background: url('assets/ui/wooden_panels/top_left.webp') center / 100% 100% no-repeat;
  border: none; border-radius: 0; color: #ffffff;
  text-shadow: 0 1px 0 rgba(255,240,200,0.4);
  width: 120px; height: 46px; padding: 0;
}
.mtp-roll-btn:hover { filter: brightness(1.08); }
.mtp-cont-btn {
  background: url('assets/ui/wooden_panels/bottom_right_end_activation.webp') center / 100% 100% no-repeat;
  border: none; border-radius: 0; color: #2a1c0c;
  text-shadow: 0 1px 0 rgba(255,240,200,0.4);
  width: 120px; height: 46px; padding: 0;
}
.mtp-cont-btn:hover { filter: brightness(1.08); }

/* Blood = a PERMANENT battlefield record (Si). Fades IN then HOLDS forever (no `forwards` fade-to-0).
   The base opacity IS the resting value, so it survives reduce-motion too.

   IT WAS DRAWN AT 0.5 — HALF TRANSPARENT. Which is why Si kept asking for "more blood" and never got it: red at
   50% over green grass renders PINK, so every extra splatter was another thing he could only half-see. The dial
   he needed was never the count or the colour of the art; it was this. One variable changed at a time so the
   effect is actually attributable. */
.blood-splatter {
  pointer-events: none;
  opacity: 0.9;
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: blood-appear 0.45s ease-out;
}
@keyframes blood-appear {
  from { opacity: 0;   transform: scale(0.55); }
  to   { opacity: 0.9; transform: scale(1); }
}
.dead-body-marker { pointer-events: none; opacity: 0.5; }   /* legacy — blood is used everywhere now */
/* THE FALLEN — a real body, on the same permanent layer as the blood, under the living (Si: "start using these
   here and there along with the awesome blood splatters").
   OPACITY ONLY — deliberately. `blood-appear` above animates `transform: scale()`, and a CSS transform OVERRIDES
   an element's SVG `transform` ATTRIBUTE: borrow that animation and every corpse snaps to 0° and lies in the same
   direction, which is the one thing that would make a field of dead look fake. The rotate lives on the attribute
   (placeBody), so nothing here may touch transform. Slightly under full opacity so the blood still reads through
   and the ground doesn't turn into a sticker sheet. No filter — arch rule on per-model art. */
.dead-body { pointer-events: none; opacity: 0.88; animation: body-settle 0.5s ease-out; }
@keyframes body-settle { from { opacity: 0; } to { opacity: 0.88; } }

/* ---- ARROW VOLLEY ---- */
.arrow-volley {
  pointer-events: none;
  opacity: 0;
  animation: arrow-volley-fade 2.5s ease-out forwards;   /* 31s→2.5s: arrows thunk in, hold, and clear — the old lifetime littered the field for half a minute (Si 2026-07-23 "floating on the battlefield after the shot") */
}
@keyframes arrow-volley-fade {
  0%   { opacity: 0;    transform: scale(0.85); }
  4%   { opacity: 0.85; transform: scale(1); }
  75%  { opacity: 0.75; }
  95%  { opacity: 0.4; }
  100% { opacity: 0; }
}

/* ---- WAR WAGON SMOKE TRAIL ---- */
.wagon-smoke {
  pointer-events: none;
  opacity: 0;
  animation: wagon-smoke-rise 4.5s ease-out forwards;
  transform-box: fill-box;
  transform-origin: center;
}
@keyframes wagon-smoke-rise {
  0%   { opacity: 0;    transform: scale(0.55) translateY(0px); }
  12%  { opacity: 0.72; transform: scale(1.0)  translateY(-6px); }
  50%  { opacity: 0.5;  transform: scale(1.25) translateY(-18px); }
  100% { opacity: 0;    transform: scale(1.5)  translateY(-36px); }
}

/* ---- MORALE DRAMA ---- */
.pb-title--morale-warn  { color: #f0c040; font-size: 1.6em; letter-spacing: 0.12em; }
.pb-title--morale-hold  { color: #7eff9a; font-size: 1.5em; }
.pb-title--morale-rout  { color: #ff3030; font-size: 2.2em; letter-spacing: 0.15em;
                           text-shadow: 0 0 20px rgba(255,40,40,0.9); animation: rout-pulse 0.25s ease-in-out 3 alternate; }
@keyframes rout-pulse {
  from { transform: scale(1); }
  to   { transform: scale(1.08); letter-spacing: 0.25em; }
}
.phase-banner--rout { border-color: #ff3030 !important; box-shadow: 0 0 30px rgba(255, 30, 30, 0.6) !important; }

/* ============================  BURGER MENU  ============================ */
.burger {
  flex: 0 0 auto; align-self: center; margin: -5px 10px 0 4px;
  width: 44px; height: 50px; border-radius: 0;
  background: url('assets/ui/wooden_panels/top_right_burger_bar.webp') center / 100% 100% no-repeat;
  border: none; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  transition: filter 140ms ease;
}
.burger:hover { filter: brightness(1.1); }
.burger span { display: block; width: 17px; height: 2px; background: #2a1206; border-radius: 2px; box-shadow: 0 1px 0 rgba(255,220,150,0.3); }

.scrim {
  position: fixed; inset: 0; z-index: 90; background: rgba(0,0,0,0.55);
  opacity: 0; pointer-events: none; transition: opacity 220ms ease;
}
.scrim.show { opacity: 1; pointer-events: auto; }

.menu-panel {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 100;
  width: min(86vw, 320px);
  background: linear-gradient(180deg, #12121f 0%, var(--bg-void) 100%);
  border-left: 1px solid var(--gold-mid);
  box-shadow: -6px 0 28px rgba(0,0,0,0.6);
  transform: translateX(100%); transition: transform 280ms ease;
  display: flex; flex-direction: column;
  padding: 8px 22px calc(22px + env(safe-area-inset-bottom));
  padding-right: calc(22px + env(safe-area-inset-right));
}
.menu-panel.open { transform: translateX(0); }
.menu-logo { width: 78%; max-width: 220px; height: auto; align-self: center; margin: 0px;
  filter: drop-shadow(0 3px 10px rgba(139,26,26,0.6)); }
.menu-nav { display: flex; flex-direction: column; }
.menu-link {
  text-align: left; background: none; border: none; cursor: pointer;
  font-family: var(--font-display); font-size: 1.4rem; color: var(--gold-bright);
  padding: 14px 8px; border-radius: 4px; transition: background 140ms ease;
}
.menu-link:hover, .menu-link:active { background: rgba(201,168,76,0.12); }
/* Sign-in lives in the menu too (Si) — a subtle account action grouped under the nav. Placeholder until the public
   release's account sync; styling mirrors the home-screen sign-in. */
.menu-signin-bar { padding: 8px 0 2px; }
.menu-signin-btn {
  width: 100%; text-align: center; cursor: pointer;
  background: rgba(8,8,15,0.5); border: 1px solid var(--gold-dim); border-radius: 6px; padding: 11px 12px;
  font-family: var(--font-body); font-size: 0.78rem; letter-spacing: 0.05em; color: var(--text-secondary);
  transition: color 140ms ease, border-color 140ms ease, background 140ms ease;
}
.menu-signin-btn:hover { color: var(--gold-bright); border-color: var(--gold-mid); background: rgba(201,168,76,0.1); }
.menu-icons { margin-top: auto; display: flex; gap: 14px; padding-top: 18px;
  border-top: 1px solid var(--gold-dim); }
.round-icon {
  width: 46px; height: 46px; border-radius: 50%;
  background: rgba(8,8,15,0.7); border: 1px solid var(--gold-mid); color: var(--gold-mid);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  transition: all 140ms ease;
}
.round-icon:hover { border-color: var(--gold-bright); color: var(--gold-bright); }
#sound-btn.muted, #strip-sound-btn.muted { color: var(--text-muted); border-color: var(--text-muted); }
#sound-btn.muted .snd-wave, #strip-sound-btn.muted .snd-wave { display: none; }
.round-icon--strip { width: 34px; height: 34px; background: rgba(8,8,15,0.55); border-color: var(--gold-dim); }

/* ---- generic page / settings overlay ---- */
.overlay {
  position: fixed; inset: 0; z-index: 110; background: rgba(0,0,0,0.86);
  display: flex; align-items: center; justify-content: center; padding: 20px;
  opacity: 0; pointer-events: none; transition: opacity 200ms ease;
}
.overlay.show { opacity: 1; pointer-events: auto; }
.overlay-card {
  position: relative; width: min(640px, 100%); max-height: 86%; overflow-y: auto;
  /* generic textured background for ALL modals (Si) */
  background:
    linear-gradient(rgba(10, 8, 6, 0.64), rgba(10, 8, 6, 0.8)),
    url('assets/ui/web_assets/generic_background_dark_covers.webp') center / cover no-repeat;
  border: 1px solid #840000; border-radius: 6px;
  box-shadow: 0 0 30px var(--glow-gold-soft); padding: 26px 28px;
}

/* ---- unit info modal: dominant 2-column card (portrait right, info left) ---- */
.overlay-card--unit {
  width: min(1100px, 96%); height: 92%; max-height: 92%;
  padding: 0; overflow: hidden;
  display: flex; flex-direction: column;          /* so the body can fill a definite height */
}
/* Column: the card fills the space, the flip bar sits UNDER it. It used to be an absolutely-positioned pill
   floating bottom-left over the card, which on a phone came down squarely on top of the MP stat tile. */
.overlay-card--unit .overlay-body {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column;
}
.overlay-card--unit .overlay-close {
  top: 8px; right: 10px; width: 44px; height: 44px; z-index: 6;
}
.overlay-card--unit .overlay-close:hover { opacity: 1; }
/* commander/unit flip — a full-width bar along the BOTTOM of the info card. In flow, so it can never
   overlap a stat, and a full-width 48px target is far easier to hit with a thumb than a small corner pill. */
.uc-flip {
  flex: 0 0 auto; width: 100%; min-height: 48px;
  background: rgba(201,168,76,0.14);
  border: none; border-top: 1px solid var(--gold-dim);
  color: var(--gold-bright); font-family: var(--font-body); font-size: 0.85rem; font-weight: 600;
  letter-spacing: 0.05em; text-transform: uppercase;
  padding: 12px 16px; cursor: pointer;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  transition: background 0.12s;
}
.uc-flip:hover  { background: rgba(201,168,76,0.30); }
.uc-flip:active { background: rgba(201,168,76,0.40); }
.uc-flip:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: -3px; }
.ucard { flex: 1 1 auto; display: flex; min-height: 0; }
.ucard-left {
  flex: 1 1 auto; min-width: 0; min-height: 0; padding: 24px 26px; overflow-y: auto;
  background: rgba(8, 6, 4, 0.34); display: flex; flex-direction: column;
}
.ucard-right {
  flex: 0 0 42%; position: relative; overflow: hidden;
  /* let the modal's generic texture show through as the image backdrop until portrait art is added */
  background: linear-gradient(rgba(8, 6, 4, 0.2), rgba(8, 6, 4, 0.5));
  display: flex; align-items: center; justify-content: center;
}
.ucard-portrait { width: 100%; height: 100%; object-fit: cover; display: block; 
border: solid 1px var(--gold-bright);
}
.ucard-portrait--sprite {
  object-fit: contain;
  image-rendering: pixelated;

  padding: 20px;
  filter: drop-shadow(0 4px 16px rgba(0,0,0,0.8));
}
.ucard-noart {
  position: absolute; color: var(--text-muted); font-family: var(--font-display);
  font-size: 1.1rem; letter-spacing: 0.05em; text-align: center; padding: 16px;
}
.ucard-name { font-family: var(--font-display); font-weight: 700; color: var(--gold-bright);
  font-size: 1.7rem; margin: 0; line-height: 1.12; }
.ucard-type { color: var(--text-secondary); font-size: 0.82rem; text-transform: capitalize; margin: 0; }
.ucard-kw { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 16px; }
.ucard-kwbox {
  font-family: var(--font-body); font-weight: 600; font-size: 0.7rem; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--text-gold); background: rgba(8, 8, 15, 0.55);
  border: 1px solid var(--gold-dim); border-radius: 4px; padding: 5px 11px;
}
/* ═══ MAGIC ITEMS ON THE UNIT CARD ═════════════════════════════════════════════════════════════════════════════
   Si: "it would be a new keyword on Geralt and then have a sort of 'i' on it which leads on to tell you the more
   info on it, its keywords and its lovely new artwork. That way we're not just cramming in loads of more artwork
   and finding space. All we've done is added the sword (But it can be a super nice shiney button)."
   So: a chip, sitting ABOVE the keyword boxes because it is the REASON for some of them — and deliberately not
   looking like one. A keyword is a rule; this is treasure. It carries its own painting as a thumbnail and reads
   as pressable, because it is. */
.ucard-items { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
.ucard-item {
  display: inline-flex; align-items: center; gap: 0; padding: 0 0 0 0;
  border: 1px solid var(--gold-mid); border-radius: 5px; cursor: pointer;
  background: linear-gradient(100deg, rgba(201,168,76,0.22), rgba(201,168,76,0.05) 70%), rgba(8,8,15,0.6);
  overflow: hidden; transition: box-shadow 0.15s, border-color 0.15s, transform 0.1s;
  box-shadow: 0 0 0 rgba(201,168,76,0);
}
.ucard-item:hover  { border-color: var(--gold-bright); box-shadow: 0 0 12px rgba(201,168,76,0.4); }
.ucard-item:active { transform: translateY(1px); }
.uci-thumb { width: 30px; height: 30px; object-fit: cover; display: block; flex: 0 0 auto;
  border-right: 1px solid rgba(201,168,76,0.45); }
.uci-thumb--missing { background: radial-gradient(circle at 50% 45%, rgba(201,168,76,0.4), rgba(201,168,76,0.05) 70%); }
.uci-name {
  font-family: var(--font-body); font-weight: 700; font-size: 0.7rem; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--gold-bright); padding: 0 8px;
}
/* The ⓘ — the affordance that says "there's more here", in the game's own gold. */
.uci-i {
  flex: 0 0 auto; width: 15px; height: 15px; margin-right: 7px; border-radius: 50%;
  border: 1px solid var(--gold-mid); color: var(--gold-bright);
  font-family: var(--font-display); font-size: 0.6rem; font-style: italic; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center; line-height: 1;
}
.ucard-item:hover .uci-i { border-color: var(--gold-bright); background: rgba(201,168,76,0.2); }
/* A granted keyword, marked on the ITEM's card so it reads as "this is what the sword did". */
.ucard-kwbox--item { border-color: var(--gold-mid); color: var(--gold-bright); }

/* ── THE ITEM'S OWN CARD — the painting first, the rules under it ── */
.mi-card { display: flex; flex-direction: column; }
.mi-art-wrap { position: relative; width: 100%; max-height: 46vh; overflow: hidden; border-radius: 6px; background: rgba(0,0,0,0.5); }
.mi-art { width: 100%; display: block; object-fit: cover; }
/* The name sits ON the art, so the painting keeps the full width and loses none of its height to a header. */
.mi-art-fade { position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top, rgba(6,5,3,0.94) 0%, rgba(6,5,3,0.55) 22%, transparent 52%); }
.mi-heading { position: absolute; left: 14px; right: 14px; bottom: 9px; }
.mi-tag { font-size: 0.56rem; letter-spacing: 0.2em; text-transform: uppercase; color: var(--gold-bright); opacity: 0.85; }
.mi-name { font-family: var(--font-display); color: var(--gold-bright); font-size: 1.6rem;
  margin: 1px 0 0; line-height: 1.1; text-shadow: 0 2px 10px rgba(0,0,0,0.9); }
/* No art → the wrap collapses to nothing rather than leaving a black slab, and the name moves inline below. */
.mi-art-wrap--missing { max-height: none; }
.mi-art-wrap--missing .mi-art { display: none; }
.mi-art-wrap--missing .mi-art-fade { display: none; }
.mi-art-wrap--missing .mi-heading { position: static; padding: 4px 0 8px; }
.mi-body { padding: 12px 2px 0; }
.mi-text { font-size: 0.86rem; color: var(--text-secondary); line-height: 1.5; margin-bottom: 12px; }
.mi-sub { font-family: var(--font-body); font-size: 0.6rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 6px; }
.mi-grants { display: flex; flex-wrap: wrap; gap: 7px; }
.mi-desc { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }
@media (max-height: 560px) {
  .mi-art-wrap { max-height: 38vh; }
  .mi-name { font-size: 1.15rem; }
  .mi-text { font-size: 0.74rem; margin-bottom: 8px; }
}

.ucard-stats { margin-bottom: 12px; max-width: 340px; }
.ucard-desc { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }
.kw-desc { background: rgba(8, 8, 15, 0.5); border: 1px solid var(--gold-dim); border-radius: 5px; padding: 9px 12px; }
.kw-desc__name { font-family: var(--font-body); font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--text-gold); font-size: 0.74rem; margin-right: 8px; }
.kw-desc__body { font-family: var(--font-body); color: var(--text-primary); font-size: 0.86rem; line-height: 1.45; }
.overlay-close {
  position: absolute; top: 10px; right: 12px; width: 36px; height: 36px;
  background: none; border: none; padding: 0;
  cursor: pointer; border-radius: 4px; opacity: 0.8;
  display: flex; align-items: center; justify-content: center;
}
.overlay-close img { width: 100%; height: 100%; object-fit: contain; display: block; }
.overlay-close:hover { opacity: 1; }
.overlay-card h2 {
  font-family: var(--font-display); font-weight: 700; color: var(--gold-bright);
  font-size: 1.6rem; margin-bottom: 14px; padding-right: 32px;
}
.overlay-body { font-family: var(--font-body); color: var(--text-primary);
  font-size: 0.96rem; line-height: 1.55; }
.overlay-body h3 { font-family: var(--font-body); color: var(--text-gold); font-weight: 600;
  font-size: 1.05rem; margin: 18px 0 6px; }
.overlay-body p { margin-bottom: 10px; }
.overlay-body b { color: var(--gold-bright); }
.overlay-body .pg-muted { color: #aaa8b0; font-style: italic; }
.overlay-body .pg-dl dt { color: var(--text-gold); font-weight: 600; margin-top: 12px; }
.overlay-body .pg-dl dd { margin: 2px 0 0; color: var(--text-secondary); }
.overlay-body .pg-table { width: 100%; border-collapse: collapse; margin: 8px 0 14px; font-size: 0.88rem; }
.overlay-body .pg-table th { text-align: left; color: var(--gold-mid); font-weight: 600; padding: 4px 8px; border-bottom: 1px solid rgba(255,255,255,0.1); }
.overlay-body .pg-table td { padding: 4px 8px; color: #ffffff; border-bottom: 1px solid rgba(255,255,255,0.06); }
.overlay-body .pg-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; margin-bottom: 12px; }
.overlay-body .pg-table--wound { width: auto; min-width: 100%; font-size: 0.8rem; }
.overlay-body .pg-table--wound th { text-align: center; padding: 3px 6px; white-space: nowrap; }
.overlay-body .pg-table--wound td { text-align: center; padding: 3px 6px; font-family: var(--font-data); }
.overlay-body .pg-table--wound .pg-crit { color: #c06030; font-style: italic; }
/* ===== WIKI MODAL ===== */
.overlay-card--wiki {
  width: min(900px, 96%); height: 88%; max-height: 88%;
  padding: 0; overflow: hidden; display: flex; flex-direction: column;
}
.overlay-card--wiki h2 { padding: 18px 24px 0; margin-bottom: 0; flex-shrink: 0; }
.overlay-card--wiki .overlay-body {
  flex: 1 1 auto; min-height: 0; padding: 0; display: flex; flex-direction: column; overflow: hidden;
}
.wiki { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.wiki-tabs {
  flex-shrink: 0; display: flex; gap: 6px; flex-wrap: wrap;
  padding: 12px 24px 10px; border-bottom: 1px solid var(--gold-dim);
}
.wiki-tab {
  font-family: var(--font-body); font-size: 0.76rem; letter-spacing: 0.08em;
  padding: 6px 16px; border: 1px solid var(--gold-dim); border-radius: 4px;
  background: transparent; color: var(--text-secondary); cursor: pointer; transition: all 140ms ease;
}
.wiki-tab:hover { border-color: var(--gold-mid); color: var(--text-primary); }
.wiki-tab--active { background: var(--gold-mid); color: #1a1408; border-color: var(--gold-bright); font-weight: 700; }
.wiki-pane { display: none; overflow-y: auto; flex: 1 1 auto; min-height: 0; padding: 16px 24px 24px; }
.wiki-pane--active { display: block; }
.wiki-intro { color: #eeeeee; font-style: italic; font-size: 0.82rem; margin-bottom: 14px; }
.wiki-kw-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.wiki-kw-card {
  border: 1px solid rgba(138,110,42,0.4); background: rgba(20,16,8,0.6);
  padding: 10px 12px; border-radius: 4px;
}
.wiki-kw-name {
  font-family: var(--font-body); font-size: 0.67rem; font-weight: 700;
  letter-spacing: 0.13em; text-transform: uppercase; color: var(--gold-mid); margin-bottom: 4px;
}
.wiki-kw-desc { font-size: 0.78rem; color: var(--text-secondary); line-height: 1.4; }
.wiki-unit-table { width: 100%; border-collapse: collapse; font-size: 0.75rem; }
.wiki-unit-table th {
  font-family: var(--font-body); color: var(--gold-mid); font-weight: 600;
  text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--gold-dim);
  font-size: 0.67rem; letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap;
}
.wiki-unit-table td { padding: 7px 8px; border-bottom: 1px solid rgba(138,110,42,0.18); color: var(--text-secondary); vertical-align: top; }
.wiki-unit-table tr:last-child td { border-bottom: none; }
.wiki-td-wpn { color: var(--text-primary); white-space: nowrap; }
/* How-to-Play inline stat icon — sits right before a bolded stat name (F, ATT, INIT…) in the Unit Stats table and prose,
   matching the battle UI's stat-icon key. Small, baseline-aligned, never disrupts the line height. */
.pg-stat-ic { height: 1.05em; width: auto; vertical-align: -0.16em; margin-right: 4px; opacity: 0.92; }
/* Weapons wiki tab — Melee / Ranged section headers + a category chip on each card (reuses the keyword card grid). */
.wiki-wpn-head {
  font-family: var(--font-body); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--gold-mid); margin: 12px 0 6px; padding-bottom: 3px; border-bottom: 1px solid var(--gold-dim);
}
.wiki-wpn-head:first-child { margin-top: 0; }
.wiki-wpn-cat {
  font-size: 0.6rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  padding: 1px 6px; border-radius: 3px; vertical-align: middle; margin-left: 4px;
}
.wiki-wpn-cat--melee  { color: #e6c98a; background: rgba(214,168,74,0.16); border: 1px solid rgba(214,168,74,0.4); }
.wiki-wpn-cat--ranged { color: #9ec8e6; background: rgba(90,150,200,0.16); border: 1px solid rgba(90,150,200,0.4); }
/* Unit info-card weapon tag — a named chip + its rule, sitting between the Stats and Keywords sections. */
.ui-weapon { display: flex; flex-direction: column; gap: 4px; }
.ui-weapon-name {
  align-self: flex-start; font-family: var(--font-body); font-size: 0.66rem; font-weight: 700;
  letter-spacing: 0.09em; text-transform: uppercase; color: var(--gold-mid);
  padding: 2px 8px; border-radius: 3px; background: rgba(214,168,74,0.14); border: 1px solid rgba(214,168,74,0.38);
}
.ui-weapon-rule { font-size: 0.76rem; color: var(--text-secondary); line-height: 1.4; }
.wiki-unit-table tbody tr:hover td { background: rgba(245,216,124,0.04); }
.wiki-td-name { color: var(--text-primary) !important; font-family: var(--font-body); white-space: nowrap; }
.wiki-td-type { font-size: 0.70rem; white-space: nowrap; color:#eeeeee !important; }
.wiki-td-kw { color: #eeeeee !important; font-size: 0.71rem; }

/* ---- wiki sub-tabs (a second level inside a pane, e.g. Characters → Heroes & Stats / Special Rules) ---- */
.wiki-subtabs { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 16px; }
.wiki-subtab {
  font-family: var(--font-body); font-size: 0.72rem; letter-spacing: 0.06em;
  padding: 5px 15px; border: 1px solid var(--gold-dim); border-radius: 999px;
  background: transparent; color: var(--text-secondary); cursor: pointer; transition: all 140ms ease;
}
.wiki-subtab:hover { border-color: var(--gold-mid); color: var(--text-primary); }
.wiki-subtab--active { background: rgba(245,216,124,0.14); color: var(--gold-bright); border-color: var(--gold-mid); font-weight: 700; }
.wiki-subpane { display: none; }
.wiki-subpane--active { display: block; }

/* ---- per-character special-rules cards (Characters → Special Rules sub-tab) ---- */
.wiki-charkw-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 8px 0 20px; }
.wiki-charkw-card {
  border: 1px solid rgba(138,110,42,0.4); background: rgba(20,16,8,0.6);
  border-radius: 5px; padding: 12px 14px; display: flex; flex-direction: column; gap: 10px;
}
.wiki-charkw-head { display: flex; align-items: center; gap: 11px; border-bottom: 1px solid var(--gold-dim); padding-bottom: 9px; }
.wiki-charkw-portrait {
  width: 68px; height: 68px; border-radius: 50%; object-fit: cover; object-position: center top;
  border: 2px solid var(--gold-mid); flex-shrink: 0; background: rgba(0,0,0,0.3);
  box-shadow: 0 2px 8px rgba(0,0,0,0.55), inset 0 0 0 1px rgba(0,0,0,0.4);   /* let the bigger portrait sit proud (Si: "little larger, love the circles — clean") */
}
.wiki-charkw-name { font-family: var(--font-display); font-size: 0.98rem; color: var(--gold-bright); font-weight: 700; line-height: 1.1; }
.wiki-charkw-role { font-size: 0.66rem; color: var(--text-secondary); letter-spacing: 0.05em; text-transform: uppercase; margin-top: 3px; }
.wiki-charkw-list { display: flex; flex-direction: column; gap: 8px; }
.wiki-charkw-kwname {
  font-family: var(--font-body); font-size: 0.66rem; font-weight: 700; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--gold-mid); display: block; margin-bottom: 2px;
}
.wiki-charkw-kwdesc { font-size: 0.76rem; color: var(--text-secondary); line-height: 1.4; }
.wiki-charkw-none { font-size: 0.76rem; color: var(--text-secondary); font-style: italic; }
/* COMMANDER SCARS ladder inside a hero's wiki card (Si 2026-07-23). Scar-red header, then the shared .sc-* rows
   (home.css) so the wiki and the roster scar-detail modal read identically. */
.wiki-charinj { border-top: 1px solid rgba(210,70,55,0.28); padding-top: 9px; display: flex; flex-direction: column; gap: 6px; }
.wiki-charinj-head { font-family: var(--font-body); font-size: 0.66rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: #ff8a72; }
.wiki-charinj-axis { font-weight: 400; letter-spacing: 0.04em; text-transform: none; color: var(--text-secondary); opacity: 0.85; }
@media (max-width: 720px) { .wiki-charkw-grid { grid-template-columns: 1fr; } }

/* ---- combat tables pane ---- */
.ct-section { display: flex; gap: 24px; margin-bottom: 2px; }
.ct-title { font-family: var(--font-body); font-size: 0.82rem; font-weight: 600; color: var(--gold-bright); margin-bottom: 4px; }
.ct-axis { font-size: 0.72rem; color: #eeeeee; font-weight: 400; }
.ct-shared-note { margin-top: 0; margin-bottom: 10px; }
.ct-grid { border-collapse: collapse; font-family: var(--font-data); font-size: 0.72rem; margin-bottom: 14px; }
.ct-grid th { padding: 4px 6px; text-align: center; color: var(--gold-mid); font-weight: 600; background: rgba(138,110,42,0.12); }
.ct-corner { font-size: 0.62rem; color: #eeeeee; text-align: center; white-space: nowrap; min-width: 90px; }
.ct-grid td { padding: 3px 0; text-align: center; width: 32px; min-width: 28px; border: 1px solid rgba(255,255,255,0.05); font-weight: 600; }
.ct-easy    { background: rgba(30, 90, 30, 0.55); color: #a8e8a8; }
.ct-mid     { background: rgba(100, 70, 10, 0.55); color: #f0c860; }
.ct-hard    { background: rgba(120, 40, 10, 0.55); color: #f09050; }
.ct-extreme { background: rgba(100, 10, 10, 0.65); color: #f06060; font-size: 0.64rem; }
.ct-note { font-size: 0.78rem; color: var(--text-secondary); line-height: 1.5; margin-bottom: 12px; padding: 8px 12px; background: rgba(255,255,255,0.04); border-left: 2px solid var(--gold-dim); border-radius: 2px; }
.ct-legend { display: flex; align-items: center; gap: 6px; font-size: 0.74rem; color: #eeeeee; flex-wrap: wrap; }
.ct-ranged-title {
  font-family: var(--font-display); font-size: 0.92rem; color: var(--gold-bright);
  margin: 20px 0 6px; border-bottom: 1px solid var(--gold-dim); padding-bottom: 4px;
}
.ct-ranged-table { width: 100%; margin-bottom: 8px; }
.ct-ranged-table th { color: var(--gold-mid); font-size: 0.78rem; padding: 4px 8px; text-align: left; }
.ct-ranged-table td { font-size: 0.78rem; padding: 3px 8px; color: var(--text-secondary); }
.ct-ranged-table td:last-child { font-weight: 600; text-align: center; min-width: 90px; }
.ct-pip { display: inline-block; padding: 2px 6px; border-radius: 3px; font-family: var(--font-data); font-size: 0.7rem; font-weight: 700; }

/* settings rows */
.set-row { display: flex; align-items: center; justify-content: space-between;
  padding: 12px 2px; border-bottom: 1px solid rgba(138,110,42,0.2); }
.set-row .set-label { font-family: var(--font-body); color: var(--text-primary); }
.set-toggle {
  min-width: 76px; height: 34px; border-radius: 4px; cursor: pointer;
  border: 1px solid var(--gold-mid); background: rgba(8,8,15,0.6);
  font-family: var(--font-body); font-size: 0.78rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--text-secondary);
}
.set-toggle.on { color: var(--gold-bright); border-color: var(--gold-bright);
  background: rgba(201,168,76,0.14); }
.set-btn {
  margin-top: 18px; width: 100%; height: 40px; border-radius: 4px; cursor: pointer;
  border: 1px solid var(--blood-bright); background: rgba(8,8,15,0.6); color: var(--blood-bright);
  font-family: var(--font-body); text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.8rem;
}
.set-btn:hover { background: rgba(196,32,32,0.15); }
/* dev-only rows (force scenario, etc.) — dimmer, monospace hint */
.set-row--dev .set-label small { color: var(--text-muted); font-family: var(--font-data); font-size: 0.62rem; letter-spacing: 0.04em; margin-left: 6px; }
.set-select {
  min-width: 190px; max-width: 60%; height: 34px; padding: 0 8px; cursor: pointer;
  border: 1px solid var(--gold-mid); border-radius: 4px; background: rgba(8,8,15,0.8);
  color: var(--text-primary); font-family: var(--font-body); font-size: 0.78rem;
}
.set-select:focus { outline: none; border-color: var(--gold-bright); }

/* ── Scenario reveal (shown on battle load, before deployment) ───────────── */
.scenario-reveal {
  position: fixed; inset: 0; z-index: 80; display: flex; justify-content: center;
  /* safe center: centred when it fits, but a card taller than a short landscape phone aligns to the top and the
     container SCROLLS instead of clipping the title off the top and the button off the bottom (Si). */
  align-items: safe center; overflow-y: auto; padding: 16px;
  background: rgba(4,3,2,0.78); opacity: 0; transition: opacity 0.32s ease;
}
.scenario-reveal.show { opacity: 1; }
.sr-card {
  position: relative; width: 92vw; max-width: 560px; margin: auto; padding: 34px 34px 30px; text-align: center;
  background:
    linear-gradient(rgba(10,8,6,0.72), rgba(10,8,6,0.88)),
    url('assets/ui/web_assets/generic_background_dark_covers.webp') center / cover no-repeat;
  border: 1px solid var(--gold-mid); border-radius: 8px;
  box-shadow: 0 0 60px rgba(0,0,0,0.9), 0 0 20px rgba(201,168,76,0.16);
  transform: translateY(14px) scale(0.98); transition: transform 0.34s cubic-bezier(0.2,0.9,0.3,1);
}
.scenario-reveal.show .sr-card { transform: translateY(0) scale(1); }
.sr-eyebrow {
  font-family: var(--font-data); font-size: 0.64rem; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--text-secondary); margin-bottom: 10px;
}
.sr-title {
  font-family: var(--font-display); font-weight: 900; color: var(--gold-bright);
  font-size: clamp(1.7rem, 6vw, 2.5rem); letter-spacing: 0.1em; text-transform: uppercase;
  text-wrap: balance; margin: 0 0 14px; text-shadow: 0 2px 14px rgba(0,0,0,0.6);
}
.sr-desc {
  font-family: var(--font-body); font-size: 0.98rem; line-height: 1.5; color: var(--text-primary);
  max-width: 44ch; margin: 0 auto 20px;
}
/* The objectives now render as the SAME .obj-block cards the in-game Objectives scroll uses (Si: "the in game
   objectives screen is much nicer at displaying the same info"). Left-aligned so the eyebrow/headline/task read
   like the modal; the card's own text-align:center still governs the title, desc and button above/below. */
.sr-objs { text-align: left; margin: 4px 0 24px; }
.sr-objs .obj-block:last-child { margin-bottom: 0; }
/* A usual wooden UI CTA (Si) — the same plank the End-Activation / Use / March buttons wear, not a gold slab. */
.sr-begin {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 240px; padding: 15px 40px; border: none; cursor: pointer;
  background: url('assets/ui/wooden_panels/bottom_right_end_activation.webp') center / 100% 100% no-repeat;
  font-family: var(--font-display); font-size: 0.95rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: #2a1c0c; text-shadow: 0 1px 0 rgba(255,240,200,0.4);
  transition: filter 0.12s ease, transform 0.1s ease;
}
.sr-begin:hover  { filter: brightness(1.08); transform: translateY(-1px); }
.sr-begin:active { filter: brightness(0.94); transform: translateY(1px); }
.sr-forced {
  position: absolute; top: 10px; right: 12px; font-family: var(--font-data); font-size: 0.56rem;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--blood-bright);
  border: 1px solid var(--blood-bright); border-radius: 3px; padding: 2px 6px; opacity: 0.8;
}
/* SHORT viewports (landscape phones — the S25 is 780×360): shrink type, padding and gaps so the whole card fits
   between the title and the Begin button without the container having to scroll (Si: "need scaling for mobile"). */
@media (max-height: 560px) {
  .sr-card    { padding: 18px 26px 16px; }
  .sr-eyebrow { margin-bottom: 6px; }
  .sr-title   { font-size: clamp(1.2rem, 5.5vh, 1.9rem); margin-bottom: 7px; }
  .sr-desc    { font-size: 0.82rem; line-height: 1.35; margin-bottom: 11px; }
  .sr-objs    { margin: 2px 0 12px; }
  .obj-block  { padding: 8px 12px; margin-bottom: 8px; }
  .obj-label  { font-size: 0.6rem; }
  .obj-name   { font-size: 0.94rem; margin: 2px 0 3px; }
  .obj-desc   { font-size: 0.78rem; line-height: 1.32; }
  .sr-begin   { padding: 10px 30px; min-width: 200px; font-size: 0.85rem; }
}
/* NARROW viewports (portrait phones): let the card use the width and keep the title from overflowing. */
@media (max-width: 520px) {
  .sr-card  { width: 94vw; padding: 24px 20px 20px; }
  .sr-title { font-size: clamp(1.5rem, 8vw, 2rem); letter-spacing: 0.06em; }
  .sr-begin { width: 100%; min-width: 0; }
}

/* ── Mist of War — fog of war ──────────────────────────────────────────── */
/* Everything the army can't see darkens — on-board hexes AND the off-board margins — via a masked
   sheet; enemy tokens on un-revealed hexes aren't drawn at all (handled in JS). */
.mist-fog-sheet { fill: rgba(10, 14, 22, 0.62); }
/* Mist of War: a pale, edgeless haze over the whole board — everything stays visible, just foggy. */
.mist-haze-sheet { fill: rgba(196, 202, 212, 0.22); }
/* Drifting greyscale mist clouds — ambient, low opacity, floats over the field. */
.mist-drift {
  opacity: 0.20; filter: grayscale(1) brightness(1.8) contrast(0.75);
  mix-blend-mode: screen; will-change: transform;
}
.mist-drift--0 { animation: mistDriftA 30s ease-in-out infinite alternate; }
.mist-drift--1 { animation: mistDriftB 42s ease-in-out infinite alternate; }
.mist-drift--2 { animation: mistDriftC 36s ease-in-out infinite alternate; }
@keyframes mistDriftA { from { transform: translate(-18px, 6px); }  to { transform: translate(34px, -12px); } }
@keyframes mistDriftB { from { transform: translate(20px, -8px); }  to { transform: translate(-26px, 10px); } }
@keyframes mistDriftC { from { transform: translate(-10px, -6px); } to { transform: translate(22px, 14px); } }
.reduce-motion .mist-drift { animation: none; }
@media (prefers-reduced-motion: reduce) { .mist-drift { animation: none; } }

/* ── Burning Village — flames ──────────────────────────────────────────── */
.fire-flame { transform-box: fill-box; transform-origin: 50% 90%; will-change: transform, opacity;
  filter: drop-shadow(0 0 6px rgba(255,140,30,0.55)); }
.fire-flame--0 { animation: fireFlicker 0.70s ease-in-out infinite alternate; }
.fire-flame--1 { animation: fireFlicker 0.90s ease-in-out infinite alternate; }
.fire-flame--2 { animation: fireFlicker 0.55s ease-in-out infinite alternate; }
@keyframes fireFlicker {
  from { transform: scaleY(0.92) scaleX(1.03); opacity: 0.82; }
  to   { transform: scaleY(1.07) scaleX(0.97); opacity: 1; }
}
.reduce-motion .fire-flame { animation: none; }
@media (prefers-reduced-motion: reduce) { .fire-flame { animation: none; } }

/* ── Burning Village — smoke (burned-out hexes smoulder, harmless) ──────── */
.smoke-plume { transform-box: fill-box; transform-origin: 50% 100%; opacity: 0; will-change: transform, opacity; }
.smoke-plume--0 { animation: smokeRise 5.0s ease-in-out infinite; }
.smoke-plume--1 { animation: smokeRise 6.6s ease-in-out infinite; }
.smoke-plume--2 { animation: smokeRise 5.8s ease-in-out infinite; }
.smoke-plume--b { animation-delay: -2.8s; }   /* second plume offset so the column looks continuous */
@keyframes smokeRise {
  0%   { transform: translateY(6px) scale(0.6); opacity: 0; }
  30%  { opacity: 0.26; }
  70%  { opacity: 0.14; }
  100% { transform: translateY(-20px) scale(1.15); opacity: 0; }
}
.reduce-motion .smoke-plume { animation: none; opacity: 0.14; }
@media (prefers-reduced-motion: reduce) { .smoke-plume { animation: none; opacity: 0.14; } }

/* ---- SMOULDERING WRECK (chariots + war machines) ----
   The frames ARE the animation (swapped by animTick), so CSS only sets the mood: held back to a haze so it reads
   as a wreck still burning rather than a hex on fire — the fire layer above owns "this ground is dangerous", and
   a plume that shouted would start a conversation about a hazard that isn't there.
   No filters: these persist for the whole battle, and drop-shadow on a per-frame href swap is the exact cost the
   sprite layer already refuses to pay. */
.wreck-smoke {
  pointer-events: none;
  opacity: 0.42;
  transform-box: fill-box; transform-origin: 50% 100%;
  animation: wreckSmokeSway 7.5s ease-in-out infinite;
}
@keyframes wreckSmokeSway {
  0%, 100% { transform: translateX(0) scale(1); }
  50%      { transform: translateX(2.5px) scale(1.06); }
}
.reduce-motion .wreck-smoke { animation: none; }
@media (prefers-reduced-motion: reduce) { .wreck-smoke { animation: none; } }
/* The moment of ruin — a hard hit that blows out fast. Brighter and quicker than the siege spatter: that one is
   blood and dust settling on men; this is a hull letting go. */
.wreck-impact {
  pointer-events: none;
  transform-box: fill-box; transform-origin: 50% 50%;
  animation: wreckImpact 420ms cubic-bezier(0.15, 0.8, 0.3, 1) forwards;
}
@keyframes wreckImpact {
  0%   { opacity: 0;    transform: scale(0.5); }
  18%  { opacity: 1;    transform: scale(1.05); }
  100% { opacity: 0;    transform: scale(1.45); }
}
/* Fire burn-timer — tiny turns-to-burnout counter under the flame */
.fire-timer { font: 700 7px/1 var(--font-display, sans-serif); fill: #ffe08a; paint-order: stroke;
  stroke: #3a1600; stroke-width: 2px; stroke-linejoin: round; pointer-events: none; }
/* Burnt-out impact scar — the charred ground left after a hex burns out (smoke rises above it) */
.impact-scar { opacity: 0.7; filter: grayscale(0.7) brightness(0.5); pointer-events: none; }
/* Poison X — Si's green TEARDROP token over a poisoned unit, the tokens-remaining number resting on it (Si: more
   obvious than the old flat circle). The number keeps its white-on-dark stroke so it reads over the green. */
.poison-token { filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.65)); }
.poison-badge-txt { font: 700 9px/1 var(--font-display, sans-serif); fill: #eafff0; paint-order: stroke; stroke: #123a16; stroke-width: 1.7px; stroke-linejoin: round; pointer-events: none; }
/* STUN — Si's swirling STAR token, WHIRLING as the out-of-action tell (Si: "a star that whirls round"). Spins around
   its own centre (transform-box:fill-box so the origin is the image, not the SVG root). No number — stun has no tiers. */
.stun-token { transform-box: fill-box; transform-origin: center; animation: stun-whirl 1.7s linear infinite; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6)); }
@keyframes stun-whirl { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .stun-token { animation: none; } }
/* Poison End-Phase result — green float text (no dice tray, Si) */
.combat-float--poison { fill: #5fd06a; font-size: 11px; font-weight: 700; }
/* Ambient poison bubbles — rise off a poisoned unit and pop */
.poison-bubble { fill: rgba(95,208,106,0.55); stroke: rgba(30,120,50,0.5); stroke-width: 0.5;
  transform-box: fill-box; transform-origin: 50% 50%; animation: poisonBubble 1.3s ease-out forwards; }
@keyframes poisonBubble {
  0%   { opacity: 0;    transform: translateY(0) scale(0.4); }
  25%  { opacity: 0.85; }
  75%  { opacity: 0.55; transform: translateY(-13px) scale(1); }
  100% { opacity: 0;    transform: translateY(-17px) scale(1.5); }
}
.reduce-motion .poison-bubble { display: none; }
.set-slider {
  -webkit-appearance: none; appearance: none;
  width: 140px; height: 6px; border-radius: 3px;
  background: rgba(255,255,255,0.12); outline: none; cursor: pointer;
}
.set-slider::-webkit-slider-thumb {
  -webkit-appearance: none; width: 18px; height: 18px; border-radius: 50%;
  background: var(--gold-mid); border: 2px solid var(--gold-bright); cursor: pointer;
}
.set-slider::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--gold-mid); border: 2px solid var(--gold-bright); cursor: pointer;
}

/* ?demo=all — roster catalog labels. Gold name under each unit; red name + ✗ = no sprite built yet. */
#all-demo-labels text {
  font-family: system-ui, sans-serif; font-size: 13px; font-weight: 700;
  fill: #ffe4a0; paint-order: stroke; stroke: #000; stroke-width: 3px; stroke-linejoin: round;
  pointer-events: none;
}
#all-demo-labels .all-demo-label--todo { fill: #ff7a5c; }
/* Characters sit in the same sweep as the units, so they get their own tint to read apart at a glance.
   A red ✗ on a hero means more than "no art": HERO_SPRITES is what gates a commander onto the battlefield at
   all, so an unbuilt hero literally cannot take the field. */
#all-demo-labels .all-demo-label--hero { fill: #9fd8ff; }
#all-demo-labels .all-demo-label--hero.all-demo-label--todo { fill: #ff7a5c; }