/* game.css — the shell styles for the Tata and Isaac client.
   OWNS: the design tokens, the chunky Mini War button/panel look, the login
   screen, the WHILE YOU WERE AWAY panel, the HUD, the top bar, the VISIT/SELL
   sheets, toasts and floats.
   Must NOT own: markup (that stays in 3d.html), the BUY/TRADE panels
   (css/panels.css, Agent C), or any inline style set from JS.

   Binding constraints — docs/13 §7 + docs/11 §6b:
     border 3px #14202E · radius 16 (buttons) / 20 (panels)
     depth 0 6px 0 rgba(0,0,0,.30) · gloss inset 0 3px 0 rgba(255,255,255,.35)
     press translateY(4px) · font weight 900
     >=18px body, >=24px buttons, touch targets >=56px,
     nothing interactive within 40px of a screen edge. */

:root {
  --ink: #14202E;
  --gold: #FFDF70; --gold-d: #F5B723;
  --green: #8BE86B; --green-d: #3FBF3F;
  --blue: #6ED0F6; --blue-d: #2AA7E8;
  --red: #F98C7F; --red-d: #E8503A;
  --purple: #C89BF5; --purple-d: #8B5CD6;
  --dark: #4A5C74; --dark-d: #2E3C4E;
  --tata: #F2A33C;
  --isaac: #4FC3F7;
  --panel: rgba(20, 32, 46, .88);
  --radius: 16px;
  --radius-panel: 20px;
  --depth: 0 6px 0 rgba(0, 0, 0, .30);
  --gloss: inset 0 3px 0 rgba(255, 255, 255, .35);
  --edge: 40px;              /* iPad Safari gesture margin — docs/11 §6b.5 */
  --bar: 78px;               /* legacy: emote.css anchors #tiEnter off this */
  /* Vertical space the touch joystick owns in the lower-left: it sits at
     bottom:46px and is 148px across (#tiStick, injected by controls.js), plus
     a little breathing room. The HUD column MUST stop above this — without it
     the ARMY chip renders behind the joystick ring and Isaac cannot read his
     army count on the iPad. Confirmed on a screenshot, 2026-08-02. */
  --joy: 210px;
  /* THE FIVE REGIONS (see the header of public/src/hud.js and §HUD below).
     These two are the widths the left and right columns reserve; every other
     region is positioned by subtracting them, so one number moves a whole
     side of the screen and tools/shots/hud.js re-proves it. */
  --navcol: 108px;           /* left round-button column */
  --rcol: 268px;             /* top-right status column. 344 when it held four
                                counters in a 2x2 block, an ally chip and a
                                two-line Red Army chip. v37 folded all of that
                                into the buttons it belongs to, so the column is
                                ONE chip with two numbers — and every pixel it
                                gives back widens the centre band that #hudCast,
                                #toast3 and #hint share. */
  --rail: 46px;              /* the right-hand utility column controls.js,
                                settings.css and emote.css all measure from:
                                JUMP 52 · DRAG-TO-LOOK 172 · gear 232 ·
                                emote 316 · and now MAP at 410. */
  --font: "Arial Black", "Segoe UI Black", Impact, Verdana, sans-serif;
}

html, body {
  margin: 0; padding: 0; height: 100%; overflow: hidden;
  font-family: var(--font); font-weight: 900; font-size: 18px;
  -webkit-user-select: none; user-select: none;
  -webkit-text-size-adjust: 100%;
  touch-action: none; background: #57C8F2;
  -webkit-tap-highlight-color: transparent;
}
#c3d { position: fixed; inset: 0; width: 100%; height: 100%; display: block; }
.hidden { display: none !important; }
svg.ticon { display: block; flex: 0 0 auto; }

/* ============================ buttons ============================ */
/* NOTE: the default background lives on `.mw` ALONE. Putting it on a combined
   `.mw, button.mw` selector makes it win against `.mwgold` etc (button.mw is
   more specific), which silently paints every coloured button slate grey. */
.mw {
  font-family: var(--font); font-weight: 900; color: #fff;
  border: 3px solid var(--ink); border-radius: var(--radius);
  text-shadow: 0 2px 0 rgba(0, 0, 0, .40);
  box-shadow: var(--depth), var(--gloss);
  cursor: pointer; background: linear-gradient(var(--dark), var(--dark-d));
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
}
button.mw {
  font-family: var(--font); font-weight: 900;
  font-size: 24px; min-height: 56px; padding: 8px 22px; margin: 0;
  line-height: 1.05;
}
button.mw:active { transform: translateY(4px); box-shadow: 0 2px 0 rgba(0, 0, 0, .30), var(--gloss); }
button.mw:disabled { filter: grayscale(.75); opacity: .55; cursor: default; }
button.mw.huge { font-size: 30px; min-height: 72px; padding: 10px 44px; }
.mwblue   { background: linear-gradient(var(--blue), var(--blue-d)); }
.mwgreen  { background: linear-gradient(var(--green), var(--green-d)); }
.mwred    { background: linear-gradient(var(--red), var(--red-d)); }
.mwgold   { background: linear-gradient(var(--gold), var(--gold-d)); color: #3A2A00; text-shadow: 0 2px 0 rgba(255,255,255,.35); }
.mwpurple { background: linear-gradient(var(--purple), var(--purple-d)); }
.mwdark   { background: linear-gradient(var(--dark), var(--dark-d)); }

/* ============================= LOGIN ============================= */
#login {
  /* above anything the world/controls/panel modules draw: the login screen
     must never have a joystick or a shop peeking through it */
  position: fixed; inset: 0; z-index: 60; overflow: auto;
  display: flex; align-items: center; justify-content: center;
  background:
    radial-gradient(120% 80% at 50% 0%, #8FE0FB 0%, #3AA8E8 55%, #2A86C4 100%);
}
#login::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 24%;
  background: linear-gradient(#6FCF54, #4EA83A); border-top: 5px solid var(--ink);
  clip-path: polygon(0 32%, 12% 18%, 28% 30%, 44% 12%, 62% 26%, 80% 10%, 100% 24%, 100% 100%, 0 100%);
}
#loginInner {
  position: relative; z-index: 1; text-align: center;
  padding: var(--edge) var(--edge) 30px; max-width: 1120px; width: 100%;
}
#loginInner.shake { animation: shake .38s; }
@keyframes shake {
  20% { transform: translateX(-11px); } 40% { transform: translateX(9px); }
  60% { transform: translateX(-6px); } 80% { transform: translateX(4px); } 100% { transform: none; }
}

#logo {
  margin: 0; font-size: 62px; letter-spacing: 2px; color: #fff;
  text-shadow: 0 5px 0 var(--ink), 0 10px 22px rgba(0, 0, 0, .3);
}
#logo .amp { color: var(--gold); font-size: 40px; }
.tagline { margin: 6px 0 20px; font-size: 20px; color: var(--ink); }

#cards { display: flex; gap: 26px; justify-content: center; flex-wrap: wrap; }
.charCard {
  font-family: var(--font); font-weight: 900; cursor: pointer;
  width: 320px; padding: 20px 16px 16px;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  border: 5px solid var(--ink); border-radius: var(--radius-panel);
  background: linear-gradient(#FFFFFF 0%, #EAF3FA 60%, #D3E4F1 100%);
  box-shadow: 0 8px 0 rgba(0, 0, 0, .30), var(--gloss);
  color: var(--ink);
}
.charCard:active { transform: translateY(4px); box-shadow: 0 4px 0 rgba(0, 0, 0, .30); }
.charCard .cardFace {
  width: 128px; height: 128px; border-radius: 50%;
  border: 5px solid var(--ink); background: var(--c);
  display: flex; align-items: center; justify-content: center;
  box-shadow: var(--gloss);
}
.charCard .cardName { font-size: 44px; color: var(--c); text-shadow: 0 3px 0 var(--ink); letter-spacing: 1px; }
.charCard .cardCountry { font-size: 22px; color: var(--ink); }
.charCard .cardSpec { font-size: 19px; color: #56636F; }
.charCard .cardGo {
  margin-top: 8px; font-size: 19px; color: #fff; background: var(--c);
  border: 3px solid var(--ink); border-radius: 12px; padding: 7px 18px;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .35);
}
.charCard.sel { outline: 6px solid var(--gold); outline-offset: 3px; }
#cards.compact .charCard.off { display: none; }
#cards.compact .charCard {
  width: auto; flex-direction: row; gap: 14px; padding: 10px 22px 10px 12px; align-items: center;
}
#cards.compact .charCard .cardFace { width: 62px; height: 62px; border-width: 4px; }
#cards.compact .charCard .cardFace svg { width: 42px; height: 42px; }
#cards.compact .charCard .cardName { font-size: 30px; }
#cards.compact .charCard .cardCountry { font-size: 18px; }
#cards.compact .charCard .cardSpec, #cards.compact .charCard .cardGo { display: none; }

