:root {
  --bg: #0c1322;
  --panel: #141d33;
  --panel-2: #1c2742;
  --line: #2a3a5e;
  --ink: #e8eefc;
  --muted: #90a0c4;
  --you: #3ea6ff;
  --ai: #ff5d5d;
  --gold: #ffcf5c;
  --ok: #4cd07d;
  --shadow: 0 6px 22px rgba(0,0,0,.5);
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; margin: 0; }
body {
  font: 14px/1.4 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  background: var(--bg); color: var(--ink); overflow: hidden;
}
/* HUD on top, then the map fills the rest; the panel is a full-screen overlay that sits ON the map area
   (same grid cell) and shows only when a view is open */
#app { position: fixed; inset: 0; display: grid; grid-template-rows: minmax(0, 1fr); }
/* PRE-LAY THE BOARD IN LANDSCAPE while the rotate gate is up. Behind the gate the app was being sized to
   the PORTRAIT window, so the map projected itself for a tall narrow board — and turning the phone made
   it redo the whole layout in front of the player, which is the re-adjust that looks unfinished. Held at
   100vh x 100vw the app takes the exact size it will have ONCE ROTATED (the axes are swapped in portrait),
   so rotating swaps the class off and lands on identical dimensions: nothing to recompute, nothing to
   watch. The gate covers the overflow in the meantime. */
html.pre-landscape #app { inset: auto; top: 0; left: 0; width: 100vh; height: 100vw; }
#map { grid-row: 1; grid-column: 1; min-height: 0; }

/* HUD — keep the ☰ menu and End Turn clear of the Dynamic Island / rounded corners in landscape
   (iPhone 15 with viewport-fit=cover puts the notch on a SIDE when rotated, not the top) */
/* z 800: the HUD is a stacking context, so its dropdowns (☰ menus, z 910 INSIDE it) can never escape it —
   it must sit above the open view panel (620), the resource bar (650) and the unit bar (700), or the
   Views menu drops BEHIND the panel it's meant to drive (reported). Modals (920+) still cover it. */
/* NO title bar — the map runs edge to edge, and the two control clusters float on it as chips:
   ☰ + brand top-left, Views + End Turn top-right. The strip itself ignores the pointer so the
   map is tappable between them. */
#hud { position: absolute; top: 0; left: 0; right: 0; z-index: 800; background: none; border: none;
  pointer-events: none;
  padding: 8px 10px; padding-top: max(8px, env(safe-area-inset-top));
  padding-left: max(10px, env(safe-area-inset-left)); padding-right: max(10px, env(safe-area-inset-right)); }
.hud-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 2px 0; }
#wyrm:empty { display: none; }   /* the Age timeline is removed — no clock row */
.hud-row.top { justify-content: space-between; }
.hud-left, .top-btns { pointer-events: auto; display: inline-flex; align-items: center; gap: 8px;
  background: rgba(10,16,29,.86); border: 1px solid var(--line); border-radius: 12px; padding: 4px 9px;
  box-shadow: var(--shadow); backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); }
/* phone: a full-screen view covers the map — the chips step aside; the view's ✕ leads back to them.
   The ⤢ maximize belongs to the board, so it goes too (it lives on #app, not #map, to survive the tilt). */
