@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700;900&family=Cinzel+Decorative:wght@400;700&family=EB+Garamond:ital,wght@0,400;0,600;0,700;1,400&display=swap');
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent;user-select:none}
html{height:100%;overscroll-behavior:none}
/* iPhone top/right cut-off fix, part 1 (CSS): this used to be
   `min-height:100dvh` on BOTH body and #app. Because
   `*{box-sizing:border-box}` is set above, body's safe-area padding
   (top/bottom/left/right) is supposed to eat into its own 100dvh box --
   but #app's own `min-height:100dvh` ignored that and demanded the FULL
   100dvh again on top of it, so on any notched/Dynamic-Island iPhone
   (nonzero safe-area-inset-top) the total content came out taller than
   one screen's worth. Fixed by giving body a single fixed
   `height:100dvh` (not min-height) with `overflow:hidden`, and having
   #app fill that exact box via `height:100%` instead of re-asserting
   its own 100dvh.
   Part 2: `overflow:hidden` alone stops CSS from *painting* overflow,
   but does NOT stop iOS Safari's native touch-drag/rubber-band gesture
   from panning the whole page a few pixels regardless of whether
   there's any real overflow -- that's what let the topbar cut-off keep
   happening after part 1 (reported as "I can shift the whole screen up
   and down").
   NOTE: an earlier pass here tried `position:fixed` on html/body plus a
   JS-level global touchmove blocker as a second layer. That combination
   was too heavy-handed -- it broke internal scrolling AND general touch
   responsiveness (button taps) across the app, because `position:fixed`
   changes the containing-block chain for descendants' percentage
   heights, and the JS touchmove guard's scrollable-ancestor detection
   didn't reliably match every real scroll panel, so it ended up
   preventDefault-ing touches it shouldn't have -- which some browsers
   also read as cancelling the tap/click that would otherwise follow.
   Reverted both. `touch-action` is the correct, purpose-built CSS tool
   for exactly this problem and needs no JS at all: `none` on body blocks
   the page-level pan/bounce gesture outright, while every genuine
   internal scroll panel gets `touch-action:pan-y` explicitly so it keeps
   scrolling normally (see "TOUCH-ACTION SCROLL PANELS" further down). */
body{background:#1a120a;display:flex;justify-content:center;height:100dvh;overflow:hidden;overscroll-behavior:none;
  touch-action:none;
  padding-top:env(safe-area-inset-top);padding-bottom:env(safe-area-inset-bottom);
  padding-left:env(safe-area-inset-left);padding-right:env(safe-area-inset-right);}
#app{width:100%;max-width:500px;background:var(--pch-walnut-dark);display:flex;flex-direction:column;position:relative;height:100%}

/* -- SCREEN TRANSITIONS ------------------------------------------------
   Every top-level screen (all 13 ids matching screen-* -- see the list
   in _hideAllTopScreens() in game.js) switches via instant
   style.display='none'/'flex' toggling in JS, with no fade or slide at
   all. This is a pure CSS addition instead: keyframe animations replay
   automatically whenever an element re-enters the render tree (i.e.
   display:none -> flex), so a single attribute-selector rule animates
   every one of those ~13 showXScreen() call sites' transitions without
   editing any of them individually, and without touching the hide side
   (screens still disappear instantly -- only the incoming screen
   animates in, not a cross-fade of the outgoing one, to keep this a
   zero-risk CSS-only change rather than reworking the hide timing
   everywhere). Confirmed via grep that no other element's id happens to
   start with "screen-" and get caught by this unintentionally, and that
   no screen-* element already sets its own transform/opacity/animation
   that this would fight with. #screen-title starts as display:flex from
   the base CSS (not JS-toggled on first load), so this also plays once
   automatically as a title-screen intro fade for free. */
[id^="screen-"]{animation:screenFadeIn .22s ease-out;}
@keyframes screenFadeIn{
  from{opacity:0;transform:translateY(6px) scale(.99);}
  to{opacity:1;transform:none;}
}

/* -- GREGOR CURSOR --------------------------------------------------
   Custom mouse cursor (Gregor's face) replacing the OS arrow wherever
   one would actually be visible. Touchscreens never render a CSS cursor
   image at all regardless of this rule, so no separate touch-vs-mouse
   detection is needed -- this only ever shows up on an actual mouse.
   Toggled on at page load via `html.gregor-cursor` (see index.html) so
   it applies before any script even runs, with the "click" variant
   swapped in on mousedown and back out on mouseup (see
   _initGregorCursor() in game.js). Both images are 40x48 -- the
   originally-uploaded 165x200 portraits were far too large to reliably
   render as an actual OS cursor, so they were downscaled first.
   Uses `*` + !important specifically because dozens of buttons across
   this file already declare their own `cursor:pointer`, which would
   otherwise beat a plain `html{cursor:...}` rule for every one of them
   (an element's own more-specific cursor rule normally wins). The
   battle board's drag-to-pan state (canvas#gc.dragging) used to fall
   back to the OS "grabbing" hand here since it's real functional UX
   feedback mid-drag, not just a generic pointer -- but per developer
   request it now reuses the gregor_pointer_click asset instead, so the
   custom cursor never gets interrupted by the native one. Still carved
   out with its own higher-specificity selector (rather than just
   letting the two rules above cascade) in case pan feedback ever needs
   to diverge from the plain click state again later. */
html.gregor-cursor *{cursor:url('assets/img/ui/gregor_pointer.webp') 4 4,auto !important;}
html.gregor-cursor.gregor-cursor-click *{cursor:url('assets/img/ui/gregor_pointer_click.webp') 4 4,auto !important;}
html.gregor-cursor canvas#gc.dragging{cursor:url('assets/img/ui/gregor_pointer_click.webp') 4 4,auto !important;}


/* ══════════════════════════════════════════════════════════════════
   LOADING SCREEN — shown before anything else; fades out once both
   the sprite images and the core audio have finished loading (see
   _updateLoadingBar()/_maybeFinishLoading() in game.js).
   ══════════════════════════════════════════════════════════════════ */
#loading-screen{
  position:fixed;inset:0;z-index:10000;
  display:flex;align-items:center;justify-content:center;
  background:#0c0804; /* shows briefly before loading_screen.png itself decodes */
  opacity:1;transition:opacity .6s ease-out;
}
#loading-screen.loaded{opacity:0;pointer-events:none;}
#loading-bg{
  position:absolute;inset:0;z-index:0;
  background:url('assets/img/backgrounds/loading_screen.webp') center/cover no-repeat;
}
/* Faint vignette so the label/bar stay legible over whatever art sits
   behind them, same technique used on the reward screen's backdrop. */
#loading-bg::after{
  content:'';position:absolute;inset:0;
  background:radial-gradient(ellipse at 50% 68%,rgba(0,0,0,.15) 0%,rgba(0,0,0,.55) 100%);
}
#loading-content{
  display:flex;flex-direction:column;align-items:center;gap:10px;
  width:100%;
  /* Bottom-center, per developer spec, with room to breathe above the
     safe-area edge on notched phones. */
  position:absolute;z-index:1;left:50%;bottom:max(28px,env(safe-area-inset-bottom) + 20px);
  transform:translateX(-50%);
  padding:0 5vw;
}
#loading-label{
  font-family:'Cinzel',serif;font-size:13px;font-weight:700;letter-spacing:3px;
  text-transform:uppercase;color:var(--pch-gold-bright);
  text-shadow:0 0 14px rgba(255,190,80,.6),0 2px 4px rgba(0,0,0,.8);
}
@keyframes loadingDotsBlink{0%,100%{opacity:.2;}50%{opacity:1;}}
#loading-dots{animation:loadingDotsBlink 1.4s ease-in-out infinite;}
#loading-bar-wrap{
  position:relative;
  width:min(560px,82vw);
  aspect-ratio:1056/185;
}
#loading-bar-frame{
  position:absolute;inset:0;z-index:2;
  width:100%;height:100%;object-fit:contain;
  pointer-events:none;
  filter:drop-shadow(0 4px 14px rgba(0,0,0,.6));
}
/* Insets re-measured directly against the asset's alpha channel (the
   previous 28%/18.5% values were wrong -- verified via pixel analysis:
   track opening spans x=183-988 of 1056px width, y-bounds were already
   accurate). This is what was causing the visible gap between the
   medallion and the start of the fill. */
#loading-bar-fill-track{
  position:absolute;z-index:1;
  left:17.3%;right:6.6%;top:37%;bottom:33%;
  border-radius:999px;overflow:hidden;
  background:rgba(10,4,0,.55);
  box-shadow:inset 0 1px 3px rgba(0,0,0,.7);
}
#loading-bar-fill{
  position:relative;height:100%;width:0%;overflow:hidden;
  border-radius:0 999px 999px 0;
  background:linear-gradient(180deg,#fff2c8 0%,#f0c860 35%,#c8860c 65%,#8a5406 100%);
  box-shadow:0 0 12px rgba(255,200,90,.75),inset 0 1px 0 rgba(255,255,255,.5);
  transition:width .3s ease-out;
}
#loading-bar-shimmer{
  position:absolute;inset:0;width:220%;
  background:linear-gradient(100deg,transparent 35%,rgba(255,255,255,.65) 50%,transparent 65%);
  animation:loadingShimmer 1.5s linear infinite;
}
@keyframes loadingShimmer{
  from{transform:translateX(-45%);}
  to{transform:translateX(0%);}
}
#loading-pct{
  font-family:'Cinzel',serif;font-size:10px;letter-spacing:1.5px;
  color:#d8c090;text-shadow:0 1px 3px rgba(0,0,0,.8);
}

/* ══════════════════════════════════════════════════════════════════
   FANTASY RPG THEME — shared tokens (FFT/Tactics Ogre/Fell Seal style).
   Palette per design brief. Consumed by every screen in the game.
   ══════════════════════════════════════════════════════════════════ */
:root{
  --pch-parchment:#E5D6B5;
  --pch-parchment-light:#f0e4c8;
  --pch-parchment-dark:#d4c29c;
  --pch-walnut:#4B3727;
  --pch-walnut-dark:#332316;
  --pch-bronze:#8C6A35;
  --pch-bronze-light:#a8855a;
  --pch-gold:#D6B24A;
  --pch-gold-bright:#ecc868;
  --pch-gold-dim:#a8893a;
  --pch-navy:#2C3E63;
  --pch-navy-dark:#1c2840;
  --pch-burgundy:#6B2D2D;
  --pch-burgundy-dark:#4a1f1f;
  --pch-stone:#726C63;
  --pch-stone-dark:#4e4a44;
  --pch-forest:#4F8A4D;

  /* semantic aliases (kept so earlier work still reads sensibly) */
  --pch-bg:var(--pch-parchment);
  --pch-bg-light:var(--pch-parchment-light);
  --pch-bg-dark:var(--pch-parchment-dark);
  --pch-panel:var(--pch-parchment);
  --pch-panel-2:var(--pch-parchment-light);
  --pch-ink:var(--pch-walnut);
  --pch-ink-soft:#5c4530;
  --pch-ink-faint:var(--pch-stone);
  --pch-border:var(--pch-bronze);
  --pch-border-dark:var(--pch-walnut);
  --pch-shadow:0 2px 6px rgba(40,28,8,.35);

  /* ── Polish Pass 2: hierarchy tones (#2) ── */
  --pch-tone-primary:var(--pch-parchment-light);   /* lightest — HUD, action menu, mission cards */
  --pch-tone-secondary:var(--pch-parchment-dark);  /* mid — headers, footers, info bars */
  --pch-tone-frame:var(--pch-walnut-dark);         /* darkest — decorative outer frames only */

  /* ── Polish Pass 2: layered shadow system (#1, #12) ── */
  --pch-shadow-soft:0 1px 3px rgba(35,22,8,.28);
  --pch-shadow-panel:0 3px 10px rgba(30,18,6,.4),0 1px 0 rgba(255,255,255,.18) inset;
  --pch-shadow-inset:inset 0 1px 3px rgba(35,22,8,.35),inset 0 -1px 0 rgba(255,255,255,.15);
  --pch-shadow-raised:0 2px 5px rgba(20,12,4,.45),0 1px 0 rgba(255,255,255,.1) inset;
}
/* Shared "shiny dual-tone gold" text treatment -- background-clip:text
   gradient (bright cream-gold at top fading to deeper bronze-gold at the
   bottom) plus a drop-shadow stack for legibility/glow. Originally built
   for the Collection Rewards title; per developer request this exact
   color/look is now the single shared definition reused by the Army
   Builder/Select titles and the Collection reward names (e.g. "100
   Coins") too, instead of three separate near-identical gradients that
   could drift out of sync. -webkit-text-fill-color:transparent is
   required alongside background-clip:text for the gradient to actually
   show through in WebKit; color is a solid gold fallback otherwise. */
.gold-shine-text{
  color:var(--pch-gold-bright);
  background:linear-gradient(180deg,#fff3c8 0%,var(--pch-gold-bright) 35%,var(--pch-gold) 65%,var(--pch-gold-dim) 100%);
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;
  filter:drop-shadow(0 1px 0 rgba(255,255,255,.5)) drop-shadow(0 2px 2px rgba(0,0,0,.85)) drop-shadow(0 0 8px rgba(255,205,90,.45));
}
.pch-corner{
  position:absolute;width:16px;height:16px;pointer-events:none;
  border:2px solid var(--pch-bronze);opacity:.9;
}
.pch-corner.tl{top:-1px;left:-1px;border-right:none;border-bottom:none;}
.pch-corner.tr{top:-1px;right:-1px;border-left:none;border-bottom:none;}
.pch-corner.bl{bottom:-1px;left:-1px;border-right:none;border-top:none;}
.pch-corner.br{bottom:-1px;right:-1px;border-left:none;border-top:none;}

/* ── Polish Pass 2 (#1): layered carved-panel construction ──
   Dark frame → bronze trim → parchment inset, with paper texture and soft shadows.
   Apply .pch-panel to any container; nests bronze border + parchment fill automatically. */
.pch-panel{
  position:relative;
  background:
    radial-gradient(ellipse at 30% 20%,rgba(255,255,255,.10),transparent 55%),
    linear-gradient(180deg,var(--pch-tone-primary),var(--pch-parchment));
  border:1px solid var(--pch-bronze);
  border-radius:6px;
  box-shadow:var(--pch-shadow-panel);
}
.pch-panel::before{
  content:'';position:absolute;inset:2px;pointer-events:none;border-radius:4px;
  border:1px solid rgba(75,55,39,.18);
  box-shadow:var(--pch-shadow-inset);
}
.pch-panel.pch-panel-secondary{
  background:
    radial-gradient(ellipse at 30% 20%,rgba(255,255,255,.06),transparent 55%),
    linear-gradient(180deg,var(--pch-tone-secondary),var(--pch-parchment-dark));
}
.pch-panel.pch-panel-frame{
  border-color:var(--pch-walnut);
  background:linear-gradient(180deg,var(--pch-walnut),var(--pch-tone-frame));
  box-shadow:var(--pch-shadow-raised);
}

/* Reusable carved fantasy button (#4: bevel, top highlight, inner shadow, soft drop shadow) */
.pch-btn{
  font-family:'Cinzel',serif;letter-spacing:.6px;cursor:pointer;
  border-radius:3px;border:1px solid var(--pch-bronze);
  background:linear-gradient(180deg,var(--pch-walnut),var(--pch-walnut-dark));
  color:var(--pch-gold-bright);
  box-shadow:0 1px 0 rgba(255,255,255,.14) inset,0 -1px 2px rgba(0,0,0,.3) inset,var(--pch-shadow-soft);
  transition:filter .12s,transform .08s,box-shadow .12s;
}
.pch-btn:hover{filter:brightness(1.12);box-shadow:0 0 7px var(--pch-gold)77,0 1px 0 rgba(255,255,255,.14) inset,var(--pch-shadow-soft);}
.pch-btn:active{transform:translateY(1px) scale(.98);filter:brightness(.9);box-shadow:inset 0 2px 3px rgba(0,0,0,.4);}
.pch-btn.pch-btn-primary{background:linear-gradient(180deg,var(--pch-navy),var(--pch-navy-dark));border-color:var(--pch-gold);}
.pch-btn.pch-btn-danger{background:linear-gradient(180deg,var(--pch-burgundy),var(--pch-burgundy-dark));border-color:#8a4040;color:#f0d8c8;}
.pch-btn.pch-btn-disabled,.pch-btn:disabled{background:linear-gradient(180deg,var(--pch-stone),var(--pch-stone-dark));border-color:var(--pch-stone-dark);color:#cfc8bc;opacity:.75;pointer-events:none;filter:saturate(.4);box-shadow:none;}

/* ══ DEPLOYMENT SCREEN ══════════════════════════════════════════ */
#screen-place{display:flex;flex-direction:column;flex:1;background:var(--pch-walnut-dark);position:relative;}
/* "DEPLOY YOUR FORCES" header -- now the actual gold-parchment banner
   artwork (assets/img/ui/panel_gold_parchment.webp) instead of a flat CSS
   gradient, matching the Chapter/Level Select banner-card upgrade. */
#place-header{
  background-color:#3a2c1c;
  border-style:solid;border-width:13px;border-image-repeat:stretch;
  border-image-slice:90 fill;border-image-width:13px;
  border-image-source:url('assets/img/ui/panel_gold_parchment.webp');
  padding:7px 12px 6px;flex-shrink:0;position:relative;
}
#place-title{color:#241706;font-size:14px;font-weight:700;letter-spacing:2px;font-family:'Cinzel Decorative',serif;text-shadow:0 1px 0 rgba(255,255,255,.5)}
#place-sub{color:#3a2c18;font-size:10px;margin-top:2px;letter-spacing:.5px;font-style:italic;font-weight:600}
/* Column, not row -- was flex-direction:row (the default) with
   flex-wrap:wrap, which let the "X / Y placed" text wrap onto its own
   line below the gem row whenever both didn't fit on one line, but
   left-aligned within #pts-bar's own box instead of centered under the
   gems above it (row-wrap doesn't re-center a wrapped line). Explicit
   column + center puts the gem row and the text on their own centered
   lines every time, not just when a wrap happens to occur. */
#pts-bar{display:flex;flex-direction:column;align-items:center;gap:6px;margin-top:5px;}
#pts-bar-slot-label,#pts-bar-cost-label{color:#4a3418;font-size:9px;font-weight:600}
/* Deployment slot gems (levels 1-8's fixed-slot system) -- more jewel-
   like polish than the old flat parchment diamond: a soft radial
   highlight for some depth/shine, warmer gold-toned border even in the
   empty state, subtle glow. .used keeps its own distinct (brighter,
   glowing) treatment below, unchanged. */
.pt-gem{
  width:16px;height:19px;border-radius:0 0 3px 3px/0 0 6px 6px;
  clip-path:polygon(0% 0%,100% 0%,100% 55%,50% 100%,0% 55%);
  background:radial-gradient(circle at 35% 25%,var(--pch-parchment-light),var(--pch-parchment-dark) 70%);
  border:1.5px solid var(--pch-bronze);
  box-shadow:var(--pch-shadow-inset),0 1px 3px rgba(0,0,0,.35);
  transition:all .25s;
}
.pt-gem.used{background:linear-gradient(160deg,var(--pch-gold-bright),var(--pch-bronze) 60%,var(--pch-walnut));border-color:var(--pch-gold);box-shadow:0 0 6px var(--pch-gold)88,0 1px 0 rgba(255,255,255,.3) inset;}
/* Slots-mode readout ("X / Y placed") -- decorative font + a warmer gold
   tone for a bit more polish, alongside the gem/centering work above.
   Kept more modest than .pts-num below (see #place-slots-panel
   #deploy-pts-remaining) -- this is a supporting label next to the gem
   row, which is the primary visual here, not a standalone hero number
   the way the cost-mode points figure is. */
#pts-remaining,#deploy-pts-remaining{color:#e8c878;font-size:10px;margin-left:2px;font-weight:700;font-family:'Cinzel Decorative',serif;}
/* flex:1 (not a fixed height) so this panel fills whatever vertical
   space is left over after the header/roster/footer take theirs.
   min-height keeps it from collapsing too far on short screens. */
#place-board-wrap{position:relative;width:100%;flex:1 1 230px;min-height:170px;background:radial-gradient(ellipse at 50% 30%,#3a2c1c,#1a120a);overflow:hidden;border-bottom:1px solid var(--pch-walnut)}
canvas#pc{position:absolute;top:0;left:0;image-rendering:pixelated}
/* "Tap a yellow tile..." tooltip -- now the stone-and-vine panel artwork,
   an ambient/environmental banner sitting right over the battlefield.
   Narrowed (thinner border + tighter padding) so it sits lower-profile
   over the map. */
#place-instructions{
  position:absolute;left:8px;right:8px;bottom:8px;
  background-color:rgba(20,14,8,.5);
  border-style:solid;border-width:9px;border-image-repeat:stretch;
  border-image-slice:90 fill;border-image-width:9px;
  border-image-source:url('assets/img/ui/panel_stone_vine.webp');
  padding:6px 24px 6px 8px;display:flex;align-items:center;
  transition:opacity .18s,transform .18s;
}
#place-instructions.hidden{opacity:0;transform:translateY(6px);pointer-events:none;}
#place-instructions-text{color:#241c10;font-size:10px;line-height:1.35;letter-spacing:.2px;font-weight:600;text-shadow:0 1px 1px rgba(255,255,255,.3);}
#place-instructions-close{
  position:absolute;top:4px;right:5px;background:none;border:none;color:#4a3418;
  font-size:13px;line-height:1;cursor:pointer;padding:2px 4px;font-weight:700;
}
#place-instructions-close:hover{color:#1a1006;}
/* "Choose your warriors" panel -- framed with the same Gregor banner
   artwork used for his dialogue boxes (assets/img/ui/gregor_banner.webp).
   Always visible now (there's enough vertical room without hiding it
   during placement) -- but capped to a single row of ~4 cards via
   #roster's nowrap + ROSTER_VISIBLE=4 in game.js, so it can never eat
   more than one row's worth of height. Extra units are one tap away via
   the prev/next arrows either side. */
#roster-section{
  padding:13px 12px 10px;flex-shrink:0;
  background-color:#1a1108;
  border-style:solid;border-width:15px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:15px;
  border-image-source:url('assets/img/ui/gregor_banner.webp');
  margin:6px 8px 0;
}
#exc-place-btn{
  display:block;margin:0 0 6px;width:100%;
  background:linear-gradient(180deg,#3a3010,#221c08);
  color:var(--pch-gold-bright);border:1px solid var(--pch-gold);border-radius:4px;
  font-size:9px;font-family:'Cinzel',serif;letter-spacing:.5px;padding:6px 8px;cursor:pointer;font-weight:700;
}
#exc-place-btn:hover{filter:brightness(1.15);}
#exc-place-btn.active{background:linear-gradient(180deg,#6a5818,#3a2e0c);box-shadow:0 0 8px var(--pch-gold)77;}
#roster-wrap{display:flex;align-items:center;gap:6px}
#roster{display:flex;gap:6px;overflow-x:auto;overflow-y:hidden;touch-action:pan-x;flex:1;flex-wrap:nowrap;scroll-behavior:smooth;scrollbar-width:none;}
#roster::-webkit-scrollbar{display:none;}
.roster-scroll-btn{background:linear-gradient(180deg,var(--pch-bronze-light),var(--pch-bronze));border:1px solid var(--pch-gold);border-radius:4px;color:#2a1c0c;font-size:21px;width:33px;height:144px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all .15s;padding:0}
.roster-scroll-btn:active{transform:scale(.93) translateY(1px);filter:brightness(.9)}
.roster-scroll-btn:disabled{opacity:.3;cursor:default}
/* BUGFIX: #place-footer, #place-instructions-panel (+icon/text), and
   #place-slots-panel (+title/row/gem/labels) previously had NO base
   (unconditional) styling at all -- every rule for them lived inside
   `@media (orientation:landscape)`. This game is landscape-locked in
   practice (see every "Landscape footer" comment throughout this file),
   but iOS Safari's `orientation` media feature can fail to match during
   transient states (address-bar show/hide, dynamic viewport resize) --
   when that happens these three panels fell back to fully unstyled
   block-level divs stacking vertically with zero layout, which is why
   the deploy screen intermittently showed nothing but a single icon and
   an unstyled button in the top-left corner, with the roster/slots
   panel pushed off-screen below the fold ("no banner at all to deploy
   units" on iPhone). Promoting the landscape layout to the unconditional
   default (the tighter phone-landscape media query further down still
   applies its own additional size compaction on top of this) removes
   the dependency on that media-feature detection succeeding at all.
   #roster-section's OLDER portrait-only styling above (border-image from
   gregor_banner.png, stacked margin) is superseded by this override,
   consistent with the game only ever actually being played landscape. */
#place-footer{
  position:absolute;bottom:8px;left:50%;transform:translateX(-50%);
  z-index:15;
  display:flex;flex-direction:row;align-items:stretch;
  gap:12px;padding:0;
  /* BUGFIX: was width:auto, relying on the browser's "shrink-to-fit"
     algorithm for an absolutely-positioned flex container to size this
     bar around its children. In Dev Mode (76-unit roster vs Player
     Mode's 2-9), Safari/iPhone was computing that shrink-to-fit size
     from the roster's full UNCLIPPED content width (~4800px+) rather
     than its actual rendered/scrolled width (which is correctly clamped
     -- confirmed via live device inspection), pushing the whole bar off
     both edges of the screen ("no banner at all" in Dev Mode on iPhone).
     Explicit width removes that ambiguous calculation entirely: 1200px
     is the exact sum of the three panels below (216+744+216+2x12 gap) at
     this tier -- scaled 50% up from the original 800px (144+496+144+2x8)
     per developer request, everything below scaled by the same factor --
     capped against the real viewport via calc(100vw - 16px) so it also
     works correctly on narrower phones -- see #roster-section below,
     which is now allowed to shrink (flex:0 1 744px) to absorb any
     deficit on a narrow screen while the two side panels stay fixed. */
  width:min(1200px,calc(100vw - 16px));
}
#place-instructions-panel{
  flex:0 0 216px;min-width:0;
  background:rgba(20,14,8,.55);border:1px solid rgba(200,160,80,.4);
  border-radius:6px;box-shadow:0 2px 8px rgba(0,0,0,.4);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:9px;padding:13.5px;text-align:center;
}
/* &#127807; leaf icon removed per developer request (freed-up vertical
   room went to bigger instruction text below instead) -- #place-
   instructions-panel-icon's CSS rule removed along with it, here and in
   its two other copies below.
   BUGFIX: 20px turned out way too large for this panel's fixed 216px
   width once wrapped -- Cinzel Decorative is a notably wide/bold face,
   so at 20px "TAP A YELLOW TILE..." wrapped across many lines, growing
   this panel far taller than the roster/slots panels beside it. Since
   #place-footer stretches all three panels to match the tallest one
   (align-items:stretch), that blew out the ENTIRE banner's height, not
   just this one panel -- see the screenshot that caught this. Dropped
   back down to a size that keeps each line to 2-3 wrapped lines at most
   within this panel's actual width. */
#place-instructions-panel-text{
  font-size:11px;color:#f0e0c0;line-height:1.4;
  font-family:'Cinzel Decorative',serif;font-weight:700;
  text-shadow:0 1px 3px rgba(0,0,0,.5);
}
#place-instructions-panel-text div{margin-bottom:4px;}
#place-instructions-panel-text div:last-child{margin-bottom:0;}
#roster-section{
  /* flex-shrink now 1 (was 0) -- on a narrow phone this panel absorbs
     the deficit instead of forcing the fixed-width side panels or the
     bar itself to overflow. Its own internal roster strip already
     scrolls (#roster{overflow-x:auto}), so shrinking it below 496px
     just means fewer cards visible before a scroll, never any clipping
     or layout breakage. min-width floor keeps the scroll buttons + at
     least one card usable even on a very small screen. */
  flex:0 1 744px;min-width:210px;margin:0;padding:12px 9px;
  background:rgba(20,14,8,.55);border:1px solid rgba(200,160,80,.4);
  border-radius:6px;box-shadow:0 2px 8px rgba(0,0,0,.4);
}
#roster-section .roster-scroll-btn{height:168px;width:36px;}
#roster-section img.rc-portrait{max-width:117px;}
#place-slots-panel{
  flex:0 0 216px;min-width:0;
  background:rgba(20,14,8,.55);border:1px solid rgba(200,160,80,.4);
  border-radius:6px;box-shadow:0 2px 8px rgba(0,0,0,.4);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:10.5px;padding:13.5px;
}
#place-slots-title{
  font-family:'Cinzel',serif;font-size:15.75px;font-weight:700;letter-spacing:1.3px;
  color:var(--pch-gold-bright);text-transform:uppercase;
  text-shadow:0 1px 2px rgba(0,0,0,.6);text-align:center;
}
#place-slots-row{display:flex;gap:10.5px;justify-content:center;}
#place-slots-panel .pt-gem{width:45px;height:54px;}
#place-slots-panel #pts-bar-slot-label{display:none;}
#place-slots-panel #pts-remaining,
#place-slots-panel #pts-bar-cost-label,
#place-slots-panel #deploy-pts-remaining{
  font-size:15px;color:#e8c878;text-align:center;font-family:'Cinzel Decorative',serif;
}
/* The points/slots NUMBER itself, much bigger and more decorative than
   the supporting label text around it -- same gold + glow treatment
   already established for #reward-coins-amount (Collection/reward
   screens), reused here so it reads as the same "this is a big deal"
   gold-number style throughout the game rather than inventing a new one.
   .pts-num specifically wraps just the number in cost-deploy mode's "N
   pts remaining" text (see updatePtsUI() in game.js) -- slots-mode's own
   "X / Y placed" text is deliberately NOT wrapped this way; the gem row
   is the primary visual for slots (see point 3 polish above), so that
   text stays a supporting label rather than competing for attention. */
#place-slots-panel .pts-num{
  display:inline-block;font-size:32px;line-height:1;
  color:#ffe060;text-shadow:0 0 10px #ffaa00;
  vertical-align:middle;
}
/* Unit cards -- brown-leather panel artwork as the card face (matching
   Chapter/Level Select), now sized to match the Army Builder's own
   selected-units tokens: bigger full-body composite art + a single name
   line underneath, no separate job/cost text (the cost is already baked
   into the top-right corner of the composite artwork itself). */
.roster-card{
  min-width:126px;flex:0 0 126px;
  background-color:#241a10;
  border-style:solid;border-width:15px;border-image-repeat:stretch;
  border-image-slice:90 fill;border-image-width:15px;
  border-image-source:url('assets/img/ui/panel_brown_leather.webp');
  padding:12px 6px 9px;cursor:pointer;
  display:flex;flex-direction:column;align-items:center;gap:6px;
  transition:transform .15s,filter .15s;
  position:relative;overflow:hidden;
  /* Without these, mousedown+move on the portrait <img> below starts the
     browser's OWN native "ghost image" drag (visible as the image trailing
     the cursor with no drop handling) instead of the custom Pointer-Events
     drag-and-drop in game.js -- this is what was actually being seen, not
     our #roster-drag-ghost. -webkit-user-drag covers Chrome/Safari/Edge;
     touch-action:none stops touch-drag from being interpreted as a page
     scroll gesture instead of a unit drag. */
  -webkit-user-drag:none;user-select:none;touch-action:none;
}
.roster-card:hover:not(.disabled-card):not(.selected){transform:translateY(-2px);filter:brightness(1.12);}
.roster-card.disabled-card{opacity:.4;filter:grayscale(.5);}
.pt-gem.hidden-gem{visibility:hidden;}
.roster-card.selected{
  border-image-source:url('assets/img/ui/panel_gold_parchment.webp');
  filter:brightness(1.1);
  box-shadow:0 0 12px var(--pch-gold)66;
  transform:translateY(-3px) scale(1.03);
}
.roster-card.selected::after{content:'\2605';position:absolute;top:2px;right:3px;color:var(--pch-gold);font-size:12px;text-shadow:0 0 4px rgba(0,0,0,.4)}
img.rc-portrait{width:100%;max-width:105px;flex:1 1 auto;min-height:0;min-width:0;object-fit:contain;image-rendering:pixelated;-webkit-user-drag:none;user-select:none;pointer-events:none;}
.rc-name{
  color:#ffffff;font-size:13.5px;font-weight:700;letter-spacing:.2px;font-family:'Cinzel',serif;
  text-align:center;max-width:100%;line-height:1.2;text-shadow:0 1px 2px rgba(0,0,0,.7);
  /* Was a single nowrap line with text-overflow:ellipsis, which cut off
     anything longer than ~8-9 characters ("WATER M...", "EARTH A..." --
     see the deploy roster screenshot). Nothing actually constrains
     .roster-card to a fixed height (it's sized by its own content, not
     stretched to match .roster-scroll-btn), so there was room for a 2nd
     line the whole time. -webkit-line-clamp:2 wraps normally and only
     falls back to an ellipsis if a name is still too long for 2 full
     lines at this width -- in practice that shouldn't happen for any
     unit name in the roster. */
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
  overflow:hidden;white-space:normal;word-break:break-word;
}
/* Roster-card drag-and-drop (see the drag IIFE in game.js, right after the
   deploy pan handlers). Dims the source card while its unit is being
   dragged, and #roster-drag-ghost is the floating token image that follows
   the pointer/finger from the card to wherever it's dropped on the map. */
.roster-card.rc-dragging{opacity:.35;}
#roster-drag-ghost{
  display:none;position:fixed;top:0;left:0;z-index:95;
  width:84px;height:84px;object-fit:contain;pointer-events:none;
  filter:drop-shadow(0 4px 10px rgba(0,0,0,.6));
  transform:translate(-50%,-50%);opacity:.92;
}
#roster-drag-ghost.show{display:block;}
/* Floating map actions -- replaces the old static "Place units to begin"
   footer bar entirely. Both buttons sit directly over the top-right of
   the battlefield (out of the way of the tap-tile tooltip at the bottom)
   and stay invisible/inert until at least one unit has been placed --
   see updatePtsUI()'s classList.toggle('show', ...) calls. */
#deploy-map-actions{
  position:absolute;top:8px;right:8px;z-index:6;
  display:flex;flex-direction:column;align-items:stretch;gap:6px;
}
/* Battle button -- blue/lion heraldic banner (the primary call-to-action) */
#start-battle-btn{
  background-color:#0e1a30;
  border-style:solid;border-width:10px;border-image-repeat:stretch;
  border-image-slice:90 fill;border-image-width:10px;
  border-image-source:url('assets/img/ui/panel_blue_lion.webp');
  color:#f0e0a0;padding:8px 18px;font-size:10px;font-family:'Cinzel',serif;
  cursor:pointer;letter-spacing:.8px;font-weight:700;
  opacity:0;transform:translateX(8px) scale(.92);pointer-events:none;
  transition:opacity .2s,transform .2s;
}
#start-battle-btn.show{opacity:1;transform:translateX(0) scale(1);pointer-events:auto;}
#start-battle-btn:hover{filter:brightness(1.2);}
#start-battle-btn:active{transform:translateX(0) scale(.97) translateY(1px);}
/* Green neon "ready" glow -- added once every deployment slot is used
   (see updatePtsUI() in game.js), since early testers didn't realise
   they needed to press this button to start the fight. Pulses so it
   reads as "press me" rather than just a static highlight. */
#start-battle-btn.ready-glow{
  animation:battleReadyPulse 1.1s ease-in-out infinite;
}
@keyframes battleReadyPulse{
  0%,100%{box-shadow:0 0 6px 2px rgba(60,255,120,.55),0 0 0 1px rgba(80,255,140,.4);filter:brightness(1);}
  50%{box-shadow:0 0 18px 7px rgba(80,255,140,.9),0 0 0 2px rgba(140,255,180,.75);filter:brightness(1.18);}
}
/* Clear button -- red/gold banner (danger tone), smaller/secondary */
#clear-btn{
  background-color:#2a0e0e;
  border-style:solid;border-width:8px;border-image-repeat:stretch;
  border-image-slice:90 fill;border-image-width:8px;
  border-image-source:url('assets/img/ui/panel_red_gold.webp');
  color:#f8e8c8;padding:6px 10px;font-size:9px;font-family:'Cinzel',serif;
  cursor:pointer;letter-spacing:.4px;
  opacity:0;transform:translateX(8px);pointer-events:none;
  transition:opacity .2s,transform .2s;
}
#clear-btn.show{opacity:1;transform:translateX(0);pointer-events:auto;}
#clear-btn:hover{filter:brightness(1.2);}
#clear-btn:active{transform:translateX(0) translateY(1px);}

/* ══ BATTLE SCREEN ══════════════════════════════════════════════ */
#screen-battle{display:none;flex-direction:column;flex:1;position:relative}
/* #turn-label no longer renders as a full-width banner -- see the
   floating menu/zoom controls below -- but stays in the DOM/hidden since
   updateTurnLabel() still writes to it every turn (CTB ticker text used
   elsewhere) and #phase-dot's colour-swap logic still targets it. */
#phase-dot{
  display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:5px;
  background:var(--pch-forest);box-shadow:0 0 4px var(--pch-forest),inset 0 0 1px rgba(0,0,0,.3);
  vertical-align:middle;
}
#turn-label.enemy-phase #phase-dot{background:var(--pch-burgundy);box-shadow:0 0 4px var(--pch-burgundy),inset 0 0 1px rgba(0,0,0,.3);}
#turn-label{display:none;}

/* ── Floating menu + zoom controls (battle screen) ─────────────────
   Direct visual match for #place-menu-btn on the deploy screen: same
   walnut/gold boxed button at rest, same translucent floating-pill
   treatment in the landscape media query below. #zoom-btn sits right
   next to the menu button in the same style, just narrower. */
#battle-top-controls{
  position:absolute;top:10px;right:12px;z-index:20;
  display:flex;align-items:center;gap:6px;
}
#battle-menu-btn,#battle-top-controls #zoom-btn{
  background:linear-gradient(180deg,var(--pch-walnut),var(--pch-walnut-dark));
  border:1px solid var(--pch-bronze);border-radius:4px;
  color:var(--pch-gold-bright);font-family:'Cinzel',serif;
  cursor:pointer;font-weight:700;
  box-shadow:0 2px 6px rgba(0,0,0,.4);
}
#battle-menu-btn{font-size:12px;padding:7px 10px;line-height:1;}
#battle-top-controls #zoom-btn{font-size:10px;letter-spacing:.5px;padding:7px 9px;}
#battle-menu-btn:hover,#battle-top-controls #zoom-btn:hover{filter:brightness(1.15);}
#battle-menu-btn:active,#battle-top-controls #zoom-btn:active{transform:translateY(1px);filter:brightness(.92);}

/* Popup opened by #battle-menu-btn -- toggle sound / return to menu /
   redeploy. Same translucent walnut card look used throughout the
   floating map UI (#l5-reinforce-banner, the landscape #place-menu-btn
   treatment, etc). Anchored just under the menu button. */
#battle-menu-popup{
  position:absolute;top:46px;right:12px;z-index:25;
  display:none;flex-direction:column;gap:4px;
  background:rgba(20,14,8,.85);border:1px solid rgba(200,160,80,.55);
  border-radius:6px;box-shadow:0 3px 12px rgba(0,0,0,.5);
  padding:6px;min-width:150px;
}
#battle-menu-popup.show{display:flex;}
#battle-menu-popup button{
  background:transparent;border:none;border-radius:4px;
  color:var(--pch-gold-bright);font-family:'Cinzel',serif;font-size:10px;
  letter-spacing:.4px;text-align:left;padding:7px 9px;cursor:pointer;
  display:flex;align-items:center;gap:6px;
}
#battle-menu-popup button:hover{background:rgba(200,160,80,.18);}
#battle-menu-popup button:active{filter:brightness(.92);}
#battle-menu-popup #mute-btn{font-size:12px;}

/* Reinforcement-deployment CTA -- #endturn-btn stays in the DOM (hidden
   by default via an inline style="display:none" in index.html, NOT here;
   see that comment for why it has to be inline) purely because several
   levels' mid-battle reinforcement phases relabel this same button to
   "BATTLE"/"DONE" via _setReinforceDoneCTA() and rely on it existing --
   see the show/hide calls scattered through game.js's reinforcement
   functions. Floats top-right, just below the menu/zoom row. */
#endturn-btn{
  position:absolute;top:48px;right:12px;z-index:18;
  background:linear-gradient(180deg,var(--pch-navy),var(--pch-navy-dark));
  color:var(--pch-gold-bright);border:1px solid var(--pch-gold);border-radius:2px;
  padding:3px 9px;font-size:9px;font-family:'Cinzel',serif;cursor:pointer;font-weight:700;
}
#endturn-btn:active{transform:translateY(1px);filter:brightness(.92)}
/* While relabeled "BATTLE / DONE" (see the comment above), it gets the
   same visual weight as the initial deployment screen's #start-battle-btn
   banner instead of staying styled as the small WAIT nav pill -- per
   developer request, so the moment reads with the same prominence both
   times the player is asked to commit to a deployment. Reuses the same
   blue-lion border art and pulsing glow as #start-battle-btn.ready-glow.
   The class is removed the instant a normal turn begins again. */
#endturn-btn.reinforce-cta{
  background-color:#0e1a30;
  border-style:solid;border-width:8px;border-image-repeat:stretch;
  border-image-slice:90 fill;border-image-width:8px;
  border-image-source:url('assets/img/ui/panel_blue_lion.webp');
  color:#f0e0a0;padding:6px 14px;font-size:10px;letter-spacing:.6px;
  border-radius:0;
  animation:battleReadyPulse 1.1s ease-in-out infinite;
}

/* Board takes up most space */
#board-wrap{position:relative;width:100%;flex:1;min-height:320px;overflow:hidden;flex-shrink:0;background-color:#7a8bb0;
  /* Query container for #turn-queue's large-screen scaling below --
     establishes "cqw" as a % of THIS element's own rendered width, the
     same pattern already used for the Chapter/Level Select cards. */
  container-type:inline-size;
}
/* Soft fade at the very bottom edge of the map, blending toward the
   app's own background tone instead of the canvas cutting off with a
   hard edge right where the HUD banner begins below it. Doesn't touch
   layout (absolute, no box size) and never blocks board taps
   (pointer-events:none). Sits above both canvases (z-index 1/2) but
   below any real UI chrome that might overlay the board later. */
#board-wrap::after{
  content:'';position:absolute;left:0;right:0;bottom:0;height:52px;
  background:linear-gradient(180deg,rgba(51,35,22,0) 0%,rgba(51,35,22,.85) 100%);
  pointer-events:none;z-index:3;
}
#board-bg{position:absolute;bottom:-15%;left:0;width:100%;height:135%;background-image:url('assets/img/img_001.webp');background-repeat:repeat-x;background-position:bottom left;background-size:auto 100%;z-index:0}
#weather-fx{position:absolute;inset:0;z-index:1;pointer-events:none;}
canvas#gc{z-index:1}
canvas#fx{z-index:2}
canvas#gc{position:absolute;top:0;left:0;image-rendering:pixelated;cursor:default}
canvas#gc.dragging{cursor:grabbing}
canvas#fx{position:absolute;top:0;left:0;pointer-events:none}

/* ── TURN ORDER QUEUE (FFT-style, left edge of board) ────────── */
#turn-queue{
  position:absolute;top:8px;left:6px;z-index:6;
  display:flex;flex-direction:column;gap:clamp(6px,.85cqw,10px);
  pointer-events:none;
}
/* Epic boss health bar -- top-center overlay for specific campaign boss
   fights only. Same frame+track+fill technique as .sb-bar-frame/.sb-track
   (reuses .sb-shimmer's animation from there), just with its own geometry.
   boss_hp_frame.png was cropped to its own visible bbox before use (the
   source file had ~20-30% transparent padding above/below the actual
   bar+circle artwork) -- these percentages are measured off THAT cropped
   canvas (2186x375), not the original upload. Recompute if the frame art
   is ever re-exported. */
#boss-hp-banner{
  display:none;position:absolute;top:10px;left:50%;transform:translateX(-50%);
  width:min(80vw,440px);aspect-ratio:2186/375;z-index:7;pointer-events:none;
}
#boss-hp-banner.show{display:block;}
#boss-hp-portrait{
  position:absolute;left:3.66%;top:14.93%;width:11.99%;aspect-ratio:1;
  border-radius:50%;overflow:hidden;z-index:1;background:#000;
  box-shadow:inset 0 0 6px rgba(0,0,0,.85);
}
#boss-hp-portrait-img{width:100%;height:100%;object-fit:cover;object-position:center 20%;}
#boss-hp-track{
  position:absolute;left:19.53%;right:7.59%;top:32.27%;bottom:30.13%;
  z-index:1;border-radius:999px;overflow:hidden;background:rgba(10,4,0,.55);
  box-shadow:inset 0 1px 3px rgba(0,0,0,.7);
}
#boss-hp-fill{
  position:relative;height:100%;width:100%;border-radius:999px;overflow:hidden;transition:width .3s;
  background:linear-gradient(180deg,#ff8a6a 0%,#e0402a 35%,#a5150f 65%,#6a0a08 100%);
  box-shadow:0 0 10px rgba(220,50,20,.65),inset 0 1px 0 rgba(255,255,255,.35);
}
/* Hit flash -- brief white spike in sync with the boss sprite's own
   hit-flash (see triggerHitFlash()/u._flashUntilTick in game.js). No
   transition on purpose: the underlying state is only ~4 ticks long, so
   an instant snap on/off reads as a flash rather than a fade. */
#boss-hp-fill.flash{background:#fff;box-shadow:0 0 16px rgba(255,255,255,.9),inset 0 1px 0 rgba(255,255,255,.6);}
#boss-hp-name{
  position:absolute;left:19.53%;right:7.59%;top:32.27%;bottom:30.13%;z-index:2;
  display:flex;align-items:center;justify-content:center;
  color:#ffe9a8;font-family:'Cinzel',serif;font-weight:700;
  font-size:clamp(10px,2.4vw,14px);letter-spacing:.06em;
  -webkit-text-stroke:.4px rgba(80,10,5,.6);
  text-shadow:0 1px 3px rgba(0,0,0,.9);text-transform:uppercase;
  pointer-events:none;
}
#boss-hp-frame-img{
  position:absolute;inset:0;width:100%;height:100%;z-index:3;pointer-events:none;
  filter:drop-shadow(0 2px 6px rgba(0,0,0,.6));
}
.tq-slot{
  display:flex;align-items:center;gap:clamp(6px,.85cqw,10px);
  background:rgba(10,8,6,.68);border:1px solid var(--pch-bronze);
  border-radius:clamp(7px,1cqw,11px);
  padding:clamp(4px,.57cqw,7px);box-shadow:0 2px 5px rgba(0,0,0,.4);
  transition:opacity .15s;
  /* #turn-queue (the parent) sets pointer-events:none so the empty gaps
     between slots don't intercept taps meant for the board underneath --
     but that also silently blocked the tap-to-enlarge click handler each
     slot already has wired up in JS (see updateTurnQueueUI() in game.js).
     Re-enabling it here, scoped to just the slot itself, is what actually
     makes that handler reachable at all. */
  pointer-events:auto;
}
.tq-slot.tq-hero{border-color:var(--pch-gold-bright);box-shadow:0 0 6px rgba(236,200,104,.5),0 2px 5px rgba(0,0,0,.4);}
.tq-slot.tq-enemy{border-color:#c04030;}
.tq-slot.tq-neutral{border-color:#9060ff;}
.tq-slot:first-child{transform:scale(1.08);}
/* Large-screen scaling: confirmed-good size was measured at a 705x375
   board-wrap -- everything below is a clamp() with that size as the
   floor (so nothing changes at/under ~705px wide) and a cqw-based value
   that only starts growing past it, using #board-wrap's own container
   query (cqw = % of #board-wrap's rendered width) rather than raw
   viewport units, same reasoning as the Chapter/Level Select card
   scaling: it tracks the actual board size, not the full page, in case
   the board is ever narrower than the viewport for some other reason.
   Ceilings are a first-pass reasonable cap, tunable once tested on an
   actual large screen. */
.tq-portrait{
  width:clamp(34px,4.82cqw,58px);height:clamp(34px,4.82cqw,58px);
  border-radius:clamp(4px,.57cqw,7px);
  object-fit:cover;image-rendering:pixelated;background:#1a1410;display:block;
}
.tq-num{
  font-family:'Cinzel',serif;color:var(--pch-gold-bright);font-weight:700;text-align:center;
  font-size:clamp(12px,1.7cqw,20px);min-width:clamp(13px,1.84cqw,21px);
}

/* ── UNIT ACTION MENU (docked beside the unit banner) ──────────
   Full 3-column x 2-row grid of SIX equal buttons (Move/Attack/Skill2
   on top, Reset/Skill/Wait below), height pinned to exactly match
   #bottom-bar's height (see the shared --hud-bar-h var below) so the
   two panels line up as one HUD strip. The "Action" label no longer
   eats a grid cell of its own -- it's now a tiny centered strip above
   the grid, freeing up a full 6th button slot so Skill and Skill2 each
   get their own full-size chip instead of being squeezed together.
   Columns size to their own content (max-content) rather than an even
   1fr split, so a long label + its cost badge has room to breathe; any
   label still too long for its cell gets shrunk by fitActionMenuText()
   in game.js (see .am-item.compact below) rather than clipping. Each
   pressable item is its own small raised "chip" (subtle gradient +
   border + raised shadow) so it reads as a button. Layout:
        ACTION
     [Move  ] [Attack] [Skill2]
     [Reset ] [Skill ] [Wait  ]
*/
/* Bumped 122->138px -- HP/MP bars are now a fixed 24px tall each (up
   from an earlier 16px pass that read as too small -- see
   .sb-bar-wrap's comment), plus tightened padding/gaps elsewhere in the
   card per developer request to keep the increase mostly going toward
   bar size rather than empty space. Estimate, not a measured value --
   #bottom-bar has a hard overflow:hidden backstop, so if this number is
   still a little short, content clips cleanly at the border instead of
   visibly spilling past it, rather than breaking outright. Note this
   also scales up #head-portrait-box, which ties its own width to this
   same var. */
:root{--hud-bar-h:138px;}
#action-menu{
  display:none;
  flex-direction:column;
  gap:2px;
  flex:1;min-width:0;
  height:var(--hud-bar-h);
  padding:10px 8px 2px; /* was 4px 8px 6px (bottom-heavy) -- flipped to top-heavy per developer note: "ACTION" was overlapping panel_black_gold's top scrollwork, same fix as #stats-bar's padding */
  background:transparent; /* BUGFIX: was a solid #14100a fill -- invisible back when this panel sat on the app's own dark brown backdrop, but border-image-slice:90 FILL already paints the entire visible card (border + interior) from panel_black_gold.png itself. This background-color was only ever showing through the image's own transparent corner margins -- i.e. exactly the rectangular black "halo" around the ornate artwork that became obvious once the HUD started floating over the bright battlefield. Removing it lets those margins show the real map instead. */
  border-style:solid;border-width:14px;border-image-repeat:stretch;
  border-image-slice:90 fill;border-image-width:14px;
  border-image-source:url('assets/img/ui/panel_black_gold.webp');
  overflow:hidden;
  animation:amFadeIn .18s ease-out;
}
@keyframes amFadeIn{from{opacity:0;transform:translateY(3px);}to{opacity:1;transform:translateY(0);}}
#action-menu.show{display:flex;}
.am-title{
  flex-shrink:0;
  text-align:center;
  color:var(--pch-bronze-light);font-size:7px;letter-spacing:1.5px;text-transform:uppercase;
  font-family:'Cinzel',serif;font-weight:700;
  line-height:1;padding:6px 0 1px; /* was 3px 0 2px -- still wasn't enough clearance from the border-image's top scrollwork, see #action-menu's own padding note above for the matching fix on its container */
}
.am-grid{
  flex:1;min-height:0;
  display:grid;
  /* Columns 1-2 (Move/Attack/Reset/Wait -- same 4 short labels on every
     unit) narrower and equal; column 3 (Skill/Skill2 -- varies per unit,
     can run longer e.g. "Charge Up" + a cost badge) wider. Matches the
     grid-column reassignment below. */
  grid-template-columns:0.85fr 0.85fr 1.3fr;
  grid-template-rows:repeat(2,1fr);
  gap:3px 6px;
}
/* Pressable "chip" look -- subtle raised gradient + bronze border +
   raised shadow, not a flat solid fill. Disabled items drop all of
   this (see further down) so only real buttons look pressable. */
.am-item{
  display:flex;align-items:center;gap:5px;padding:5px 9px;
  cursor:pointer;transition:box-shadow .1s,background .1s;
  position:relative;min-width:0;height:100%;box-sizing:border-box;
  background:linear-gradient(180deg,rgba(255,255,255,.07),rgba(0,0,0,.22));
  border:1px solid rgba(140,106,53,.55);
  border-radius:4px;
  box-shadow:var(--pch-shadow-raised);
}
/* Grid placement -- reorganized per developer request: the 4 actions
   that are identical for every unit (Move/Attack/Reset/Wait) are
   grouped into the first two (narrower) columns, and the 2 unit-
   specific special-ability slots (Skill/Skill2 -- e.g. "Charge Up") are
   both in the 3rd (wider) column, since their labels+cost badges vary
   and can run longer. Previously these were interleaved (Attack shared
   a column with Skill, Wait shared a column with Skill2), so every
   column had to be sized for a worst-case special-ability label even
   though only 2 of the 6 cells ever actually contain one -- see
   .am-grid's grid-template-columns below for the matching width split. */
#am-move{grid-column:1;grid-row:1;}
#am-reset{grid-column:1;grid-row:2;}
#am-atk{grid-column:2;grid-row:1;}
#am-wait{grid-column:2;grid-row:2;}
#am-skill2{grid-column:3;grid-row:1;}
#am-skill{grid-column:3;grid-row:2;}
.am-item:hover,.am-item.active{
  background:linear-gradient(180deg,rgba(255,255,255,.03),rgba(0,0,0,.3));
  box-shadow:var(--pch-shadow-inset),0 0 0 1px var(--pch-gold);
}
.am-item.active::before{content:'\25C6';position:absolute;left:2px;top:50%;transform:translateY(-50%);color:var(--pch-gold-bright);font-size:8px;text-shadow:0 0 4px var(--pch-gold);}
.am-item.active .am-name{padding-left:7px;}
.am-icon{font-size:12px;width:14px;text-align:center;flex-shrink:0;line-height:1}
.am-name{color:var(--pch-ink);font-size:10px;font-weight:700;font-family:'Cinzel',serif;letter-spacing:.2px;line-height:1.1;white-space:nowrap;}
/* Descriptive subtitle text (e.g. "2x ATK next turn") -- game.js still
   writes to it, but there's no room to show it in the compact grid, so
   it stays hidden rather than removed (avoids null-ref errors there). */
.am-sub{display:none;}
.am-cost{color:var(--pch-gold);font-size:7px;font-weight:700;flex-shrink:0;margin-left:auto;white-space:nowrap;}
/* "compact" -- applied by fitActionMenuText() in game.js when a
   button's icon+name+cost would otherwise overflow its cell (e.g. a
   long skill name like "Charge Up" next to its cost badge on a narrow
   screen). Only the offending button shrinks; everything else stays
   normal size. */
.am-item.compact{padding:5px 6px;gap:3px;}
.am-item.compact .am-name{
  font-size:8px;letter-spacing:0;
  /* Secondary fallback beyond just shrinking the font: on the narrowest
     iPhones, even 8px nowrap text can still be wider than a 1fr column
     (see fitActionMenuText() in game.js for why nowrap text can overflow
     silently under equal-width grid columns). Letting it wrap onto a 2nd
     line here, instead of staying nowrap and spilling into the
     neighboring button, is what actually fixes that case -- there's
     enough cell height (--hud-bar-h split across 2 rows) for 2 short
     lines at this font size. */
  white-space:normal;text-align:center;line-height:1.05;
}
.am-item.compact .am-cost{font-size:6px;}
/* Colour classes -- Attack stays red/primary; Move, Wait, Reset (once
   viable) and Skill/Skill2 all share the same gold "danger" colour.
   These rules must come BEFORE .disabled below (not after) so a
   disabled Reset Move -- which carries both classes -- still resolves
   to muted stone: same specificity, so whichever rule is later in the
   source wins. */
.am-item.primary .am-name{color:#d97a3e;}
.am-item.danger .am-name{color:var(--pch-gold-bright);}
/* Disabled actions -- flatten the button chip entirely (no gradient,
   no border, no raised shadow) so unavailable actions don't compete
   visually with real buttons; colour-only fade, same as before. */
.am-item.disabled{
  cursor:default;pointer-events:none;
  background:none;border-color:transparent;box-shadow:none;
}
.am-item.disabled .am-name{color:var(--pch-stone);}
.am-item.disabled .am-icon{filter:grayscale(1) brightness(.8);opacity:.6;}


/* ── HUD BOTTOM ROW ─────────────────────────────────────────────────
   RESTRUCTURED: this used to be a normal-flow row sitting BELOW
   #board-wrap (the canvas), in its own reserved strip of the page --
   which meant no amount of "transparent background" could ever reveal
   the battlefield through it, because the battlefield was never behind
   it to begin with, just a separate box above it. Per developer
   request, it's now an absolutely-positioned overlay docked to the
   bottom of #screen-battle (which #board-wrap fills entirely, since
   render()/battleOrigin()/clampCam() in game.js all measure
   #board-wrap's live offsetWidth/offsetHeight every frame rather than a
   cached size -- so #board-wrap simply expands to fill the space this
   row no longer reserves, no other game.js changes needed). Everywhere
   this row's own box is transparent (i.e. not actually covered by the
   unit banner or, when shown, the action menu), the live map now
   genuinely shows through, including the ~4px gap between the two
   panels. z-index sits above both canvases and the legibility scrim
   below, but under #overlay/#necro-overlay etc, which already hide this
   row outright while active (see the :has() rule further down).
   align-items:flex-end keeps both panels bottom-aligned; Previously this
   row itself was `width:fit-content;align-self:center`, which centered
   it as a narrow block with dead space on both sides instead of
   spanning the screen -- and #action-menu had a fixed `min-width:260px`
   that could exceed the remaining space on narrow iPhones, which is
   what made "Wait" require a sideways scroll to reach (a real UX
   problem, not a good fallback). Both fixed now: this row stretches
   full width, and #action-menu grows/shrinks to fill exactly whatever's
   left after the unit banner, so every button is always visible with no
   scrolling needed. */
#hud-bottom-row{
  display:flex;align-items:flex-end;gap:4px;
  position:absolute;left:0;right:0;bottom:0;z-index:15;
  width:100%;
  background:transparent; /* no filler fill of its own -- only the unit banner and (when shown) the action menu should ever paint anything here */
  /* No padding here -- `body` already applies
     padding-left/right:env(safe-area-inset-left/right) globally (see its
     own rule, near the top of this file), same as every other screen
     including #board-wrap above. Adding it AGAIN here meant this row was
     inset from the edges by safe-area-inset TWICE over, which is exactly
     why it visibly didn't reach the same edges as the map even though
     both are width:100% -- #board-wrap never had this extra padding, so
     it only got inset once, correctly. Bottom safe-area is likewise
     already handled -- body pads itself with
     env(safe-area-inset-bottom), and #app/#screen-battle fill that
     already-inset box, so bottom:0 here naturally lands above the home
     indicator on notched phones without any extra offset. */
  box-sizing:border-box;
}
/* BUGFIX: whenever the action menu isn't showing (previewing an enemy's
   or another hero's turn, not the player's own active unit -- see
   updatePanel()/showActionMenu() in game.js), #action-menu is
   display:none and drops out of the flex layout entirely, but this row
   was still width:100% -- leaving a big dead rectangle of plain #app
   background to the right of the (much narrower, fit-content) unit
   banner. Collapsing the row to fit-content in that state removes the
   filler; it snaps back to 100% (so the grid has its usual room) the
   instant the menu actually shows. */
#hud-bottom-row:not(:has(#action-menu.show)){width:fit-content;}
/* JS-driven equivalent of the rule above (see _syncHudRowWidth() in
   game.js) -- kept alongside it rather than instead of it so this still
   works correctly even on a browser that doesn't support :has(). */
#hud-bottom-row.am-hidden{width:fit-content;}

/* ── COMPACT BOTTOM BAR (unit stat panel) ──────────────────────────────
   Now framed with the same panel_black_gold.png banner artwork used
   elsewhere for dark/high-contrast UI chrome, replacing the old flat
   parchment-gradient + single top border-line treatment.
   Narrowed to fit its content (portrait + stat/attr columns) instead of
   stretching to fill the row -- the freed-up width to the right is where
   #action-menu now docks (see #hud-bottom-row above). */
#bottom-bar{
  background:transparent; /* BUGFIX: was a solid #14100a fill -- see the matching note on #action-menu above, same cause. border-image-slice:90 FILL already paints the whole visible card from the PNG; this was only ever showing through as a black rectangular halo around the ornate artwork's own transparent corner margins. */
  border-style:solid;border-width:14px;border-image-repeat:stretch;
  border-image-slice:90 fill;border-image-width:14px;
  border-image-source:url('assets/img/ui/panel_black_gold.webp');
  display:flex;align-items:stretch;
  flex-shrink:0;
  width:fit-content;
  height:var(--hud-bar-h);
  position:relative;
  transition:border-image-source .1s;
  /* Hard backstop -- clips any content (a wrapped buff chip, etc.) that
     still doesn't fit within --hud-bar-h at the card's own padding edge,
     so a height estimate being slightly off in some future case reads as
     "neatly clipped" rather than "visibly poking out past the border".
     Doesn't affect the border-image itself -- overflow only clips this
     element's own children/content, not its border/background painting. */
  overflow:hidden;
}
/* Enemy/neutral turn or inspection -- red/gold banner instead of the
   default black/gold, same danger-tone artwork #clear-btn already uses
   elsewhere, so the frame itself signals "not your unit" at a glance.
   Toggled by updatePanel() in game.js based on u.isEnemy. */
#bottom-bar.bb-enemy{
  border-image-source:url('assets/img/ui/panel_red_gold_2.webp');
}

/* Head portrait — sized to match the banner's height (--hud-bar-h) so
   it fills the taller panel edge-to-edge instead of leaving a blank
   strip below a still-76px-square image. */
#head-portrait-box{
  width:var(--hud-bar-h);flex-shrink:0;
  background:#221a10;
  border-right:2px solid var(--pch-bronze);
  overflow:hidden;display:flex;align-items:center;justify-content:center;
  position:relative;
  box-shadow:var(--pch-shadow-inset);
}
#head-portrait-box::before,#head-portrait-box::after{
  content:'';position:absolute;width:9px;height:9px;pointer-events:none;z-index:2;
  border:2px solid var(--pch-gold);opacity:.85;
}
#head-portrait-box::before{top:2px;left:2px;border-right:none;border-bottom:none;}
#head-portrait-box::after{top:2px;right:2px;border-left:none;border-bottom:none;}
#portrait{image-rendering:auto;display:block;width:100%;height:100%;object-fit:contain;}
#head-portrait-box .port-name{
  position:absolute;bottom:0;left:0;right:0;
  background:rgba(20,12,4,.82);color:#e8d8a0;font-size:7px;
  font-family:'Cinzel',serif;text-align:center;padding:2px 2px;
  letter-spacing:.3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  border-top:1px solid var(--pch-bronze);
}
#head-portrait-box .port-name.hidden{display:none}

/* Stats section */
#stats-bar{
  flex:1;display:flex;flex-direction:column;justify-content:center;
  padding:14px 10px 4px; /* top-heavy on purpose -- see developer note: content was overlapping panel_red_gold_2's top scrollwork, so weight shifted from bottom toward top to push everything down within the card rather than growing the card itself */
  gap:4px;border-right:1px solid var(--pch-border);
  overflow:hidden;
}
#stat-unit-info{display:flex;align-items:baseline;gap:8px;margin-bottom:0}
/* Same gold-outline treatment as .sb-val below, per developer request --
   name, HP/MP labels, and HP/MP numbers now share one consistent text
   style instead of the name being the odd one out. */
#stat-name{
  color:#f0e0a0;font-size:15px;font-weight:700;font-family:'Cinzel',serif;letter-spacing:.3px;
  -webkit-text-stroke:.5px var(--pch-gold);
  text-shadow:0 1px 2px rgba(0,0,0,.75);
}
#stat-job{color:#b8a888;font-size:8px;letter-spacing:.8px;text-transform:uppercase}
.sb-row{display:flex;align-items:center;gap:4px}
.sb-lbl{
  color:#c8b898;font-size:8px;width:16px;letter-spacing:.3px;flex-shrink:0;font-weight:700;font-family:'Cinzel',serif;
  -webkit-text-stroke:.4px var(--pch-gold);
  text-shadow:0 1px 2px rgba(0,0,0,.75);
}
/* HP/MP bars -- reuse loading_bar.png (the same ornate frame asset the
   loading screen uses) instead of a plain rounded-rect track, per
   developer request. Same technique as #loading-bar-wrap: the PNG
   supplies the frame (rails/end-caps/medallion) as a plain <img>, and a
   separate absolutely-positioned div sits in the transparent "track"
   baked into that art to render the actual colored fill + shimmer --
   .sb-track's inset percentages are copied directly from
   #loading-bar-fill-track since it's literally the same source image, so
   the same measured numbers apply unchanged.
   The image's native aspect ratio (1500:200 = 7.5:1) is real and getting
   it to look right meant these rows need to be noticeably taller than
   the old plain 8px track -- aspect-ratio here derives the row's actual
   height from whatever width the flex layout gives it, so it sizes
   itself rather than needing a hand-guessed pixel height. --hud-bar-h
   was bumped accordingly (see its own comment) to give the now-taller
   HP/MP rows room without cramping the rest of the card. */
/* Fixed size instead of flex:1+aspect-ratio -- the previous version
   stretched to fill the row's available width and derived height from
   that via aspect-ratio, which meant the bar (and therefore the whole
   HP/MP block's height) grew or shrank along with whatever else was
   making the card wider or narrower -- including a wrapped buff chip
   below indirectly widening the card, which in turn made these bars
   *taller*, compounding the very overflow it was already close to. Fixed
   height + auto width (computed from the same aspect-ratio) breaks that
   feedback loop entirely: this block's height is now a hard constant no
   matter what else is going on in the card. Bumped 16->24px (developer
   feedback: 16px read as too small against the card's actual width) --
   width follows automatically from the same 7.5:1 ratio, so this one
   number controls both dimensions together. See #bottom-bar's own
   overflow:hidden below for a hard backstop against any remaining edge
   case. */
.sb-bar-wrap{position:relative;height:24px;width:auto;aspect-ratio:1056/185;flex:0 0 auto;}
.sb-bar-frame{position:absolute;inset:0;z-index:2;width:100%;height:100%;object-fit:contain;pointer-events:none;filter:drop-shadow(0 1px 3px rgba(0,0,0,.5));}
.sb-track{position:absolute;z-index:1;left:17.3%;right:6.6%;top:37%;bottom:33%;border-radius:999px;overflow:hidden;background:rgba(10,4,0,.55);box-shadow:inset 0 1px 3px rgba(0,0,0,.7);}
.sb-fill{position:relative;height:100%;border-radius:999px;transition:width .3s;overflow:hidden;}
#sb-hp{background:linear-gradient(180deg,#9dfa6a 0%,#5ad62a 35%,#2e8a14 65%,#1c5a0c 100%);box-shadow:0 0 8px rgba(90,214,42,.65),inset 0 1px 0 rgba(255,255,255,.4);}
#sb-mp{background:linear-gradient(180deg,#7ec8ff 0%,#3a8ee0 35%,#1c5aa0 65%,#0e3868 100%);box-shadow:0 0 8px rgba(58,142,224,.65),inset 0 1px 0 rgba(255,255,255,.4);}
.sb-shimmer{position:absolute;inset:0;width:220%;background:linear-gradient(100deg,transparent 35%,rgba(255,255,255,.55) 50%,transparent 65%);animation:sbShimmer 2.4s linear infinite;}
@keyframes sbShimmer{from{transform:translateX(-45%);}to{transform:translateX(0%);}}
/* Thin gold outline instead of the earlier black-outline/bevel attempt,
   per developer feedback -- same --pch-gold used throughout the rest of
   the UI's bronze/gold accents, so the numbers read as part of the same
   material language rather than a mismatched black stroke. Kept a
   subtle dark drop shadow underneath so the gold stroke itself has
   something to contrast against on lighter backgrounds, not stacked
   bevel shadows this time. */
.sb-val{
  color:#f0e4c8;font-size:12px;min-width:42px;text-align:right;flex-shrink:0;font-weight:700;font-family:'Cinzel',serif;letter-spacing:.2px;
  -webkit-text-stroke:.5px var(--pch-gold);
  text-shadow:0 1px 2px rgba(0,0,0,.75);
}
#attr-row{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px;max-width:280px;}
/* BUGFIX: #attr-row previously had no width constraint at all -- inside
   the #bottom-bar(width:fit-content) -> #stats-bar(flex:1) chain, that
   meant flex-wrap:wrap never actually had anything to wrap AGAINST (a
   wrapping flex container needs a bounded width to know when it's out
   of room), so adding a 7th chip (a terrain/aura buff like HIGH GROUND
   -- see #lava-status below) just grew this row, and therefore
   #bottom-bar, wider on ONE line every time -- and since #action-menu is
   the only side of #hud-bottom-row built to shrink (flex:1;min-width:0),
   it silently absorbed that entire cost, squishing its buttons past what
   .am-item.compact's own text-shrink fallback could handle. 280px is
   the measured natural single-line width of the 6 baseline chips
   (ATK/DEF/MOV/RNG/SPD/elem) at 701px viewport via devtools
   getBoundingClientRect -- not a guessed number -- so today's normal
   (no-buff) display is unaffected; only 7+-chip cases exceed it.
   IMPORTANT: this alone would let flex-wrap push a 7th chip onto a 2nd
   line -- but #bottom-bar is a FIXED 138px tall (--hud-bar-h) with
   overflow:hidden as a hard vertical clip backstop, and the existing
   content (name row + 2 stat bars + one chip line) already uses nearly
   all of that height, so a wrapped 2nd chip line would very likely get
   silently clipped off the bottom instead of visibly squishing sideways
   -- a worse bug (invisible content) than the one this fixes. See
   fitAttrRow() in game.js, which measures real overflow the same way
   fitActionMenuText() already does for the action menu's own buttons,
   and adds the .compact class below to shrink chips down to fit back
   onto ONE line instead of wrapping, so this max-width is really just
   the trigger threshold, not the actual layout fix. */
#attr-row.compact{gap:2px;}
#attr-row.compact .attr{padding:1px 3px;font-size:7px;gap:1px;}
#attr-row.compact .attr-icon{width:8px;height:8px;}
.attr{
  background:linear-gradient(180deg,var(--pch-parchment-light),var(--pch-parchment-dark));
  border:1px solid var(--pch-bronze);border-radius:2px;padding:2px 5px;font-size:8px;
  color:var(--pch-ink-faint);font-weight:600;font-family:'Cinzel',serif;display:flex;align-items:center;gap:2px;
  box-shadow:var(--pch-shadow-inset);
}
.attr-icon{width:10px;height:10px;line-height:1;opacity:.9;flex-shrink:0;object-fit:contain;image-rendering:pixelated;}
.attr span{color:var(--pch-ink);font-weight:700;}
#no-sel-msg{flex:1;display:flex;align-items:center;justify-content:center;color:#b8a888;font-size:8px;font-style:italic;letter-spacing:.5px}

/* ── TERRAIN/AURA BUFF CHIP ─────────────────────────────────────────
   Lives inside #attr-row now (see index.html), same footprint as the
   plain ATK/DEF/etc chips, but keeps its own glowing color treatment per
   category instead of the flat parchment look, so it still reads as
   "special" rather than blending in as just another stat. The full
   effect description (previously spelled out in the chip text itself --
   "HIGH GROUND · +50% ATK DEALT · 50% DMG TAKEN FROM BELOW" -- which is
   what broke the compact card's spacing once the HUD started floating
   over the battlefield) now lives in the chip's `title` attribute
   instead (see updatePanel() in game.js), a tap-and-hold/hover away. */
#lava-status.attr{
  padding:2px 6px;border-radius:2px;
  font-size:7px;font-weight:700;letter-spacing:.4px;
  gap:3px;box-shadow:none;white-space:nowrap;
}
#lava-status.lava-buff{
  background:linear-gradient(90deg,#2a0c00,#1e0800);
  border:1px solid #a03808;color:#e07030;
  text-shadow:0 0 6px #ff4000;
  box-shadow:0 0 6px rgba(255,90,0,.45);
}
#lava-status.lava-curse{
  background:linear-gradient(90deg,#1a0000,#140000);
  border:1px solid #7a0800;color:#cc2818;
  text-shadow:0 0 6px #800000;
  box-shadow:0 0 6px rgba(200,30,20,.4);
}
#lava-status.lava-penalty{
  background:linear-gradient(90deg,#1c0800,#140600);
  border:1px solid #6a2808;color:#b04820;
  text-shadow:0 0 5px #600000;
  box-shadow:0 0 5px rgba(180,70,30,.35);
}
#lava-status.lava-neutral{
  background:linear-gradient(90deg,#00080e,#000610);
  border:1px solid #083050;color:#4080c0;
  text-shadow:0 0 5px #003060;
  box-shadow:0 0 5px rgba(60,120,200,.35);
}

/* Log bar */
/* Hidden per developer request -- this row used to show turn-instruction
   text ("Elara acted -- Move to reposition...") that duplicated what the
   top turn-label already says, and its vertical space is now folded into
   the taller unit banner / action menu below (--hud-bar-h). Left in the
   DOM (not deleted) since setLog() still writes to it from dozens of
   call sites elsewhere for combat feedback -- just not shown here. */
#logbar{display:none;}

/* Overlay */
#overlay{display:none;position:absolute;inset:0;background:rgba(20,12,4,.92);z-index:30;align-items:center;justify-content:center;flex-direction:column;gap:12px;
  /* BUGFIX: #overlay sits inside #board-wrap (overflow:hidden, viewport-
     bounded height) and previously had no overflow rule of its own, so
     on levels where the DEFEAT hint text (OLDMAN_DEFEAT_HINTS) runs long
     enough to push #ov-card taller than the available board height, the
     bottom of the card (rest of the hint text, the buttons) was clipped
     by #board-wrap with no way to reach it -- reported by a tester on
     Level 8's hint. #overlay is exactly #board-wrap's size (inset:0), so
     making #overlay itself the scroll container is enough to reveal the
     overflow without touching #board-wrap's own overflow:hidden (still
     needed elsewhere). justify-content:center degrades to a "safe center"
     (effectively flex-start) once content overflows in modern browsers,
     so the banner stays pinned at the top and the scrollable overflow
     lands at the bottom, which is the direction that was actually stuck. */
  overflow-y:auto;-webkit-overflow-scrolling:touch;
}
#overlay.show{display:flex}
/* BUGFIX: the bottom unit-info banner (#hud-bottom-row -- portrait/stats
   or the "-- Select a unit --" filler) is a DOM sibling of #overlay, not
   a child, so it was never hidden when Victory/Defeat showed -- there are
   5 separate call sites in game.js that can trigger #overlay.show, so a
   single CSS rule here covers all of them (current and future) instead
   of touching every call site individually. On short landscape iPhone
   viewports this banner sat low enough to visually cover the Redeploy/
   Chapters buttons underneath the overlay card. #overlay comes before
   #hud-bottom-row in the DOM, so the general sibling combinator applies. */
/* BUGFIX: the previous attempt here used a general sibling selector
   (#overlay.show ~ #hud-bottom-row), which only matches elements that
   share the same direct parent -- but #overlay actually lives one level
   deeper, nested inside #board-wrap, while #hud-bottom-row is a sibling
   of #board-wrap itself (both children of #screen-battle). They're
   cousins, not siblings, so that selector could never match and the fix
   silently did nothing. :has() checks any descendant regardless of
   nesting depth, so this actually matches the real structure. */
#screen-battle:has(#overlay.show) #hud-bottom-row{display:none;}
/* Victory/Defeat card -- redesigned per developer brief to reuse three
   existing plate assets instead of flat CSS gradients/text:
   1) #ov-banner-wrap: the ornate blue/gold brawler_header_banner.png
      (same asset + same top:44% safe-band text positioning already
      proven on #brawler-title/#brawler-banner-wrap above) carries the
      VICTORY/DEFEAT title.
   2) #ov-card: bg_parchment.png (new asset -- save the uploaded
      parchment texture to assets/img/ui/bg_parchment.webp) as a
      center/cover background encasing the sub-text, hint, and buttons.
   3) .ov-btn: gold_button.png, same aspect-ratio-locked treatment as
      .shop-card-buy-btn/.collection-reward-claim-btn elsewhere (never
      stretched off-ratio -- width fixed, height follows from
      aspect-ratio) instead of the old navy/gold-border CSS button.
   BUGFIX: the banner was originally `position:absolute` with a negative
   top offset so it would bridge/overlap the card's top edge. #overlay
   sits inside #board-wrap, which is `overflow:hidden` (see #board-wrap
   above) -- on short landscape viewports #ov-card doesn't have much
   clearance above it before hitting #board-wrap's own top edge, so the
   crest/wingtips that poked above the card were getting clipped off
   there (reported as the banner "not in the right place"). Banner is
   now a normal in-flow flex child, sized to the card's own content
   width (bigger, per developer request, and guaranteed to render fully
   since it never leaves #ov-card's own box, which has no overflow
   clipping) instead of escaping the card's bounds. */
#ov-card{
  position:relative;
  display:flex;flex-direction:column;align-items:center;
  gap:10px;
  width:92%;max-width:300px;
  padding:20px 18px 20px;
  background:url('assets/img/ui/bg_parchment.webp') center/cover no-repeat;
  border-radius:6px;
  box-shadow:0 10px 36px rgba(0,0,0,.6),0 0 0 1px rgba(120,90,40,.4);
}
#ov-banner-wrap{
  position:relative;
  width:100%;
  aspect-ratio:1425/696;
  background:url('assets/img/ui/brawler_header_banner.webp') center top/contain no-repeat;
  filter:drop-shadow(0 4px 10px rgba(0,0,0,.5));
  pointer-events:none;
}
#ov-banner-text{
  position:absolute;left:0;right:0;top:44%;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
}
#ov-title{color:var(--pch-gold-bright);font-size:21px;font-weight:700;letter-spacing:3px;font-family:'Cinzel Decorative',serif;text-shadow:0 1px 3px rgba(0,0,0,.75),0 0 16px rgba(214,178,74,.5)}
/* Larger, using the app's plain readable body serif (EB Garamond -- same
   family as .tbc-ability-text) and the same dark ink tone used for text
   printed directly on light plate art elsewhere (.shop-card-buy-btn /
   .collection-reward-claim-btn), rather than the old small gold Cinzel
   treatment, now that this sits on parchment instead of the dark
   backdrop. */
#ov-sub{color:#3a2408;font-family:'EB Garamond',serif;font-size:15px;font-weight:700;letter-spacing:.2px;line-height:1.4;text-align:center}
/* Star rating (campaign levels only) -- 3 individual star icons rather
   than the star_rating_banner.png frame (that asset's reused instead
   for the compact Level Select tile badge, see .lvl-star-badge further
   down) -- unearned stars are just the SAME gold star image dimmed/
   desaturated via filter, no separate "empty star" asset needed.
   Populated by _updateVictoryStarsAndRewards() in game.js. */
#ov-stars{display:flex;justify-content:center;gap:6px;margin:0;}
.ov-star-img{
  width:26px;height:26px;object-fit:contain;
  filter:grayscale(1) brightness(.45);opacity:.5;
  transition:filter .2s,opacity .2s;
}
.ov-star.ov-star-filled .ov-star-img{
  filter:drop-shadow(0 0 5px rgba(255,180,40,.7));opacity:1;
}
/* Compact reward row (unit/Coins/3-star Gold bonus) -- sits on the same
   light parchment card as #ov-sub/.ov-btn, so it uses the same dark-ink
   text treatment as those rather than the dark-background pill style
   used on Level Select's .lvl-reward-preview (that one sits on a dark
   card). Each slot independently toggles .ov-reward-earned (see
   _updateVictoryStarsAndRewards()) to fade its icon/label and reveal
   the "Earned" tag, reusing the exact glow keyframes already defined
   for the Level Select/Chapter Select "Earned" badges further down so
   all three look/feel consistent. */
#ov-rewards-row{display:flex;justify-content:center;align-items:flex-start;gap:7px;flex-wrap:wrap;margin:0;}
.ov-reward-item{
  position:relative;display:flex;flex-direction:column;align-items:center;gap:2px;
  background:rgba(58,36,8,.08);border:1px solid rgba(120,90,40,.4);
  border-radius:8px;padding:5px 7px;min-width:44px;
}
.ov-reward-item img{width:24px;height:24px;object-fit:contain;}
.ov-reward-label{
  font-family:'Cinzel',serif;font-size:8.5px;font-weight:700;
  color:#3a2408;white-space:nowrap;letter-spacing:.2px;
}
.ov-reward-earned-badge{display:none;}
.ov-reward-item.ov-reward-earned img,
.ov-reward-item.ov-reward-earned .ov-reward-label{opacity:.35;filter:grayscale(.6);}
.ov-reward-item.ov-reward-earned .ov-reward-earned-badge{
  display:block;position:absolute;top:50%;left:50%;
  transform:translate(-50%,-50%) rotate(-8deg);
  z-index:2;background:rgba(58,36,8,.85);
  border:1px solid rgba(214,178,74,.7);border-radius:3px;
  padding:2px 5px;white-space:nowrap;
  font-family:'Cinzel',serif;font-weight:700;font-size:7.5px;
  letter-spacing:.4px;text-transform:uppercase;color:#ffe8a0;
  text-shadow:0 0 5px #ffaa00;
  box-shadow:0 0 8px rgba(255,170,0,.4);
  pointer-events:none;
  animation:chapterRewardEarnedGlow 1.8s ease-in-out infinite;
}
.ov-btns{display:flex;gap:8px;justify-content:center}
.ov-btn{
  width:118px;aspect-ratio:1931/656;
  background:url('assets/img/ui/gold_button.webp') center/100% 100% no-repeat;
  border:none;border-radius:0;padding:0 6px;
  display:flex;align-items:center;justify-content:center;
  color:#3a2408;font-weight:800;letter-spacing:.3px;white-space:nowrap;
  font-size:8.5px;font-family:'Cinzel',serif;
  text-shadow:0 1px 0 rgba(255,255,255,.4);
  cursor:pointer;transition:filter .12s,transform .08s;
}
.ov-btn:hover{filter:brightness(1.1);}
.ov-btn:active{transform:translateY(1px) scale(.97);}
/* Defeat variant -- #overlay gets an .ov-defeat class (see the DEFEAT/
   VICTORY textContent call sites in checkWin()) so the same #ov-card
   markup swaps to the spiked crimson defeat_banner.png (native
   1983x793, ratio ~2.5 -- different from brawler_header_banner's
   ~2.047, hence its own aspect-ratio here) instead of restyling a
   second copy of the overlay.
   CORRECTION: the readable dark plate on this banner was originally
   measured from a single pixel-sampled column and came out too low
   (est. center ~36%) -- a gridline overlay across several columns of
   the actual asset showed the real plate runs ~19%-45% of the banner's
   height. Centered via top+translateY(-50%) (robust to text
   length/wrapping) instead of the old top-of-box percentage, which had
   to assume a fixed text height. Sizing the banner up 20% afterward
   (see width:120% below) shifted the apparent text position again
   relative to the plate, so the center landed at top:40% rather than
   the plate's raw geometric center (~32%) once that resize was
   accounted for -- verified against gridline overlays on the actual
   PNG both times rather than eyeballing it.
   Banner is also sized up 20% (120% of the card's content width,
   scoped to defeat only -- brawler_header_banner/Victory wasn't
   reported as cramped) so the plate has more room around the title
   text instead of a tight fit; #ov-card has no overflow clipping and
   sits with wide clearance inside #board-wrap horizontally, so the
   banner overhanging the card's edges here is safe. */
.ov-defeat #ov-banner-wrap{
  width:120%;
  aspect-ratio:1983/793;
  background-image:url('assets/img/ui/defeat_banner.webp');
}
.ov-defeat #ov-banner-text{top:40%;transform:translateY(-50%);}
.ov-defeat #ov-title{color:#f0d8c8;text-shadow:0 1px 3px rgba(0,0,0,.8),0 0 16px rgba(200,20,15,.75)}
/* Gregor's end-game defeat tip (OLDMAN_DEFEAT_HINTS) -- same new Gregor
   banner artwork as #bim-box above (assets/img/ui/gregor_banner.webp),
   replacing the old plain gold-border/parchment-tint box. Also doubles
   as the Brawler post-match rank-up hint (_brawlerFinishMatch), unrelated
   to Gregor but sharing the same slot/look is harmless. */
#ov-hint{
  color:#e8e0c8;font-size:9px;font-style:italic;letter-spacing:.5px;
  text-align:center;max-width:200px;line-height:1.6;
  padding:8px 12px;
  border-style:solid;border-width:12px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:12px;
  border-image-source:url('assets/img/ui/gregor_banner.webp');
}
/* ── Mode toggle ── */
#mode-toggle-wrap{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:8px;}
#mode-label{font-size:8px;letter-spacing:1.5px;color:var(--pch-ink-soft);font-family:'Cinzel',serif;text-transform:uppercase;}
#mode-btn{
  background:linear-gradient(180deg,var(--pch-walnut),var(--pch-walnut-dark));
  border:1px solid var(--pch-gold);border-radius:3px;
  color:var(--pch-gold-bright);font-size:8px;font-family:'Cinzel',serif;letter-spacing:.8px;
  padding:4px 10px;cursor:pointer;transition:all .15s;
}
#mode-btn:active{transform:scale(.96);}
#mode-btn.dev{
  background:linear-gradient(180deg,var(--pch-burgundy),var(--pch-burgundy-dark));
  border-color:#c87850;color:#f0d0b8;
}
#mode-btn.easy{
  background:linear-gradient(180deg,#2a7a2a,#1a5a1a);
  border-color:#60dd60;color:#c0ffb0;
}
#mode-label.dev{color:var(--pch-burgundy);}
#mode-label.easy{color:#40cc40;}

/* ── Squire Level-Up badge ── */
.sq-levelup-badge{
  position:absolute;pointer-events:none;
  font-family:'Cinzel',serif;font-weight:700;font-size:11px;
  color:#ffe060;text-shadow:0 0 8px #ffbb00,0 0 16px #ff8800;
  animation:sqLvlPop .8s ease-out forwards;
  z-index:25;
}
@keyframes sqLvlPop{
  0%{transform:translateY(0) scale(.8);opacity:1;}
  60%{transform:translateY(-28px) scale(1.2);opacity:1;}
  100%{transform:translateY(-52px) scale(.9);opacity:0;}
}

/* ── Charge-up aura drawn on canvas — no CSS needed, but status pill: */
.charge-pip{display:inline-block;width:7px;height:7px;border-radius:50%;background:#c060ff;border:1px solid #e0a0ff;margin-left:2px;vertical-align:middle;box-shadow:0 0 4px #9020ff;}

/* ══ OLD MAN GUIDE OVERLAY ══════════════════════════════════════ */
/* position:fixed (not absolute) -- this overlay now lives as a top-level
   sibling of the screens (see index.html) instead of nested inside
   #screen-battle, so it can also be reused for the Shop's idle-nudge
   nag (see startShopIdleTimer() in game.js) and any other screen later.
   Anchored to the TOP of the screen, just below where the old #topbar
   banner used to sit (now the floating menu/zoom controls) -- it used to
   dock at the bottom (align-items:flex-end), which put it directly over
   #hud-bottom-row (action menu + unit banner) and #l5-reinforce-banner
   (deployment roster), both fixed, always-present click targets the
   player needs on every turn. The board area just below the topbar has
   no standing button of its own, and Gregor's messages (intros, hints,
   errors, wave-arrival banners) mostly fire at moments outside active
   tile-tapping anyway, so that's the safer resting spot. */
#oldman-overlay{
  display:none;position:fixed;inset:0;z-index:91;
  align-items:flex-start;justify-content:center;
  padding-top:calc(env(safe-area-inset-top) + 56px);
  pointer-events:none;
}
#oldman-overlay.show{display:flex;}
#oldman-box{
  display:flex;align-items:flex-end;gap:10px;
  max-width:340px;width:90%;
  pointer-events:auto;
  animation:oldmanSlideUp .35s cubic-bezier(.22,.8,.42,1) both;
}
@keyframes oldmanSlideUp{from{transform:translateY(40px);opacity:0;}to{transform:translateY(0);opacity:1;}}
#oldman-portrait{
  flex-shrink:0;width:72px;height:72px;
  border:2px solid var(--pch-bronze);border-radius:4px;
  background:var(--pch-walnut-dark);overflow:hidden;
  box-shadow:0 0 0 1px var(--pch-walnut-dark),0 4px 14px rgba(0,0,0,.8);
  image-rendering:pixelated;
}
#oldman-portrait img{width:100%;height:100%;object-fit:cover;image-rendering:pixelated;display:block;}
#oldman-bubble{
  flex:1;min-width:0;
  background:linear-gradient(180deg,var(--pch-parchment-light),var(--pch-parchment));
  border:1px solid var(--pch-bronze);border-radius:4px;
  padding:8px 10px 7px;
  box-shadow:0 0 0 1px var(--pch-walnut-dark),0 4px 14px rgba(0,0,0,.6);
  position:relative;
}
#oldman-bubble::before{
  content:'';position:absolute;left:-7px;bottom:18px;
  border-top:6px solid transparent;border-bottom:6px solid transparent;
  border-right:7px solid var(--pch-bronze);
}
#oldman-bubble::after{
  content:'';position:absolute;left:-5px;bottom:19px;
  border-top:5px solid transparent;border-bottom:5px solid transparent;
  border-right:6px solid var(--pch-parchment-light);
}
#oldman-name{
  color:var(--pch-burgundy);font-size:8px;font-weight:700;font-family:'Cinzel',serif;
  letter-spacing:1.5px;text-transform:uppercase;margin-bottom:4px;
}
#oldman-text{
  color:var(--pch-ink-soft);font-size:10px;line-height:1.5;font-style:italic;
}
#oldman-dismiss{
  display:block;margin-top:6px;
  color:var(--pch-bronze);font-size:7px;letter-spacing:1px;
  font-family:'Cinzel',serif;text-transform:uppercase;
  cursor:pointer;text-align:right;
  transition:color .15s;
}
#oldman-dismiss:hover{color:var(--pch-burgundy);}

/* ══ NECROMANCER DIALOGUE OVERLAY ═══════════════════════════════ */
#necro-overlay{
  display:none;position:absolute;inset:0;z-index:45;
  align-items:flex-end;justify-content:center;
  padding-bottom:12px;
  pointer-events:none;
  background:rgba(20,0,30,.55);
}
#necro-overlay.show{display:flex;}
#necro-box{
  display:flex;align-items:flex-end;gap:10px;
  max-width:340px;width:90%;
  pointer-events:auto;
  animation:oldmanSlideUp .35s cubic-bezier(.22,.8,.42,1) both;
}
#necro-portrait{
  flex-shrink:0;width:72px;height:72px;
  border:2px solid #6a20a0;border-radius:4px;
  background:#0a0010;overflow:hidden;
  box-shadow:0 0 0 1px #0a0010,0 4px 14px rgba(80,0,120,.6),0 0 20px rgba(120,0,200,.3);
  image-rendering:pixelated;
}
#necro-portrait img{width:100%;height:100%;object-fit:cover;image-rendering:pixelated;display:block;}
/* Shared by EVERY boss/unit dialogue that isn't Gregor (Necromancer,
   Goblin King, Ivory Queen, Dragon Lord, King Barron, Claudia, Ferrus,
   Gaia, Vxd -- see showGoblinKingDialogue/showIqDialogue/etc, they all
   reuse this one #necro-bubble element) -- now uses the same banner-card
   artwork as Chapter/Level Select instead of a bespoke purple bubble. */
#necro-bubble{
  flex:1;min-width:0;
  background:linear-gradient(180deg,#120818,#0a0410);
  border-style:solid;border-width:14px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:14px;
  border-image-source:url('assets/img/ui/shop_banner_purple.webp');
  padding:8px 10px 7px;
  box-shadow:0 4px 14px rgba(80,0,120,.5);
  position:relative;
}
#necro-name{
  color:#c060ff;font-size:8px;font-weight:700;font-family:'Cinzel',serif;
  letter-spacing:1.5px;text-transform:uppercase;margin-bottom:4px;
  text-shadow:0 0 8px #8020cc;
}
#necro-text{
  color:#d8b0f0;font-size:10px;line-height:1.5;font-style:italic;
}
#necro-dismiss{
  display:block;margin-top:6px;
  color:#6a20a0;font-size:7px;letter-spacing:1px;
  font-family:'Cinzel',serif;text-transform:uppercase;
  cursor:pointer;text-align:right;
  transition:color .15s;
}
#necro-dismiss:hover{color:#c060ff;}

/* ══ LEVEL SELECT SCREEN ════════════════════════════════════════ */
/* ══ CHAPTER SELECT SCREEN — ASSET-BASED ═══════════════════════ */
#screen-chapter{
  display:none;flex-direction:column;flex:1;position:relative;
  height:100%;overflow:hidden;
}
/* #chp-header-controls positioning lives further down, alongside the
   rest of the Chapter Select redesign (previously duplicated here with
   a conflicting value that got silently overridden later in the file). */
/* #chp-back-btn is now defined further down, alongside the rest of the
   Chapter Select redesign, using the shop banner border-image treatment. */

/* ══ NAME ENTRY SCREEN ══════════════════════════════════════════
   Same full-bleed layout as #screen-title (it IS a top screen, shown
   before title rather than as an overlay on top of it, since the title
   screen's own player bar needs a real profile to render). The box/input/
   button styling deliberately mirrors #confirm-box and .army-edit-name-
   input rather than inventing a new visual language for a screen the
   player only ever sees once. */
/* BUGFIX (Sep 2026): the scroll fix in the previous pass put overflow-y:auto
   on #screen-name-entry itself, with #name-entry-bg absolutely positioned
   (inset:0) inside it -- inset:0 sizes to the ELEMENT's own box (the
   original, pre-scroll viewport height), not to the taller scrolled content
   height, so scrolling down revealed bare unbacked space below it. Moved
   the scrolling down onto #name-entry-box itself instead: the outer screen
   is back to a fixed, non-scrolling full-height box (so #name-entry-bg's
   inset:0 always exactly matches the visible screen, scroll or not), and
   the box now scrolls its own content internally when it's taller than
   the available space. The box's own background (bg_parchment, set
   directly on it below) stays put across that internal scroll by default
   (background-attachment:scroll ties a background to the element's own
   box, not to its scrolled content), so nothing goes bare either way.
*/
#screen-name-entry,#screen-signed-out{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  flex:1;position:relative;height:100%;overflow:hidden;
}
#name-entry-bg,#signed-out-bg{
  position:absolute;inset:0;z-index:0;
  background:url('assets/img/backgrounds/title_bg.webp') center/cover no-repeat;
}
#name-entry-bg::after,#signed-out-bg::after{content:'';position:absolute;inset:0;background:rgba(0,0,0,.4);}
/* Redesigned per developer request (Sep 2026) to reuse existing plate
   assets instead of flat CSS gradients:
   - box background: bg_parchment.png (same texture as the Victory/Defeat
     card and Ashigami intro card elsewhere)
   - header: brawler_find_opponent_banner.png, same asset already used
     as Brawler's "Find Opponent" banner, reused here purely as a header
     plate (not a button) carrying the welcome title
   - buttons: army_save.png ribbon, same asset already used for the Army
     Builder's Save button
   #screen-signed-out (Sep 2026) reuses this exact same visual language --
   deliberately not a new look, since the player has already seen this
   screen's styling once at name-entry and re-learning a new appearance
   right after signing out would add friction for no benefit. */
#name-entry-box,#signed-out-box{
  position:relative;z-index:1;
  width:88%;max-width:340px;
  max-height:94%;overflow-y:auto;-webkit-overflow-scrolling:touch;
  background:url('assets/img/ui/bg_parchment.webp') center/cover no-repeat;
  border-radius:6px;
  padding:0 18px 20px;text-align:center;
  box-shadow:0 8px 32px rgba(0,0,0,.85),0 0 0 1px rgba(120,90,40,.4);
  animation:rewardPop .25s cubic-bezier(.22,.8,.42,1) both;
}
#name-entry-banner-wrap,#signed-out-banner-wrap{
  position:relative;width:104%;left:-2%;margin-top:-4px;
  aspect-ratio:1191/256;
  background:url('assets/img/ui/brawler_find_opponent_banner.webp') center/contain no-repeat;
}
#name-entry-banner-label,#signed-out-banner-label{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-family:'Cinzel Decorative',serif;font-size:15px;font-weight:700;letter-spacing:1px;
  color:#f5e6bc;text-shadow:0 1px 3px rgba(0,0,0,.8);padding:0 20%;
}
#name-entry-text,#signed-out-text{font-family:'Cinzel',serif;font-size:12px;color:#3a2c18;line-height:1.5;margin:14px 0;}
.name-entry-input{
  display:block;margin:0 auto 8px;width:100%;text-align:center;
  background:rgba(255,255,255,.55);border:1px solid var(--pch-bronze);border-radius:4px;
  color:#241a0e;font-family:'Cinzel Decorative',serif;font-size:15px;
  padding:9px 10px;letter-spacing:.5px;
}
#name-entry-error{font-family:'Cinzel',serif;font-size:10px;color:#a02020;min-height:14px;margin-bottom:4px;}
/* Ribbon-style button shared by the Begin/Google/Apple actions on this
   screen -- same army_save.png asset/aspect-ratio-locked treatment as
   .army-save-btn, just sized down for the two secondary sign-in buttons.
   Also shared by #screen-signed-out's three buttons (same classes, see
   index.html) -- no separate rules needed there. */
.name-entry-ribbon-btn{
  width:min(230px,80%);aspect-ratio:1965/542;margin:0 auto;
  background:url('assets/img/ui/army_save.webp') center/100% 100% no-repeat;
  border:none;border-radius:0;padding:0;display:block;
  color:#fff8e0;font-family:'Cinzel Decorative',serif;font-size:13px;letter-spacing:1px;
  text-shadow:0 1px 3px rgba(0,20,0,.85),0 0 6px rgba(0,0,0,.4);
  cursor:pointer;transition:filter .12s,transform .1s;
}
.name-entry-ribbon-btn:hover{filter:brightness(1.12);}
.name-entry-ribbon-btn:active{transform:scale(.97);}
.name-entry-ribbon-btn-sm{width:min(210px,74%);font-size:11px;margin-top:6px;}
#name-entry-divider,#signed-out-divider{
  display:flex;align-items:center;gap:8px;margin:14px 0 8px;
  font-family:'Cinzel',serif;font-size:10px;color:#5a4a30;letter-spacing:1px;text-transform:uppercase;
}
#name-entry-divider::before,#name-entry-divider::after,#signed-out-divider::before,#signed-out-divider::after{content:'';flex:1;height:1px;background:rgba(90,74,48,.4);}
#name-entry-returning-text,#signed-out-returning-text{font-family:'Cinzel',serif;font-size:10.5px;color:#3a2c18;line-height:1.5;margin-bottom:10px;}
/* #screen-signed-out's guest button gets its own top margin since,
   unlike name-entry's flow, there's no text input pushing it down
   naturally -- otherwise identical .name-entry-ribbon-btn styling. */
#signed-out-guest-btn{margin-top:2px;}

/* ══ GUEST PURCHASE BACKUP NUDGE (Sep 2026) ══════════════════════
   Shown at most once per session, right after a guest's first real
   purchase -- see _maybeShowGuestBackupNudge() in game.js. Shares the
   exact same modal/box/button pattern as confirm-modal and friends
   (#confirm-modal etc. below) rather than inventing a new visual
   language. */
#backup-nudge-modal,#iap-signin-modal{
  display:none;position:fixed;inset:0;z-index:80;
  background:rgba(0,0,0,.72);align-items:center;justify-content:center;
  backdrop-filter:blur(2px);
}
#backup-nudge-modal.show,#iap-signin-modal.show{display:flex;}
#backup-nudge-box,#iap-signin-box{
  width:82%;max-width:320px;
  background:linear-gradient(180deg,#241a0e,#150f08);
  border:1px solid var(--pch-gold);border-radius:8px;
  padding:18px 16px 16px;text-align:center;
  box-shadow:0 8px 32px rgba(0,0,0,.85);
  animation:rewardPop .25s cubic-bezier(.22,.8,.42,1) both;
}
#backup-nudge-title,#iap-signin-title{font-family:'Cinzel Decorative',serif;font-size:13px;color:var(--pch-gold-bright);letter-spacing:1px;margin-bottom:8px;font-weight:700;}
#backup-nudge-text,#iap-signin-text{font-family:'Cinzel',serif;font-size:11px;color:#e8e0c8;line-height:1.5;margin-bottom:14px;}
#backup-nudge-btns,#iap-signin-btns{display:flex;gap:6px;justify-content:center;flex-wrap:wrap;}
#backup-nudge-later-btn,#backup-nudge-google-btn,#backup-nudge-apple-btn,#iap-signin-cancel-btn,#iap-signin-google-btn,#iap-signin-apple-btn{font-family:'Cinzel',serif;font-size:10px;letter-spacing:.5px;padding:8px 12px;border-radius:4px;cursor:pointer;font-weight:700;}
#backup-nudge-later-btn,#iap-signin-cancel-btn{background:linear-gradient(180deg,var(--pch-bronze-light),var(--pch-bronze));border:1px solid var(--pch-gold);color:#2a1c0c;}
#backup-nudge-later-btn:hover,#iap-signin-cancel-btn:hover{filter:brightness(1.1);}
#backup-nudge-google-btn,#backup-nudge-apple-btn,#iap-signin-google-btn,#iap-signin-apple-btn{background:linear-gradient(180deg,var(--pch-gold),#8a6820);border:1px solid var(--pch-bronze);color:#241a0e;}
#backup-nudge-google-btn:hover,#backup-nudge-apple-btn:hover,#iap-signin-google-btn:hover,#iap-signin-apple-btn:hover{filter:brightness(1.1);}

/* ══ TITLE SCREEN ══════════════════════════════════════════════ */
#screen-title{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  flex:1;position:relative;height:100%;overflow:hidden;
}
#title-bg{
  position:absolute;inset:0;z-index:0;
  background:url('assets/img/backgrounds/title_bg.webp') center/cover no-repeat;
}
#title-content{
  position:relative;z-index:1;display:flex;flex-direction:column;
  align-items:center;gap:28px;padding:20px;
  /* Scroll fix: #screen-title itself is a fixed, non-scrolling box
     (overflow:hidden, so #title-bg always covers exactly the screen).
     Before this, #title-content had NO scroll mechanism of its own --
     it relied entirely on the OLD page-level body scroll (removed
     several fixes ago to stop the topbar cut-off bug), so once that was
     gone, any overflow (e.g. all 5 menu buttons not fitting on a short
     landscape phone) became completely unreachable. max-height:100%
     caps it to the available box so it can scroll internally instead --
     harmless when content already fits, since #screen-title's own
     justify-content:center still centers it normally in that case.
     width:100% -- without this, #title-content shrink-wraps to its
     widest child (the ~340px-wide menu column) because #screen-title
     centers it horizontally, so the scrollable box itself was only ever
     as wide as the buttons -- a touch starting in the wider empty
     margins on either side never registered as touching a scrollable
     element at all, and did nothing. Full width means a swipe anywhere
     across the screen scrolls it, not just directly over the buttons. */
  width:100%;max-height:100%;overflow-y:auto;touch-action:pan-y;
  /* Hide the OS-level scrollbar track -- visible on large screens/desktop
     Chrome where this box actually has overflow to scroll (phones rarely
     show it since the content fits). Same convention already used for
     #chp-cards/#lvl-cards/#shop-cards. */
  scrollbar-width:none;
}
#title-content::-webkit-scrollbar{display:none;}
#title-logo-wrap{display:flex;justify-content:center;}
#title-logo-img{
  width:min(82vw,340px);height:auto;
  filter:drop-shadow(0 4px 12px rgba(0,0,0,.65));
}
#title-menu{display:flex;flex-direction:column;gap:12px;width:min(88vw,340px);}
.title-menu-btn{
  position:relative;display:flex;flex-direction:column;justify-content:center;
  align-items:flex-start;text-align:left;
  padding:8px 40px 8px 68px;min-height:60px;
  border:none;cursor:pointer;background-color:transparent;
  background-size:100% 100%;background-repeat:no-repeat;background-position:center;
  transition:transform .15s,filter .15s;
}
.title-menu-btn:hover{transform:translateY(-2px);filter:brightness(1.12);}
.title-menu-btn:active{transform:translateY(0) scale(.98);filter:brightness(.92);}
.title-menu-campaign{background-image:url('assets/img/ui/btn_menu_campaign.webp');}
.title-menu-brawler{background-image:url('assets/img/ui/btn_menu_brawler.webp');}
.title-menu-army{background-image:url('assets/img/ui/btn_menu_army.webp');}
.title-menu-settings{background-image:url('assets/img/ui/btn_menu_settings.webp');}
.title-menu-shop{background-image:url('assets/img/ui/btn_menu_shop.webp');}
.title-menu-label{
  font-family:'Cinzel Decorative',serif;font-size:16px;letter-spacing:1.5px;
  color:var(--pch-gold-bright);text-shadow:0 1px 3px rgba(0,0,0,.75);
}
.title-menu-sub{
  font-family:'Cinzel',serif;font-size:9px;letter-spacing:.3px;
  color:#d8c8a8;text-shadow:0 1px 2px rgba(0,0,0,.75);
}
.title-menu-chevron{
  position:absolute;right:16px;top:50%;transform:translateY(-50%);
  font-size:20px;line-height:1;color:var(--pch-gold-bright);
  text-shadow:0 1px 2px rgba(0,0,0,.6);
}
/* Locked title menu buttons (Brawler/Army Builder/Shop before Chapter II
   is cleared) -- stays clickable (tapping it is what triggers Gregor's
   comedic pushback, see titleStartBrawler()/showArmyBuilder()/
   showShopScreen() in game.js) but reads as visually unavailable:
   dimmed + desaturated, and the hover-lift is suppressed so it doesn't
   invite a tap the way a live button would. */
.title-menu-btn-locked{opacity:.42;filter:grayscale(.5);}
.title-menu-btn-locked:hover{transform:none;filter:grayscale(.5);}

/* ══ SETTINGS SCREEN ═══════════════════════════════════════════ */
#screen-settings{
  display:none;flex-direction:column;flex:1;height:100%;
  background:linear-gradient(180deg,var(--pch-walnut),var(--pch-walnut-dark));
  align-items:center;justify-content:center;position:relative;
}
#settings-content{display:flex;flex-direction:column;align-items:stretch;gap:18px;width:100%;max-width:340px;padding:0 20px;}
#settings-title{
  font-family:'Cinzel Decorative',serif;font-size:22px;letter-spacing:2px;color:var(--pch-gold-bright);
  text-align:center;margin-bottom:6px;
}
#screen-settings .army-back-btn{position:absolute;top:10px;left:10px;}
#settings-list{display:flex;flex-direction:column;gap:2px;}
/* Build version -- BUILD_VERSION in game.js, populated by
   showSettingsScreen(). Exists so staleness (wrong build running vs
   what was actually delivered/deployed) is a 2-second on-device check
   instead of a guessing game. */
#settings-version{
  text-align:center;margin-top:6px;
  font-family:'Cinzel',serif;font-size:9px;letter-spacing:.3px;
  color:rgba(216,200,168,.5);
}
.settings-row{
  display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:14px 4px;border-bottom:1px solid rgba(255,255,255,.08);
}
.settings-row-label{display:flex;flex-direction:column;gap:3px;min-width:0;}
.settings-row-name{font-family:'Cinzel',serif;font-size:13px;color:var(--pch-gold-bright);font-weight:700;letter-spacing:.4px;}
.settings-row-desc{font-family:'Cinzel',serif;font-size:9px;color:#b8a888;line-height:1.3;}
/* Toggle switch -- OFF: dark track, knob left. ON: gold track, knob right. */
.settings-toggle{
  position:relative;flex-shrink:0;width:44px;height:24px;border-radius:12px;
  background:#241a10;border:1px solid var(--pch-bronze);cursor:pointer;padding:0;
  box-shadow:inset 0 1px 3px rgba(0,0,0,.6);transition:background .2s,border-color .2s;
}
.settings-toggle.on{background:linear-gradient(180deg,var(--pch-gold-bright),var(--pch-bronze));border-color:var(--pch-gold);}
.settings-toggle-knob{
  position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;
  background:linear-gradient(180deg,#f0e4c8,#c8b088);box-shadow:0 1px 3px rgba(0,0,0,.5);
  transition:transform .2s;
}
.settings-toggle.on .settings-toggle-knob{transform:translateX(20px);background:linear-gradient(180deg,#fff8e0,#f0e0a0);}

/* ══ PLAYER CURRENCY BAR (title + shop headers) ═══════════════ */
.player-bar{
  position:absolute;top:10px;right:10px;z-index:3;
  display:flex;align-items:center;gap:6px;
  padding:3px 10px 3px 6px;
  border-style:solid;border-width:10px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:10px;
  border-image-source:url('assets/img/ui/shop_banner_purple.webp');
  box-shadow:0 3px 8px rgba(0,0,0,.4);
}
.player-bar-avatar{
  width:26px;height:26px;border-radius:50%;flex-shrink:0;
  display:flex;align-items:center;justify-content:center;
  background:radial-gradient(circle at 35% 30%,#7a5a28,#3a2810);
  border:1px solid var(--pch-gold);color:var(--pch-gold-bright);
  font-family:'Cinzel Decorative',serif;font-size:13px;
}
.player-bar-info{display:flex;flex-direction:column;gap:1px;}
.player-bar-name{
  font-family:'Cinzel',serif;font-size:11px;font-weight:700;letter-spacing:1px;
  color:var(--pch-gold-bright);text-shadow:0 1px 2px rgba(0,0,0,.6);
}
.player-bar-currencies{display:flex;gap:10px;}
.player-bar-currency{display:flex;align-items:center;gap:3px;}
.player-bar-icon{width:15px;height:15px;object-fit:contain;}
.player-bar-coins,.player-bar-gems,.player-bar-astral-gems{
  /* .player-bar-astral-gems added Oct 2026 (PrimeTestLab M-03): the third
     counter was missing from this rule, so its number fell back to dark
     text on the purple panel and was almost invisible. */
  font-family:'Cinzel',serif;font-size:12px;font-weight:700;color:#e8dcc0;
}
/* TESTING ONLY -- switches between the Jace/Alice seeded profiles */
.player-bar-switch-btn{
  width:20px;height:20px;border-radius:50%;flex-shrink:0;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  background:rgba(0,0,0,.35);border:1px solid var(--pch-gold);
  color:var(--pch-gold-bright);font-size:11px;line-height:1;padding:0;
}
.player-bar-switch-btn:hover{filter:brightness(1.2);}
.player-bar-switch-btn:active{transform:scale(.9);}

/* Hidden by default -- only shown when the player has explicitly enabled
   dev mode (localStorage.setItem('tactix_dev_mode','1')). Currently just
   the test-profile switcher above. */
.dev-only-btn{display:none;}
body.dev-mode .dev-only-btn{display:flex;}

.player-bar-signin-btn{
  flex-shrink:0;cursor:pointer;
  background:rgba(0,0,0,.35);border:1px solid var(--pch-gold);
  color:var(--pch-gold-bright);font-family:'Cinzel',serif;
  font-size:10px;font-weight:700;line-height:1;
  padding:4px 8px;border-radius:10px;white-space:nowrap;
}
.player-bar-signin-btn:hover{filter:brightness(1.2);}
.player-bar-signin-btn:active{transform:scale(.95);}

.player-bar-apple-btn{
  flex-shrink:0;cursor:pointer;
  background:rgba(0,0,0,.35);border:1px solid var(--pch-gold);
  color:var(--pch-gold-bright);font-family:'Cinzel',serif;
  font-size:10px;font-weight:700;line-height:1;
  padding:4px 8px;border-radius:10px;white-space:nowrap;
}
.player-bar-apple-btn:hover{filter:brightness(1.2);}
.player-bar-apple-btn:active{transform:scale(.95);}

/* ══ COLLECTION REWARDS BUTTON (title screen, top-left -- mirrors
   .player-bar's top-right treatment/asset so the two corners read as a
   matched pair) ══════════════════════════════════════════════════ */
.collection-rewards-btn{
  position:absolute;top:10px;left:10px;z-index:3;
  display:flex;align-items:center;gap:6px;cursor:pointer;
  padding:3px 12px 3px 8px;border:none;background-color:transparent;
  border-style:solid;border-width:10px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:10px;
  border-image-source:url('assets/img/ui/shop_banner_purple.webp');
  box-shadow:0 3px 8px rgba(0,0,0,.4);
  transition:filter .12s,transform .08s;
}
.collection-rewards-btn:hover{filter:brightness(1.15);}
.collection-rewards-btn:active{transform:scale(.96);}
.collection-rewards-btn-icon{width:18px;height:18px;object-fit:contain;}
.collection-rewards-btn-label{
  font-family:'Cinzel',serif;font-size:11px;font-weight:700;letter-spacing:.8px;
  color:var(--pch-gold-bright);text-shadow:0 1px 2px rgba(0,0,0,.6);
}

/* ══ COLLECTION REWARDS SCREEN ═══════════════════════════════════ */
/* Same skeleton as #screen-shop (bg layer + dark gradient + centered
   content column) -- see there for the pattern this mirrors. */
#screen-collection{
  display:none;flex-direction:column;flex:1;height:100%;position:relative;
  overflow:hidden;
}
#screen-brawler-rewards{
  display:none;flex-direction:column;flex:1;height:100%;position:relative;
  overflow:hidden;
}
/* Reuses the reward-row chrome from Collection Rewards (.shop-item,
   .collection-reward-row, .collection-reward-threshold, the cycling
   border colors) -- see renderBrawlerSeasonRewards() in game.js. */
#collection-bg,.collection-bg-shared{
  position:absolute;inset:0;z-index:0;
  background:url('assets/img/backgrounds/chapter_select_bg.webp') center/cover no-repeat;
}
#collection-bg::after,.collection-bg-shared::after{
  content:'';position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(30,18,8,.18),rgba(20,12,4,.38));
}
#collection-content,.collection-content-shared{
  position:relative;z-index:1;display:flex;flex-direction:column;flex:1;
  align-items:center;gap:14px;padding:56px 16px 30px;width:100%;box-sizing:border-box;
  /* Scroll fix: the scroll capability used to live on #screen-collection
     itself. #collection-bg is `position:absolute;inset:0` -- for an
     absolutely positioned element that sizes to its containing block's
     HEIGHT PROPERTY, not to however tall the scrolled content gets, so
     it was always exactly one screen tall regardless of content. Since
     it scrolled along with #screen-collection's content (not pinned the
     way position:fixed would be), scrolling down moved it up and out of
     view while the now-taller reward list kept going -- exactly the
     "background ends" symptom reported. Fix: keep #screen-collection
     itself a fixed, non-scrolling box (so #collection-bg always covers
     it exactly, at every scroll position) and move the actual scrolling
     to this inner content wrapper instead -- same pattern already used
     correctly by Chapter/Level Select's own content wrappers. */
  overflow-y:auto;touch-action:pan-y;
  /* Hide the OS-level scrollbar track -- visible on large screens/desktop
     Chrome where this box actually has overflow to scroll. Same
     convention already used for #chp-cards/#lvl-cards/#shop-cards. */
  scrollbar-width:none;
}
#collection-content::-webkit-scrollbar,.collection-content-shared::-webkit-scrollbar{display:none;}
#screen-collection .army-back-btn,#screen-brawler-rewards .army-back-btn{
  position:absolute;top:10px;left:10px;z-index:3;
  background:none;border-style:solid;border-width:14px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:14px;
  border-image-source:url('assets/img/ui/shop_banner_red.webp');
  color:var(--pch-gold-bright);padding:5px 12px;
  font-size:9px;font-family:'Cinzel',serif;letter-spacing:.5px;cursor:pointer;
}
/* Title switched from a flat color to a shiny dual-tone gold gradient
   (same background-clip:text technique as the Army Builder title -- see
   there for the full explanation) plus bumped up in weight/size so it
   reads as bold and crisp rather than thin, per developer request. */
#collection-title,.collection-title-shared{
  display:flex;align-items:center;gap:8px;
  font-family:'Cinzel Decorative',serif;font-size:24px;font-weight:700;letter-spacing:1.5px;
  text-align:center;
}
/* Bramble Realms crest -- sized way up (28px -> 64px) per developer
   request so it actually reads as a logo mark next to the title instead
   of a small favicon-sized afterthought. */
#collection-title-logo,.collection-title-shared img{height:64px;width:auto;object-fit:contain;filter:drop-shadow(0 2px 4px rgba(0,0,0,.5));}
#collection-progress-bar-wrap{width:min(92vw,420px);display:flex;flex-direction:column;gap:8px;align-items:center;}
/* Progress count now reads as a small pill/tag sitting above the bar
   (design-brief style) instead of plain caption text underneath it, in
   bold crisp gold to match the title treatment. */
#collection-progress-count{
  font-family:'Cinzel',serif;font-size:11px;font-weight:700;letter-spacing:1px;
  color:var(--pch-gold-bright);text-transform:uppercase;
  background:rgba(0,0,0,.4);border:1px solid var(--pch-gold);border-radius:12px;
  padding:3px 14px;text-shadow:0 1px 2px rgba(0,0,0,.7);
}
/* Wraps the track + the milestone tick layer so milestone dots/labels can
   be absolutely positioned against the bar's exact width regardless of
   where in the layout it ends up -- extra bottom padding reserves room
   for the threshold-number labels sitting just under the track. */
#collection-progress-track-wrap{position:relative;width:100%;padding-bottom:16px;}
#collection-progress-bar-track{
  width:100%;height:14px;border-radius:8px;background:rgba(0,0,0,.4);
  border:1px solid var(--pch-bronze);overflow:hidden;box-shadow:inset 0 1px 3px rgba(0,0,0,.6);
  position:relative;z-index:1;
}
#collection-progress-bar-fill{
  height:100%;width:0%;border-radius:8px 0 0 8px;
  background:linear-gradient(90deg,var(--pch-gold),#ffe060);
  box-shadow:0 0 8px rgba(255,220,120,.6);
  transition:width .4s ease;
}
/* Milestone ticks -- one per COLLECTION_REWARD_TIERS entry, positioned by
   JS (renderCollectionRewards) at left:<threshold/finalThreshold>% so
   they sit at their true proportional spot on the bar rather than being
   evenly spaced. A small gold diamond marks the spot on the track itself;
   the threshold number sits just below it. Reached milestones (collected
   total already past that threshold) glow brighter gold; unreached ones
   stay a dim bronze. */
#collection-milestones{position:absolute;inset:0;pointer-events:none;z-index:2;}
.collection-milestone{
  position:absolute;top:0;transform:translateX(-50%);
  display:flex;flex-direction:column;align-items:center;
}
.collection-milestone-dot{
  width:9px;height:9px;margin-top:2.5px;
  background:linear-gradient(135deg,#7a6238,#4a3a20);
  border:1px solid #2a2010;transform:rotate(45deg);
  box-shadow:0 1px 2px rgba(0,0,0,.5);
}
.collection-milestone.reached .collection-milestone-dot{
  background:linear-gradient(135deg,#fff3c8,var(--pch-gold));
  border-color:#fff3c8;box-shadow:0 0 6px rgba(255,220,120,.85);
}
.collection-milestone-label{
  margin-top:3px;font-family:'Cinzel',serif;font-size:8px;font-weight:700;
  color:#a8916c;text-shadow:0 1px 2px rgba(0,0,0,.7);white-space:nowrap;
}
.collection-milestone.reached .collection-milestone-label{color:var(--pch-gold-bright);}
#collection-rewards-list{display:flex;flex-direction:column;gap:14px;width:min(92vw,420px);}
/* Reward rows reuse .shop-item's exact card chrome (border-image, hover
   sparkle/sweep, padding) -- see .shop-item above -- rather than
   reinventing a parallel card style. Only the locked-state dimming and
   claimed-button color are new here. Icon/name/threshold sized up (and
   given the same shiny-gold text treatment as the title) scoped to
   `.collection-reward-row` specifically so the Shop screen's own
   `.shop-item`/`.shop-item-name` sizing elsewhere is untouched. */
.collection-reward-row{padding:14px 20px;min-height:96px;}
.collection-reward-row.locked{filter:grayscale(.55) brightness(.8);opacity:.75;}
.collection-reward-row.locked:hover{box-shadow:0 4px 14px rgba(0,0,0,.4);}
.collection-reward-row.locked:hover::before{opacity:0;animation:none;}
.collection-reward-row.locked:hover::after{left:-160%;}
.collection-reward-row .shop-item-img{width:118px;height:118px;}
/* Info column (threshold/name/status) centered rather than left-aligned
   -- per developer request -- since the icon and Claim button already
   anchor the two ends of the row, centering reads better than text
   hugging the left edge. */
.collection-reward-row .shop-item-info{align-items:center;text-align:center;}
.collection-reward-threshold{
  font-family:'Cinzel',serif;font-size:10px;font-weight:700;letter-spacing:1px;
  text-transform:uppercase;color:#c8ac7a;text-shadow:0 1px 2px rgba(0,0,0,.6);margin-bottom:2px;
}
.collection-reward-row .shop-item-name{
  font-family:'Cinzel Decorative',serif;font-size:19px;font-weight:700;letter-spacing:.5px;
}
.collection-reward-row .shop-item-desc{
  font-family:'Cinzel',serif;font-size:10px;font-weight:700;letter-spacing:.4px;
  text-transform:uppercase;color:#d8c8a8;
}
.collection-reward-row.locked .shop-item-desc{color:#a89878;text-transform:none;font-weight:400;font-style:italic;letter-spacing:normal;}
#collection-rewards-list .collection-reward-claim-btn.claimed{
  background:linear-gradient(180deg,#4a7040,#2c4626);border:1px solid #6a9058;
  color:#e8f0d8;opacity:1;filter:none;cursor:default;
  width:auto;aspect-ratio:auto;border-radius:6px;padding:8px 14px;
}
/* Claim button now uses the dedicated gold_button.png plate asset (native
   1931x656, ratio ~2.944) instead of the flat CSS gradient .shop-buy-btn
   uses everywhere else -- scoped with the #collection-rewards-list
   ancestor for specificity so it wins over the shared .shop-buy-btn rule
   without touching the Shop screen's own buy buttons, which still use
   the plain gradient style. Sized via width + aspect-ratio (like the Save
   Army ribbon) so the plate art is never stretched off-ratio. */
#collection-rewards-list .collection-reward-claim-btn{
  width:104px;aspect-ratio:1931/656;
  background:url('assets/img/ui/gold_button.webp') center/100% 100% no-repeat;
  border:none;border-radius:0;padding:0;
  color:#3a2408;font-weight:800;letter-spacing:.3px;
  text-shadow:0 1px 0 rgba(255,255,255,.4);
  justify-content:center;
}
#collection-rewards-list .collection-reward-claim-btn:disabled{
  filter:grayscale(.55) brightness(.85);opacity:.85;
}

/* ══ SHOP SCREEN ═══════════════════════════════════════════════ */
#screen-shop{
  display:none;flex-direction:column;flex:1;height:100%;position:relative;
  overflow:hidden;
}
#shop-bg{
  position:absolute;inset:0;z-index:0;
  background:url('assets/img/backgrounds/shop_bg.webp') center/cover no-repeat;
}
/* Redesigned to match Chapter Select's structure -- a fixed, non-
   scrolling banner header up top (see #shop-banner-wrap) and the actual
   item row (#shop-cards) taking the rest of the height and doing its own
   scrolling, horizontally in landscape (see the
   @media(orientation:landscape) rules further down, shared with
   #chp-cards/#lvl-cards). Kept #shop-content itself non-scrolling now
   (unlike the old version) since #shop-cards handles its own overflow
   the same way #chp-cards does -- no more risk of #shop-bg scrolling out
   from behind the content. */
#shop-content{
  position:relative;z-index:1;display:flex;flex-direction:column;flex:1;
  padding:0 0 12px;width:100%;box-sizing:border-box;overflow:hidden;
}
#screen-shop .army-back-btn,#screen-iap-shop .army-back-btn{
  background:none;border-style:solid;border-width:14px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:14px;
  border-image-source:url('assets/img/ui/shop_banner_red.webp');
  color:var(--pch-gold-bright);padding:5px 12px;
  font-size:9px;font-family:'Cinzel',serif;letter-spacing:.5px;cursor:pointer;
  position:absolute;top:10px;left:10px;z-index:5;
}
/* Title banner -- same title_banner.png artwork/technique as Chapter
   Select's #chp-banner-wrap (per developer request), with the "shiny
   dual-tone gold" .gold-shine-text treatment from Collection Rewards on
   the title itself instead of a flat color, and bumped up a size (26px
   vs Chapter Select's 23px) since "SHOP" is a short word with room to
   read bigger/bolder. */
#shop-banner-wrap{
  position:relative;flex-shrink:0;
  width:min(60%,600px);aspect-ratio:3/1;
  margin:6px auto 0;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:0 15%;
  background-image:url('assets/img/ui/title_banner.webp');
  background-size:100% 100%;background-repeat:no-repeat;background-position:center;
  filter:drop-shadow(0 4px 10px rgba(0,0,0,.45));
}
/* Same box model as .army-back-btn right next to it (padding/font-size),
   so both buttons come out about the same width/height per developer
   spec -- just swaps the red banner border-image for a gold_button.png
   background instead, since this one navigates somewhere rather than
   going back. */
/* Positioned directly below Title, whose actual rendered box was
   measured via Web Inspector as 77.81x50 at top:10px/left:10px (Title
   itself uses .army-back-btn's shared position:absolute;top:0;left:0,
   inherited from the base rule used by every back button in the game --
   NOT overridden here, since fighting that per-screen was what caused
   the overlap bug in the first place). 8px gap below Title's bottom
   edge (10+50+8=68). Explicit width/height matching Title's measured
   box exactly, rather than auto-sizing from padding+font like Title
   does -- Title's size comes from its border-image treatment (14px
   border eating into the box), which this button doesn't use, so
   matching size needs to be explicit rather than incidental. */
#shop-iap-nav-btn{
  position:absolute;top:68px;left:10px;z-index:5;
  width:78px;height:50px;
  display:flex;align-items:center;justify-content:center;
  background:url('assets/img/ui/gold_button.webp') center/100% 100% no-repeat;
  border:none;padding:0;cursor:pointer;
  color:#3a2408;font-family:'Cinzel',serif;font-size:9px;font-weight:800;letter-spacing:.2px;
  text-align:center;line-height:1.15;
  text-shadow:0 1px 0 rgba(255,255,255,.4);
  transition:filter .12s,transform .08s;
}
#shop-iap-nav-btn:hover{filter:brightness(1.1);}
#shop-iap-nav-btn:active{transform:scale(.95);}
#shop-banner-text{
  position:relative;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  pointer-events:none;
}
#shop-title{
  font-size:26px;font-weight:700;letter-spacing:2.5px;
  font-family:'Cinzel Decorative',serif;
  line-height:1;white-space:nowrap;
}
#shop-sub{
  margin-top:5px;text-align:center;
  color:#d8c8a8;font-size:11px;letter-spacing:1.6px;
  text-transform:uppercase;font-style:italic;
  text-shadow:0 1px 2px rgba(0,0,0,.6);
  white-space:nowrap;
}
/* ══ REAL-MONEY COIN PACKS SCREEN -- exact structural copy of
   #screen-shop/#shop-bg/#shop-content/#shop-banner-wrap/#shop-title/
   #shop-sub just above (same background image, same layout technique),
   per developer request to keep this looking like "the Shop" rather
   than a visually distinct screen. Only the ids differ, so both can
   exist in the DOM at once. #iap-cards/.shop-card chrome itself is fully
   shared already (see #shop-cards,#iap-cards above and .shop-card
   further down) -- nothing IAP-specific needed there. ══ */
#screen-iap-shop{
  display:none;flex-direction:column;flex:1;height:100%;position:relative;
  overflow:hidden;
}
#iap-shop-bg{
  position:absolute;inset:0;z-index:0;
  background:url('assets/img/backgrounds/shop_bg.webp') center/cover no-repeat;
}
#iap-shop-content{
  position:relative;z-index:1;display:flex;flex-direction:column;flex:1;
  padding:0 0 12px;width:100%;box-sizing:border-box;overflow:hidden;
}
#iap-shop-banner-wrap{
  position:relative;flex-shrink:0;
  width:min(60%,600px);aspect-ratio:3/1;
  margin:6px auto 0;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:0 15%;
  background-image:url('assets/img/ui/title_banner.webp');
  background-size:100% 100%;background-repeat:no-repeat;background-position:center;
  filter:drop-shadow(0 4px 10px rgba(0,0,0,.45));
}
#iap-shop-banner-text{
  position:relative;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  pointer-events:none;
}
#iap-shop-title{
  font-size:26px;font-weight:700;letter-spacing:2.5px;
  font-family:'Cinzel Decorative',serif;
  line-height:1;white-space:nowrap;
}
#iap-shop-sub{
  margin-top:5px;text-align:center;
  color:#d8c8a8;font-size:11px;letter-spacing:1.6px;
  text-transform:uppercase;font-style:italic;
  text-shadow:0 1px 2px rgba(0,0,0,.6);
  white-space:nowrap;
}
/* Cards row -- vertical/scroll-y fallback for non-landscape views;
   becomes a horizontal scroll-snap strip in real (landscape-locked)
   play, sharing that behavior with #chp-cards/#lvl-cards (see the
   @media(orientation:landscape) block). Extra top padding (54px vs the
   22px every other row uses) exists solely to give the Adventurers
   Tin's "Best Value" banner room to float above its card without
   getting clipped by this row's own edge -- see .shop-card-value-banner
   below for why it needs that headroom. */
#shop-cards,#iap-cards{
  flex:1;display:flex;flex-direction:column;gap:14px;align-items:center;
  padding:54px 10px 6px;overflow-y:auto;touch-action:pan-y;
}
/* Featured slot wrapper -- ONLY the Adventurers Tin uses this, so the
   "Best Value" ribbon can float above its card. Deliberately NOT sized
   by the banner (see .shop-card-value-banner's position:absolute below)
   -- the wrapper's box is exactly the card's own height, same as every
   plain .shop-card sibling, so all four cards' TOP edges stay aligned in
   the row instead of the featured one being pushed down by the banner's
   own flow height (that was the previous bug). The wrapper is the actual
   flex item in #shop-cards' row; the .shop-card inside it goes back to a
   plain, un-sized panel that just fills whatever width the wrapper gives
   it (see the width:100% overrides in the landscape rules further down)
   rather than carrying its own fixed width/flex-basis like the other
   cards do directly. */
.shop-card-featured-wrap{
  position:relative;
  width:100%;max-width:280px;
}
/* ── Shop card -- full-bleed panel art (shop_tin_booster_panel.png etc.)
   IS the card: no border-image frame, no separate icon-in-a-box layout.
   The panel already bakes in its own background glow/frame per developer
   spec ("these images cover both the panel and the actual purchase
   item"), so this element is just a positioning context for that image
   plus the text/button overlaid on top of it. ── */
.shop-card{
  position:relative;
  width:100%;max-width:280px;aspect-ratio:1;
  border-radius:16px;overflow:hidden;cursor:pointer;
  box-shadow:0 4px 14px rgba(0,0,0,.5),0 0 16px rgba(214,178,74,.28);
  transition:transform .15s,box-shadow .15s,filter .15s;
  /* Same native-image-drag fix as .chp-card/.lvl-card just below --
     see that comment for the full explanation. */
  -webkit-user-drag:none;user-select:none;
}
.shop-card img{-webkit-user-drag:none;user-select:none;}
.shop-card:hover{transform:translateY(-2px);box-shadow:0 8px 20px rgba(0,0,0,.55);filter:brightness(1.06);}
.shop-card:active{transform:scale(.985);}
.shop-card-art{
  position:absolute;inset:0;z-index:0;
  width:100%;height:100%;object-fit:cover;
}
/* "i" info button -- same small circular treatment as the old
   .shop-info-btn, just renamed/rescoped for the new card. */
.shop-card-info-btn{
  position:absolute;top:8px;right:8px;z-index:3;
  width:20px;height:20px;border-radius:50%;
  background:rgba(0,0,0,.55);border:1px solid var(--pch-gold);
  color:var(--pch-gold-bright);font-family:'Georgia',serif;font-size:12px;font-style:italic;font-weight:700;
  display:flex;align-items:center;justify-content:center;cursor:pointer;padding:0;line-height:1;
}
.shop-card-info-btn:hover{filter:brightness(1.3);}
.shop-card-info-btn:active{transform:scale(.9);}
/* "Best Value" ribbon -- real horizontal banner artwork (1997x820, ratio
   ~2.44). Layered via position:absolute (relative to
   .shop-card-featured-wrap) rather than sitting in normal document flow
   above the card, per developer feedback -- flow positioning added the
   banner's own height to the wrapper's box, which pushed the Adventurers
   Tin card down below its siblings' top edge and threw the whole row out
   of alignment. Positioned to mostly float above the card with a small
   ~15px dip onto its very top edge (still safely inside the card's own
   ~9%-of-height top padding before the "Adventurers Tin" name text
   starts -- see .shop-card-overlay -- so it still doesn't cover any
   text, just layers over a sliver of empty margin) instead of floating
   entirely clear of it, which would need far more headroom above the
   row than is otherwise useful to reserve. */
.shop-card-value-banner{
  position:absolute;top:-44px;left:50%;transform:translateX(-50%);
  width:62%;height:auto;z-index:4;
  pointer-events:none;
  filter:drop-shadow(0 3px 6px rgba(0,0,0,.5));
}
/* Overlay -- name pinned to the top edge, price pinned to the bottom
   edge, with a flexible spacer between them so the actual tin artwork in
   the middle of the panel (which every one of these panels leaves clear
   per the asset design) never gets covered by text regardless of exact
   card size. Description text removed per developer request -- that
   detail now lives only in the "i" info popup (openShopInfo()). */
.shop-card-overlay{
  position:absolute;inset:0;z-index:2;
  display:flex;flex-direction:column;align-items:center;
  padding:9% 8% 7%;text-align:center;
  pointer-events:none;
}
.shop-card-overlay>*{pointer-events:auto;}
.shop-card-spacer{flex:1;}
.shop-card-name{
  font-family:'Cinzel Decorative',serif;font-size:15px;font-weight:700;letter-spacing:.4px;
  line-height:1.15;
}
/* Point 5: price sits at the very bottom of the panel, on the same
   gold_button.png plate as the Collection Rewards Claim button and the
   Army Builder Save button -- aspect-ratio-locked to its native 1931x656
   so the art is never stretched. */
.shop-card-buy-btn,.shop-iap-buy-btn{
  width:60%;min-width:96px;aspect-ratio:1931/656;
  background:url('assets/img/ui/gold_button.webp') center/100% 100% no-repeat;
  border:none;border-radius:0;padding:0;
  display:flex;align-items:center;justify-content:center;gap:5px;
  cursor:pointer;transition:filter .12s,transform .08s;
  color:#3a2408;font-family:'Cinzel',serif;font-size:12px;font-weight:800;
  text-shadow:0 1px 0 rgba(255,255,255,.4);
}
.shop-card-buy-btn:hover:not(:disabled),.shop-iap-buy-btn:hover:not(:disabled){filter:brightness(1.1);}
.shop-card-buy-btn:active:not(:disabled),.shop-iap-buy-btn:active:not(:disabled){transform:scale(.95);}
.shop-card-buy-btn:disabled,.shop-iap-buy-btn:disabled{opacity:.5;cursor:default;filter:grayscale(.5) brightness(.85);}
.shop-card-buy-btn.unaffordable{opacity:.65;filter:grayscale(.35) brightness(.9);}
.shop-card-buy-icon{width:15px;height:15px;object-fit:contain;}
/* IAP product cards (#iap-cards only) -- REBUILT a second time. The
   first two attempts (separate name-bar above the image, inside a box
   with an ambiguous WebKit-derived height) caused invisible text and
   cropped art. This version abandons that structure entirely and
   reuses the EXACT pattern the regular Coins-shop cards already use
   successfully: .shop-card-art fills the whole card, .shop-card-overlay
   sits on top of it (position:absolute;inset:0, proven padding of
   9%/8%/7% already gives the bottom button real clearance from the
   edge -- no more clipping). The only IAP-specific addition is
   .iap-product-name, a small solid-background chip near the top of
   that same overlay (these images are busier than the Coins-shop ones,
   so plain text-shadow alone wasn't enough) -- explicit solid color,
   NOT gold-shine-text, which was confirmed (via
   -webkit-text-fill-color:transparent in its own rule) to render fully
   invisible in any context, regardless of any competing color rule.
   .shop-card keeps its normal, unmodified aspect-ratio:1/width rules --
   same as every other card in the game, no special-casing needed. */
.iap-product-name{
  flex:0 0 auto;
  margin-bottom:4%;
  padding:5px 10px;
  text-align:center;
  background:rgba(10,8,4,.85);
  border:1px solid rgba(214,178,74,.55);
  border-radius:5px;
  font-family:'Cinzel Decorative',serif;font-size:12px;font-weight:700;
  letter-spacing:.3px;line-height:1.2;
  color:#ffd77a;
}

/* ── Legacy .shop-item rules below are NOT used by the Shop screen
   anymore (it now renders .shop-card instead -- see above) but are kept
   as-is because Collection Rewards' reward rows (.collection-reward-row)
   still share this exact class/chrome (border-image, hover sparkle,
   .shop-item-img/-info/-name/-desc, .shop-buy-btn) -- see
   renderCollectionRewards() in game.js. Do not remove without also
   reworking Collection Rewards. ── */
.shop-item{
  display:flex;align-items:center;gap:12px;padding:10px 18px;
  border-style:solid;border-width:24px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:24px;box-sizing:border-box;
  position:relative;overflow:hidden;transition:box-shadow .25s;
}
/* Hover sparkle -- a soft twinkling dust of small gold points (::before)
   plus a diagonal light sweep (::after) that runs across the card, both
   only active on hover, to make each item feel a little more "premium"
   and inviting to tap. Pure CSS, no extra art needed. */
.shop-item::before{
  content:'';position:absolute;inset:0;pointer-events:none;opacity:0;z-index:1;
  background-image:
    radial-gradient(circle,#fff8d0 0%,transparent 65%),
    radial-gradient(circle,#fff8d0 0%,transparent 65%),
    radial-gradient(circle,#fff4c0 0%,transparent 65%),
    radial-gradient(circle,#fff8d0 0%,transparent 65%);
  background-size:5px 5px,4px 4px,3px 3px,4px 4px;
  background-position:12% 25%,88% 20%,25% 78%,72% 70%;
  background-repeat:no-repeat;
  transition:opacity .25s;
}
.shop-item::after{
  content:'';position:absolute;top:0;left:-160%;width:55%;height:100%;z-index:1;
  pointer-events:none;
  background:linear-gradient(115deg,transparent 0%,rgba(255,255,255,.28) 45%,rgba(255,236,170,.55) 50%,rgba(255,255,255,.28) 55%,transparent 100%);
  transform:skewX(-18deg);transition:left 1.8s ease;
}
.shop-item:hover{box-shadow:0 0 18px rgba(255,210,110,.45),0 4px 14px rgba(0,0,0,.4);}
.shop-item:hover::before{opacity:1;animation:shopSparkleTwinkle 2.4s ease-in-out infinite;}
.shop-item:hover::after{left:160%;}
@keyframes shopSparkleTwinkle{
  0%,100%{opacity:.35;}
  50%{opacity:1;}
}
/* "i" info button -- top-right corner of each shop item card, opens
   #shop-info-modal with purchase-decision-relevant info (see
   SHOP_ITEM_INFO in game.js / openShopInfo()). Kept small and out of the
   way so it doesn't compete with the buy button for attention. */
/* .shop-info-btn intentionally removed -- was only ever used by the old
   Shop item markup, which now uses .shop-card-info-btn instead (see
   above). Not shared with Collection Rewards (unlike .shop-item and its
   other descendant classes below), so safe to drop outright. */
.shop-item-booster{border-image-source:url('assets/img/ui/shop_banner_red.webp');}
.shop-item-collector{border-image-source:url('assets/img/ui/shop_banner_blue.webp');}
.shop-item-gempouch{border-image-source:url('assets/img/ui/shop_banner_green.webp');}
.shop-item-gemchest{border-image-source:url('assets/img/ui/shop_banner_purple.webp');}
.shop-item-img{width:88px;height:88px;object-fit:contain;flex-shrink:0;position:relative;z-index:2;}
.shop-item-img-currency{padding:10px;box-sizing:border-box;}
.shop-item-info{flex:1;display:flex;flex-direction:column;gap:3px;min-width:0;position:relative;z-index:2;}
.shop-item-name{
  font-family:'Cinzel Decorative',serif;font-size:13px;letter-spacing:.5px;
  color:var(--pch-gold-bright);text-shadow:0 1px 2px rgba(0,0,0,.6);
}
.shop-item-desc{font-family:'Cinzel',serif;font-size:9px;color:#d8c8a8;line-height:1.3;}
.shop-buy-btn{
  display:flex;align-items:center;gap:5px;flex-shrink:0;cursor:pointer;
  background:linear-gradient(180deg,var(--pch-gold),#8a6820);border:1px solid var(--pch-bronze);
  border-radius:6px;padding:7px 10px;font-family:'Cinzel',serif;font-size:11px;font-weight:700;
  color:#241a0e;transition:filter .12s,transform .08s;
  position:relative;z-index:2;
}
.shop-buy-btn:hover:not(:disabled){filter:brightness(1.1);}
.shop-buy-btn:active:not(:disabled){transform:scale(.95);}
.shop-buy-btn:disabled{opacity:.45;cursor:default;filter:grayscale(.3);}
/* Dimmed-but-still-tappable when unaffordable -- see renderShop(), which
   deliberately leaves this button enabled so a tap still fires
   buyShopItem() and shows the "not enough Coins" prompt. */
.shop-buy-btn.unaffordable{opacity:.5;filter:grayscale(.25);}
.shop-buy-icon{width:14px;height:14px;object-fit:contain;}

/* ══ SHOP REWARD MODAL ═════════════════════════════════════════ */
#shop-reward-modal{
  display:none;position:fixed;inset:0;z-index:50;
  align-items:center;justify-content:center;
  background:rgba(6,4,2,.8);
}
#shop-reward-modal.show{display:flex;}
#shop-reward-card{
  display:flex;flex-direction:column;align-items:center;gap:14px;
  width:min(90vw,380px);max-height:80vh;overflow-y:auto;touch-action:pan-y;
  padding:20px;border-radius:10px;
  /* Same bg_parchment.png texture as the Victory/Defeat #ov-card, per
     developer request -- a purchase reveal now shares that parchment
     identity instead of a flat CSS gradient or the Shop's own busy
     background art. No dark tint layered on top here (unlike the
     shop_bg.png attempt this replaced) since the parchment is already a
     light, fairly uniform texture -- see the title/gems color note
     below for the readability adjustment that comes with switching from
     a dark backdrop to a light one. */
  background:url('assets/img/ui/bg_parchment.webp') center/cover no-repeat;
  border:1px solid var(--pch-gold);box-shadow:0 8px 24px rgba(0,0,0,.6);
}
#shop-reward-title{
  /* Was --pch-gold-bright, tuned for the old dark backdrop -- barely
     readable against light parchment, so switched to the same dark ink
     tone used for text printed directly on light plate art elsewhere
     (.shop-card-buy-btn, and #ov-sub on the Victory/Defeat card, which
     hit this identical problem when that card got the parchment
     treatment first). */
  font-family:'Cinzel Decorative',serif;font-size:16px;letter-spacing:1.5px;color:#3a2408;
}
/* Compensates for smaller screens where #shop-reward-close-btn
   ("Continue", at the bottom of the card) can require scrolling past
   the token grid to reach -- see the developer's attached screenshot.
   #shop-reward-card is ITSELF the scroll container (overflow-y:auto
   above), so a plain position:absolute X here would scroll away with
   everything else instead of staying reachable; position:sticky keeps
   it pinned to the card's own top-right corner as its content scrolls,
   without needing to restructure the modal into fixed header/scrolling
   body sections just for this button. align-self:flex-end places it on
   the right within the column-flex card; the negative margin cancels
   the card's own 14px flex gap so it doesn't push #shop-reward-title
   down an extra row's worth of space.
   z-index:3 -- one above the token grid's own cell stacking (which
   goes up to 2 for duplicate badges) so it's never covered. */
#shop-reward-close-x{
  position:sticky;top:0;align-self:flex-end;z-index:3;margin-bottom:-14px;
  width:26px;height:26px;border-radius:50%;flex-shrink:0;
  background:rgba(0,0,0,.5);border:1px solid var(--pch-gold);
  color:var(--pch-gold-bright);font-size:17px;line-height:1;font-family:'Cinzel',serif;
  display:flex;align-items:center;justify-content:center;
  cursor:pointer;
}
#shop-reward-close-x:hover{background:rgba(0,0,0,.72);}
#shop-reward-close-x:active{transform:scale(.9);}
#shop-reward-grid{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(70px,1fr));gap:8px;width:100%;
}
/* Fixed 3-column layout for the 6-token booster reward (3 Common / 2
   Uncommon / 1 Rare, ordered so the Rare lands centered in row 2). */
#shop-reward-grid.shop-reward-grid-compact{
  grid-template-columns:repeat(3,minmax(0,88px));justify-content:center;gap:10px;
}
.shop-reward-token{
  position:relative;display:flex;align-items:center;justify-content:center;
  background:rgba(0,0,0,.25);border-radius:6px;padding:4px;
  cursor:pointer;transition:transform .12s,background .12s;
}
.shop-reward-token:hover{background:rgba(255,255,255,.08);transform:translateY(-2px);}
.shop-reward-token:active{transform:translateY(0) scale(.95);}
.shop-reward-token-dup{background:rgba(120,60,160,.28);}
/* Staggered reveal (see showShopReward() in game.js) -- cells start
   hidden/shrunk via -pending, then JS swaps in -pop on a per-cell delay
   instead of all 6 appearing in one synchronous pass. The 0% keyframe
   below matches -pending's values exactly so the class swap has nothing
   to visually jump from. */
.shop-reward-token-pending{opacity:0;transform:scale(.3);}
.shop-reward-token-pop{animation:shopRewardPopIn .38s cubic-bezier(.2,1.4,.4,1) forwards;}
@keyframes shopRewardPopIn{
  0%{opacity:0;transform:scale(.3);}
  60%{opacity:1;transform:scale(1.12);}
  100%{opacity:1;transform:scale(1);}
}
.shop-reward-dup-badge{
  position:absolute;bottom:2px;right:2px;z-index:2;
  background:rgba(0,0,0,.7);border:1px solid var(--pch-gold);border-radius:4px;
  color:var(--pch-gold-bright);font-family:'Cinzel',serif;font-size:9px;font-weight:700;
  padding:1px 4px;
}
.shop-reward-token-img{width:100%;aspect-ratio:1/1;object-fit:contain;}
#shop-reward-gems{
  display:none;align-items:center;gap:6px;
  font-family:'Cinzel',serif;font-size:13px;color:#3a2408; /* same light-backdrop->parchment readability fix as #shop-reward-title */
}
#shop-reward-gem-icon{width:20px;height:20px;object-fit:contain;}
#shop-reward-close-btn{
  cursor:pointer;padding:8px 22px;border-radius:6px;border:1px solid var(--pch-bronze);
  background:linear-gradient(180deg,var(--pch-gold),#8a6820);
  font-family:'Cinzel',serif;font-size:12px;font-weight:700;color:#241a0e;
}
#title-footer-text{
  position:relative;z-index:1;padding-bottom:16px;
  color:#8a7458;font-size:9px;letter-spacing:1.5px;text-transform:uppercase;font-style:italic;
}

/* ══ BRAWLER HUB SCREEN ═══════════════════════════════════════ */
#screen-brawler{
  display:none;flex-direction:column;flex:1;position:relative;
  height:100%;overflow:hidden;
}
#brawler-info-modal{
  display:none;position:absolute;inset:0;z-index:20;
  background:rgba(20,12,4,.92);align-items:center;justify-content:center;
}
#brawler-info-modal-card{
  max-width:320px;width:88%;max-height:80vh;padding:20px;box-sizing:border-box;
  background:rgba(30,20,10,.95);border:2px solid var(--pch-gold-bright);
  border-radius:8px;display:flex;flex-direction:column;gap:12px;align-items:center;
  text-align:center;box-shadow:var(--pch-shadow-soft);
}
#brawler-info-modal-title{font-family:'Cinzel Decorative',serif;font-size:16px;flex-shrink:0;}
#brawler-info-modal-text{
  font-family:'Cinzel',serif;font-size:12px;color:#e8ddc8;line-height:1.6;text-align:left;
  overflow-y:auto;touch-action:pan-y;scrollbar-width:none;flex:1;min-height:0;
}
#brawler-info-modal-text::-webkit-scrollbar{display:none;}
#brawler-info-modal-text b{color:var(--pch-gold-bright);}
#brawler-info-modal-card>.ov-btn{flex-shrink:0;}
#brawler-bg{
  position:absolute;inset:0;z-index:0;
  /* New dedicated Brawler background -- save it here (same cover-fit +
     dark-gradient-overlay treatment as the other redesigned screens). */
  background:url('assets/img/backgrounds/brawler_bg.webp') center/cover no-repeat;
}
#brawler-bg::after{
  content:'';position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(30,18,8,.18),rgba(20,12,4,.38));
}
#brawler-content{
  position:relative;z-index:1;display:flex;flex-direction:column;flex:1;
  align-items:center;padding:0 16px 20px;
  /* Scroll fix -- same root cause/fix as #title-content above: this had
     no scroll mechanism of its own and relied on the old page-level
     body scroll (removed for the topbar cut-off fix), leaving any
     overflow unreachable. #screen-brawler itself stays a fixed,
     non-scrolling box so #brawler-bg keeps covering the full screen.
     flex:1 must stay here (not just overflow-y:auto) so this box is
     actually height-bounded by its flex parent -- without it the box
     would just grow to fit its content instead of clipping/scrolling. */
  overflow-y:auto;touch-action:pan-y;
}
/* Back button floats independently over the background, above the
   header banner -- same treatment as Chapter/Level Select. */
#brawler-header-controls{
  position:relative;flex-shrink:0;align-self:flex-start;
  padding:10px 0 0 0;
}
#brawler-back-btn{
  background:none;border-style:solid;border-width:14px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:14px;
  border-image-source:url('assets/img/ui/shop_banner_red.webp');
  color:var(--pch-gold-bright);padding:5px 12px;
  font-size:9px;font-family:'Cinzel',serif;letter-spacing:.5px;cursor:pointer;
}
#brawler-back-btn:hover{filter:brightness(1.15);}
/* ── Header banner -- now the dedicated ornate blue shield/wreath
   banner art (see brawler_header_banner.png) instead of the shared
   header_banner.png. It's a one-off decorative graphic (shield emblem
   overlapping the top edge, a small pendant tab hanging off the bottom)
   rather than a repeatable frame, so it's rendered as a background-image
   at its native aspect ratio instead of a 9-slice border-image. Title
   sits in the safe band below the shield and above the pendant tab. ── */
#brawler-banner-wrap{
  position:relative;flex-shrink:0;width:100%;max-width:420px;
  margin:2px -6px 0;
  aspect-ratio:1425/696;
  background:url('assets/img/ui/brawler_header_banner.webp') center top/contain no-repeat;
  filter:drop-shadow(0 4px 10px rgba(0,0,0,.5));
}
#brawler-banner-text{
  position:absolute;left:0;right:0;top:44%;
  display:flex;flex-direction:column;
  align-items:center;justify-content:center;pointer-events:none;
}
#brawler-title{
  color:var(--pch-gold-bright);font-size:20px;font-weight:700;letter-spacing:4px;
  font-family:'Cinzel Decorative',serif;
  text-shadow:0 1px 3px rgba(0,0,0,.75);
}
#brawler-sub{
  margin-top:4px;text-align:center;
  color:#d8c8a8;font-size:9px;letter-spacing:1.5px;text-transform:uppercase;font-style:italic;
  text-shadow:0 1px 2px rgba(0,0,0,.6);
}
/* ── Rank/win-tally card -- now the same Shop/Army Builder banner
   border-image treatment (see .army-slot-card/.shop-item) instead of a
   flat gradient box. ── */
#brawler-rank-card{
  margin-top:18px;width:100%;max-width:340px;box-sizing:border-box;
  display:flex;align-items:center;gap:14px;padding:16px 18px;
  border-style:solid;border-width:20px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:20px;
  border-image-source:url('assets/img/ui/shop_banner_blue.webp');
  box-shadow:var(--pch-shadow-soft);
  position:relative;cursor:pointer;
}
#brawler-rank-info-btn{
  position:absolute;top:6px;right:6px;width:22px;height:22px;border-radius:50%;
  background:rgba(0,0,0,.5);border:1px solid var(--pch-gold-bright);color:var(--pch-gold-bright);
  font-family:'Cinzel',serif;font-weight:bold;font-size:13px;line-height:1;cursor:pointer;
  display:flex;align-items:center;justify-content:center;padding:0;
}
#brawler-rank-gem-wrap{flex-shrink:0;width:56px;height:56px;display:flex;align-items:center;justify-content:center;}
#brawler-rank-gem{width:52px;height:52px;object-fit:contain;filter:drop-shadow(0 0 10px rgba(255,220,140,.4));}
#brawler-rank-info{display:flex;flex-direction:column;gap:3px;}
#brawler-rank-label{font-family:'Cinzel Decorative',serif;font-size:17px;color:var(--pch-gold-bright);letter-spacing:1px;text-shadow:0 1px 3px rgba(0,0,0,.6);}
#brawler-rank-progress{font-family:'Cinzel',serif;font-size:10px;color:#e8ddc8;}
#brawler-record{font-family:'Cinzel',serif;font-size:9px;color:#a8987c;letter-spacing:.5px;}
#brawler-rank-hint{font-family:'Cinzel',serif;font-size:9px;color:var(--pch-gold-bright);letter-spacing:.5px;margin-top:2px;font-style:italic;}
#brawler-find-wrap{
  margin-top:26px;display:flex;flex-direction:column;align-items:center;gap:10px;width:100%;max-width:340px;
}
/* ── Find Opponent -- now the dedicated red spear-tipped banner art
   (see brawler_find_opponent_banner.png) instead of btn_primary.png,
   rendered at its native aspect ratio. ── */
.brawler-find-btn-wrap{
  position:relative;width:100%;max-width:260px;aspect-ratio:1191/256;cursor:pointer;
  background:url('assets/img/ui/brawler_find_opponent_banner.webp') center/contain no-repeat;
  filter:drop-shadow(0 3px 6px rgba(0,0,0,.4));
  transition:filter .12s,transform .08s;
}
.brawler-find-btn-wrap:hover{filter:drop-shadow(0 3px 6px rgba(0,0,0,.4)) brightness(1.12);}
.brawler-find-btn-wrap:active{transform:scale(.96);}
.brawler-find-btn-label{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-family:'Cinzel',serif;font-size:13px;font-weight:700;letter-spacing:1px;color:#f5e6bc;
  text-shadow:0 1px 3px rgba(0,0,0,.8);
}
#brawler-find-hint{
  color:var(--pch-ink-soft);font-size:9px;letter-spacing:.3px;font-style:italic;text-align:center;
  max-width:280px;line-height:1.5;text-shadow:0 1px 2px rgba(255,255,255,.6);
}
/* Goblin Bard mascot -- tap for witty commentary about Brawler mode
   (developer request, Sep 2026; repositioned to sit right off the rank
   card per follow-up request, rather than pinned to the far corner of
   the whole screen). Anchored to #brawler-rank-card itself (already
   position:relative for the "i" info button) so he sits consistently
   right next to the banner regardless of screen size, peeking out past
   its bottom-right corner. His own gb_attack_2.png battle sprite frame,
   reused as-is rather than dedicated mascot art. onclick stops
   propagation so tapping him doesn't also trigger the card's own
   Season Rewards click-through.  */
/* Oct 2026: nudged further right, larger, with a gold glow and a short
   wriggle every few seconds so players notice he's tappable. The wriggle
   uses the standalone `rotate` property (not transform), so it never
   fights the :active press scale below. */
#brawler-mascot-btn{
  position:absolute;right:-16px;bottom:-34px;width:74px;height:auto;z-index:5;
  cursor:pointer;
  filter:drop-shadow(0 0 6px rgba(255,200,60,.85)) drop-shadow(0 0 14px rgba(255,180,40,.45)) drop-shadow(0 4px 8px rgba(0,0,0,.5));
  transition:transform .15s ease;transform-origin:50% 85%;
  animation:brawlerMascotWriggle 5s ease-in-out 1.5s infinite;
}
#brawler-mascot-btn:active{transform:scale(0.92);}
@keyframes brawlerMascotWriggle{
  0%,76%,100%{rotate:0deg;}
  80%{rotate:-9deg;}
  84%{rotate:8deg;}
  88%{rotate:-6deg;}
  92%{rotate:4deg;}
  96%{rotate:0deg;}
}
@media (prefers-reduced-motion:reduce){#brawler-mascot-btn{animation:none;}}
#brawler-mascot-bubble{
  position:absolute;right:-8px;bottom:46px;max-width:200px;z-index:6;
  background:rgba(30,20,10,.95);border:2px solid var(--pch-gold-bright);border-radius:10px;
  padding:10px 12px;box-shadow:var(--pch-shadow-soft);
}
#brawler-mascot-bubble::after{
  content:'';position:absolute;right:24px;bottom:-9px;
  border-width:9px 7px 0 7px;border-style:solid;
  border-color:var(--pch-gold-bright) transparent transparent transparent;
}
#brawler-mascot-bubble-text{
  font-family:'Cinzel',serif;font-size:11px;color:#e8ddc8;line-height:1.5;text-align:left;
}

/* ══ VS SCREEN ═════════════════════════════════════════════════ */
#screen-vs{
  display:none;flex-direction:column;flex:1;position:relative;
  height:100%;overflow:hidden;background:#0a0704;
}
#vs-bg{
  position:absolute;inset:0;z-index:0;
  background:url('assets/img/backgrounds/brawler_bg.webp') center/cover no-repeat;
}
#vs-bg::after{
  content:'';position:absolute;inset:0;
  background:radial-gradient(ellipse at 50% 40%,rgba(36,26,18,.55),rgba(10,7,4,.85) 75%);
}
#vs-content{
  position:relative;z-index:1;flex:1;display:flex;align-items:center;justify-content:center;
  padding:24px 10px;
}
.vs-side{display:flex;flex-direction:column;align-items:center;gap:6px;width:38%;}
.vs-avatar-wrap{
  width:100%;aspect-ratio:1/1;max-width:150px;border-radius:10px;overflow:hidden;
  border:2px solid var(--pch-bronze);box-shadow:0 6px 18px rgba(0,0,0,.6);
  background:#1a1209;
}
.vs-avatar-img{width:100%;height:100%;object-fit:cover;}
.vs-side-player .vs-avatar-wrap{border-color:#5a9040;box-shadow:0 0 18px rgba(90,180,60,.35),0 6px 18px rgba(0,0,0,.6);}
.vs-side-opp .vs-avatar-wrap{border-color:#c04030;box-shadow:0 0 18px rgba(220,60,40,.35),0 6px 18px rgba(0,0,0,.6);}
.vs-name{
  font-family:'Cinzel Decorative',serif;font-size:14px;color:var(--pch-gold-bright);
  text-align:center;text-shadow:0 2px 4px rgba(0,0,0,.7);margin-top:4px;
}
.vs-name-sub{font-family:'Cinzel',serif;font-size:9px;letter-spacing:1px;text-transform:uppercase;color:#a8987c;}
.vs-rank-chip{
  display:flex;align-items:center;gap:5px;margin-top:4px;padding:3px 8px;border-radius:12px;
  background:rgba(0,0,0,.4);border:1px solid var(--pch-bronze);
}
.vs-rank-gem{width:16px;height:16px;object-fit:contain;}
.vs-rank-chip span{font-family:'Cinzel',serif;font-size:9px;color:#e8ddc8;letter-spacing:.5px;}
#vs-center{display:flex;align-items:center;justify-content:center;width:18%;}
#vs-text{
  font-family:'Cinzel Decorative',serif;font-size:34px;color:#ff9040;
  text-shadow:0 0 20px rgba(255,120,40,.8),0 0 40px rgba(255,80,20,.5),0 2px 4px rgba(0,0,0,.8);
  transform:skewX(-6deg);
}
#vs-footer{position:relative;z-index:1;display:flex;justify-content:center;padding-bottom:26px;}
.vs-fight-btn-wrap{position:relative;width:min(60vw,220px);cursor:pointer;}
.vs-fight-btn-asset{width:100%;display:block;filter:drop-shadow(0 3px 6px rgba(0,0,0,.4));}
.vs-fight-btn-label{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-family:'Cinzel',serif;font-size:13px;font-weight:700;letter-spacing:1px;color:var(--pch-gold-bright);
}
#chp-bg{
  position:absolute;inset:0;z-index:0;
  /* New dedicated Chapter Select background -- save it here (same
     cover-fit + dark-gradient-overlay treatment as Shop/Army Builder's
     own backgrounds). */
  background:url('assets/img/backgrounds/chapter_select_bg.webp') center/cover no-repeat;
}
#chp-bg::after{
  content:'';position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(30,18,8,.18),rgba(20,12,4,.38));
}
#chp-content{
  position:relative;z-index:1;
  /* min-height:0 -- without it, this flex:1 child's default
     min-height:auto lets a deep descendant's intrinsic size (see
     .chp-card's aspect-ratio, further down) inflate it past whatever
     space #screen-chapter's height:100% actually allotted, and since
     #screen-chapter clips with overflow:hidden the excess (cards'
     Enter buttons included) just gets cut off instead of the row
     shrinking to fit. Classic nested-flexbox shrink bug -- needs the
     same override on #chp-cards below too, not just here. */
  display:flex;flex-direction:column;flex:1;min-height:0;
  padding:0 0 12px;
}
/* Back button floats independently over the background, positioned to
   sit alongside the banner rather than stacked above it (the previous
   rule here said as much in its own comment but actually left
   position:relative in place, which put it back in normal flow above
   the banner -- this was a real bug, not just cosmetic). */
#chp-header-controls{
  position:absolute;top:14px;left:14px;z-index:5;
}
/* ── Banner -- now the real title_banner.png artwork (2172x724, a
   clean 3:1 ratio) instead of the old shop_banner_purple.png border-
   image guess. Sized off that real aspect ratio via aspect-ratio
   rather than 9-slicing, since the asset is one cohesive graphic (a
   pointed ribbon + a sword ornament overlapping its top edge) that
   doesn't tile or stretch cleanly -- scaling it as a whole keeps every
   part of it proportional. Narrower and taller than before so it
   fully encases two lines of text without spilling out the top/bottom,
   and sits centered near the back button instead of spanning the
   full width below it. ── */
#chp-banner-wrap{
  position:relative;flex-shrink:0;
  width:min(60%,600px);aspect-ratio:3/1;
  margin:6px auto 0;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:0 15%;
  background-image:url('assets/img/ui/title_banner.webp');
  background-size:100% 100%;background-repeat:no-repeat;background-position:center;
  filter:drop-shadow(0 4px 10px rgba(0,0,0,.45));
}
#chp-banner-text{
  position:relative;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  pointer-events:none;
}
#chp-title{
  color:var(--pch-gold-bright);font-size:23px;font-weight:700;letter-spacing:2.5px;
  font-family:'Cinzel Decorative',serif;
  text-shadow:0 1px 3px rgba(0,0,0,.75);
  line-height:1;white-space:nowrap;
}
#chp-sub{
  margin-top:5px;text-align:center;
  color:#d8c8a8;font-size:11px;letter-spacing:1.6px;
  text-transform:uppercase;font-style:italic;
  text-shadow:0 1px 2px rgba(0,0,0,.6);
  white-space:nowrap;
}
#mode-toggle-wrap-chp{
  display:flex;align-items:center;justify-content:center;gap:8px;
  padding:6px 14px 2px;
}
#mode-label-chp{color:var(--pch-ink-soft);font-size:9px;letter-spacing:1px;font-family:'Cinzel',serif;}
#mode-btn-chp{
  background:linear-gradient(180deg,var(--pch-walnut),var(--pch-walnut-dark));
  color:var(--pch-gold-bright);border:1px solid var(--pch-bronze);border-radius:3px;
  padding:3px 10px;font-size:9px;font-family:'Cinzel',serif;letter-spacing:.5px;cursor:pointer;
  box-shadow:0 1px 0 rgba(255,255,255,.1) inset,var(--pch-shadow-soft);
}
#mode-btn-chp:hover{filter:brightness(1.15);}
#mode-btn-chp.dev{color:#d07070;}
#mode-btn-chp.easy{color:#70c070;}
/* Secret Chapter 6 -- only ever shown via JS when Dev/Easy mode is active
   (see showChapterSelect). Deliberately styled apart from the normal
   parchment/bronze chapter cards -- deep crimson/black, matching
   Ashigami's own solemn commentary modal. */
#secret-ch6-btn{
  display:flex;margin:2px auto 0;align-items:center;justify-content:center;gap:6px;
  background:linear-gradient(180deg,#2a0a0a,#150606);
  color:#c07070;border:1px solid #601818;border-radius:3px;
  padding:4px 12px;font-size:9px;font-family:'Cinzel',serif;letter-spacing:1px;cursor:pointer;
  box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 2px 6px rgba(0,0,0,.5);
}
#secret-ch6-btn:hover{filter:brightness(1.2);}
/* Secret Chapter 7 -- Suzanne's Coven. Same treatment as Secret Chapter 6's
   button, deep violet/black instead of crimson to match a witch's theme
   rather than Ashigami's. */
#secret-ch7-btn{
  display:flex;margin:2px auto 0;align-items:center;justify-content:center;gap:6px;
  background:linear-gradient(180deg,#1c0a2a,#0c0615);
  color:#a070c0;border:1px solid #401860;border-radius:3px;
  padding:4px 12px;font-size:9px;font-family:'Cinzel',serif;letter-spacing:1px;cursor:pointer;
  box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 2px 6px rgba(0,0,0,.5);
}
#secret-ch7-btn:hover{filter:brightness(1.2);}
/* Chapter Select back button -- same shop_banner_red treatment as the
   Shop/Army Builder back buttons, now floating over the background above
   the header banner instead of embedded inside it. */
#chp-back-btn{
  background:none;border-style:solid;border-width:14px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:14px;
  border-image-source:url('assets/img/ui/shop_banner_red.webp');
  color:var(--pch-gold-bright);padding:5px 12px;
  font-size:9px;font-family:'Cinzel',serif;letter-spacing:.5px;cursor:pointer;
}
#chp-back-btn:hover{filter:brightness(1.15);}
/* ══ CHAPTER CARDS — identical skeleton to level cards, reuses lvl- classes ══
   The card frame itself now reuses the Shop/Army Builder banner
   border-image technique (see .shop-item/.army-slot-card) instead of the
   old mission_card.png + color-wash overlay. Portrait, name, flavor text,
   tags, divider and enemy row are all untouched in structure -- only the
   outer framing changed, plus the text-contrast fixes below (the old
   text colors were tuned for a light parchment card and were nearly
   unreadable against these dark banner interiors). Colors cycle
   red/blue/green/purple across the 5 chapters (only 4 banner colors
   exist, so Chapter 5 reuses red). */
#chp-cards{
  flex:1;min-height:0;display:flex;flex-direction:column;gap:10px;
  padding:22px 10px 6px;overflow-y:auto;touch-action:pan-y;
}
.chp-card{
  position:relative;
  width:100%;height:168px;box-sizing:border-box;
  cursor:pointer;
  border-style:solid;border-width:20px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:20px;
  box-shadow:0 4px 14px rgba(0,0,0,.5),0 0 16px rgba(214,178,74,.28);
  transition:transform .15s,box-shadow .15s,filter .15s;
}
/* Without this, mousedown+move starting on any of the card's own <img>
   elements (portrait/divider/corner-ornament/Enter-button icon) starts
   the browser's OWN native "ghost image" drag instead of the mouse
   drag-to-scroll added for _initDragScroll() in game.js -- exact same
   root cause already diagnosed and fixed once for .roster-card/
   .rc-portrait elsewhere in this file (see that comment for the fuller
   explanation). -webkit-user-drag doesn't reliably inherit from the card
   down to its own <img> children in every browser, so it's set on both
   the card AND every image inside it here, not just the card. */
.chp-card,.lvl-card{-webkit-user-drag:none;user-select:none;}
.chp-card img,.lvl-card img{-webkit-user-drag:none;user-select:none;}
.chp-card:hover{transform:translateY(-2px);box-shadow:0 8px 20px rgba(0,0,0,.55);filter:brightness(1.06);}
.chp-card:active{transform:scale(.985);}
.chp-banner-red{border-image-source:url('assets/img/ui/shop_banner_red.webp');}
.chp-banner-blue{border-image-source:url('assets/img/ui/shop_banner_blue.webp');}
.chp-banner-green{border-image-source:url('assets/img/ui/shop_banner_green.webp');}
.chp-banner-purple{border-image-source:url('assets/img/ui/shop_banner_purple.webp');}
/* overflow:hidden lives here now, not on .chp-card itself -- this is
   what lets .chp-rewards-teaser (a sibling of this element, not a
   descendant) poke past the card's frame while everything actually
   inside the grid (text, portrait, buttons) stays safely clipped to
   the card, same protection as before. */
.chp-card-inner{
  position:absolute;inset:0;overflow:hidden;
  display:flex;flex-direction:row;align-items:stretch;
  z-index:1;
}
/* Corner ornaments were sized for the old mission_card.png art and clash
   with the new banner frame -- hidden here only (Level Select's own
   .lvl-card corners, a different screen, are untouched). */
.chp-card .lvl-corner{display:none;}
/* Chapter rewards teaser -- sits below the Enter button (see
   .lvl-btn-col's justify-content:space-between), clickable to open the
   rewards info popup (showChapterRewardsInfo()). Shared class -- used by
   Chapters 3, 4, and 5, each with their own image (ch3/4/5_rewards_teaser.png).
   These chapters are slated to become purchase-only later -- the image is
   meant to work as a selling point ahead of that, so it's sized to
   actually be noticed rather than tucked away as a tiny icon. */
.chp-rewards-teaser{
  width:100%;max-height:66px;object-fit:contain;
  cursor:pointer;border-radius:6px;
  filter:drop-shadow(0 2px 6px rgba(0,0,0,.5));
  transition:transform .12s,filter .12s;
  animation:ch3TeaserPulse 2s ease-in-out infinite;
}
.chp-rewards-teaser:hover{transform:scale(1.06);filter:drop-shadow(0 2px 6px rgba(0,0,0,.5)) brightness(1.15);}
@keyframes ch3TeaserPulse{0%,100%{filter:drop-shadow(0 2px 6px rgba(0,0,0,.5)) drop-shadow(0 0 0 rgba(255,200,80,0));}50%{filter:drop-shadow(0 2px 6px rgba(0,0,0,.5)) drop-shadow(0 0 8px rgba(255,200,80,.6));}}
/* Chapter 6's rewards row -- unlike Chapters 3/4/5, there's no combined
   marketing teaser image (just a flat one-time 1,000 Coins + 1 Astral Gem
   grant), so this renders the two reward icons directly instead. */
.chp-static-rewards{display:flex;align-items:center;justify-content:center;gap:18px;height:44px;}
.chp-static-reward-item{display:flex;align-items:center;gap:6px;}
.chp-static-reward-item img{width:28px;height:28px;object-fit:contain;filter:drop-shadow(0 1px 3px rgba(0,0,0,.6));}
.chp-static-reward-item span{font-family:'Cinzel',serif;font-size:14px;font-weight:700;color:var(--pch-gold-bright);text-shadow:0 1px 2px rgba(0,0,0,.7);}
/* ── Text contrast fixes -- the original .lvl-name/.lvl-flavor/.lvl-tag/
   .lvl-enemy-* colors were dark ink tones designed to sit on a light
   parchment card background. Against these dark banner interiors they
   were nearly illegible, so everything text-related is brightened here,
   scoped to .chp-card only -- Level Select's own cards (still on the
   original light parchment art) are untouched. Compound selectors here
   (.chp-card .lvl-name etc.) are already higher-specificity than the old
   single-class fire/castle/dungeon/goblin color modifiers, so they win
   automatically without needing !important. ──*/
/* Point 3: center the text column instead of left-aligning it, matching
   the design brief's centered card layout. */
.chp-card .lvl-content-block{text-align:center;}
.chp-card .lvl-tags,.chp-card .lvl-enemy-row{justify-content:center;}
/* Point 4: bolder + dual-tone name -- reuses the -webkit-text-stroke
   technique already established elsewhere in the UI (deploy/HUD titles)
   instead of a background-clip gradient, for visual consistency and to
   avoid cross-browser gradient-text quirks. */
.chp-card .lvl-name{
  color:var(--pch-gold-bright);
  font-weight:800;
  text-shadow:0 2px 0 rgba(40,24,8,.85),0 1px 2px rgba(0,0,0,.5);
}
.chp-card .lvl-flavor{color:#f0e6d0;text-shadow:0 1px 2px rgba(0,0,0,.7);}
.chp-card .lvl-tag:not(.lvl-tag-new-goblin):not(.lvl-tag-tutorial):not(.lvl-tag-locked){
  background:rgba(0,0,0,.4);border-color:rgba(255,215,120,.55);color:#f5e6bc;
}
.chp-card .lvl-divider{filter:brightness(0) invert(1);opacity:.4;}
.chp-card .lvl-enemy-label{color:#f0dca0;text-shadow:0 1px 2px rgba(0,0,0,.6);}
.chp-card .lvl-enemy-names{color:#e8dcc0;text-shadow:0 1px 2px rgba(0,0,0,.6);}
/* ── Chapter numbering flags -- replaces the old flat clip-path ribbon
   with the new pennant artwork (see chapter_flag_red/blue/green/purple.png
   -- flag color matches each chapter's own banner color, Chapter 5 reuses
   red same as its card frame). The Roman numeral is now overlaid near the
   top of the pennant, above its crown emblem, instead of on its own
   colored ribbon shape. ──*/
.chp-card .lvl-badge-wrap{
  width:34px;height:66px;
  top:-20px;left:2px;
  padding-top:11px;
  background-repeat:no-repeat;background-position:center top;background-size:contain;
  filter:drop-shadow(0 3px 5px rgba(0,0,0,.5));
}
.chp-card .lvl-badge-wrap::before{display:none;}
.chp-card .lvl-badge-wrap.lvl-badge-green{background-image:url('assets/img/ui/chapter_flag_red.webp');}
.chp-card .lvl-badge-wrap.lvl-badge-goblin{background-image:url('assets/img/ui/chapter_flag_blue.webp');}
.chp-card .lvl-badge-wrap.lvl-badge-mossy{background-image:url('assets/img/ui/chapter_flag_green.webp');}
.chp-card .lvl-badge-wrap.lvl-badge-carbon{background-image:url('assets/img/ui/chapter_flag_purple.webp');}
.chp-card .lvl-badge-wrap.lvl-badge-steel{background-image:url('assets/img/ui/chapter_flag_red.webp');}
.chp-card .lvl-badge-wrap.lvl-badge-void{background-image:url('assets/img/ui/chapter_flag_purple.webp');}
.chp-card .lvl-badge-roman{
  font-size:11px;text-shadow:0 1px 3px rgba(0,0,0,.95),0 0 4px rgba(0,0,0,.6);
}
/* Levels/Enemies row removed on the chapter cards per developer request
   -- Level Select's own cards (a different screen) keep theirs. */
.chp-card .lvl-enemy-row{display:none;}
/* ── Enter button: btn_secondary.png art, sibling of .chp-card-inner
   (not nested inside it) so it isn't confined to a grid cell or clipped
   by .chp-card-inner's overflow:hidden, and can cross the
   portrait/content boundary. z-index:6 vs .chp-card-inner's z:1 (that
   whole element is its own stacking context, so beating its z-index
   puts this on top of everything inside it, portrait included).
   left:58% intentionally overlaps the portrait's right edge a little.
   Applied to all 6 chapter cards. ── */
.chp-card .chp-enter-btn-wrap{
  position:absolute;
  left:58%;bottom:14px;transform:translateX(-50%);
  z-index:6;
  display:flex;align-items:center;justify-content:center;
}
/* Button art swapped from btn_secondary.png to the same army_save.png
   ribbon used for the Army Builder Save button (per developer request --
   "that will look a lot cooler"), and narrowed to hug the "Enter" text
   instead of the old oversized 150px pill. Sized via aspect-ratio
   (matching army_save's native 1965x542) instead of a fixed height, so
   the ribbon art is never stretched off-proportion. */
.chp-card .lvl-btn-wrap{
  position:relative;
  width:104px;aspect-ratio:1965/542;height:auto;flex:0 0 auto;
  display:flex;align-items:center;justify-content:center;
  cursor:pointer;
  background:none;border:none;box-shadow:none;
}
.chp-card .lvl-btn-wrap:hover .lvl-btn-asset{
  filter:drop-shadow(0 3px 6px rgba(0,0,0,.65)) drop-shadow(0 0 12px rgba(214,178,74,.75)) brightness(1.1);
}
.chp-card .lvl-btn-wrap:active{transform:scale(.95);}
.chp-card .lvl-btn-asset{
  display:block;
  position:absolute;inset:0;width:100%;height:100%;object-fit:contain;
  filter:drop-shadow(0 3px 6px rgba(0,0,0,.65)) drop-shadow(0 0 8px rgba(214,178,74,.55));
  transition:filter .12s;
}
.chp-card .lvl-btn-label{
  position:relative;z-index:1;
  font-size:13px;color:var(--pch-gold-bright);text-shadow:0 1px 3px rgba(0,0,0,.9);
  font-weight:700;pointer-events:none;
}
#chp-footer-bar{
  flex-shrink:0;padding:4px 12px 6px;text-align:center;
  display:flex;flex-direction:column;align-items:center;gap:4px;
}
#chp-footer-text{color:var(--pch-ink-soft);font-size:10px;letter-spacing:1px;font-style:italic;
  text-shadow:0 1px 2px rgba(255,255,255,.6);}

/* ══ LEVEL SELECT SCREEN — ASSET-BASED ═════════════════════════ */
#screen-level{
  display:none;flex-direction:column;flex:1;
  position:relative;height:100%;overflow:hidden;
}
#lvl-bg{
  position:absolute;inset:0;z-index:0;
  /* level_select_bg.png was never actually added as an asset -- falls
     back to the same background Chapter Select uses, per developer
     request, rather than a broken image reference. */
  background:url('assets/img/backgrounds/chapter_select_bg.webp') center/cover no-repeat;
}
#lvl-bg::after{
  content:'';position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(30,18,8,.18),rgba(20,12,4,.38));
}
#lvl-content{
  position:relative;z-index:1;
  display:flex;flex-direction:column;flex:1;min-height:0;
  padding:0 0 10px;
}
/* Back button floats independently, alongside the banner -- same fix
   as Chapter Select's header (see its comment for the bug this corrects). */
#lvl-header-controls{
  position:absolute;top:14px;left:14px;z-index:5;
}
/* ── Banner -- real title_banner.png artwork, same treatment as
   Chapter Select's own header (see its comment for the reasoning). ── */
#lvl-banner-wrap{
  position:relative;flex-shrink:0;
  width:min(60%,600px);aspect-ratio:3/1;
  margin:6px auto 0;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:0 15%;
  background-image:url('assets/img/ui/title_banner.webp');
  background-size:100% 100%;background-repeat:no-repeat;background-position:center;
  filter:drop-shadow(0 4px 10px rgba(0,0,0,.45));
}
#lvl-banner-text{
  position:relative;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  pointer-events:none;
}
#lvl-title{
  color:var(--pch-gold-bright);font-size:23px;font-weight:700;letter-spacing:2.5px;
  font-family:'Cinzel Decorative',serif;
  text-shadow:0 1px 3px rgba(0,0,0,.75);
  line-height:1;white-space:nowrap;
}
#lvl-chapter-sub{
  margin-top:5px;text-align:center;
  color:#d8c8a8;font-size:11px;letter-spacing:1.2px;
  text-transform:uppercase;font-style:italic;
  text-shadow:0 1px 2px rgba(0,0,0,.6);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;
}
#lvl-back-btn{
  background:none;border-style:solid;border-width:14px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:14px;
  border-image-source:url('assets/img/ui/shop_banner_red.webp');
  color:var(--pch-gold-bright);padding:5px 12px;
  font-size:9px;font-family:'Cinzel',serif;letter-spacing:.5px;cursor:pointer;
}
#lvl-back-btn:hover{filter:brightness(1.15);}
#mode-toggle-wrap{
  display:flex;align-items:center;justify-content:center;gap:8px;
  padding:6px 14px 2px;
}

/* ══ LEVEL CARDS ════════════════════════════════════════════════ */

/*
  CORRECTED GEOMETRY - from DevTools measurements:
  Portrait img renders at 118×129px
  Card should be ~170px tall (just taller than portrait)
  Card width: 480px
  
  APPROACH: Fixed height card (not aspect-ratio locked)
  Everything in normal flex flow - NO absolute positioning on content
  
  COLUMNS:
  - Portrait: 130px wide column
  - Content:  flex:1
  - Buttons:  104px wide column
*/

#lvl-cards{
  flex:1;min-height:0;display:flex;flex-direction:column;
  gap:10px;padding:22px 10px 6px;overflow-y:auto;touch-action:pan-y;
}

/* Card frame -- now reuses the Shop/Army Builder/Chapter Select banner
   border-image technique instead of the old mission_card.png + sepia
   filter modifiers. Colors cycle red/blue/green/purple across all 20
   cards in document order (see the .lvl-banner-* classes added
   alongside each .lvl-card in index.html), independent of the old
   lava/castle/dungeon/goblin theme classes (which still exist but no
   longer drive a background image -- harmless leftover markers). */
.lvl-card{
  position:relative;
  width:100%;
  height:168px;box-sizing:border-box;
  cursor:pointer;
  border-style:solid;border-width:20px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:20px;
  box-shadow:0 4px 14px rgba(0,0,0,.5);
  transition:transform .15s,box-shadow .15s,filter .15s;
}
.lvl-card:hover{transform:translateY(-2px);box-shadow:0 8px 20px rgba(0,0,0,.55);filter:brightness(1.06);}
.lvl-card:active{transform:scale(.985);}
.lvl-banner-red{border-image-source:url('assets/img/ui/shop_banner_red.webp');}
.lvl-banner-blue{border-image-source:url('assets/img/ui/shop_banner_blue.webp');}
.lvl-banner-green{border-image-source:url('assets/img/ui/shop_banner_green.webp');}
.lvl-banner-purple{border-image-source:url('assets/img/ui/shop_banner_purple.webp');}

/* Card inner: flex row, fills card exactly. overflow:hidden lives here
   (not on .lvl-card) so .lvl-reward-preview -- now a sibling of this
   element, not a descendant -- can poke past the card frame while
   everything actually inside the grid stays safely clipped. */
.lvl-card-inner{
  position:absolute;inset:0;overflow:hidden;
  display:flex;flex-direction:row;align-items:stretch;
  z-index:1;
}

/* ── BADGE: now the same pennant-flag artwork as Chapter Select (see
   chapter_flag_red/blue/green/purple.png), mapped from the existing
   8 badge color modifier classes onto the 4 flag colors by closest
   thematic match. ── */
.lvl-badge-wrap{
  position:absolute;top:-20px;left:2px;
  width:34px;height:66px;
  display:flex;align-items:flex-start;justify-content:center;
  padding-top:11px;z-index:10;
  background-repeat:no-repeat;background-position:center top;background-size:contain;
  filter:drop-shadow(0 3px 5px rgba(0,0,0,.5));
}
.lvl-badge-wrap::before{display:none;}
/* Level info button ("i") -- top-right corner of a level card,
   opposite the roman-numeral badge. Level Select's cards hide their
   description (see .lvl-flavor{display:none} further down) to make
   room for the rewards row, so this is how a player still reads it --
   opens #level-info-modal via showLevelInfo() in game.js. */
.lvl-info-btn{
  position:absolute;top:6px;right:6px;z-index:10;
  width:20px;height:20px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  background:radial-gradient(circle at 35% 30%,#3a2c14,#160f06);
  border:1px solid var(--pch-gold);color:var(--pch-gold-bright);
  font-family:'Cinzel',serif;font-size:12px;font-weight:700;font-style:italic;
  cursor:pointer;padding:0;line-height:1;
  box-shadow:0 2px 5px rgba(0,0,0,.5);
  transition:transform .12s,filter .12s;
}
.lvl-info-btn:hover{transform:scale(1.12);filter:brightness(1.2);}
.lvl-badge-green{background-image:url('assets/img/ui/chapter_flag_green.webp');}
.lvl-badge-goblin{background-image:url('assets/img/ui/chapter_flag_green.webp');}
.lvl-badge-mossy{background-image:url('assets/img/ui/chapter_flag_green.webp');}
.lvl-badge-red{background-image:url('assets/img/ui/chapter_flag_red.webp');}
.lvl-badge-brown{background-image:url('assets/img/ui/chapter_flag_red.webp');}
.lvl-badge-purple{background-image:url('assets/img/ui/chapter_flag_purple.webp');}
.lvl-badge-carbon{background-image:url('assets/img/ui/chapter_flag_purple.webp');}
.lvl-badge-steel{background-image:url('assets/img/ui/chapter_flag_blue.webp');}
.lvl-badge-roman{
  position:relative;z-index:1;
  font-family:'Cinzel Decorative',serif;
  font-size:11px;font-weight:700;color:#e8d8a0;
  text-shadow:0 1px 3px rgba(0,0,0,.95),0 0 4px rgba(0,0,0,.6);line-height:1;
}

/* ── PORTRAIT COLUMN: 130px wide, image bottom-aligned ──
   overflow:hidden clips portrait cleanly at card edges
   portrait-wrap is exactly card height (168px) so portrait
   sits on the floor and clips at top if too tall
*/
.lvl-portrait-wrap{
  flex:0 0 130px;width:130px;
  height:135px;
  align-self:center;
  display:flex;align-items:flex-end;justify-content:center;
  overflow:hidden;z-index:2;
  padding:0 6px 8px 10px;
}
.lvl-portrait{
  display:block;
  width:118px;height:auto;
  max-height:164px;
  object-fit:contain;object-position:bottom center;
  filter:drop-shadow(2px 0 6px rgba(0,0,0,.5));
}
.lvl-portrait-alice{width:110px;max-height:164px;}
.lvl-portrait-necro{width:114px;max-height:164px;}
/* Silhouette variant -- used by Chapter 6's Ashigami card until a
   dedicated pre-blacked-out image is available. brightness(0) turns any
   non-transparent pixel pure black while preserving the alpha/shape, so
   the existing ag_mouth_open art reads as a solid silhouette. */
.lvl-portrait-silhouette{filter:brightness(0) drop-shadow(2px 0 6px rgba(0,0,0,.5));}

/* ── CONTENT BLOCK: flex:1, vertically distributes ──
   Text colors brightened for contrast against the new dark banner
   interiors (same fix as Chapter Select -- the old dark-ink colors were
   tuned for a light parchment card). The old fire/castle/dungeon/goblin
   single-class color modifiers are lower specificity than these base
   rules now, so plain overrides here are enough -- no !important needed. */
.lvl-content-block{
  flex:1;min-width:0;
  display:flex;flex-direction:column;
  padding:22px 6px 10px 10px;
  z-index:2;
}
.lvl-name{
  font-family:'Cinzel',serif;font-size:16px;font-weight:800;
  color:var(--pch-gold-bright);letter-spacing:.3px;margin-bottom:2px;
  text-shadow:0 2px 0 rgba(40,24,8,.85),0 1px 2px rgba(0,0,0,.5);
}
.lvl-card .lvl-content-block{text-align:center;}
.lvl-card .lvl-tags,.lvl-card .lvl-enemy-row{justify-content:center;}
.lvl-name-fire,.lvl-name-castle,.lvl-name-dungeon,.lvl-name-goblin{color:var(--pch-gold-bright);}
.lvl-flavor{font-size:9.5px;color:#f0e6d0;line-height:1.35;margin-top:4px;margin-bottom:4px;text-shadow:0 1px 2px rgba(0,0,0,.7);}

.lvl-tags{display:flex;flex-wrap:wrap;gap:3px;}
.lvl-tag{
  display:inline-flex;align-items:center;
  background:rgba(0,0,0,.4);border:1px solid rgba(255,215,120,.55);
  border-radius:3px;padding:2px 7px;
  font-size:8.5px;font-family:'Cinzel',serif;color:#f5e6bc;letter-spacing:.3px;
}
.lvl-tag-fire,.lvl-tag-castle,.lvl-tag-dungeon,.lvl-tag-goblin{
  background:rgba(0,0,0,.4);border-color:rgba(255,215,120,.55);color:#f5e6bc;
}
.lvl-tag-tutorial{  background:#3d5c1a;border-color:#2a3f10;color:#d8f0c0;font-weight:700;}
.lvl-tag-new-fire{  background:#8a2010;border-color:#6a1408;color:#ffe0c0;font-weight:700;}
.lvl-tag-new-castle{background:#4a3880;border-color:#342870;color:#e0d8ff;font-weight:700;}
.lvl-tag-new-goblin{background:#2a5818;border-color:#1a3c08;color:#d0ffc0;font-weight:700;}
.lvl-tag-locked{    background:#524840;border-color:#3c3028;color:#c0b8a8;font-style:italic;}

/* Divider pushes enemies to bottom via margin-top:auto. Inverted so it
   shows up against the dark banner interior instead of vanishing. */
.lvl-divider{
  display:block;width:95%;max-height:11px;
  object-fit:contain;object-position:left;
  opacity:.4;pointer-events:none;flex-shrink:0;
  margin:auto 0 3px;
  filter:brightness(0) invert(1);
}
.lvl-enemy-row{display:flex;flex-direction:row;align-items:baseline;gap:3px;padding-bottom:10px;}
.lvl-enemy-label{
  font-family:'Cinzel',serif;font-size:8.5px;font-weight:700;
  color:#f0dca0;letter-spacing:.5px;text-transform:uppercase;
  white-space:nowrap;flex-shrink:0;text-shadow:0 1px 2px rgba(0,0,0,.6);
}
.lvl-enemy-names{
  font-size:8.5px;color:#e8dcc0;font-style:italic;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  text-shadow:0 1px 2px rgba(0,0,0,.6);
}
.lvl-enemy-unknown{color:#c0b8a4;letter-spacing:2px;}

/* ── ENTER BUTTONS (Scan + Deploy): sibling of .lvl-card-inner (not a
   descendant), position:absolute, z-index above .lvl-card-inner (z:1)
   and .lvl-portrait-wrap (z:2) -- same fix as Chapter Select's Enter
   button, so the pair can cross the portrait/content boundary and
   isn't confined to a grid cell or clipped by overflow:hidden.
   left:58% intentionally overlaps the portrait's right edge a little,
   matching Chapter Select. ── */
.lvl-card .lvl-enter-btn-wrap{
  position:absolute;
  left:58%;bottom:14px;transform:translateX(-50%);
  z-index:6;
  display:flex;align-items:center;justify-content:center;gap:8px;
}
.lvl-card .lvl-btn-wrap{
  position:relative;
  width:92px;height:34px;flex:0 0 auto;
  display:flex;align-items:center;justify-content:center;
  cursor:pointer;
  background:none;border:none;box-shadow:none;
}
.lvl-card .lvl-btn-wrap:hover .lvl-btn-asset{
  filter:drop-shadow(0 3px 6px rgba(0,0,0,.65)) drop-shadow(0 0 12px rgba(214,178,74,.75)) brightness(1.1);
}
.lvl-card .lvl-btn-wrap:active{transform:scale(.95);}
.lvl-card .lvl-btn-wrap-disabled{cursor:not-allowed;opacity:.55;pointer-events:none;}
.lvl-card .lvl-btn-asset{
  display:block;
  position:absolute;inset:0;width:100%;height:100%;object-fit:fill;
  filter:drop-shadow(0 3px 6px rgba(0,0,0,.65)) drop-shadow(0 0 8px rgba(214,178,74,.55));
  transition:filter .12s;
}
.lvl-card .lvl-btn-label{
  position:relative;z-index:1;
  font-size:12px;font-family:'Cinzel',serif;font-weight:700;
  color:var(--pch-gold-bright);letter-spacing:.3px;
  text-shadow:0 1px 3px rgba(0,0,0,.9);
  white-space:nowrap;pointer-events:none;
}
.lvl-card .lvl-btn-label-primary{color:var(--pch-gold-bright);text-shadow:0 1px 3px rgba(0,0,0,.9);}
.lvl-card .lvl-btn-label-disabled{color:#7a7060;text-shadow:none;}

/* Corner ornaments were sized for the old mission_card.png art and clash
   with the new banner frame. */
.lvl-corner{display:none;}
/* Level Select reward preview -- now the first item in .lvl-btn-col
   (Reward preview / Scan / Deploy), evenly spaced via that container's
   justify-content:space-between rather than floating as an absolutely-
   positioned corner badge (which used to hang slightly over the Scan
   button below it). Shows what that level actually awards on victory
   (token + Coins, or the Level 8 Booster Tin). Populated by
   renderLevelRewardPreviews() in game.js, not hardcoded per-card, since
   it's driven by LEVEL_REWARDS/coin logic that already exists for the
   actual reward screen -- this is just a preview of the same data. */
.lvl-reward-preview{
  position:relative;flex-shrink:0;
  align-self:center;width:fit-content;max-width:none;
  display:flex;align-items:center;justify-content:center;gap:4px;
  background:rgba(10,8,4,.55);border:1px solid rgba(200,160,80,.35);
  border-radius:15px;padding:3px 10px;
  min-height:22px;
}
/* Already-earned level: same "dim the art, glowing gold tag on top"
   treatment as Chapter Select's rewards popup (.chapter-reward-row.earned
   / .chapter-reward-earned-badge below) -- previously this whole preview
   just didn't render once a level was cleared, which read as "you never
   got a reward here" rather than "you already claimed this". Now it
   stays visible (all its icons dimmed via the >* selector, whether
   that's one token thumb or a token + a separate Coins badge) with a
   single "Earned" tag centered over the whole pill, reusing the exact
   glow keyframes already defined for the chapter-select version further
   down so both look/feel consistent. */
.lvl-reward-preview.earned>*{opacity:.3;filter:grayscale(.5);}
.lvl-reward-earned-badge{
  position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) rotate(-8deg);
  z-index:2;
  background:rgba(10,8,4,.82);border:1px solid rgba(255,204,68,.7);border-radius:3px;
  padding:2px 5px;white-space:nowrap;
  font-family:'Cinzel',serif;font-weight:700;font-size:8px;letter-spacing:.5px;
  text-transform:uppercase;color:#ffe8a0;
  text-shadow:0 0 5px #ffaa00;
  box-shadow:0 0 8px rgba(255,170,0,.5);
  pointer-events:none;
  animation:chapterRewardEarnedGlow 1.8s ease-in-out infinite;
}
.lvl-reward-token-thumb{
  width:30px;height:30px;object-fit:contain;border-radius:50%;
  cursor:pointer;transition:transform .12s;
  filter:drop-shadow(0 1px 3px rgba(0,0,0,.6));
}
.lvl-reward-token-thumb:hover{transform:scale(1.15);}
.lvl-reward-tin-wrap{position:relative;cursor:pointer;transition:transform .12s;display:inline-flex;}
.lvl-reward-tin-wrap:hover{transform:scale(1.15);}
.lvl-reward-tin-count{
  position:absolute;bottom:-3px;right:-4px;
  background:var(--pch-gold-bright);color:#241a0e;
  font-family:'Cinzel',serif;font-size:9px;font-weight:800;
  border-radius:8px;padding:0 3px;line-height:1.3;
  border:1px solid #241a0e;box-shadow:0 1px 2px rgba(0,0,0,.6);
}
.lvl-reward-coin-badge{
  display:flex;align-items:center;gap:2px;cursor:pointer;
  transition:transform .12s;
}
.lvl-reward-coin-badge:hover{transform:scale(1.1);}
.lvl-reward-coin-badge img{width:20px;height:20px;object-fit:contain;}
.lvl-reward-coin-badge span{
  font-family:'Cinzel',serif;font-size:11px;font-weight:700;
  color:#ffe060;text-shadow:0 1px 2px rgba(0,0,0,.8);
}
/* Level Select tile star-rating badge -- best-ever rating for that
   level (playerProfile.levelStars), populated by renderLevelStarBadges()
   in game.js. Sits just below .lvl-reward-preview in the same
   .lvl-rewards-section; hidden entirely until the level has been
   cleared at least once (0 stars -- see the function's early-return).
   Same "one gold star image, dim it via filter for empty slots"
   approach as #ov-stars on the Victory card. */
.lvl-star-badge{display:flex;gap:2px;align-items:center;justify-content:center;margin-top:3px;}
.lvl-star-badge-icon{
  width:13px;height:13px;object-fit:contain;
  filter:grayscale(1) brightness(.45);opacity:.5;
}
.lvl-star-badge-icon.lvl-star-badge-filled{
  filter:drop-shadow(0 0 3px rgba(255,180,40,.7));opacity:1;
}
/* Simple click-to-enlarge lightbox for the reward preview's non-token
   images (the coin icon, the Level 8 tin icon) -- unit tokens instead
   open the full flip-card Token Viewer (openTokenViewer()), same as
   everywhere else in the game. */
#img-zoom-modal{
  display:none;position:fixed;inset:0;z-index:96;
  align-items:center;justify-content:center;
  background:rgba(0,0,0,.82);cursor:pointer;
}
#img-zoom-modal.show{display:flex;}
#img-zoom-modal img{
  max-width:70vw;max-height:60vh;object-fit:contain;
  filter:drop-shadow(0 8px 30px rgba(0,0,0,.7));
  animation:rewardPop .35s cubic-bezier(.22,.8,.42,1) both;
}
#img-zoom-modal-label{
  position:absolute;bottom:18%;left:0;right:0;text-align:center;
  font-family:'Cinzel Decorative',serif;font-size:14px;color:#ffe060;
  text-shadow:0 0 12px #ffaa00;
}
/* Enlarged resistance-badge popup -- see showStatusBadgeInfo() in game.js.
   z-index above #token-viewer (90) since it opens from a badge nested
   inside that overlay. Same backdrop treatment as #img-zoom-modal/
   #chapter-rewards-modal, just with a small text card instead of a big
   image. */
#status-badge-popup{
  display:none;position:fixed;inset:0;z-index:98;
  align-items:center;justify-content:center;
  background:rgba(0,0,0,.82);cursor:pointer;
}
#status-badge-popup.show{display:flex;}
#status-badge-popup-card{
  display:flex;flex-direction:column;align-items:center;gap:8px;
  background:linear-gradient(160deg,#120c06,#0a0806,#0d0a04);
  border:1px solid #7a5c28;border-radius:10px;
  padding:20px 28px;max-width:80vw;
  box-shadow:0 8px 30px rgba(0,0,0,.7);
  animation:rewardPop .35s cubic-bezier(.22,.8,.42,1) both;
  cursor:default;
}
#status-badge-popup-icon{width:48px;height:48px;object-fit:contain;filter:drop-shadow(0 2px 6px rgba(0,0,0,.6));}
#status-badge-popup-title{
  font-family:'Cinzel Decorative',serif;font-size:14px;color:#ffe060;
  text-shadow:0 0 12px #ffaa00;text-align:center;letter-spacing:.5px;
}
#status-badge-popup-desc{
  font-family:'Cinzel',serif;font-size:11px;color:#c8b880;
  text-align:center;line-height:1.5;max-width:220px;
}
/* Chapter rewards info popup -- opened from a chapter banner's rewards
   teaser image (see #ch3-rewards-teaser). Same full-screen backdrop
   treatment as the other reward modals in this file. */
#chapter-rewards-modal{
  display:none;position:fixed;inset:0;z-index:97;
  align-items:center;justify-content:center;
  background:rgba(0,0,0,.82);
}
#chapter-rewards-modal.show{display:flex;}
#chapter-rewards-box{
  display:flex;flex-direction:column;align-items:center;
  gap:10px;padding:22px 24px;max-width:320px;width:88%;
  background:linear-gradient(180deg,#100c06,#0a0806);
  border:1px solid #7a5c28;border-radius:8px;
  box-shadow:0 0 40px rgba(200,160,40,.18),0 8px 32px rgba(0,0,0,.9);
  animation:rewardPop .35s cubic-bezier(.22,.8,.42,1) both;
}
#chapter-rewards-title{
  font-family:'Cinzel Decorative',serif;font-size:15px;
  color:#ffe060;text-shadow:0 0 12px #ffaa00;
  letter-spacing:1px;text-align:center;
}
#chapter-rewards-sub{
  font-family:'Cinzel',serif;font-size:9px;color:#a8b880;
  text-align:center;line-height:1.4;margin-bottom:4px;
}
#chapter-rewards-list{
  display:flex;flex-direction:column;gap:8px;width:100%;
}
.chapter-reward-row{
  display:flex;align-items:center;gap:10px;
  background:rgba(255,255,255,.04);border-radius:6px;padding:6px 10px;
}
.chapter-reward-row img{width:38px;height:38px;object-fit:contain;flex-shrink:0;}
.chapter-reward-img-wrap{position:relative;flex-shrink:0;width:38px;height:38px;display:flex;align-items:center;justify-content:center;}
/* Already-claimed reward: image dims, and a glowing gold "Earned" tag
   sits centered on top of it. Rebuilt from a first pass that stretched
   6.5px text across the full 32px icon with a heavy 3-layer glow --
   technically present but functionally invisible at that size. Now a
   proper auto-sized pill (own dark background for guaranteed contrast
   against any icon colour, single crisp glow, readable font size)
   instead of text crammed edge-to-edge with no breathing room. Icon
   bumped 32px->38px to give it a bit more room to sit on. */
.chapter-reward-row.earned .chapter-reward-img-wrap img{opacity:.3;filter:grayscale(.5);}
.chapter-reward-earned-badge{
  position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) rotate(-8deg);
  background:rgba(10,8,4,.82);border:1px solid rgba(255,204,68,.7);border-radius:3px;
  padding:2px 5px;white-space:nowrap;
  font-family:'Cinzel',serif;font-weight:700;font-size:8px;letter-spacing:.5px;
  text-transform:uppercase;color:#ffe8a0;
  text-shadow:0 0 5px #ffaa00;
  box-shadow:0 0 8px rgba(255,170,0,.5);
  pointer-events:none;
  animation:chapterRewardEarnedGlow 1.8s ease-in-out infinite;
}
@keyframes chapterRewardEarnedGlow{
  0%,100%{box-shadow:0 0 6px rgba(255,170,0,.4);}
  50%{box-shadow:0 0 12px rgba(255,204,68,.75);}
}
.chapter-reward-row-text{display:flex;flex-direction:column;gap:1px;}
.chapter-reward-row-level{
  font-family:'Cinzel',serif;font-size:9px;color:#c8a040;
  letter-spacing:1px;text-transform:uppercase;
}
.chapter-reward-row-desc{
  font-family:'Cinzel',serif;font-size:11px;font-weight:700;color:#e8d8b0;
}

#level-info-modal{
  display:none;position:fixed;inset:0;z-index:97;
  align-items:center;justify-content:center;
  background:rgba(0,0,0,.82);
}
#level-info-modal.show{display:flex;}
#level-info-box{
  display:flex;flex-direction:column;align-items:center;
  gap:10px;padding:22px 24px;max-width:320px;width:88%;
  background:linear-gradient(180deg,#100c06,#0a0806);
  border:1px solid #7a5c28;border-radius:8px;
  box-shadow:0 0 40px rgba(200,160,40,.18),0 8px 32px rgba(0,0,0,.9);
  animation:rewardPop .35s cubic-bezier(.22,.8,.42,1) both;
}
#level-info-title{
  font-family:'Cinzel Decorative',serif;font-size:15px;
  color:#ffe060;text-shadow:0 0 12px #ffaa00;
  letter-spacing:1px;text-align:center;
}
#level-info-desc{
  font-family:'Cinzel',serif;font-size:11px;color:#e8d8b0;
  text-align:center;line-height:1.5;
}


/* ── Footer ── */
#lvl-footer{
  flex-shrink:0;padding:4px 12px 6px;text-align:center;
  display:flex;flex-direction:column;align-items:center;gap:4px;
}
#lvl-footer-text{
  color:var(--pch-ink-soft);font-size:10px;letter-spacing:1px;font-style:italic;
  text-shadow:0 1px 2px rgba(255,255,255,.6);
}


/* ══ LAVA BATTLE THEMING ════════════════════════════════════════ */
/* When level 2 is active: dark ember sky, no scrolling clouds */
#screen-battle.lava-level #board-wrap{background-color:#1a0800;}
/* #bottom-bar's own dedicated background override removed -- same fix as
   the base #bottom-bar rule (see its comment): border-image already
   paints the full card, so a level-tinted fill here would just
   reintroduce the black-halo bug on this level specifically. */
#screen-battle.lava-level #logbar{
  background:#080200;color:#8a4020;border-top-color:#1e0c04;
}

/* Note: the old dedicated "Castle card (Level 3)" style block (lvl-card-castle
   background/border, lvl-badge-castle, lvl-tag-castle-dim/bright,
   lvl-card-footer-castle, lvl-play-castle) has been removed -- it was a
   leftover from an earlier card design, fully superseded by the new
   banner border-image system above, and its .lvl-name-castle color
   override was actively fighting the new text-contrast fix (same
   specificity, later in the file, so it silently won for that one card). */

/* ══ CASTLE BATTLE THEMING ════════════════════════════════════════ */
#screen-battle.castle-level #board-wrap{background-color:#2e2e38;}
/* #bottom-bar's own dedicated background override removed -- same fix as
   the base #bottom-bar rule (see its comment). */
#screen-battle.castle-level #logbar{
  background:#080810;color:#8080b0;border-top-color:#1a1a28;
}


/* Note: the old dedicated "Dungeon card (Level 4)" style block has been
   removed for the same reason as the Castle block above -- superseded,
   and its .lvl-name-dungeon override was fighting the contrast fix. */

/* ══ DUNGEON BATTLE THEMING ═══════════════════════════════════════ */
#screen-battle.dungeon-level #board-wrap{background-color:#1a1410;}
/* #bottom-bar's own dedicated background override removed -- same fix as
   the base #bottom-bar rule (see its comment). */
#screen-battle.dungeon-level #logbar{
  background:#080604;color:#a07030;border-top-color:#1a1008;
}

/* Back button — shown during action targeting */
#back-btn{
  display:none;position:absolute;z-index:21;
  bottom:8px;left:50%;transform:translateX(-50%);
  background:linear-gradient(180deg,#0d1508,#080e06);
  border:1px solid #4a6820;border-radius:3px;
  padding:5px 18px;
  color:#a8c060;font-size:9px;font-family:'Cinzel',serif;
  letter-spacing:1px;cursor:pointer;
  box-shadow:0 0 0 1px #0a0c08,1px 2px 6px rgba(0,0,0,.8);
}
#back-btn::before{content:'\25C4 ';color:#6a8838}
#back-btn.show{display:block}
.dmg-float{position:absolute;pointer-events:none;font-family:'Cinzel',serif;font-weight:700;font-size:14px;text-shadow:0 0 6px #ff3000,0 1px 3px #000;z-index:25}

/* ══ REWARD SCREEN ══════════════════════════════════════════════
   position:fixed (not absolute) -- this now lives as a top-level
   overlay (see index.html, moved out of #board-wrap), covering the
   whole viewport including the floating menu/zoom controls and the
   turn-order queue, which used to show/bleed through behind the old
   translucent version.
   Background is solid now for the same reason -- nothing from the
   battle screen should be visible here at all. */
#reward-screen{
  display:none;position:fixed;inset:0;z-index:50;
  align-items:safe center;justify-content:center;
  background:radial-gradient(ellipse at center,#1c1408 0%,#08060300 70%),#050300;
  overflow-y:auto;touch-action:pan-y;padding:16px 0;
}
#reward-screen.show{display:flex;}
/* Level 8's Booster Tin reveal -- same full-screen dark backdrop treatment
   as #reward-screen, but its own simpler box (just a title/sub, the tin
   itself, and a tap hint -- no card, no diamond, no Continue button, since
   tapping the tin IS the action here; the actual 6-token reveal happens in
   the Shop's own #shop-reward-modal afterward, see openLevel8BoosterTin()). */
#booster-tin-reveal{
  display:none;position:fixed;inset:0;z-index:50;
  align-items:safe center;justify-content:center;
  background:radial-gradient(ellipse at center,#1c1408 0%,#08060300 70%),#050300;
  overflow-y:auto;touch-action:pan-y;padding:16px 0;
}
#booster-tin-reveal.show{display:flex;}
#booster-tin-box{
  display:flex;flex-direction:column;align-items:center;
  gap:10px;padding:24px 20px 20px;
  background:linear-gradient(180deg,#100c06,#08060300);
  border:1px solid #7a5c28;border-radius:8px;
  box-shadow:0 0 40px rgba(200,160,40,.18),0 8px 32px rgba(0,0,0,.9);
  max-width:280px;width:90%;
  max-height:calc(100vh - 32px);overflow-y:auto;touch-action:pan-y;
  animation:rewardPop .5s cubic-bezier(.22,.8,.42,1) both;
}
#booster-tin-title{
  font-family:'Cinzel Decorative',serif;font-size:13px;
  color:#ffe060;text-shadow:0 0 16px #ffaa00,0 0 32px #ff6600;
  letter-spacing:2px;text-align:center;
}
#booster-tin-sub{
  font-family:'Cinzel',serif;font-size:9px;
  color:#c8a040;letter-spacing:2px;text-transform:uppercase;
  margin-bottom:4px;
}
#booster-tin-img{
  width:min(160px,50vw,42vh);height:min(160px,50vw,42vh);object-fit:contain;cursor:pointer;
  filter:drop-shadow(0 0 20px rgba(255,200,80,.5));
  animation:boosterTinPulse 1.6s ease-in-out infinite;
}
@keyframes boosterTinPulse{0%,100%{transform:scale(1);}50%{transform:scale(1.06);}}
/* Anticipation rattle before the tin "pops" -- see openLevel8BoosterTin()
   in game.js, which adds this class on tap and delays the actual
   roll/reveal until it finishes, instead of resolving instantly. This
   is a DOM element (not the battle canvas), so it's a CSS keyframe
   shake rather than the triggerShake()/shakeOffX-Y mechanism used for
   in-battle screen shake -- same idea, different rendering surface.
   Overrides boosterTinPulse for its duration (a single element can only
   run one `animation` at a time); nothing needs to restore the pulse
   afterward since the tin screen is hidden once the reveal modal takes
   over. */
#booster-tin-img.tin-shaking{animation:tinRattle .5s ease-in-out;}
@keyframes tinRattle{
  0%,100%{transform:translateX(0) rotate(0);}
  10%{transform:translateX(-4px) rotate(-3deg);}
  20%{transform:translateX(4px) rotate(3deg);}
  30%{transform:translateX(-5px) rotate(-4deg);}
  40%{transform:translateX(5px) rotate(4deg);}
  50%{transform:translateX(-4px) rotate(-3deg);}
  60%{transform:translateX(4px) rotate(3deg);}
  70%{transform:translateX(-3px) rotate(-2deg);}
  80%{transform:translateX(3px) rotate(2deg);}
  90%{transform:translateX(-1px) rotate(-1deg);}
}
#booster-tin-hint{
  color:#8a9860;font-size:9px;letter-spacing:1.5px;font-family:'Cinzel',serif;
  animation:tokenHintBlink 1.8s ease-in-out infinite;
}
/* Coins bonus alongside the tin -- Level 12 only (Level 8's tin has none,
   per developer spec). Hidden by default; showBoosterTinReward() toggles
   it. Reuses the same visual language as #reward-coins-badge. */
#booster-tin-coins{
  display:none;align-items:center;justify-content:center;gap:6px;
  margin-top:4px;padding:5px 14px;border-radius:20px;
  background:rgba(0,0,0,.35);border:1px solid var(--pch-gold);
}
#booster-tin-coins.show{display:flex;}
#booster-tin-coins img{width:20px;height:20px;object-fit:contain;filter:drop-shadow(0 0 6px #ffd70088);}
#booster-tin-coins-amount{
  font-family:'Cinzel Decorative',serif;font-size:15px;font-weight:700;
  color:#ffe060;text-shadow:0 0 8px #ffaa00;letter-spacing:.5px;
}

#reward-box{
  display:flex;flex-direction:column;align-items:center;
  gap:10px;padding:22px 26px;
  background:linear-gradient(180deg,#100c06,#08060300);
  border:1px solid #7a5c28;border-radius:8px;
  box-shadow:0 0 40px rgba(200,160,40,.18),0 8px 32px rgba(0,0,0,.9);
  max-width:560px;width:92%;
  max-height:calc(100vh - 32px);overflow-y:auto;touch-action:pan-y;
  animation:rewardPop .5s cubic-bezier(.22,.8,.42,1) both;
}
@keyframes rewardPop{from{transform:scale(.8) translateY(30px);opacity:0;}to{transform:scale(1) translateY(0);opacity:1;}}
#reward-title{
  font-family:'Cinzel Decorative',serif;font-size:13px;
  color:#ffe060;text-shadow:0 0 16px #ffaa00,0 0 32px #ff6600;
  letter-spacing:2px;text-align:center;
}
#reward-sub{
  font-family:'Cinzel',serif;font-size:9px;
  color:#c8a040;letter-spacing:2px;text-transform:uppercase;
}
/* Token card (left) + reward info/Continue (right), as one centered
   package -- the row itself centers in #reward-box, and since the token
   column comes first it reads as "slightly left" with the info column
   to its right, per developer spec. Wraps to a centered vertical stack
   on very narrow viewports where a side-by-side row won't fit. */
#reward-content-row{
  display:flex;align-items:center;justify-content:center;
  gap:28px;width:100%;flex-wrap:wrap;margin-top:4px;
}
#reward-left-col{
  display:flex;flex-direction:column;align-items:center;gap:6px;
}
#reward-info-col{
  display:flex;flex-direction:column;align-items:flex-start;gap:12px;
  min-width:150px;
}
#reward-token-wrap{
  position:relative;
  /* Sized to always fit the viewport without the page needing to scroll
     to see it -- same min(px, vw, vh-derived-width) pattern as the
     token viewer's card. 160/200 aspect ratio == 4:5. */
  width:min(160px,52vw,44vh);
  aspect-ratio:4/5;
  margin:0;perspective:700px;
}
#reward-token-wrap canvas{
  position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:3;
}
#reward-flip-card{
  position:absolute;inset:0;cursor:pointer;z-index:1;
  transform-style:preserve-3d;
  transition:transform .55s cubic-bezier(.4,0,.2,1);
  border-radius:6px;
}
#reward-flip-card.flipped{transform:rotateY(180deg);}
.reward-token-face{
  position:absolute;inset:0;backface-visibility:hidden;-webkit-backface-visibility:hidden;
  border-radius:6px;overflow:hidden;
  box-shadow:0 4px 20px rgba(0,0,0,.7);
}
.reward-token-front{background:#0a0a0a;}
#reward-token-img{
  width:100%;height:100%;object-fit:contain;
  filter:drop-shadow(0 0 12px rgba(200,160,40,.5));
}
.reward-token-back{
  transform:rotateY(180deg);
  background:linear-gradient(160deg,#120c06,#0a0806,#0d0a04);
  border:1px solid #7a5c28;
  display:flex;flex-direction:column;gap:4px;
  padding:10px 8px;overflow-y:auto;touch-action:pan-y;
}
#reward-tbc-name{
  font-family:'Cinzel Decorative',serif;font-size:12px;font-weight:700;
  color:#ffe060;text-shadow:0 0 10px #ffaa00;
  text-align:center;letter-spacing:.5px;
}
#reward-tbc-job{
  font-family:'Cinzel',serif;font-size:8px;color:#c8a040;
  text-align:center;letter-spacing:1px;text-transform:uppercase;
  border-bottom:1px solid #3a2a10;padding-bottom:4px;margin-bottom:2px;
}
#reward-tbc-stats{
  display:grid;grid-template-columns:1fr 1fr;gap:3px 6px;font-size:8px;
  color:#a8c060;font-family:'Cinzel',serif;
}
#reward-tbc-skill{
  border:1px solid #3a2a10;border-radius:4px;padding:5px 6px;margin-top:2px;
  background:rgba(0,0,0,.3);
}
#reward-tbc-skill-name{color:#d8b860;font-size:8px;font-weight:700;font-family:'Cinzel',serif;letter-spacing:.4px;}
#reward-tbc-skill-desc{color:#a8c878;font-size:7.5px;margin-top:2px;line-height:1.4;}
/* MTG-style ability block, smaller scale to match this card's tighter
   footprint -- see the matching #tbc-skill rules and _renderAbilitiesHTML
   in game.js for the full explanation. */
#reward-tbc-skill .tbc-ability{padding-top:3px;margin-top:3px;border-top:1px solid #3a2a10;}
#reward-tbc-skill .tbc-ability:first-child{padding-top:0;margin-top:0;border-top:none;}
#reward-tbc-skill .tbc-ability-name{color:#d8b860;font-size:8px;font-weight:700;font-family:'Cinzel',serif;letter-spacing:.4px;}
#reward-tbc-skill .tbc-ability-cost{color:#8a9860;font-size:7px;font-style:italic;margin-top:1px;}
#reward-tbc-skill .tbc-ability-text{color:#a8c878;font-size:7.5px;margin-top:1px;line-height:1.4;}
#reward-tbc-desc{color:#b8c090;font-size:7px;line-height:1.45;font-style:italic;margin-top:2px;}
#reward-flip-hint{
  color:#8a9860;font-size:8px;letter-spacing:1px;font-family:'Cinzel',serif;
  animation:tokenHintBlink 1.8s ease-in-out infinite;
  margin-bottom:2px;
}
.reward-diamond{
  width:26px;height:26px;
  object-fit:contain;
  animation:gemBreath 1.5s ease-in-out infinite alternate;
  filter:drop-shadow(0 0 16px #fff8);
}
#reward-unit-name{
  font-family:'Cinzel',serif;font-size:12px;font-weight:700;
  color:#d0b880;letter-spacing:1px;text-align:left;
}
/* Gems reward badge (Levels 9-11) -- the PRIMARY reward on these levels
   (no unit token), so styled bigger/bolder than the Coins badge below it,
   which is always just a secondary add-on. */
#reward-gems-badge{
  display:none;align-items:center;justify-content:center;gap:8px;
  margin-top:0;padding:8px 20px;border-radius:24px;
  background:rgba(20,10,30,.5);border:1.5px solid #9060ff;
  box-shadow:0 0 18px rgba(140,80,255,.35);
}
#reward-gems-badge.show{display:flex;}
#reward-gems-badge img{width:30px;height:30px;object-fit:contain;filter:drop-shadow(0 0 8px #b080ff);}
#reward-gems-amount{
  font-family:'Cinzel Decorative',serif;font-size:20px;font-weight:700;
  color:#d8b8ff;text-shadow:0 0 10px #9050ff;letter-spacing:.5px;
}
/* Astral Gem reward badge -- Chapter 6 (Ashigami's Trial) only, see
   showSecretChapterReward() in game.js. Same shape/sizing as the Gems
   badge above, distinct icy-blue/starlight color scheme so it reads as
   its own rarer currency rather than more Gems. */
#reward-astral-badge{
  display:none;align-items:center;justify-content:center;gap:8px;
  margin-top:0;padding:8px 20px;border-radius:24px;
  background:rgba(10,20,35,.5);border:1.5px solid #60c0ff;
  box-shadow:0 0 18px rgba(80,180,255,.35);
}
#reward-astral-badge.show{display:flex;}
#reward-astral-badge img{width:30px;height:30px;object-fit:contain;filter:drop-shadow(0 0 8px #90d8ff);}
#reward-astral-amount{
  font-family:'Cinzel Decorative',serif;font-size:20px;font-weight:700;
  color:#c0e8ff;text-shadow:0 0 10px #50a8ff;letter-spacing:.5px;
}
/* Coin reward badge -- hidden by default (display:none), shown by
   showRewardScreen() whenever _lastVictoryCoinsAwarded>0. Bold coin icon
   + amount, styled distinctly from the token name above it so it reads
   as a separate, additional reward rather than more token detail. */
#reward-coins-badge{
  display:none;align-items:center;justify-content:center;gap:6px;
  margin-top:0;padding:5px 14px;border-radius:20px;
  background:rgba(0,0,0,.35);border:1px solid var(--pch-gold);
}
#reward-coins-badge.show{display:flex;}
#reward-continue-btn{margin-top:2px;align-self:flex-start;}
#reward-coins-badge img{width:20px;height:20px;object-fit:contain;filter:drop-shadow(0 0 6px #ffd70088);}
#reward-coins-amount{
  font-family:'Cinzel Decorative',serif;font-size:15px;font-weight:700;
  color:#ffe060;text-shadow:0 0 8px #ffaa00;letter-spacing:.5px;
}

/* ══ TOKEN VIEWER (flip card) ════════════════════════════════════
   Shared by every "enlarge a token" entry point in the game -- Army
   Builder, the roster's inspect button, Mission Scan's enemy chips,
   the Shop's reward reveal, and clicking a unit's portrait in battle.
   A single CSS fix here covers all of them. */
#token-viewer{
  display:none;position:fixed;inset:0;z-index:90;
  background:rgba(0,0,0,.92);align-items:safe center;justify-content:center;
  backdrop-filter:blur(6px);
  overflow-y:auto;touch-action:pan-y;padding:16px 0;
}
#token-viewer.show{display:flex;}
#token-flip-scene{
  display:flex;flex-direction:column;align-items:center;gap:12px;
  perspective:900px;
  margin:auto 0;
}
#token-flip-left{display:contents;}
#token-flip-right{display:contents;}
/* Army Builder mode (openTokenViewerFromArmy/FromSelected) -- horizontal
   layout instead of the default vertical stack: token card (+ its flip
   hint) forms its own left column, the gem balance / Add-Remove-Buy
   buttons form a right column, and the whole row centers itself as one
   package so the token sits slightly left of center with the
   text/buttons to its right. */
#token-viewer.token-viewer-army #token-flip-scene{
  flex-direction:row;align-items:center;justify-content:center;gap:28px;
}
#token-viewer.token-viewer-army #token-flip-left{
  display:flex;flex-direction:column;align-items:center;gap:10px;
}
#token-viewer.token-viewer-army #token-flip-right{
  display:flex;flex-direction:column;align-items:flex-start;gap:14px;
}
#token-flip-card{
  /* Responsive instead of a fixed 260x340px -- scales down on short/
     narrow viewports (the original complaint: the close button below
     the card got pushed off-screen) while keeping the card's 260:340
     aspect ratio. The 52vh figure is 260/340 of 68vh, i.e. "however
     wide the card would be if it were exactly 68% of viewport height". */
  width:min(300px,82vw,52vh);
  aspect-ratio:260/340;
  height:auto;
  cursor:pointer;
  position:relative;transform-style:preserve-3d;
  transition:transform .55s cubic-bezier(.4,0,.2,1);
  border-radius:12px;
  overflow:visible;
}
#token-viewer.token-viewer-army #token-flip-card{
  width:min(260px,60vw,48vh);
}
#token-flip-card.flipped{transform:rotateY(180deg);}
.token-face{
  position:absolute;inset:0;backface-visibility:hidden;-webkit-backface-visibility:hidden;
  border-radius:12px;overflow:hidden;
  box-shadow:0 8px 40px rgba(0,0,0,.8);
}
.token-front{background:#0a0a0a;}
.token-front img:not(.token-gem-img){width:100%;height:100%;object-fit:contain;}
/* Animated Legendary/Ghost sparkle overlay -- see startTokenSparkleAnim()
   in game.js. Sits above the token art, hidden by default; only shown
   (via .show, toggled in openTokenViewer()) for Legendary/Ghost units. */
#token-sparkle-canvas{
  position:absolute;inset:0;width:100%;height:100%;
  pointer-events:none;z-index:2;display:none;
}
#token-sparkle-canvas.show{display:block;}
.token-back{
  transform:rotateY(180deg);
  background:linear-gradient(160deg,#120c06,#0a0806,#0d0a04);
  display:flex;flex-direction:column;
  padding:14px 12px 32px;gap:5px;
  /* Unit image as faded background */
  background-size:contain;
  background-repeat:no-repeat;
  background-position:center 40%;
}
/* Ornate gold frame, replacing the old flat 1px border -- a separate
   pseudo-element (rather than a stylesheet background-image on .token-back
   itself) because .token-back's own background-image is set inline by JS
   for the faded unit-art backdrop (see openTokenViewer) and would win over
   anything declared here. Sits above #token-back-content (z-index:1) so the
   frame reads as a wraparound border over the card edges, pointer-events
   none so it never blocks clicks/scroll on the content underneath. */
.token-back::after{
  content:'';position:absolute;inset:0;border-radius:12px;
  background-image:url('assets/img/ui/token_back_frame.webp');
  background-size:100% 100%;background-repeat:no-repeat;background-position:center;
  pointer-events:none;z-index:2;
}
.token-back::before{
  content:'';position:absolute;inset:0;border-radius:12px;
  background:linear-gradient(160deg,rgba(18,12,6,.88),rgba(10,8,6,.82),rgba(13,10,4,.88));
  z-index:0;
}
/* Back-of-token text was uniformly tiny (7.5-9px) and several labels used
   very dark, low-contrast colors against the dark card background (stat
   labels, skill description, lore), making the whole back face hard to
   read at a glance. Sizes bumped up and dark colors lightened throughout;
   overflow-y:auto added as a safety net so the larger text never gets
   silently clipped by the card's fixed 340px height. */
#token-back-content{
  position:relative;z-index:1;display:flex;flex-direction:column;gap:5px;
  max-height:100%;overflow-y:auto;touch-action:pan-y;
  /* Shifted right slightly so text clears the ornate gold frame's left
     edge (see .token-back::after) instead of sitting flush against it. */
  padding-left:12px;padding-right:4px;
  /* Discrete themed scrollbar (Firefox) instead of the default OS chrome --
     thin gold-on-dark track matching the card's own palette. */
  scrollbar-width:thin;scrollbar-color:#7a5c28 transparent;
}
/* Same scrollbar treatment for WebKit/Blink (Chrome, Safari, Edge) --
   Firefox only honors the scrollbar-width/-color pair above. */
#token-back-content::-webkit-scrollbar{width:5px;}
#token-back-content::-webkit-scrollbar-track{background:transparent;}
#token-back-content::-webkit-scrollbar-thumb{background:#7a5c28;border-radius:3px;}
#token-back-content::-webkit-scrollbar-thumb:hover{background:#a07c38;}
#tbc-name{
  font-family:'Cinzel Decorative',serif;font-size:16px;font-weight:700;
  color:#ffe060;text-shadow:0 0 12px #ffaa00;
  text-align:center;letter-spacing:1px;
}
#tbc-job{
  font-family:'Cinzel',serif;font-size:10.5px;color:#c8a040;
  text-align:center;letter-spacing:2px;text-transform:uppercase;
  border-bottom:1px solid #3a2a10;padding-bottom:6px;margin-bottom:2px;
}
#tbc-stats{
  display:grid;grid-template-columns:1fr 1fr;gap:4px 8px;font-size:11px;
  color:#a8c060;font-family:'Cinzel',serif;
}
.tbc-stat{display:flex;justify-content:space-between;
  background:rgba(255,255,255,.04);border-radius:3px;padding:3px 6px;
}
.tbc-stat-label{color:#8fae5a;}
.tbc-stat-val{color:#e0c040;font-weight:700;}
/* Sleep/Stop resistance badges -- shares the free grid cell (see
   _tbcResistanceBadgesHTML's comment in game.js for why there's a free
   cell at all). Centered rather than space-between since there's no
   label/value pair here, just one or two icons. anti_sleep_icon.png /
   anti_slow_icon.png replace what used to be a hand-drawn inline SVG
   (a slashed eye) for sleep only -- Stop immunity had no badge at all
   before this. */
.tbc-stat-badges{justify-content:center;gap:5px;}
.tbc-status-badge{width:26px;height:26px;object-fit:contain;display:block;} /* developer request, Sep 2026: 100% larger (was 13px) -- restored after it was found reverted */
#tbc-skill{
  border:1px solid #3a2a10;border-radius:4px;padding:6px 8px;margin-top:2px;
  background:rgba(0,0,0,.3);
}
#tbc-skill-name{color:#d8b860;font-size:10.5px;font-weight:700;font-family:'Cinzel',serif;letter-spacing:.5px;}
/* MTG-style ability block -- one per named ability (see _renderAbilitiesHTML
   in game.js). Stacked with a divider between multiple abilities on the
   same card; the last block's divider is suppressed. */
.tbc-ability{padding-top:4px;margin-top:4px;border-top:1px solid #3a2a10;}
.tbc-ability:first-child{padding-top:0;margin-top:0;border-top:none;}
.tbc-ability-name{color:#d8b860;font-size:10.5px;font-weight:700;font-family:'Cinzel',serif;letter-spacing:.5px;}
.tbc-ability-cost{color:#8a9860;font-size:9px;font-style:italic;margin-top:1px;}
/* Engaging serif body font (was inheriting the page's plain default) at a
   bumped-up size, per developer request. EB Garamond pairs with the
   existing Cinzel headers rather than clashing with a sans body font. */
.tbc-ability-text{color:#a8c878;font-size:12px;margin-top:2px;line-height:1.5;font-family:'EB Garamond',serif;}
/* Key stat callouts (numbers like "+5 ATK", "2.2x", "50% HP") -- wrapped in
   this span by _highlightKeyStats() in game.js so a first-time player can
   scan a description and immediately spot the numbers that matter. */
.tbc-key-stat{color:#ffd54a;font-weight:700;}
#tbc-desc{color:#b8c090;font-size:9.5px;line-height:1.6;font-style:italic;margin-top:2px;}
#token-flip-hint{
  color:#8a9860;font-size:9px;letter-spacing:1px;font-family:'Cinzel',serif;
  animation:tokenHintBlink 1.8s ease-in-out infinite;
}
@keyframes tokenHintBlink{0%,100%{opacity:.5;}50%{opacity:1;}}
#token-viewer-gem-balance{
  display:flex;align-items:center;gap:5px;
  background:linear-gradient(180deg,#241a0e,#150f08);
  border:1px solid var(--pch-gold);border-radius:12px;
  padding:5px 12px;
  font-family:'Cinzel',serif;font-size:12px;font-weight:700;color:var(--pch-gold-bright);
}
.token-viewer-gem-icon{width:15px;height:15px;object-fit:contain;flex-shrink:0;}
#token-close-btn{
  background:linear-gradient(180deg,#1a1408,#100e06);
  border:1px solid #5a4a20;border-radius:4px;color:#c8a040;
  font-size:10px;font-family:'Cinzel',serif;letter-spacing:1px;
  padding:7px 22px;cursor:pointer;
}
#token-army-actions{
  display:none;gap:10px;flex-wrap:wrap;justify-content:flex-start;
}
#token-buy-gems-btn{
  background:linear-gradient(180deg,#6a3a9a,#3a1a5a);color:#f0e0ff;
  border:1px solid var(--pch-bronze);border-radius:4px;
  font-size:10px;font-family:'Cinzel',serif;letter-spacing:1px;
  padding:7px 18px;cursor:pointer;
}
#token-buy-gems-btn:disabled{opacity:.4;cursor:default;filter:grayscale(.5);}
/* Dimmed-but-still-tappable state when the player can't afford it -- see
   _refreshTokenArmyActionBtn(), which deliberately leaves this button
   enabled (not :disabled) so a tap still fires tokenViewerBuyForGems()
   and surfaces the "not enough Gems" prompt, rather than the tap just
   silently doing nothing the way a native disabled button would. */
#token-buy-gems-btn.unaffordable{opacity:.55;filter:grayscale(.4);}
/* Rarity-locked / collection-only state (see _refreshTokenArmyActionBtn +
   showGregorRarityLocked/GREGOR_ERRORS.collectionOnly) -- same
   still-tappable pattern as .unaffordable above, but visually distinct
   (cooler/darker tint + reduced opacity vs. the warm dim of just being
   unaffordable) so it reads as "not available yet" rather than "you can't
   afford it right now". */
#token-buy-gems-btn.locked{opacity:.5;filter:grayscale(.6) brightness(.85);}
#token-add-army-btn{
  background:linear-gradient(180deg,#4a7030,#2c4a18);color:#e8ffc8;
  border:1px solid var(--pch-bronze);border-radius:4px;
  font-size:10px;font-family:'Cinzel',serif;letter-spacing:1px;
  padding:7px 18px;cursor:pointer;
}
#token-add-army-btn:disabled{opacity:.4;cursor:default;filter:grayscale(.5);}
#token-add-army-btn.token-army-action-remove{
  background:linear-gradient(180deg,#8a3020,#5a1810);color:#ffd0c0;
}
#token-back-btn{
  background:linear-gradient(180deg,#1a1408,#100e06);
  border:1px solid #5a4a20;border-radius:4px;color:#c8a040;
  font-size:10px;font-family:'Cinzel',serif;letter-spacing:1px;
  padding:7px 18px;cursor:pointer;
}

/* ══ MISSION SCAN OVERLAY ════════════════════════════════════════ */
#mission-scan-overlay{
  display:none;position:fixed;inset:0;z-index:80;
  flex-direction:column;background:var(--pch-walnut-dark);
}
#mission-scan-overlay.show{display:flex;}
#mscan-header{
  display:flex;align-items:center;gap:8px;
  padding:11px 12px 9px;flex-shrink:0;
  background:linear-gradient(180deg,var(--pch-parchment-light),var(--pch-parchment));
  border-bottom:2px solid var(--pch-bronze);
}
#mscan-title{
  font-family:'Cinzel Decorative',serif;font-size:12px;color:var(--pch-walnut);
  letter-spacing:2px;flex:1;
}
#mscan-sub{color:var(--pch-ink-soft);font-size:11px;letter-spacing:.5px;font-family:'Cinzel',serif;font-weight:600;font-style:italic;}
#mscan-close-btn{
  background:linear-gradient(180deg,var(--pch-burgundy),var(--pch-burgundy-dark));
  border:1px solid #8a4040;border-radius:3px;color:#f0d8c8;
  font-size:9px;font-family:'Cinzel',serif;letter-spacing:.5px;font-weight:700;
  padding:5px 12px;cursor:pointer;
}
#mscan-close-btn:active{transform:translateY(1px);filter:brightness(.9);}
#mscan-board-wrap{
  flex:1;position:relative;overflow:hidden;
  background:radial-gradient(ellipse at 50% 30%,#3a2c1c,#15100a);touch-action:none;
  border-bottom:1px solid var(--pch-walnut);
}
#mscan-canvas{
  position:absolute;top:0;left:0;
  image-rendering:pixelated;image-rendering:crisp-edges;
}
#mscan-hint{
  position:absolute;bottom:6px;left:50%;transform:translateX(-50%);
  background:rgba(20,12,4,.78);color:var(--pch-gold-bright);font-size:11px;
  font-family:'Cinzel',serif;letter-spacing:.5px;font-weight:600;
  padding:5px 12px;border-radius:2px;pointer-events:none;
  white-space:nowrap;border:1px solid var(--pch-bronze);
}
#mscan-bottom-bar{
  display:flex;align-items:center;justify-content:space-between;
  padding:9px 10px;flex-shrink:0;gap:8px;
  background:linear-gradient(180deg,var(--pch-parchment),var(--pch-parchment-dark));
  border-top:2px solid var(--pch-bronze);
}
#mscan-enemy-list{
  display:flex;flex-wrap:wrap;gap:6px;flex:1;
}
#mscan-zoom-bar{
  display:flex;align-items:center;gap:4px;flex-shrink:0;
}
.mscan-zoom-btn{
  background:linear-gradient(180deg,var(--pch-bronze-light),var(--pch-bronze));
  border:1px solid var(--pch-walnut);border-radius:3px;color:#2a1c0c;
  font-size:12px;width:28px;height:28px;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  font-family:'Cinzel',serif;font-weight:700;
}
.mscan-zoom-btn:active{filter:brightness(.85);transform:translateY(1px);}
#mscan-zoom-label{color:var(--pch-ink-soft);font-size:8px;font-family:'Cinzel',serif;min-width:30px;text-align:center;}
.mscan-enemy-chip{
  display:flex;align-items:center;gap:8px;
  background:linear-gradient(180deg,var(--pch-parchment-light),var(--pch-parchment-dark));
  border:1px solid var(--pch-bronze);border-radius:6px;
  padding:8px 12px;cursor:pointer;transition:border-color .15s,box-shadow .15s,transform .15s;
  box-shadow:0 1px 3px rgba(40,28,8,.25);
}
.mscan-enemy-chip:hover{border-color:var(--pch-gold);box-shadow:0 0 8px var(--pch-gold)55,0 3px 7px rgba(30,18,6,.3);transform:translateY(-1px);}
.mscan-enemy-chip:active{transform:translateY(0);}
.mscan-enemy-chip-portrait{width:56px;height:56px;border-radius:3px;object-fit:contain;border:1px solid var(--pch-bronze);background:var(--pch-walnut-dark);}
.mscan-enemy-chip-info{font-size:12px;color:var(--pch-ink-soft);font-family:'Cinzel',serif;}
.mscan-enemy-chip-name{font-weight:700;color:var(--pch-burgundy);font-size:14px;}


/* ══ BATTLE INTRO MODAL (big Gregor) ════════════════════════════ */
#battle-intro-modal{
  display:none;position:absolute;inset:0;z-index:60;
  background:rgba(0,0,0,.88);align-items:center;justify-content:center;
  backdrop-filter:blur(3px);
}
#battle-intro-modal.show{display:flex;}
/* Gregor's own pre-battle intro (default state, no .bim-solemn) now uses
   a dedicated Gregor banner asset instead of the old plain gold/parchment
   border -- save the file as assets/img/ui/gregor_banner.webp (same 9-slice
   technique as the Shop/Chapter/Level banners: a square-ish frame image,
   thick painted border, transparent/fillable center). */
#bim-box{
  display:flex;flex-direction:column;align-items:center;gap:10px;
  width:86%;max-width:380px;
  background:linear-gradient(180deg,#18110a,#100c06);
  border-style:solid;border-width:18px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:18px;
  border-image-source:url('assets/img/ui/gregor_banner.webp');
  padding:20px 18px 18px;
  box-shadow:0 0 40px rgba(200,160,40,.18),0 8px 32px rgba(0,0,0,.9);
  animation:rewardPop .4s cubic-bezier(.22,.8,.42,1) both;
}
#bim-portrait{
  width:110px;height:110px;border:2px solid #7a5c28;border-radius:6px;
  overflow:hidden;background:#0a0806;image-rendering:pixelated;
}
#bim-portrait img{width:100%;height:100%;object-fit:cover;image-rendering:pixelated;}
#bim-name{
  font-family:'Cinzel',serif;font-size:11px;font-weight:700;
  color:#c8a040;letter-spacing:2px;
}
#bim-text{
  font-family:'Cinzel',serif;font-size:11px;color:#e8e0c8;
  text-align:center;line-height:1.7;min-height:80px;letter-spacing:.3px;
}
#bim-next-btn{
  background:linear-gradient(180deg,#1a2a0a,#0d1508);
  border:1px solid #4a6820;border-radius:4px;
  color:#a8c060;font-size:10px;font-family:'Cinzel',serif;
  letter-spacing:1px;padding:8px 26px;cursor:pointer;margin-top:4px;
}
/* Ashigami (and any other non-Gregor speaker that reuses #bim-box) now
   reuses the same Chapter/Level Select banner-card artwork as the
   necro-box family below, instead of a bespoke crimson look -- red,
   fitting "the Fallen Blade God" theme, distinct from necro-box's purple. */
#bim-box.bim-solemn{
  background:linear-gradient(180deg,#0e0507,#070304);
  border-image-source:url('assets/img/ui/shop_banner_red.webp');
  box-shadow:0 0 40px rgba(120,20,30,.16),0 8px 32px rgba(0,0,0,.9);
}
#bim-box.bim-solemn #bim-portrait{border-color:#4a1418;}
#bim-box.bim-solemn #bim-name{color:#a03030;letter-spacing:2.5px;}
#bim-box.bim-solemn #bim-text{color:#d8c8c8;letter-spacing:.4px;}
#bim-box.bim-solemn #bim-next-btn{
  background:linear-gradient(180deg,#2a0a0a,#150606);
  border:1px solid #601818;color:#c07070;
}
/* Tactician difficulty's pre-battle intro (Levels 1-8, dark banner per
   developer spec) -- still Gregor (unlike .bim-solemn's Ashigami), so
   only the frame/box treatment changes, not the portrait/name colors.
   Needs assets/img/ui/gregor_banner_dark.webp saved alongside the
   existing gregor_banner.png (same 9-slice technique). */
#bim-box.bim-tactician{
  background:linear-gradient(180deg,#120808,#0a0505);
  border-image-source:url('assets/img/ui/gregor_banner_dark.webp');
  box-shadow:0 0 40px rgba(200,40,30,.2),0 8px 32px rgba(0,0,0,.9);
}
#bim-box.bim-tactician #bim-name{color:#c85030;}
#bim-box.bim-tactician #bim-text{color:#e8d8c8;}
#bim-box.bim-tactician #bim-next-btn{
  background:linear-gradient(180deg,#2a1408,#150a04);
  border:1px solid #703818;color:#d09060;
}

/* ══ CONFIRM MODAL (back-to-menu, deploy "still have slots/points left?", chapter-purchase, AND shop-purchase) ══ */
#confirm-modal,#deploy-confirm-modal,#ag-notice-modal,#chapter-unlock-modal,#shop-confirm-modal,#delete-account-modal{
  display:none;position:fixed;inset:0;z-index:80;
  background:rgba(0,0,0,.72);align-items:center;justify-content:center;
  backdrop-filter:blur(2px);
}
#confirm-modal.show,#deploy-confirm-modal.show,#ag-notice-modal.show,#chapter-unlock-modal.show,#shop-confirm-modal.show,#delete-account-modal.show{display:flex;}
#confirm-box,#deploy-confirm-box,#ag-notice-box,#chapter-unlock-box,#shop-confirm-box,#delete-account-box{
  width:82%;max-width:320px;
  background:linear-gradient(180deg,#241a0e,#150f08);
  border:1px solid var(--pch-gold);border-radius:8px;
  padding:18px 16px 16px;text-align:center;
  box-shadow:0 8px 32px rgba(0,0,0,.85);
  animation:rewardPop .25s cubic-bezier(.22,.8,.42,1) both;
}
#confirm-title,#deploy-confirm-title,#ag-notice-title,#chapter-unlock-title,#shop-confirm-title,#delete-account-title{font-family:'Cinzel Decorative',serif;font-size:13px;color:var(--pch-gold-bright);letter-spacing:1px;margin-bottom:8px;font-weight:700;}
#confirm-text,#deploy-confirm-text,#ag-notice-text,#chapter-unlock-text,#shop-confirm-text,#delete-account-text{font-family:'Cinzel',serif;font-size:11px;color:#e8e0c8;line-height:1.5;margin-bottom:14px;}
#chapter-unlock-gregor{
  font-family:'Cinzel',serif;font-style:italic;font-size:10px;color:#c8a868;
  line-height:1.5;margin:-6px 0 14px;padding:8px 10px;
  background:rgba(0,0,0,.25);border-left:2px solid var(--pch-gold);border-radius:3px;
}
#confirm-btns,#deploy-confirm-btns,#ag-notice-btns,#chapter-unlock-btns,#shop-confirm-btns,#delete-account-btns{display:flex;gap:6px;justify-content:center;flex-wrap:wrap;}
#confirm-no-btn,#confirm-yes-btn,#confirm-redeploy-btn,#deploy-confirm-no-btn,#deploy-confirm-yes-btn,#ag-notice-ok-btn,#chapter-unlock-no-btn,#chapter-unlock-yes-btn,#shop-confirm-no-btn,#shop-confirm-yes-btn,#delete-account-cancel-btn,#delete-account-confirm-btn{font-family:'Cinzel',serif;font-size:10px;letter-spacing:.5px;padding:8px 12px;border-radius:4px;cursor:pointer;font-weight:700;}
#confirm-no-btn,#deploy-confirm-no-btn,#chapter-unlock-no-btn,#shop-confirm-no-btn,#delete-account-cancel-btn{background:linear-gradient(180deg,var(--pch-bronze-light),var(--pch-bronze));border:1px solid var(--pch-gold);color:#2a1c0c;}
#confirm-no-btn:hover,#deploy-confirm-no-btn:hover,#chapter-unlock-no-btn:hover,#shop-confirm-no-btn:hover,#delete-account-cancel-btn:hover{filter:brightness(1.1);}
#confirm-redeploy-btn{background:linear-gradient(180deg,#4a7040,#2c4626);border:1px solid #6a9058;color:#e8f0d8;}
#confirm-redeploy-btn:hover{filter:brightness(1.1);}
#confirm-yes-btn,#deploy-confirm-yes-btn,#ag-notice-ok-btn,#delete-account-confirm-btn{background:linear-gradient(180deg,var(--pch-burgundy),var(--pch-burgundy-dark));border:1px solid #8a4040;color:#f0d8c8;}
#confirm-yes-btn:hover,#deploy-confirm-yes-btn:hover,#ag-notice-ok-btn:hover,#delete-account-confirm-btn:hover{filter:brightness(1.1);}
#delete-account-confirm-btn:disabled{opacity:.6;filter:none;cursor:default;}
/* Chapter-unlock's and shop-purchase's "Yes" buttons get the gold/affirmative
   color (not the burgundy "Yes, Exit" treatment above) since purchasing is a
   positive action, not a destructive one. */
#chapter-unlock-yes-btn,#shop-confirm-yes-btn{background:linear-gradient(180deg,var(--pch-gold),#8a6820);border:1px solid var(--pch-bronze);color:#241a0e;}
#chapter-unlock-yes-btn:hover,#shop-confirm-yes-btn:hover{filter:brightness(1.1);}
/* Small destructive action button inside a settings row (currently just
   "Delete Account") -- same burgundy palette as the destructive modal
   buttons above, scaled down to sit inline in a settings list row. */
.settings-danger-btn{
  flex-shrink:0;
  font-family:'Cinzel',serif;font-size:10px;letter-spacing:.5px;font-weight:700;
  padding:8px 14px;border-radius:5px;cursor:pointer;
  background:linear-gradient(180deg,var(--pch-burgundy),var(--pch-burgundy-dark));
  border:1px solid #8a4040;color:#f0d8c8;
}
.settings-danger-btn:hover{filter:brightness(1.1);}

/* ══ SHOP ITEM INFO MODAL ═════════════════════════════════════════ */
#shop-info-modal{
  display:none;position:fixed;inset:0;z-index:85;
  background:rgba(0,0,0,.72);align-items:center;justify-content:center;
  backdrop-filter:blur(2px);
}
#shop-info-modal.show{display:flex;}
#shop-info-box{
  width:85%;max-width:340px;
  background:linear-gradient(180deg,#241a0e,#150f08);
  border:1px solid var(--pch-gold);border-radius:8px;
  padding:20px 18px 16px;text-align:left;
  box-shadow:0 8px 32px rgba(0,0,0,.85);
  animation:rewardPop .25s cubic-bezier(.22,.8,.42,1) both;
}
#shop-info-title{font-family:'Cinzel Decorative',serif;font-size:15px;color:var(--pch-gold-bright);letter-spacing:1px;margin-bottom:10px;font-weight:700;text-align:center;}
#shop-info-text{font-family:'Cinzel',serif;font-size:11px;color:#e8e0c8;line-height:1.6;margin-bottom:16px;}
#shop-info-close-btn{
  display:block;width:100%;
  font-family:'Cinzel',serif;font-size:10px;letter-spacing:.5px;padding:9px 14px;border-radius:4px;cursor:pointer;font-weight:700;
  background:linear-gradient(180deg,var(--pch-gold),#8a6820);border:1px solid var(--pch-bronze);color:#241a0e;
}
#shop-info-close-btn:hover{filter:brightness(1.1);}
#shop-info-close-btn:active{transform:translateY(1px);}
/* Secondary action (e.g. "View Coin Packs" on a Not Enough Coins alert
   -- see showGameAlert()'s optional actionLabel/actionFn params in
   game.js). Sits above the close button and takes the prominent gold
   treatment when present, which demotes the close button itself to a
   muted outline via .shop-info-btn-secondary (toggled by the same
   function) so the action reads as the primary choice. Hidden by
   default -- every other showGameAlert() caller across the game never
   sees this button at all. */
#shop-info-action-btn{
  display:block;width:100%;margin-bottom:8px;
  font-family:'Cinzel',serif;font-size:10px;letter-spacing:.5px;padding:9px 14px;border-radius:4px;cursor:pointer;font-weight:700;
  background:linear-gradient(180deg,var(--pch-gold),#8a6820);border:1px solid var(--pch-bronze);color:#241a0e;
}
#shop-info-action-btn:hover{filter:brightness(1.1);}
#shop-info-action-btn:active{transform:translateY(1px);}
#shop-info-close-btn.shop-info-btn-secondary{
  background:none;border:1px solid var(--pch-bronze);color:#d8c8a8;
}
#shop-info-close-btn.shop-info-btn-secondary:hover{filter:brightness(1.2);}

/* ══ MENU BUTTON ══════════════════════════════════════════════════ */
/* Standalone boxed button, top-right of the whole screen (not inside
   the header banner) -- moved out to its own element in the HTML so
   it can float independently once the header banner narrows down to
   just the title. Same dark-walnut/gold styling as the generic
   .pch-btn buttons used elsewhere. */
#place-menu-btn{
  position:absolute;top:10px;right:12px;z-index:20;
  background:linear-gradient(180deg,var(--pch-walnut),var(--pch-walnut-dark));
  border:1px solid var(--pch-bronze);border-radius:4px;
  color:var(--pch-gold-bright);font-size:11px;font-family:'Cinzel',serif;
  letter-spacing:.5px;padding:7px 14px;cursor:pointer;font-weight:700;
  box-shadow:0 2px 6px rgba(0,0,0,.4);
}
#place-menu-btn:hover{filter:brightness(1.15);}
#place-menu-btn:active{transform:translateY(1px);}
/* #battle-menu-btn (battle screen's equivalent) is styled up in the
   ══ BATTLE SCREEN ══ section alongside #battle-top-controls, so it and
   #place-menu-btn stay visually identical without a duplicate rule here
   -- see the cascade note about scattered duplicate selectors elsewhere
   in this file; keeping this one definition avoids repeating that
   mistake. */

/* ══ LEVEL CARD FOOTER BUTTONS ═══════════════════════════════════ */
.lvl-footer-btns{display:flex;align-items:center;gap:8px;}
.lvl-scan-btn{
  background:linear-gradient(180deg,var(--pch-bg-light),var(--pch-bg-dark));
  border:1px solid var(--pch-border);border-radius:3px;color:var(--pch-ink-soft);
  font-size:8px;font-family:'Cinzel',serif;letter-spacing:.5px;font-weight:600;
  padding:4px 8px;cursor:pointer;
  box-shadow:0 1px 0 rgba(255,255,255,.3) inset,var(--pch-shadow-soft);
  transition:filter .12s,box-shadow .12s,transform .08s;
}
.lvl-scan-btn:hover:not(:disabled){filter:brightness(1.08);box-shadow:0 0 5px var(--pch-gold)55,0 1px 0 rgba(255,255,255,.3) inset;}
.lvl-scan-btn:active:not(:disabled){transform:translateY(1px);filter:brightness(.94);}
.lvl-scan-btn-fire{border-color:#8a3010;color:#7a3010;background:linear-gradient(180deg,#e0c8a0,#c8a878);}
.lvl-scan-btn-castle{border-color:#5a4a88;color:#4a3c70;background:linear-gradient(180deg,#d8d0e8,#c0b8d4);}
.lvl-scan-btn-dungeon{border-color:#7a5a18;color:#5a4012;background:linear-gradient(180deg,#cdbc90,#b8a478);}

/* ══ LOCKED LEVEL CARD ═════════════════════════════════════════ */
/* Explorer/Tactician stamp (Levels 1-8 only, see renderDifficultyBadges()
   in game.js) -- a ribbon across the top of the level's banner announcing
   Tactician is available/cleared for that level, per developer spec ("a
   message over the level banner"). Now the real banner artwork the
   developer supplied (assets/img/ui/badge_tactician_available.webp /
   badge_tactician_cleared.png) rather than a CSS-drawn ribbon -- swap the
   <img>'s src between the two rather than toggling a color variant.
   z-index above .lvl-card-inner (1) so it always sits on top of the card
   art; sized off the card's own width so it scales with the grid rather
   than a fixed px value. Positioned to crest slightly above the card's
   top edge and bleed a little past the left edge, matching the medallion
   overlapping the card's own frame in the reference mockup. */
.lvl-difficulty-badge{
  position:absolute;top:-9%;left:-5%;width:92%;z-index:6;
  pointer-events:none;
  filter:drop-shadow(0 3px 6px rgba(0,0,0,.6));
}
.lvl-card-locked{filter:grayscale(.6);opacity:.7;cursor:default;}
.lvl-card-locked:hover{box-shadow:var(--pch-shadow);}
/* ══ LOCKED CHAPTER CARD ═══════════════════════════════════════ */
/* Same treatment as .lvl-card-locked above, scoped to Chapter Select's
   .chp-card instead of Level Select's .lvl-card. Card stays clickable --
   selectChapter() in game.js is what decides whether to show a comedic
   Gregor refusal or the purchase-prompt modal, this class is purely
   visual. */
.chp-card-locked{filter:grayscale(.6) brightness(.75);opacity:.72;}
.chp-card-locked:hover{transform:none;box-shadow:0 4px 14px rgba(0,0,0,.5);filter:grayscale(.6) brightness(.75);}
/* ══ LOCK ICON (centered on the banner, both card types) ═══════ */
/* Pure-CSS ::after -- both .lvl-card and .chp-card are already
   position:relative, so this centers cleanly with no HTML/JS changes
   needed per-card. Only shows on the locked variant of either card. */
.lvl-card-locked::after,.chp-card-locked::after{
  content:'';position:absolute;top:50%;left:50%;
  width:52px;height:52px;transform:translate(-50%,-50%);
  background:url('assets/img/ui/level_lock.webp') center/contain no-repeat;
  filter:drop-shadow(0 3px 8px rgba(0,0,0,.7));
  z-index:5;pointer-events:none;
}
/* ══ CHAPTER "READY TO UNLOCK" STATE (Sep 2026) ══════════════════ */
/* Previous chapter cleared, but this one not bought yet. Player feedback:
   the old greyed-out look made it seem the last win hadn't counted. So:
   full colour + a soft gold glow, the lock still shown but wiggling
   every few seconds, and a "Ready to unlock!" tag under it. Set by
   _renderChapterLockUI() in game.js (never together with
   .chp-card-locked). Card stays clickable -> purchase prompt as before. */
.chp-card-ready{box-shadow:0 0 0 2px rgba(255,210,110,.55),0 0 18px rgba(255,190,80,.35);}
.chp-card-ready::after{
  content:'';position:absolute;top:50%;left:50%;
  width:52px;height:52px;transform:translate(-50%,-50%);
  background:url('assets/img/ui/level_lock.webp') center/contain no-repeat;
  filter:drop-shadow(0 3px 8px rgba(0,0,0,.7)) drop-shadow(0 0 6px rgba(255,200,90,.6));
  z-index:5;pointer-events:none;transform-origin:50% 20%;
  animation:chpLockWiggle 2.8s ease-in-out infinite;
}
.chp-card-ready::before{
  content:'Ready to unlock!';position:absolute;top:calc(50% + 32px);left:50%;
  transform:translateX(-50%);white-space:nowrap;z-index:5;pointer-events:none;
  padding:3px 10px;border-radius:999px;
  background:rgba(20,10,4,.82);border:1px solid rgba(255,210,110,.7);
  color:#ffd97a;font-size:clamp(10px,3.4cqw,15px);font-weight:700;letter-spacing:.5px;
  text-shadow:0 1px 2px #000;
}
@keyframes chpLockWiggle{
  0%,70%,100%{transform:translate(-50%,-50%) rotate(0deg);}
  74%{transform:translate(-50%,-50%) rotate(-14deg);}
  78%{transform:translate(-50%,-50%) rotate(12deg);}
  82%{transform:translate(-50%,-50%) rotate(-9deg);}
  86%{transform:translate(-50%,-50%) rotate(6deg);}
  90%{transform:translate(-50%,-50%) rotate(-3deg);}
  94%{transform:translate(-50%,-50%) rotate(0deg);}
}
@media (prefers-reduced-motion:reduce){.chp-card-ready::after{animation:none;}}
.lvl-tag-locked{
  border-color:var(--pch-border-dark);color:var(--pch-ink-faint);
  background:var(--pch-bg-dark);letter-spacing:1px;
}
.lvl-scan-btn:disabled,.lvl-play.lvl-play-disabled{
  opacity:.5;pointer-events:none;
}

/* ══ ROSTER CARD INSPECT (ZOOM) BUTTON ═══════════════════════════ */
/* Same round corner-badge treatment/position as the Army Builder's
   selected-unit "remove" button, just a magnifying glass instead of a
   minus sign since tapping this only zooms in to inspect the unit --
   it doesn't remove or affect placement at all. */
.rc-inspect-btn{
  position:absolute;top:3px;left:3px;z-index:2;width:16px;height:16px;line-height:1;
  border-radius:50%;border:1px solid var(--pch-gold);background:rgba(0,0,0,.75);
  color:var(--pch-gold-bright);font-size:9px;cursor:pointer;
  display:flex;align-items:center;justify-content:center;padding:0;
}
.rc-inspect-btn:hover,.rc-inspect-btn:active{filter:brightness(1.25);}

/* ══ GREGOR AUTO-FADE (tips) ═════════════════════════════════════ */
#oldman-overlay.tip-mode{pointer-events:none;}
/* Dismiss link stays visible even in tip-mode -- every Gregor message
   (taunts/tips included) needs a manual way to close it, since the
   bubble can otherwise sit on top of a button until the 5s auto-fade
   or the player's next click elsewhere clears it (see dismissOldMan()/
   gregClearTip() in game.js). */
#oldman-overlay.fading{animation:oldmanFadeOut .8s forwards;}
@keyframes oldmanFadeOut{to{opacity:0;transform:translateY(20px);}}

/* ══ TOKEN DIAMONDS ══════════════════════════════════════════════ */
#token-diamond-front-wrap{
  position:absolute;bottom:3px;left:50%;transform:translateX(-50%);
  display:flex;flex-direction:column;align-items:center;z-index:10;
  pointer-events:none;
}
#token-diamond-back-wrap{
  position:absolute;bottom:3px;left:50%;transform:translateX(-50%);
  display:flex;flex-direction:column;align-items:center;z-index:10;
  pointer-events:none;
}
#diamond-glow-canvas{
  position:absolute;bottom:0px;left:50%;transform:translateX(-50%);
  width:48px;height:48px;pointer-events:none;z-index:9;
}
/* Real gem image */
.token-gem-img{
  width:46px;height:46px;
  object-fit:contain;
  position:relative;z-index:3;
  animation:gemBreath 1.8s ease-in-out infinite alternate;
  filter:drop-shadow(0 0 10px currentColor);
}
@keyframes gemBreath{
  from{transform:scale(1);  filter:drop-shadow(0 0 8px  #fff8) brightness(1);}
  to{  transform:scale(1.1);filter:drop-shadow(0 0 22px #fffc) brightness(1.3);}
}
/* Hide old CSS diamond by default — JS replaces with img */
.token-diamond{display:none;}
.diamond-bronze{color:#cd7f32;}
.diamond-silver{color:#c0d8ff;}
.diamond-gold{color:#ffd700;}
/* The gem sits outside both card faces (so it can overhang the bottom
   edge without being clipped -- see the token-flip-card HTML comment),
   which means by default it stays visible on the BACK face too and
   sits on top of the lore text down there. Hide it once the card is
   flipped -- the back face doesn't need the rarity gem repeated on it. */
#token-flip-card.flipped #token-diamond-front-wrap{display:none;}
.diamond-amethyst{color:#c060ff;}
.diamond-opal{color:#e0f0ff;}
.diamond-carbon{color:#808080;}

/* ══ ROSTER CARD VIEW BAR ════════════════════════════════════════ */
.rc-view-bar{
  width:100%;text-align:center;font-size:7px;color:#6a9830;
  letter-spacing:.5px;font-family:'Cinzel',serif;
  padding:2px 0;border-top:1px solid #1e2e0c;
  cursor:pointer;margin-top:auto;
  background:rgba(0,0,0,.3);border-radius:0 0 3px 3px;
}
.rc-view-bar:hover,.rc-view-bar:active{color:#a8d060;background:rgba(0,255,0,.06);}


/* ══ LEVEL CARD INNER — make it look clickable but only DEPLOY fires selectLevel ══ */
.lvl-card-inner{cursor:default;}
.lvl-play{cursor:pointer;}

/* Note: the old dedicated "Level 5 Goblin Wastes card styles" block has
   been removed -- same reason as Castle/Dungeon above. Its
   #screen-level .lvl-badge-goblin rule was ID-scoped (higher specificity
   than a plain class), so it was silently overriding the new flag-image
   background for every goblin-badged card; .lvl-name-goblin was fighting
   the contrast fix the same way. The rest (scan-btn-goblin, play-goblin,
   footer-goblin) were already fully dead, unreferenced by any current
   markup. */


/* ══ REINFORCEMENT ROSTER BANNER (Levels 5,7,8,9,11-21) ═══════════
   Redesigned to match the initial "Deploy Your Forces" banner as
   closely as possible: a narrow, content-width, partially-transparent
   floating bar (not a full-width opaque strip) that sits bottom-center
   over the map instead of encasing the whole bottom edge, so the
   maximum amount of battlefield stays visible. Row layout mirrors the
   deploy screen's three-panel footer -- text on the left, the roster
   picker in the middle, deployment points/slots on the right (see
   #l5-reinforce-pts, populated by _updateReinforcePtsUI() in game.js)
   -- instead of stacking title/sub/roster in three separate full-width
   rows with a heavy gregor_banner.png panel encasing the roster alone. */
#l5-reinforce-banner{
  position:absolute;bottom:8px;left:50%;transform:translateX(-50%);
  z-index:30;width:fit-content;max-width:94%;
  display:none;
  flex-direction:row;align-items:stretch;gap:12px;
  background:rgba(20,14,8,.6);
  border:1px solid rgba(200,160,80,.45);
  border-radius:7px;
  box-shadow:0 3px 12px rgba(0,0,0,.5);
  padding:9px 12px;
  /* BUGFIX: the redesign dropped this, so the banner's own background/
     padding -- which sits directly over the map, often right over the
     gold reinforcement zone itself -- silently swallowed every tap
     intended for a map tile underneath it (map taps landed on the
     banner's div instead of the canvas, so nothing ever reached
     _lXHandleReinforceDeploy: no unit placed, no points spent, no FX).
     pointer-events is inherited, so re-enabling it just on the roster
     picker below (the only part that actually needs to be clickable)
     restores tap-through everywhere else, same as the pre-redesign
     banner did. */
  pointer-events:none;
}
#l5-reinforce-banner.show{display:flex;}
#l5-reinforce-text{
  flex:0 0 auto;max-width:225px;min-width:0;
  display:flex;flex-direction:column;justify-content:center;
  padding-right:12px;border-right:1px solid rgba(200,160,80,.3);
}
#ag-sacrifice-prompt{
  position:absolute;top:8px;left:8px;right:8px;z-index:35;
  background:linear-gradient(180deg,rgba(40,4,36,.95),rgba(20,2,18,.94));
  border:2px solid #c828b4;border-radius:8px;
  box-shadow:0 0 14px rgba(200,40,180,.5);
  padding:8px 34px 8px 12px;text-align:center;
  color:#f0c8ec;font-family:'Cinzel',serif;font-weight:700;font-size:12px;
  display:flex;flex-direction:column;align-items:center;gap:6px;
}
#ag-sacrifice-prompt.collapsed{
  padding:4px 10px;width:max-content;left:8px;right:auto;
}
#ag-sac-chip{cursor:pointer;font-size:11px;white-space:nowrap;}
#ag-sac-close-btn{
  position:absolute;top:6px;right:6px;
  background:rgba(0,0,0,.4);border:1px solid #c828b4;color:#f0c8ec;
  width:20px;height:20px;line-height:18px;border-radius:50%;
  font-size:11px;cursor:pointer;padding:0;
}
#ag-sac-close-btn:hover{filter:brightness(1.3);}
#ag-sac-text{line-height:1.4;}
#ag-sac-lockin-btn{
  font-family:'Cinzel',serif;font-size:11px;font-weight:700;letter-spacing:.5px;
  padding:6px 14px;border-radius:5px;cursor:pointer;
  background:linear-gradient(180deg,#4a7040,#2c4626);border:1px solid #6a9058;color:#e8f0d8;
}
#ag-sac-lockin-btn:hover{filter:brightness(1.15);}
#l5-reinforce-title{
  color:var(--pch-gold-bright);font-family:'Cinzel',serif;font-weight:700;
  font-size:15.75px;letter-spacing:1.3px;text-transform:uppercase;
  text-shadow:0 0 8px rgba(0,0,0,.6);
  line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
#l5-reinforce-sub{
  color:#c8b888;font-size:13.5px;letter-spacing:.2px;margin-top:3px;line-height:1.6;
}

/* ══ REINFORCEMENT ROSTER PICKER -- no more heavy gregor_banner.png
   panel-within-a-panel; the outer #l5-reinforce-banner is now the only
   frame (partially transparent, same as the deploy screen's floating
   footer panels), so the roster picker itself is just bare content --
   "no extra UI encasing the banners" per developer request, keeping as
   much of the map visible as possible. ══════════════════════════════ */
#l5-reinforce-panel{
  display:flex;align-items:center;gap:7.5px;pointer-events:auto;
  flex:1;min-width:0;padding:0;
}
#l5-reinforce-panel .roster-scroll-btn{height:168px;width:36px;font-size:21px;}
#l5-reinforce-roster{
  display:flex;gap:9px;flex-wrap:nowrap;flex:1;min-width:0;
  overflow-x:auto;scroll-behavior:smooth;padding:3px;touch-action:pan-x;
  scrollbar-width:none;
}
#l5-reinforce-roster::-webkit-scrollbar{display:none;}
/* BUGFIX/per developer request: this used to scope its own smaller
   card/portrait sizes (58px/42px), independently tuned from the initial
   deploy screen's roster -- which is exactly how the two ended up
   visibly different sizes ("should just be the same as the initial
   deployment banner"). Dropping the .roster-card override entirely lets
   it inherit the SAME shared .roster-card rule the initial deploy
   roster already uses (126px, or 84px at the tight phone tier -- see
   below), so there's only one card size definition for both banners to
   drift apart from, not two. The portrait override stays, but now
   matches #roster-section's own portrait override (117px) exactly rather
   than a separately-chosen smaller value. Both scaled 50% up from their
   original values (84px/56px card, 78px portrait) per developer
   request, alongside everything else in both banners. */
#l5-reinforce-roster img.rc-portrait{max-width:117px;}
/* Reinforcement roster cards now reuse the exact same .roster-card /
   .rc-portrait / .rc-name classes as the initial "Deploy Your Forces"
   picker (see #roster-section above) instead of their own bespoke
   card style -- per developer request, the reinforcement picker should
   look and behave like "the same deployment picker, again", not a
   separate-looking widget. The old cost/copies-remaining number badges
   are gone entirely (they weren't self-explanatory in play and the main
   deploy roster never had them either). */
.l5-reinforce-empty{font-size:13.5px;color:#c8b888;font-style:italic;padding:6px 0;}

/* Deployment points/slots badge -- right-hand side, mirroring the
   initial deploy banner's #place-slots-panel (points/slots go to the
   right, any extra text to the left). Populated by
   _updateReinforcePtsUI() in game.js. */
#l5-reinforce-pts{
  flex:0 0 auto;min-width:66px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:3px;padding-left:12px;border-left:1px solid rgba(200,160,80,.3);
}
#l5-reinforce-pts-label{
  font-family:'Cinzel',serif;font-size:9.75px;font-weight:700;letter-spacing:1px;
  color:var(--pch-gold-bright);text-transform:uppercase;text-align:center;
}
#l5-reinforce-pts-val{
  /* Same big/gold/decorative treatment as .pts-num on the initial deploy
     screen (see #place-slots-panel .pts-num) -- reusing the established
     #reward-coins-amount look, not inventing a new one. This element is
     always just a bare number/ratio (never mixed with label words, in
     either slots or cost mode -- see _updateReinforcePtsUI() in game.js),
     so unlike the deploy screen's cost-mode text, the whole element gets
     the treatment directly rather than needing an inner span. */
  font-family:'Cinzel Decorative',serif;font-size:28px;font-weight:700;
  color:#ffe060;text-shadow:0 0 10px #ffaa00;text-align:center;white-space:nowrap;line-height:1;
}

/* ══ ARMY BUILDER / EDITOR / SELECT SCREENS ═══════════════════════ */
#screen-army-builder,#screen-army-editor,#screen-army-select{
  display:none;flex-direction:column;flex:1;position:relative;
  height:100%;overflow:hidden;
}
/* Dedicated Army Builder background (same treatment as the Shop's own
   #shop-bg -- full painted artwork, cover-fit, dark gradient overlay on
   top for text legibility). Save the new background as
   assets/img/backgrounds/army_builder_bg.webp. */
#army-builder-bg,#army-editor-bg,#army-select-bg{
  position:absolute;inset:0;z-index:0;
  background:url('assets/img/backgrounds/army_builder_bg.webp') center/cover no-repeat;
}
#army-builder-bg::after,#army-editor-bg::after,#army-select-bg::after{
  content:'';position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(30,18,8,.18),rgba(20,12,4,.38));
}
#army-builder-content,#army-select-content{
  position:relative;z-index:1;display:flex;flex-direction:column;flex:1;
  padding:10px 6px 16px;overflow:hidden;
  width:100%;min-height:0;
}
/* #army-editor-content (build/edit a single army) split out from the
   group above -- was sharing #army-builder-content/#army-select-content's
   rule, which pins header/filters/footer and scrolls only the inner
   .army-editor-roster-grid. That nested-scroll pattern works fine for
   Chapter/Level Select's own short headers, but Army Editor's pinned
   elements (header + the 10-slot selected-units grid + the filter row +
   the Save Army footer button) are collectively tall enough that on a
   real phone they can eat most of the available height, squeezing the
   "scrollable" roster grid down to almost nothing -- which reads as
   "won't scroll at all" even though the CSS is technically correct.
   This also explains why it worked before the topbar-cutoff fix: back
   then the whole PAGE could grow taller than the viewport and scrolled
   as one block (header, slots, filters, grid, footer, all together) --
   capping the screen to exactly one viewport tall exposed this. Fix:
   stop trying to keep header/filters/footer pinned above a separately-
   scrolling inner grid, and instead let this ENTIRE block scroll
   together as one unit, same as Title/Brawler/Shop/Collection above. */
#army-editor-content{
  position:relative;z-index:1;display:flex;flex-direction:column;flex:1;
  padding:10px 6px 16px;
  width:100%;min-height:0;overflow-y:auto;touch-action:pan-y;
  /* Hide the OS-level scrollbar track -- visible on large screens/desktop
     Chrome where this box actually has overflow to scroll. Same
     convention already used for #chp-cards/#lvl-cards/#shop-cards. */
  scrollbar-width:none;
}
#army-editor-content::-webkit-scrollbar{display:none;}
#army-builder-header,#army-editor-header,#army-select-header{
  position:relative;flex-shrink:0;padding:6px 0 10px;text-align:center;
}
.army-back-btn{
  position:absolute;top:0;left:0;z-index:2;
  background:linear-gradient(180deg,var(--pch-walnut),var(--pch-walnut-dark));
  color:var(--pch-gold-bright);border:1px solid var(--pch-bronze);border-radius:3px;
  padding:4px 9px;font-size:9px;font-family:'Cinzel',serif;letter-spacing:.5px;cursor:pointer;
}
/* Army Builder/Editor/Select back button now reuses the exact same
   banner border-image treatment as the Shop's back button (see
   #screen-shop .army-back-btn above), per developer request to bring
   Shop's panel/banner art over to the Army Builder. */
#screen-army-builder .army-back-btn,
#screen-army-editor .army-back-btn,
#screen-army-select .army-back-btn{
  background:none;border-style:solid;border-width:14px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:14px;
  border-image-source:url('assets/img/ui/shop_banner_red.webp');
  color:var(--pch-gold-bright);padding:5px 12px;
}
/* Title switched from a near-invisible flat walnut-brown (blended
   straight into the background art behind it) to a shiny dual-tone gold
   -- a background-clip:text gradient running bright gold at the top down
   to a deeper bronze-gold at the bottom for a metallic/embossed look,
   plus a dark stroke-ish text-shadow stack underneath so it stays
   legible over any part of the busy background art, and a soft gold
   glow on top for the "shiny" finish. -webkit-text-fill-color:transparent
   is required alongside background-clip:text for the gradient to
   actually show through in WebKit; color is kept as a solid gold
   fallback for any browser that doesn't support text-clip gradients. */
#army-builder-title,#army-select-title{
  font-family:'Cinzel Decorative',serif;font-size:16px;letter-spacing:2px;
}
/* #army-builder-sub removed per developer request (Army Builder header no
   longer shows the "Build up to 5 armies..." line) -- #army-select-sub
   is a separate screen (the pre-battle army-choice gate) and keeps its
   subtitle. */
#army-select-sub{
  font-family:'Cinzel',serif;font-size:9px;letter-spacing:.5px;color:var(--pch-ink-soft);
  font-style:italic;margin-top:2px;
}
/* Gem balance chip -- top-right of the Army Builder/Editor header
   (mirrors the back button's top-left placement), so the player can
   always see what they have to spend while browsing units or buying a
   copy with Gems. Reuses .player-bar-gems so it updates automatically
   from the same updatePlayerBarUI() call as the Title/Shop screens. */
.ab-gem-balance{
  position:absolute;top:0;right:0;z-index:2;
  display:flex;align-items:center;gap:4px;
  background:linear-gradient(180deg,#241a0e,#150f08);
  border:1px solid var(--pch-gold);border-radius:12px;
  padding:5px 10px;
  font-family:'Cinzel',serif;font-size:11px;font-weight:700;color:var(--pch-gold-bright);
}
.ab-gem-balance-icon{width:14px;height:14px;object-fit:contain;flex-shrink:0;}
/* Army EDITOR header only: the top Save button (see .army-save-btn-top)
   used to share the exact same top:0;right:0 spot as .ab-gem-balance,
   sitting directly on top of it and hiding the player's Gem balance
   entirely -- per developer request, wrapped both in a flex group
   instead of a hardcoded pixel offset (which would've broken again the
   moment the Gem count grew an extra digit). Scoped to
   .army-header-right-group specifically so Army Builder/Select's own
   gem balance (same .ab-gem-balance class, no save button alongside it
   there) keeps its original simple position:absolute behavior below,
   untouched.
*/
.army-header-right-group{
  position:absolute;top:0;right:0;z-index:2;
  display:flex;align-items:center;gap:6px;
}
.army-header-right-group .ab-gem-balance,
.army-header-right-group .army-save-btn-top{
  position:static;top:auto;right:auto;
}
#army-select-hint{
  display:none;text-align:center;font-family:'Cinzel',serif;font-size:9px;
  color:#a04020;font-style:italic;margin-bottom:8px;flex-shrink:0;
}

/* -- Army slot list (shared: Builder list + Select gate) -- */
.army-slot-list{
  flex:1;overflow-y:auto;touch-action:pan-y;display:flex;flex-direction:column;gap:10px;padding-bottom:10px;
  /* Hide the OS-level scrollbar track -- visible on large screens/desktop
     Chrome where this box actually has overflow to scroll. Same
     convention already used for #chp-cards/#lvl-cards/#shop-cards. */
  scrollbar-width:none;
}
.army-slot-list::-webkit-scrollbar{display:none;}
/* Army slot cards now reuse the Shop's banner-panel border-image frame
   (same technique as .shop-item) instead of a flat gradient box, per
   developer request. Blue banner distinguishes a roster/army panel from
   the red back-button banner and the gold "select/edit" buttons below. */
.army-slot-card{
  padding:7px 16px 12px;box-sizing:border-box;
  border-style:solid;border-width:22px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:22px;
  border-image-source:url('assets/img/ui/shop_banner_blue.webp');
  box-shadow:var(--pch-shadow-soft);
  /* Two-column layout: name+thumbs on the left (.army-slot-main), Edit/
     Delete stacked vertically on the far right (.army-slot-actions) --
     per developer request, replacing the old stacked-rows layout where
     the buttons sat in their own row underneath the thumbs. */
  display:flex;align-items:center;gap:14px;
}
.army-slot-card{cursor:pointer;transition:filter .15s,transform .15s;}
.army-slot-card:hover{filter:brightness(1.12);transform:translateY(-1px);}
.army-slot-card.empty{opacity:.75;}
.army-slot-main{flex:1;min-width:0;display:flex;flex-direction:column;}
.army-slot-name{
  font-family:'Cinzel Decorative',serif;font-size:13px;color:var(--pch-gold-bright);
  letter-spacing:.5px;margin:0 0 3px;text-shadow:0 1px 2px rgba(0,0,0,.6);
}
.army-slot-empty-hint{font-family:'Cinzel',serif;font-size:9px;color:#a8987c;font-style:italic;}
/* Thumbs sized up again and packed tighter still (2px gap, 66px tokens --
   up from 52px) per developer request for "as large as possible without
   deforming the layout": 66px keeps a full 10-unique-unit army (the max
   possible, ARMY_SIZE) on a single row within the card's available width
   instead of wrapping to a second row, which is what would visually
   "deform" the card (sudden height jump vs. shorter armies). */
.army-slot-thumbs{display:flex;flex-wrap:wrap;gap:2px;}
.army-thumb-wrap{position:relative;width:66px;height:66px;}
.army-thumb{width:66px;height:66px;object-fit:contain;border-radius:5px;background:#000;image-rendering:pixelated;}
.army-thumb-count{
  position:absolute;bottom:-3px;right:-3px;background:#3a2c18;color:#ffd700;
  font-size:8px;font-family:'Cinzel',serif;padding:0 4px;border-radius:6px;border:1px solid var(--pch-bronze);
}
.army-slot-actions{
  flex-shrink:0;display:flex;flex-direction:column;justify-content:center;gap:8px;
}
/* Edit/Select/Delete buttons restyled to match the Shop's gold buy-button
   look (see .shop-buy-btn) -- primary/danger variants keep their own
   tint on top of the same rounded-gold base. */
.army-slot-btn{
  background:linear-gradient(180deg,var(--pch-gold),#8a6820);color:#241a0e;
  border:1px solid var(--pch-bronze);border-radius:6px;padding:6px 11px;
  font-family:'Cinzel',serif;font-size:9px;font-weight:700;letter-spacing:.3px;cursor:pointer;
  white-space:nowrap;text-align:center;
  transition:filter .12s,transform .08s;
}
.army-slot-btn:hover{filter:brightness(1.1);}
.army-slot-btn:active{transform:scale(.95);}
.army-slot-btn-primary{background:linear-gradient(180deg,#6ab048,#3c7024);color:#eaffd8;}
.army-slot-btn-danger{background:linear-gradient(180deg,#c04838,#7a2418);color:#ffe0d8;}

/* -- Army Editor -- */
.army-edit-name-input{
  display:block;margin:0 auto 4px;width:min(60vw,220px);text-align:center;
  background:rgba(0,0,0,.25);border:1px solid var(--pch-bronze);border-radius:4px;
  color:var(--pch-gold-bright);font-family:'Cinzel Decorative',serif;font-size:14px;
  padding:5px 8px;letter-spacing:.5px;
}
.army-edit-count{
  font-family:'Cinzel',serif;font-size:15px;letter-spacing:.5px;color:#a04020;font-weight:700;
}
.army-edit-count.complete{color:#2c7a30;}

/* -- Selected-units summary strip (top of Army Editor, 10 fixed slots) --
   Redesigned per developer brief: the composite token render (see
   drawCompositeToken/setCompositeTokenImg in game.js) already bakes in its
   own rarity-tier frame, element emblem, cost stamp and rarity gem -- so
   the card itself no longer needs a second background/border wrapper
   around a shrunken thumbnail. The <img> now fills the full card width at
   its native aspect ratio (composite canvas is always w × w*1.15, i.e.
   ~0.87 width/height) and the name is a small plate seamlessly attached
   to its bottom edge, matching the design-brief look instead of floating
   in a padded dark box. */
.army-editor-selected-grid{
  flex-shrink:0;display:grid;grid-template-columns:repeat(5,1fr);gap:8px;
  padding:8px 3px 12px;margin-bottom:8px;border-bottom:1px solid var(--pch-bronze);
}
.army-selected-slot{
  display:flex;flex-direction:column;min-width:0;position:relative;
}
.army-selected-slot.filled{
  /* No background/border here anymore -- the composite art supplies its
     own gold/regular/ornate frame per rarity tier. */
}
.army-selected-slot.empty{
  aspect-ratio:0.87;align-self:start;
  display:flex;align-items:center;justify-content:center;
  background:rgba(0,0,0,.15);border:1px dashed #4a3620;border-radius:8px;
}
.army-selected-remove-btn{
  position:absolute;top:3px;left:3px;z-index:3;width:17px;height:17px;line-height:1;
  border-radius:50%;border:1px solid #7a2418;background:linear-gradient(180deg,#8a3020,#5a1810);
  color:#ffd0c0;font-size:12px;font-weight:700;cursor:pointer;
  display:flex;align-items:center;justify-content:center;padding:0;
  box-shadow:0 2px 4px rgba(0,0,0,.5);
}
.army-selected-remove-btn:hover{filter:brightness(1.25);}
.army-selected-img{
  display:block;width:100%;height:auto;min-width:0;
  cursor:pointer;
  filter:drop-shadow(0 3px 6px rgba(0,0,0,.6));
  transition:transform .12s;
}
.army-selected-img:active{transform:scale(.96);}
/* Wraps the token image so the Legendary/Ghost sparkle canvas (see
   startTokenSparkleAnim() in game.js) can be absolutely positioned to
   exactly cover it -- the wrapper takes its height from the img (normal
   flow), and the canvas (position:absolute;inset:0) then fills that same
   box. Present even on non-sparkly units; harmless (no canvas child, no
   extra layout cost worth mentioning). */
.army-selected-imgwrap{position:relative;display:block;}
.army-selected-sparkle{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:2;}
.army-selected-name{
  margin-top:-3px;position:relative;z-index:1;
  background:linear-gradient(180deg,#2c2016,#160f07);
  border:1px solid var(--pch-gold);
  border-radius:7px;
  padding:3px 2px 4px;
  font-family:'Cinzel',serif;font-size:7.5px;font-weight:600;
  color:#f0e0b8;letter-spacing:.3px;text-align:center;line-height:1.15;
  box-shadow:0 3px 6px rgba(0,0,0,.45);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.army-selected-empty{font-family:'Cinzel',serif;font-size:15px;color:#5a4a38;}

.army-editor-roster-grid{
  /* No longer its own scroll container -- #army-editor-content (the
     whole screen) scrolls as one block now, see its own comment above
     for why. flex-shrink:0 so this renders at its full natural height
     within that scroll, instead of the old flex:1/overflow-y:auto
     nested-scroll setup (which would now be a second scroll container
     nested inside the first, fighting over the same touch gesture).
     Column min-width doubled (78px -> 150px) alongside .army-edit-img's
     doubled size below, so cards actually have room for the bigger
     token instead of it overflowing a still-78px-wide column. */
  flex-shrink:0;width:100%;display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));
  gap:8px;padding-bottom:10px;align-content:start;
}
#army-editor-filters{
  flex-shrink:0;display:flex;gap:14px;justify-content:center;padding:2px 0 10px;
}
.army-filter-label{
  display:flex;align-items:center;gap:6px;font-family:'Cinzel',serif;font-size:9px;
  letter-spacing:.5px;color:#c8b8a0;text-transform:uppercase;
}
.army-filter-select{
  background:linear-gradient(180deg,#2c2016,#1a1209);border:1px solid var(--pch-bronze);
  border-radius:4px;color:#e8ddc8;font-family:'Cinzel',serif;font-size:10px;
  padding:3px 6px;cursor:pointer;
}
.army-edit-card{
  display:flex;flex-direction:column;align-items:center;gap:2px;padding:6px 4px;border-radius:6px;
  background:linear-gradient(180deg,#241a0e,#160f07);border:1px solid #4a3620;
}
.army-edit-card.has-copies{border-color:var(--pch-gold);box-shadow:0 0 6px rgba(255,200,80,.25);}
.army-edit-card.locked{opacity:.85;}
.army-edit-card.locked .army-edit-img{filter:grayscale(1) brightness(.5);opacity:.7;}
.army-edit-card.locked .army-edit-cap{color:#a04020;}
.army-edit-img{width:72px;height:80px;object-fit:contain;cursor:pointer;}
.army-edit-name{font-size:6.5px;color:#c8b8a0;letter-spacing:.2px;text-align:center;line-height:1.1;}
.army-edit-cap{font-size:6px;color:#8a7458;font-style:italic;}
.army-edit-stepper{display:flex;align-items:center;gap:5px;margin-top:2px;}
.army-step-btn{
  width:18px;height:18px;border-radius:3px;border:1px solid var(--pch-bronze);
  background:linear-gradient(180deg,#3a2c18,#241a0e);color:#e8ddc8;font-size:11px;line-height:1;
  cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;
}
.army-step-btn:disabled{opacity:.3;cursor:default;}
.army-step-count{font-family:'Cinzel',serif;font-size:10px;color:var(--pch-gold-bright);min-width:12px;text-align:center;}
#army-editor-footer{flex-shrink:0;display:flex;justify-content:center;padding-top:10px;}
/* Save Army button now uses the dedicated army_save.png ribbon asset
   (native 1965×542, ratio ~3.626) as its background instead of a flat CSS
   gradient. Sized via aspect-ratio + a fixed width rather than padding, so
   the banner art is never stretched off-ratio, with the label centered on
   top via flex. */
.army-save-btn{
  width:min(230px,62vw);aspect-ratio:1965/542;
  background:url('assets/img/ui/army_save.webp') center/100% 100% no-repeat;
  border:none;border-radius:0;padding:0;
  display:flex;align-items:center;justify-content:center;
  color:#fff8e0;font-family:'Cinzel Decorative',serif;font-size:13px;letter-spacing:1px;
  text-shadow:0 1px 3px rgba(0,20,0,.85),0 0 6px rgba(0,0,0,.4);
  cursor:pointer;transition:filter .12s,transform .1s;
}
.army-save-btn:hover:not(:disabled){filter:brightness(1.12);}
.army-save-btn:active:not(:disabled){transform:scale(.97);}
.army-save-btn:disabled{opacity:.45;cursor:default;filter:grayscale(.7) brightness(.85);}
/* Top copy of the Save button, mirroring the back button's placement on
   the opposite corner of the header -- per developer request, so Save is
   reachable without scrolling all the way down past the full roster
   grid. Same saveArmyEditor() click handler and same disabled/enabled
   sync as the bottom button (see _renderArmyEditor's saveBtnTop). Sized
   down to fit the header row rather than reusing the bottom button's
   larger width wholesale, same ribbon asset just smaller.
*/
.army-save-btn-top{
  position:absolute;top:0;right:0;z-index:2;
  width:118px;font-size:9px;letter-spacing:.5px;
}

/* ══════════════════════════════════════════════════════════════════
   DEPLOY SCREEN — FLOATING OVERLAY LAYOUT
   The map now fills the entire screen (position:absolute;inset:0 on
   #place-board-wrap) instead of being squeezed between a header row
   and a footer row that both took their own real vertical space.
   Everything else -- title, Menu button, instructions, roster,
   deployment slots -- floats on top of it as absolutely-positioned
   overlays, each with a light semi-transparent backing instead of the
   old opaque parchment/leather panel art, so the map itself stays the
   visible, dominant thing. Gated on orientation alone (not a
   max-height cap) since this works at any landscape height.
   ══════════════════════════════════════════════════════════════════ */
@media (orientation:landscape){

  /* This was previously only uncapped in the phone-compact tier below,
     which meant any landscape content relying on real width (like this
     deploy screen's side panels) stayed squeezed into the old 500px
     portrait cap on a normal desktop window. Belongs here unconditionally
     since nothing about it is phone-specific. */
  #app{max-width:none;width:100%;}

  /* Map fills the whole screen; everything else layers on top of it. */
  #place-board-wrap{
    position:absolute;inset:0;flex:none;min-height:0;
  }

  /* Title -- floats top-center, no more parchment-panel background
     ("a brown container which serves no purpose") -- just text, with
     enough text-shadow to stay readable over whatever's behind it on
     the map. */
  #place-header{
    position:absolute;top:10px;left:50%;transform:translateX(-50%);
    z-index:15;width:auto;max-width:80%;
    background:none;border:none;padding:0;margin:0;
    pointer-events:none; /* it's just a label now, nothing to click */
  }
  #place-title{
    color:var(--pch-gold-bright);
    text-shadow:0 2px 5px rgba(0,0,0,.9),0 0 10px rgba(0,0,0,.7);
  }
  #place-sub{display:none;} /* the instructions panel covers this now */

  /* Menu button -- still its own control (unlike the title, this is
     interactive), but a light floating pill instead of a solid box,
     matching the "hover over the map" treatment everywhere else here. */
  #place-menu-btn{
    background:rgba(20,14,8,.55);border:1px solid rgba(200,160,80,.55);
    box-shadow:0 2px 6px rgba(0,0,0,.4);
  }

  /* Clear/Battle buttons -- pushed down below the menu button (they used
     to both sit at ~top:8-10px, right on top of each other, since
     #place-board-wrap fills the whole screen in this layout so both
     absolutely-positioned groups compute nearly the same corner). */
  #deploy-map-actions{top:52px;}

  /* Battle screen's menu/zoom pair -- same light floating-pill treatment
     as #place-menu-btn just above, so both screens match exactly. */
  #battle-menu-btn,#battle-top-controls #zoom-btn{
    background:rgba(20,14,8,.55);border:1px solid rgba(200,160,80,.55);
    box-shadow:0 2px 6px rgba(0,0,0,.4);
  }
  /* Reinforcement CTA pushed down below the menu/zoom row, same reasoning
     as #deploy-map-actions above. */
  #endturn-btn{top:52px;}

  /* Footer group -- instructions / roster / deployment slots -- floats
     bottom-center as one content-width unit (not stretched full-width),
     so map is visible on both sides of it, not just in the gaps
     between the three panels. */
  #place-footer{
    position:absolute;bottom:8px;left:50%;transform:translateX(-50%);
    z-index:15;
    display:flex;flex-direction:row;align-items:stretch;
    gap:12px;padding:0;
    width:min(1200px,calc(100vw - 16px));
  }

  /* Static instructions panel -- left of the roster. Light floating
     backing instead of the old opaque gregor_banner.png panel.
     Separate from #place-instructions (the dismissible floating hint
     box, now hidden by default -- see showLevelInfo-adjacent comment
     in game.js -- and only used for transient validation messages like
     "not enough points" instead of duplicating this permanent one). */
  #place-instructions-panel{
    flex:0 0 216px;min-width:0;
    background:rgba(20,14,8,.55);border:1px solid rgba(200,160,80,.4);
    border-radius:6px;box-shadow:0 2px 8px rgba(0,0,0,.4);
    display:flex;flex-direction:column;align-items:center;justify-content:center;
    gap:9px;padding:13.5px;text-align:center;
  }
  #place-instructions-panel-text{
    font-size:11px;color:#f0e0c0;line-height:1.4;
    font-family:'Cinzel Decorative',serif;font-weight:700;
    text-shadow:0 1px 3px rgba(0,0,0,.5);
  }
  #place-instructions-panel-text div{margin-bottom:4px;}
  #place-instructions-panel-text div:last-child{margin-bottom:0;}

  /* Roster -- capped to roughly 5 cards' width instead of stretching
     to fill whatever space is left (which on a wide window meant lots
     of dead space next to just 2-3 tokens). Still scrolls left/right
     via the same prev/next arrows once it has more cards than fit.
     Light floating backing, same as the two side panels, instead of
     the old opaque gregor_banner.png strip. Enlarged (+16px wide,
     +16px tall via the taller scroll-btn/portrait below) per developer
     request -- the side panels grow by the same amount so the whole
     footer row stays visually unified. */
  #roster-section{
    flex:0 1 744px;min-width:210px;margin:0;padding:12px 9px;
    background:rgba(20,14,8,.55);border:1px solid rgba(200,160,80,.4);
    border-radius:6px;box-shadow:0 2px 8px rgba(0,0,0,.4);
  }
  #roster-section .roster-scroll-btn{height:168px;width:36px;}
  #roster-section img.rc-portrait{max-width:117px;}

  /* Deployment slots panel -- right of the roster. Same treatment as
     the instructions panel opposite it for symmetry. */
  #place-slots-panel{
    flex:0 0 216px;min-width:0;
    background:rgba(20,14,8,.55);border:1px solid rgba(200,160,80,.4);
    border-radius:6px;box-shadow:0 2px 8px rgba(0,0,0,.4);
    display:flex;flex-direction:column;align-items:center;justify-content:center;
    gap:10.5px;padding:13.5px;
  }
  #place-slots-title{
    font-family:'Cinzel',serif;font-size:15.75px;font-weight:700;letter-spacing:1.3px;
    color:var(--pch-gold-bright);text-transform:uppercase;
    text-shadow:0 1px 2px rgba(0,0,0,.6);text-align:center;
  }
  #place-slots-row{display:flex;gap:10.5px;justify-content:center;}
  #place-slots-panel .pt-gem{width:45px;height:54px;}
  /* "Slots:" label is redundant with the "Deployment Slots" title
     above it now -- hidden here specifically rather than everywhere,
     since updatePtsUI() in game.js only ever clears this element's
     inline style (never sets it), so a stylesheet rule is the one
     thing that can reliably keep it hidden in both slot and cost mode. */
  #place-slots-panel #pts-bar-slot-label{display:none;}
  #place-slots-panel #pts-remaining,
  #place-slots-panel #pts-bar-cost-label,
  #place-slots-panel #deploy-pts-remaining{
    font-size:15px;color:#e8c878;text-align:center;font-family:'Cinzel Decorative',serif;
  }
  #place-slots-panel .pts-num{
    display:inline-block;font-size:32px;line-height:1;
    color:#ffe060;text-shadow:0 0 10px #ffaa00;
    vertical-align:middle;
  }

  /* The old floating validation-hint box (still used by showHint() for
     transient messages, no longer for the permanent default text --
     see game.js) needs to sit above the new footer instead of right at
     the map's bottom edge, now that the footer overlays that same
     area. */
  #place-instructions{bottom:104px;}
}

/* ══════════════════════════════════════════════════════════════════
   LANDSCAPE LAYOUT (phones held sideways) — real reflow, not scaling.
   max-height guards this to phone-in-landscape (~<=520px tall); tablets
   and desktop windows keep the normal portrait-style layout since they
   have plenty of height to spare.
   BUGFIX: the max-height check alone doesn't actually verify "phone" --
   only viewport height, so a desktop browser window resized short
   (common when testing, e.g. a laptop window shrunk vertically) matched
   this exact same breakpoint and got the phone-compact layout too, which
   is why the Level 5-style reinforcement banner suddenly rendered tiny
   in a normal desktop screenshot -- it isn't mobile-only sizing, it's
   short-viewport sizing, and a resized desktop window is a short
   viewport. Added (hover:none) and (pointer:coarse) -- standard, always-
   true indicators of an actual touchscreen -- so a mouse-driven desktop
   window (hover:hover, pointer:fine) can NEVER match this block again,
   regardless of how short it's resized. Only a genuine phone/tablet in
   landscape, at this height, triggers it now.
   ══════════════════════════════════════════════════════════════════ */
@media (orientation:landscape) and (max-height:520px) and (hover:none) and (pointer:coarse){

  /* Let the app use the full wide viewport instead of staying capped
     at the 500px portrait design width. */
  #app{max-width:none;width:100%;}

  /* ── Deploy screen: same overlay approach as the general landscape
     block above (map fills the screen, everything else floats on top),
     just further compacted for real phone heights -- smaller panels,
     smaller roster cards so ~5 still fit before needing to scroll. ── */
  #place-header{top:6px;}
  #place-title{font-size:11px;}
  #place-menu-btn{padding:5px 10px;font-size:9px;top:6px;right:8px;}
  #deploy-map-actions{top:34px;}
  /* Battle screen's menu/zoom pair -- same compacting as #place-menu-btn
     just above. */
  #battle-top-controls{top:6px;right:8px;}
  #battle-menu-btn{padding:5px 8px;font-size:10px;}
  #battle-top-controls #zoom-btn{padding:5px 7px;font-size:9px;}
  #battle-menu-popup{top:34px;right:8px;}
  #endturn-btn{top:34px;}
  #place-footer{bottom:7.5px;gap:7.5px;width:min(747px,calc(100vw - 16px));}
  #place-instructions{bottom:111px;}
  #place-instructions-panel,#place-slots-panel{
    flex-basis:123px;padding:7.5px;gap:6px;
  }
  #place-instructions-panel-text{font-size:8px;line-height:1.3;}
  #place-slots-title{font-size:11.25px;letter-spacing:1.2px;}
  #place-slots-panel .pt-gem{width:30px;height:36px;}
  #place-slots-panel #pts-remaining,
  #place-slots-panel #pts-bar-cost-label,
  #place-slots-panel #deploy-pts-remaining{font-size:11.25px;}
  #place-slots-panel .pts-num{font-size:24px;text-shadow:0 0 8px #ffaa00;}
  #roster-section{
    flex-basis:486px;min-width:150px;padding:9px 9px;
  }
  #roster-wrap{gap:4.5px;}
  #roster-section .roster-scroll-btn{height:99px;width:30px;font-size:16.5px;}
  .roster-card{
    min-width:84px;flex:0 0 84px;padding:6px 3px 4.5px;gap:3px;
    border-width:10.5px;border-image-width:10.5px;
  }
  img.rc-portrait{max-width:63px;}
  .rc-name{font-size:10.5px;-webkit-line-clamp:1;}

  /* ── Battle screen: board-wrap already flexes to fill remaining
     space, and the turn queue overlay is absolutely positioned, so this
     mostly just needs room to breathe. (#hud-bottom-row itself now
     floats over the bottom of the canvas -- see its own comment above --
     but #action-menu is still just a static flex child docked beside
     the unit banner within that floating row, not independently
     positioned.) ── */
  #board-wrap{min-height:0;}

  /* ── HUD bottom row (unit banner + action menu) -- #action-menu is
     flex:1 (fills whatever space remains after the unit banner), grid
     columns are equal 1fr. Chip padding/gap trimmed for these more
     cramped phone-landscape heights. NOTE: a previous attempt here
     capped #bottom-bar's width and shrank --hud-bar-h to guarantee
     #action-menu more room -- reverted, it broke rendering worse than
     this baseline (reported: stats/HP/MP missing, action grid collapsed
     to a single column). Back to this simpler, known-safer state while
     a proper fix is worked out -- see the reply in this session for the
     real next step (stacking the HUD vertically instead of splitting it
     horizontally, rather than more pixel-tuning blind). ── */
  #action-menu{padding:3px 6px 5px;}
  .am-item{padding:4px 7px;gap:4px;}

  /* ── Level select / shop / reward / mission-scan: these already
     scroll internally (overflow-y:auto), so the wider #app above is
     enough for them to lay out comfortably without further changes. ── */

  /* ── Reinforcement roster banner (#l5-reinforce-banner) -- BUGFIX
     (this comment now corrected after a real device diagnosis session --
     see that conversation for the full story): this banner previously
     had its own separately-tuned card/portrait/scroll-button sizes at
     this tight phone tier, noticeably smaller than the initial "Deploy
     Your Forces" screen's roster -- which is exactly why they looked
     different sizes side by side, even though the intent was always for
     them to match. Only `gap`/`padding`/`max-width` stay scoped here now;
     every card/portrait/scroll-button/text size below is either removed
     (letting the shared .roster-card/img.rc-portrait rules above apply,
     exactly as the initial deploy roster already does) or set to
     literally the same value as its #roster-section counterpart, so the
     two banners can't drift apart in size again.
     `bottom` REMOVED from this override entirely (BUGFIX, this session):
     it used to sit at a large offset specifically to clear the bottom HUD
     row (unit banner + action menu) underneath it -- but that row is
     already fully hidden for the whole reinforcement phase (see
     syncBottomBarForPhase()/hideActionMenu(), both called the moment
     reinforce starts), so there's nothing left to clear. The large offset
     just pushed the banner a quarter of the way up a real iPhone screen
     for no reason (worse once the previous session scaled it 50% bigger
     along with everything else). Dropping the override here lets it fall
     back to the base rule's bottom:8px, identical to #place-footer. ── */
  #l5-reinforce-banner{gap:7.5px;padding:6px 9px;max-width:96%;width:min(747px,calc(100vw - 16px));}
  #l5-reinforce-text{max-width:123px;padding-right:7.5px;}
  #l5-reinforce-title{font-size:11.25px;letter-spacing:1.2px;}
  #l5-reinforce-sub{font-size:10.5px;margin-top:1.5px;line-height:1.4;}
  #l5-reinforce-panel{gap:4.5px;flex:0 1 486px;min-width:150px;}
  #l5-reinforce-panel .roster-scroll-btn{height:99px;width:30px;font-size:16.5px;}
  #l5-reinforce-roster{gap:4.5px;}
  #l5-reinforce-roster img.rc-portrait{max-width:63px;}
  .l5-reinforce-empty{font-size:10.5px;}
  #l5-reinforce-pts{min-width:48px;padding-left:7.5px;}
  #l5-reinforce-pts-label{font-size:8.25px;}
  #l5-reinforce-pts-val{font-size:21px;text-shadow:0 0 8px #ffaa00;}
}

/* ══════════════════════════════════════════════════════════════════
   ROTATE-TO-LANDSCAPE LOCK
   Landscape is now the only supported way to play. Whenever the
   viewport is portrait-shaped -- iPhone held upright, OR a PC browser
   window that's taller than it is wide -- hide the game and show a
   prompt instead. This is a software approximation of orientation
   lock (Safari has no API to truly prevent rotation on a web page);
   real OS-level lock happens later when this is wrapped natively via
   Capacitor and set to landscape-only in Xcode.
   ══════════════════════════════════════════════════════════════════ */
#rotate-lock{
  display:none;position:fixed;inset:0;z-index:9999;
  background:#120c06;color:var(--pch-gold-bright);
  flex-direction:column;align-items:center;justify-content:center;gap:14px;
  text-align:center;padding:20px;font-family:'Cinzel',serif;
}
#rotate-lock-icon{font-size:48px;animation:rotateHint 1.8s ease-in-out infinite;}
@keyframes rotateHint{0%,100%{transform:rotate(0deg)}50%{transform:rotate(90deg)}}
#rotate-lock-text{font-size:14px;letter-spacing:.5px;max-width:280px;line-height:1.5;}

@media (orientation:portrait){
  #rotate-lock{display:flex;}
  #app{display:none;}
}

/* ══════════════════════════════════════════════════════════════════
   CHAPTER SELECT / LEVEL SELECT — LANDSCAPE CARD LAYOUT
   Cards go from a vertically-scrolling stack of wide short strips to a
   horizontally-scrolling row of tall cards (portrait art on one side,
   a colored text panel on the other) — same idea as the reference
   layout. Gated on orientation alone (no max-height cap) since this
   works fine at any landscape height, phone or a wide PC window, and
   portrait is already fully blocked by the rotate-lock above.
   Shares CSS between #chp-cards/.chp-card and #lvl-cards/.lvl-card
   since they're built from the same lvl-* class skeleton in the HTML.
   ══════════════════════════════════════════════════════════════════ */
@media (orientation:landscape){

  /* Slightly tighter chrome above the cards frees up real card height
     even on a roomy desktop window -- request was "make cards taller,
     there's spare room at the bottom" -- so this isn't just a phone-
     height concern anymore. */
  #chp-cards,#lvl-cards,#shop-cards,#iap-cards{
    flex-direction:row;align-items:stretch;
    gap:14px;overflow-x:auto;overflow-y:hidden;touch-action:pan-x;
    padding:24px 14px 8px;
    scroll-snap-type:x proximity;
    /* Hides the OS-level scrollbar track that shows up under this row on
       a large desktop window (mouse/trackpad, not touch, so the native
       scrollbar was rendering) -- same convention already used for
       #roster/#l5-reinforce-roster elsewhere in this file. Scrolling
       itself is untouched; this only hides the track's own visuals. */
    scrollbar-width:none;
  }
  #chp-cards::-webkit-scrollbar,#lvl-cards::-webkit-scrollbar,#shop-cards::-webkit-scrollbar,#iap-cards::-webkit-scrollbar{display:none;}
  /* Shop's row needs extra top headroom specifically -- see
     .shop-card-value-banner's comment for why -- otherwise identical to
     the shared rule above (kept as a second, more-specific-by-source-
     order declaration rather than duplicating gap/overflow/etc). Applies
     to #iap-cards too, which shares .shop-card's chrome and would
     otherwise sit at a different height than the Shop it's one tap away
     from. */
  #shop-cards,#iap-cards{padding-top:56px;}
  #mode-toggle-wrap-chp,#mode-toggle-wrap{padding:4px 14px 0;}
  #chp-footer-bar,#lvl-footer{padding:3px 12px 6px;}

  /* Width used to be a flat 340px (270px on short/touch phones -- see
     the other .chp-card,.lvl-card rule further down) while height:auto
     + this row's align-items:stretch let the card grow to whatever the
     row's cross-size was. That's fine on a genuinely short phone-
     landscape row, but on anything taller the card just kept
     stretching vertically with a frozen width, crushing the cover-fit
     portrait into a thinner and thinner crop the taller the window got
     -- confirmed with devtools measurements: 682x369 (short) rendered
     correctly, 682x436 (taller, same width) visibly cropped worse, with
     nothing else about the layout changing in between.
     Fixed target ratio measured directly from the confirmed-good render
     (790x369, level-select): card came out 270x189 -- exactly 10:7 --
     so aspect-ratio locks width and height together at that ratio
     instead of letting them drift apart as the window resizes. Width is
     no longer a hardcoded breakpoint value at all; every screen size
     now derives it from height via the same ratio the working phone
     size already had, so the portrait crop stays visually identical at
     every size instead of getting worse as the window gets taller.
     min-height:0 is required alongside aspect-ratio: a flex item with
     aspect-ratio and the default min-height:auto gets an automatic
     minimum cross-size DERIVED FROM that ratio, which is a much bigger
     floor than this card normally has (.chp-card-inner is
     position:absolute, so it contributes ~0 to intrinsic size
     otherwise) -- that floor previously forced #chp-cards taller than
     its flex:1 allocation on any short window, overflowing #app
     (overflow:hidden) and clipping each card's Enter button off the
     bottom of the page. min-height:0 stops that automatic-minimum-size
     calculation from ever engaging. (That same fix needed to be applied
     one level up too -- see the min-height:0 added to
     #chp-content/#lvl-content and #chp-cards/#lvl-cards.) */
  .chp-card,.lvl-card{
    /* height:100% instead of height:auto -- this is the iPhone Safari fix.
       Both this rule's dimensions used to be `auto`, relying on Safari to
       resolve `align-items:stretch` (from #chp-cards/#lvl-cards) and
       `aspect-ratio` at the same time to figure out the card's cross-size
       (height) before deriving width from it. Chrome/desktop resolves
       that fine, but this is a known WebKit gap: when a flex item's
       cross-size AND main-size are both `auto` and it also has
       aspect-ratio, Safari doesn't reliably apply the stretch first --
       it can fall back toward the item's near-zero intrinsic content size
       instead (this card's real content all lives in .chp-card-inner,
       which is position:absolute and so contributes ~0 intrinsic size --
       see the min-height:0 comment above), producing a tiny, squished
       card even though the exact same CSS renders correctly on desktop.
       Setting height:100% gives Safari a hard, unambiguous percentage of
       #chp-cards'/#lvl-cards' own (already-definite, via the flex:1 chain
       above) height to resolve FIRST, with width then derived from that
       via aspect-ratio -- removing the ambiguity entirely instead of
       depending on stretch-vs-aspect-ratio resolution order. */
    width:auto;height:100%;min-height:0;
    aspect-ratio:270/189;
    flex:0 0 auto;
    scroll-snap-align:start;
    /* Establishes this card as a query container, so descendants further
       down (badge, name, description, rewards, buttons) can be sized in
       cqw ("% of THIS card's own rendered width") instead of fixed px or
       viewport units. Since width now scales via aspect-ratio, cqw gives
       every child a size that's automatically, continuously proportional
       to the card at any screen size -- no separate breakpoint math to
       keep in sync with the card's own sizing rule. */
    container-type:inline-size;
  }
  /* Shop cards scroll in the same horizontal strip as Chapter/Level
     Select (per developer request) but keep their own square sizing --
     they're full-bleed panel art (see .shop-card below), not the
     portrait+content grid the chapter/level cards use, so they don't
     share .chp-card/.lvl-card's grid-template rules. */
  .shop-card{
    width:230px;height:230px;flex:0 0 230px;
    scroll-snap-align:start;
  }
  /* Featured slot (Adventurers Tin's banner+card wrapper) is the actual
     flex item here instead of .shop-card directly -- same 230px sizing,
     just on the wrapper, with the inner card reset to fill it rather
     than carrying its own conflicting width/flex-basis. */
  .shop-card-featured-wrap{
    width:230px;flex:0 0 230px;height:auto;
    scroll-snap-align:start;
  }
  .shop-card-featured-wrap .shop-card{
    width:100%;flex:none;scroll-snap-align:none;
  }

  /* Card interior switches from the old row-of-3 flex (portrait |
     content | buttons side by side) to a grid: portrait spans the
     full card height on one side, content and buttons stack on the
     other side with buttons under the text -- matching the reference
     layout without needing to touch the HTML structure.
     minmax(0,1fr) -- NOT bare 1fr -- on the content row is the actual
     fix for the "content hanging off the bottom of the card" bug:
     grid tracks default to a content-based MINIMUM size even when
     sized as 1fr, so a card with more tags/buttons than its neighbors
     was forcing the whole grid taller than the card's own fixed
     height, and everything spilled out past the card frame onto the
     background behind it instead of fitting or clipping. minmax(0,..)
     removes that implicit minimum so the row actually shrinks to fit
     the space it's given. */
  .chp-card .chp-card-inner,.chp-card-inner,
  .lvl-card .lvl-card-inner,.lvl-card-inner{
    display:grid;
    grid-template-columns:44% 1fr;
    grid-template-rows:minmax(0,1fr) auto;
    grid-template-areas:"portrait content" "portrait buttons";
  }

  /* Portrait bleeds edge-to-edge (no padding) and fills its full
     column height via cover -- matches the reference brief and the
     actual portrait art, which is already tightly cropped around the
     character with almost no dead space, so cover reads as a clean
     full-bleed hero shot rather than an awkward zoom. Anchored toward
     the top so faces/crowns stay fully in frame; any cropping happens
     at the feet instead. */
  .chp-card .lvl-portrait-wrap,.lvl-portrait-wrap{
    grid-area:portrait;width:auto;height:auto;min-height:0;
    align-self:stretch;justify-self:stretch;flex:none;
    padding:0;overflow:hidden;
  }
  .chp-card .lvl-portrait,.lvl-portrait{
    width:100%;height:100%;max-height:none;
    object-fit:cover;object-position:center 12%;
  }

  /* min-height:0 + overflow:hidden here are what actually let the
     minmax(0,1fr) row above take effect -- without them, this element's
     own content would still refuse to compress below its natural size
     and we'd be right back to the overflow bug. */
  .chp-card .lvl-content-block,.lvl-content-block{
    grid-area:content;padding:18px 10px 4px 12px;
    min-height:0;overflow:hidden;
  }
  .chp-card .lvl-name,.lvl-name{margin-bottom:1px;}
  .chp-card .lvl-flavor{
    margin:2px 0;-webkit-line-clamp:2;
  }
  /* Chapter AND level cards: drop the tag pills, enemy row, and
     divider entirely to make more room -- extended to chapter cards
     this round after rewards became invisible there again (more
     content variability -- e.g. 4-tag chapters -- was eating into the
     space the rewards row needs; removing tags/enemies uniformly is a
     more robust fix than continuing to fine-tune clamps per-card). */
  .lvl-card .lvl-tags,.lvl-card .lvl-divider,.lvl-card .lvl-enemy-row,
  .chp-card .lvl-tags,.chp-card .lvl-divider,.chp-card .lvl-enemy-row{
    display:none;
  }
  /* Level cards only: the description moves into an info popup (see
     .lvl-info-btn / showLevelInfo() in game.js) so it's hidden here
     entirely rather than clamped -- chapter cards keep their
     description as before. */
  .lvl-card .lvl-flavor{display:none;}
  /* Rewards now sit directly under the title instead of being pushed to
     the bottom -- the old margin-top:auto anchored them against the
     .lvl-btn-col buttons row, which no longer exists in the content
     flow now that Scan/Deploy are absolute-positioned outside it. */
  .lvl-card .lvl-rewards-section{margin-top:8px;}

  /* ── Rewards section -- back in the normal content flow now (see
     .lvl-rewards-section/.lvl-rewards-label in the HTML), sitting
     between the enemy row and the buttons row: "Rewards" label with
     flanking lines, then the actual reward art/icons below it. ── */
  /* Point 3 (developer follow-up): centered rather than left-aligned --
     both the section itself (in case the label/image differ in natural
     width) and the teaser image's own internal alignment via
     object-position, since object-fit:contain here can letterbox the art
     inside a taller/shorter box than its native aspect. */
  .lvl-rewards-section{margin-top:4px;display:flex;flex-direction:column;align-items:center;}
  .lvl-rewards-label{
    display:flex;align-items:center;gap:8px;
    font-family:'Cinzel',serif;font-size:9px;font-weight:700;letter-spacing:2px;
    text-transform:uppercase;color:var(--pch-gold-bright);
    text-shadow:0 1px 2px rgba(0,0,0,.6);
    margin-bottom:4px;
    width:100%;
  }
  .lvl-rewards-label::before,.lvl-rewards-label::after{
    content:'';flex:1;height:1px;
    background:linear-gradient(90deg,transparent,rgba(220,180,100,.7),transparent);
  }
  /* Chapter teaser art -- ch3/4/5 each have a different native aspect
     ratio (single combined marketing images, not separate icons), so
     this is sized by height with width:auto rather than a fixed box,
     keeping all three visually consistent regardless of shape.
     object-position switched from left-center to center-center (plus
     margin:0 auto as a belt-and-suspenders centering of the box itself)
     per developer request -- it was reading as pinned to the left edge
     of the rewards row instead of centered under the "Rewards" label. */
  .chp-card .chp-rewards-teaser,.chp-rewards-teaser{
    display:block;width:auto;height:40px;max-height:40px;max-width:100%;
    margin:0 auto;
    object-fit:contain;object-position:center center;
    filter:drop-shadow(0 2px 5px rgba(0,0,0,.5));
    cursor:pointer;
  }
  /* .lvl-reward-preview align-self override REMOVED -- was flex-start,
     fighting the base rule's align-self:center (see its own comment)
     and winning here specifically since this landscape block comes
     later in the file. That's what was pushing the reward preview to
     the left edge instead of centering it under the "Rewards" label. */
  /* .lvl-reward-token-thumb / .lvl-reward-coin-badge width/height/
     font-size REMOVED from here too -- now set via cqw in the
     "Scale-with-the-card pass" further down this same media query,
     which already wins the cascade over these (later in the block). */

  .chp-card .lvl-btn-col,.lvl-btn-col{
    grid-area:buttons;width:auto;height:auto;
    flex-direction:row;flex-wrap:wrap;gap:6px;
    padding:5px 12px 8px;
  }
  .chp-card .lvl-btn-col>*,.lvl-btn-col>*{flex:1 1 auto;min-width:64px;}
  /* The actual Enter button wrapper (.chp-enter-btn-wrap) is a SIBLING of
     .chp-card-inner, not a grid item inside it (see its own comment at
     the base rule for why -- deliberately escapes the grid so it isn't
     clipped by overflow:hidden), so it can't be positioned with
     grid-area like .lvl-btn-col above. In this real landscape layout the
     card switched to a grid with the portrait as a 44%-wide column (see
     .chp-card-inner's grid-template-columns above) instead of the old
     fixed 130px flex box the base rule's left:58% guess was tuned for --
     re-anchored here to span exactly from the portrait's right edge
     (left:44%) to the card's right edge, then centered within that span,
     so it always sits centered under the content column (title/rewards)
     rather than the old fixed-offset approximation. Bottom brought up
     to sit right against the portrait's own bottom edge instead of
     floating past it. */
  .chp-card .chp-enter-btn-wrap{
    left:44%;right:0;bottom:10px;transform:none;
  }

  /* Titles/descriptions must wrap now that cards are narrower than the
     old full-width strip they were designed for -- nothing should get
     clipped or force the card wider. */
  .chp-card .lvl-name,.lvl-name{
    white-space:normal;overflow-wrap:break-word;
  }
  .chp-card .lvl-flavor,.lvl-flavor{
    white-space:normal;overflow-wrap:break-word;
  }
  .chp-card .lvl-enemy-names,.lvl-enemy-names{
    white-space:normal;overflow:visible;text-overflow:clip;
  }

  /* ── Scale-with-the-card pass ───────────────────────────────────────
     Everything above sizes the CARD itself (aspect-ratio, min-height:0)
     but every child inside it was still a fixed px value tuned for one
     specific breakpoint -- fine at that one size, but frozen everywhere
     else, so a big card on a large screen ends up mostly empty while
     its badge/name/description/rewards/buttons stay phone-sized. cqw is
     "% of this card's own rendered width" (see container-type:inline-
     size on .chp-card,.lvl-card above), so every value below scales
     continuously with the card at any screen size instead of needing
     its own breakpoint. Placed last in this media query (and matching
     the exact selectors already used above) so it wins the cascade over
     every earlier fixed-px value here AND in both short-landscape-phone
     blocks below, without needing !important anywhere.
     Each clamp(min,val,max) floor is calibrated to equal the CURRENT
     smallest (short-phone) value exactly -- so nothing changes in
     appearance at the size that's already confirmed working -- and the
     ceiling is a first-pass reasonable cap for very large screens; if
     it feels too big/small at your actual large-screen size once you've
     tested it, the ceiling numbers are the only thing that needs
     tuning, not the whole mechanism. */

  /* Chapter-number banner -- previously had NO responsive sizing at
     all (fixed 34x66px, 11px numeral) at any breakpoint. */
  .chp-card .lvl-badge-wrap,.lvl-badge-wrap{
    width:clamp(34px,12.6cqw,72px);height:clamp(66px,24.4cqw,140px);
  }
  .chp-card .lvl-badge-roman,.lvl-badge-roman{
    font-size:clamp(11px,4.1cqw,22px);
  }
  /* Chapter/level name */
  .chp-card .lvl-name,.lvl-name{
    font-size:clamp(12px,4.4cqw,32px);
  }
  /* Chapter description (Level Select hides .lvl-flavor entirely via its
     own rule further up -- this only visibly affects Chapter Select). */
  .chp-card .lvl-flavor{
    font-size:clamp(8px,2.96cqw,19px);
  }
  .lvl-tag{
    font-size:clamp(8.5px,3.1cqw,18px);padding:clamp(2px,.74cqw,5px) clamp(7px,2.6cqw,16px);
  }
  .chp-card .lvl-enemy-label,.chp-card .lvl-enemy-names,
  .lvl-card .lvl-enemy-label,.lvl-card .lvl-enemy-names{
    font-size:clamp(7px,2.96cqw,17px);
  }
  /* Rewards -- "Rewards" label, Chapter 3/4/5's teaser art, Chapter 6's
     flat icon+coin row, and Level Select's reward preview badge. */
  .lvl-rewards-label{
    font-size:clamp(7px,2.96cqw,17px);
  }
  .chp-card .chp-rewards-teaser,.chp-rewards-teaser{
    height:clamp(26px,9.6cqw,66px);max-height:clamp(26px,9.6cqw,66px);
  }
  .chp-static-rewards{height:clamp(26px,16.3cqw,66px);}
  .chp-static-reward-item img{width:clamp(18px,10.4cqw,48px);height:clamp(18px,10.4cqw,48px);}
  .chp-static-reward-item span{font-size:clamp(9px,5.2cqw,24px);}
  .lvl-reward-token-thumb{width:clamp(19px,7cqw,42px);height:clamp(19px,7cqw,42px);}
  .lvl-reward-coin-badge img{width:clamp(13px,4.8cqw,28px);height:clamp(13px,4.8cqw,28px);}
  .lvl-reward-coin-badge span{font-size:clamp(8px,2.96cqw,17px);}
  .lvl-reward-tin-count{font-size:clamp(9px,3.3cqw,19px);}
  .lvl-reward-earned-badge{font-size:clamp(8px,2.96cqw,17px);padding:clamp(2px,.74cqw,5px) clamp(5px,1.85cqw,11px);}
  /* Enter button (Chapter Select) -- height already comes from
     aspect-ratio on the base rule, only width needs scaling. */
  .chp-card .lvl-btn-wrap{width:clamp(86px,31.9cqw,190px);}
  /* Scan + Deploy buttons (Level Select) -- no aspect-ratio here, so
     both dimensions need to scale together explicitly. */
  .lvl-card .lvl-btn-wrap{width:clamp(72px,26.7cqw,160px);height:clamp(28px,10.4cqw,62px);}
  .chp-card .lvl-btn-label,.lvl-card .lvl-btn-label{font-size:clamp(10px,3.7cqw,22px);}
}

/* ── Short landscape (real phones, not wide PC windows) needs its own,
   tighter pass: the banner/header/mode-toggle rows above the cards eat
   too much of a ~390px-tall viewport, leaving too little card height
   for tags/buttons to fit without clipping. Compresses the chrome
   above the cards and the cards' own internal spacing/type scale.
   Same (hover:none) and (pointer:coarse) guard as the deploy-screen
   block above -- the comment here already said "not wide PC windows"
   but the max-height check alone never actually enforced that. ── */
@media (orientation:landscape) and (max-height:520px) and (hover:none) and (pointer:coarse){
  #chp-header-controls,#lvl-header-controls{top:6px;left:8px;}
  #chp-back-btn,#lvl-back-btn{padding:3px 8px;font-size:8px;border-width:9px;border-image-width:9px;}
  #chp-banner-wrap,#lvl-banner-wrap,#iap-shop-banner-wrap{width:min(54%,320px);margin:3px auto 0;}
  #shop-banner-wrap{width:min(54%,320px);margin:3px auto 0;}
  #chp-title,#lvl-title{font-size:14px;letter-spacing:1px;}
  #shop-title,#iap-shop-title{font-size:20px;letter-spacing:1.5px;}
  #chp-sub,#lvl-chapter-sub,#shop-sub,#iap-shop-sub{font-size:7.5px;margin-top:2px;}
  #mode-toggle-wrap-chp,#mode-toggle-wrap{padding:1px 14px 0;}
  #chp-cards,#lvl-cards{padding:16px 12px 4px;gap:10px;}
  #shop-cards,#iap-cards{padding:46px 12px 4px;gap:10px;}
  /* .chp-card,.lvl-card width override REMOVED -- this was a leftover
     fixed width:270px from before .chp-card/.lvl-card had aspect-ratio.
     It matched exactly the same media query as the aspect-ratio rule
     (orientation:landscape, max-height:520px, hover:none, pointer:coarse)
     and comes later in the file, so it silently won and froze width at
     270px regardless of height -- on precisely the short/touch viewports
     this whole fix targets. Since 270/189 (the aspect-ratio target) was
     measured AT this exact breakpoint, dropping this override doesn't
     change anything at that reference size -- it just stops it from
     fighting the ratio at every other short-viewport size too. */
  .shop-card{width:190px;height:190px;flex:0 0 190px;}
  .shop-card-featured-wrap{width:190px;flex:0 0 190px;}
  .shop-card-featured-wrap .shop-card{width:100%;flex:none;}
  /* Scaled down to match the smaller 190px card at this breakpoint --
     same ~25% overlap ratio as the default size (see
     .shop-card-value-banner's own comment). */
  .shop-card-value-banner{top:-36px;}
  /* Guarantees the title/flavor/tags column a real minimum even when
     the button column below it (up to 3 stacked buttons on a level
     card) is at its tallest -- without this, minmax(0,1fr) will happily
     shrink content to nothing before it touches the buttons row, which
     just traded "overflowing the card" for "invisible inside it". */
  .chp-card .chp-card-inner,.chp-card-inner,
  .lvl-card .lvl-card-inner,.lvl-card-inner{
    grid-template-rows:minmax(80px,1fr) auto;
  }
  .chp-card .lvl-content-block,.lvl-content-block{padding:12px 8px 2px 9px;}
  .chp-card .lvl-name,.lvl-name{margin-bottom:0;}
  .chp-card .lvl-flavor{margin:2px 0;-webkit-line-clamp:2;}
  /* font-size for lvl-name/lvl-flavor/lvl-enemy-label/lvl-enemy-names/
     lvl-btn-wrap(w/h)/lvl-btn-label/lvl-rewards-label/chp-rewards-teaser/
     lvl-reward-token-thumb/lvl-reward-coin-badge -- all REMOVED from
     this block. They used to hardcode a smaller size for this specific
     cramped breakpoint; now that those same properties are set via cqw
     in the "Scale-with-the-card pass" above (in the enclosing general
     landscape block), a fixed px value here would win the cascade
     purely by coming later in the file and silently undo that scaling
     right at this exact breakpoint -- which is also the one the cqw
     values were calibrated against, so it would have looked unchanged
     and hidden the bug. Non-size properties (margin/gap/letter-spacing/
     position) stay, since those aren't handled by the cqw pass. */
  .chp-card .chp-enter-btn-wrap{bottom:8px;}
  .lvl-card .lvl-enter-btn-wrap{bottom:10px;gap:5px;}
  .lvl-rewards-section{margin-top:2px;}
  .lvl-rewards-label{letter-spacing:1px;gap:5px;margin-bottom:2px;}
  #chp-footer-bar,#lvl-footer{padding:1px 12px 3px;}
  #chp-footer-text,#lvl-footer-text{font-size:7px;}
}


/* ══ BUG REPORT MODAL + SETTINGS ACTION BUTTON ═══════════════════ */
/* Positive (non-destructive) counterpart of .settings-danger-btn. */
.settings-action-btn{
  flex-shrink:0;
  font-family:'Cinzel',serif;font-size:10px;letter-spacing:.5px;font-weight:700;
  padding:8px 14px;border-radius:5px;cursor:pointer;
  background:linear-gradient(180deg,var(--pch-gold),#8a6820);
  border:1px solid var(--pch-bronze);color:#241a0e;
}
.settings-action-btn:hover{filter:brightness(1.1);}
#bug-report-modal{
  display:none;position:fixed;inset:0;z-index:82;
  background:rgba(0,0,0,.72);align-items:center;justify-content:center;
  backdrop-filter:blur(2px);
}
#bug-report-modal.show{display:flex;}
#bug-report-box{
  width:90%;max-width:440px;max-height:92vh;max-height:92dvh;overflow-y:auto;
  background:linear-gradient(180deg,#241a0e,#150f08);
  border:1px solid var(--pch-gold);border-radius:8px;
  padding:16px 16px 14px;text-align:left;
  box-shadow:0 8px 32px rgba(0,0,0,.85);
  animation:rewardPop .25s cubic-bezier(.22,.8,.42,1) both;
  font-family:'Cinzel',serif;color:#e8e0c8;font-size:11px;line-height:1.5;
}
#bug-report-title{font-family:'Cinzel Decorative',serif;font-size:13px;color:var(--pch-gold-bright);letter-spacing:1px;font-weight:700;text-align:center;margin-bottom:6px;}
#bug-report-intro{text-align:center;margin-bottom:8px;}
#bug-report-rewards{border:1px solid rgba(201,162,77,.35);border-radius:5px;padding:6px 10px;margin-bottom:10px;background:rgba(0,0,0,.25);}
#bug-report-rewards summary{cursor:pointer;color:var(--pch-gold-bright);font-weight:700;}
#bug-report-rewards ul{margin:6px 0 6px 16px;padding:0;}
#bug-report-rewards p{margin:4px 0 0;font-size:10px;opacity:.85;}
#bug-report-box label{display:block;margin:6px 0 3px;color:var(--pch-gold-bright);font-size:10px;letter-spacing:.3px;}
#bug-report-box select,#bug-report-box textarea{
  width:100%;box-sizing:border-box;
  background:#0f0a05;color:#f0e6cc;border:1px solid rgba(201,162,77,.5);border-radius:4px;
  padding:6px 8px;font-family:Georgia,serif;font-size:13px;
}
#bug-report-box textarea{resize:vertical;}
#bug-report-msg{min-height:14px;margin:6px 0 2px;font-size:10px;text-align:center;}
#bug-report-msg.error{color:#f08a7a;}
#bug-report-msg.success{color:#9ad88a;}
#bug-report-btns{display:flex;gap:6px;justify-content:center;margin-top:6px;}
#bug-report-cancel-btn,#bug-report-submit-btn{font-family:'Cinzel',serif;font-size:10px;letter-spacing:.5px;padding:8px 14px;border-radius:4px;cursor:pointer;font-weight:700;}
#bug-report-cancel-btn{background:linear-gradient(180deg,var(--pch-bronze-light),var(--pch-bronze));border:1px solid var(--pch-gold);color:#2a1c0c;}
#bug-report-submit-btn{background:linear-gradient(180deg,var(--pch-gold),#8a6820);border:1px solid var(--pch-bronze);color:#241a0e;}
#bug-report-submit-btn:disabled{opacity:.6;cursor:default;}
/* ══ REWARDS PANEL (daily login + Brawler daily wins) ═══════════ */
/* Parchment card + gold_button plate -- the same assets as the Victory/
   Defeat card (#ov-card / .ov-btn), so it reads as part of that family.
   Tiles are dark inset plates on the parchment; today's glows + pulses,
   claimed ones dim with a tick, the big reward (Day 7 / Win 1) is gold. */
#daily-modal{
  display:none;position:fixed;inset:0;z-index:82;
  background:rgba(0,0,0,.72);align-items:center;justify-content:center;
  backdrop-filter:blur(2px);
}
#daily-modal.show{display:flex;}
#daily-box{
  position:relative;
  width:92%;max-width:600px;max-height:92vh;max-height:92dvh;overflow-y:auto;
  background:url('assets/img/ui/bg_parchment.webp') center/cover no-repeat;
  border-radius:6px;padding:14px 18px 14px;text-align:center;
  box-shadow:0 10px 36px rgba(0,0,0,.6),0 0 0 1px rgba(120,90,40,.4);
  animation:rewardPop .25s cubic-bezier(.22,.8,.42,1) both;
  font-family:'Cinzel',serif;color:#3a2408;font-size:11px;line-height:1.5;
}
#daily-close-btn{
  position:absolute;top:8px;right:8px;width:28px;height:28px;border-radius:50%;
  border:1px solid rgba(90,60,20,.6);background:rgba(58,36,8,.12);color:#3a2408;
  font-size:14px;font-weight:700;line-height:1;cursor:pointer;padding:0;
}
#daily-close-btn:hover{background:rgba(58,36,8,.25);}
#daily-title{font-family:'Cinzel Decorative',serif;font-size:18px;color:#4a2c08;letter-spacing:1.5px;font-weight:700;text-shadow:0 1px 0 rgba(255,255,255,.45);padding:0 30px;}
#daily-sub{margin:3px auto 10px;max-width:470px;font-size:11px;font-weight:600;color:#4a3214;}
#daily-track{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;}
#daily-track.daily-track-short{max-width:340px;margin:0 auto;}
.daily-tile{
  position:relative;border-radius:6px;padding:6px 2px 5px;
  background:linear-gradient(180deg,rgba(58,36,12,.9),rgba(30,18,6,.92));
  border:1px solid rgba(201,162,77,.55);box-shadow:inset 0 1px 0 rgba(255,220,150,.15),0 2px 4px rgba(0,0,0,.35);
  display:flex;flex-direction:column;align-items:center;gap:2px;min-width:0;
}
.daily-tile-day{font-size:9px;letter-spacing:.5px;color:var(--pch-gold-bright);font-weight:700;white-space:nowrap;}
.daily-tile-icons{display:flex;justify-content:center;gap:1px;height:30px;align-items:center;}
.daily-tile-icons img{height:26px;width:auto;filter:drop-shadow(0 2px 3px rgba(0,0,0,.6));}
.daily-tile-amt{font-size:11px;font-weight:700;color:#f0e6cc;white-space:nowrap;}
.daily-tile-big{background:linear-gradient(180deg,rgba(130,92,22,.95),rgba(50,32,6,.95));border-color:var(--pch-gold);}
.daily-tile-today{
  border-color:#ffd97a;background:linear-gradient(180deg,rgba(160,118,32,.95),rgba(70,46,10,.95));
  box-shadow:0 0 0 1px #ffd97a,0 0 14px rgba(255,190,70,.75);
  animation:dailyTodayPulse 1.6s ease-in-out infinite;
}
.daily-tile-claimed{opacity:.5;}
.daily-tile-check{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:26px;color:#9ad88a;text-shadow:0 2px 4px #000;font-weight:700;
}
@keyframes dailyTodayPulse{0%,100%{transform:scale(1);}50%{transform:scale(1.06);}}
@media (prefers-reduced-motion:reduce){.daily-tile-today{animation:none;}}
#daily-msg{min-height:16px;margin:8px 0 2px;font-size:11px;font-weight:600;color:#4a3214;}
#daily-msg.error{color:#9a2a1a;}
#daily-msg.success{color:#2f6b1f;font-weight:700;}
#daily-btns{display:flex;justify-content:center;margin-top:6px;}
#daily-claim-btn{
  width:150px;aspect-ratio:1931/656;
  background:url('assets/img/ui/gold_button.webp') center/100% 100% no-repeat;
  border:none;border-radius:0;padding:0 8px;
  display:flex;align-items:center;justify-content:center;
  color:#3a2408;font-weight:800;letter-spacing:.5px;white-space:nowrap;
  font-size:11px;font-family:'Cinzel',serif;text-shadow:0 1px 0 rgba(255,255,255,.4);
  cursor:pointer;transition:filter .12s,transform .08s;
}
#daily-claim-btn:hover{filter:brightness(1.1);}
#daily-claim-btn:active{transform:translateY(1px) scale(.97);}
#daily-claim-btn:disabled{filter:grayscale(.4) brightness(.9);cursor:default;}

/* ══ TITLE SCREEN REWARD BUTTONS (chest + ogre, top-left) ═════════ */
/* Stacked under .collection-rewards-btn. Round framed art (320px WebP,
   daily_reward_btn / brawler_reward_btn), a small pill label under each.
   .title-reward-ready (reward available) adds a gold glow pulse and a
   brighter label. Size scales with screen height so it stays clear of the
   centred logo/menu column on short landscape phones. */
#title-reward-btns{
  position:absolute;top:54px;left:8px;z-index:3;
  display:flex;flex-direction:column;align-items:center;gap:6px;
}
.title-reward-btn{
  display:flex;flex-direction:column;align-items:center;gap:1px;
  background:none;border:none;padding:0;cursor:pointer;
  transition:transform .08s,filter .12s;
}
.title-reward-btn img{
  width:clamp(54px,15vh,96px);height:auto;display:block;
  filter:drop-shadow(0 3px 6px rgba(0,0,0,.55));
}
.title-reward-btn:hover{filter:brightness(1.1);}
.title-reward-btn:active{transform:scale(.95);}
.title-reward-label{
  margin-top:-6px;padding:1px 8px;border-radius:999px;white-space:nowrap;
  background:rgba(20,10,4,.85);border:1px solid rgba(201,162,77,.6);
  font-family:'Cinzel',serif;font-size:9px;font-weight:700;letter-spacing:.3px;
  color:#e8dcc0;
}
.title-reward-label:empty{display:none;}
.title-reward-ready img{animation:titleRewardGlow 1.8s ease-in-out infinite;}
.title-reward-ready .title-reward-label{color:#ffd97a;border-color:#ffd97a;box-shadow:0 0 8px rgba(255,200,90,.6);}
@keyframes titleRewardGlow{
  0%,100%{filter:drop-shadow(0 3px 6px rgba(0,0,0,.55)) drop-shadow(0 0 0 rgba(255,200,90,0));transform:scale(1);}
  50%{filter:drop-shadow(0 3px 6px rgba(0,0,0,.55)) drop-shadow(0 0 10px rgba(255,200,90,.95));transform:scale(1.05);}
}
@media (prefers-reduced-motion:reduce){.title-reward-ready img{animation:none;filter:drop-shadow(0 0 8px rgba(255,200,90,.9));}}
/* Brawler hub: daily win bonus status line (_refreshBrawlerDailyLine) */
#brawler-daily-wins{
  color:var(--pch-ink);font-size:10px;font-weight:700;letter-spacing:.3px;text-align:center;
  max-width:300px;line-height:1.4;text-shadow:0 1px 2px rgba(255,255,255,.6);
}
#brawler-daily-wins:empty{display:none;}
/* ══ CREDITS MODAL (Settings -> Credits) ═══════════════════════ */
/* Same parchment-dark box + gold border as the bug report modal. */
#credits-modal{
  display:none;position:fixed;inset:0;z-index:82;
  background:rgba(0,0,0,.72);align-items:center;justify-content:center;
  backdrop-filter:blur(2px);
}
#credits-modal.show{display:flex;}
#credits-box{
  width:90%;max-width:420px;max-height:92vh;max-height:92dvh;overflow-y:auto;
  background:linear-gradient(180deg,#241a0e,#150f08);
  border:1px solid var(--pch-gold);border-radius:8px;
  padding:16px 18px 14px;text-align:center;
  box-shadow:0 8px 32px rgba(0,0,0,.85);
  animation:rewardPop .25s cubic-bezier(.22,.8,.42,1) both;
  font-family:'Cinzel',serif;color:#e8e0c8;font-size:12px;line-height:1.5;
}
#credits-title{font-family:'Cinzel Decorative',serif;font-size:17px;color:var(--pch-gold-bright);letter-spacing:1.5px;font-weight:700;margin-bottom:10px;text-shadow:0 2px 6px rgba(0,0,0,.8);}
.credits-section{padding:7px 0;border-top:1px solid rgba(201,162,77,.25);}
.credits-heading{color:var(--pch-gold-bright);font-size:10px;letter-spacing:1.5px;text-transform:uppercase;font-weight:700;margin-bottom:2px;}
.credits-line{font-family:'EB Garamond',Georgia,serif;font-size:15px;color:#f0e6cc;}
.credits-name{font-family:'Cinzel',serif;font-size:15px;font-weight:700;letter-spacing:.5px;}
.credits-small{font-size:12px;opacity:.75;}
#credits-copyright{margin-top:8px;padding-top:8px;border-top:1px solid rgba(201,162,77,.25);font-size:9px;opacity:.7;letter-spacing:.3px;}
#credits-btns{display:flex;justify-content:center;margin-top:10px;}
#credits-close-btn{font-family:'Cinzel',serif;font-size:10px;letter-spacing:.5px;padding:8px 18px;border-radius:4px;cursor:pointer;font-weight:700;background:linear-gradient(180deg,var(--pch-gold),#8a6820);border:1px solid var(--pch-bronze);color:#241a0e;}
#bug-report-mine-title{margin-top:12px;padding-top:8px;border-top:1px solid rgba(201,162,77,.3);color:var(--pch-gold-bright);font-weight:700;}
#bug-report-mine{font-size:10px;}
.bug-report-mine-row{display:flex;justify-content:space-between;gap:8px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.06);}
.bug-report-mine-row span:last-child{flex-shrink:0;color:var(--pch-gold-bright);}


/* ══ PrimeTestLab report fixes (Report 7969, Oct 2026) ═══════════════
   Kept together at the end of the file so they win over the earlier
   rules they correct. */

/* M-03: the chapter / level footer hints used the dark parchment ink
   (--pch-ink-soft) with a white glow, from when those screens sat on a
   parchment background. They now sit on dark painted art, so they get
   the same light gold + dark shadow treatment as the rest of the text
   there. */
#chp-footer-text,#lvl-footer-text{
  color:#e8d8b0;
  text-shadow:0 1px 3px rgba(0,0,0,.95),0 0 8px rgba(0,0,0,.75);
}
/* M-03: "Select a mode to begin" -- readable colour everywhere, and
   hidden on short landscape phones where it slides behind the bottom
   menu card (it doesn't add anything the buttons don't already say). */
#title-footer-text{
  color:#d8c8a0;
  text-shadow:0 1px 3px rgba(0,0,0,.95);
}
@media (orientation:landscape) and (max-height:500px){
  #title-footer-text{display:none;}
}

/* S-05: "Deploy your forces" gets a dark translucent banner behind it so
   it stays readable over bright grass -- same pill style as the Menu
   button next to it. */
@media (orientation:landscape){
  #place-header{
    background:rgba(20,14,8,.62);
    border:1px solid rgba(200,160,80,.5);
    border-radius:6px;
    padding:3px 14px;
  }
}

/* S-04: "Signed in with Google. No adventurer on this account yet..."
   note on the Welcome panel (see showNameEntryScreen in game.js). */
#name-entry-signed-in{
  font-family:'Cinzel',serif;font-size:12px;font-weight:700;
  color:#2f5a1e;text-align:center;line-height:1.35;
  max-width:92%;margin:0 auto 6px;
}

/* M-01: level names ran under the round "i" button. Measured at 915x412
   and 891x411: the title box spans the full content column (x175-344)
   while the button sits at x327-347, a 17px overlap. 24px of padding on
   both sides keeps the title clear of the button (and still centred);
   long names now wrap onto two balanced lines instead of hiding. */
#lvl-cards .lvl-card .lvl-name{
  padding-left:24px;padding-right:24px;
  line-height:1.15;text-wrap:balance;
}

/* M-02: Back as a normal item in the bottom HUD row (see index.html),
   sitting just right of the stats panel and lined up with its stat row. */
#hud-bottom-row #back-btn{
  position:static;left:auto;bottom:auto;transform:none;
  align-self:flex-end;flex-shrink:0;
  margin:0 0 12px 4px;
}

/* ══ Refer a Friend modal (Oct 2026) ══ same frame as the other small
   modals (#delete-account-modal etc.). */
#referral-modal{
  display:none;position:fixed;inset:0;z-index:80;
  background:rgba(0,0,0,.72);align-items:center;justify-content:center;
  backdrop-filter:blur(2px);
}
#referral-modal.show{display:flex;}
#referral-box{
  width:86%;max-width:360px;max-height:92vh;overflow-y:auto;
  background:linear-gradient(180deg,#241a0e,#150f08);
  border:1px solid var(--pch-gold);border-radius:8px;
  padding:16px 16px 14px;text-align:center;
  box-shadow:0 8px 32px rgba(0,0,0,.85);
  animation:rewardPop .25s cubic-bezier(.22,.8,.42,1) both;
}
#referral-title{font-family:'Cinzel Decorative',serif;font-size:13px;color:var(--pch-gold-bright);letter-spacing:1px;margin-bottom:10px;font-weight:700;}
#referral-body{font-family:'Cinzel',serif;color:#e8e0c8;}
.referral-label{font-size:10px;letter-spacing:1px;text-transform:uppercase;color:#c8a868;margin-bottom:4px;}
.referral-code{
  font-family:'Cinzel',serif;font-size:22px;font-weight:700;letter-spacing:4px;
  color:var(--pch-gold-bright);background:rgba(0,0,0,.35);
  border:1px solid rgba(200,160,80,.5);border-radius:6px;padding:6px 10px;margin:0 auto 8px;
  display:inline-block;user-select:all;-webkit-user-select:all;
}
.referral-actions,.referral-redeem{display:flex;gap:6px;justify-content:center;margin-bottom:8px;}
.referral-btn,#referral-close-btn{
  font-family:'Cinzel',serif;font-size:10px;letter-spacing:.5px;font-weight:700;
  padding:7px 14px;border-radius:4px;cursor:pointer;
  background:linear-gradient(180deg,var(--pch-bronze-light),var(--pch-bronze));
  border:1px solid var(--pch-gold);color:#2a1c0c;
}
.referral-btn:disabled{opacity:.5;cursor:default;}
.referral-btn:hover,#referral-close-btn:hover{filter:brightness(1.1);}
#referral-input{
  width:120px;font-family:'Cinzel',serif;font-size:14px;font-weight:700;letter-spacing:3px;
  text-transform:uppercase;text-align:center;
  background:#f0e6d0;color:#2a1c0c;border:1px solid var(--pch-gold);border-radius:4px;padding:5px 6px;
}
.referral-note{font-size:10px;line-height:1.5;color:#d8ccb0;margin-bottom:6px;}
.referral-note b{color:var(--pch-gold-bright);}
.referral-count{font-size:11px;font-weight:700;color:#e8dcc0;margin-bottom:4px;}
.referral-divider{height:1px;background:rgba(200,160,80,.35);margin:10px 0;}
.referral-msg{font-size:11px;line-height:1.45;color:#9fe08a;margin:6px 0 2px;}
.referral-msg.error{color:#f0a090;}
#referral-btns{margin-top:10px;display:flex;justify-content:center;}

/* ══ Settings: title-screen backdrop + scrolling (Oct 2026) ══════════
   Same image as the title screen (already cached by the time Settings
   opens, so no extra download), under a dark wash so the text stays
   readable. The list now scrolls: #settings-content is the full-width
   scroll area (so a swipe anywhere scrolls, not just over the column),
   its side padding keeps the 340px column centred, and the auto margins
   centre it vertically when everything fits -- unlike
   justify-content:center they never push the top out of reach once the
   list is taller than the screen. The Back button stays put because it
   sits outside the scroll area. */
#screen-settings{
  background:
    linear-gradient(180deg,rgba(12,8,3,.74),rgba(12,8,3,.86)),
    url('assets/img/backgrounds/title_bg.webp') center/cover no-repeat;
  justify-content:flex-start;
}
#settings-content{
  max-width:none;width:100%;height:100%;
  overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;
  padding:44px max(20px,calc(50% - 170px)) 20px;
  box-sizing:border-box;
}
/* (the Title button is the first child but absolutely positioned against
   #screen-settings, so it stays put while the list scrolls) */
#settings-content>#settings-title{margin-top:auto;}
#settings-content>#settings-version{margin-bottom:auto;}
#screen-settings .army-back-btn{z-index:2;}

/* Refer a Friend button: right-hand side of the title screen, mirroring
   #title-reward-btns on the left. Sits below the player bar (top:10px,
   about 56px tall). Reuses .title-reward-btn / .title-reward-label. */
#title-refer-btns{
  position:absolute;top:74px;right:8px;z-index:3;
  display:flex;flex-direction:column;align-items:center;gap:6px;
}
.referral-code.small{font-size:16px;letter-spacing:3px;padding:4px 8px;}

/* ══ Title-screen promo panel (Oct 2026) ══ opened from the promo
   buttons under Refer a Friend (TITLE_PROMOS in game.js). Built for short
   landscape phones: the image is capped by height so text + Close always
   fit on screen. */
#promo-modal{
  display:none;position:fixed;inset:0;z-index:80;
  background:rgba(0,0,0,.78);align-items:center;justify-content:center;
  backdrop-filter:blur(2px);
}
#promo-modal.show{display:flex;}
#promo-box{
  width:min(92vw,620px);max-height:94vh;overflow-y:auto;box-sizing:border-box;
  background:linear-gradient(180deg,#241a0e,#150f08);
  border:1px solid var(--pch-gold);border-radius:8px;
  padding:10px 10px 12px;text-align:center;
  box-shadow:0 8px 32px rgba(0,0,0,.85);
  animation:rewardPop .25s cubic-bezier(.22,.8,.42,1) both;
}
#promo-img{
  display:block;width:100%;height:auto;max-height:58vh;object-fit:contain;
  border-radius:5px;margin:0 auto 8px;background:#000;
}
#promo-title{font-family:'Cinzel Decorative',serif;font-size:14px;color:var(--pch-gold-bright);letter-spacing:1px;font-weight:700;margin-bottom:4px;}
#promo-text{font-family:'Cinzel',serif;font-size:11px;line-height:1.5;color:#e8e0c8;margin:0 8px 8px;}
#promo-btns{display:flex;justify-content:center;}
#promo-close-btn{
  font-family:'Cinzel',serif;font-size:10px;letter-spacing:.5px;font-weight:700;
  padding:7px 16px;border-radius:4px;cursor:pointer;
  background:linear-gradient(180deg,var(--pch-bronze-light),var(--pch-bronze));
  border:1px solid var(--pch-gold);color:#2a1c0c;
}
#promo-close-btn:hover{filter:brightness(1.1);}


/* ── BRAWLER BEST-OF-3 (Oct 2026) ─────────────────────────────────────
   Score chip beside the battle Menu/Zoom buttons, styled to match them
   (read-only, so no hover/active states). Spent tokens: units that
   fought in Game 1 sit out Game 2 -- more faded than a merely
   unaffordable card, with a "USED" tag so the two read differently. */
#brawler-score-chip{
  background:linear-gradient(180deg,var(--pch-walnut),var(--pch-walnut-dark));
  border:1px solid var(--pch-bronze);border-radius:4px;
  color:var(--pch-gold-bright);font-family:'Cinzel',serif;font-weight:700;
  font-size:10px;letter-spacing:.5px;padding:7px 9px;line-height:1;white-space:nowrap;
  box-shadow:0 2px 6px rgba(0,0,0,.4);pointer-events:none;
}
.roster-card.spent-card{opacity:.55;filter:grayscale(1) brightness(.65);position:relative;}
.roster-card.spent-card::after{
  content:'USED';position:absolute;left:50%;top:38%;transform:translate(-50%,-50%) rotate(-12deg);
  font-family:'Cinzel',serif;font-weight:700;font-size:11px;letter-spacing:1px;
  color:#f3e6c4;background:rgba(40,20,10,.85);border:1px solid rgba(200,160,80,.8);
  padding:2px 6px;border-radius:3px;pointer-events:none;
}
@media (orientation:landscape){
  #brawler-score-chip{background:rgba(20,14,8,.55);border:1px solid rgba(200,160,80,.55);}
}
@media (orientation:landscape) and (max-height:520px) and (hover:none) and (pointer:coarse){
  #brawler-score-chip{padding:5px 7px;font-size:9px;}
}


/* ══ OCT 2026 ROUND: boss dialogue, chapter text, Tactician title, Wait undo ══ */

/* Boss / post-level dialogue (Necromancer, Goblin King, Gaia, Ivory Queen,
   Dragon Lord, King Barron, Claudia, Ferrus, Vxd -- all share #necro-*):
   now the same big centred card as Gregor's pre-battle intro
   (#battle-intro-modal / #bim-box) instead of a small strip at the bottom.
   Keeps its purple banner so bosses still read as "not Gregor". */
#necro-overlay{
  align-items:center;justify-content:center;padding:0;
  pointer-events:auto;
  background:rgba(8,0,14,.86);
  -webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);
}
#necro-box{
  flex-direction:column;align-items:center;gap:10px;
  width:86%;max-width:380px;
  background:linear-gradient(180deg,#140a1c,#0a0410);
  border-style:solid;border-width:18px;border-image-repeat:stretch;
  border-image-slice:70 fill;border-image-width:18px;
  border-image-source:url('assets/img/ui/shop_banner_purple.webp');
  padding:20px 18px 18px;
  box-shadow:0 0 40px rgba(140,40,200,.2),0 8px 32px rgba(0,0,0,.9);
  animation:rewardPop .4s cubic-bezier(.22,.8,.42,1) both;
}
#necro-portrait{width:110px;height:110px;border-radius:6px;}
#necro-bubble{
  flex:none;width:100%;
  background:none;border:none;border-image:none;box-shadow:none;padding:0;
  display:flex;flex-direction:column;align-items:center;gap:10px;
}
#necro-name{font-size:11px;letter-spacing:2px;margin:0;text-align:center;}
#necro-text{
  font-family:'Cinzel',serif;font-style:normal;font-size:11px;
  color:#e2cdf0;text-align:center;line-height:1.7;min-height:80px;letter-spacing:.3px;
}
#necro-dismiss{
  display:inline-block;margin-top:4px;text-align:center;
  background:linear-gradient(180deg,#24103a,#12081e);
  border:1px solid #6a20a0;border-radius:4px;
  color:#c890f0;font-size:10px;letter-spacing:1px;padding:8px 26px;
}
#necro-dismiss:hover{color:#e8c8ff;border-color:#a050e0;}

/* Chapter Select: the white description text, about 20% larger. Three
   lines allowed instead of two so the bigger text isn't cut short. */
.chp-card .lvl-flavor{font-size:11.5px;}
@media (orientation:landscape){
  .chp-card .lvl-flavor{font-size:clamp(9.5px,3.55cqw,23px);-webkit-line-clamp:3;}
}
@media (orientation:landscape) and (max-height:520px) and (hover:none) and (pointer:coarse){
  .chp-card .lvl-flavor{-webkit-line-clamp:3;}
}

/* Tactician deploy title ("TACTICIAN – DEPLOY YOUR FORCES") in red, on
   both the parchment (portrait) and dark (landscape) headers. */
#place-title.place-title-tactician{color:#d0301c !important;}

/* Wait undo window: Wait becomes "Cancel Wait" with a gold bar draining
   over 1.5s; the other actions dim and can't be pressed meanwhile. */
#am-wait.wait-pending{
  background:linear-gradient(180deg,rgba(150,40,20,.45),rgba(60,10,5,.6));
  border-color:var(--pch-gold);
  box-shadow:0 0 0 1px var(--pch-gold),0 0 10px rgba(240,190,80,.35);
  overflow:hidden;
}
#am-wait.wait-pending .am-name{color:var(--pch-gold-bright);}
.am-wait-bar{
  position:absolute;left:4px;right:4px;bottom:3px;height:3px;
  background:rgba(0,0,0,.45);border-radius:2px;overflow:hidden;pointer-events:none;
}
.am-wait-bar span{
  display:block;height:100%;width:100%;transform-origin:left center;
  background:linear-gradient(90deg,var(--pch-gold),var(--pch-gold-bright));
  animation:amWaitDrain 1.5s linear forwards;
}
@keyframes amWaitDrain{from{transform:scaleX(1);}to{transform:scaleX(0);}}
#action-menu.wait-pending .am-item:not(#am-wait){opacity:.35;pointer-events:none;}

/* ══ OCT 2026 ROUND 2 ══ */
/* Gregor's in-battle speech-bubble hints: slightly larger text. */
#oldman-box{max-width:380px;}
#oldman-name{font-size:9.5px;}
#oldman-text{font-size:12px;line-height:1.45;}
/* Brawler hub: the line under "Enter Arena" was dark ink on a dark
   background -- now light parchment with a dark outline so it reads. */
#brawler-find-hint{
  color:#f0e6d0;font-size:10px;
  text-shadow:0 1px 2px rgba(0,0,0,.95),0 0 6px rgba(0,0,0,.8);
}

/* Marketing Mode (dev accounts only): bottom HUD fades out while an
   action is being picked/played, so promo footage shows the full board. */
#screen-battle.mkt-clean #hud-bottom-row{opacity:0;pointer-events:none;transition:opacity .2s ease;}

/* ══ ARMY BUILDER CARD (Oct 2026): token panel art + image +/- buttons ══
   Save these alongside the other UI art:
     assets/img/ui/army_card_panel.webp  (360x354)
     assets/img/ui/army_btn_minus.webp   (96x89)
     assets/img/ui/army_btn_plus.webp    (96x85)
   Layout follows the panel art: token centred in the upper area, minus +
   "own" to its left, plus + "max" to its right, and the unit name and
   "In army" count below the panel's gold divider line (~79% down). Sizes
   scale with the card (cqw), so it holds together at any column width. */
.army-edit-card{
  position:relative;display:block;aspect-ratio:360/354;padding:0;gap:0;
  container-type:inline-size;
  background:url('assets/img/ui/army_card_panel.webp') center/100% 100% no-repeat;
  border:none;border-radius:0;box-shadow:none;
}
.army-edit-card.has-copies{border:none;box-shadow:none;filter:drop-shadow(0 0 5px rgba(255,200,80,.6));}
.army-edit-card .army-edit-img{
  /* Box sized to EXACTLY the token's own shape (tokens are 1:1.15), so
     there's no hidden letterbox space under it: 44% card width x 1.15 =
     51.5% of the card's height (card ratio 360:354). Same visible spot as
     before -- it used to be a taller 64% box with the token centred in it,
     which left ~6% of empty space below the art. */
  position:absolute;left:50%;top:16.3%;transform:translateX(-50%);
  width:44%;height:51.5%;object-fit:contain;
}
.army-edit-side{
  position:absolute;top:22%;width:20%;
  display:flex;flex-direction:column;align-items:center;gap:6%;
}
.army-edit-side.left{left:7.5%;}
.army-edit-side.right{right:7.5%;}
.army-edit-card .army-step-btn{
  width:100%;height:auto;aspect-ratio:96/87;border:none;border-radius:0;padding:0;
  background:center/100% 100% no-repeat;font-size:0;cursor:pointer;
}
.army-edit-card .army-step-btn.minus{background-image:url('assets/img/ui/army_btn_minus.webp');}
.army-edit-card .army-step-btn.plus{background-image:url('assets/img/ui/army_btn_plus.webp');}
.army-edit-card .army-step-btn:disabled{opacity:.3;filter:grayscale(.6);}
.army-edit-own,.army-edit-max{
  font-family:'Cinzel',serif;font-size:clamp(6px,5.2cqw,11px);color:#c8b090;
  white-space:nowrap;text-align:center;line-height:1;
}
.army-edit-card.locked .army-edit-own{color:#d06030;white-space:normal;}
.army-edit-foot{
  /* Name tucked straight under the token (which ends at ~67.8%), with
     "In army" directly beneath it (Oct 2026 tweak). */
  position:absolute;left:10%;right:10%;top:70%;bottom:6%;
  display:flex;flex-direction:column;align-items:center;justify-content:flex-start;gap:1px;
}
.army-edit-card .army-edit-name{font-size:clamp(6px,5.6cqw,12px);color:#e8dcc0;line-height:1.05;}
.army-edit-card .army-step-count{font-size:clamp(6px,5cqw,11px);min-width:0;line-height:1.05;
  /* Pushes ONLY "In army" back down (~80% of its own line height), so it
     sits where it was before the name was moved up. The name is untouched. */
  margin-top:4.2cqw;}