#codeStep { margin-top: 16px; }
.codePrompt { font-size: 22px; color: var(--ink); margin: 10px 0 8px; letter-spacing: 1px; }
.codePrompt b { font-size: 26px; text-shadow: 0 2px 0 var(--ink); }

#codeBlanks, #secretBlanks {
  display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin: 4px 0 14px;
}
.blank {
  width: 58px; height: 72px; border: 4px solid var(--ink); border-radius: 12px;
  background: rgba(255, 255, 255, .35); color: #fff; font-size: 42px;
  display: flex; align-items: center; justify-content: center;
  box-shadow: inset 0 -5px 0 rgba(0, 0, 0, .14);
}
.blank.on { background: linear-gradient(var(--gold), var(--gold-d)); color: var(--ink); text-shadow: none; }
.blank.next { border-color: var(--gold); box-shadow: 0 0 0 4px rgba(255, 223, 112, .55); }

#letterPad, #secretPad {
  display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; max-width: 960px; margin: 0 auto;
}
#letterPad.locked { opacity: .4; pointer-events: none; }
.key {
  font-family: var(--font); font-weight: 900; font-size: 26px; color: #fff;
  width: 62px; height: 62px; border: 3px solid var(--ink); border-radius: 14px;
  background: linear-gradient(var(--blue), var(--blue-d));
  box-shadow: 0 5px 0 rgba(0, 0, 0, .30), var(--gloss);
  cursor: pointer; text-shadow: 0 2px 0 rgba(0, 0, 0, .35);
}
.key:active { transform: translateY(3px); box-shadow: 0 2px 0 rgba(0, 0, 0, .30); }

.rowBtns { display: flex; gap: 14px; justify-content: center; margin-top: 16px; flex-wrap: wrap; }
#loginMsg { min-height: 32px; font-size: 22px; margin: 12px 0 0; color: #fff; }
#loginMsg.bad { color: #FFE3DE; text-shadow: 0 2px 0 #A8271A; }

/* ==================== WHILE YOU WERE AWAY ======================== */
#away {
  position: fixed; inset: 0; z-index: 58; overflow: auto;
  display: flex; justify-content: center;
  background: radial-gradient(120% 90% at 50% 10%, rgba(255, 223, 112, .35), rgba(10, 18, 28, .90) 62%);
  padding: var(--edge);
}
#awayCard {
  /* margin:auto (not align-items:center) — a centred flex child clips its own
     top when it overflows, which ate the ribbon on short viewports. */
  margin: auto;
  position: relative; width: min(900px, 100%); text-align: center;
  background: linear-gradient(#FFF8E2, #FFE9AE);
  border: 5px solid var(--ink); border-radius: var(--radius-panel);
  box-shadow: 0 10px 0 rgba(0, 0, 0, .35); padding: 44px 22px 22px;
}
#awayRibbon {
  position: absolute; top: -26px; left: 50%; transform: translateX(-50%);
  background: linear-gradient(var(--gold), var(--gold-d)); color: var(--ink);
  border: 4px solid var(--ink); border-radius: 14px; padding: 8px 26px;
  font-size: 26px; letter-spacing: 2px; white-space: nowrap;
  box-shadow: var(--depth);
}
#awayTime { font-size: 20px; color: #6B5A22; margin: 4px 0 14px; }
.awaySection { margin: 0 0 14px; }
.awayHead { font-size: 19px; letter-spacing: 2px; color: var(--ink); margin: 0 0 8px; }
.awaySection.friend .awayHead { color: var(--blue-d); }
.awaySection.foe .awayHead { color: var(--red-d); }
.awayGrid { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.prize {
  min-width: 116px; padding: 9px 12px; background: #fff;
  border: 4px solid var(--ink); border-radius: 16px; box-shadow: 0 5px 0 rgba(0, 0, 0, .22);
  display: flex; flex-direction: column; align-items: center; gap: 1px;
}
.prize b { font-size: 26px; color: var(--green-d); }
.prize span { font-size: 15px; color: #56636F; text-transform: uppercase; }
.awayNone { font-size: 18px; color: #6B5A22; margin: 0; }
.awayLines { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.awayLine {
  display: flex; align-items: center; gap: 10px; font-size: 19px; color: var(--ink);
  background: rgba(255, 255, 255, .78); border: 3px solid var(--ink);
  border-radius: 14px; padding: 6px 16px; min-height: 48px;
}
#btnAwayGo { margin-top: 8px; }

/* ===================================================================
   THE HUD — six buttons, four readouts, and nothing else
   ===================================================================
   v37, 2026-08-05 — THE DECLUTTER. Alex played one round and counted the
   screen back to us: nineteen boxes. "How overwhelming it is... He's eight
   here. Three buttons up, three buttons down, and everything is there.
   This is not for humans, this is for AI to play the game!"

   What is already spoken for and may not be entered (controls.js,
   settings.css, emote.css, plot.css — none of them ours):

     lower-LEFT     #tiStick    left 46, bottom 46, 148x148   (--joy = 210)
     lower-RIGHT    #tiJump     right 46, bottom 52,  104x104
                    #tiLook     right 46, bottom 172, 104x44  (first run only —
                                body.seenLook retires it, see below)
                    #tiGear     right 46, bottom 232,  72x72
                    #tiEmote    right 46, bottom 316,  72x72   (fan opens LEFT
                                across y = H-370..H-290)
     bottom-centre  #tiPlotBar  700 wide, centred, bottom 46, ~176 tall
     top-centre     #tiPlotTitle centred, top 84 (iPad) / 96, ~54 tall

   And ours, all of them:

     LEFT COLUMN   #topbar     top 40, left 40, 108 wide — BUY / COUNTRY / SELL
     BOTTOM-RIGHT  #actionRow  right 170, bottom 46, 288x114 — ITEMS / HAMMER /
                               ARMY, the last carrying the soldier count
     RIGHT RAIL    #hudRail    right 46, bottom 410 — MAP, and only MAP. It
                               continues the utility column above the emote
                               button, which is where a view belongs: not one
                               of the six, but never two taps away either.
     BOTTOM-LEFT   #hud        left 40, bottom --joy + 56, <=300 wide — the
                               money card and ONE material chip
     TOP-RIGHT     #rightHud   top 40, right 40, 268 wide — ONE counters chip
     TOP-CENTRE    #hudCast    top 166, inset past both columns
     BOTTOM-CENTRE #interactWrap  ONE banner. Never a card AND a button.

   WHAT LEFT, AND WHERE IT WENT (nothing was deleted):
     GO       -> COUNTRY, which already opened the same WHERE TO? sheet
     TRADE    -> the SELL sheet, first row
     CODES    -> the SELL sheet head
     ARMY / CONQUERED counters -> badges on the ARMY square and the MAP disc
     the ally chip  -> the WHERE TO? sheet (his country says HERE / AWAY)
     the enemy chip -> the ARMY sheet, at the top
     the objective CARD -> merged into the action button, one box

   tools/shots/hud.js measures every one of these at 1180x820 and 1600x900
   and FAILS on any overlap, any control under 56px, or anything inside the
   40px edge margin. Change a number here, run it. */

/* ------------------- LEFT COLUMN: the round nav buttons ------------------
   Reference §6: "big round buttons with a label UNDERNEATH ... bold white
   with a heavy black outline, and it sits OUTSIDE the button, not in it."
   #btnMap is built by map.js into this same container with the same
   svg + span + em shape, so it styles itself without map.js knowing. The
   `#topbar #btnMap` selectors below exist only to out-specify map.css,
   which paints that button as a gold pill for the old horizontal bar. */
#topbar {
  position: fixed; z-index: 12;
  top: calc(var(--edge) + env(safe-area-inset-top, 0px));
  left: calc(var(--edge) + env(safe-area-inset-left, 0px));
  width: var(--navcol);
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  pointer-events: none;
}
#topbar button, #topbar #btnMap {
  pointer-events: auto; position: relative; box-sizing: border-box;
  width: var(--navcol); min-height: 0; margin: 0; padding: 0;
  background: none; border: 0; box-shadow: none; border-radius: 0;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  font-family: var(--font); font-weight: 900; cursor: pointer;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
#topbar button > svg, #topbar #btnMap > svg {
  width: 70px; height: 70px; box-sizing: border-box; padding: 7px;
  border-radius: 50%; border: 4px solid var(--ink);
  background: var(--disc, linear-gradient(var(--dark), var(--dark-d)));
  box-shadow: var(--depth), var(--gloss);
  transition: transform .09s, box-shadow .09s;
}
#topbar button:active > svg {
  transform: translateY(4px); box-shadow: 0 2px 0 rgba(0, 0, 0, .30), var(--gloss);
}
/* the heavy black outline the reference calls for — eight ink shadows, which
   is the only way to stroke HTML text without the stroke eating the glyph */
#topbar button > span, #topbar #btnMap > span {
  font-size: 18px; line-height: 22px; letter-spacing: .5px; color: #fff;
  white-space: nowrap;
  text-shadow:
     2px 0 0 var(--ink), -2px 0 0 var(--ink), 0 2px 0 var(--ink), 0 -2px 0 var(--ink),
     2px 2px 0 var(--ink), -2px 2px 0 var(--ink), 2px -2px 0 var(--ink), -2px -2px 0 var(--ink),
     0 4px 0 rgba(0, 0, 0, .35);
}
/* map.js's live `4/16` conquest score, clipped to the disc like the
   reference's red `!` badge on the Shop button */