@media (max-width: 1023.9px) { #app.view-open #hud, #app.view-open #minimap { display: none; } }
.brand { font-weight: 800; letter-spacing: .3px; display: inline-flex; align-items: center; gap: 6px; }
.brand-ic { width: 1.7em; height: 1.7em; border-radius: 24%; display: block; background: #fff; box-shadow: 0 0 0 1px rgba(255,255,255,.25); }
.stat { font-weight: 700; font-variant-numeric: tabular-nums; }
/* resources WRAP so every one is always visible; compact chips keep it to 1–2 short rows */
/* Resource strip: a compact bar FLOATING over the map at the bottom-right (it used to be a HUD row —
   moving it out gives the short landscape screen a whole row of extra map). Stays on ONE line. */
#res { position: fixed; left: calc(8px + env(safe-area-inset-left)); top: calc(var(--hud-h, 56px) + 8px);
  z-index: 650; display: flex; align-items: center; gap: 4px; flex-wrap: nowrap;
  background: rgba(12,19,34,.86); border: 1px solid var(--line); border-radius: 10px; padding: 4px 6px; box-shadow: var(--shadow); }
#res.hidden { display: none; }
/* resource stockpile shown at the top of the Strongholds view (chips wrap, never overflow) */
.res-row { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid var(--line); }
/* the stronghold view FREEZES the purse at the top — what you can spend stays in sight while you scroll */
#pane-hold .res-row { position: sticky; top: -11px; z-index: 6; background: var(--panel); padding-top: 11px; }
/* Split building card: the main 2/3 builds/upgrades in place; the tinted right 1/3 with the › arrow
   steps into the building's own view (description + the beings mustered there). */
/* the buildings page packs THREE columns so all nine cards fit without scrolling */
.build-grid.bld3 { grid-template-columns: 1fr 1fr 1fr; gap: 6px; }
.bld3 .build { padding: 6px 7px; font-size: 12.5px; }
.bld3 .bmore { flex-basis: 30%; font-size: 16px; }
/* the short landscape screen tightens the page so the whole grid sits above the fold */
@media (max-height: 500px) {
  #pane-hold { font-size: 13px; }
  .tab-body { padding-bottom: max(14px, env(safe-area-inset-bottom)); }
  #pane-hold .res-row { padding-bottom: 5px; margin-bottom: 6px; padding-top: 6px; top: -6px; }
  #pane-hold .city-head + .muted { display: none; }   /* the stat line yields — room/rank live in the cards */
  #pane-hold h4 { margin: 6px 0 4px; }
  .build-grid.bld3 { grid-template-columns: repeat(4, 1fr); gap: 5px; }   /* ten halls, three rows, no scroll */
  .bld3 .build { padding: 4px 6px; font-size: 12px; }
  .bld3 .build .d { font-size: 10px; }
  /* the arrow zone slims at this width: the wordy labels go, but the bare "3/4" tameable count stays */
  .bld3 .bmore small:not(.bcount) { display: none; }
  .bld3 .bmore small.bcount { font-size: 9px; }
  .bld3 .bmore { flex-basis: 24%; }
}
.build-card { display: flex; align-items: stretch; min-width: 0; }
.build-card .build { flex: 1 1 auto; min-width: 0; border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: 0; }
.bmore { flex: 0 0 32%; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  padding: 4px 2px; font-size: 19px; font-weight: 800; line-height: 1; color: var(--ink);
  background: #263457; border: 1px solid var(--line); border-top-left-radius: 0; border-bottom-left-radius: 0; }
.bmore small { font-size: 9.5px; font-weight: 700; color: var(--muted); white-space: nowrap; }
.bmore:hover { background: #2f4069; color: var(--gold); }
/* a building not yet raised: its build half dims, the › stays bright */
.build-card.unbuilt .build { opacity: .62; }
.r { flex: 0 0 auto; background: var(--panel-2); border: 1px solid var(--line); border-radius: 7px; padding: 3px 7px; font-weight: 700; font-variant-numeric: tabular-nums; }
.r.food { color: #9fe08a; } .r.wood { color: #c79a6b; } .r.stone { color: #c8cdd6; } .r.metal { color: #d8c79a; }
.r.mana { color: #7fc8ff; } .r.gems { color: #8be8ff; } .r.gold { color: #ffd86b; }
.r.coin { color: #ffcf5c; } .r.ren { color: var(--gold); }
.r .gain { font-size: .82em; font-weight: 700; color: #7fd08a; opacity: .95; }
.r .gain.loss { color: #ff9166; }
#wyrm { gap: 8px; font-size: 12px; color: var(--muted); }
#wyrm .bar { flex: 1; }

button { font: inherit; cursor: pointer; border-radius: 9px; border: 1px solid var(--line); background: var(--panel-2); color: var(--ink); padding: 8px 11px; }
button:active { transform: translateY(1px); }
button.primary { background: var(--you); border-color: var(--you); color: #06203a; font-weight: 800; }
/* End Turn wears the runic arrow-ring — a bare icon button (icon-only on phone, worded on desktop) */
#btn-end { background: none; border: none; box-shadow: none; padding: 2px 4px; display: inline-flex; align-items: center; gap: 7px; }
#btn-end .et-ic { width: 38px; height: 38px; display: block; filter: drop-shadow(0 2px 5px rgba(0,0,0,.65)); transition: transform .12s ease;
  /* a soft light disc behind the dark runic ring so its bronze detail reads on the dark HUD */
  background: radial-gradient(circle at 50% 45%, rgba(230,224,255,.85), rgba(190,200,235,.35) 60%, transparent 72%); border-radius: 50%; }
#btn-end:hover .et-ic { transform: scale(1.1) rotate(10deg); }
#btn-end:disabled { opacity: .45; filter: grayscale(.5); }
#btn-end .et-label { display: none; font-weight: 800; color: var(--gold); }
button.ghost { background: transparent; }
button:disabled { opacity: .42; cursor: not-allowed; }

/* Map */
#map { width: 100%; height: 100%; background: #0a1120; z-index: 1; }
/* the board tilts back like a war-table seen from a rise — but ONLY on the short phone screen, where
   the recession buys vertical room (see the phone media query below). On desktop the map stays FLAT so
   it fills the wide viewport with no empty bands, and the docked side-view keeps its grid. --tilt (set
   only on phone) drives the BILLBOARD counter-rotation so objects stand upright on the tilted ground;
   on desktop --tilt is 0, so those rules are no-ops. Hexes are SVG paths, so taps hit-test correctly
   through the 3D transform (verified). */
#map.tilt { transform-origin: 50% 100%; }
/* stand every OBJECT on the board upright (characters, holds, buildings, resource badges, features).
   A true 3D counter-rotation gets flattened by Leaflet's panes (transform-style stays flat), which left
   the tokens squished FLAT onto the board. Instead we counter the board's vertical foreshortening with
   a --unsquish scaleY, so each token reads as a round piece STANDING perpendicular to the board.
   --unsquish is set (with --tilt) only while tilted; unset → 1 (no-op) on the flat desktop-with-view. */
#map.tilt .unit       { transform: translate(-50%,-50%) scale(var(--mz,1))  scaleY(var(--unsquish,1)); }
/* The resource badge BILLBOARDS with everything else. It was laid flat on the board on the reasoning that
   a yield badge isn't standing on anything, so it should read as painted on the hex — but a badge is not
   scenery, it is a NUMBER you have to read, and the tilt squashes it to 0.57 of its height. Legibility
   beats the diorama here. */
#map.tilt .cult-badge { transform: translate(-50%,-50%) scale(var(--mzc,1)) scaleY(var(--unsquish,1)); }
#map.tilt .hold       { transform: translate(-50%,-50%) scale(var(--mzb,1)) scaleY(var(--unsquish,1)); }
#map.tilt .holdname   { transform: translate(-50%,-50%) scale(var(--mzb,1)) scaleY(var(--unsquish,1)); }
#map.tilt .hexbld     { transform: translate(-50%,-50%) scale(var(--mzb,1)) scaleY(var(--unsquish,1)); }
#map.tilt .outpost    { transform: translate(-50%,-50%) scale(var(--mz,1))  scaleY(var(--unsquish,1)); }
#map.tilt .feat       { transform: translate(-50%,-50%) scale(var(--mz,1))  scaleY(var(--unsquish,1)); }
#map.tilt .scry       { transform: translate(-50%,-50%) scale(var(--mz,1))  scaleY(var(--unsquish,1)); }
/* traveller info popups un-squish the same way so their text stays readable on the tilted board */
#map.tilt .leaflet-popup-content-wrapper, #map.tilt .leaflet-popup-tip-container { transform: scaleY(var(--unsquish,1)); }
/* the ✕ is a SIBLING of that wrapper, not a child, so it missed the un-squish and the tilt flattened it
   to a hairline (26px x cos55° = 15px) — it needs the correction applied to itself */
#map.tilt .leaflet-popup-close-button { transform: scaleY(var(--unsquish,1)); transform-origin: 50% 50%; }
/* PHONE: the tilt is steeper AND the map is grown past the viewport (clipped by #app) so the receded
   top/sides fall off-screen — no bare container edge cutting the board when you pan or zoom in. */
@media (max-width: 1023.9px) {
  #app { overflow: hidden; }
  /* !important beats Leaflet's inline position:relative on the container */
  /* ORTHOGRAPHIC 45° (no perspective): the board tilts as a uniform vertical squish (cos45°), so a single
     --unsquish (1/cos45° ≈ 1.414) makes EVERY token perfectly round at any position — no perspective ovals. */
  #map.tilt { position: absolute !important; left: -10%; width: 120%; bottom: 0; top: auto; height: 190%;
    transform: rotateX(55deg); transform-origin: 50% 100%; --tilt: 55deg; --unsquish: 1.743; }
}
/* DESKTOP: the same board-game tilt. The tilted map is absolutely positioned and oversized so the receded
   top of the plane falls off-screen, which is why it used to be switched OFF whenever a view docked — out
   of the grid flow it would have spanned straight under the panel. Instead the tilt now stays and the map
   is simply held to the WIDTH OF ITS COLUMN, so the board keeps its perspective while a view is open. */
@media (min-width: 1024px) {
  #app { overflow: hidden; }
  #map.tilt { position: absolute !important; left: -8%; width: 116%; bottom: 0; top: auto; height: 190%;
    transform: rotateX(55deg); transform-origin: 50% 100%; --tilt: 55deg; --unsquish: 1.743; }
  /* docked: the same tilt, confined to the map column beside the panel */
  #app.view-open #map.tilt { left: 0; width: calc(100% - clamp(380px, 32vw, 520px)); }
}
.leaflet-container { background: #0a1120; font: inherit; }

/* feature + hold + unit markers */
.wave { font-size: 11px; transform: translate(-50%,-50%) scale(var(--mz,1)); opacity: .45; color: #9cc7ee; pointer-events: none; }
.wave.dim { opacity: .2; }
.scry { font-size: 14px; transform: translate(-50%,-50%) scale(var(--mz,1)); filter: drop-shadow(0 0 5px #c79bff); position: relative; }
.scry span { position: absolute; top: -6px; right: -8px; font-size: 10px; font-weight: 800; color: #c79bff; }
.scry-actions { display: flex; gap: 6px; flex-wrap: wrap; margin: 6px 0 2px; }
.scry-actions button { padding: 5px 9px; font-size: 12px; }
/* inline-block so the box SHRINKS TO ITS ART and translate(-50%) has a width to work with. As a plain
   block inside Leaflet's zero-width marker it measured 0 wide, so the -50% resolved to 0 and every
   feature sat half its own width right of the hex centre (measured: +43px on an 85px ruin). Organic
   terrain hid it; a stockade drawn around the tile did not. */
.feat { font-size: 15px; display: inline-block; transform: translate(-50%,-50%) scale(var(--mz,1)); opacity: .9; filter: saturate(.85) drop-shadow(0 1px 2px rgba(0,0,0,.45)); }
/* Sized to sit INSIDE a stockade. The ring's clear interior is about half the hex's on-screen height,
   and at 1.7em a ruin filled nearly all of it — features draw under the palisade (Z_DECAL), so the
   near wall cut across whatever spilled past. */
.feat .ic { width: 1.42em; height: 1.42em; }
.feat.on { opacity: 1; filter: drop-shadow(0 0 5px var(--gold)); text-shadow: 0 0 6px var(--gold); }
.feat.dim { opacity: .4; filter: grayscale(.7); }
.outpost { transform: translate(-50%,-50%) scale(var(--mz,1)); font-size: 18px; position: relative; filter: drop-shadow(0 0 4px #d98a3a); }
.outpost .ic { width: 1.7em; height: 1.7em; }
.outpost .oc { position: absolute; top: -6px; right: -11px; font-size: 9px; font-weight: 800; color: #ffcf5c; background: rgba(8,14,28,.85); border: 1px solid var(--line); border-radius: 8px; padding: 0 3px; }
.outpost.dim { opacity: .5; filter: grayscale(.6); }
/* The explicit 24px box is load-bearing: Leaflet's iconSize [0,0] makes the marker element zero-width, so
   a block child inherits width:0 and `translate(-50%)` resolves to 0 — the icon then hangs its full width
   to the RIGHT of the hex centre (height was fine, being auto from the svg). Sizing the box makes both
   axes centre. Same reason .unit declares a width. */
.desttarget { width: 24px; height: 24px; transform: translate(-50%,-50%) scale(var(--mz,1)); transform-origin: 50% 50%; display: grid; place-items: center; filter: drop-shadow(0 1px 3px #000); pointer-events: none; }
.desttarget svg { display: block; transform-origin: 50% 50%; animation: desttarget-pulse 1.4s ease-in-out infinite; }
@keyframes desttarget-pulse { 0%,100% { transform: scale(1); opacity: .95; } 50% { transform: scale(1.14); opacity: .65; } }
/* cultivation badge: level number + the raw-material icon a hex yields, sits just below hex centre */
.cult-badge { transform: translate(-50%,-50%) scale(var(--mzc,1)); transform-origin: 50% 50%; display: inline-flex; align-items: center; gap: 1px; font-size: 10px; line-height: 1; padding: 0 3px; background: rgba(8,14,28,.82); border: 1px solid var(--line); border-radius: 7px; white-space: nowrap; pointer-events: none; box-shadow: 0 1px 2px rgba(0,0,0,.55); }
/* the badge carries a SMALLER resource icon than the HUD does — enlarging those made this pill swell to
   most of its hex, and out here it has to sit on the ground rather than dominate it */
.cult-badge .ric { width: 1.2em; height: 1.2em; }
.cult-badge .lvl { font-weight: 800; font-size: 11px; color: var(--gold); }
.cult-badge.dim { opacity: .45; filter: grayscale(.5); }
.cult-badge .pali { font-size: 10px; filter: drop-shadow(0 0 2px #7fd0ff); }   /* the hex is palisaded */
/* The stockade ring standing on a palisaded hex. Sized to a hex at zoom 8 and scaled by --mz like every
   other piece, so it tracks the board exactly; scaleY(--unsquish) stands it back up against the 55° tilt
   (without it the wall lies flattened across the tile). PALI_FIT keeps the whole ring inside its hex. */
/* --prot is this hex's own angle (see paliFit): h3 cells sit ~13.6° off vertical and no two alike, so a
   ring with its point straight up misses the corners. Read right-to-left, the transform undoes the art's
   squash, scales with the board, THEN turns the upright ring onto this cell's angle. */
.pali-wall { position: absolute; left: 0; top: 0; pointer-events: none;
  transform: translate(-50%, -50%) rotate(var(--prot, 0deg)) scale(var(--mz, 1)) scaleY(var(--unsquish, 1));
  transform-origin: 50% 50%; filter: drop-shadow(0 2px 3px rgba(0,0,0,.55)); }
.pali-wall.dim { opacity: .5; }
.hold.dim { opacity: .5; filter: grayscale(.6); }
.hold { transform: translate(-50%,-50%) scale(var(--mzb,1)); transform-origin: 50% 50%; font-size: 22px; text-align: center; position: relative; filter: drop-shadow(0 2px 3px #000); }
.hold .ic { width: 1.9em; height: 1.9em; display: block; transform: translateX(-50%); pointer-events: auto; cursor: pointer; }
/* the keep reached through the tap-cycle: lifted above the unit tokens (zIndexOffset) and lit gold */
.hold.focus { filter: drop-shadow(0 0 7px var(--gold)) drop-shadow(0 0 2px var(--gold)); }
/* The stronghold's name is its own marker in the top pane (see the pane block in the map setup) so a
   neighbouring hex's yield pill can never sit on it. `.holdname` is a zero-width box as tall as the keep
   art and centred on the same point, so `bottom:100%` lands the label exactly where it sat when it was
   a child of `.hold`. */
.holdname { width: 0; height: 1.9em; font-size: 22px; position: relative; transform: translate(-50%,-50%) scale(var(--mzb,1)); transform-origin: 50% 50%; filter: drop-shadow(0 2px 3px #000); }
.holdname .hn { position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: 3px; white-space: nowrap; font-size: 10px; background: rgba(8,14,28,.85); border: 1px solid var(--line); border-radius: 6px; padding: 0 4px; pointer-events: none; }
.holdname.you .hn { color: var(--you); } .holdname.ai .hn { color: var(--wc, var(--ai)); }
.holdname.dim { opacity: .5; filter: grayscale(.6); }
.hold .av { position: absolute; top: -3px; right: -7px; font-size: 9px; opacity: .9; }
.hold .spy { position: absolute; bottom: 0; right: -8px; font-size: 9px; filter: drop-shadow(0 0 2px #c79bff); }
/* denizen (defender) HP bar under a wounded stronghold on the map */
.hold .hold-hp { position: absolute; left: 50%; transform: translateX(-50%); top: 100%; margin-top: 2px; width: 30px; height: 4px; background: rgba(8,14,28,.9); border: 1px solid var(--line); border-radius: 3px; overflow: hidden; }
/* green like the panel's denizen bar — solid ORANGE at any health read as "damaged" even at 90% */
.hold .hold-hp > i { display: block; height: 100%; background: linear-gradient(90deg, #6fd08a, #4cd07d); }
.hold .hold-hp > i.low { background: linear-gradient(90deg, #ffab5e, #ff6f5e); }
/* denizen readout in the hold panel / hex popup */
.denizen { display: flex; align-items: center; gap: 8px; margin: 7px 0; font-size: 12px; }
.dz-bar { flex: 0 0 130px; height: 9px; background: rgba(255,255,255,.1); border-radius: 5px; overflow: hidden; border: 1px solid var(--line); }
.dz-bar > i { display: block; height: 100%; background: linear-gradient(90deg, #6fd08a, #4cd07d); }
.dz-bar > i.low { background: linear-gradient(90deg, #ffab5e, #ff6f5e); }
/* a stronghold's buildings, spilled onto the surrounding hexes */
/* A hold's buildings, ringed round the keep on its own hex at z11 (see BLD_R). Mirrors .unit-fan: the
   container sits on the hex centre and scales with the map; each building is offset by an inline transform.
   Everything here is ×8 on screen, so the base px are deliberately tiny. */
.bld-fan { position: absolute; left: 0; top: 0; width: 0; height: 0; transform: scale(var(--mz,1)); transform-origin: 0 0; }
.bld-fan.dim { opacity: .5; filter: grayscale(.6); }
.bld-fig { position: absolute; left: 0; top: 0; width: 9px; height: 9px; display: grid; place-items: center;
  filter: drop-shadow(0 1px 1.5px rgba(0,0,0,.9)); }
/* explicit width/height — Leaflet forces max-width:none !important on imgs in .leaflet-marker-pane */
.bld-fig .ic { width: 100% !important; height: 100% !important; object-fit: contain; display: block; }
.bld-fig .hbl { position: absolute; bottom: -1.5px; right: -1.5px; font-size: 2.4px; font-weight: 800; line-height: 1;
  background: rgba(8,14,28,.9); border: 0.2px solid var(--line); border-radius: 2px; padding: 0 0.8px; color: var(--gold); }
.hexbld { transform: translate(-50%,-50%) scale(var(--mzb,1)); transform-origin: 50% 50%; position: relative; font-size: 17px; filter: drop-shadow(0 2px 3px #000); }
.hexbld .ic { width: 1.55em; height: 1.55em; display: block; object-fit: contain; }
.hexbld .hbl { position: absolute; bottom: -4px; right: -5px; font-size: 8px; font-weight: 800; line-height: 1; background: rgba(8,14,28,.9); border: 1px solid var(--line); border-radius: 6px; padding: 0 3px; color: var(--gold); }
.hexbld.dim { opacity: .5; filter: grayscale(.6); }
.unit { transform: translate(-50%,-50%) scale(var(--mz,1)); width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; font-size: 14px; border: 2px solid #fff; box-shadow: var(--shadow); position: relative; }
/* PNG art token (falls back to emoji if the file is missing) */
.ic { width: 1.55em; height: 1.55em; object-fit: contain; display: inline-block; vertical-align: middle; pointer-events: none; }
/* Resource icons ride inline with their number, so they're sized in em: growing this enlarges the ART
   ONLY — the chip, its padding and the figure beside it are driven by font-size and don't move. */
.ric { width: 1.55em; height: 1.55em; object-fit: contain; display: inline-block; vertical-align: -0.34em; pointer-events: none; }
/* compact resource chips in the floating strip */
#res .r { font-size: 12px; padding: 0 5px; border-radius: 6px; }
/* THE ART FILLS THE PILL. A chip's height is its line-box plus vertical padding, so the icon was capped
   by that padding rather than by the chip itself. Dropping the vertical padding (the horizontal stays,
   it's what gives the chip its shape) hands that space to the icon: the pills keep the exact height they
   had and the glyph grows into them. Measured: chip 28px before and after, icon 20px → 26px. */
.res-row .r { padding-top: 0; padding-bottom: 0; }
.res-row .ric, #res .ric { width: 2em; height: 2em; vertical-align: middle; }
/* the HUD purse button does the same against its 40px frame */
#btn-res { padding-top: 2px; padding-bottom: 2px; }
#btn-res .ric { width: 2.4em; height: 2.4em; vertical-align: middle; }
#res .gain { font-size: 10px; }
/* collapsed state: just a ‹ arrow the player taps to pull the resources back out (while a unit is selected) */
#res.collapsed { padding: 2px; }
.res-toggle { min-width: 30px; padding: 5px 8px; font-size: 16px; font-weight: 800; line-height: 1; background: transparent; border: 0; color: var(--ink); cursor: pointer; }
/* framed panel icon on a subtle backdrop: .pbust crops characters to a bust; .pframe shows the whole icon (buildings) */
.pbust, .pframe { display: inline-grid; place-items: center; width: 1.9em; height: 1.9em; border-radius: 5px; overflow: hidden;
  position: relative; vertical-align: middle; background: rgba(150,172,205,.20); box-shadow: inset 0 0 0 1px rgba(255,255,255,.10); }
.pbust > img.ic { position: absolute; left: var(--cx, 50%); top: var(--cy, 0%); width: var(--cw, 235%); height: auto; transform: translateX(-50%); }
.pframe > img.ic { width: 100%; height: 100%; object-fit: contain; }
/* compact busts in the battle log */
.bt-log .pbust { width: 1.35em; height: 1.35em; border-radius: 4px; vertical-align: -0.4em; }
/* busts inside dungeon-crawl tiles shrink to fit the tile */
.qg .pbust { width: 1.25em; height: 1.25em; border-radius: 3px; background: none; box-shadow: none; }
/* dungeon props (the Witch, a relic, a charm on the floor) — whole artwork, not a cropped bust, since
   an item is a thing rather than a face. Sized in em so it tracks the tile size the crawl computes. */
.qg .qart { width: 1.3em; height: 1.3em; object-fit: contain; display: block; }
/* Map token: the full-body art is auto-cropped to a head-and-shoulders BUST. The .tk wrapper is a
   circular clip so the art can be scaled up & top-aligned (head sits at the top of centered art),
   while HP bar / count / badges stay OUTSIDE the clip and overflow the token as before.
   --cw/--cy tune the crop (a per-type nudge can override them inline, e.g. for off-centre beasts). */
.unit .tk { position: absolute; inset: 0; border-radius: 50%; overflow: hidden; display: grid; place-items: center; }
.unit .tk .ic { position: absolute; left: var(--cx, 50%); top: var(--cy, 0%); width: var(--cw, 240%); height: auto; transform: translateX(-50%); filter: drop-shadow(0 1px 1px rgba(0,0,0,.55)); }
/* Close-zoom fan: at maxZoom a hex is ~300px across, so each echelon stands on it as its FULL artwork
   ringed around the hexagon — no circular clip, no bust crop. The container sits on the hex centre and
   scales with the map; each figure is offset around the ring by an inline transform. */
.unit-fan { position: absolute; left: 0; top: 0; width: 0; height: 0; transform: scale(var(--mz,1)); transform-origin: 0 0; }
.fan-fig { position: absolute; left: 0; top: 0; width: 19px; height: 19px; display: grid; place-items: end center; cursor: pointer; }
/* !important is REQUIRED here, not laziness: leaflet.css sizes every img inside .leaflet-marker-pane with
   its own !important rules, and its selector outranks this one — without !important the art renders at its
   natural 1254px. object-position stands each figure on the ground rather than centring it in the box. */
/* STAND THEM ALL THE SAME HEIGHT. These are figures on ground, so height is what the eye compares — but
   the box pinned WIDTH and let height follow the plate's aspect, so a square plate came out 50% shorter
   than a narrow one from the same box (measured: avatar-f 152x153 beside centaur-f 152x229). Avatars are
   square because a full-body figure with a spread cloak crops to a square; most creatures are narrow
   portraits. The result read as "the avatars are small" when nothing about them was small.
   Height-driven with width auto, anchored to the bottom, so everyone stands the same height and a broad
   creature is simply broader. Absolute against the box, because `.fan-fig` is a grid whose row auto-sizes
   to its content — a percentage height there resolves against a track the image itself is sizing and
   silently does nothing (the same trap as the battle roster and the art sheets). */
.fan-fig .fan-ic { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%);
  height: 100% !important; width: auto !important; max-width: none !important; object-fit: contain;
  display: block; filter: drop-shadow(0 1px 2px rgba(0,0,0,.85)) drop-shadow(0 0 1px rgba(0,0,0,.6)); }
.fan-fig .fan-fb { font-size: 14px; line-height: 1; filter: drop-shadow(0 1px 2px #000); }

/* ---- BREATHING. No new art and no frames: a slow 1.4% stretch anchored at the feet reads as breath on
   a still plate, and it works on all 61 creatures the moment it exists. Each figure gets its own phase
   (--bd, hashed from its uid) so a line of them never pulses in lockstep, which is what would give it
   away as an effect. Close-up only — at map zoom the tokens are 22px and nobody would see it — and off
   while the board is moving, since Safari re-rasterises filtered layers inside the 3D tilt every frame
   and there is no sense paying for that mid-gesture. */
@keyframes fanBreathe {
  0%, 100% { transform: translateX(-50%) scaleY(1); }
  50%      { transform: translateX(-50%) scaleY(1.014); }
}
.fan-fig .fan-ic { transform-origin: 50% 100%;
  animation: fanBreathe 3.6s ease-in-out infinite; animation-delay: var(--bd, 0s); }
#map.busy .fan-fig .fan-ic { animation: none; }
@media (prefers-reduced-motion: reduce) { .fan-fig .fan-ic { animation: none; } }

/* ---- GROUND CONTACT. What makes a palisade on the wastes look pasted on is not its colour, it is that
   nothing touches the ground: the object floats above a picture of terrain. A soft ellipse under it,
   multiplied into the tile, borrows that tile's own colour and texture — so one rule does every biome
   without a single new file, and without 180 baked combinations. */
.feat, .hexbld, .outpost { position: relative; }
.feat::after, .hexbld::after, .outpost::after, .pali-wall::after {
  content: ''; position: absolute; left: 50%; bottom: -2%; width: 78%; height: 16%;
  transform: translateX(-50%); border-radius: 50%; pointer-events: none; z-index: -1;
  background: radial-gradient(ellipse at center, rgba(0,0,0,.55) 0%, rgba(0,0,0,.32) 45%, rgba(0,0,0,0) 72%);
  mix-blend-mode: multiply; }
.pali-wall::after { width: 86%; height: 12%; bottom: 2%; }
.feat.dim::after, .hexbld.dim::after, .outpost.dim::after, .pali-wall.dim::after { opacity: .45; }
/* a coloured ground-shadow under each figure stands in for the token ring — it still reads as whose it is,
   but stays a faint disc at the feet rather than a glowing platform */
.fan-fig::before { content: ""; position: absolute; left: 50%; bottom: 0; width: 52%; height: 2.2px; transform: translateX(-50%);
  border-radius: 50%; background: rgba(0,0,0,.5); opacity: .75; z-index: -1; }
.fan-fig.you::before   { background: var(--you); }
.fan-fig.ai::before    { background: var(--wc, var(--ai)); }
.fan-fig.neutral::before { background: #8b93a7; }
.fan-fig.barb::before  { background: #d98a3a; }
.fan-fig.wild::before  { background: #b06bd9; }
.fan-fig.avatar::before { background: var(--gold); opacity: .95; }
.fan-fig.sel .fan-ic { filter: drop-shadow(0 0 3px var(--gold)) drop-shadow(0 0 6px var(--gold)); }
.fan-fig .fan-hp { position: absolute; left: 50%; bottom: -2.5px; transform: translateX(-50%); width: 44%; height: 1.4px;
  background: rgba(0,0,0,.6); border-radius: 2px; overflow: hidden; }
.fan-fig .fan-hp i { display: block; height: 100%; background: linear-gradient(90deg, #d64b4b, #6fcf6f); }
/* Labels stay subordinate to the art: everything here is ×8 at z11, so these are deliberately tiny in
   base px — 2.2px reads as ~18px on screen, against a ~150px figure. */
.fan-fig .fan-ech, .fan-fig .fan-rank, .fan-fig .fan-link { position: absolute; font-size: 2.2px; line-height: 1;
  padding: 0.4px 1px; border-radius: 1.5px; background: rgba(8,14,28,.8); color: #e9edf6; white-space: nowrap;
  pointer-events: none; }
.fan-fig .fan-ech  { top: -1px; left: 50%; transform: translateX(-50%); font-weight: 700; letter-spacing: .04em; }
.fan-fig .fan-rank { top: -1px; right: 0; color: var(--gold); }
.fan-fig .fan-link { bottom: -4px; left: 50%; transform: translateX(-50%); }
/* The disguise mask & dug-in shield ride the figure they belong to. NB they need their own rules here:
   the existing ones are scoped to `.unit`, so inside a fan figure they'd fall back to static positioning
   and pile up on the hex centre. */
.fan-fig .mask, .fan-fig .fan-fort { position: absolute; top: -1px; left: -1px; font-size: 3px; line-height: 1;
  filter: drop-shadow(0 1px 1px #000); pointer-events: none; }
.fan-fig .mask.spotted { filter: drop-shadow(0 0 2px var(--ai)); }
.fan-fig .fan-fort { top: auto; bottom: 2px; left: -3px; filter: drop-shadow(0 0 2px #7fd0ff); }
.unit.you { background: #16395c; border-color: var(--you); }
/* a rival's ring is ITS colour (--wc, set inline per warden); --ai is the fallback for a lone rival */
.unit.ai { background: #5c1f1f; border-color: var(--wc, var(--ai)); }
.unit.neutral { background: #2b3346; border-color: #8b93a7; }
.unit.barb { background: #4a2f14; border-color: #d98a3a; }
.unit.wild { background: #3a2340; border-color: #b06bd9; }
.unit.avatar { border-color: var(--gold); box-shadow: 0 0 8px var(--gold); }
.unit.sel { outline: 3px solid var(--gold); outline-offset: 2px; }
.unit .hp { position: absolute; bottom: -6px; left: 2px; right: 2px; height: 3px; background: #300; border-radius: 2px; overflow: hidden; }
.unit .hp > i { display: block; height: 100%; background: var(--ok); }
.unit .cnt { position: absolute; bottom: -7px; left: -7px; min-width: 15px; text-align: center; background: #0e1626; color: #fff; border: 1px solid var(--line); border-radius: 9px; font-size: 10px; font-weight: 800; padding: 0 3px; line-height: 14px; }
.stack { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 8px 0; }
.stack-chip { padding: 5px 9px; font-size: 16px; line-height: 1; }
.stack-chip.on { border-color: var(--gold); box-shadow: inset 0 0 0 1px var(--gold); }
/* linked party members (different-typed units 🔗 Linked together) get a gold box in the Unit tab */
.stack-chip.linked { border-color: var(--gold); box-shadow: 0 0 0 2px var(--gold); }
.stack-chip.linked.on { box-shadow: 0 0 0 2px var(--gold), inset 0 0 0 1px var(--gold); }
.unit .mask { position: absolute; top: -8px; right: -8px; font-size: 10px; }
.unit .mask.spotted { filter: drop-shadow(0 0 3px var(--ai)); }
.unit .ech-map { position: absolute; top: -8px; left: -7px; font-size: 9px; line-height: 12px; font-weight: 800; color: #cfe3ff; background: #0e1626; border: 1px solid var(--line); border-radius: 6px; padding: 0 3px; letter-spacing: -1px; }
.unit .rankpip { position: absolute; bottom: -8px; right: -6px; font-size: 8px; line-height: 10px; font-weight: 800; color: #06203a; background: var(--gold); border-radius: 5px; padding: 0 2px; }
.unit.fortified { box-shadow: 0 0 0 2px #7fd0ff, var(--shadow); }
.unit.moving { box-shadow: 0 5px 12px rgba(0,0,0,.55); z-index: 500; }
.unit .fortbadge { position: absolute; top: -8px; left: -7px; font-size: 10px; filter: drop-shadow(0 0 2px #7fd0ff); }
/* crossed swords: this foe is inside the selected unit's reach and can be struck this turn (its hex is
   washed red to match). Right corner, clear of the 🎭 mask (top-right) and 🛡 fortify badge (top-left). */
.unit .hitbadge { position: absolute; bottom: -6px; right: -8px; font-size: 11px; filter: drop-shadow(0 0 3px #ff6b6b) drop-shadow(0 1px 1px #000); pointer-events: none; }
/* an attackable foe keeps its OWNER'S ring colour — the red hex wash, the ⚔ badge and this soft red
   glow do the signalling, so you can still tell WHOSE unit you're about to strike */
.unit.marked { box-shadow: 0 0 8px rgba(255,107,107,.85); }
.fan-fig .hitbadge { position: absolute; bottom: 0; right: -2px; font-size: 5px; filter: drop-shadow(0 0 1.5px #ff6b6b) drop-shadow(0 0.5px 0.5px #000); pointer-events: none; }
/* rank chip used in the Beasts tab & unit panel — brighter as the grade climbs */
.rank { display: inline-block; min-width: 30px; text-align: center; font-size: 10px; font-weight: 800; padding: 1px 4px; border-radius: 5px; background: #223047; color: #b9c6dc; border: 1px solid var(--line); }
.rank-shield { font-size: 12px; filter: drop-shadow(0 0 2px #7fd0ff); }
.rank.r2, .rank.r3 { background: #2c3d2a; color: #bfe0a8; }
.rank.r4, .rank.r5 { background: #40391c; color: #f0d98a; }
.rank.r6, .rank.r7 { background: #4a2f14; color: var(--gold); border-color: var(--gold); }
.ech { display: inline-block; min-width: 16px; text-align: center; color: #cfe3ff; font-weight: 800; letter-spacing: 1px; }
button.mini { padding: 1px 8px; font-size: 11px; font-weight: 700; border-radius: 6px; }
/* the ‹ back button on a building's page: big enough to hit without aiming */
#bld-back { padding: 8px 16px; font-size: 15px; font-weight: 800; border-radius: 10px; }
#bld-back:hover { border-color: var(--gold); color: var(--gold); }
button.fort-on { border-color: #7fd0ff; color: #7fd0ff; box-shadow: inset 0 0 0 1px #7fd0ff; }
.chip.fort-chip { color: #7fd0ff; border-color: #7fd0ff; }
.chip.ech-chip { color: #cfe3ff; border-color: #4a6a9e; }
/* floating per-unit quick-action menu, bottom-right */
/* the selected-unit control (shown when the side panel is collapsed) is a horizontal BAR pinned to the
   bottom-right, everything on one row: name · stats · action buttons · close */
/* sits ABOVE the floating resource strip (bottom-right) so the two never overlap. On the PHONE the
   whole bar is ONE compact wrapping row (identity, chips and actions flow together — every pixel of
   the short landscape counts); desktop restores the roomier two-row layout in its media block. */
#unit-menu { position: fixed; right: calc(10px + env(safe-area-inset-right)); bottom: calc(8px + env(safe-area-inset-bottom)); z-index: 700;
  max-width: calc(100vw - 20px - env(safe-area-inset-left) - env(safe-area-inset-right)); background: var(--panel); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--shadow);
  padding: 4px 6px; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; }
#unit-menu.hidden { display: none; }
/* phone: the two structural rows dissolve — their children join the bar's own wrapping row */
.um-info, .um-acts { display: contents; }
.um-title { font-weight: 800; font-size: 13px; white-space: nowrap; flex: 0 0 auto; }
.um-sub { color: var(--muted); font-size: 10px; font-weight: 600; white-space: nowrap; }
button.um-x { padding: 3px 8px; font-weight: 800; flex: 0 0 auto; order: 99; }
/* ℹ full-profile button sits between the stat chips and ✕ */
button.um-i { padding: 3px 8px; font-weight: 800; font-style: normal; flex: 0 0 auto; margin-left: auto; color: var(--muted); order: 98; }
button.um-i:hover { color: var(--gold); border-color: var(--gold); }
/* 🪖 echelon-chart button (org-tree glyph) — sits just before ℹ on a formation/linked bar */
button.um-hier { padding: 3px 7px; flex: 0 0 auto; color: var(--muted); order: 97; display: inline-grid; place-items: center; }
button.um-hier:hover { color: var(--gold); border-color: var(--gold); }
/* the echelon flow chart (in the shared info modal): a combined-echelon root over its member branches */
.ech-root { text-align: center; background: var(--panel-2); border: 1px solid var(--line); border-radius: 10px; padding: 8px 12px; margin: 4px 0 0; font-weight: 700; }
.ech-branches { margin: 12px 0 0 12px; padding-left: 16px; border-left: 2px solid var(--line); display: flex; flex-direction: column; gap: 8px; }
.ech-node { position: relative; border: 1px solid var(--line); border-radius: 10px; background: #0e1626; padding: 8px 10px; }
.ech-node::before { content: ''; position: absolute; left: -17px; top: 17px; width: 14px; height: 2px; background: var(--line); }
.ech-node-head { font-weight: 700; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ech-comp { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 7px; }
.ech-sq { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; background: var(--panel-2); border: 1px solid var(--line); border-radius: 7px; padding: 2px 8px; }
/* the manager's per-element rows: who they are on the left, what you can do with them on the right */
.ech-hp { height: 5px; margin: 6px 0 2px; background: rgba(255,255,255,.1); border-radius: 3px; overflow: hidden; }
.ech-hp > i { display: block; height: 100%; background: linear-gradient(90deg, #6fd08a, #4cd07d); }
.ech-hp > i.low { background: linear-gradient(90deg, #ffab5e, #ff6f5e); }
.ech-rows { display: flex; flex-direction: column; gap: 5px; margin-top: 7px; }
.ech-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px; padding: 4px 8px; }
.ech-who { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; }
.ech-who .pbust { width: 1.6em; height: 1.6em; }
.ech-ops { display: inline-flex; gap: 5px; flex-wrap: wrap; }
.ech-btn { font-size: 11.5px; font-weight: 700; padding: 3px 8px; border-radius: 7px;
  background: #1d2536; border: 1px solid var(--line); color: var(--ink); cursor: pointer; }
.ech-btn:hover:not(:disabled) { background: #26304a; border-color: #3d4a66; }
.ech-btn:disabled { opacity: .38; cursor: not-allowed; }
/* 🎭 disguise picker — anchored to the bar; opens UP on the phone (bar sits bottom-right), DOWN on desktop */
.um-drop { position: absolute; right: 8px; bottom: calc(100% + 8px); display: flex; flex-direction: column; gap: 3px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 6px; box-shadow: var(--shadow);
  max-height: 62vh; max-height: 62dvh; overflow-y: auto; z-index: 5; }
.um-drop button { display: flex; align-items: center; gap: 8px; white-space: nowrap; padding: 6px 10px; text-align: left; }
.um-drop button b { margin-left: auto; color: var(--gold); }
.um-drop button.cur { border-color: var(--gold); color: var(--gold); }
.um-hp { display: flex; gap: 4px; flex: 0 0 auto; }
.um-hp .chip { font-size: 11px; padding: 2px 5px; white-space: nowrap; }
/* the formation's makeup rides the blank end of the actions row */
.um-acts button { font-size: 12px; padding: 5px 8px; text-align: center; white-space: nowrap; }

/* Panel — a FULL-SCREEN view overlaying the map area, opened from the ☰ Views dropdown or a stronghold tap */
#panel { grid-row: 1; grid-column: 1; z-index: 620; background: var(--panel); border-top: 1px solid var(--line);
  display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
#panel.hidden { display: none; }
#panel-head { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 8px 12px; padding-right: max(12px, env(safe-area-inset-right)); padding-left: max(12px, env(safe-area-inset-left));
  border-bottom: 1px solid var(--line); background: var(--panel-2); }
#panel-title { font-size: 16px; font-weight: 800; }
/* a view header wearing real artwork instead of an emoji — sized to sit on the text baseline */
#panel-title .view-ic { width: 1.5em; height: 1.5em; vertical-align: -0.42em; }
#panel-close { min-width: 34px; font-weight: 800; padding: 5px 10px; }
/* menus */
.top-btns { display: flex; align-items: center; gap: 8px; }
.hud-btn { min-width: 40px; text-align: center; font-weight: 800; line-height: 1; }
.hud-left { position: relative; display: flex; align-items: center; gap: 8px; }
.menu-wrap { position: relative; display: inline-flex; }
.menu-btn { min-width: 34px; font-size: 18px; padding: 6px 8px; }
#game-menu, #view-menu { position: absolute; top: calc(100% + 6px); z-index: 910; display: flex; flex-direction: column; min-width: 184px; background: #141c30; border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 10px 28px rgba(0,0,0,.55); padding: 6px; gap: 2px; }
#game-menu { left: 0; }
#view-menu { right: 0; }
/* 🪙 dropdown: the stockpile as a vertical ledger — one resource per row, gains at the right edge */
#res-menu { position: absolute; right: 0; top: calc(100% + 6px); z-index: 910; display: flex; flex-direction: column; gap: 3px; min-width: 132px; background: #141c30; border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 10px 28px rgba(0,0,0,.55); padding: 8px 10px; }
#res-menu.hidden { display: none; }
#res-menu .r { display: flex; align-items: center; gap: 7px; background: none; border: none; padding: 3px 2px; font-size: 14px; }
#res-menu .gain { margin-left: auto; padding-left: 10px; }
/* 🏷 hold-to-label button — no text selection / long-press menu while held down */
#btn-legend { touch-action: none; user-select: none; -webkit-user-select: none; }
/* the 🏷 label-peek is a phone-only affordance — desktop already shows each action button's word */
@media (min-width: 1024px) { #btn-legend { display: none; } }
#game-menu.hidden, #view-menu.hidden { display: none; }
#game-menu button, #view-menu button { text-align: left; padding: 10px 12px; background: transparent; border: 0; border-radius: 7px; color: var(--ink); font-size: 14px; font-weight: 600; cursor: pointer; white-space: nowrap; }
#game-menu button:hover, #view-menu button:hover { background: rgba(255,255,255,.08); }
#game-menu button:disabled { opacity: .4; cursor: default; }
/* extra bottom room so the floating resource strip (bottom-right) never hides the last panel content */
.tab-body { flex: 1 1 auto; overflow: auto;
  padding: 11px max(13px, env(safe-area-inset-right)) calc(52px + env(safe-area-inset-bottom)) max(13px, env(safe-area-inset-left)); }
.tab-pane { display: none; } .tab-pane.active { display: block; max-width: 720px; margin: 0 auto; }
/* The Strongholds page is a CARD GRID, not prose. The 720px reading cap that keeps the text-heavy panes
   legible was handing ~130px of dead margin to each side of the buildings, so it gets the full width and
   a tighter gutter — the cards grow into it instead. */
#pane-hold.active { max-width: 1200px; }
#panel:has(#pane-hold.active) .tab-body {
  padding-left: max(8px, env(safe-area-inset-left)); padding-right: max(8px, env(safe-area-inset-right)); }

h3 { margin: 0 0 8px; font-size: 15px; } h4 { margin: 12px 0 6px; font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .6px; }
.muted { color: var(--muted); } .empty { color: var(--muted); padding: 16px 4px; text-align: center; }
.city-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; } .city-head .name { font-size: 16px; font-weight: 800; }
/* the garrison bar rides the head row, right beside the stronghold's name */
.city-head .denizen { margin: 0; flex: 1 1 auto; min-width: 0; }
.city-head .denizen .dz-bar { flex: 1 1 90px; max-width: 150px; }
.yields { display: flex; flex-wrap: wrap; gap: 6px; margin: 9px 0; }
.chip { background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px; padding: 4px 8px; font-weight: 700; font-variant-numeric: tabular-nums; }
.actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.bar { height: 8px; background: var(--panel-2); border-radius: 99px; overflow: hidden; border: 1px solid var(--line); }
.bar > i { display: block; height: 100%; background: var(--ok); }
.bar.wyrm > i { background: linear-gradient(90deg, var(--gold), var(--ai)); }
.row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.roster-row { padding: 5px 0; border-bottom: 1px dashed var(--line); }
.build-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.build { text-align: left; display: flex; flex-direction: column; gap: 2px; padding: 8px 9px; }
.hold-list { display: flex; flex-direction: column; gap: 7px; margin-top: 8px; }
.hold-pill { text-align: left; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 11px; width: 100%; }
.hold-pill .t { font-weight: 700; flex: 0 0 auto; }
/* the halls this keep has raised, each with its level in the corner — wraps rather than squeezing the name */
.hold-blds { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 4px; flex: 1 1 auto; }
.hold-blds .hb { position: relative; display: inline-grid; place-items: center; line-height: 1; }
/* the building art is warm-lit and sits on transparency, so it needs a LIGHT plate under it — on the
   default dark frame a 24px keep just read as a smudge. Bigger, brighter, and padded off the edges. */
.hold-blds .hb .pframe { width: 2.15em; height: 2.15em; padding: 2px;
  background: linear-gradient(180deg, rgba(226,235,250,.92), rgba(188,205,232,.82));
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.5), 0 1px 3px rgba(0,0,0,.35); }
.hold-blds .hb > i { position: absolute; right: -3px; bottom: -3px; font-style: normal; font-size: 9px; font-weight: 800;
  line-height: 1; padding: 1px 3px; border-radius: 5px; background: #0f1830; color: var(--gold);
  box-shadow: 0 0 0 1px rgba(255,255,255,.16); }
.squads { margin: 2px 0 4px; }
.squad-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 4px 0; border-bottom: 1px dashed var(--line); font-size: 13px; }
.squad-hp { display: flex; align-items: center; gap: 6px; }
.squad-hp .bar { width: 70px; height: 7px; }
.build .t { font-weight: 700; } .build .t small { color: var(--muted); font-weight: 600; }
/* "Lv 5" must never break across lines — the ⓘ that follows may wrap on its own instead */
.build .t .lv-tag { white-space: nowrap; }
/* the building cards sit four to a row, so their ⓘ is trimmed down to stay on the name's line */
.build .t .info-i { font-size: 10px; width: 13px; height: 13px; line-height: 11px; margin-left: 1px; vertical-align: 1px; }
/* A creature row is a ROW, not a button: it carries its own ⓘ and Retain controls, at a size a thumb can
   actually hit. The old 13px ⓘ nested inside a full-width button was both too small and invalid markup. */
.creature-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; cursor: default; }
.creature-row .t { flex: 1 1 auto; min-width: 0; }
.creature-row .d { flex: 0 0 auto; }
.cr-ops { display: inline-flex; gap: 6px; margin-left: auto; flex: 0 0 auto; }
.cr-btn { font: inherit; font-size: 12px; font-weight: 700; color: var(--ink); background: #1d2536;
  border: 1px solid var(--line); border-radius: 8px; padding: 5px 10px; min-height: 30px; cursor: pointer; }
.cr-btn:hover:not(:disabled) { background: #26304a; border-color: #3d4a66; }
.cr-btn:disabled { opacity: .4; cursor: not-allowed; }
.cr-btn.cr-info { min-width: 32px; padding: 5px 8px; font-size: 14px; color: var(--muted); }
.cr-btn.cr-info:hover { color: var(--ink); border-color: var(--you); }
.cr-btn.cr-retain:not(:disabled) { background: linear-gradient(180deg,#2f3f63,#233152); border-color: #44557d; }
.creature-row.locked .t, .creature-row.locked .d { opacity: .75; }
.build .d { font-size: 11px; color: var(--muted); } .build.locked { opacity: .6; }
/* the Monastery's ascension orders — one chip per order, lit once this house has founded it */
.orders-open { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 6px; }
.order-chip { font-size: 11px; font-weight: 700; color: var(--muted); border: 1px solid var(--line);
  background: rgba(255,255,255,.03); border-radius: 999px; padding: 3px 10px; }
.order-chip small { font-weight: 500; opacity: .85; }
.order-chip.open { color: var(--ok); border-color: var(--ok); background: rgba(60,180,110,.10); }
/* standing market orders: a ± stepper per resource, lit once an order is actually running */
/* stands where the Flee button would be when defending your own keep — the option is gone, and the
   reason is on screen rather than left for the player to work out */
.bt-noflee { font-size: 11px; font-weight: 700; color: #ffcfa8; padding: 6px 10px; border-radius: 8px;
  background: rgba(150,60,30,.16); border: 1px solid rgba(210,110,60,.45); }
.auto-note { font-size: 11px; line-height: 1.4; color: var(--muted); margin: 6px 0 2px;
  padding: 6px 9px; border-radius: 8px; background: rgba(255,255,255,.03); border: 1px solid var(--line); }
.auto-live { color: var(--gold); font-weight: 700; }
.trade-row { flex-wrap: wrap; row-gap: 4px; }
.auto-cell { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; width: 100%; justify-content: flex-end; }
.auto-lbl { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: .4px; }
.auto-step { display: inline-flex; align-items: center; gap: 3px; padding: 1px 3px; border-radius: 7px;
  border: 1px solid var(--line); background: rgba(255,255,255,.02); font-size: 11px; }
.auto-step.on { border-color: var(--gold); color: var(--gold); background: rgba(255,207,92,.10); }
.auto-step b { min-width: 14px; text-align: center; font-variant-numeric: tabular-nums; }
.auto-step small { color: var(--muted); font-size: 9px; }
.as-btn { min-width: 18px; padding: 0 4px; font-size: 13px; font-weight: 800; line-height: 18px;
  border-radius: 5px; border: 1px solid var(--line); background: var(--panel-2); color: var(--ink); cursor: pointer; }
.as-btn:disabled { opacity: .35; cursor: default; }
/* a portal house whose ground has been lost — everything it raises is on hold until the field returns */
.house-dark { font-size: 11px; line-height: 1.35; margin: 2px 0 6px; padding: 6px 9px; border-radius: 8px;
  color: #ffcfa8; background: rgba(150,60,30,.16); border: 1px solid rgba(210,110,60,.45); }
.build .d .short { color: #ff9166; font-weight: 700; }   /* a resource you can't yet afford */
.info-i { display: inline-block; font-size: 12px; color: var(--muted); border: 1px solid var(--line); border-radius: 50%; width: 15px; height: 15px; line-height: 13px; text-align: center; cursor: help; }
.info-i:hover { color: var(--ink); border-color: var(--you); }
/* NPC recruit paths shown in the wanderer popup */
.npc-recruit { margin-top: 7px; padding-top: 6px; border-top: 1px solid var(--line); font-size: 12px; line-height: 1.5; }
.npc-recruit > div { margin: 2px 0; }
.ok-txt { color: var(--ok); }

/* info sheet modal */
/* KEEP CLEAR OF THE CAMERA. In landscape the notch covers one long edge, so overlays pad both — the
   inset is 0 on a device without one, and max() keeps the normal spacing there. */
#info-modal { position: fixed; inset: 0; background: rgba(4,8,16,.72); z-index: 920; display: grid; place-items: center;
  padding: max(16px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-left), env(safe-area-inset-right))
           max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left), env(safe-area-inset-right)); }
#info-modal.hidden { display: none; }
/* Widths are % of the modal's PADDED box, never vw: vw includes the notch, so a vw-wide card is shoved
   right by the safe-area padding while keeping its full width, and hangs off the far edge (measured at
   852px with a 47px inset: 47px of the champion picker off-screen). */
#info-card { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--shadow); width: min(420px, 100%); max-height: min(100%, 90vh); max-height: min(100%, 90dvh); overflow: auto; padding: 16px; position: relative; }
/* ✕ in the info modal's corner — every sheet closes back to wherever you were */
.info-x { position: absolute; top: 8px; right: 8px; width: 30px; height: 30px; padding: 0; line-height: 1; border-radius: 50%; font-weight: 800; display: grid; place-items: center; z-index: 2; }
/* the new-game setup tabs fit their content on a short landscape screen — a touch more height & tighter
   padding so the 5-rank Difficulty pane never scrolls */
/* the new-game setup spans the FULL screen width (edge to edge), centred vertically */
#info-modal:has(.set-tabs) {
  padding-left: max(16px, env(safe-area-inset-left), env(safe-area-inset-right));
  padding-right: max(16px, env(safe-area-inset-left), env(safe-area-inset-right)); }
#info-card:has(.set-tabs) { width: 100%; max-width: 100%; padding: 14px; border-radius: 12px;
  max-height: min(100%, 94vh); max-height: min(100%, 94dvh); }
#info-card:has(.set-tabs) h3 { margin: 0; }
/* setup header: title on the left, ⚔ Start Age pinned to the right of the same line */
.set-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.set-head #set-start { flex: 0 0 auto; padding: 7px 12px; font-weight: 800; white-space: nowrap; }
/* the two-column creature profile is wider and short enough not to scroll; the compaction keeps all but
   the very heaviest apex creatures at zero overflow, and the scrollbar is hidden so none ever shows */
#info-card:has(.profile-row) { width: min(700px, 100%); scrollbar-width: none;
  max-height: min(100%, 94vh); max-height: min(100%, 94dvh); }
#info-card:has(.profile-row)::-webkit-scrollbar { display: none; }
/* a building's write-up: art LEFT, text RIGHT, so the description never falls below the fold. Same
   widening as the creature profile — the modal earns its horizontal space instead of scrolling. */
#info-card:has(.bld-info-row) { width: min(720px, 100%); max-height: min(100%, 94vh); max-height: min(100%, 94dvh); }
.bld-info-row { display: flex; flex-direction: row; gap: 16px; align-items: flex-start; }
.bld-info-art { flex: 0 0 42%; min-width: 0; }
.bld-info-art .hero-img { width: 100%; height: auto; object-fit: contain;
  max-height: min(58vh, 380px); max-height: min(58dvh, 380px); }
.bld-info-text { flex: 1 1 auto; min-width: 0; }
.bld-info-text p { margin-top: 8px; font-size: 13px; line-height: 1.45; }
/* stacked on a narrow phone, where two columns would squeeze both to nothing */
@media (max-width: 560px) { .bld-info-row { flex-direction: column; } .bld-info-art { flex: 0 0 auto; width: 100%; } }
.info-head { font-size: 18px; font-weight: 800; margin-bottom: 6px; }
#info-body p { margin: 10px 0 0; line-height: 1.5; }
.info-actions { margin-top: 14px; text-align: right; display: flex; gap: 8px; justify-content: flex-end; }
.gameover { text-align: center; }
.gameover .go-title { font-size: 30px; font-weight: 900; letter-spacing: .5px; margin-bottom: 6px; }
.gameover.win .go-title { color: var(--gold); text-shadow: 0 0 16px rgba(255,207,92,.5); }
.gameover.lose .go-title { color: var(--ai); }
.gameover .go-why { color: var(--muted); margin-bottom: 6px; }
.gameover h4 { text-align: left; }
.go-row { display: flex; justify-content: space-between; gap: 12px; padding: 5px 9px; border: 1px solid var(--line); border-radius: 8px; margin: 5px 0; background: #0e1626; }
.gameover .info-actions { justify-content: center; margin-top: 16px; }
.diff-list { display: flex; flex-direction: column; gap: 5px; margin-top: 8px; max-width: 360px; margin-left: auto; margin-right: auto; }
/* single line per rank so all five fit a short screen without scrolling: name fixed, blurb truncates */
.diff-row { text-align: center; display: flex; justify-content: center; align-items: center; gap: 8px; padding: 9px 11px; border: 1px solid var(--line); border-radius: 10px; background: #0e1626; cursor: pointer; }
.diff-row:hover { border-color: var(--gold); }
.diff-row.cur { border-color: var(--gold); box-shadow: inset 0 0 0 1px var(--gold); }
.diff-name { font-size: 14px; }

/* new-game setup: a 3-tab flow (Avatar → Rivals → Difficulty) so each step fits a short screen, no scroll */
.set-tabs { display: flex; gap: 6px; margin: 8px 0; }
.set-tab { flex: 1; display: flex; align-items: center; justify-content: center; gap: 5px; padding: 7px 5px; font-size: 12.5px; font-weight: 700; border: 1px solid var(--line); border-radius: 9px; background: #0e1626; color: var(--muted); cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.set-tab.cur { color: var(--ink); border-color: var(--you); background: var(--panel-2); box-shadow: inset 0 -2px 0 var(--you); }
.set-tab-n { display: inline-grid; place-items: center; width: 16px; height: 16px; border-radius: 50%; background: rgba(150,172,205,.18); font-size: 10px; font-weight: 800; flex: 0 0 auto; }
.set-tab.cur .set-tab-n { background: var(--you); color: #06121f; }
.set-note { margin: 8px 0 0 !important; font-size: 12px; line-height: 1.45; }
/* start settings: avatar gender picker */
.set-sub { font-size: 11px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); font-weight: 800; margin: 14px 0 6px; }
.gender-row { display: flex; gap: 10px; }
.gender-btn { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 10px; border: 2px solid var(--line); border-radius: 10px; background: #0e1626; cursor: pointer; font-weight: 700; }
.gender-btn:hover { border-color: var(--gold); }
.gender-btn.cur { border-color: var(--gold); box-shadow: 0 0 12px rgba(255,207,92,.35); }
/* Avatar picker: ‹ › arrows flank a horizontal scroller of lineage cards — swipe/scroll or tap arrows */
.avatar-picker { display: flex; align-items: stretch; gap: 6px; }
.av-arrow { flex: 0 0 auto; width: 34px; padding: 0; font-size: 22px; font-weight: 800; line-height: 1; border-radius: 9px; color: var(--ink); }
.av-arrow:hover { border-color: var(--gold); }
.avatar-scroll { flex: 1 1 auto; min-width: 0; display: flex; gap: 10px; overflow-x: auto; overflow-y: hidden; padding: 2px 2px 8px; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; }
.avatar-scroll::-webkit-scrollbar { height: 8px; }
.avatar-scroll::-webkit-scrollbar-thumb { background: var(--panel-2); border-radius: 99px; }
.avatar-card { flex: 0 0 auto; scroll-snap-align: start; display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 8px; border: 2px solid var(--line); border-radius: 10px; background: #0e1626; cursor: pointer; font-weight: 700; }
.avatar-card:hover { border-color: var(--gold); }
.avatar-card.cur { border-color: var(--gold); box-shadow: 0 0 12px rgba(255,207,92,.35); }
.avatar-name { font-size: 11px; color: var(--muted); white-space: nowrap; display: flex; align-items: center; gap: 5px; }
.avatar-card.cur .avatar-name { color: var(--ink); }
/* ℹ beside each champion's name — opens the full-screen lineage sheet */
.av-infobtn { width: 16px; height: 16px; border-radius: 50%; display: inline-grid; place-items: center; font-size: 10px; font-weight: 800; font-style: normal; color: var(--muted); border: 1px solid var(--line); background: #0a101d; cursor: pointer; flex: none; }
.av-infobtn:hover { color: var(--gold); border-color: var(--gold); }
.avatar-card .gender-art { width: 116px; height: 150px; }
/* ZOOM to the head & torso: the img box is 2× the card's height, so the visible window shows the upper
   half of the figure at double the detail — full-body art shrunk to card size turned faces to mush */
.av-portrait { position: absolute; top: var(--cy, 0%); left: var(--cx, 50%); width: var(--cw, 235%);
  height: auto; transform: translateX(-50%); display: block; }
/* room to breathe on taller screens: bigger cards, crisper champions */
@media (min-height: 500px) {
  .avatar-card .gender-art { width: 160px; height: 210px; }
  .avatar-name { font-size: 12.5px; }
}
/* SHORT LANDSCAPE. The champion picker needed 378px of stack in a 361px opening, so the card overflowed
   its own padding and `place-items: center` split the difference — the whole panel sat 4px proud of the
   box on every edge and read as shifted. Cap it to the opening and take the 17px back out of the gaps
   between the rows, which nobody misses, rather than out of the portraits, which everybody would. */
/* PIN THE HEADER. When the stack is taller than the opening the card scrolls, and a scrolled card takes
   ⚔️ Start Age up out of reach with it. Head and tabs hold their place; only the pane below them moves. */
#info-card:has(.set-tabs) { display: flex; flex-direction: column; overflow: hidden; }
#info-card:has(.set-tabs) > div { display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto; }
#info-card:has(.set-tabs) .set-head, #info-card:has(.set-tabs) .set-tabs { flex: 0 0 auto; }
#info-card:has(.set-tabs) .set-pane { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
@media (max-height: 460px) {
  #info-card:has(.set-tabs) { padding: 12px 14px; }
  .set-tabs { margin: 5px 0; }
  #info-card:has(.set-tabs) h4 { margin: 6px 0 4px; }
  .avatar-scroll { padding-bottom: 1px; }
  #info-card:has(.set-tabs) .set-q { margin: 2px 0 4px; }
  #info-card:has(.set-tabs) .set-pane > p.muted { margin-top: 4px; }
}
/* Chrome on Android keeps its URL bar even in landscape, leaving ~344px where an iPhone gives 393. The
   portraits shrink again rather than the champions scrolling out of sight — you cannot choose what you
   cannot see. Set at 370 so a 393px phone keeps the larger art it already had room for. */
@media (max-height: 370px) {
  .avatar-card .gender-art { width: 84px; height: 108px; }
  .avatar-name { font-size: 11.5px; }
  #info-card:has(.set-tabs) .set-q { font-size: 12.5px; margin: 1px 0 2px; }
  #info-card:has(.set-tabs) .set-pane > p.muted { font-size: 11px; margin-top: 2px; line-height: 1.35; }
}
/* black backdrop so every avatar portrait reads cleanly (dark art on black; hides any non-transparent edges) */
.gender-art { width: 84px; height: 84px; border-radius: 10px; overflow: hidden; background: #05060a; display: grid; place-items: center; position: relative; }
.gender-art .ic { position: absolute; left: var(--cx, 50%); top: var(--cy, 0%); width: var(--cw, 235%); height: auto; transform: translateX(-50%); }
/* full-screen lineage sheet (ℹ on a champion card): full art on the left, its benefits on the right.
   Sits ABOVE the new-game modal (z 920) and closes back to the picker untouched. */
#av-sheet { position: fixed; inset: 0; z-index: 940; background: rgba(4,7,14,.95); display: flex; gap: 14px;
  padding: max(10px, env(safe-area-inset-top)) max(14px, env(safe-area-inset-right)) 10px max(14px, env(safe-area-inset-left)); }
#av-sheet .avs-art { flex: 1 1 44%; min-width: 0; display: grid; place-items: center; border-radius: 14px; overflow: hidden; }
#av-sheet .avs-art img { max-width: 100%; max-height: 100%; object-fit: contain; filter: drop-shadow(0 6px 18px rgba(0,0,0,.6)); }
#av-sheet .avs-info { flex: 1 1 56%; min-width: 0; overflow-y: auto; padding: 4px 44px 14px 0; }
#av-sheet h2 { margin: 2px 0 6px; font-size: 20px; }
#av-sheet h3 { margin: 12px 0 4px; font-size: 13.5px; text-transform: uppercase; letter-spacing: .4px; color: var(--muted); }
#av-sheet .avs-close { position: absolute; top: 10px; right: 12px; width: 34px; height: 34px; padding: 0; line-height: 1; border-radius: 50%; font-size: 15px; font-weight: 800; display: grid; place-items: center; }
.avs-kin { display: flex; flex-wrap: wrap; gap: 6px; }
.avs-kin .k { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--line); border-radius: 8px; padding: 3px 9px 3px 4px; background: #0e1626; font-weight: 700; font-size: 13px; }
.avs-atk { display: flex; align-items: baseline; gap: 8px; padding: 3.5px 0; border-bottom: 1px dashed rgba(150,172,205,.16); font-size: 13px; }
.avs-atk .chip { font-size: 11px; padding: 1px 7px; }
.avs-atk .lock { margin-left: auto; color: var(--muted); font-size: 11.5px; white-space: nowrap; }
.avs-atk.locked { opacity: .55; }
.avs-facts { display: flex; flex-wrap: wrap; gap: 5px; margin: 4px 0 8px; }
.avs-emoji { font-size: 90px; line-height: 1; }
.link-list { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.link-row { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border: 1px solid var(--line); border-radius: 9px; background: #0e1626; cursor: pointer; }
.link-row input { width: 16px; height: 16px; accent-color: var(--gold); flex: none; }
.link-row .lr-name { flex: 1; font-weight: 600; }
.two { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.tech { margin-bottom: 7px; } .tech-btn { width: 100%; text-align: left; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.tech-btn.owned { border-color: var(--ok); color: var(--ok); } .tech-btn.active { border-color: var(--gold); box-shadow: inset 0 0 0 1px var(--gold); } .tech-btn.locked { opacity: .5; }
.tech .meta { font-size: 11px; color: var(--muted); margin: 2px 2px 0; }
.tech-prog { height: 7px; margin: 4px 2px 0; } .tech-prog > i { background: var(--gold); }
ol { margin: 0 0 10px; padding-left: 20px; } ol li { margin: 5px 0; }

/* Battle modal */
/* ---- quest crawl (2D dungeon overlay over the frozen map) ---- */
/* corner control: a compact expand icon that opens the fullscreen world map (was a 158×132 overview box
   that ate too much of the board — the overview now lives in the fullscreen view) */
#minimap { position: absolute; left: calc(8px + env(safe-area-inset-left)); bottom: calc(8px + env(safe-area-inset-bottom)); width: 40px; height: 40px; z-index: 640; border: 1px solid var(--line); border-radius: 9px; background: rgba(8,14,28,.82); box-shadow: 0 3px 10px rgba(0,0,0,.55); cursor: pointer; color: #cfe3ff; display: grid; place-items: center; }
#minimap:hover { background: rgba(24,40,70,.95); }
#minimap > svg { width: 18px; height: 18px; display: block; }
#minimap-full { position: fixed; inset: 0; z-index: 3000; background: rgba(4,8,16,.93); display: flex; align-items: center; justify-content: center; }
/* the 🔮 scry offer that pops where the overview map was clicked */
.mm-scry { position: absolute; z-index: 3010; padding: 9px 14px; font-weight: 800; font-size: 13px; border-radius: 10px; background: #2b1a4d; border: 1px solid #7a4fd0; color: #e6d9ff; box-shadow: 0 6px 18px rgba(0,0,0,.6); white-space: nowrap; }
.mm-scry:disabled { opacity: .75; }
.mm-scry.hidden { display: none; }
#minimap-full.hidden { display: none; }
.mmf-inner { position: relative; width: min(92vw, 88vh); height: min(92vw, 88vh); background: #070c16; border: 1px solid var(--line); border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,.6); padding: 10px; }
.mmf-inner svg { width: 100%; height: 100%; display: block; }
.mmf-close { position: absolute; top: 10px; right: 10px; width: 34px; height: 34px; border: 1px solid var(--line); border-radius: 8px; background: rgba(8,14,28,.9); color: #cfe3ff; font-size: 18px; line-height: 1; padding: 0; cursor: pointer; z-index: 2; display: grid; place-items: center; }
.mmf-legend { position: absolute; left: 14px; bottom: 12px; display: flex; gap: 12px; font-size: 12px; color: var(--muted); background: rgba(8,14,28,.7); padding: 5px 9px; border-radius: 8px; }
.mmf-legend i { display: inline-block; width: 11px; height: 11px; border-radius: 3px; vertical-align: -1px; margin-right: 4px; }
.mmf-legend i.dot { border-radius: 50%; }
#quest { position: absolute; inset: 0; z-index: 800; display: none; flex-direction: column; background: radial-gradient(120% 90% at 50% 30%, #10152a, #070a12 80%); }
#quest.on { display: flex; }
.qhud { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-bottom: 1px solid var(--line); background: rgba(6,10,20,.7); }
.qhud .qname { font-weight: 800; color: var(--gold); font-size: 14px; }
.qhud .qstat { color: var(--ok); font-size: 13px; font-weight: 700; }
.qhud .qdepth { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.qhud .qbtn { margin-left: auto; font-size: 12px; padding: 5px 11px; }
.qstage { flex: 1; display: grid; place-items: center; overflow: hidden; padding: 8px; }
.qgrid { display: grid; gap: 0; border: 3px solid #05070d; border-radius: 6px; box-shadow: 0 12px 40px rgba(0,0,0,.6); overflow: hidden; }
/* The crawl's D-PAD — four buttons, one square per press. A drag-based stick had to track a moving finger
   across an element the crawl rebuilds under it every step; buttons just work. Hidden until a crawl is on
   (`#quest.on ~ .qpad`), and it lives beside #quest so the rebuild can't tear it out mid-press. */
.qpad { display: none; position: absolute; right: max(16px, env(safe-area-inset-right)); bottom: max(16px, env(safe-area-inset-bottom)); z-index: 810;
  grid-template-columns: repeat(3, 52px); grid-template-rows: repeat(3, 52px); gap: 5px;
  touch-action: none; -webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent; }
#quest.on ~ .qpad { display: grid; }
.qpad-btn { border: 1px solid rgba(120,150,210,.45); border-radius: 11px; color: #dbe6ff; font-size: 20px;
  line-height: 1; display: grid; place-items: center; cursor: pointer; padding: 0; touch-action: none;
  background: linear-gradient(180deg, rgba(46,62,98,.92), rgba(20,30,52,.92));
  box-shadow: 0 2px 8px rgba(0,0,0,.5), inset 0 1px 0 rgba(255,255,255,.10); }
.qpad-btn:active { background: linear-gradient(180deg, #5b7cc4, #3d5794); color: #fff; transform: translateY(1px); }
.qpad-btn.up    { grid-column: 2; grid-row: 1; }
.qpad-btn.left  { grid-column: 1; grid-row: 2; }
.qpad-btn.right { grid-column: 3; grid-row: 2; }
.qpad-btn.down  { grid-column: 2; grid-row: 3; }
/* a short landscape phone gets a slightly tighter pad so it never crowds the dungeon floor */
@media (max-height: 420px) { .qpad { grid-template-columns: repeat(3, 45px); grid-template-rows: repeat(3, 45px);
  right: max(12px, env(safe-area-inset-right)); bottom: max(12px, env(safe-area-inset-bottom)); } }
.qtile { position: relative; display: grid; place-items: center; line-height: 1; }
.qtile.floor { background: #161d2e; box-shadow: inset 0 0 0 1px rgba(255,255,255,.02); }
.qtile.wall { background: linear-gradient(160deg, #333c52, #232a3b); box-shadow: inset 0 0 0 1px #454f68, inset 0 -4px 6px rgba(0,0,0,.4); }
.qtile.door { background: #3b2c15; box-shadow: inset 0 0 0 2px #6b4f24; }
.qtile.exit { background: radial-gradient(circle, #3a2c66, #1a1330); box-shadow: inset 0 0 12px #9b7bff; }
.qtile:not(.wall):hover { outline: 1px solid rgba(255,207,92,.35); outline-offset: -1px; cursor: pointer; }
.qg { filter: drop-shadow(0 2px 2px rgba(0,0,0,.7)); }
.qg.av { filter: drop-shadow(0 0 6px var(--gold)); }
.qg.foe { filter: drop-shadow(0 0 4px #ff6a5a); }
.qg.item { filter: drop-shadow(0 0 5px #7fd0ff); animation: qbob 1.6s ease-in-out infinite; }
.qg.relic { filter: drop-shadow(0 0 8px var(--gold)); animation: qbob 1.6s ease-in-out infinite; }
.qg.npc { filter: drop-shadow(0 0 6px #c79bff); }
/* name tag under a dungeon NPC (the Witch) so it clearly reads as a character */
.qtile .qlabel { position: absolute; left: 50%; bottom: -2px; transform: translateX(-50%); white-space: nowrap; font-size: 9px; line-height: 1; font-weight: 700; color: #d9c7ff; background: rgba(8,10,24,.9); border: 1px solid #4a3a70; border-radius: 5px; padding: 1px 4px; pointer-events: none; z-index: 3; }
@keyframes qbob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-9%); } }
.quest-reward { margin: 4px 0 2px; padding-left: 20px; }
.quest-reward li { font-size: 13px; padding: 2px 0; }
.qhud .qgoal { font-size: 12px; margin-left: 6px; color: var(--muted); }
.qhud .qgoal.have { color: var(--ok); }
.qhud .qloot { font-size: 15px; letter-spacing: 1px; }
.qhp { position: absolute; left: 12%; right: 12%; bottom: 6%; height: 3px; background: #300; border-radius: 2px; overflow: hidden; }
.qhp > i { display: block; height: 100%; background: var(--ai); }
.qg.deco { opacity: .9; filter: none; }
/* the beast's state, worn on its shoulder: 💤 asleep (safe to slip past) · ❗ startled awake (run) */
.qtile .qzz { position: absolute; top: -4%; right: 0; font-size: 55%; line-height: 1; pointer-events: none; z-index: 3; filter: drop-shadow(0 1px 2px rgba(0,0,0,.8)); }
.qhud .qhint { font-size: 12px; color: #c79bff; font-weight: 700; }
.qhud .qghost { font-size: 14px; filter: drop-shadow(0 0 4px rgba(207,227,255,.6)); }
/* quest storytelling: intro/epilogue paragraphs and the two-way choice buttons of an encounter */
.qstory { margin: 10px 0 4px; font-size: 13.5px; line-height: 1.6; color: #cfd9ec; font-style: italic; }
.q-choices { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.q-choices button { text-align: left; display: flex; flex-direction: column; gap: 3px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px; background: #0e1626; }
.q-choices button:hover { border-color: var(--gold); }
.q-choices button small { font-size: 11.5px; }
/* forest theme — mossy glade under a leafy canopy */
#quest.theme-forest { background: radial-gradient(120% 90% at 50% 30%, #16301f, #081409 80%); }
#quest.theme-forest .qtile.floor { background: #16331f; box-shadow: inset 0 0 0 1px rgba(120,200,120,.04); }
#quest.theme-forest .qtile.wall { background: linear-gradient(160deg, #244a2a, #16311b); box-shadow: inset 0 0 0 1px #2f5c34, inset 0 -4px 6px rgba(0,0,0,.4); }
#quest.theme-forest .qtile.door { background: #3a2c15; box-shadow: inset 0 0 0 2px #5a7a3a; }
#quest.theme-forest .qtile.exit { background: radial-gradient(circle, #2c4a1e, #10240f); box-shadow: inset 0 0 12px #9be27b; }
#quest.theme-forest .qhud .qname { color: #a7e08a; }

/* full-screen battle: the defender's hex is the battlefield; the two sides face off across it */
#battle { position: fixed; inset: 0; z-index: 900; }
#battle.hidden { display: none; }
#battle-card { position: absolute; inset: 0; width: 100%; height: 100%; max-height: none; border: 0; border-radius: 0; box-shadow: none; padding: 0; background: #05070d; overflow: hidden; }
#battle-body { position: absolute; inset: 0; display: flex; flex-direction: column; overflow: hidden; }
.bt-bg { position: absolute; inset: 0; background-size: cover; background-position: center; opacity: .5; filter: saturate(.9); z-index: 0; pointer-events: none; }
#battle-body::after { content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none; background: radial-gradient(130% 80% at 50% 42%, transparent 25%, rgba(4,8,16,.78)); }
#battle-body > *:not(.bt-bg) { position: relative; z-index: 1; }
.bt-head { font-weight: 800; font-size: 18px; text-align: center; text-shadow: 0 2px 4px #000;
  padding: max(14px, env(safe-area-inset-top)) max(12px, env(safe-area-inset-right)) 6px max(12px, env(safe-area-inset-left)); }
/* the battlefield fills the middle; the two sides are pushed to the left & right edges, facing each other */
.bt-sides { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding-left: max(clamp(10px, 4vw, 60px), env(safe-area-inset-left));
  padding-right: max(clamp(10px, 4vw, 60px), env(safe-area-inset-right)); }
.bt-vs { font-size: clamp(22px, 4vw, 40px); opacity: .55; text-shadow: 0 2px 6px #000; }
.bt-side { display: flex; flex-direction: column; gap: 4px; align-items: center; max-height: 100%; overflow: auto; }
.bt-label { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; margin-bottom: 6px; text-align: center; font-weight: 800; text-shadow: 0 1px 2px #000; }
.bt-label.you { color: var(--you); } .bt-label.enemy { color: var(--ai); }
/* combatants stack in rows of 4, filling downward */
.bt-col { display: grid; grid-template-columns: repeat(4, auto); gap: clamp(6px, 1.2vw, 12px); justify-content: center; align-content: center; }
.combatant { display: flex; flex-direction: column; align-items: center; gap: 3px; width: clamp(72px, 8.5vw, 104px); background: rgba(10,16,30,.55); border: 2px solid rgba(120,140,175,.4); border-radius: 12px; padding: 6px 5px; cursor: pointer; position: relative; backdrop-filter: blur(1px); }
.combatant.sel { border-color: var(--gold); box-shadow: 0 0 18px rgba(255,207,92,.5); }
.combatant.tgt { border-color: var(--ai); box-shadow: 0 0 18px rgba(255,93,93,.5); }
.combatant .cg { position: relative; width: clamp(56px, 6.5vw, 84px); height: clamp(56px, 6.5vw, 84px); display: grid; place-items: center; }
/* TAKE THE PLATE OUT OF GRID FLOW. `.cg` is a grid whose single row is auto-sized, so a percentage height
   or max-height on the image resolves against the ROW — and the row then sizes to the image. That circle
   let a tall plate grow its own row and hang 42px below the box, printing the creature over its own name:
   every card with a standing figure (Goblin, Centaur, Troll) had its name buried, while wide ones (Wolf,
   Ogre) looked fine. Absolute + inset:0 against the already-relative `.cg` gives a definite box on both
   axes that no intrinsic size can argue with; `contain` keeps the aspect.
   NOT overflow:hidden on .cg — the floating damage numbers (.cfx) are its children and rise out of it. */
.combatant .cg .ic { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; filter: drop-shadow(0 3px 5px rgba(0,0,0,.7)); }
.bt-side.enemy .combatant .cg .ic { transform: scaleX(-1); }   /* enemies face back toward your side */
.combatant .cn { font-size: 12px; font-weight: 700; text-align: center; line-height: 1.15; text-shadow: 0 1px 2px #000; }
.combatant .bar { width: 100%; height: 8px; }
.combatant .bar > i.low { background: var(--ai); }
.combatant .chp { font-size: 11px; font-weight: 700; color: #cfe3ff; text-shadow: 0 1px 2px #000; }
.combatant .c-cnt { position: absolute; bottom: -2px; left: -4px; background: #0e1626; color: #fff; border: 1px solid var(--line); border-radius: 8px; font-size: 11px; font-weight: 800; padding: 0 4px; line-height: 15px; }
.combatant.shielded { opacity: .6; cursor: not-allowed; }
.combatant .cshield { position: absolute; top: -4px; right: -4px; font-size: 13px; filter: drop-shadow(0 0 2px #7fd0ff); }
/* floating combat numbers */
.cfx { position: absolute; left: 50%; top: 22%; transform: translateX(-50%); font-weight: 900; font-size: 26px; pointer-events: none; z-index: 6; text-shadow: 0 2px 5px #000, 0 0 3px #000; animation: cfxFloat 1.3s ease-out forwards; }
.cfx-dmg { color: #ff6b6b; } .cfx-heal { color: #6fe08a; }
.cfx-xp { color: var(--gold); font-size: 17px; animation: cfxFloat 1.3s ease-out .55s both; }
/* A wordy fx cannot wear the numbers' 26px: '✊ resisted' runs ~105px against a 72px card, and .bt-side's
   scroll box hugs the card, so the fist and the first letters were cut clean off at the border. Words get
   a compact pill capped to the card's width — short ones ('✊ held') hug their text, long ones wrap under
   the emoji — and the pill keeps them legible over the battlefield art at the smaller size. */
.cfx-word { font-size: 12px; line-height: 1.2; letter-spacing: -.1px; text-align: center;
  width: max-content; max-width: clamp(62px, 7.4vw, 92px); padding: 2px 5px; border-radius: 8px;
  background: rgba(6,10,20,.88); border: 1px solid rgba(120,140,175,.45);
  text-shadow: 0 1px 2px #000; box-shadow: 0 2px 6px rgba(0,0,0,.55); }
@keyframes cfxFloat { 0%{opacity:0; transform:translateX(-50%) translateY(8px) scale(.7);} 18%{opacity:1; transform:translateX(-50%) translateY(0) scale(1.15);} 68%{opacity:1;} 100%{opacity:0; transform:translateX(-50%) translateY(-30px) scale(1);} }
/* bottom control bar */
.bt-controls { background: rgba(6,10,20,.82); border-top: 1px solid var(--line);
  padding: 8px max(12px, env(safe-area-inset-right)) max(12px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left)); }
.bt-bar-cap { text-align: center; font-size: 11px; margin-bottom: 6px; }
.bt-moves { display: flex; flex-wrap: wrap; gap: 8px; align-items: stretch; justify-content: center; }
.bt-acting { width: 100%; text-align: center; margin-bottom: 6px; font-size: 13px; }
.bt-acting .pbust { width: 1.5em; height: 1.5em; }
/* THE COMPANY'S ELEMENTS — one card per creature type in a mixed formation, stacked above the attacks.
   Tapping one narrows the move list to that element's own attacks, struck at that element's strength. */
.bt-elements { display: flex; gap: 6px; justify-content: center; flex-wrap: wrap; margin-bottom: 7px; }
.el-card { display: flex; flex-direction: column; align-items: center; gap: 1px; min-width: 78px;
  background: rgba(14,22,38,.86); border: 2px solid rgba(120,140,175,.4); border-radius: 10px;
  padding: 4px 8px; font-weight: 700; cursor: pointer; line-height: 1.15; }
.el-card:hover { border-color: #3d4a66; }
.el-card.sel { border-color: var(--gold); box-shadow: 0 0 12px rgba(255,207,92,.45); background: rgba(38,30,10,.9); }
.el-card .el-ic { font-size: 15px; }
.el-card .el-ic .pbust { width: 1.5em; height: 1.5em; }
.el-card .el-n { font-size: 11.5px; }
.el-card .el-s { font-size: 10px; color: var(--muted); font-weight: 600; }
.move-btn { display: flex; flex-direction: column; align-items: center; gap: 1px; background: #25324f; padding: 9px 14px; font-weight: 700; }
.move-btn.heal { background: #1f4636; } .move-btn small { color: var(--muted); font-weight: 600; }
.move-btn.locked { opacity: .45; cursor: default; }   /* an Avatar attack its Level hasn't unlocked yet */
.move-btn.sway { background: #4a2f5e; } .move-btn.sway.locked { opacity: .45; }
.bt-actions { margin-top: 10px; display: flex; gap: 8px; justify-content: center; }
/* PHONE LANDSCAPE: the control deck was eating 181px of a 393px screen — 46%, half the battle — while the
   fighters it belongs to were squeezed into what was left. Everything here trims the CHROME and leaves
   the tap targets usable: the standing hint goes (it's a tutorial line you read once), the acting unit
   folds onto one row, and the move buttons lose their padding rather than their labels. */
@media (max-height: 460px) {
  .bt-controls { padding: 4px 10px 6px; }
  .bt-bar-cap { display: none; }
  .bt-acting { margin-bottom: 3px; font-size: 12px; }
  .bt-moves { gap: 5px; }
  .move-btn { padding: 4px 10px; font-size: 12px; line-height: 1.15; }
  .move-btn small { font-size: 9px; }
  .bt-actions { margin-top: 5px; gap: 6px; }
  .bt-actions button, .bt-noflee { padding: 5px 10px; font-size: 11px; }
}
.bt-decided { text-align: center; font-weight: 800; font-size: 18px; padding: 4px 0; letter-spacing: .5px; }
.bt-decided.won { color: var(--gold); } .bt-decided.lost { color: #ff6f5e; } .bt-decided.fled { color: #9fb3d8; }
/* once decided, centre the banner + the fail-safe Return-to-map button together */
.bt-controls:has(.bt-decided) { display: flex; flex-direction: column; align-items: center; gap: 8px; }
#bt-continue { min-width: 190px; }
/* creature/unit profile modal */
.prof-link { color: var(--gold); text-decoration: underline dotted; text-underline-offset: 2px; cursor: pointer; }
.prof-link:hover { text-decoration: underline; }
.profile { display: flex; flex-direction: column; gap: 12px; align-items: stretch; margin-bottom: 6px; }
/* creature profile: text column on the LEFT, art on the RIGHT, sized to fit without scrolling */
.profile-row { flex-direction: row; gap: 14px; align-items: stretch; margin-bottom: 0; }
.prof-info { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; }
.prof-info .info-actions { margin-top: auto; padding-top: 10px; }   /* pin the buttons to the bottom of the column */
.profile-row .prof-art { flex: 0 0 33%; width: auto; min-height: 0; max-height: none; align-self: stretch; }
.profile-row .prof-art .hero-img { max-height: min(62vh, 420px); max-height: min(62dvh, 420px); }
/* the info column is compact so even a fuse/apex creature's long requirement list fits without scrolling */
.profile-row .prof-name { font-size: 17px; }
.profile-row .prof-tags { margin: 2px 0; }
.profile-row .prof-stats { font-size: 12px; }
.profile-row .prof-specials { font-size: 11px; line-height: 1.25; margin-top: 3px; }
.profile-row .prof-sub { margin: 6px 0 3px; }
.profile-row .prof-req { line-height: 1.3; }
.profile-row .prof-cost { font-size: 12px; }
#info-card:has(.profile-row) { padding: 14px; }
.profile-row .prof-atks { gap: 5px; }
.profile-row .prof-atk { padding: 4px 7px; font-size: 12px; }
.profile-row .prof-reqs { display: flex; flex-wrap: wrap; gap: 0 12px; }   /* requirements flow in two columns */
.profile-row .prof-reqs .prof-req { flex: 0 0 calc(50% - 6px); font-size: 12px; padding: 1px 0; }
.profile-row .prof-cost { margin-top: 4px; }
/* ART SHEETS GO FULL SCREEN. These two windows exist to show a plate, and both were boxing it: the card
   capped at ~700px wide with the image held to 380-420px tall, so on any real screen the artwork was a
   fraction of a dimmed page. Give them the whole glass and let the picture take whatever is left after
   the text — the plates are 1024px and were being shown at a third of that.
   Only these two: every other sheet (confirmations, the echelon manager, game over) still wants to be a
   small card floating over the board. */
#info-modal:has(.bld-info-row), #info-modal:has(.profile-row) {
  padding: max(10px, env(safe-area-inset-top)) max(10px, env(safe-area-inset-right))
           max(10px, env(safe-area-inset-bottom)) max(10px, env(safe-area-inset-left)); }
#info-card:has(.bld-info-row), #info-card:has(.profile-row) {
  width: 100%; max-width: 100%; height: 100%; max-height: 100%; border-radius: 12px;
  display: flex; flex-direction: column; overflow: hidden; }
#info-card:has(.bld-info-row) > #info-body,
#info-card:has(.profile-row) > #info-body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
/* the two-column row fills the card, and each column scrolls on its own if it must */
#info-body > .bld-info-row, #info-body > .profile-row { flex: 1 1 auto; min-height: 0; align-items: stretch; }
.bld-info-row > .bld-info-text, .profile-row > .prof-info { overflow-y: auto; min-height: 0; }

/* the picture takes the height the text does not need, instead of a fixed cap */
/* max-*, never width:100%. Pinning the width makes a landscape plate stop at the column's width and
   leave the whole vertical half of the sheet empty; letting BOTH axes cap lets whichever one binds do
   the work, so a wide building fills across and a tall creature fills down. */
/* The TEXT takes a readable measure and no more; the picture takes everything left. A 50/50 split gave
   the prose a 560px line — wider than is comfortable to read — while holding a 1024px plate below its
   own native size. Neither is ever upscaled past 1:1 (see max-width above), so this simply stops the
   layout from shrinking art it has the room for. */
#info-card:has(.bld-info-row) .bld-info-art { flex: 1 1 auto; display: flex; min-height: 0; min-width: 0; }
#info-card:has(.bld-info-row) .bld-info-text { flex: 0 1 min(42%, 460px); min-width: 0; }
#info-card:has(.profile-row) .prof-art { flex: 1 1 auto; min-height: 0; max-height: none; min-width: 0; }
#info-card:has(.profile-row) .prof-info { flex: 0 1 min(52%, 560px); }
/* TAKE THE PLATE OUT OF GRID FLOW — the same trap as the battle roster. `.prof-art` is a grid whose row
   auto-sizes to its content, so a percentage max-height on the image resolves against a track that is
   itself sized by the image: it never binds, the picture overflows, and `overflow: hidden` shears the top
   off. Measured at 903x460: a 499x748 image inside a 515x410 box, 338px of Treant gone. Absolute against
   a definite box cannot be argued with, and `contain` keeps the aspect. */
#info-card:has(.bld-info-row) .bld-info-art,
#info-card:has(.profile-row) .prof-art { position: relative; }
#info-card:has(.bld-info-row) .bld-info-art .hero-img,
#info-card:has(.profile-row) .prof-art .hero-img {
  position: absolute; inset: 8px; width: auto; height: auto;
  max-width: calc(100% - 16px); max-height: calc(100% - 16px);
  margin: auto; object-fit: contain; }
/* a phone stacks them, and the art still gets the lion's share */
@media (max-width: 560px) {
  #info-card:has(.bld-info-row) .bld-info-art,
  #info-card:has(.profile-row) .prof-art { flex: 1 1 auto; width: 100%; }
}

/* full uncropped hero image, shared by creature profiles and building info windows */
.prof-art, .info-hero { width: 100%; min-height: 180px; max-height: 340px; border-radius: 12px; overflow: hidden; background: rgba(150,172,205,.14); box-shadow: inset 0 0 0 1px rgba(255,255,255,.08); display: grid; place-items: center; padding: 8px; }
.info-hero { margin-bottom: 10px; }
.prof-art .hero-img, .info-hero .hero-img { max-width: 100%; max-height: 324px; width: auto; height: auto; object-fit: contain; display: block; }
.hero-fallback { font-size: 96px; line-height: 1; }
.prof-name { font-size: 20px; font-weight: 800; }

/* wanderer full-artwork viewer — resizable via the slider / −/+ buttons */
#info-card:has(.artview) { width: min(760px, 100%); }
.artview { display: flex; flex-direction: column; align-items: center; gap: 12px; text-align: center; }
.artview-name { font-size: 19px; font-weight: 700; display: inline-flex; align-items: center; gap: 8px; }
.artview-stage { width: 100%; display: grid; place-items: center; min-height: 120px; }
.artview-stage .hero-img { max-height: var(--art-h, 74vh); max-width: 100%; width: auto; height: auto; object-fit: contain; display: block; border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,.5); transition: max-height .12s ease; }
.artview-stage .hero-fallback { font-size: 120px; line-height: 1; }
.artview-desc { max-width: 56ch; font-size: 13.5px; line-height: 1.55; }
.artview-size { display: flex; align-items: center; gap: 10px; width: 100%; max-width: 460px; }
.artview-size input[type="range"] { flex: 1; accent-color: var(--gold); }
.artview-size button { white-space: nowrap; padding: 5px 12px; }
.npc-desc { margin: 7px 0 3px; font-size: 12px; line-height: 1.5; }
.npc-art-btn { margin-top: 9px; width: 100%; padding: 6px 10px; }
.prof-tags { margin: 4px 0; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.prof-stats { font-size: 13px; font-weight: 600; }
.prof-specials { margin-top: 6px; font-size: 12px; color: #bcd0ea; line-height: 1.5; }
.prof-sub { font-size: 11px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); font-weight: 800; margin: 12px 0 5px; }
.prof-atks { display: flex; flex-wrap: wrap; gap: 6px; }
.prof-atk { background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px; padding: 5px 9px; font-size: 13px; }
.prof-atk small { color: var(--muted); }
.prof-req { font-size: 13px; padding: 2px 0; }
.prof-req.ok { color: var(--ok); } .prof-req.no { color: #ff9166; }
.prof-cost { margin-top: 6px; font-size: 13px; }
.battle-result { text-align: center; margin-top: 8px; font-size: 14px; }
.battle-result .pbust { width: 1.6em; height: 1.6em; }

/* Hex info popup (dark themed) */
.leaflet-popup-content-wrapper { background: var(--panel); color: var(--ink); border: 1px solid var(--line); border-radius: 10px; box-shadow: var(--shadow); }
.leaflet-popup-tip { background: var(--panel); border: 1px solid var(--line); }
.leaflet-popup-content { margin: 9px 12px; font: 13px/1.45 inherit; }
.pu { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 700; padding: 1px 6px 1px 2px; margin: 2px 2px 0 0; border-radius: 6px; background: var(--panel-2); border: 1px solid var(--line); }
.pu .pbust { width: 1.7em; height: 1.7em; border-radius: 4px; }
.pu-you { color: var(--you); } .pu-ai { color: var(--ai); } .pu-barb { color: #d98a3a; } .pu-neu { color: #aeb6c6; } .pu-wild { color: #c79bff; }
/* press-and-hold hex peek — a glance panel above the action bar, shown only while a hex is held */
#hex-peek { position: fixed; left: 50%; transform: translateX(-50%); bottom: 16px; z-index: 690;
  max-width: min(92vw, 460px); background: rgba(12,19,34,.95); border: 1px solid var(--line);
  border-radius: 12px; box-shadow: var(--shadow); padding: 8px 12px; font-size: 13px; line-height: 1.5;
  pointer-events: none; opacity: 0; display: none; backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); }
#hex-peek.show { display: block; opacity: 1; }
/* Leaflet's stock ✕ is a 24×14 hairline flush to the panel's corner — under half the 44px a thumb needs,
   and it sits ON the content box, so a long name runs beneath it. Make it a real round control, inset
   from the corner, with an invisible 44px pad around it so the tap target is honest at any size. */
.leaflet-container a.leaflet-popup-close-button {
  position: absolute; top: 6px; right: 6px; width: 26px; height: 26px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  font: 700 16px/1 inherit; color: var(--muted); text-indent: 0;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 50%;
}
.leaflet-container a.leaflet-popup-close-button::after {   /* the honest tap target, unseen */
  content: ''; position: absolute; top: 50%; left: 50%; width: 44px; height: 44px;
  transform: translate(-50%,-50%);
}
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus-visible { color: var(--ink); background: var(--panel); border-color: var(--gold); }
/* no transform on :active — the tilt correction above owns this element's transform */
.leaflet-container a.leaflet-popup-close-button:active { background: var(--gold); color: #1a0b04; }
/* keep the text clear of it — the close control now occupies the top-right corner */
.leaflet-popup-content { margin-right: 38px; }

/* Trade / market */
.trade-row .t { font-weight: 700; }
.trade-row button { padding: 5px 9px; font-size: 12px; }
/* next-turn income beside each stock figure — same green/orange reading as the top-bar chips, but the
   "have 500" text is already .muted, so restate the colour to lift the number back out of it */
.trade-row .gain, .city-head .gain { font-weight: 700; color: #7fd08a; opacity: .95; white-space: nowrap; }
.trade-row .gain.loss, .city-head .gain.loss { color: #ff9166; }
/* what a STANDING ORDER moves each turn — deliberately not the same colour as income, because it is a
   thing you set rather than a thing you earn. Incoming reads cyan, outgoing amber. */
.auto-flow { font-weight: 700; font-size: .82em; color: #7fd0ff; white-space: nowrap; margin-left: 3px; }
.auto-flow.out { color: #ffc46b; }
/* the wanderer-approach modal: its three answers stacked, each wide enough to read its terms */
.wc-acts { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.wc-acts button { width: 100%; text-align: left; padding: 10px 12px; font-weight: 700; }
/* THE ECHELON KEY — each formation's own insignia beside the count it starts at. This is a legend, not
   an explanation, so it stays on the page while the rules move behind the ⓘ. */
.ech-key { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 8px; }
.ech-key-item { display: inline-flex; align-items: baseline; gap: 5px; font-size: 11.5px; font-weight: 700;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 7px; padding: 2px 8px; white-space: nowrap; }
.ech-key-item .ech { font-family: ui-monospace, Menlo, monospace; color: var(--gold); letter-spacing: .5px; }
.ech-key-wide { margin-top: 10px; }
.city-head .gain { font-size: .78em; margin-left: 2px; }

/* Toast */
#toast { position: fixed; left: 50%; top: max(10px, env(safe-area-inset-top)); bottom: auto; transform: translateX(-50%) translateY(-6px); background: #0a1120; border: 1px solid var(--line); color: var(--ink); padding: 9px 14px; border-radius: 10px; box-shadow: var(--shadow); z-index: 950; opacity: 0; transition: opacity .2s, transform .2s; pointer-events: none; max-width: 84vw; text-align: center; }
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* Landscape lock — a rotate prompt shown only when a phone is held in portrait */
#rotate-gate { display: none; }
.rg-inner { text-align: center; padding: 24px; max-width: 340px; }
.rg-icon { font-size: 60px; display: inline-block; animation: rg-rot 2.4s ease-in-out infinite; }
.rg-inner h2 { margin: 14px 0 6px; font-size: 20px; }
@keyframes rg-rot { 0%, 55%, 100% { transform: rotate(0); } 25%, 45% { transform: rotate(90deg); } }
@media (orientation: portrait) and (max-width: 600px) and (pointer: coarse) {
  #rotate-gate { display: grid; position: fixed; inset: 0; z-index: 1000; place-items: center;
    background: radial-gradient(120% 120% at 50% 0%, #12203a, var(--bg)); color: var(--ink);
    padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom); }
}

/* narrow screens: compact the resource chips so all of them fit in 1–2 rows without scrolling */
@media (max-width: 480px) {
  #res { gap: 4px; }
  #res .r { padding: 2px 5px; font-size: 12px; border-radius: 6px; }
  .hud-row.top .brand { font-size: 15px; }
}

/* short PORTRAIT phones (small devices / split-screen): trim the panel so the map keeps room */
@media (orientation: portrait) and (max-height: 620px) {
  #panel { max-height: 40vh; max-height: 40dvh; }
  #hud { padding-top: max(4px, env(safe-area-inset-top)); padding-bottom: 3px; }
  .hud-row { padding: 1px 0; }
  #wyrm { font-size: 11px; }
}


/* ================= DESKTOP layout =================
   Phones (the iPhone-15-landscape target) keep the full-screen overlay views above. On a big screen the
   map stays visible and an open view DOCKS as a right-hand side panel instead — no more whole-screen
   takeover — and the new-game setup becomes a centred card rather than spanning edge to edge. */
@media (min-width: 1024px) {
  .um-drop { bottom: auto; top: calc(100% + 8px); }   /* the bar lives top-right on desktop → picker drops DOWN */
  #app.view-open { grid-template-columns: minmax(0, 1fr) clamp(380px, 32vw, 520px); }
  #app.view-open #hud { right: clamp(380px, 32vw, 520px); }   /* chips float over the MAP column only — the docked view keeps its ✕ clear */
  #app.view-open #map { grid-column: 1; }
  #app.view-open #panel { grid-column: 2; border-top: none; border-left: 1px solid var(--line); }
  #app.view-open ~ #toast { left: calc((100% - clamp(380px, 32vw, 520px)) / 2); }   /* centre over the map column */
  .tab-pane.active { max-width: none; }          /* the docked panel is already a comfortable column */
  #info-modal:has(.set-tabs) { padding-left: 16px; padding-right: 16px; }
  #info-card:has(.set-tabs) { width: min(1080px, 100%); max-width: 100%; border-radius: 14px; }
  /* desktop champions are BIG — a portrait gallery, not thumbnails */
  .avatar-card .gender-art { width: 200px; height: 264px; }
  .avatar-name { font-size: 13px; }
  /* the selected-unit bar rides TOP-RIGHT on desktop (bottom-right is the phone's thumb zone) —
     bigger, and each action wears its WORD beside the icon */
  #btn-end .et-label { display: inline; }
  #btn-end .et-ic { width: 44px; height: 44px; }
  #unit-menu { bottom: auto; top: calc(var(--hud-h, 56px) + 8px); padding: 10px 12px; gap: 8px; flex-direction: column; align-items: stretch; }
  .um-info { display: flex; align-items: center; gap: 10px; }
  .um-acts { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
  button.um-x, button.um-i { padding: 4px 9px; }
  .um-title { font-size: 16px; }
  .um-sub { font-size: 12.5px; }
  .um-hp .chip { font-size: 14px; padding: 5px 10px; }
  .um-acts button { font-size: 15px; padding: 10px 14px; }
  .um-acts .um-label { margin-left: 7px; font-weight: 700; }
}
/* phone: the action buttons stay icon-only and compact */
@media (max-width: 1023.9px) {
  .um-acts .um-label { display: none; }
}
/* …until 🏷 is held down — then every action button wears its word (a peek at what each does).
   Higher specificity than the phone rule above, so it wins while the class is on. */
#app.show-btn-labels .um-acts .um-label { display: inline; margin-left: 5px; font-weight: 700; }
#app.show-btn-labels #unit-menu { max-width: calc(100vw - 16px - env(safe-area-inset-left) - env(safe-area-inset-right)); }
/* a pane's QUESTION — what this chooser is asking, unmissable even on a very wide screen */
.set-q { font-size: 15px; font-weight: 800; margin: 2px 0 8px; }
/* per-lineage framing dials: minotaur-m's art fills its frame edge-to-edge, so the shared 2× head-zoom
   made him tower over every other champion — zoom him out to parity */
/* …and each art carries different headroom above the head, so per-slug lifts put every champion's head
   almost at the top of its portrait (offsets measured from each file's solid-content top × the 235% zoom) */
.ai-btn small { font-size: 11px; font-weight: 600; color: var(--muted); }

/* ================= title / cover screen ================= */
#title-screen { position: fixed; inset: 0; z-index: 4000; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 30px; overflow: hidden;
  background: radial-gradient(120% 90% at 50% 22%, #241033, #08060f 76%); }
#title-screen.hidden { display: none; }
#title-screen.leaving { animation: tsFade .42s ease forwards; }
@keyframes tsFade { to { opacity: 0; visibility: hidden; } }

/* blurred, dimmed copy of the cover fills the whole screen so there are no letterbox bars … */
.ts-cover { position: absolute; inset: 0; z-index: 0;
  background: url('assets/cover.webp') center/cover no-repeat;
  filter: blur(26px) brightness(.5) saturate(1.1); transform: scale(1.15); }
/* … while the SHARP, WHOLE cover sits centered on top (any aspect ratio shows in full), inset a little
   from the screen edges so it reads as a framed cover with breathing room around it. */
.ts-art { position: absolute; inset: 6vh 5vw; z-index: 1; pointer-events: none;
  background: url('assets/cover.webp') center/contain no-repeat;
  filter: drop-shadow(0 10px 40px rgba(0,0,0,.6)); }
.ts-vignette { position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background:
    radial-gradient(125% 85% at 50% 40%, transparent 30%, rgba(6,4,12,.35) 74%, rgba(6,4,12,.85) 100%),
    linear-gradient(180deg, rgba(6,4,12,.4), transparent 26%, transparent 58%, rgba(6,4,12,.8)); }

.ts-wordmark { position: relative; z-index: 3; width: min(760px, 92vw); transform-origin: center 42%;
  margin-top: -30px;   /* nudged 30px higher than centre */
  animation: tsDrop 1.7s cubic-bezier(.2,.9,.25,1) forwards; }
@keyframes tsDrop {
  0%   { transform: translateY(-170%) scale(1); opacity: 0; }
  8%   { opacity: 1; }
  52%  { transform: translateY(0) scale(1); }
  64%  { transform: translateY(0) scale(1.05); }   /* small settle bounce */
  100% { transform: translateY(0) scale(.9); }      /* … then shrink 10% */
}
.ts-svg { width: 100%; height: auto; overflow: visible; animation: tsFlicker 2.6s ease-in-out infinite; }
@keyframes tsFlicker {
  0%,100% { filter: drop-shadow(0 0 10px rgba(255,140,30,.85)) drop-shadow(0 0 24px rgba(150,45,224,.7)); }
  50%     { filter: drop-shadow(0 0 18px rgba(255,175,55,1))   drop-shadow(0 0 34px rgba(175,70,240,.9)); }
}
.ts-text { font-family: 'Metamorphous','Cinzel Decorative',Georgia,serif; font-size: 120px; font-weight: 700;
  letter-spacing: 3px; paint-order: stroke fill; stroke: rgba(30,6,48,.7); stroke-width: 2px; }

.ts-actions { position: absolute; z-index: 3; left: 0; right: 0; bottom: 5vh; display: flex; justify-content: center; gap: 14px; flex-wrap: wrap;
  opacity: 0; animation: tsRise .8s ease 1.55s forwards; }
/* 📂 Continue: the cool-ember twin of Begin — a saved Age resumes right from the cover */
.ts-begin.ts-load { color: #eee6ff; background: linear-gradient(180deg,#4a3a7e,#2c1f55); border-color: #8b76d8;
  box-shadow: 0 6px 26px rgba(120,80,220,.45), inset 0 0 0 1px rgba(0,0,0,.3); }
.ts-begin.ts-load:hover { box-shadow: 0 10px 34px rgba(140,95,240,.65); }
.ts-begin.ts-load:disabled { opacity: .4; filter: grayscale(.7); cursor: default; transform: none; box-shadow: none; }
@keyframes tsRise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
/* The install offer sits on its own row under the two cover buttons — present on the first screen, but
   quiet enough that it never competes with ⚔️ Begin. Vanishes once the game is running installed. */
.ts-install { flex: 0 0 100%; display: flex; justify-content: center; align-items: center; gap: 9px; flex-wrap: wrap; }
.ts-install:empty { display: none; }
.ts-inst { font-family: inherit; font-size: 13px; font-weight: 700; letter-spacing: .3px; cursor: pointer;
  padding: 7px 15px; border-radius: 999px; color: #ffe9c4; background: rgba(12,18,34,.72);
  border: 1px solid rgba(255,190,110,.55); box-shadow: 0 3px 14px rgba(0,0,0,.5); }
.ts-inst:hover { background: rgba(28,40,70,.85); border-color: rgba(255,210,140,.85); }
.ts-inst.ghost-inst { color: #d8e4ff; border-color: rgba(150,175,225,.45); }
.ts-inst-note { font-size: 12px; color: rgba(226,235,255,.72); text-shadow: 0 1px 3px #000; }
.inst-steps { margin: 6px 0 12px; padding-left: 22px; line-height: 1.6; font-size: 14px; }
.inst-steps li { margin: 4px 0; }
.inst-key { display: inline-block; padding: 0 4px; }
.inst-warn { margin: 8px 0; padding: 8px 10px; border-radius: 8px; font-size: 13px; line-height: 1.5;
  background: rgba(120,70,20,.35); border: 1px solid rgba(255,180,90,.45); color: #ffe0b8; }
.inst-close { display: flex; justify-content: flex-end; margin-top: 4px; }
/* a short landscape phone has no room for a third row of chrome — the note goes, the button stays */
@media (max-height: 460px) { .ts-install { gap: 6px; } .ts-inst-note { display: none; }
  .ts-inst { font-size: 12px; padding: 5px 12px; } }
.ts-begin { font-family: 'Metamorphous','Cinzel Decorative',Georgia,serif; font-size: 19px; letter-spacing: 1px;
  font-weight: 700; padding: 13px 30px; color: #1a0b04; cursor: pointer;
  background: linear-gradient(180deg,#ffca57,#ff8a1e); border: 1px solid #ffd88a; border-radius: 12px;
  box-shadow: 0 6px 26px rgba(255,120,30,.5), inset 0 0 0 1px rgba(0,0,0,.3);
  transition: transform .12s ease, box-shadow .2s ease; }
.ts-begin:hover { transform: translateY(-2px); box-shadow: 0 10px 34px rgba(255,140,40,.7); }
.ts-begin:active { transform: translateY(0); }

@media (max-width: 560px) { .ts-text { font-size: 92px; } .ts-wordmark { width: 94vw; } }

/* start-screen rival count — how many AI Wardens share the board */
.ai-row { display: flex; flex-direction: column; gap: 5px; margin: 4px auto 2px; max-width: 360px; }
.ai-btn { display: flex; align-items: center; justify-content: center; gap: 7px; padding: 9px 0; font-weight: 800; font-variant-numeric: tabular-nums; }
.ai-btn.cur { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold) inset; color: var(--gold); }
