:root{color-scheme:dark;font:18px/1.5 system-ui,sans-serif;background:#101923;color:#f7f4eb}*{box-sizing:border-box}body{margin:0;min-height:100dvh}header,main,footer,#connection{max-width:62rem;margin:auto;padding:1.25rem}header{display:flex;justify-content:space-between;gap:1rem;flex-wrap:wrap;border-bottom:1px solid #697885}header strong{font-size:1.7rem;letter-spacing:.12em}header span{color:#cbd5df}h1{font-size:clamp(1.8rem,5vw,3rem);line-height:1.15}nav,.actions{display:flex;gap:1rem;flex-wrap:wrap}button,a,input{font:inherit}button{padding:.8rem 1.2rem;border:1px solid #cbb777;background:#24364a;color:inherit;border-radius:.3rem;min-height:48px;cursor:pointer}button:disabled{opacity:.65;cursor:default}a{color:#f1d88d}footer{display:flex;gap:2rem}:focus-visible{outline:3px solid #ffe28b;outline-offset:4px}
/* [tabindex="-1"] elements (route headings, [role=alert], card-detail panels) are focused
   programmatically for screen-reader route-change announcement only - a real keyboard user can
   never Tab to them (tabindex=-1 removes them from the tab order), so the visible ring here is
   never a genuine "here's your keyboard focus" cue, only an unwanted side effect: on a true fresh
   page load (no prior pointer interaction yet) the browser's own focus-visible heuristic shows the
   ring anyway, and a 3px gold box around the page's own h1 reads as a warning/alert on literally
   every new player's first paint, not a title. Interactive controls (button/input/a) keep the ring. */
[tabindex="-1"]:focus-visible{outline:none}
.surface{border-left:4px solid #b7cde2;background:#1c2b3b;padding:1rem;margin:1rem 0}
/* Every surface() kind rendered identically - a real blocking error (fatal) looked exactly like a
   neutral informational status (unavailable/loading/offline), which is both a missed severity cue
   and the specific reason Privacy's "production policies are awaiting approval" notice (a calm
   status, role=status not alert) read as loudly as a warning. Recolors only - same component, same
   text, same ARIA role per kind, calibrated by severity, not a one-off Privacy-only patch. */
.surface[data-state=fatal]{border-left-color:#8b2635}
.surface[data-state=recoverable]{border-left-color:#cbb777}
.surface[data-state=unavailable],.surface[data-state=loading],.surface[data-state=offline]{
 border-left-color:#3a4a58;background:#16222f}
label{display:block;margin:1.5rem 0}input{width:1.4rem;height:1.4rem;margin-right:.8rem}.skip{position:absolute;top:-100px}.skip:focus{top:0;background:#101923;padding:1rem}main{min-height:65vh}p{max-width:65ch}html[data-reduced-motion=true] *{scroll-behavior:auto;animation:none;transition:none}@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto}} @media(max-width:480px){nav{display:grid;grid-template-columns:1fr;width:100%}header,main,footer{padding:1rem}}
/* The tagline is a secondary descriptor, not a second headline - it was inheriting the 18px body
   size, reading as near-equal weight to the OUT3 wordmark and, on mobile, wrapping to two lines
   and pushing real content down before the player has seen anything else. */
header span{font-size:.85rem;align-self:center}
@media(max-width:480px){header span{font-size:.78rem}}

/* OUT3 Web Design System (Phase 3): foundation tokens. Additive only - the base reset above
   and every selector below still hold; these give later rules a shared scale instead of more
   arbitrary literals. */
:root{--sp-1:.25rem;--sp-2:.5rem;--sp-3:.75rem;--sp-4:1rem;--sp-5:1.5rem;--sp-6:2rem;--sp-7:3rem;
 --radius-sm:.35rem;--radius-md:.6rem;--radius-lg:1rem;
 --surface-0:#0c141d;--surface-1:#16222f;--surface-2:#1f3040;
 --border-subtle:#3a4a58;--border-strong:#cbb777;
 --text-secondary:#aab8c4;--text-muted:#7c8a97;
 --shadow-sm:0 .15rem .4rem rgba(0,0,0,.35);--shadow-md:0 .5rem 1.4rem rgba(0,0,0,.45);
 --font-display:Georgia,'Times New Roman',serif;}
body{background:radial-gradient(ellipse 80% 55% at 50% -10%,#1b2a38 0%,var(--surface-0) 62%) var(--surface-0) fixed;}
header{background:linear-gradient(180deg,rgba(203,183,119,.07),transparent)}
header strong{font-family:var(--font-display);letter-spacing:.18em}
header strong::before{content:'◆';margin-right:.5rem;color:var(--border-strong);font-size:.75em}
/* OUT³ wordmark (Docs/Design/OUT3BrandIdentity.md §2): "OUT" + a separate, deliberately smaller,
   raised "3" - a real composed mark, not the plain Unicode superscript character and not plain
   "OUT3" prose. Mirrors CardVisualBuilder.BuildOut3Wordmark's own two-element composition (smaller
   size + negative top offset) rather than retyping a literal "³" glyph. */
.wordmark-3{font-size:.6em;position:relative;top:-.5em;margin-left:.05em}
h1{font-family:var(--font-display);font-weight:700;margin-top:0}
main{padding-top:1.75rem}
button{border-radius:var(--radius-md);box-shadow:var(--shadow-sm);transition:transform .15s ease,box-shadow .15s ease,background-color .15s ease}
button:hover:not(:disabled){transform:translateY(-1px);box-shadow:var(--shadow-md)}
button:active:not(:disabled){transform:translateY(0)}
html[data-reduced-motion=true] button,html[data-reduced-motion=true] button:hover:not(:disabled){transition:none;transform:none}
.btn-primary{box-shadow:0 .4rem 1rem rgba(255,226,139,.25),var(--shadow-sm)}
.btn-primary:hover:not(:disabled){box-shadow:0 .5rem 1.4rem rgba(255,226,139,.35),var(--shadow-md);transform:translateY(-1px)}

section { margin-block: 1rem; padding: .75rem; border: 1px solid #665848; border-radius: .5rem; overflow-wrap: anywhere; }
section[aria-label="Your hand"] { display: flex; flex-wrap: wrap; gap: .5rem; }
section[aria-label="Current choice"] label { display: block; margin-block: .6rem; }
button:focus-visible, input:focus-visible { outline: 3px solid #edca73; outline-offset: 3px; }

button[data-guided=true]:not(:disabled) { border-width: 3px; }

/* Card presentation: same brand tokens as client/styles.css (Theme.cs/CardVisualTokens.cs
   derived) - card family identity is expressed through these colors everywhere in the product,
   not invented locally. */
/* --ink/--parchment match CardVisualTokens.cs's ParchmentText/Parchment exactly (#211F1A/#EDE6D6)
   - the real production card-body colours, not an approximation - every card surface in the
   product (the OUT³ frame below included) shares these two values. */
:root{--food:#4c7a3d;--wood:#5b7042;--iron:#71797e;--infantry:#4a5568;--archers:#2f6b5e;--cavalry:#8b2635;--wall:#8c7b6b;--maurya:#b33a3a;--gupta:#d4a72c;--chola:#2e6e9e;--mughal:#3e7a4c;--maratha:#d2691e;--rajput:#7a4a8f;--ink:#211F1A;--parchment:#EDE6D6}
@keyframes hand-card-arrive{0%{opacity:0;transform:translateY(10px) scale(.96)}100%{opacity:1;transform:translateY(0) scale(1)}}
.card{display:flex;flex-direction:column;align-items:center;text-align:center;min-width:0;padding:.75rem;background:var(--parchment);color:var(--ink);border:2px solid var(--iron);border-radius:.5rem;box-shadow:0 .3rem .8rem #0003;min-height:48px}
.card:hover:not(:disabled){background:#fffaf0}
.card:disabled{opacity:.7}
.card[aria-pressed=true]{outline:3px solid #ffe28b;outline-offset:2px}
.card[data-identity=Food]{border-color:var(--food)}
.card[data-identity=Gold]{border-color:#cbb777}
.card[data-identity=Wood]{border-color:var(--wood)}
.card[data-identity=Iron]{border-color:var(--iron)}
.card[data-identity=Infantry]{border-color:var(--infantry)}
.card[data-identity=Archers]{border-color:var(--archers)}
.card[data-identity=Cavalry]{border-color:var(--cavalry)}
.card[data-identity=WallFortification]{border-color:var(--wall)}
.card[data-identity=Maurya]{border-color:var(--maurya)}
.card[data-identity=Gupta]{border-color:var(--gupta)}
.card[data-identity=Chola]{border-color:var(--chola)}
.card[data-identity=Mughal]{border-color:var(--mughal)}
.card[data-identity=Maratha]{border-color:var(--maratha)}
.card[data-identity=Rajput]{border-color:var(--rajput)}
.card img{width:3.5rem;height:3.5rem;object-fit:contain;margin-block:.5rem}
.card-kind{font-size:.65rem;text-transform:uppercase;letter-spacing:.12em}
.card strong{font-size:1rem}
.card-detail{font-size:.72rem;margin-top:.35rem}
.card-cost,.card-availability{font-size:.75rem;margin-top:.35rem}
.card-availability{font-weight:650}
/* Finished-card art (CardVisualBuilder.cs parity): every production card fills the same one
   illustration slot inside the full parchment/border/title frame - Dynasty/Military/Action with a
   painted scene (card--illustrated, cover-cropped), Resource with its own resource icon
   (card--icon-art, contain + a colored field so a flat glyph is never cropped or distorted). Both
   share this frame/strip layout; only the image treatment differs below. Wild Dynasty cards (no
   per-card art registered, same as Android) are the sole remaining user of the plain stacked-text
   layout above. */
.card.card--illustrated,.card.card--icon-art{padding:0;overflow:hidden;align-items:stretch;text-align:left;min-height:12.5rem;position:relative}
/* .card .card-illustration (two classes) deliberately outweighs the plain ".card img" rule above
   (one class + one type = higher specificity than one class alone) that would otherwise clamp
   this to the small 3.5rem icon size - a real bug this caught via screenshot (the illustration
   rendered as a tiny square instead of filling the card). */
.card .card-illustration{width:100%;height:auto;aspect-ratio:5/7;object-fit:cover;margin:0;flex-shrink:0}
.card--icon-art .card-illustration{object-fit:contain;padding:1.4rem;background:#e9e1cc}
.card--icon-art[data-identity=Food] .card-illustration{background:#dce6d4}
.card--icon-art[data-identity=Gold] .card-illustration{background:#ede2c3}
.card--icon-art[data-identity=Wood] .card-illustration{background:#dde0d3}
.card--icon-art[data-identity=Iron] .card-illustration{background:#dcdcdc}
.card--illustrated .card-kind,.card--icon-art .card-kind{position:absolute;top:.35rem;left:.45rem;background:rgba(10,15,20,.72);color:#f1d88d;
 padding:.1rem .4rem;border-radius:.25rem;letter-spacing:.08em}
.card--illustrated strong,.card--icon-art strong{padding:.4rem .5rem 0;color:var(--ink);font-size:.88rem;line-height:1.2}
.card--illustrated .card-detail,.card--icon-art .card-detail{padding:0 .5rem;color:#4a3d2c}
.card--illustrated .card-cost,.card--illustrated .card-availability,.card--icon-art .card-cost,.card--icon-art .card-availability{padding:0 .5rem .45rem}
.card--illustrated .card-availability,.card--icon-art .card-availability{padding-bottom:.55rem}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}

/* Structured board (ported from HudSection.cs/ActiveSeatBoardView.cs/OpponentBoardView.cs). */
.hud{padding:.6rem 1rem;background:#1c2b3b;border:1px solid #665848;border-radius:.5rem;margin-block:.5rem;display:flex;flex-wrap:wrap;align-items:center;gap:.6rem 2rem}
.hud-round{font-size:1.1rem;font-weight:700}
.hud-turn{font-weight:700;color:#ffe28b}
/* Turn transition cue (Section 23): a one-shot emphasis the instant the active player actually
   changes (tracked client-side against the previous paint) - a routine Tier-1 event, never a
   blocking modal every turn. */
.hud-turn[data-just-changed=true]{animation:turn-changed-flash .9s ease-out}
@keyframes turn-changed-flash{0%{text-shadow:0 0 0 transparent}30%{text-shadow:0 0 10px rgba(255,226,139,.85)}100%{text-shadow:0 0 0 transparent}}
.phase-stepper{display:flex;align-items:center;gap:.4rem;margin-left:auto}
.phase-step{display:flex;flex-direction:column;align-items:center;gap:.2rem;min-width:3.2rem}
.phase-dot{width:.6rem;height:.6rem;border-radius:50%;background:#4a5568;transform:rotate(45deg)}
.phase-step[data-current=true] .phase-dot{background:#ffe28b;width:.8rem;height:.8rem}
.phase-step[data-completed=true] .phase-dot{background:#cbb777;opacity:.7}
.phase-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.08em;color:#8a97a5}
.phase-step[data-current=true] .phase-label{color:#f7f4eb;font-weight:700}
.hud-play-budget{font-size:.8rem;color:#cbd5df;background:#101923;border:1px solid #665848;border-radius:1rem;padding:.3rem .7rem}
@media(max-width:480px){.hud-play-budget{order:1;width:100%}}
/* Table (Android V2 parity: GameScreenV2TablePresentation.cs's own TABLE panel). */
/* The global section{border...} rule sized this to the full battle-center column width while its
   real content (one draw-pile chip) is much narrower, reading as a mostly-empty bordered box on
   desktop widths - shrink-wrap it to its content instead, matching the draw-pile's own compact,
   already-correct sizing. */
/* Table/board (CEO Gameplay Screen parity: Docs/QA/OUT3-CEOGameplayScreen/Final/Pixel7/
   pixel7-ceo-commercial-final3.png + ReferenceConvergence/MeasuredReferenceSpec.md - a real
   physical draw-pile card-back (not a text box), "YOUR BOARD" as six dynasty rows with real
   card-slot rectangles (not a 2-column grid), and the Rival Empire's lighter-weight six-dynasty
   pip strip for the opponent. Dynasty color tokens are the same ones used everywhere else in the
   product (--maurya/--gupta/etc, defined earlier in this file). */
.table-area{margin-block:.5rem;display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-evenly;gap:1rem;
 padding:.75rem .5rem;position:relative}
/* OUT3 compass watermark - the same restrained table-surface motif the reference uses behind the
 piles, reusing Theme's own gold token rather than a new image asset. */
.table-area::before{content:'';position:absolute;inset:10% 30%;pointer-events:none;opacity:.05;
 background:radial-gradient(circle,var(--border-strong) 0%,transparent 70%)}
/* A persistent small caption above each pile (matching the wireframe's DRAW/DISCARD labels) -
   without it, a pile's identity (resource vs action discard) was only legible while still empty,
   since a real card landing there fills the same visual slot the empty-state's own text label used. */
.pile-group{display:flex;flex-direction:column;align-items:center;gap:.3rem}
.pile-caption{font-size:.65rem;text-transform:uppercase;letter-spacing:.1em;color:var(--text-muted)}
.draw-pile{position:relative;display:inline-flex;align-items:flex-end;z-index:1}
.draw-pile-art{width:6.5rem;border-radius:.4rem;box-shadow:0 .5rem 1.2rem rgba(0,0,0,.5);display:block}
.draw-pile-count{position:absolute;left:50%;bottom:-.6rem;transform:translateX(-50%);background:#101923;
 color:#ffe28b;border:1px solid var(--border-strong);border-radius:999px;padding:.15rem .6rem;font-size:.85rem;font-weight:700}
/* Discard piles: mobile genuinely has two (Resource/Action), each face-up once a card has landed
 there - GameScreenV2TablePresentation.cs parity, not a Web simplification. */
.discard-pile{position:relative;display:flex;flex-direction:column;align-items:center;gap:.4rem;z-index:1;min-width:5.5rem}
.discard-pile .card{width:5.2rem}
.discard-pile-top{pointer-events:auto}
.discard-pile-empty{width:5.2rem;height:7.3rem;border:1px dashed var(--border-subtle);border-radius:.4rem;
 display:flex;align-items:center;justify-content:center;text-align:center;padding:.3rem}
.discard-pile-empty span{font-size:.62rem;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}
.discard-pile-count{background:#101923;color:#ffe28b;border:1px solid var(--border-strong);border-radius:999px;
 padding:.1rem .55rem;font-size:.78rem;font-weight:700}
.board-area{margin-block:1rem}
.area-heading{font-family:var(--font-display);font-size:.95rem;margin:0 0 .6rem;color:#cbd5df;text-transform:uppercase;letter-spacing:.1em}
/* .zone is still used directly by buildResultDynasties' match-end "Completed dynasties" panel -
   the only remaining caller of the zone() helper now that Own/Opponent areas build their own
   board-row/rival-empire markup instead. */
.zone{border:1px solid var(--zone-accent,#665848);border-radius:.5rem;padding:.6rem .75rem;background:#182534;margin-block:1rem}
.zone-label{margin:0 0 .4rem;font-size:.7rem;text-transform:uppercase;letter-spacing:.1em;color:var(--zone-accent,#cbd5df)}
.zone-empty{color:#5f6b78;font-size:.85rem}
/* Rival Empire's compact six-dynasty pip strip - a single row, not Your Board's physical slots. */
.dynasty-grid{display:flex;flex-wrap:wrap;gap:.5rem}
.dynasty-chip{display:flex;flex-direction:column;align-items:center;gap:.15rem;font-size:.68rem;color:#8a97a5;min-width:3.4rem}
.dynasty-chip img{width:1.4rem;height:1.4rem;object-fit:contain}
.dynasty-pips{font-size:.6rem;letter-spacing:.1em;color:#5f6b78}
.dynasty-chip[data-complete=true]{color:#f7f4eb;font-weight:700}
.dynasty-chip[data-complete=true] .dynasty-pips{color:inherit}
.dynasty-chip[data-identity=Maurya][data-complete=true]{color:#b33a3a}
.dynasty-chip[data-identity=Gupta][data-complete=true]{color:#d4a72c}
.dynasty-chip[data-identity=Chola][data-complete=true]{color:#2e6e9e}
.dynasty-chip[data-identity=Mughal][data-complete=true]{color:#3e7a4c}
.dynasty-chip[data-identity=Maratha][data-complete=true]{color:#d2691e}
.dynasty-chip[data-identity=Rajput][data-complete=true]{color:#7a4a8f}
/* Your Board's six physical dynasty rows: crest, name, progress, three card-slot rectangles. */
.board-rows{display:flex;flex-direction:column;gap:.35rem}
.board-row{display:flex;align-items:center;gap:.5rem;padding:.35rem .5rem;border-left:3px solid var(--row-accent,#665848);
 background:rgba(255,255,255,.03);border-radius:.3rem}
.board-row-crest{width:1.9rem;height:1.9rem;object-fit:contain;flex-shrink:0}
.board-row-name{font-weight:700;font-size:.82rem;min-width:4.6rem;color:var(--row-accent,#cbd5df)}
.board-row-progress{font-size:.72rem;color:#8a97a5;min-width:2rem}
.board-row-slots{display:flex;gap:.25rem;margin-left:auto}
.board-row-slot{width:1.1rem;height:1.45rem;border-radius:.2rem;border:1px solid var(--row-accent,#665848);overflow:hidden;position:relative}
.board-row-slot[data-filled=true]{background:var(--row-accent,#cbb777)}
/* Real earned-card thumbnail inside a filled slot (the dynasty-place illustration this exact card
   id resolves to) - falls back to the plain accent-color fill above when no art has loaded yet. */
.board-row-slot-art{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.board-row[data-identity=Maurya]{--row-accent:var(--maurya)}
.board-row[data-identity=Gupta]{--row-accent:var(--gupta)}
.board-row[data-identity=Chola]{--row-accent:var(--chola)}
.board-row[data-identity=Mughal]{--row-accent:var(--mughal)}
.board-row[data-identity=Maratha]{--row-accent:var(--maratha)}
.board-row[data-identity=Rajput]{--row-accent:var(--rajput)}
/* Dynasty progress tiers (game-feel milestone, Section 10-13): tier 2 ("one card from
   completion") is the real strategic-tension moment - a restrained pulse on the row's own
   border/background using its own dynasty colour, readable without counting pips. Tier 3 is the
   static completed state (unchanged styling); `data-just-completed` is the one-shot signature-
   moment flourish fired only on the exact paint the third card lands - a brief glow sweep, not an
   infinite animation, so it reads as an event rather than a permanent decoration. */
.board-row[data-tier="2"]{border-left-width:4px;box-shadow:0 0 0 1px var(--row-accent,#665848) inset;
 animation:dynasty-near-pulse 2.2s ease-in-out infinite}
@keyframes dynasty-near-pulse{0%,100%{background:rgba(255,255,255,.03)}50%{background:color-mix(in srgb,var(--row-accent,#665848) 16%,rgba(255,255,255,.03))}}
.board-row[data-just-completed=true]{animation:dynasty-complete-flourish 1.1s ease-out}
@keyframes dynasty-complete-flourish{
 0%{box-shadow:0 0 0 0 var(--row-accent,#cbb777);background:rgba(255,255,255,.03)}
 30%{box-shadow:0 0 0 6px color-mix(in srgb,var(--row-accent,#cbb777) 55%,transparent);background:color-mix(in srgb,var(--row-accent,#cbb777) 35%,rgba(255,255,255,.03))}
 100%{box-shadow:0 0 0 0 transparent;background:rgba(255,255,255,.03)}
}
.dynasty-chip[data-tier="2"]{animation:dynasty-pip-near-pulse 2.2s ease-in-out infinite}
@keyframes dynasty-pip-near-pulse{0%,100%{opacity:1}50%{opacity:.65}}
.dynasty-chip[data-tier="2"] .dynasty-pips{color:var(--text-primary,#f7f4eb)}
/* Military/Resources: a compact labelled two-up dashboard beneath the six rows. */
.board-dashboard{display:grid;grid-template-columns:1fr 1fr;gap:.75rem;margin-top:.75rem;padding-top:.6rem;
 border-top:1px solid var(--border-subtle,#3a4a58)}
.board-dashboard-group{min-width:0}
.military-row,.resource-row{display:flex;flex-wrap:wrap;gap:.5rem}
.military-item,.resource-item{display:flex;flex-direction:column;align-items:center;gap:.2rem;font-size:.75rem;min-width:2.6rem}
.military-item img,.resource-item img{width:1.6rem;height:1.6rem;object-fit:contain}
.opponent-hand-count{font-size:.8rem;color:#8a97a5}
/* Rival Empire: identity + dynasty pip strip + public military, one compact row. */
.rival-empire-row{display:flex;flex-wrap:wrap;align-items:center;gap:1rem}
.rival-identity{font-family:var(--font-display);font-weight:700;color:#f7f4eb;flex-shrink:0}
.rival-dynasty-block{flex:1 1 auto;min-width:0}
.rival-military{flex-shrink:0}
/* Military threat tiers (0=none counted as threat 0 by the Math.ceil(0/2)=0 call site, 1=light,
   2=building, 3=dangerous) - a restrained colour-weight shift, never a warning icon or text
   telling the player what to do. */
.rival-military[data-threat="2"] .zone-label{color:#e3a356}
.rival-military[data-threat="3"] .zone-label{color:#e3808f;font-weight:700}
.rival-military[data-threat="3"] .military-item img{filter:drop-shadow(0 0 3px rgba(227,128,143,.5))}
@media(max-width:480px){.hud{gap:.5rem}.phase-stepper{margin-left:0;width:100%;justify-content:space-between}
 .board-dashboard{grid-template-columns:1fr}}

/* Battle table (mobile parity: GameScreenV2Layout.cs/GameScreenV2RightColumn.cs). Mobile keeps
   the opponent compact and secondary (a TopBar strip, never the player's own visual weight), and
   gives the player's own board a distinct "YOUR BOARD" sidebar beside a center Table+Hand column -
   not a flat stack treating both players identically. At <=1000px this collapses to one column
   (opponent strip, then center, then sidebar) - close to mobile's own single-screen composition. */
.battle-opponent-strip{margin-block:0 .6rem;opacity:.92}
.battle-opponent-strip .board-area{margin-block:0;padding:.6rem .75rem;background:var(--surface-1);
 border:1px solid var(--border-subtle);border-radius:var(--radius-md)}
.battle-opponent-strip .area-heading{font-size:.82rem}
.battle-table{display:grid;grid-template-columns:1fr 19rem;gap:1rem;align-items:start}
.battle-center{display:flex;flex-direction:column;gap:1rem;min-width:0}
.battle-sidebar{background:var(--surface-1);border:1px solid var(--border-strong);border-radius:var(--radius-lg);
 padding:1rem;display:flex;flex-direction:column;gap:.5rem;min-width:0}
.battle-sidebar .board-area{margin-block:0}
/* Reuses buildOwnArea's own "Your area" heading (no duplicate DOM label) with the gold-tab
   pill treatment mobile's "YOUR BOARD" header uses, via the existing .area-heading class. */
.battle-sidebar .area-heading{align-self:center;font-family:var(--font-display);font-size:.72rem;
 letter-spacing:.14em;text-transform:uppercase;color:var(--border-strong);border:1px solid var(--border-strong);
 border-radius:999px;padding:.25rem .85rem;margin:0 0 .5rem}
.battle-turn-controls{display:flex;gap:.5rem;flex-wrap:wrap;align-items:stretch;margin-top:auto;padding-top:.75rem;
 border-top:1px solid var(--border-subtle)}
/* Draw/End Turn are large terminal actions flanking the Action Economy's 1/2/3 progression (CEO
   Gameplay Screen parity) - not three equal stacked buttons. */
.draw-control,.end-turn-control{flex:1 1 5.5rem;font-weight:700}
.end-turn-control{background:#32101e;border-color:#d2140f}
.action-economy{flex:1 1 7rem;display:flex;flex-direction:column;gap:.3rem;justify-content:center}
.economy-node{display:flex;align-items:center;gap:.5rem;padding:.3rem .5rem;border-radius:.3rem;
 border:1px solid var(--border-subtle,#3a4a58);opacity:.6}
.economy-node-number{width:1.3rem;height:1.3rem;border-radius:50%;border:1px solid currentColor;
 display:flex;align-items:center;justify-content:center;font-size:.7rem;flex-shrink:0}
.economy-node-label{font-size:.7rem;letter-spacing:.05em}
.economy-node[data-state=current]{opacity:1;background:rgba(255,226,139,.12);border-color:var(--border-strong);color:#ffe28b;font-weight:700}
.economy-node[data-state=done]{opacity:.85;color:#cbd5df}
.battle-turn-controls button:not(.draw-control):not(.end-turn-control){flex:1 1 5.5rem}
@media(max-width:1000px){.battle-table{grid-template-columns:1fr}}

/* Combat panel (ported from client/combat-presentation.ts's logic; see board-view.ts). */
.combat-panel{margin-block:1rem;padding:.75rem 1rem;border:2px solid #8b2635;border-radius:.5rem;background:#1c2230}
.combat-budget{font-size:.85rem;color:#cbd5df;margin:0 0 .5rem}
.combat-board+.combat-board{margin-top:.75rem}
.combat-units{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:.4rem}
.combat-unit{display:flex;flex-direction:column;align-items:center;gap:.3rem;min-width:8rem}
.combat-unit .unit-state{font-size:.72rem;color:#8a97a5;margin:0}
/* Choice options carry their own visual card, distinct from the plain radio label above it. */
.choice-option-card{margin-top:.4rem}

/* Commercial front door: identity chips reuse the same dynasty color tokens as everywhere else
   in the product (see the brand-token comment above) - never introduces new colors. */
.hero{margin-block:var(--sp-4) var(--sp-5)}
.hero-tagline{font-size:1.15rem;font-weight:650;color:#f7f4eb;max-width:44ch;margin:0 0 var(--sp-3)}
.hero-dynasties{display:flex;flex-wrap:wrap;gap:.4rem}
.hero-chip{font-size:.72rem;padding:.3rem .65rem;border-radius:1rem;border:1px solid #665848;color:#cbd5df;background:#182534}
.hero-chip[data-identity=Maurya]{border-color:var(--maurya);color:var(--maurya)}
.hero-chip[data-identity=Gupta]{border-color:var(--gupta);color:var(--gupta)}
.hero-chip[data-identity=Chola]{border-color:var(--chola);color:var(--chola)}
.hero-chip[data-identity=Mughal]{border-color:var(--mughal);color:var(--mughal)}
.hero-chip[data-identity=Maratha]{border-color:var(--maratha);color:var(--maratha)}
.hero-chip[data-identity=Rajput]{border-color:var(--rajput);color:var(--rajput)}

/* First-run consent decision (owner UX review): a real two-choice card, not a wall of legal
   paragraphs - heading, one short question, a reassurance line, then exactly two equally-weighted
   actions. Mobile-first: actions stack vertically by default (full-width, comfortably tappable);
   @media(min-width:480px) below lays them side by side once there is real room. Neither action is
   larger, brighter or positioned to be found faster than the other - "Yes" uses .btn-primary only
   because this screen needs ONE visually anchoring action for scannability, not because declining
   is a lesser choice (both call the identical persist()+go() path; see shell.ts). */
.consent-decision{background:var(--surface-1);border:1px solid var(--border-subtle);
 border-radius:var(--radius-lg);padding:var(--sp-5);margin-block:var(--sp-5) var(--sp-4);
 display:flex;flex-direction:column;gap:var(--sp-3);max-width:34rem}
.consent-heading{margin:0;font-size:1.05rem;font-family:var(--font-display);letter-spacing:.02em}
.consent-question{margin:0;font-size:1rem;color:#f7f4eb;max-width:46ch}
.consent-reassurance{margin:0;font-size:.85rem;color:var(--text-secondary)}
/* One consistent gap (the panel's own flex gap, var(--sp-3)) carries every step of this card's
   internal rhythm - heading->question->reassurance->privacy link->actions - rather than a mix of
   the flex gap plus ad-hoc per-element margins, which previously gave the link/actions step a
   visibly larger gap than every other step in the same card. */
.privacy-link{display:inline-block;font-size:.85rem;color:#f1d88d;text-decoration:underline}
.consent-actions{display:flex;flex-direction:column;gap:.6rem}
.consent-actions button{width:100%}
@media(min-width:480px){.consent-actions{flex-direction:row}.consent-actions button{width:auto;flex:1 1 0}}
/* Narrow-viewport compaction: the real ~185px below-the-fold measurement at 390x844 (visual
   certification) this fixes - the consent decision is the one thing on this screen that must
   never need a scroll to discover. Trims only this card's own spacing and its two buttons' own
   padding/size (never the global button/header/main rules other screens rely on), and keeps
   "Yes, share gameplay analytics" on one line at common phone widths instead of wrapping to two -
   the single largest individual saving measured. */
@media(max-width:480px){
 .hero{margin-block:.5rem .75rem}
 .consent-decision{padding:var(--sp-4);margin-block:var(--sp-4) var(--sp-3);gap:var(--sp-2)}
 .consent-actions button{padding:.6rem .8rem;font-size:.88rem;min-height:44px}
}
/* Second pass: 360x800 specifically (visual certification) still needed ~33px of scroll to reach
   "No, thanks" - a height-based trim (short viewports only, never affecting the taller 390x844
   case already fixed above) closes the rest of that gap. */
@media(max-height:820px){
 header,main{padding-block:.75rem}
 .hero{margin-block:.35rem .5rem}
}

/* Entry hierarchy: one obvious primary action, a full-weight secondary, and (only where a
   primary/secondary pair already exists on-screen) a de-emphasized tertiary fallback link -
   never a disabled or hidden option, only placement/typography/weight. */
.btn-primary{background:#ffe28b;color:#1a130c;border-color:#ffe28b;font-weight:700}
.btn-primary:hover:not(:disabled){background:#fff0bd}
.btn-secondary{background:#24364a}
.btn-tertiary{background:none;border:none;color:#f1d88d;text-decoration:underline;padding:.4rem .2rem;min-height:auto}
.entry-actions{display:flex;flex-direction:column;align-items:flex-start;gap:var(--sp-2);margin-block:var(--sp-4)}
/* UI polish (owner review): a deliberate gap before the CTA - this paragraph previously had
   margin:0 on every side, leaving the "Start guided battle" button sitting directly against the
   explanatory sentence with no breathing room at all. var(--sp-5) lands at ~27px at this page's
   18px root font-size, inside the requested ~20-28px target, using the existing scale rather than
   an arbitrary value. */
.entry-hint{font-size:.82rem;color:#8a97a5;margin:0 0 var(--sp-5)}

/* FTUE intro (mobile parity: FtueIntroView.cs) - four real-icon pillars naming what the mandatory
   guided battle teaches, matching mobile's own BuildPillar composition. */
.ftue-intro{font-weight:600;margin-block:0 var(--sp-2)}
.ftue-pillars{display:flex;flex-wrap:wrap;gap:.6rem;margin-block:0 var(--sp-4)}
.ftue-pillar{display:flex;align-items:center;gap:.5rem;flex:1 1 13rem;padding:.65rem .9rem;
 background:var(--surface-1);border:1px solid var(--border-subtle);border-radius:var(--radius-md)}
.ftue-pillar img{width:1.6rem;height:1.6rem}
.ftue-pillar span{font-size:.88rem;font-weight:600}

/* Settings: was a bare checkbox + two floating buttons in an otherwise empty page - the single
   clearest "developer preview" moment in the app. Same surface-card language every other secondary
   screen already uses. */
.settings-card{background:var(--surface-1);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);
 padding:1.25rem;display:flex;flex-direction:column;align-items:flex-start;gap:1rem;max-width:28rem}
.settings-card label{display:flex;align-items:center;gap:.6rem;margin:0;font-size:.95rem}
.settings-card input{margin:0}
.settings-note{font-size:.85rem;color:var(--text-secondary);margin:0}

/* Match-end result: outcome is always named in text (win/loss/draw), color is a reinforcement,
   never the only cue. */
/* Victory/defeat payoff (Section 26-27): a Tier-3 entrance - the result screen is reached once per
   match and its root element is freshly created on that exact transition, so a plain creation-time
   animation plays naturally without separate transition-tracking (unlike the dynasty/turn cues
   above, which can re-render the same logical state many times per match). */
.result-banner{margin-block:1rem;padding:1rem 1.25rem;border-radius:.6rem;border:2px solid #665848;background:#182534;
 animation:result-banner-in .6s ease-out}
@keyframes result-banner-in{0%{opacity:0;transform:translateY(8px)}100%{opacity:1;transform:translateY(0)}}
.result-heading{margin:0 0 .4rem;font-size:1.7rem}
.result-banner[data-outcome=win]{border-color:#3e7a4c}
.result-banner[data-outcome=win] .result-heading{color:#7fd99a}
.result-banner[data-outcome=loss]{border-color:#8b2635}
.result-banner[data-outcome=loss] .result-heading{color:#e3808f}
.result-banner[data-outcome=draw]{border-color:#cbb777}
.result-banner[data-outcome=draw] .result-heading{color:#f1d88d}
.result-dynasties{max-width:26rem}

/* Matchmaking "system is working" cue - decorative only, never the sole source of the status
   text beside it. Reduced motion is honored directly via the OS-level media query, not only the
   app's own toggle. */
.finding-dot{display:inline-block;width:.55rem;height:.55rem;border-radius:50%;background:#ffe28b;margin-right:.5rem;animation:finding-pulse 1.4s ease-in-out infinite}
@keyframes finding-pulse{0%,100%{opacity:.3}50%{opacity:1}}
@media(prefers-reduced-motion:reduce){.finding-dot{animation:none;opacity:1}}

/* Home (Phase 4): identity strip reused from .hero-chip tokens, a dominant PLAY section with
   mode tiles, and a visibly demoted secondary row - the "choose your next step" list this
   replaces treated every destination as equal weight, which read as an admin menu, not a game. */
.home-identity{display:flex;flex-wrap:wrap;gap:.4rem;margin-block:0 1.5rem}
.home-play{margin-block:1.5rem}
.home-play h2{font-family:var(--font-display);font-size:1.05rem;letter-spacing:.16em;text-transform:uppercase;
 color:var(--text-secondary);margin:0 0 .85rem;display:flex;align-items:center;gap:.7rem}
.home-play h2::after{content:'';flex:1;height:1px;background:linear-gradient(90deg,var(--border-subtle),transparent)}
.mode-cards{display:grid;grid-template-columns:1fr 1fr;gap:1rem}
.mode-card{background:var(--surface-1);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);
 padding:1.25rem;box-shadow:var(--shadow-sm);display:flex;flex-direction:column;gap:.4rem;align-items:flex-start}
.mode-card-icon{font-size:1.5rem;color:var(--border-strong);line-height:1}
.mode-card h3{margin:.2rem 0 0;font-size:1.1rem}
.mode-card p{margin:0;font-size:.85rem;color:var(--text-secondary);max-width:none}
.mode-card button{margin-top:.6rem;align-self:stretch}
.resume-card{border-color:var(--border-strong);margin-block:0 1.5rem}
.home-secondary{display:flex;gap:1.5rem;flex-wrap:wrap;margin-block:1.75rem 0;padding-top:1rem;border-top:1px solid var(--border-subtle)}
@media(max-width:600px){.mode-cards{grid-template-columns:1fr}}

/* Tutorial spotlight (mobile parity: TutorialFocusOverlay.cs - dim everything except a real hole
   cut around the target's own measured rect, with a pulsing gold ring. Mobile builds the hole from
   4 dim strips; the CSS box-shadow-cutout technique below is the standard web equivalent of the
   same real effect - dim everywhere except one real rect, never a guessed/absolute position.
   Reduced motion already disables all animation globally (see html[data-reduced-motion=true] *
   above), so the pulse needs no separate gate.
   The instruction callout itself docks to the bottom of the viewport rather than chasing the
   target's above/below side (mobile's TutorialPromptView has the same "reserved slot, not
   target-relative" split) - a real bug this caught: several spotlight targets (hand-region,
   your-board-column, opponent-strip) are taller than the viewport, so neither "above" nor "below"
   the target ever has room for a floating callout; docking to the bottom guarantees it is always
   fully reachable regardless of the target's size or scroll position. */
.tutorial-hole{position:fixed;z-index:40;border-radius:var(--radius-sm);border:3px solid var(--border-strong);
 box-shadow:0 0 0 9999px rgba(0,0,0,.65);pointer-events:none;transition:left .25s ease,top .25s ease,width .25s ease,height .25s ease;
 animation:tutorial-pulse 1.2s ease-in-out infinite}
@keyframes tutorial-pulse{0%,100%{border-width:3px}50%{border-width:5px}}
.tutorial-callout{position:fixed;z-index:41;left:1rem;right:1rem;bottom:calc(1rem + env(safe-area-inset-bottom,0px));
 max-width:34rem;margin:0 auto;background:var(--surface-2);border:1px solid var(--border-strong);border-top-width:3px;
 border-radius:var(--radius-md);padding:.75rem .9rem;box-shadow:var(--shadow-md);max-height:40vh;overflow-y:auto}
.tutorial-callout p{margin:0;font-size:.9rem}
/* Player-facing coach heading (the humanized chapter name) - a short, deliberate label above the
   real instruction, replacing the raw "chapter-N-id · N learning steps completed" engineering
   sentence that used to render verbatim here. */
.tutorial-heading{font-family:var(--font-display);font-size:.78rem;font-weight:700;letter-spacing:.1em;
 text-transform:uppercase;color:var(--border-strong);margin:0 0 .3rem}
.tutorial-instruction{font-weight:600}
.tutorial-hint{color:var(--text-secondary);font-style:italic}
/* Real bug this caught: the callout docks fixed to the bottom ~40vh of the viewport (see the
   comment above), but a spotlighted target button near the end of a long, tall table (End Turn,
   on a narrow viewport with a tall hand) can still land underneath it after a plain
   scrollIntoView - the browser considers it "in view" without knowing the bottom band is covered
   by an opaque fixed panel, so the taught action becomes unclickable. scroll-margin-bottom keeps
   every spotlightable control clear of the callout's own reserved band regardless of which code
   path (this app's positionSpotlight, or any native/assistive scrollIntoView) does the scrolling. */
[data-spotlight]{scroll-margin-bottom:42vh}
/* Reserves real, scrollable room below the last control while a target is spotlighted, so a
   control that is also the last element on a tall table (e.g. End Turn) can actually be scrolled
   clear of the fixed callout above instead of the page already being at its scroll end. */
body.tutorial-coaching-active main{padding-bottom:42vh}
html[data-reduced-motion=true] .tutorial-hole{animation:none;transition:none}

/* Battle Format (mobile parity: SelectBattleView.cs/BattleFormatCardView.cs/BattleVariantRowView.cs
   ported directly - same three family accent colors from Theme.cs, same medallion/title/tagline/
   variant-row composition, same real gold line-art icons and hero art. Responsive grid replaces
   mobile's fixed 1600x900 canvas; the family/variant relationship itself is unchanged. */
:root{--battle-duel:#2E5A8E;--battle-conquest:#2F5D3A;--battle-empire:#6E1F2A;}
.battle-subtitle{color:var(--text-secondary);margin:0 0 1.25rem;max-width:56ch}
.battle-families{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem}
.battle-family{position:relative;border-radius:var(--radius-lg);overflow:hidden;border:2px solid var(--border-strong);
 box-shadow:var(--shadow-md);min-height:26rem;display:flex;flex-direction:column;background:var(--surface-1)}
/* The source illustrations are already near-black/moody (see Assets/_Project/Resources/Art/
   BattleSelection) - full opacity with only a bottom-anchored legibility gradient (not a wash over
   the whole panel) lets the real painted scene read clearly, matching the wireframe reference
   instead of a flat color-gradient card with a faint hint of art at the very top. */
.battle-family-art{position:absolute;inset:0;background-size:cover;background-position:center 20%;opacity:1}
.battle-family-tint{position:absolute;inset:0}
.battle-family[data-format=duel] .battle-family-tint{background:linear-gradient(180deg,rgba(10,16,24,.1) 0%,rgba(10,16,24,.35) 45%,rgba(8,13,19,.97) 88%)}
.battle-family[data-format=conquest] .battle-family-tint{background:linear-gradient(180deg,rgba(10,16,24,.1) 0%,rgba(10,16,24,.35) 45%,rgba(8,13,19,.97) 88%)}
.battle-family[data-format=empire] .battle-family-tint{background:linear-gradient(180deg,rgba(10,16,24,.1) 0%,rgba(10,16,24,.35) 45%,rgba(8,13,19,.97) 88%)}
.battle-family-body{position:relative;padding:1.1rem;display:flex;flex-direction:column;align-items:center;flex:1}
.battle-family-medallion{width:72px;height:72px;border-radius:50%;background:rgba(3,6,9,.88);border:2px solid var(--border-strong);
 display:flex;align-items:center;justify-content:center;margin-bottom:.6rem}
.battle-family-medallion img{width:38px;height:38px}
.battle-family-title{font-family:var(--font-display);font-size:1.7rem;color:#fff0cc;margin:0 0 .35rem;letter-spacing:.04em}
.battle-family-tagline{font-size:.85rem;color:var(--text-secondary);text-align:center;white-space:pre-line;margin:0 0 1rem}
.battle-variants{display:flex;flex-direction:column;gap:.5rem;width:100%;margin-top:auto}
.battle-variant-row{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem .6rem;padding:.6rem .75rem;border-radius:var(--radius-sm);
 background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.18);font:inherit;color:var(--text-primary);
 min-height:44px;box-shadow:none;text-align:left}
.battle-variant-row[data-enabled=true]{border-color:var(--border-strong);cursor:pointer}
.battle-variant-row[data-enabled=false]{opacity:.55;cursor:default}
.battle-variant-row:hover:not(:disabled){transform:none;box-shadow:none}
.battle-variant-name{font-weight:700;flex:1 1 auto;text-align:left}
.battle-variant-fact{display:flex;align-items:center;gap:.3rem;font-size:.78rem;color:var(--text-secondary);white-space:nowrap}
.battle-variant-fact img{width:16px;height:16px}
.battle-variant-status{font-size:.68rem;text-transform:uppercase;letter-spacing:.06em;color:var(--border-strong);
 flex-basis:100%;margin-top:.15rem}
@media(max-width:1000px){.battle-families{grid-template-columns:1fr}}

/* In-match Settings access (Android V2 parity: reach Settings without leaving the match). */
.match-settings{margin-block:.25rem 0}
.match-settings label{display:flex;align-items:center;gap:.5rem;margin:0;font-size:.85rem;color:#cbd5df}
.match-settings input{width:1.1rem;height:1.1rem;margin:0}

/* Battle Menu (mobile parity: GameMenuOverlay.cs - Resume/Settings/Leave with confirmation). */
.battle-menu-button{margin-block:0 .75rem}
.battle-menu-overlay{position:fixed;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;
 background:rgba(0,0,0,.72);padding:1rem}
.battle-menu-dialog{background:var(--surface-2);border:1px solid var(--border-strong);border-radius:var(--radius-lg);
 padding:1.25rem;width:min(22rem,100%);box-shadow:var(--shadow-lg);display:flex;flex-direction:column;gap:.6rem}
.battle-menu-dialog h2{margin:0 0 .25rem;font-family:var(--font-display)}

/* ════════════════════════════════════════════════════════════════════════════════════════════
   OUT³ card presentation (production/out3-card.ts — direct port of Out3CardFrame.cs +
   Out3{Dynasty,Military,Resource,Action,WildDynasty}CardTemplate.cs). Every size below mirrors
   the Android master-coordinate region system (1000x1420 units) via CSS container query units
   (cqw/cqh = percent of the card's OWN rendered size, set per out3-card.ts via inline style for
   text that already had an explicit FontPx() value there; the structural chrome below carries the
   remaining Android master-unit values converted to cqh, so changing --out3-card-w is the only
   thing any context (hand/board/zoomed) needs to do - the whole composition scales with it,
   exactly matching Android's own "same composition, only scale changes" design intent). */
.out3-card{all:unset;display:block;position:relative;cursor:pointer;flex-shrink:0;
 width:var(--out3-card-w,150px);aspect-ratio:1000/1420;container-type:size;
 box-shadow:.6cqw .4cqh .9cqh rgba(0,0,0,.45);border-radius:9% 11% 10% 9%;
 transition:transform .15s ease,box-shadow .15s ease}
html[data-reduced-motion=true] .out3-card{transition:none}
.out3-card:focus-visible{outline:3px solid #ffe28b;outline-offset:3px;border-radius:9% 11% 10% 9%}
.out3-card[aria-pressed=true]{outline:3px solid #ffe28b;outline-offset:2px}
.out3-paper{position:absolute;inset:0;background:var(--parchment,#EDE6D6);overflow:hidden;
 border-radius:9% 11% 10% 9%;border:3px solid var(--ink,#211F1A);color:var(--ink,#211F1A)}
.out3-paper-inner{position:absolute;left:.6%;top:.3%;right:.5%;bottom:.4%;border:1px solid var(--ink,#211F1A);
 border-radius:7% 9% 8% 6%;pointer-events:none;opacity:.8}
.out3-rail{display:flex;flex-direction:column;align-items:center;overflow:hidden}
.out3-rail-point{position:absolute;bottom:-7%;left:-30%;width:160%;height:4.5cqh;background:var(--parchment,#EDE6D6);transform:rotate(-7deg)}
.out3-rail-rainbow{background:linear-gradient(180deg,#7A4A8F,#2E6E9E,#3E7A4C,#D4A72C)}
.out3-rail-emblem{width:9.2cqw;height:6.5cqh;object-fit:contain;margin-top:1.3cqh}
.out3-rail-label{position:absolute;top:58%;left:50%;transform:translate(-50%,-50%) rotate(-90deg);
 color:var(--parchment,#EDE6D6);font-weight:700;letter-spacing:.14em;white-space:nowrap;font-size:2.1cqh}
.out3-crown{color:var(--parchment,#EDE6D6);margin-top:2cqh;font-size:2.5cqh;line-height:1}
.out3-sword{color:var(--parchment,#EDE6D6);margin-top:1.6cqh;font-size:2.8cqh;line-height:1}
.out3-hero-title{display:flex;align-items:center;font-family:var(--font-display,Georgia,serif);
 font-weight:700;letter-spacing:.01em;white-space:nowrap;overflow:visible}
.out3-epithet,.out3-bold{font-weight:700}
.out3-small{font-size:1.2cqh}
.out3-hero-art{position:absolute;background-size:cover;background-position:center}
.out3-hero-art--contain{background-size:contain;background-repeat:no-repeat;background-position:center}
/* position:relative by default - a normal flex-row child (effect/flavor/gain/exchange panel icons,
   Wild's dynasty-row rings). The few callers that need free placement in the master coordinate
   space (power badge via box(), dynasty effect icon via region()) already get position:absolute
   from box()/region()'s own inline style, which wins over this default. */
.out3-circle{position:relative;flex-shrink:0;border-radius:50%;border-style:solid;border-width:.22cqw;display:flex;
 flex-direction:column;align-items:center;justify-content:center;background:var(--parchment,#EDE6D6);overflow:hidden}
.out3-circle img{width:62%;height:62%;object-fit:contain}
.out3-power-badge{flex-direction:column}
.out3-power-label{font-size:1.1cqh;font-weight:700}
.out3-power-value{font-size:4.5cqh;font-weight:700;line-height:1}
/* Decorative border-only layer (Out3CardFrame.ApplySketchPanel) - always a sibling of the panel's
   real content (icon/text appended to the same outer box() container, matching the pattern every
   Android template itself uses: Box() sizes the region, ApplySketchPanel only paints a border onto
   it). Absolutely filling its own container (inset:0) keeps it out of flex flow entirely, so it
   can never push its content siblings out of view - the bug an earlier pass here hit by using
   width/height:100% on a flex sibling instead. */
.out3-panel{position:absolute;inset:0;box-sizing:border-box;border-style:solid;pointer-events:none;
 border-width:.26cqw;border-radius:8% 11% 9% 7%;overflow:hidden}
.out3-panel-echo{position:absolute;left:3%;right:-2%;top:2%;bottom:-3%;border:1px solid;
 border-radius:9% 7% 6% 10%;opacity:.5;pointer-events:none}
/* The real content row for a bordered panel (icon + text), siblings of the decorative .out3-panel
   above, both children of the same box()-positioned outer container. */
.out3-effect-panel{display:flex;flex-direction:row;align-items:center}
.out3-panel-text{margin-left:4cqw;display:flex;flex-direction:column;gap:.3cqh;font-size:1.55cqh;min-width:0}
.out3-panel-text .out3-bold{font-size:1.35cqh}
.out3-panel-text-inline{margin-left:4cqw;font-size:1.55cqh;line-height:1.3}
.out3-gain-value{font-size:3.2cqh;font-weight:700;display:block}
.out3-divider{position:absolute;width:.14cqw;background:var(--ink,#211F1A)}
.out3-divider--light{background:var(--parchment,#EDE6D6)}
.out3-triptych{display:flex;flex-direction:column;align-items:center;text-align:center;padding-top:.6cqh;gap:.5cqh}
.out3-triptych-heading{font-size:1.2cqh;font-weight:700}
.out3-triptych-body{font-size:1.3cqh;line-height:1.25}
.out3-metadata-band{background:var(--ink,#211F1A)}
.out3-triptych--dark .out3-triptych-heading,.out3-triptych--dark .out3-triptych-body{color:var(--parchment,#EDE6D6)}
.out3-index-banner{display:flex;flex-direction:column;align-items:center;justify-content:flex-start;
 padding-top:1cqh;color:var(--parchment,#EDE6D6);font-weight:700}
.out3-progress{display:flex;flex-direction:column;align-items:center}
.out3-progress-number{font-size:2.9cqh;font-weight:700}
.out3-progress-label{font-size:1.1cqh;margin-top:.3cqh}
.out3-progress-boxes{display:flex;gap:.8cqw;margin-top:.7cqh}
.out3-progress-boxes span{width:4.8cqw;height:3.4cqh;border:2px solid var(--ink,#211F1A);display:block}
.out3-footer{display:flex;align-items:center;justify-content:space-between;padding:0 1.4cqw}
.out3-footer-tag{background:var(--ink,#211F1A);color:var(--parchment,#EDE6D6);font-size:2.1cqh;
 font-weight:700;padding:.1cqh 1cqw}
.out3-footer-wordmark{display:flex;align-items:flex-start;font-family:var(--font-display,Georgia,serif);
 font-weight:700;letter-spacing:.06em;font-size:2.1cqh}
.out3-footer-wordmark .wordmark-3{font-size:.62em;position:relative;top:-.5em;margin-left:.05em}
.out3-footer-diamonds{display:flex;gap:.3cqw}
.out3-diamond{width:1.3cqw;height:1.3cqh;border:1.5px solid;transform:rotate(45deg)}
.out3-wild-badge{display:flex;flex-direction:row;align-items:center;justify-content:center;gap:.4cqw}
.out3-wild-pill{background:var(--ink,#211F1A);color:#D4A72C;font-weight:700;padding:.2cqh 1cqw;font-size:2.1cqh}
.out3-wild-medallion{border-radius:50%;display:flex;align-items:center;justify-content:center;
 background:linear-gradient(135deg,#7A4A8F,#2E6E9E,#3E7A4C,#D4A72C)}
.out3-wild-medallion-inner{width:83%;height:83%;background:var(--parchment,#EDE6D6);border-radius:50%;
 display:flex;align-items:center;justify-content:center;font-size:5cqh}
.out3-wild-ribbon{display:flex;align-items:center;justify-content:center;font-weight:700;font-size:2.7cqh;
 background:rgba(237,230,214,.9);border-top:2px solid #7A4A8F;border-bottom:2px solid #7A4A8F;text-align:center}
.out3-wild-dynasty-row{display:flex}
.out3-wild-dynasty-cell{flex:1;display:flex;flex-direction:column;align-items:center;gap:.4cqh}
.out3-wild-dynasty-cell .out3-circle{position:relative;width:8cqw;height:5.7cqh}
.out3-cost-module{display:flex;flex-direction:column;align-items:center;padding-top:2.3cqh}
.out3-cost-icon img{width:9cqw;height:6.4cqh;object-fit:contain;margin-bottom:1.4cqh}
.out3-cost-label{font-size:1.6cqh}
.out3-cost-value{font-size:4.3cqh;font-weight:700}
.out3-cost-pill{color:var(--parchment,#EDE6D6);font-size:1.4cqh;font-weight:700;padding:.1cqh .8cqw;margin-top:.6cqh}
.out3-action-class{text-align:center;font-weight:700}
.out3-action-title{justify-content:center;width:100%;text-align:center}
.out3-action-effect{display:flex;align-items:center;justify-content:center;text-align:center;line-height:1.25}
.out3-action-cost{display:flex;align-items:center;justify-content:center;text-align:center;font-weight:700}
.out3-small-panel{display:flex;flex-direction:column;padding:.8cqh 1cqw;overflow:hidden;justify-content:center}
.out3-small-panel .out3-bold{font-size:1.3cqh;margin-bottom:.4cqh}
.out3-small-panel span:last-child{font-size:1.12cqh;line-height:1.3}
.out3-meta-panel{align-items:center;text-align:center}
.out3-brush-label{display:flex;align-items:center;justify-content:center;color:var(--parchment,#EDE6D6);
 font-weight:700;letter-spacing:.04em;overflow:hidden;white-space:nowrap;font-size:1.97cqh}
.out3-medallion-art{width:62%;height:62%;object-fit:contain}

/* Per-context card scale - same DOM/CSS, only the rendered width changes (aspect-ratio keeps
   1000:1420; container query units inside scale automatically with it). */
/* Declared on .hand itself (not .hand .out3-card) so .hand-card - the fan/overlap wrapper, a
   PARENT of .out3-card, never a descendant - inherits the same value a custom property declared
   only on .out3-card could never reach (CSS custom properties cascade down, not up); see the fan-
   overlap rule below, which depends on reading this same value from .hand-card. */
.hand{--out3-card-w:clamp(96px,11.5vw,190px)}
.discard-pile .out3-card,.draw-pile .out3-card{--out3-card-w:92px}
.combat-unit .out3-card{--out3-card-w:120px}
.choice-option-card.out3-card{--out3-card-w:128px}
.card-detail-zoom.out3-card{--out3-card-w:min(58vw,300px)}
@media(max-width:480px){.hand{--out3-card-w:clamp(84px,34vw,150px)}}

/* Hand geometry (Section 4/5): one horizontal row, never a vertical page of cards. Cards fan/
   overlap once the hand is wider than fits (negative margin scales with card count past 5 -
   --hand-count is set by journey-view.ts from the real hand size every paint). */
section[aria-label="Your hand"]{display:flex;flex-wrap:nowrap;align-items:flex-end;gap:.5rem;
 overflow-x:auto;overflow-y:visible;padding:.6rem .5rem .5rem;margin-block:.5rem 0;scrollbar-width:thin;
 border-color:var(--border-subtle,#3a4a58);background:rgba(255,255,255,.015)}
.hand-card{position:relative;flex:0 0 auto}
.hand-card:not(:first-child){margin-left:clamp(-3.2rem,calc((5 - var(--hand-count,0)) * .6rem),0rem)}
.hand-card:hover,.hand-card:focus-within,.hand-card:has(.out3-card[aria-pressed=true]){z-index:5}
.hand-card:hover .out3-card,.hand-card:focus-within .out3-card,.hand-card .out3-card[aria-pressed=true]{
 transform:translateY(-14px) scale(1.04);box-shadow:0 .9rem 1.6rem rgba(0,0,0,.55)}
html[data-reduced-motion=true] .hand-card:hover .out3-card,
html[data-reduced-motion=true] .hand-card:focus-within .out3-card,
html[data-reduced-motion=true] .hand-card .out3-card[aria-pressed=true]{transform:none}
.hand-card[data-just-drawn=true]{animation:hand-card-arrive .3s ease-out}

.card-detail-panel{display:flex;flex-direction:column;align-items:flex-start;gap:.3rem}
.card-detail-zoom{align-self:center;margin-bottom:.4rem}

/* Reaction window (Section 19 game-feel milestone): a genuine control shift away from normal turn
   order - distinct gold-pulsed framing (the same restrained Tier-2 language the dynasty-near and
   tutorial-hole treatments already use), never a blocking modal that would hide the board. */
.reaction-window{border-color:var(--border-strong);border-width:2px;background:var(--surface-2);
 animation:reaction-window-pulse 1.6s ease-in-out infinite}
@keyframes reaction-window-pulse{0%,100%{box-shadow:0 0 0 0 rgba(203,183,119,.35)}50%{box-shadow:0 0 0 5px rgba(203,183,119,.12)}}
.reaction-window-heading{margin:0 0 .4rem;font-family:var(--font-display);font-size:.85rem;letter-spacing:.1em;
 text-transform:uppercase;color:var(--border-strong)}

/* Desktop gameplay-screen compaction (wireframe convergence: /Gameplay Screen V2_New.png's own
   composition is a single compact screen - branded header folded away, one slim identity/status
   strip, dense board rows, a horizontal phase-control row - never a page that scrolls to reach the
   hand or turn controls). Scoped two ways so nothing outside an active match is touched: (1) the
   :has(.battle-table) guard - .battle-table only exists in JourneyView's live-match paint(), never
   on the menu/tutorial-intro/settings/result screens that share this same header/h1/button CSS;
   (2) min-width:1000px - the same breakpoint .battle-table itself already switches on (see
   `@media(max-width:1000px){.battle-table{grid-template-columns:1fr}}` above), so narrow/mobile
   viewports keep exactly the already-certified CEO-Gameplay-Screen-parity sizing this never
   touches. Every rule here only shrinks padding/margin/font-size on existing classes - no element
   is removed from the DOM, no class is renamed, so every existing role/text/class selector journey-
   tests and production-tests rely on is unchanged. */
@media(min-width:1000px){
 body:has(.battle-table) header{display:none}
 body:has(.battle-table) main{padding-top:.75rem}
 body:has(.battle-table) main>h1{font-size:1rem;margin:0 0 .5rem;line-height:1.2}
 body:has(.battle-table) .battle-top-bar{display:flex;align-items:center;gap:.75rem;margin-block:0 .5rem}
 body:has(.battle-table) .battle-top-bar .hud{flex:1 1 auto;margin-block:0;padding:.4rem .75rem}
 body:has(.battle-table) .battle-top-bar .battle-menu-button{margin-block:0;min-height:40px;padding:.5rem 1rem}
 body:has(.battle-table) .battle-opponent-strip{margin-block:0 .4rem}
 body:has(.battle-table) .battle-opponent-strip .board-area{padding:.4rem .6rem}
 body:has(.battle-table) .battle-table{gap:.6rem}
 body:has(.battle-table) .battle-center{gap:.6rem}
 body:has(.battle-table) .battle-sidebar{padding:.6rem;gap:.35rem}
 body:has(.battle-table) .board-rows{gap:.16rem}
 body:has(.battle-table) .board-row{padding:.16rem .4rem}
 body:has(.battle-table) .board-row-crest{width:1.2rem;height:1.2rem}
 body:has(.battle-table) .board-dashboard{margin-top:.3rem;padding-top:.3rem}
 /* Now a direct child of body (see journey-view.ts's desktopGameplay relocation), not nested in the
    19rem sidebar - drop the flex-column "push to bottom" margin-top:auto (meaningless outside that
    context) and align as a right-hugging bottom bar, matching the wireframe's own bottom-right
    DRAW/PLAY1/PLAY2/PLAY3/END cluster. */
 body:has(.battle-table) .battle-turn-controls{margin-top:.6rem;padding-top:.4rem;gap:.3rem;justify-content:flex-end}
 /* Play 1/2/3 as one slim horizontal row beside Draw/End Turn (was a 3-row vertical stack inside
    a flex item taller than every other control in the row) - the single largest sidebar saving.
    Real bug this caught: at the 19rem sidebar width, draw-control+end-turn-control+action-economy's
    DEFAULT flex-basis (5.5rem+5.5rem+7rem=18rem content request, before gaps) left no slack inside
    the padded ~17.7rem content box, so .battle-turn-controls's own flex-wrap:wrap (needed for
    narrow/mobile) silently wrapped the row in two even on desktop - the single largest remaining
    source of sidebar height. Narrower fixed bases (not flex:1 sizing, which re-requests the same
    oversized minimum) remove the overflow outright. */
 body:has(.battle-table) .draw-control,body:has(.battle-table) .end-turn-control{min-height:38px;padding:.4rem .5rem;flex:0 1 4rem;font-size:.82rem}
 body:has(.battle-table) .action-economy{flex:0 1 7.6rem;flex-direction:row;gap:.25rem;justify-content:center}
 body:has(.battle-table) .economy-node{flex-direction:column;padding:.15rem .25rem;gap:.1rem}
 body:has(.battle-table) .economy-node-number{width:1.1rem;height:1.1rem}
 /* Opponent pip strip (rival-empire-row): the certified mobile reference stacks icon above
    label+pips in each chip; at the desktop single-screen budget that stacked block is taller than
    it needs to be purely for a secondary/"informational, not dominant" panel (board-view.ts's own
    description of this row), so here only (same dynasty-chip markup/classes, no DOM change) the
    icon sits beside the stacked label+pips text instead of above it. */
 body:has(.battle-table) .dynasty-chip{flex-direction:row;font-size:.62rem;gap:.3rem}
 body:has(.battle-table) .dynasty-chip img{width:1.1rem;height:1.1rem}
 body:has(.battle-table) .rival-military .zone-label{margin:0 0 .15rem;font-size:.68rem}
 body:has(.battle-table) .rival-military .military-item img{width:1.1rem;height:1.1rem}
 body:has(.battle-table) .battle-opponent-strip .area-heading{font-size:.68rem;margin:0 0 .3rem}
}
/* Shorter desktop viewports (1440x900, 1366x768 are the two smallest required certification
   sizes): the fixed per-row/control paddings above are the same at every desktop width, but a
   shorter viewport has materially less vertical budget to spend them from, and the hand card's own
   width-only clamp (`clamp(96px,11.5vw,190px)`) does not shrink just because height dropped - two
   viewports of the same width-based card size end up needing the same absolute vertical space
   regardless of how tall the viewport actually is. Bounding card width by viewport HEIGHT too (not
   only width) is the real fix: it makes the hand genuinely responsive to the vertical budget, the
   same way it was already responsive to the horizontal one. */
@media(min-width:1000px) and (max-height:960px){
 body:has(.battle-table) .hand{--out3-card-w:clamp(64px,min(11.5vw,8.5vh),190px)}
 body:has(.battle-table) main{padding-top:.15rem}
 body:has(.battle-table) main>h1{font-size:.85rem;margin-bottom:.25rem}
 body:has(.battle-table) .battle-top-bar{margin-block:0 .25rem}
 body:has(.battle-table) .battle-top-bar .hud{padding:.3rem .6rem}
 body:has(.battle-table) .battle-opponent-strip{margin-block:0 .25rem}
 body:has(.battle-table) .battle-opponent-strip .board-area{padding:.3rem .5rem}
 body:has(.battle-table) .battle-table{gap:.4rem}
 body:has(.battle-table) .board-rows{gap:.08rem}
 body:has(.battle-table) .board-row{padding:.08rem .35rem}
 body:has(.battle-table) .board-row-crest{width:.85rem;height:.85rem}
 body:has(.battle-table) .board-row-name{font-size:.7rem;min-width:3.4rem}
 body:has(.battle-table) .board-row-progress{font-size:.64rem;min-width:1.6rem}
 body:has(.battle-table) .battle-sidebar{padding:.4rem;gap:.25rem}
 body:has(.battle-table) .battle-sidebar .area-heading{margin:0 0 .25rem;padding:.12rem .55rem;font-size:.66rem}
 body:has(.battle-table) .board-dashboard{margin-top:.15rem;padding-top:.15rem;gap:.4rem}
 body:has(.battle-table) .battle-turn-controls{margin-top:.1rem;padding-top:.1rem}
 body:has(.battle-table) .draw-control,body:has(.battle-table) .end-turn-control{min-height:30px;padding:.25rem .4rem;font-size:.76rem}
 body:has(.battle-table) .economy-node-number{width:.95rem;height:.95rem;font-size:.6rem}
 /* Real bug this caught (journey-tests' "complete authored eight-chapter tutorial" test):
    .hand-card's fan overlap (base rule, a few lines up) is a FIXED rem value, independent of the
    hand card's own rendered width - harmless at the card system's normal ~150-190px width, where a
    -3.2rem/57.6px overlap still leaves most of each card exposed, but at this media query's much
    smaller card floor (--out3-card-w down to 64px) that same fixed overlap covers most of, or the
    entire center of, the smaller card underneath - exactly the earlier per-card-badge stacking bug
    this same test previously caught (see journey-view.ts's own comment on that), recurring here for
    a different reason. The fix scales the overlap proportionally to the card's OWN current width
    (var(--out3-card-w)) instead of a fixed length, so a fixed 30% of each card - never its center -
    is covered regardless of how small the compaction above shrinks it. */
 body:has(.battle-table) .hand-card:not(:first-child){margin-left:calc(var(--out3-card-w,150px) * -.3)}
 body:has(.battle-table) .economy-node-label{font-size:.6rem}
}