#topbar button > em, #topbar #btnMap > em {
  position: absolute; top: -3px; right: 12px; font-style: normal;
  font-size: 15px; line-height: 1; padding: 4px 7px;
  color: #fff; background: var(--ink); border: 2px solid #fff;
  border-radius: 9px; letter-spacing: .5px; text-shadow: none;
}
#topbar .tGold  { --disc: linear-gradient(var(--gold), var(--gold-d)); }
#topbar .tGreen { --disc: linear-gradient(var(--green), var(--green-d)); }
#topbar .tBlue  { --disc: linear-gradient(var(--blue), var(--blue-d)); }
/* map.js builds #btnMap into #topbar and it is loaded asynchronously, so it can
   land in the top bar a tick or two before hud.js `adoptMap()` moves it to the
   rail. THREE UP means three, including for that tick — so it is invisible for
   as long as it is here, and #hudRail's rules take over the moment it leaves. */
#topbar #btnMap { display: none !important; }

/* SELL's live market rate, which used to be a ribbon across the foot of a
   square. On a disc there is no foot, so it takes the badge corner map.js's
   conquest score uses, and the territory bonus takes the other one. Wider than
   a plain badge because it carries "36%" plus an arrow. */
#topbar button.hasRate > em { right: 4px; padding: 4px 6px; display: flex; align-items: center; gap: 1px; }
#topbar button.hasRate > em svg { width: 14px; height: 14px; }
#topbar button > u {
  position: absolute; top: -3px; left: 8px; text-decoration: none;
  font-size: 14px; line-height: 1; padding: 4px 6px; color: #fff;
  background: var(--green-d); border: 2px solid var(--ink); border-radius: 9px;
}

/* ---- #hudRail: the right-hand utility column ------------------------------
   ONE button: MAP. The six Alex named are things you DO; a map is a view of
   the world you are standing in, so it belongs with the other things that are
   neither an action nor a readout — JUMP, the gear, the emote button — which
   climb the right edge at 52 / 232 / 316. This continues that column at 410,
   which is the first slot clear of the emote fan (y = H-370..H-290).

   It carries map.js's live `4/26` conquest badge, which is why CONQUERED could
   leave the counters chip: the score is drawn on the button that shows you the
   twenty-six places it counts. */
#hudRail {
  position: fixed; z-index: 12; pointer-events: none;
  right: calc(var(--rail) + env(safe-area-inset-right, 0px));
  bottom: calc(410px + env(safe-area-inset-bottom, 0px));
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
#hudRail button, #hudRail #btnMap {
  pointer-events: auto; position: relative; box-sizing: border-box;
  width: 76px; min-height: 0; margin: 0; padding: 0;
  background: none; border: 0; box-shadow: none; border-radius: 0;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  font-family: var(--font); font-weight: 900; cursor: pointer;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
  color: #fff; text-shadow: none;
}
#hudRail button > svg, #hudRail #btnMap > svg {
  width: 72px; height: 72px; box-sizing: border-box; padding: 8px;
  border-radius: 50%; border: 4px solid var(--ink);
  background: var(--disc, linear-gradient(var(--dark), var(--dark-d)));
  box-shadow: var(--depth), var(--gloss);
  transition: transform .09s, box-shadow .09s;
}
#hudRail button:active > svg, #hudRail #btnMap:active > svg {
  transform: translateY(4px); box-shadow: 0 2px 0 rgba(0, 0, 0, .30), var(--gloss);
}
#hudRail button > span, #hudRail #btnMap > span {
  font-size: 15px; line-height: 18px; letter-spacing: .5px; color: #fff;
  white-space: nowrap;
  text-shadow:
     2px 0 0 var(--ink), -2px 0 0 var(--ink), 0 2px 0 var(--ink), 0 -2px 0 var(--ink),
     2px 2px 0 var(--ink), -2px 2px 0 var(--ink), 2px -2px 0 var(--ink), -2px -2px 0 var(--ink),
     0 3px 0 rgba(0, 0, 0, .35);
}
#hudRail button > em, #hudRail #btnMap > em {
  position: absolute; top: -5px; right: -2px; font-style: normal;
  font-size: 14px; line-height: 1; padding: 4px 6px;
  color: #fff; background: var(--ink); border: 2px solid #fff;
  border-radius: 9px; letter-spacing: .5px; text-shadow: none;
}
#hudRail .tGold   { --disc: linear-gradient(var(--gold), var(--gold-d)); }
#hudRail #btnMap  { --disc: linear-gradient(var(--gold), var(--gold-d)); }

/* -------------- BOTTOM-RIGHT: the numbered action row -------------------
   Reference §6: "three chunky rounded squares each with a small number badge
   in the corner". The badge is the keyboard shortcut on a PC (hud.js
   wireActionKeys) and pure ordinal on the iPad, where you tap. It stops
   170px short of the right edge so it never reaches the JUMP column, and
   hud.js hides it outright while a placement ghost is live, because
   plot.js's bar owns the bottom of the screen then. */
#actionRow {
  position: fixed; z-index: 12;
  right: calc(170px + env(safe-area-inset-right, 0px));
  bottom: calc(var(--edge) + 6px + env(safe-area-inset-bottom, 0px));
  display: flex; align-items: flex-start; gap: 12px;
}
button.actSq {
  position: relative; box-sizing: border-box; width: 88px; padding: 0; margin: 0;
  background: none; border: 0; box-shadow: none;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  font-family: var(--font); font-weight: 900; cursor: pointer;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
}
button.actSq::before {
  content: ''; position: absolute; left: 0; top: 0; width: 88px; height: 88px;
  border: 4px solid var(--ink); border-radius: 22px;
  background: var(--sq, linear-gradient(var(--dark), var(--dark-d)));
  box-shadow: var(--depth), var(--gloss);
  transition: transform .09s, box-shadow .09s;
}
button.actSq:active::before {
  transform: translateY(4px); box-shadow: 0 2px 0 rgba(0, 0, 0, .30), var(--gloss);
}
button.actSq > svg {
  position: relative; height: 88px; width: 88px; padding: 20px; box-sizing: border-box;
}
/* SELL carries the live market rate. It goes INSIDE the square, on a ribbon
   across its foot — the sell-timing skill only works if the number and the
   button you press are the same object, and the outside label has to stay one
   short word or three of these run into each other. */
button.actSq.hasSub > svg { padding: 12px 20px 30px; }
button.actSq > i {
  position: absolute; left: 7px; right: 7px; top: 54px; height: 26px;
  display: flex; align-items: center; justify-content: center; gap: 1px;
  font-style: normal; font-size: 16px; line-height: 1; color: var(--ink);
  background: rgba(255, 255, 255, .62); border: 2px solid var(--ink);
  border-radius: 10px; box-sizing: border-box;
}
button.actSq > i svg { width: 18px; height: 18px; }
button.actSq > span {
  position: relative; font-size: 18px; line-height: 22px; color: #fff;
  white-space: nowrap;
  text-shadow:
     2px 0 0 var(--ink), -2px 0 0 var(--ink), 0 2px 0 var(--ink), 0 -2px 0 var(--ink),
     2px 2px 0 var(--ink), -2px 2px 0 var(--ink), 2px -2px 0 var(--ink), -2px -2px 0 var(--ink),
     0 4px 0 rgba(0, 0, 0, .35);
}
button.actSq > em {           /* the number badge, top-left corner */
  position: absolute; left: -7px; top: -7px; font-style: normal;
  width: 30px; height: 30px; line-height: 26px; text-align: center;
  font-size: 17px; color: #fff; background: var(--ink);
  border: 2px solid rgba(255, 255, 255, .9); border-radius: 10px;
}
/* THE ARMY COUNT, top-right corner. This slot carried SELL's territory bonus
   until SELL became a round disc in v35 and took its badges with it; v37 gives
   it to the soldier count, which came off the top-right counters block. ARMY is
   the last square in the row, so a badge that overhangs by 8px overhangs into
   the gap before JUMP and touches nothing. */
button.actSq > u {
  position: absolute; right: -8px; top: -7px; text-decoration: none;
  font-size: 14px; line-height: 1; padding: 5px 7px; color: #fff;
  background: var(--ink); border: 2px solid rgba(255, 255, 255, .9);
  border-radius: 10px; white-space: nowrap;
}
button.actSq > u.maxed { background: var(--gold-d); color: #3A2A00; border-color: var(--ink); }
.actSq.tBlue   { --sq: linear-gradient(var(--blue), var(--blue-d)); }
.actSq.tGold   { --sq: linear-gradient(var(--gold), var(--gold-d)); }
.actSq.tGreen  { --sq: linear-gradient(var(--green), var(--green-d)); }
.actSq.tRed    { --sq: linear-gradient(var(--red), var(--red-d)); }
.actSq.tPurple { --sq: linear-gradient(var(--purple), var(--purple-d)); }
/* HAMMER is a MODE, not an action, and a mode that does not look different
   from a button is how a ten-year-old knocks down a factory he meant to walk
   past. Armed: it goes red, it lifts, and it keeps a white ring. */
.actSq.armed::before {
  outline: 4px solid #fff; outline-offset: 3px;
  transform: translateY(-3px);
  box-shadow: 0 9px 0 rgba(0, 0, 0, .34), var(--gloss);
}

/* -------------- BOTTOM-LEFT: the resource stack -------------------------
   Reference §6 puts this bottom-left and calls it the biggest text on the
   screen: money in bright green, then the rate, then an event icon with a
   live countdown. Ours sits ABOVE the joystick (--joy) because on a tablet
   the lower-left corner itself belongs to a thumb. */
#hud {
  position: fixed; z-index: 10; pointer-events: none;
  left: calc(var(--edge) + env(safe-area-inset-left, 0px));
  bottom: calc(var(--joy) + 56px + env(safe-area-inset-bottom, 0px));
  width: min(300px, 40vw);
  display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
}
#incomeCard {
  pointer-events: none; box-sizing: border-box; width: 100%;
  display: flex; flex-direction: column; align-items: flex-start;
  background: var(--panel); border: 3px solid var(--ink); border-radius: var(--radius);
  box-shadow: var(--depth); padding: 8px 14px 9px; overflow: hidden;
}
#incomeCard #hMoney {                       /* order 1 — the headline */
  order: 1; font-size: 34px; line-height: 1.06; color: #8BE86B;
  text-shadow: 0 3px 0 var(--ink), 0 0 16px rgba(139, 232, 107, .40);
  white-space: nowrap;
}
#incomeCard #hMoney.md { font-size: 28px; }
#incomeCard #hMoney.sm { font-size: 22px; }
#incomeCard #hIncome {                      /* order 2 — the engine */
  order: 2; font-size: 22px; line-height: 1.15; color: var(--gold);
  text-shadow: 0 2px 0 var(--ink); white-space: nowrap;
}
#incomeCard.turbo #hIncome { color: #FFF3B0; transform-origin: left center; animation: pulse 1s infinite alternate; }
#incomeCard #hEvent {                       /* order 3 — the countdown */
  order: 3; display: flex; align-items: center; gap: 7px; margin-top: 6px;
  padding-top: 6px; border-top: 2px solid rgba(255, 255, 255, .13);
  width: 100%; font-size: 14px; letter-spacing: .5px; color: #93A6B8;
}
#incomeCard #hEvent span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#incomeCard #hEvent b { margin-left: auto; font-size: 18px; color: #CFE6F7; }
#incomeCard #hEvent.on { color: #FFDF70; }
#incomeCard #hEvent.on b { color: #FFDF70; }
@keyframes pulse { to { transform: scale(1.06); } }
@keyframes glow { to { box-shadow: 0 4px 0 rgba(0, 0, 0, .28), 0 0 0 6px rgba(232, 80, 58, .5); } }

/* ---- the ONE material chip -------------------------------------------
   This replaces the six-chip wall. It previews the three biggest holdings,
   says what the lot is worth, and opens the SELL sheet — the only thing you
   were ever going to do about it. Full silos turn it red and it says so,
   because a full silo throttles production to 25% (docs/12 §4). */
#matChips { display: flex; width: 100%; }
.chip {
  pointer-events: auto; display: flex; align-items: center; gap: 8px;
  background: var(--panel); border: 3px solid var(--ink); border-radius: 14px;
  padding: 5px 11px; min-height: 56px; color: #fff; font-size: 19px;
  box-sizing: border-box;   /* min-height IS the touch target, not a floor+padding */
  box-shadow: 0 4px 0 rgba(0, 0, 0, .28); cursor: pointer;
}
.chip.stock { width: 100%; gap: 10px; border-left: 6px solid var(--gold-d); }
.chip.stock .stockIcons { display: flex; flex: 0 0 auto; }
.chip.stock .stockIcons svg { margin-right: -14px; }
.chip.stock .stockIcons svg:last-child { margin-right: 0; }
.chip.stock .stockTxt { display: flex; flex-direction: column; min-width: 0; }
.chip.stock .stockTxt b { font-size: 17px; letter-spacing: .3px; white-space: nowrap; }
.chip.stock .stockTxt span {
  font-size: 13px; color: #93A6B8;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* pulse a GLOW, never a scale: scaling from the centre pushed the chip's left
   edge inside the 40px iPad gesture margin. */
.chip.stock.full {
  border-color: var(--red-d); background: rgba(168, 39, 26, .94);
  animation: glow .8s infinite alternate;
}
.chip.stock.full .stockTxt b { color: #FFE3DE; }
.chip.stock.full .stockTxt span { color: #FFC0B4; }
#siloWarn { display: none; }   /* folded into the chip above — never a 7th box */

/* -------------- TOP-RIGHT: ONE counters chip ----------------------------
   Was four counters in a 2x2 block, an ally chip and a two-line Red Army
   chip — six of the nineteen boxes, reaching 385px down an 820px screen.
   ARMY went to the ARMY square, CONQUERED to the MAP badge, the ally to the
   WHERE TO? sheet and the enemy to the ARMY sheet. What is left is BUILDERS
   and PEOPLE, side by side, in one box that is done by y=100. */
#rightHud {
  position: fixed; z-index: 10; text-align: right; pointer-events: none;
  top: calc(var(--edge) + env(safe-area-inset-top, 0px));
  right: calc(var(--edge) + env(safe-area-inset-right, 0px));
  width: var(--rcol); max-width: 40vw;
  display: flex; flex-direction: column; align-items: flex-end; gap: 8px;
}
#marketCard { display: none; }   /* the market rate lives on the SELL button now */

#capChips { display: flex; justify-content: flex-end; width: 100%; }
.chip.cap {
  cursor: default; gap: 14px; padding: 5px 12px;
  border-left: 5px solid #6E8095; justify-content: flex-end;
}
.capCell { display: flex; align-items: center; gap: 7px; min-width: 0; }
.chip.cap .capTxt { display: flex; flex-direction: column; min-width: 0; }
.chip.cap b { font-size: 18px; line-height: 1.05; }
.chip.cap .lbl { font-size: 11px; color: #93A6B8; letter-spacing: 1px; }
.capCell.maxed b { color: var(--gold); }
.capCell.maxed .lbl { color: var(--gold); }

/* ---- ally + enemy (docs/15 §A4) ---------------------------------------
   Off the screen, but not out of the game: `.wchip` is what the Red Army
   block wears inside the ARMY sheet now (hud.js redBlock). Opaque, never
   alpha over grass: 3px ink border, weight 900, ink text shadow.
   #worldLine stays in the markup and stays empty; an empty flex child would
   still cost the column an 8px gap, so it stops existing instead. */
#worldLine { display: flex; flex-direction: column; align-items: stretch; gap: 8px; width: 100%; }
#worldLine:empty { display: none; }
.wchip {
  display: flex; align-items: center; gap: 8px; box-sizing: border-box; width: 100%;
  min-height: 52px; padding: 6px 12px; border: 3px solid var(--ink);
  border-radius: 14px; box-shadow: var(--depth);
  background: #16232F; color: #fff; text-align: left;
  font-family: var(--font); font-weight: 900;
  font-size: 18px; letter-spacing: .5px; text-shadow: 0 2px 0 rgba(0, 0, 0, .55);
  flex-wrap: wrap;
}
.wchip span { white-space: nowrap; }
.wchip em {
  font-style: normal; font-size: 14px; letter-spacing: 1px; margin-left: auto;
  padding: 3px 8px; border-radius: 9px; border: 2px solid var(--ink);
  background: rgba(0, 0, 0, .32); color: #EAF3FA; text-shadow: none;
}
.wchip i {
  font-style: normal; flex: 1 0 100%; font-size: 13px; letter-spacing: .5px;
  color: #CFE6F7; text-shadow: none; opacity: .9;
}
.wchip.ally { border-left: 8px solid var(--c, var(--isaac)); }
.wchip.ally span { color: var(--c, var(--isaac)); }
.wchip.ally.on em { background: var(--green-d); color: #fff; }
/* the enemy chip IS the way to their gate — Alex's own question, docs/15 §A7 */
button.wchip.foe {
  pointer-events: auto; cursor: pointer; touch-action: manipulation;
  background: linear-gradient(#8A3A31, #6E2C24); border-left: 8px solid var(--red-d);
  box-shadow: var(--depth), var(--gloss);
}
button.wchip.foe:active { transform: translateY(4px); box-shadow: 0 2px 0 rgba(0, 0, 0, .30); }
.wchip.foe span { color: #FFD9D2; }
.wchip.foe i { color: #FFDF70; }
.wchip.foe.beaten { background: linear-gradient(#2F4A2B, #223A20); border-left-color: var(--green-d); }
.wchip.foe.beaten span { color: #DFFBD2; }
.wchip.foe.beaten i { color: #B9E8A6; }

/* The Red Army block is a whole sheet wide inside the ARMY sheet, so it drops
   the right-column width it inherited in the corner. */
#armySheet .wchip { max-width: 620px; margin: 0 auto 14px; }

/* THE HINT THAT ONLY HAS TO BE READ ONCE. controls.js draws DRAG TO LOOK and
   has no idea it has already been read; hud.js armLookHint() adds this class
   after fourteen seconds and remembers it in the same prefs store as SOUND OFF.
   Two ids' worth of specificity, because controls.js injects its `#tiLook` rule
   into <head> AFTER this stylesheet is linked and would otherwise win on
   order. */
body.seenLook #tiTouch #tiLook,
body.seenLook #tiLook { display: none !important; }

/* -------------- TOP-CENTRE: world broadcasts ---------------------------
   Reference §6: "over a captured point: Conquered in green, with the actual
   numbers underneath — Additional civilians +20, Money multiplier +25%."
   That is the docs/17 §A3 ask — the conquer -> civilians -> production chain,
   spelled out at the moment it happens.

   It sits at 166px so it clears #tiPlotTitle (top 84 iPad / 96 desktop, ~54
   tall) and is inset past BOTH columns so it can never reach either. */
#hudCast {
  position: fixed; z-index: 13; pointer-events: none;
  top: calc(var(--edge) + 126px + env(safe-area-inset-top, 0px));
  left: calc(var(--edge) + var(--navcol) + 16px);
  right: calc(var(--edge) + var(--rcol) + 20px);
  display: flex; justify-content: center;
}
.castCard {
  box-sizing: border-box; max-width: 100%; min-width: 260px;
  padding: 10px 20px 12px; text-align: center;
  background: rgba(20, 32, 46, .94); border: 4px solid var(--ink);
  border-left: 10px solid var(--accent, #93A6B8);
  border-radius: var(--radius-panel); box-shadow: 0 8px 0 rgba(0, 0, 0, .34);
  animation: castIn .26s cubic-bezier(.2, .9, .3, 1) both;
}
@keyframes castIn { from { opacity: 0; transform: translateY(-16px); } to { opacity: 1; transform: none; } }
.castCard.good  { --accent: var(--green-d); }
.castCard.ally  { --accent: var(--isaac); }
.castCard.bad   { --accent: var(--red-d); }
.castTop { display: flex; align-items: center; justify-content: center; gap: 10px; }
.castTop b {
  font-size: 30px; letter-spacing: 3px; color: var(--accent, #fff);
  text-shadow: 0 3px 0 var(--ink);
}
.castWho { margin-top: 3px; font-size: 18px; color: #fff; letter-spacing: .5px; }
.castStat {
  display: flex; align-items: center; gap: 8px; margin-top: 6px;
  padding: 5px 12px; border-radius: 12px; background: rgba(255, 255, 255, .09);
}
.castStat span { font-size: 16px; color: #CFE6F7; letter-spacing: .3px; }
.castStat b { margin-left: auto; font-size: 20px; color: var(--gold); text-shadow: 0 2px 0 var(--ink); }

/* ============ objective card + the big action button ==============
   docs/15 §A5. The old affordance was one thin button reading
   "CLAIM CAMP — 10 TROOPS" that greyed out silently when you could not afford
   it. Now: a card that announces the place, its owner and its money multiplier
   from up to 100 steps away, and under it a two-line button that says what
   will happen and what it costs. Both live in #interactWrap, bottom-centre,
   clear of the joystick (lower-left) and the jump button (lower-right). */
/* The band between the joystick (which ends at x=204) and the action row.
   THE ROW GREW AND THIS DID NOT. It was written for three buttons — 3x88 + 2x12
   + a 170px right offset = 458, so 440 cleared it. v25 added ARMY, making it
   4x88 + 3x12 + 170 = 558, and the objective card ran under the button badges
   for eight releases; you only saw it when the place had a long name, and every
   place has a long name now. 574 was 558 plus breathing room.
   v35 put the row back to THREE (ITEMS / HAMMER / ARMY) = 458.
   v37 pulled the right inset in to 442, which is 458 minus the 40px --edge
   plus a 24px gap: with the card and the button merged into ONE banner the
   headline has to fit on one line, and at 1180 the old 474 left it 456px, in
   which "ATTACK THE SOUTH CAMP" broke over three lines. 488px does not.
   Do not change one of these two numbers without the other — that is the
   entire history of this comment. */
#interactWrap {
  position: fixed; z-index: 12;
  left: calc(var(--edge) + 170px + env(safe-area-inset-left, 0px));
  right: calc(var(--edge) + 442px + env(safe-area-inset-right, 0px));
  bottom: calc(var(--edge) + 6px + env(safe-area-inset-bottom, 0px));
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  pointer-events: none;
}

.objCard {
  box-sizing: border-box; max-width: 100%;
  background: #16232F; border: 3px solid var(--ink); border-radius: var(--radius);
  box-shadow: var(--depth); padding: 9px 16px 10px; text-align: left;
  border-left: 9px solid var(--accent, var(--green-d));
}
.objCard.free   { --accent: var(--green-d); }
.objCard.foe    { --accent: var(--red-d); }
.objCard.mine   { --accent: var(--gold-d); }
.objCard.beaten { --accent: var(--green-d); background: linear-gradient(#2F4A2B, #1D3220); }
.objTop { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.objTop span {
  font-size: 26px; color: #fff; letter-spacing: .5px;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .55);
}
.objTop em {
  font-style: normal; font-size: 15px; letter-spacing: 1px; color: #14202E;
  background: var(--accent, var(--green-d)); border: 2px solid var(--ink);
  border-radius: 9px; padding: 3px 9px; color: #fff; text-shadow: none;
}
/* the money multiplier is the real prize (docs/12) — never let it be small */
.objPrize {
  display: flex; align-items: center; gap: 8px; margin-top: 6px;
  font-size: 19px; color: var(--gold); text-shadow: 0 2px 0 rgba(0, 0, 0, .5);
}
.objHow { margin-top: 4px; font-size: 17px; color: #CFE6F7; }

/* ONE BOX, THREE LINES. The tag (`HELD BY A GARRISON OF 6`) used to be a whole
   second card stacked above this button; it is now the small line inside it, so
   the place, the deed, the cost and the prize are one thing you press. */
button.mw.actBtn {
  pointer-events: auto; min-height: 78px; padding: 10px 18px; gap: 12px;
  max-width: 100%; position: relative;
}
.actBtn .actText {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  flex: 1 1 auto; min-width: 0;
}
.actBtn .actText em {
  font-style: normal; font-size: 14px; line-height: 1.1; letter-spacing: 1.2px;
  opacity: .92; text-shadow: 0 2px 0 rgba(0, 0, 0, .45);
}
/* 24px is the docs/13 §7 floor for a button, and it is where the headline sits
   now: the merged banner has to fit "ATTACK THE SOUTH CAMP" on ONE line in the
   488px an iPad has between the joystick and the action row, and 26 did not. */
.actBtn .actText b { font-size: 24px; line-height: 1.08; letter-spacing: .3px; }
.actBtn .actText i {
  font-style: normal; font-weight: 900; font-size: 16px; opacity: .95;
  letter-spacing: .3px;
}
/* THE PRIZE (docs/12: the money multiplier is the whole point of taking a
   place). It is a CORNER STICKER, not a column: inline it ate 132 of the
   488px the band has on an iPad and broke the headline over three lines, and a
   sticker is the idiom this HUD already speaks — SELL's rate, ARMY's count and
   MAP's score are all badges clipped to their own button's corner. */
.actBtn > u {
  position: absolute; top: -14px; right: -8px; z-index: 1;
  text-decoration: none; white-space: nowrap;
  font-size: 16px; line-height: 1; padding: 7px 10px; color: #3A2A00;
  background: linear-gradient(var(--gold), var(--gold-d));
  border: 3px solid var(--ink); border-radius: 12px; text-shadow: none;
  box-shadow: 0 4px 0 rgba(0, 0, 0, .28);
}
.actBtn kbd {
  font-family: var(--font); font-size: 15px; line-height: 1;
  padding: 6px 9px; border: 2px solid var(--ink); border-radius: 8px;
  background: rgba(0, 0, 0, .28); color: #fff; text-shadow: none;
}
.mwgold .actText i, .mwgold .actText em, .mwgold kbd { color: #3A2A00; }
.mwgold kbd { background: rgba(255, 255, 255, .45); }
.mwgold .actBtn > u, .actBtn.mwgold > u {
  background: rgba(255, 255, 255, .55);
}

.sheet {
  position: fixed; z-index: 16; left: 50%; transform: translateX(-50%);
  bottom: calc(var(--edge) + env(safe-area-inset-bottom, 0px));
  width: min(1000px, calc(100vw - var(--edge) * 2));
  max-height: 62vh; overflow: auto;
  background: var(--panel); border: 4px solid var(--ink); border-radius: var(--radius-panel);
  box-shadow: 0 10px 0 rgba(0, 0, 0, .35); padding: 16px; text-align: center;
}
.sheetHead { margin: 0 0 12px; font-size: 20px; color: var(--gold); letter-spacing: 1px; }
/* the sheet's own title bar: the head on the left, the rare action on the
   right. SECRET CODE lives here — one tap inside the money sheet, which is
   exactly the distance a thing Tata uses once a week deserves. */
.sheetTop {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; margin-bottom: 12px;
}
.sheetTop .sheetHead { margin: 0; text-align: left; }
button.mw.sheetTopBtn { font-size: 16px; min-height: 56px; padding: 8px 16px; gap: 8px; }
button.mw.sheetTopBtn svg { width: 26px; height: 26px; }
.sheetRow { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.sheetRow button.mw {
  flex-direction: column; gap: 2px; min-width: 172px; min-height: 96px;
  font-size: 20px; padding: 10px 14px;
}
.sheetRow button.mw small { font-size: 15px; opacity: .9; }
/* the ally chip, folded onto the door it was always about: his country's own
   button says whether he is behind it */
.sheetRow button.mw.zoneRow { position: relative; }
.sheetRow button.mw.zoneRow em {
  position: absolute; top: -8px; right: -6px; font-style: normal;
  font-size: 13px; line-height: 1; letter-spacing: 1px; padding: 5px 8px;
  color: #fff; background: var(--ink); border: 2px solid rgba(255, 255, 255, .85);
  border-radius: 9px; text-shadow: none; white-space: nowrap;
}
.sheetRow button.mw.zoneRow em.on { background: var(--green-d); }
/* his country wears HIS colour, which is what the ally chip's left border did */
.sheetRow button.mw.zoneRow.ally { outline: 4px solid var(--ally, var(--isaac)); outline-offset: 2px; }
.sheetRow button.mw.full { outline: 4px solid var(--red-d); }
.sheetNone { font-size: 19px; color: #CFE6F7; }
/* CLOSE never scrolls out of reach — see closeBtn() in hud.js */
.sheetFoot {
  position: sticky; bottom: -16px; z-index: 2; margin-top: 14px;
  padding: 12px 0 16px;
  background: linear-gradient(to top, rgba(20, 32, 46, .97) 62%, rgba(20, 32, 46, 0));
}
.sheetClose { margin-top: 0; }

/* ------------------------- THE ARMY SHEET -------------------------------
   The head IS the answer to "where is my army": one enormous number, the
   word SOLDIERS beside it, and how much land they have already won. Alex
   asked for the button twice in one message, so the thing behind it opens
   with the count and nothing else competing for the first look. */
.armyHead {
  display: flex; align-items: center; justify-content: center; gap: 12px;
  flex-wrap: wrap; margin-bottom: 14px;
  padding-bottom: 12px; border-bottom: 4px solid rgba(255, 255, 255, .12);
}
.armyHead .armyBig {
  font-size: clamp(44px, 7vw, 66px); line-height: 1; color: var(--red);
  text-shadow: 0 4px 0 var(--ink), 0 0 22px rgba(255, 90, 74, .40);
}
.armyHead .armyOf,
.armyHead .armyLand {
  display: flex; flex-direction: column; align-items: flex-start; line-height: 1.1;
  font-size: 19px; letter-spacing: 1px; color: #CFE6F7;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .5);
}
.armyHead .armyLand { margin-left: 16px; font-size: 26px; color: var(--gold); }
.armyHead i { font-style: normal; font-size: 14px; letter-spacing: 1.4px; color: #7E92A6; }

/* The live march. It is a STOP, not decoration — the one thing you want
   within reach when you realise you sent them at the wrong place. */
.armyMarch {
  display: flex; align-items: center; justify-content: center; gap: 14px;
  flex-wrap: wrap; margin: 0 0 14px;
  padding: 10px 14px; border-radius: 14px;
  background: rgba(245, 183, 35, .16); border: 3px solid var(--gold-d);
}
.armyMarch span { font-size: 20px; color: var(--gold); letter-spacing: 1px; }
.armyMarch button.mw { min-height: 56px; font-size: 18px; padding: 8px 18px; }

/* THE ROSTER (docs/23 Stage 4). One chip per kind of unit you own or are
   building — "what kind of army have I got", which is the question the six
   units exist to make askable. A kind you neither own nor build is not drawn
   at all: six chips of zeroes is exactly the clutter v37 was spent removing.
   The thin bar at the bottom of a chip is how far the buildings that make
   that kind have got with the next one. */
.armyRoster {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 10px;
  margin: 0 0 14px;
}
.unitChip {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  min-width: 92px; padding: 8px 10px 7px;
  border-radius: 14px; background: rgba(255, 255, 255, .07);
  border: 3px solid rgba(255, 255, 255, .16);
}
.unitChip.empty { opacity: .55; }
.unitChip b { font-size: 26px; color: #fff; text-shadow: 0 2px 0 var(--ink); line-height: 1; }
.unitChip span { font-size: 13px; letter-spacing: 1.5px; color: var(--gold); }
.unitChip i { font-style: normal; font-size: 11px; letter-spacing: .5px; color: #9FB6C9; }
.unitChip u {
  display: block; width: 100%; height: 5px; margin-top: 4px; border-radius: 3px;
  background: rgba(0, 0, 0, .35); overflow: hidden; text-decoration: none;
}
.unitChip u em { display: block; height: 100%; background: var(--green-d); border-radius: 3px; }

/* A target row is wider than a sell row: it carries a whole sentence about
   why you cannot go, and that sentence must never wrap to four lines. */
.sheetRow button.mw.armyRow { min-width: 300px; max-width: 460px; }
.sheetRow button.mw.armyRow small { opacity: 1; color: rgba(255, 255, 255, .92); }
/* the one you are already standing on — press this and it happens NOW */
.sheetRow button.mw.armyRow.armyHere { outline: 4px solid var(--gold); }
.sheetRow button.mw.armyRow em {
  font-style: normal; font-size: 14px; letter-spacing: 1px;
  padding: 2px 7px; margin-left: 6px; border-radius: 7px;
  background: rgba(0, 0, 0, .34); border: 2px solid rgba(255, 255, 255, .28);
}

/* ========================= toast + floats ======================== */
/* Below #hudCast's band (166 + up to ~160), so a broadcast and a toast can
   both be on screen without ever touching. Inset past both HUD columns. */
#toast3 {
  position: fixed; z-index: 54; text-align: center;
  top: calc(var(--edge) + 300px + env(safe-area-inset-top, 0px));
  left: calc(var(--edge) + var(--navcol) + 16px);
  right: calc(var(--edge) + var(--rcol) + 20px);
  margin: 0 auto; width: max-content; max-width: 100%; box-sizing: border-box;
  background: var(--panel); border: 3px solid var(--gold); border-radius: 14px;
  color: var(--gold); font-size: 20px; padding: 11px 26px;
  box-shadow: var(--depth);
}
#toast3.toast-good { border-color: var(--green-d); color: #DFFBD2; }
#toast3.toast-bad  { border-color: var(--red-d); color: #FFE3DE; }
#toast3.toast-warn { border-color: var(--gold-d); color: var(--gold); }

#floats { position: fixed; inset: 0; z-index: 52; pointer-events: none; }
.float {
  position: fixed; transform: translate(-50%, -100%);
  font-size: 24px; color: var(--gold); text-shadow: 0 3px 0 var(--ink);
  animation: rise 1.25s forwards; white-space: nowrap;
}
.float.big { font-size: 34px; }
@keyframes rise { to { transform: translate(-50%, -180%); opacity: 0; } }

/* ==================== THE ROUT — victory banner ==================
   docs/11 §7b.1. The one moment in the game where the two of them beat
   something together. It must look like a prize, not a notification. */
#routBanner {
  position: fixed; inset: 0; z-index: 57; overflow: auto;
  display: flex; align-items: center; justify-content: center; padding: var(--edge);
  background: radial-gradient(120% 90% at 50% 12%, rgba(255, 223, 112, .40), rgba(10, 18, 28, .88) 64%);
  animation: routIn .3s ease-out;
}
#routBanner.hidden { display: none !important; }
@keyframes routIn { from { opacity: 0; } to { opacity: 1; } }
.routCard {
  position: relative; margin: auto; width: min(760px, 100%); text-align: center;
  background: linear-gradient(#FFF8E2, #FFE3A6);
  border: 5px solid var(--ink); border-radius: var(--radius-panel);
  box-shadow: 0 10px 0 rgba(0, 0, 0, .35); padding: 46px 22px 24px;
}
.routRibbon, #routBanner .routRibbon {
  position: absolute; top: -26px; left: 50%; transform: translateX(-50%);
  background: linear-gradient(var(--red), var(--red-d)); color: #fff;
  border: 4px solid var(--ink); border-radius: 14px; padding: 8px 30px;
  font-size: 26px; letter-spacing: 3px; white-space: nowrap; box-shadow: var(--depth);
  text-shadow: 0 2px 0 rgba(0, 0, 0, .4);
}
.routCard h2 {
  margin: 4px 0 2px; font-size: 40px; color: var(--red-d);
  text-shadow: 0 3px 0 var(--ink); letter-spacing: 1px;
}
.routWho { margin: 0 0 14px; font-size: 21px; color: var(--ink); }
.routPrizes { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.routPrize {
  min-width: 132px; padding: 9px 12px; background: #fff;
  border: 4px solid var(--ink); border-radius: 16px; box-shadow: 0 5px 0 rgba(0, 0, 0, .22);
  display: flex; flex-direction: column; align-items: center; gap: 1px;
}
.routPrize b { font-size: 28px; color: var(--red-d); }
.routPrize span { font-size: 14px; color: #56636F; letter-spacing: .5px; }
.routPrize.gold { background: linear-gradient(var(--gold), var(--gold-d)); }
.routPrize.gold b { color: #3A2A00; }
.routPrize.gold span { color: #6B5A22; }
.routBack { margin: 16px 0 12px; font-size: 19px; color: #6B5A22; }

/* ========================= code overlay ========================== */
#codeOverlay {
  position: fixed; inset: 0; z-index: 56; overflow: auto;
  display: flex; align-items: center; justify-content: center;
  background: rgba(10, 18, 28, .90); padding: var(--edge);
}
#codeOverlay .codeCard {
  text-align: center; width: min(960px, 100%);
  background: rgba(20, 32, 46, .96); border: 5px solid var(--gold);
  border-radius: var(--radius-panel); padding: 26px; box-shadow: 0 10px 0 rgba(0, 0, 0, .4);
}
#codeOverlay .codePrompt { color: var(--gold); font-size: 28px; letter-spacing: 4px; }

/* ======================= touch joystick ========================== */
/* kept until Agent B draws its own permanent joystick + jump button */
#stickBase {
  position: fixed; width: 132px; height: 132px; border-radius: 50%;
  background: rgba(20, 32, 46, .35); border: 4px solid rgba(255, 255, 255, .55);
  z-index: 11; pointer-events: none;
}
#stickKnob {
  position: fixed; width: 58px; height: 58px; border-radius: 50%;
  background: rgba(255, 255, 255, .8); border: 3px solid var(--ink);
  z-index: 12; pointer-events: none;
}
/* Shares #hudCast's band. They are mutually exclusive by construction: the
   first thing hud.js does when it shows a broadcast is hide this, because a
   broadcast is news and the hint is scenery. */
#hint {
  position: fixed; z-index: 9; text-align: center;
  top: calc(var(--edge) + 126px + env(safe-area-inset-top, 0px));
  left: calc(var(--edge) + var(--navcol) + 16px);
  right: calc(var(--edge) + var(--rcol) + 20px);
  margin: 0 auto; width: max-content; max-width: 100%; box-sizing: border-box;
  color: #fff; font-size: 17px; line-height: 1.35; pointer-events: none;
  background: rgba(20, 32, 46, .82); border: 3px solid var(--ink);
  border-radius: 14px; padding: 8px 18px;
}

/* ================= THE BATTLE BAR (docs/23 Stage 4) ==================
   While your army is out — walking there, fighting, or coming home — this is
   the one thing on screen that says so. It takes the top-centre band, and
   #hudCast / #toast3 / #hint step DOWN behind it rather than sharing it: a
   battle ending fires a broadcast in the same second the bar is still up, so
   "mutually exclusive by construction" (the arrangement #hint uses) would
   have meant the news covering the fight that caused it.

   pointer-events: none, deliberately. There is no button in here. Calling the
   army back is one tap away in the ARMY sheet, and a control inside a banner
   this thin cannot meet the 56px floor tools/shots/hud.js enforces without
   making the banner taller than the news it sits above. */
#battleBar {
  position: fixed; z-index: 14; pointer-events: none;
  top: calc(var(--edge) + 126px + env(safe-area-inset-top, 0px));
  left: calc(var(--edge) + var(--navcol) + 16px);
  right: calc(var(--edge) + var(--rcol) + 20px);
  display: flex; justify-content: center;
}
body.battleOn #hudCast,
body.battleOn #toast3,
body.battleOn #hint {
  top: calc(var(--edge) + 126px + 108px + env(safe-area-inset-top, 0px));
}
.battleCard {
  box-sizing: border-box; min-width: 300px; max-width: 100%;
  padding: 8px 16px 10px;
  background: rgba(20, 32, 46, .94); border: 4px solid var(--ink);
  border-left: 10px solid var(--accent, #93A6B8);
  border-radius: var(--radius-panel); box-shadow: 0 8px 0 rgba(0, 0, 0, .34);
  animation: castIn .26s cubic-bezier(.2, .9, .3, 1) both;
}
.battleCard.march { --accent: var(--gold); }
.battleCard.fight { --accent: var(--red-d); }
.battleCard.home  { --accent: var(--green-d); }
.battleTop { display: flex; align-items: center; gap: 10px; }
.battleTop b {
  font-size: 20px; letter-spacing: 2px; color: var(--accent, #fff);
  text-shadow: 0 2px 0 var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.battleTop i {
  margin-left: auto; font-style: normal; font-size: 17px; color: #CFE6F7;
  letter-spacing: .5px; white-space: nowrap;
}
/* One row per side: a label, a bar that drains, and the headcount. Two rows
   side by side would halve the bar, and the bar IS the readout — a
   ten-year-old reads "mine is longer than theirs" before he reads a number. */
.battleSide { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.battleSide span {
  width: 54px; flex: 0 0 auto; font-size: 14px; letter-spacing: 1px; color: #9FB6C9;
}
.battleSide u {
  flex: 1 1 auto; height: 14px; border-radius: 7px; text-decoration: none;
  background: rgba(255, 255, 255, .12); overflow: hidden; display: block;
}
.battleSide u em { display: block; height: 100%; border-radius: 7px; transition: width .3s linear; }
.battleSide.mine u em { background: var(--green-d); }
.battleSide.theirs u em { background: var(--red-d); }
.battleSide b {
  width: 34px; flex: 0 0 auto; text-align: right; font-size: 17px; color: #fff;
}

/* ===================== narrow / portrait pass ====================
   PORTRAIT IS AN ACCEPTANCE CRITERION (docs/11 §6b.8): Isaac turns the iPad
   without thinking about it, and the HUD must not clip or pile up when he
   does. Below 1000px the two side columns shrink, and below 840px — a
   portrait iPad — the bottom of the screen simply has not got room for a
   centre band between the joystick and the JUMP button, so the objective
   card moves ABOVE both of them (the same answer plot.css reached for its
   placement bar at 820px) and the action row keeps the floor. */
@media (max-width: 1000px) {
  /* --rcol stays 268: the counters chip hugs its own content and right-aligns
     inside it, so the column is a reservation for the centre band rather than a
     width anything is stretched to. `#rightHud`'s max-width:40vw is the guard. */
  :root { --bar: 70px; }
  button.mw { font-size: 20px; padding: 8px 14px; }
  #logo { font-size: 46px; }
  #logo .amp { font-size: 30px; }
  .charCard { width: 260px; }
  .charCard .cardFace { width: 104px; height: 104px; }
  .charCard .cardName { font-size: 36px; }
  .key { width: 56px; height: 56px; font-size: 23px; }
  .blank { width: 50px; height: 62px; font-size: 34px; }
  #hud { width: min(260px, 40vw); }
  #incomeCard #hMoney { font-size: 28px; }
  #incomeCard #hMoney.md { font-size: 24px; }
  #incomeCard #hMoney.sm { font-size: 20px; }
  /* narrow screens: keep the readability floor (>=18px body / >=24px buttons,
     docs/13 §7) but let the long affordance strings wrap instead of clipping */
  .wchip { font-size: 17px; }
  .wchip em { font-size: 13px; }
  .castTop b { font-size: 25px; }
  .castWho { font-size: 17px; }
  .objTop span { font-size: 22px; }
  .objPrize { font-size: 18px; }
  .objHow { font-size: 16px; }
  button.mw.actBtn { min-height: 72px; padding: 8px 16px; }
  .actBtn .actText b { font-size: 24px; }
  .actBtn .actText i { font-size: 15px; }
  .routCard h2 { font-size: 30px; }
  .routRibbon, #routBanner .routRibbon { font-size: 21px; padding: 7px 20px; }
}
/* PORTRAIT — 820x1180 is the iPad turned upright, and docs/11 §6b.8 says it
   must be playable, not merely un-crashed. Two things change:
     1. the resource stack leaves the bottom-left entirely (in portrait the
        joystick, plot.js's full-width placement bar at bottom 214, and the
        JUMP column between them leave no room) and hangs off the TOP-left,
        under the nav column;
     2. the objective card and the action row split what is left: the card
        takes the wide band at bottom 386, the row takes the floor between
        the joystick and JUMP.
   The card and plot.js's bar are never both on screen (hud.js empties
   #interactWrap while a ghost is live), so they may share that band. */
@media (orientation: portrait) and (max-width: 900px) {
  #hud {
    top: calc(var(--edge) + 380px + env(safe-area-inset-top, 0px));
    bottom: auto; width: min(300px, 62vw);
  }
  #interactWrap {
    left: calc(var(--edge) + env(safe-area-inset-left, 0px));
    right: calc(var(--edge) + 116px + env(safe-area-inset-right, 0px));
    bottom: calc(var(--joy) + 176px + env(safe-area-inset-bottom, 0px));
  }
  #actionRow {
    right: calc(170px + env(safe-area-inset-right, 0px));
    bottom: calc(var(--edge) + 6px + env(safe-area-inset-bottom, 0px));
    gap: 8px;
  }
  button.actSq { width: 76px; }
  button.actSq::before { width: 76px; height: 76px; }
  button.actSq > svg { width: 76px; height: 76px; padding: 16px; }
  button.actSq > span { font-size: 16px; }
  /* the badge follows the smaller square's corner; it is the ARMY count now,
     not the ribbon that used to lie across a SELL square's foot */
  button.actSq > u { top: -7px; right: -8px; font-size: 13px; padding: 4px 6px; }
  /* plot.js centres #tiPlotTitle on the SCREEN and lets it grow to
     100vw - 96px. In portrait that is 724 of 820 pixels, so it reaches into
     the top-right corner and prints PLANTING WINDMILL over the caps row and
     the ally chip. Landscape has room for both; portrait does not, so the
     status column drops below the title's band instead. */
  #rightHud {
    top: calc(var(--edge) + 126px + env(safe-area-inset-top, 0px));
    width: min(var(--rcol), 52vw);
  }
  /* THE BATTLE BAR NEEDS THE WHOLE WIDTH HERE. In portrait #rightHud drops
     into the same band the bar wants, and the bar's landscape right-inset
     (past a 268px column that is no longer up there) left it about three
     hundred pixels wide — enough to print "MARCHIN…" and stop. Photographed
     at 820x1180 before it was believed. So it takes its own row UNDER the
     counters chip and keeps only the left inset, which is all it needs to
     clear the nav column. Everything that shares the band follows it down;
     the money stack moves too, because at this width it is the only other
     thing in the way and #hudCast's card is 101px tall. */
  #battleBar {
    top: calc(var(--edge) + 196px + env(safe-area-inset-top, 0px));
    right: calc(var(--edge) + env(safe-area-inset-right, 0px));
  }
  body.battleOn #hudCast,
  body.battleOn #toast3,
  body.battleOn #hint {
    top: calc(var(--edge) + 286px + env(safe-area-inset-top, 0px));
  }
  body.battleOn #hud {
    top: calc(var(--edge) + 430px + env(safe-area-inset-top, 0px));
  }
}
@media (max-height: 700px) {
  #logo { font-size: 40px; margin-top: 0; }
  .tagline { margin-bottom: 10px; font-size: 18px; }
  .charCard { width: 240px; padding: 12px; }
  .charCard .cardFace { width: 88px; height: 88px; }
  .blank { height: 56px; }
  .key { width: 56px; height: 56px; font-size: 22px; }  /* never below the 56px touch floor */
  #loginInner { padding-top: 16px; }
  #topbar { gap: 4px; }
  #topbar button > svg, #topbar #btnMap > svg { width: 62px; height: 62px; }
  #hud { bottom: calc(var(--joy) + 20px + env(safe-area-inset-bottom, 0px)); }
  #hudCast, #hint { top: calc(var(--edge) + 112px + env(safe-area-inset-top, 0px)); }
  #toast3 { top: calc(var(--edge) + 258px + env(safe-area-inset-top, 0px)); }
}
