:root{
  /* dark = Heart Pirates Navy (default). Board + analysis stay dark in both themes (see the reset below). */
  --bg:#22315c; --bg2:#1b294f; --panel:#2b3d70; --panel2:#263867; --line:#43589a;
  --txt:#EEEBEE; --muted:#aab5db; --accent:#5f8ce0; --accent2:#6ad29b;
  --red:#ff6b78; --gold:#E1CD43; --pill:#1a2547; --rest:#8a79e0;
  --ko:#63d29a; --maybe:#f2c64f; --noko:#ff6b78; --slot:#1b2a4f;
  --felt1:#1c2c57; --felt2:#12203f;
  /* chrome surface tokens - these flip in light mode */
  --field:#1b2a50; --btn-bg:#26386a; --chip:#26386a;
  --head-a:#2a3d72; --head-b:#1c2b52;
  --danger-bg:#3a2530; --danger-line:#6b3442;
}
:root[data-theme="light"]{
  /* light = Submarine White */
  --bg:#E7E5EA; --bg2:#DEDCE3; --panel:#FCFCFD; --panel2:#F1EFF4; --line:#D3D1DD;
  --txt:#223056; --muted:#6c7593; --accent:#3f68bd; --accent2:#2c9e68;
  --red:#d6455a; --gold:#c9a92f; --noko:#d6455a;
  --field:#FFFFFF; --btn-bg:#FFFFFF; --chip:#EFEDF2;
  --head-a:#FFFFFF; --head-b:#F1EFF5;
  --danger-bg:#fbe9ec; --danger-line:#e6b3bd;
}
/* board + analysis + online overlays are a dark canvas in BOTH themes (the playmat art is dark). Re-assert
   dark token values inside them so light mode never washes out the board or its readouts. --panel is what
   .analysis actually paints as its own background (`background:var(--panel)`), so THAT'S the one matched
   to the board's own dark tone (#0a0f1a, set on body.playmode) - the turn planner now reads as the SAME
   canvas as the board, not a lighter blue box beside it. */
.table, .analysis, .gamebar, .netLobby, .netPick, .netLanding, .netBanner, .netWaiting, .netOppInfo, .cardPreview, .poolBar{
  --bg:#0a0f1a; --panel:#0a0f1a; --panel2:#0d1524; --line:#33445f;
  --txt:#eef3fb; --muted:#9fb0cf; --accent:#5f8ce0; --accent2:#6ad29b;
  --gold:#E1CD43; --red:#ff6b78; --noko:#ff6b78;
  --field:#1b2a50; --btn-bg:#26386a; --chip:#26386a;
}
*{box-sizing:border-box}
body{margin:0;font-family:"Segoe UI",Roboto,system-ui,sans-serif;background:var(--bg);color:var(--txt);font-size:14px}
h1,h2{margin:0}
header{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:9px 18px;background:linear-gradient(180deg,#16213a,#0d1424);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:40}
header h1{font-size:17px;font-weight:700}
header h1 .sub{font-size:12px;color:var(--muted);font-weight:400;margin-left:6px}
/* title + theme toggle grouped together on the LEFT, always in the same fixed spot regardless of how many
   extra controls the toolbar grows on the right (game-mode buttons, prompts, etc - it used to get crowded
   in there and the toggle would shift around or get squeezed) */
.headTitle{display:flex;align-items:center;gap:12px;flex:none}
.toolbar{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.gamePrompt{display:flex;align-items:center;gap:6px;font-size:13px;background:var(--chip);border:1px solid var(--accent2);border-radius:8px;padding:5px 10px}
.gamePrompt.hidden,.gameTopBtns.hidden{display:none}
.gamePrompt b{color:var(--accent2)}
.gameTopBtns{display:flex;align-items:center;gap:6px}
/* deck library bar in the load modal */
.libBar{display:flex;gap:5px;margin:4px 0;flex-wrap:wrap}
.libSel,.libName{font-size:12px;padding:4px 6px;background:var(--field);border:1px solid var(--line);color:var(--txt);border-radius:5px}
.libName{flex:1;min-width:90px}
/* deck builder - full page */
#builderModal{align-items:stretch;justify-content:stretch;padding:0}
#builderModal .builderBox{width:100vw;height:100vh;max-width:none;max-height:none;border-radius:0;display:flex;flex-direction:column;padding:10px 14px;background:var(--panel)}
.bldCount{font-size:15px;color:var(--gold);font-weight:800;margin-left:10px} .bldCount.ok{color:var(--ko)}
/* header controls read as loose groups (load/save, navigation, account) instead of one flat row -
   tight spacing WITHIN a group, a visible divider and extra breathing room BETWEEN groups */
.bldTop{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.bldGroup{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.bldDivider{width:1px;align-self:stretch;min-height:22px;background:var(--line);flex:none}
.bldStage{font-size:12px;color:var(--muted);margin-right:8px} .bldStage b{color:var(--accent2)}
.bldStage button{margin-left:6px;font-size:11px;padding:2px 7px}
.bldBody{display:grid;grid-template-columns:320px 1fr;gap:14px;flex:1;min-height:0;margin-top:8px}
.bldList{background:var(--field);border:1px solid var(--line);border-radius:8px;padding:8px;overflow:auto}
.bldRow{display:flex;align-items:center;gap:5px;font-size:12px;padding:3px 0;border-bottom:1px solid var(--line)}
.bldRow.leader{color:var(--accent2);font-weight:700}
.bldRow .bln{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bldRow .blc{min-width:16px;text-align:center;font-weight:700}
.bldRow button{padding:1px 8px;background:var(--btn-bg);border:1px solid var(--line);color:var(--txt);border-radius:4px;font-size:13px;line-height:1.5;cursor:pointer}
.bldEmpty,.bldList .bldEmpty{color:var(--muted);font-size:12px;font-style:italic;padding:10px}
.bldSearch{display:flex;flex-direction:column;gap:8px;min-height:0}
.bldSearch>input{padding:9px 12px;font-size:14px;background:var(--field);border:1px solid var(--line);color:var(--txt);border-radius:6px}
.bldSearch .pickGrid{overflow:auto;flex:1;display:grid;grid-template-columns:repeat(6,1fr);gap:0;align-content:start;max-height:none}
/* inline-size container so the qty pill can size itself off the CARD, not the viewport: the builder grid
   is a fixed 6 columns, so card width already tracks the screen and 1cqw = 1% of a card. */
.bldCard{position:relative;width:100%;padding:0;border:none;border-radius:0;background:var(--field);cursor:pointer;line-height:0;container-type:inline-size}
.bldCard img{width:100%;height:auto;display:block}
.bldCard:hover{outline:2px solid var(--accent);outline-offset:-2px;z-index:2}
.bldCard .bldId{position:absolute;left:0;right:0;bottom:0;font-size:10px;background:#000a;color:#fff;padding:2px 4px;text-align:center}
/* Centering the label: no letter-spacing (tracking is added after the LAST glyph too, so it drags the run
   off-centre), tabular figures so 1/4 occupy the same width, and a small padding-top because grid centering
   aligns the LINE BOX, not the digit ink - digits reserve descender space they never use, so they read high.
   padding-top:p shifts the centred item down by p/2. */
.bldCard .bldQty{position:absolute;top:4%;right:4%;z-index:2;
  display:grid;place-items:center;box-sizing:border-box;
  width:clamp(22px,30cqw,72px);height:clamp(22px,30cqw,72px);border-radius:50%;
  background:#fff;color:#000;border:max(1.5px,1.4cqw) solid #000;
  font-weight:800;font-size:clamp(11px,15cqw,36px);line-height:1;
  font-variant-numeric:tabular-nums;padding-top:.1em;
  box-shadow:0 1px 4px #0009}
.bldCard.inDeck{outline:3px solid var(--gold);outline-offset:-3px;z-index:1}
.bldCard.full{outline:3px solid var(--noko);outline-offset:-3px}
.sCard.inDeck{border-color:var(--gold);box-shadow:0 0 0 2px var(--gold)}
/* builder export / import / "my cards" toolbar - grouped (My cards | Import | Copy/.txt/.png/Export-all)
   instead of one flat row of visually-identical buttons */
.bldTools{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.bldToolGroup{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.bldToolDivider{width:1px;align-self:stretch;min-height:20px;background:var(--line);flex:none;margin:0 2px}
.bldTool{display:inline-flex;align-items:center;gap:6px;font:700 12px/1 inherit;color:var(--txt);
  background:var(--btn-bg);border:1px solid var(--line);border-radius:7px;padding:7px 11px;cursor:pointer}
.bldTool:hover{border-color:var(--accent)}
.bldTool svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;opacity:.85;flex:none}
.bldTool.on{background:var(--gold);color:#2b2408;border-color:transparent}
.bldToolSpacer{flex:1}
.bldToast{position:fixed;left:50%;bottom:28px;transform:translateX(-50%) translateY(12px);
  background:#0e1626;color:#EEEBEE;border:1px solid var(--accent);border-radius:8px;padding:9px 16px;font-size:13px;font-weight:600;
  z-index:400;opacity:0;pointer-events:none;transition:opacity .18s,transform .18s;box-shadow:0 8px 30px #0007}
.bldToast.show{opacity:1;transform:translateX(-50%) translateY(0)}
.bldImport{position:fixed;inset:0;background:#0009;display:none;align-items:center;justify-content:center;z-index:390;padding:20px}
.bldImport.show{display:flex}
.bldImportBox{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px 22px;width:460px;max-width:94vw;box-shadow:0 10px 40px #000a}
.bldImportHead{font-size:16px;font-weight:800;margin-bottom:6px;color:var(--txt)}
.bldImportHint{font-size:12px;color:var(--muted);margin:0 0 10px;line-height:1.4}
.bldImportHint code{background:var(--field);padding:1px 5px;border-radius:4px;font-size:11px}
.bldImport textarea{width:100%;height:180px;resize:vertical;background:var(--field);border:1px solid var(--line);color:var(--txt);border-radius:7px;padding:10px;font-family:ui-monospace,Consolas,monospace;font-size:13px}
.bldImportBtns{display:flex;gap:8px;justify-content:flex-end;margin-top:12px}
.ctrl{display:flex;flex-direction:column;font-size:10px;color:var(--muted);gap:2px}
.ctrl input{width:130px;background:var(--field);border:1px solid var(--line);color:var(--txt);border-radius:6px;padding:5px 8px;font-size:14px}
.chand .chRow{display:flex;gap:6px}
.chand .chRow label{display:flex;align-items:center;gap:3px;font-size:11px;color:var(--muted)}
.chand .chRow input{width:46px;font-size:13px;padding:4px 6px}
button{background:var(--accent);color:#fff;border:none;border-radius:6px;padding:8px 12px;font-size:13px;cursor:pointer;font-weight:600}
button:hover{filter:brightness(1.1)}
button.ghost{background:transparent;border:1px solid var(--line);color:var(--muted)}
button.primary{background:var(--accent);color:#fff;font-weight:800}
button.danger{background:var(--danger-bg);color:var(--red);border:1px solid var(--danger-line)}
.homeLink{display:inline-flex;align-items:center;text-decoration:none;padding:8px 11px;border:1px solid var(--line);border-radius:6px;color:var(--muted);font-size:13px;font-weight:600}
.homeLink:hover{color:var(--txt);border-color:var(--accent)}

main{display:grid;grid-template-columns:1fr 360px;gap:10px;padding:8px;align-items:start}

/* ---------- playmat ---------- */
/* play mode = OP-TCG mat: hands docked left | board centre (full height) | analysis right; fits screen.
   ONE uniform card size for every slot (leader/char/stage/DON/deck/life/trash) like the real playmat. */
body{--cardW:clamp(64px,8.2vh,92px);--cardH:calc(var(--cardW)*1.4)}
/* REVERTED to body.playing (not body.playmode) - widening this to the pre-game screen squeezed the WHOLE
   board layout down critically (the pre-game DOM shape - .handCol hidden, no real .table sizing yet - does
   not tolerate this grid/height constraint the same way a live game's DOM does). The pre-game scroll issue
   needs a different, narrower fix - this is not it. */
body.playing{overflow:hidden}
/* `auto` (not 0) for the hand column: it takes .handCol's width when docked, and collapses to nothing
   when .handCol is hidden (online landing / pre-game), so the board still centres. */
body.playing main{height:calc(100vh - 56px);overflow:hidden;grid-template-columns:auto 1fr 380px;gap:6px;padding:6px;align-items:stretch}
body.playing .table{height:100%;max-width:none;margin:0;overflow:visible;display:flex;flex-direction:column;justify-content:center;transform:translate(var(--board-x),var(--board-y))}
body.playing .analysis{max-height:calc(100vh - 70px);overflow-y:auto}
/* left hand column: opponent's hand top, yours bottom, vertical card lists. In online play a combined
   chat + game-log panel docks BETWEEN the two hand rows (each hand row shrinks from flex:1 to flex:0.7
   to make room, chat gets flex:1 so it claims the freed space). */
.handCol{display:flex;flex-direction:column;gap:6px;height:calc(100vh - 70px);min-width:0}
.handCol.hidden{display:none}
.handSide{flex:1;min-height:0;overflow:auto;background:#0a0f1ccc;border:1px solid var(--line);border-radius:10px;padding:6px;display:flex;flex-direction:column;gap:4px;align-items:center}
body.mode-online .handSide, body.mode-play .handSide{flex:0.7}
/* opponent's row is smaller face-down backs (60% card size, see fitHands()) - it needs proportionally
   less vertical room than the interactive #handArea row, so the game log gets the space it frees up. */
body.mode-online #handTop.handSide, body.mode-play #handTop.handSide{flex:0.4}
/* MULLIGAN exception: #handTop briefly shows the deciding player's REAL opening hand (renderGame's
   Mulligan branch swaps in renderHand(), same full-size renderer #handArea normally uses), not the
   small opponent card-backs this flex:0.4 share was sized for - at that share only about half of each
   card was visible before you had to keep/mulligan. Give it the same room #handArea normally gets
   (#handArea itself is empty/hidden on this branch, so there's nothing else competing for the height). */
body.playing.mulliganStep #handTop.handSide{flex:0.7}
.handSide.top{opacity:.92}
.handSide .handRow{display:grid;grid-template-columns:1fr 1fr;gap:5px;width:100%;justify-items:center;margin:0}
.handSide .hlbl{font-size:10px;color:var(--muted);text-align:center;width:100%;grid-column:1/-1}
.handSide .hcard{width:100%;max-width:96px;opacity:.9}

/* combined chat + game-log panel (online play only) */
.chatLog{flex:1;min-height:0;display:flex;flex-direction:column;background:#0a0f1ccc;border:1px solid var(--line);border-radius:10px;overflow:hidden}
.chatLogList{flex:1;min-height:0;overflow-y:auto;padding:6px 8px;display:flex;flex-direction:column;gap:2px;font-size:12px;line-height:1.35}
.chatLogList .clRow{display:flex;gap:6px;align-items:baseline}
.chatLogList .clTs{flex:none;color:var(--muted);font-size:10px;font-variant-numeric:tabular-nums;opacity:.8}
.chatLogList .clMsg{flex:1;min-width:0;overflow-wrap:break-word;color:var(--txt)}
.chatLogList .clRow.clLog .clMsg{color:var(--muted)}
.chatLogList .clRow.clLog.logTurn .clMsg{color:var(--gold);font-weight:700}
.chatLogList .clRow.clLog.logAbil .clMsg{color:var(--accent2)}
.chatLogList .clRow.clChat .clWho{flex:none;font-weight:700}
.chatLogList .clRow.clChat.clMine .clWho{color:var(--accent)}
.chatLogList .clRow.clChat.clOpp .clWho{color:var(--red)}
.chatLogList .clRow.clChat.clOpp .clMsg{font-weight:700}
.chatLogForm{flex:none;display:flex;gap:7px;padding:9px;border-top:1px solid var(--line)}
.chatLogForm input{flex:1;min-width:0;background:var(--field);border:1.5px solid var(--line);color:var(--txt);
  border-radius:7px;padding:10px 12px;font-size:14px;min-height:38px}
.chatLogForm input:focus{border-color:var(--accent);outline:none}
.chatLogForm button{flex:none;padding:8px 16px;font-size:16px;min-height:38px;border-radius:7px}
.handSide .hcard.play{opacity:1}
/* ===== Playmat-accurate board: each half is the real mat image with slots absolutely positioned =====
   images/playmat.png is 1414x1000 (ratio 1.414). Every .zone is placed at the % rectangle measured from
   that art, so the slots sit exactly inside the printed zones - by construction, not by eyeballing.
   The half MUST keep the art's true ratio: the mat paints as `100% 100%`, so any other ratio distorts it
   and the printed card rectangles stop being 5:7, which is what the .zone>.slot cards are sized to. */
/* each half fills the width; max-width is capped so the two stacked halves still fit the screen height */
.matHalf{position:relative;aspect-ratio:1414/1000;width:100%;max-width:calc((100vh - 70px)/2*1.414*var(--board-scale,1));margin:0 auto}
/* plain dark panel (the playmat illustration is gone - replaced by the per-side leader-colour theme):
   a gradient BORDER in the side's own leader colour(s), a very faint radial wash of that colour behind
   the zones, and a soft outer glow - all via ONE two-layer background-image on .matHalf itself (needs
   both layers on the same element for background-clip to split border vs interior). Keeps the exact same
   box the zone-rectangle percentages were measured against, just themed instead of using mat art.
   --leader-c1/--leader-c2 are set live by applyLeaderTheme() in game.js, per side, as soon as that side's
   leader is known (defaults below cover the moment before that, e.g. before any deck is loaded at all). */
.matHalf{--leader-c1:#3a455f;--leader-c2:#3a455f;
  border:2px solid transparent;border-radius:0 0 14px 14px;
  background-image:radial-gradient(120% 120% at 50% 15%, color-mix(in srgb,var(--leader-c1) 10%,#0a0f1a) 0%, #0a0f1a 65%),
    linear-gradient(155deg, var(--leader-c1), var(--leader-c2));
  background-origin:border-box;background-clip:padding-box, border-box;
  box-shadow:0 0 26px -6px color-mix(in srgb,var(--leader-c1) 55%,transparent), 0 0 46px -18px color-mix(in srgb,var(--leader-c2) 45%,transparent)}
.matHalf.opp{transform:rotate(180deg)}   /* opponent's mat faces the centre */
.matHalf.opp .zone{transform:rotate(180deg)}            /* but keep the cards/text upright */
.matHalf .zone{position:absolute!important;margin:0;z-index:1}
/* deck/trash/life/don zones also carry .pile.cardZone / .lifebox.cardZone / .donStrip, which set
   position:relative LATER in the file - that knocked them out of absolute flow (stacking bottom-right).
   Re-assert absolute on the actual zone elements so their measured left/top hold. */
.matHalf .pile.cardZone,.matHalf .lifebox.cardZone,.matHalf .donStrip,.matHalf .donDeckSlot,.matHalf .slotwrap{position:absolute!important}
/* zone rectangles MEASURED OFF images/playmat.png (1414x1000 px) by detecting the printed rects, so each
   % below is that rect's edge / 1414 (or / 1000). Do not hand-tune: re-measure if the mat art changes.
     charArea x190-1380  y38-322    leader x707-900   y363-637
     stage    x942-1136  y363-637   deck   x1178-1371 y363-637
     dondeck  x43-234    y681-952   don    x272-1142  y665-966    trash x1178-1370 y681-952
   !important on geometry so the generic .pile.cardZone / .lifebox.cardZone fixed-size rules
   (which come LATER in the file and have equal specificity) can't pull these out of position. */
.matHalf .charArea{left:13.4%!important;top:3.8%!important;width:84.2%!important;height:28.4%!important;display:flex;align-items:center;justify-content:flex-start;gap:1%}
.matHalf #leader-me,.matHalf #leader-opp{left:50.0%!important;top:36.3%!important;width:13.6%!important;height:27.4%!important}
.matHalf #stage-me,.matHalf #stage-opp{left:66.6%!important;top:36.3%!important;width:13.7%!important;height:27.4%!important}
/* opponent hand-count zone: on the leader row, in the empty band just DOM-left of the leader. The opp mat
   is rotated 180deg, so DOM-left renders to the visual RIGHT of the leader - i.e. beside it, as intended.
   Hidden by default (desktop shows the real opp hand row); the mobile blocks reveal it. */
.matHalf #hand-opp{left:31.5%!important;top:36.3%!important;width:13.6%!important;height:27.4%!important;display:none}
.matHalf .deck{left:83.3%!important;top:36.3%!important;width:13.6%!important;height:27.4%!important}
.matHalf .trash{left:83.3%!important;top:68.1%!important;width:13.6%!important;height:27.1%!important}
.matHalf .donStrip{left:19.2%!important;top:66.5%!important;width:61.5%!important;height:30.1%!important}
.matHalf .donDeckSlot{left:3.0%!important;top:68.1%!important;width:13.5%!important;height:27.1%!important}
/* the mat prints no life zone (that spot is the ONE PIECE logo); life mirrors the don-deck column on the
   leader row, and .lifeFan straddles the outer edge from there. */
.matHalf .lifebox{left:3.0%!important;top:36.3%!important;width:13.5%!important;height:27.4%!important}
/* Slot CONTENTS are CARD-SHAPED (5:7), sized to the ZONE HEIGHT - never stretched to fill the box.
   Single-card zones (leader/stage/deck/trash/don-deck) centre one card; the character area lays out
   up to 5 cards side-by-side; the cost area lays out the DON cards in a row. */
.matHalf .zone{display:flex;align-items:center;justify-content:center}
/* min-width:0 is essential: without it a flex item's default min-width:auto = min-content, so the empty
   "+ CHARACTER" label text forces each slot wider than its card-shaped aspect-ratio once the mat scales
   down, overflowing the mat. min-width:0 lets each slot shrink to height*5/7 so all 5 fit the zone. */
.matHalf .zone>.pcard,.matHalf .zone>.slot{width:auto!important;height:100%!important;aspect-ratio:5/7;flex:0 0 auto;min-width:0!important}
/* empty slots keep their label contained (only .slot, never .pcard, so card power pills can still overhang) */
.matHalf .zone>.slot{overflow:hidden}
/* deck / trash / life / don-deck KEEP their measured absolute box; their inner card-back image fills the box */
.matHalf .pile.cardZone{padding:0;overflow:hidden;border-radius:6px}
.matHalf .pile.cardZone>.cardBack{width:100%!important;height:100%!important;object-fit:cover;border-radius:6px}
/* Deck/Trash reuse Life's fan EXACTLY - same .lifeFan class, same markup, same straddle-the-edge/
   overlapping/hover-to-count behaviour, not a lookalike clone. Every selector below that used to read
   .lifebox.lifeFan now also matches .pile.lifeFan, so Deck/Trash get IDENTICAL rules, not a parallel
   .miniFan system. Deck/Trash render with the real full pile count (no fixed-depth cap), same as Life. */
.matHalf .lifebox.lifeFan,.matHalf .pile.lifeFan{overflow:visible!important;background:transparent!important;border:none!important}
.matHalf .lifebox.lifeFan .lifeFanWrap,.matHalf .pile.lifeFan .lifeFanWrap{position:absolute;inset:0}
/* Life cards straddle the mat's OUTER edge (half on / half off) and fan with a tight step so every top
   edge is countable. Tunables (shared by both sides):
     --life-off  : how far off the edge each card hangs (50% = half off)
     --life-step : vertical gap between cards
     --life-y    : move the whole block toward the centre(-) / outward(+) from each player's view
   The opponent's .zone is counter-rotated 180deg, so its local axes are reversed. --life-yd carries the
   per-side sign for the vertical offset: +1 for you, -1 for the opp (set on .matHalf.opp below) so that
   moving --life-y mirrors correctly for both players. */
.matHalf .lifebox.lifeFan,.matHalf .pile.lifeFan{--life-yd:1;--life-xd:1}
.matHalf.opp .lifebox.lifeFan,.matHalf.opp .pile.lifeFan{--life-yd:-1;--life-xd:-1}
/* Deck/Trash fan much tighter than Life's default 13% step - a near-flush stack reads better for a
   40+ card pile than Life's wider fan (which is tuned for a life total in the single digits). Also
   flush inside their own box (--life-off:0), NOT straddling the edge like Life - Deck/Trash are
   interior zones, so a half-off-the-edge fan spills into neighbouring zones instead of reading as
   an intentional overhang the way it does for Life sitting right on the mat's outer boundary.
   --life-y must ALSO be reset to 0 here - :root's fallback (-55%, tuned for Life's own straddle look)
   otherwise inherits straight through and shoves the whole fan up out of the box entirely. */
.matHalf .pile.lifeFan{--life-step:0.1%;--life-off:0%;--life-y:0%}
.matHalf .lifebox.lifeFan .lifeCard,.matHalf .pile.lifeFan .lifeCard{position:absolute!important;left:calc(var(--life-xd,1) * -1 * var(--life-off,50%))!important;
  width:100%!important;aspect-ratio:5/7;height:auto!important;
  top:calc(var(--life-yd,1) * var(--life-y,0%) + var(--i) * var(--life-step,13%))!important;
  border-radius:5px;box-shadow:0 1px 4px #000a;z-index:calc(10 + var(--i))}
/* life count on hover: sits OVER the TOP life card (the --i:0 card), above all card z-indexes.
   The top card's box is: left = --life-xd * -1 * --life-off, top = --life-yd * --life-y, width = 100% of the
   lifebox. We use transform:translate(-50%,-50%) so left/top below define the count's CENTRE, then place that
   centre at the card's midpoint (card-left + 50% of the box width; card-top + ~30% down its face).
   NOTE: must set transform explicitly to override the inherited .lifebox.cardZone .zoneCnt translate. */
.matHalf .lifebox.lifeFan .zoneCnt,.matHalf .pile.lifeFan .zoneCnt{position:absolute!important;z-index:999!important;
  transform:translate(-50%,-50%)!important;
  left:calc(var(--life-xd,1) * -1 * var(--life-off,50%) + 50%)!important;
  top:calc(var(--life-yd,1) * var(--life-y,0%) + 28%)!important;
  font-size:22px!important;opacity:0;transition:opacity .12s;pointer-events:none}
.matHalf .lifebox.lifeFan:hover .zoneCnt,.matHalf .pile.lifeFan:hover .zoneCnt{opacity:1}
.matHalf .trash .trashTop{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:6px}   /* trash shows the most recent card's face */
.matHalf .donDeckSlot>.donDeck{width:100%!important;height:100%!important;max-width:none!important;max-height:none!important}
/* hover: orange highlight on the zone, and reveal the count (count hidden otherwise). covers deck/trash/life/don-deck/cost */
.matHalf .pile.cardZone:hover,.matHalf .lifebox.cardZone:hover,.matHalf .donDeckSlot:hover,.matHalf .donStrip:hover{
  outline:2px solid var(--gold);outline-offset:-2px;box-shadow:0 0 10px #ffd24a88,inset 0 0 14px #ffd24a33;border-radius:6px}
.matHalf .pile.cardZone:hover .zoneCnt,.matHalf .lifebox.cardZone:hover .zoneCnt{opacity:1}
/* ...but the life FAN's cards straddle the mat edge, nowhere near its zone rectangle, so highlighting the
   zone is meaningless. Kill that outline and light up the TOP life card instead. The cards carry
   z-index:10+(--i), so :last-child is the front of the pile (the fully visible one) - :first-child is the
   card buried at the BACK. No z lift needed: the top card already paints above its siblings. */
.matHalf .lifebox.lifeFan:hover{outline:none;box-shadow:none}
.matHalf .lifebox.lifeFan:hover .lifeCard:last-child{
  outline:2px solid var(--gold);outline-offset:-2px;box-shadow:0 0 10px #ffd24a88,inset 0 0 14px #ffd24a33}
/* DON cost area + DON deck: reveal an available/total badge on hover */
.matHalf .donStrip .donCnt,.matHalf .donDeckSlot .donCnt{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:6;opacity:0;transition:opacity .12s;pointer-events:none;white-space:nowrap}
.matHalf .donStrip:hover .donCnt,.matHalf .donDeckSlot:hover .donCnt{opacity:1}
/* hover counts: large + orange, nearly card-height, for deck/trash/life/don/cost */
.matHalf .pile.cardZone .zoneCnt,.matHalf .lifebox.cardZone .zoneCnt,.matHalf .donStrip .donCnt,.matHalf .donDeckSlot .donCnt{
  font-size:clamp(20px,5vh,46px);font-weight:900;color:var(--gold);text-shadow:0 1px 4px #000,0 0 8px #000,0 0 3px #000;letter-spacing:0}
/* board text (counts, names, power pills) is not selectable/highlightable */
.matHalf,.matHalf *{user-select:none;-webkit-user-select:none}
/* every image on the board (DON tokens, life cards, deck/trash backs, etc.) - user-select alone stops
   TEXT selection but not the mobile long-press "open image in new tab / save image" context menu, which
   needs -webkit-touch-callout/-webkit-user-drag specifically on the <img> itself. */
.matHalf img{-webkit-touch-callout:none;-webkit-user-drag:none}
.matHalf .donDeckSlot{position:absolute!important}   /* ensure the hover badge anchors to the slot */
/* These zone containers share ONE equal fill + border (Cost Area, Life, Deck, Trash, DON deck) - on both
   your side and the opponent's - so transparency is uniform across the whole board.
   (This applies in the board sandbox AND is re-asserted, slightly stronger, by the body.playing rules.)
   The Character Area is deliberately NOT in this list: it has no box of its own, so each character cell
   reads as its own piece (see below), matching the leader/stage slots. */
.matHalf .donStrip,.matHalf .donDeckSlot,.matHalf .trash,.matHalf .lifebox:not(.lifeFan){
  background:#0e162688!important;border:1px solid #33445f!important;border-radius:8px}
/* no Character Area box behind the cells: each empty character cell carries its OWN translucent fill +
   border, so the five potential board pieces read individually (same look as the leader/stage slots). */
.matHalf .charArea>.slot{background:#0e162688!important;border:1px solid #33445f!important;border-radius:6px}
/* character area: 5 card-shaped slots fit across it, packed from the left */
.matHalf .charArea{gap:1.4%;justify-content:flex-start;align-items:center}
.matHalf .charArea>.pcard,.matHalf .charArea>.slot{height:96%;width:auto;aspect-ratio:5/7}
/* cost area: DON cards in a single row, sized to sit INSIDE the strip's height (they used to be 118% and
   spill above/below it). Horizontal fit comes from bunching them with a negative margin. */
.matHalf .donStrip{display:flex!important;align-items:center;justify-content:center!important;gap:0;padding:0 2%!important;overflow:visible!important;flex-direction:row!important;min-height:0!important;min-width:0!important}
.matHalf .donTokens{display:flex!important;align-items:center;justify-content:center;gap:0!important;flex-wrap:nowrap!important;max-width:100%;height:100%;padding:0!important;overflow:visible}
/* --don-mr is measured by fitDon() in game.js: the per-card overlap that makes the fan span its zone
   exactly, rather than bunching in the middle with dead space at both ends. Uniform for rested + active. */
.matHalf .donCard{height:92%!important;width:auto!important;aspect-ratio:5/7;max-width:none!important;max-height:none!important;margin:0 var(--don-mr,-20%) 0 0!important;border-radius:3px;flex:0 0 auto}
.matHalf .donCard:last-child{margin-right:0!important}
.matHalf .lifeCards{height:100%;width:auto;aspect-ratio:5/7}
.matHalf .lifeCard{height:100%!important;width:auto!important;aspect-ratio:5/7}
/* DEBUG: outline every zone + show its id, so we can see exact box positions (toggle with ?debugzones=1) */
body.dbgzones .matHalf .zone{outline:2px solid #00e5ff;outline-offset:-2px;background:#00e5ff14}
body.dbgzones .matHalf .zone::before{content:attr(id);position:absolute;top:1px;left:2px;font-size:9px;font-weight:800;color:#00e5ff;background:#000a;padding:0 3px;border-radius:3px;z-index:99;pointer-events:none}
/* the two mat halves stack vertically and centre; each is aspect-locked so the slots stay aligned */
body.playing .table{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:0;padding:4px}
/* OPEN play area: keep the PLAYMAT, just drop the outer panel BOX behind it (the dark gradient, border
   and rounded container). Empty character/stage slots and empty deck/trash/life piles also lose their
   dashed outline so the mat reads cleanly; populated cards + their counts stay visible. */
body.playing .table{background:none;border:none;border-radius:0;padding:0}
/* the page background itself (behind/around the board, visible in the gaps once .table's own panel
   background is dropped above) matches the board's own dark tone instead of the lighter --bg blue -
   body.playmode so pre-game/mulligan already sits on the same dark canvas, not just once .table's
   background is actually dropped (which only happens in body.playing). */
body.playmode{background:#0a0f1a}
/* empty Leader/Stage/Character slots (no card placed there yet) are now just blank space, in pre-game AND
   live play - no border, no "+", no label. The playmat art (which these overlays used to trace) is gone;
   the zone's own border/fill (charArea, donStrip, trash, deck etc - themed to the leader colour further
   below) is what shows the board's shape, not a per-empty-slot outline. Populated cards still show fully.
   body.playmode (not the stricter body.playing) - this must already be true on the pre-game landing/
   mulligan screen, or the OLD bordered-+ placeholder (further up the cascade) wins there instead. */
body.playmode .slot,body.playmode .slot.dim{border:none!important;background:transparent!important;color:transparent!important}
body.playmode .slot:hover{border:none!important;background:transparent!important}
body.playmode .slot .plus{display:none!important}
body.playmode .matHalf .pile:not(.cardZone),body.playmode .matHalf .lifebox:not(.cardZone),body.playmode .matHalf .donDeckSlot:not(.cardZone){border:none!important;background:transparent!important}
body.playmode .matHalf .charArea>.slot{background:transparent!important;border:none!important}
body.playmode .matHalf .pile:not(.cardZone) .lbl{display:none!important}
body.playmode .matHalf .pile:not(.cardZone) .cnt{display:none!important}
/* hands: no panel box behind the cards in play mode - just the cards laid out (matches the OPTCG sim).
   The hand FLOATS over the board (fixed), positioned by these CSS variables so it sits off the right edge
   of the mat (right-anchored). Board position/scale are vars too. */
/* Board choreography vars. These are FALLBACKS only - fitBoard() in game.js measures the real
   viewport + mat on load / resize / render and overwrites them so the board fits any display. */
:root{ --hand-gap:5px; --hand-col-w:clamp(240px,26vw,520px);
       --board-scale:1; --board-x:0px; --board-y:0px;
       --life-off:70%; --life-step:10%; --life-y:-55%; }
/* Play mode: the hands DOCK IN THE LEFT COLUMN (opponent top, you bottom), spreading from the board's left
   edge out towards the screen edge - they never float over the board or sit above/below it. Five columns,
   so a normal hand reads as 2 rows. fitHands() in game.js sets --hand-cols (growing past 5 only if a huge
   hand would otherwise overflow) and pads each panel clear of the life fan, which hangs off the mat's
   outer edge into this column. */
/* z-index is load-bearing: fitHands slides the hand right, out of its own column and over .table's box.
   .table carries a transform (a stacking context), so it would otherwise paint over the hand and eat the
   clicks on the cards nearest the board. Lift the hand above it. */
body.playing .handCol{width:var(--hand-col-w);height:auto;align-self:stretch;min-height:0;position:relative;z-index:30}
/* overflow must be visible: fitHands translates the row RIGHT, out of the hand column, so it meets the
   board's left edge (the mat is centred in a much wider column). fitHands sizes the hand to fit, so
   nothing is lost by dropping the scrollbar. */
body.playing .handSide{background:none;border:none;border-radius:0;padding:0;overflow:visible}
/* Play-mode hand = a fan of overlapping cards, laid out EXACTLY like the DON strip. fitHands() (game.js)
   groups the cards into explicit .handRowLine wrappers of --hand-cols each (deterministic wrapping - flex
   -wrap's margin-box line-breaking counts the trailing negative margin inconsistently, so it can't be
   trusted to put exactly N per row). Each line is a nowrap flex; overlap is a negative margin-RIGHT on
   every card but the last (same as the DON strip's --don-mr). --hand-gap-eff is 0-or-negative only, never
   a positive gap. Lines stack in a column, bottom-anchored, LEFT-justified (hugging the screen edge). */
body.playing .handSide .handRow{display:flex;flex-direction:column;gap:0;
  justify-content:flex-end;align-items:flex-start;margin:0;padding:0}
body.playing #handArea .handRow{margin-top:auto}
body.playing .handSide .handRowLine{display:flex;flex-wrap:nowrap;gap:0;justify-content:flex-start;align-items:flex-end;margin:0;padding:0}
body.playing .handSide .hcard{width:var(--hand-card-w,72px);flex:0 0 auto;max-width:none;opacity:1;position:relative;
  margin-right:var(--hand-gap-eff,0px);margin-bottom:0}
body.playing .handSide .handRowLine .hcard:last-child{margin-right:0}
/* Hovering a hand card does NOT lift/scale/reorder it (that reflows the overlapping fan and was distracting)
   - the magnified card preview (#cardPreview, top-left under the banner) is the only hover feedback. Cancel
   the lift+scale from .hcard.play:hover and the bring-to-front z-index for BOTH your hand and the opp fan. */
body.playing .handSide .hcard:hover,
body.playing .handSide .hcard.play:hover{transform:none;box-shadow:none;z-index:auto}
/* opponent hand-count pill (desktop): hidden until you hover the opponent's hand fan, then a gold count
   badge fades in centred over it - "how many cards does the opponent hold" at a glance. The .handRow is a
   positioned, fitHands-transformed block, so anchor the badge to it and centre with translate. */
body.playing #handTop .handRow{position:relative}
body.playing #handTop .handCntBadge{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  z-index:8;display:grid;place-items:center;min-width:30px;height:30px;padding:0 9px;border-radius:15px;
  font-size:16px;font-weight:800;color:#2a1400;background:#e0a536f2;border:1.5px solid #ffd27f;
  box-shadow:0 2px 10px #000a,0 0 12px #e0a53688;opacity:0;transition:opacity .12s;pointer-events:none;white-space:nowrap}
body.playing #handTop .handRow:hover .handCntBadge{opacity:1}
body.playing .handSide .hcard .hinfo{display:none}   /* card image only - no cost/power footer chips */
body.playing .handSide .hcard .himg{aspect-ratio:5/7;border-radius:6px;overflow:hidden}
body.playing .handSide .hcard .himg img{width:100%;height:100%;object-fit:cover}
/* ===== NAVIGATION MODEL =========================================================================
   The URL is the single source of truth. applyRoute() (app.js, end of boot) stamps exactly one of
   body.mode-sandbox / .mode-play / .mode-online from the path. Sub-states live INSIDE a mode and can
   never escape it:
       play   : .playmode + .playland (setup)   ->  .playing        (game running)
       online : .onlineland (landing / lobby)   ->  .online .playing (match)
   Every exit NAVIGATES (Home -> /, Exit Play -> /), so no in-page transition can drop you into another
   mode's UI. Header control visibility is declared HERE and nowhere else - do not scatter it again. */
body:not(.mode-sandbox) .sandboxOnly{display:none}   /* opp-counter / opp-hand-size inputs */
body:not(.mode-sandbox) #btnReset{display:none}      /* "clear both boards" is a sandbox tool */
body:not(.mode-sandbox) #hand-me{display:none}       /* the planning hand is a sandbox tool */
body.mode-sandbox .netOnly{display:none}             /* chat + game log only make sense in a live game (hot-seat or online), not the sandbox planner */
body:not(.mode-online) .chatLogForm{display:none}    /* the chat INPUT is online-only - hot-seat has no opponent to message, just the log */
body:not(.mode-play)    #deckStart{display:none}     /* "Apply & Start Game" only ever starts hot-seat */
body.mode-online        #btnDecks{display:none}      /* decks are chosen in the lobby, per match */
body.playing            #btnDecks{display:none}      /* ...and are locked once a game is running */
#btnMode{display:none}                               /* "Exit Play" - hot-seat, mid-game, only */
body.mode-play.playing  #btnMode{display:inline-flex}
/* Header title: exactly one per mode. */
.titlePlay,.titleOnline{display:none}
body.mode-play   .titleSandbox,body.mode-online .titleSandbox{display:none}
body.mode-play   .titlePlay{display:inline-flex}
body.mode-online .titleOnline{display:inline-flex}
.table{background:
   radial-gradient(120% 80% at 50% 0%, #16273f 0%, #0b1422 60%, #080d16 100%);
   border:1px solid var(--line);border-radius:16px;padding:10px;display:flex;flex-direction:column;gap:4px}
.midline{display:flex;align-items:center;justify-content:center;height:0;border-top:2px dashed #38465f;margin:2px 6px;position:relative}
.midline span{position:absolute;background:var(--bg);padding:0 10px;font-size:10px;letter-spacing:2px;text-transform:uppercase;color:var(--muted);top:-8px}
.row{display:flex;gap:10px;align-items:flex-start}
.row.chars{gap:10px}
.row.front{justify-content:center;align-items:stretch}
.row.utility{align-items:center}
/* faint zone watermarks (play mode), OPTCGSim-style */
body.playing .row.chars{position:relative;min-height:0;justify-content:center;gap:6px}   /* centre played characters in the character area */
body.playing .mainRow .slotwrap{flex:0 0 auto}
body.playing .donStrip{position:relative}
body.playing .row.chars > *{position:relative;z-index:1}

/* zones / piles */
.pile{min-width:46px;min-height:58px;border:1px solid #33445f;border-radius:8px;background:#0e162688;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--muted);font-size:8px;gap:2px;text-transform:uppercase;letter-spacing:1px}
.pile .cnt{font-size:14px;color:var(--txt);font-weight:700}
.pile.deck,.pile.misc{opacity:.6}
.pile.trash{cursor:default}
.pile.clickable{cursor:pointer;border-style:solid;border-color:var(--accent2)}
.pile.clickable:hover{background:#16243c}
.zoneHint{font-size:7px;color:var(--accent2);text-transform:lowercase;letter-spacing:0}
/* visual card-image zones (play mode) - deck/life as card-back stacks */
.pile.cardZone{position:relative;border:1px solid var(--line);padding:0;width:var(--cardW);height:var(--cardH);min-width:0;overflow:hidden;flex:0 0 auto}
.pile.cardZone .cardBack{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:7px}
.pile.cardZone .zoneCnt{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:16px;font-weight:800;color:#fff;text-shadow:0 1px 4px #000,0 0 6px #000;z-index:3;opacity:0;transition:opacity .12s}
.pile.cardZone .lbl{display:none}   /* the mat art already labels the zone */
/* deck/trash/DON zone boxes on the LIVE board also pick up the side's leader-colour border - same
   two-layer background-image/background-clip technique as .pcard/the mat half itself. */
/* ---- per-zone-type textures, layered UNDER the fill colour and ABOVE nothing (subtle, low-opacity SVG
   tiles as data-URIs - no image assets needed). Each zone type gets its own pattern so the board reads by
   TYPE, not just by colour: diagonal hatch for the Character area, small dots for the DON/cost strip,
   a chevron weave for the Stage slot, fine crosshatch for Trash/Deck piles. Kept deliberately faint
   (stroke/fill opacity baked into the SVG itself) so card art and text stay perfectly legible on top. ---- */
:root{
  --tex-char:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14'%3E%3Cpath d='M0 14L14 0' stroke='%23ffffff' stroke-opacity='0.022' stroke-width='1'/%3E%3C/svg%3E");
  --tex-don:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Ccircle cx='2' cy='2' r='1.1' fill='%23ffffff' fill-opacity='0.03'/%3E%3C/svg%3E");
  --tex-stage:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='10'%3E%3Cpath d='M0 10L5 0L10 10L15 0L20 10' fill='none' stroke='%23ffffff' stroke-opacity='0.025' stroke-width='1'/%3E%3C/svg%3E");
  --tex-pile:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3E%3Cpath d='M0 0L10 10M10 0L0 10' stroke='%23ffffff' stroke-opacity='0.022' stroke-width='.8'/%3E%3C/svg%3E");
}
/* Deck (.pile.cardZone) theming REVERTED to bare (pre-session state) - it was implicated in a reported
   box-vs-image size mismatch that couldn't be root-caused from CSS alone, and the user wants Deck's whole
   rendering model changed to a Life-style card fan next anyway (separate follow-up), so there's no point
   chasing this decoration further right now. */
/* Deck/Trash keep their card-back/last-trashed-card ART (above), just leader-themed. Character Area, the
   Cost/DON strip, and Stage are different: NO card-back art of their own, so they go fully TRANSPARENT
   inside with ONLY a themed border - a single-colour INSET box-shadow on the real element (see below),
   never a background-image/background-clip split, which kept leaking a visible fill colour into the
   interior in practice no matter how it was scoped/!important'd. box-shadow with `inset` cannot paint
   past its own width, so there's no fill layer left to leak, full stop. A text label (like the old
   background.png mat art used to show) names the zone while it's genuinely empty - via a REAL .zoneEmpty
   class (game.js's applyLeaderTheme checks actual board state), not CSS :empty: donPlayHTML/renderDon
   always emit a wrapper div even at 0 DON, so #don-side is never truly childless and :empty never matched
   there at all (only #chars-me happened to look empty by coincidence pre-game - which is why the label
   only ever showed on YOUR Character Area and nowhere else). Scoped to body.playmode (NOT the stricter
   body.playing) so this shows on the pre-game "who goes first?"/mulligan screen too, not just once a real
   game is running. */
/* NOT setting position here - .donStrip/.charArea/[id^="stage-"] are already position:absolute!important
   (line ~199, their real placement on the mat). Re-declaring position:relative on top of that was
   interfering with .donCnt's centering (it's absolutely positioned relative to whichever ancestor box
   actually ends up establishing the containing block) and the DON card fan layout. */
body.playmode .matHalf .charArea,body.playmode .matHalf .donStrip,body.playmode .matHalf [id^="stage-"]{
  background:none!important;background-image:none!important;border:1.5px solid transparent!important;border-radius:8px;
  /* border colour via INSET box-shadow, not background-image/background-clip - that two-layer split kept
     leaking a visible interior fill in practice (another rule was winning the cascade for the plain
     `background` shorthand somewhere along the line). An inset shadow physically cannot paint the true
     interior beyond its own width, so there is no fill layer left to leak at all. Single colour only
     (leader-c1) - box-shadow can't do a 2-tone gradient the way background-image could, that's the
     trade-off for guaranteed no-leak borders. */
  box-shadow:inset 0 0 0 1.5px var(--leader-c1)!important}
/* ONE ::after per zone does BOTH jobs: the per-type texture (always) AND the empty-zone text label (only
   while .zoneEmpty - `content` and `background-image` aren't mutually exclusive on the same element, so
   this needed combining into a single rule set instead of two competing ::after declarations). The border
   itself is the inset box-shadow above, on the real element - always shows, populated or empty. */
body.playmode .matHalf .charArea::after,body.playmode .matHalf .donStrip::after,body.playmode .matHalf [id^="stage-"]::after{
  content:'';position:absolute;inset:0;z-index:0;pointer-events:none;border-radius:8px;
  display:flex;align-items:center;justify-content:center;font-size:clamp(13px,2.2vw,20px);font-weight:800;letter-spacing:1.5px;text-transform:uppercase;color:#ffffffc4;text-shadow:0 1px 3px #000c}
body.playmode .matHalf .charArea::after{background-image:var(--tex-char);background-repeat:repeat;background-size:14px 14px}
body.playmode .matHalf .donStrip::after{background-image:var(--tex-don);background-repeat:repeat;background-size:16px 16px}
body.playmode .matHalf [id^="stage-"]::after{background-image:var(--tex-stage);background-repeat:repeat;background-size:20px 10px}
body.playmode .matHalf .charArea.zoneEmpty::after{content:'Character Area'}
body.playmode .matHalf .donStrip.zoneEmpty::after{content:'Cost Area'}
body.playmode .matHalf [id^="stage-"].zoneEmpty::after{content:'Stage'}
/* lift real content above the ::before/::after overlays (z-index:0) WITHOUT touching elements that already
   manage their own position (.donCnt centres itself via position:absolute + translate(-50%,-50%) - forcing
   position:relative on it here overwrote that and broke the DON count's centering). z-index alone is
   enough to win the stacking order; position was never actually needed on children that are already
   positioned, and is actively wrong for ones that aren't (a bare z-index on a static element is a no-op,
   which is why .pcard/.donTokens - both already relatively/statically fine as flex children - still need
   it declared explicitly to participate in stacking at all). */
body.playmode .matHalf .charArea>.pcard,body.playmode .matHalf .charArea>.slot,
body.playmode .matHalf .donStrip>.donTokens,body.playmode .matHalf .donStrip>.donCtl,
body.playmode .matHalf [id^="stage-"]>.pcard,body.playmode .matHalf [id^="stage-"]>.slot{position:relative;z-index:1}
/* Deck/Trash: NO fill colour and NO background-image on the real element at all (that two-layer
   background-clip split kept leaking a visible interior fill from other rules winning the cascade in
   practice - box-shadow can't leak like that, since `inset` shadows never paint the true interior, only a
   ring at the edge). Border colour = inset box-shadow forming a coloured ring; texture lives on its OWN
   ::after overlay (already proven not to leak, since it's a separate stacking element). */
body.playmode .matHalf .trash{
  background:none!important;background-image:none!important;border:1.5px solid transparent!important;border-radius:8px;
  box-shadow:inset 0 0 0 1.5px var(--leader-c1)!important}
body.playmode .matHalf .trash::after{
  content:'';position:absolute;inset:0;z-index:0;pointer-events:none;border-radius:8px;
  background-image:var(--tex-pile);background-repeat:repeat;background-size:10px 10px}
body.playmode .matHalf .trash>*{position:relative;z-index:1}
.lifebox.cardZone{background:transparent;border:1px solid var(--line);position:relative;overflow:hidden;border-radius:6px}
.lifebox.cardZone .cardBack{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:6px}
.lifeCards{position:relative;width:var(--cardW);height:var(--cardH);margin:0 auto}
.lifeCard{position:absolute;width:var(--cardW);height:var(--cardH);object-fit:cover;border-radius:4px;box-shadow:0 1px 3px #0008}
.lifebox.cardZone .zoneCnt{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:16px;font-weight:800;color:#fff;text-shadow:0 1px 4px #000,0 0 6px #000;z-index:5;opacity:0;transition:opacity .12s}
.lifebox.cardZone .lh{display:none}
/* DON as card images */
.donCards{gap:2px}
.donCard.free{box-shadow:0 0 4px #ffd24a99}
/* spent/given DON is RESTED (rotated), not faded - effects can also give DON rested */
.donCard.used{transform:rotate(90deg);transform-origin:center;margin:0 calc(var(--cardW)*0.19);opacity:.92}
.zoneEmpty{color:var(--muted);font-style:italic;padding:20px;grid-column:1/-1;text-align:center}
.trashStep{display:flex;gap:3px;margin-top:2px}
.trashStep button{padding:0 6px;background:var(--btn-bg);border:1px solid var(--line);color:var(--txt);font-size:12px;line-height:1.4;border-radius:4px;cursor:pointer}

/* DON cost area */
.donStrip{flex:1;display:flex;flex-direction:column;gap:4px;background:var(--field);border:1px solid #233350;border-radius:8px;padding:6px 8px}
.donHead{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--muted)}
.donHead b{color:var(--gold);font-size:13px}
.donHead .sp{margin-left:auto;display:flex;gap:4px}
/* round glass +/- controls with the live avail/total count between them */
.donHead button{width:22px;height:22px;padding:0;border-radius:50%;background:#0b1322c9;border:1px solid #ffffff2e;color:#dbe4f8;font-size:14px;line-height:1;display:grid;place-items:center}
.donHead button:hover{border-color:var(--gold);color:var(--gold);background:#0b1322}
.donHead .donN{font-size:11px;font-weight:800;color:var(--gold);font-variant-numeric:tabular-nums;text-shadow:0 1px 3px #000c;min-width:34px;text-align:center}
.donTokens{display:flex;flex-wrap:wrap;gap:3px}
/* sandbox DON tokens are the real DON!! card art now (was a striped placeholder chip) */
.dt{width:16px;height:22px;border-radius:3px;object-fit:cover;border:1px solid #4a3f63;display:block}
.dt.free{border-color:var(--gold);box-shadow:0 0 5px #ffd24a80}
.dt.used{opacity:.3;filter:grayscale(.85)}
/* sandbox DON cost area: centred +/- controls with the DON tokens directly underneath, no text labels
   (the .matHalf .donStrip rule forces row for play-mode DON cards, so re-stack it as a column here). */
/* Keyed on mode-sandbox, NOT :not(.playing). renderDon() emits play-style .donCard images whenever
   playUIActive() is true - which is inPlay() OR _wantPlay, i.e. the whole play UI INCLUDING setup. But
   .playing is only set once a game is actually running. During setup (after Exit / New Game) the two
   disagreed: play markup met sandbox CSS, `height:auto` on .donTokens made `.donCard{height:92%}`
   indeterminate, and the images fell back to Don.png's intrinsic size and blew up. */
/* sandbox cost area: big play-mode-sized DON cards filling the strip, with a FIXED centre control
   pill overlaid on top - the buttons never move when tokens are added/removed (no layout shift) */
body.mode-sandbox .matHalf .donStrip{flex-direction:row!important;justify-content:center!important;align-items:center!important;gap:0;position:relative}
body.mode-sandbox .matHalf .donTokens{gap:0!important;flex-wrap:nowrap!important;height:100%;align-items:center;justify-content:center}
body.mode-sandbox .matHalf .dt{flex:0 0 auto;height:88%;width:auto;aspect-ratio:5/7;border-radius:4px;margin:0 var(--sb-mr,0) 0 0}
body.mode-sandbox .matHalf .dt:last-child{margin-right:0}
.donCtl{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);z-index:6;
  display:flex;align-items:center;gap:5px;background:#0b1322d9;border:1px solid #ffffff2e;border-radius:999px;padding:2px 5px;
  -webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);box-shadow:0 2px 9px #000b}
.donCtl button{width:22px;height:22px;padding:0;min-width:0;border:none;border-radius:50%;background:transparent;color:#dbe4f8;font-size:14px;line-height:1;display:grid;place-items:center}
.donCtl button:hover{background:#ffffff22;color:var(--gold);transform:none}
.donCtl .donN{font-size:11px;font-weight:800;color:var(--gold);font-variant-numeric:tabular-nums;min-width:32px;text-align:center}
/* play-mode cost area: DON deck pile + a single row of DON card images, no text.
   all DON-area cards share the true 5:7 card ratio and the same height, like the local sim.
   Border/background/box-shadow for these two zones now live ONLY in the leader-themed
   .matHalf .donStrip / .matHalf .charArea rules further down (search "type texture") - this used to
   carry its own competing !important border/fill here too, which is what was winning the cascade and
   showing a plain white-ish border instead of the leader-colour theme. */
body.playing .donStrip{flex:1 1 auto;flex-direction:row;align-items:center;gap:5px;padding:4px 8px;overflow:hidden;justify-content:flex-start;min-width:0;min-height:var(--cardH)}
body.playing .charArea{position:relative}
.donDeck{position:relative;width:var(--cardW,72px);height:var(--cardH,100px);max-width:92px;max-height:130px;flex:0 0 auto;overflow:hidden}
.donDeck img{width:100%;height:100%;object-fit:cover;border-radius:4px;display:block}
.donDeckN{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:12px;font-weight:800;color:#fff;text-shadow:0 1px 3px #000,0 0 5px #000}
/* DON cards in the cost area are full card size but FAN/overlap (like the mat) so all 10 fit */
body.playing .donTokens{display:flex;flex-wrap:nowrap;align-items:center;padding-left:calc(var(--cardW)*0.62)}
.donCard{width:var(--cardW,72px);height:var(--cardH,100px);max-width:92px;max-height:130px;border-radius:4px;object-fit:cover;border:1px solid #5a4d2a;flex:0 0 auto;margin-left:calc(var(--cardW,72px) * -0.45)}

/* life */
.lifebox{min-width:58px;background:#0e162688;border:1px solid #33445f;border-radius:8px;padding:3px 7px;display:flex;flex-direction:column;gap:2px;align-items:center}
.lifebox .lv{font-size:17px}
.donStrip{padding:4px 7px}
.pcard .pill{font-size:12px;padding:1px 9px;top:-9px}
.pcard .nm{font-size:8px;height:auto;line-height:1.1;max-height:18px;overflow:hidden}
.pcard .foot{padding:11px 3px 3px}
.stp button{padding:0 4px;font-size:11px;min-width:14px}.stp .v{min-width:13px;font-size:10px}.stp b{font-size:7px}
.lifebox .lh{font-size:10px;color:var(--muted);letter-spacing:1px}
.lifebox .lv{font-size:20px;font-weight:800}
.lifebox .lr{display:flex;gap:4px}
.lifebox .lr button{padding:0 8px;background:var(--btn-bg);border:1px solid var(--line);color:var(--txt);font-size:14px}
.lifeStack{display:flex;gap:2px;height:8px}
.lifeStack i{width:5px;height:8px;background:var(--red);border-radius:1px;display:block}

/* ---------- cards & slots ---------- */
.slotwrap{display:flex}
.row.chars{flex:1}
.pcard,.slot{width:clamp(72px,7.2vw,100px);height:calc(clamp(72px,7.2vw,100px)*1.4);border-radius:8px;position:relative;flex-shrink:0}
.slot{border:1px solid #33445f;background:#0e162688;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:1px;transition:border-color .12s,background .12s}
.slot:hover{border-color:var(--accent);background:#101d31bb;color:var(--accent)}
.slot.leaderSlot{border-color:#6a5a23}
.slot .plus{font-size:22px;line-height:1;display:block;text-align:center}

.pcard{background:#0c1018;border:1px solid #28324d;overflow:visible;cursor:pointer}
/* play UI only (sandbox/calculator .pcard keeps its plain neutral border): board Leader/Character/Stage
   cards pick up their SIDE's own leader-colour gradient border + soft glow, same two-layer
   background-image/background-clip technique the mat half itself uses. --leader-c1/-c2 are inherited
   from the ancestor .matHalf (set per-side by applyLeaderTheme in game.js). body.playmode (not the
   stricter body.playing) so this already applies on the pre-game landing/mulligan screen too. */
body.playmode .matHalf .pcard{
  border:1.5px solid transparent;border-radius:10px;
  background-image:linear-gradient(#0c1018,#0c1018), linear-gradient(155deg, var(--leader-c1), var(--leader-c2));
  background-origin:border-box;background-clip:padding-box, border-box;
  box-shadow:0 0 14px -4px color-mix(in srgb,var(--leader-c1) 60%,transparent)}
.pcard .art{position:absolute;inset:0;border-radius:9px;overflow:hidden;background:#080b12}
.pcard .art img{width:100%;height:100%;object-fit:cover;display:block}
.pcard.rested .art{filter:saturate(.55) brightness(.7)}
.pcard.rested{transform:rotate(90deg);margin:0 calc(clamp(72px,7.2vw,100px)*0.22);z-index:2}
.pcard .pill{position:absolute;top:-11px;left:50%;transform:translateX(-50%);background:var(--pill);border:1px solid #3a455f;color:#fff;font-weight:800;font-size:14px;padding:2px 12px;border-radius:14px;white-space:nowrap;z-index:3;box-shadow:0 2px 6px #000a}
.pcard .pill.buffed{color:var(--gold);border-color:#7a6320}
.pcard .pill small{font-size:9px;opacity:.6;font-weight:600}
/* play mode: bigger power bubble; hide the counter (shield) and REST text bubbles (the 90deg rotation shows rest) */
body.playing .matHalf .pcard .pill{font-size:16px;padding:2px 13px;top:-12px}
body.playing .matHalf .pcard .pill small{font-size:11px}
body.playing .matHalf .pcard .ctrTag,body.playing .matHalf .pcard .restTag{display:none!important}
/* summoning sickness: opaque grey overlay over the card instead of a text tag */
body.playing .matHalf .pcard .sickTag{display:none}
body.playing .matHalf .pcard:has(.sickTag) .art::after{content:'';position:absolute;inset:0;background:#3a3f4caa;border-radius:8px;z-index:2;pointer-events:none}
.pcard .restTag{position:absolute;top:6px;left:6px;background:var(--rest);color:#fff;font-size:9px;font-weight:700;padding:1px 5px;border-radius:3px;z-index:3;display:none}
.pcard.rested .restTag{display:block}
.pcard .ctrTag{position:absolute;top:6px;right:6px;background:#0b2e1f;color:var(--accent2);font-size:9px;font-weight:700;padding:1px 4px;border-radius:3px;border:1px solid #1c5c40;z-index:3}
/* attached DON shown as DON cards fanned out behind the character */
.pcard .donStack{position:absolute;top:0;left:0;height:100%;width:100%;z-index:0;pointer-events:none}
.pcard .donStack img{position:absolute;height:60%;width:auto;border-radius:4px;box-shadow:0 1px 4px #000b;border:1px solid #7a5f20}
.pcard .art{z-index:1}.pcard .pill,.pcard .foot,.pcard .restTag,.pcard .ctrTag,.pcard .kwbar,.pcard .autoTag,.pcard .sickTag{z-index:3}
.pcard .kwbar{position:absolute;bottom:40px;left:4px;right:4px;display:flex;flex-wrap:wrap;gap:2px;justify-content:center;z-index:3}
.pcard .chip{font-size:8px;background:#1c3a2e;color:#9fe;padding:0 3px;border-radius:3px;border:1px solid #2c5c44}
.pcard .chip.blk{background:#2a1c46;color:#c8b3ff;border-color:#473b6b}
.pcard .chip.rush{background:#3a2a17;color:#ffce8a;border-color:#5e472a}
.pcard .chip.dbl{background:#3a1822;color:#ff9fb0;border-color:#5e2a38}
.pcard .foot{position:absolute;bottom:0;left:0;right:0;background:linear-gradient(0deg,#000d,#000a 60%,transparent);border-radius:0 0 9px 9px;padding:14px 4px 4px;display:flex;flex-direction:column;gap:3px;z-index:3}
.pcard .nm{font-size:9px;font-weight:600;line-height:1.1;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* play mode: drop the name text at the bottom of leader/character cards; the only bubble is the top power pill.
   keep the +DON / ability buttons but with no dark gradient strip when that's all the foot holds. */
body.playing .pcard .foot .nm{display:none}
body.playing .matHalf .pcard .foot{background:none;padding:0 3px 3px}
/* sandbox card controls: two slim glass pills stacked under the art (DON coin pill + power-mod pill)
   instead of the old boxy DON/± strip that overflowed the card edges */
.pcard .steppers{display:flex;flex-direction:column;align-items:center;gap:2px}
.stp{display:inline-flex;align-items:center;gap:2px;background:#0b1322d9;border:1px solid #ffffff24;border-radius:999px;padding:1px 3px;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}
.stp b{font-size:9px;font-weight:800;color:var(--muted);padding:0 2px}
.stp .stpDon{width:13px;height:13px;border-radius:50%;object-fit:cover;border:1px solid #b28e2b;flex:none}
.stp button{width:16px;height:16px;padding:0;min-width:0;border:none;border-radius:50%;background:transparent;color:#c6d2ee;font-size:12px;line-height:1;display:grid;place-items:center}
.stp button:hover{background:#ffffff26;color:#fff;transform:none}
.stp .v{min-width:15px;text-align:center;font-weight:800;font-size:10.5px;font-variant-numeric:tabular-nums}
.stp .v.don{color:var(--gold)}
.stp.mod .v{color:#bfcbe8}
.pcard .rmv{position:absolute;top:-8px;right:-8px;width:20px;height:20px;border-radius:50%;background:#3a1320;border:1px solid var(--red);color:#fff;font-size:12px;line-height:1;cursor:pointer;display:none;z-index:4}
.pcard:hover .rmv{display:block}
.slot.leaderSlot,.pcard.leader .pill{}
.pcard.leader{border-color:#6a5a23}

/* ---------- picker ---------- */
.picker{position:fixed;inset:0;background:#000b;display:flex;align-items:flex-start;justify-content:center;z-index:60;padding-top:8vh}
.picker.hidden{display:none}
.pickBox{background:var(--panel);border:1px solid var(--line);border-radius:12px;width:min(960px,96vw);max-height:88vh;display:flex;flex-direction:column;padding:14px;gap:10px}
.pickHead{display:flex;align-items:center;justify-content:space-between}
.pickHead span{font-weight:700;font-size:15px}
.pickHead button{padding:4px 9px}
#pickSearch{background:var(--field);border:1px solid var(--line);color:var(--txt);border-radius:8px;padding:10px 12px;font-size:15px}
.pickFilters{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.pfColors{display:flex;gap:5px;flex-wrap:wrap}
.pfChip{font-size:11px;padding:3px 9px;border-radius:12px;border:1px solid var(--line);background:var(--field);color:var(--muted);cursor:pointer;opacity:.55}
.pfChip:hover{opacity:.85}
.pfChip.on{opacity:1;color:#fff;font-weight:700;border-color:transparent}
.pfChip.c-red.on{background:#c0392b} .pfChip.c-green.on{background:#1e8449} .pfChip.c-blue.on{background:#2471a3}
.pfChip.c-purple.on{background:#7d3c98} .pfChip.c-black.on{background:#2c3e50} .pfChip.c-yellow.on{background:#b7950b}
.pfSet{background:var(--field);border:1px solid var(--line);color:var(--txt);border-radius:8px;padding:6px 8px;font-size:12px}
/* "Std legal" filter chip: gold when active (hides banned + rotated-out results) */
.pfChip.lgOnly.on{background:color-mix(in srgb,var(--gold) 24%,var(--field));color:var(--gold);border-color:color-mix(in srgb,var(--gold) 55%,var(--line))}
.pfClear{font-size:11px;padding:4px 9px;background:var(--btn-bg);border:1px solid var(--line);color:var(--muted);border-radius:8px;cursor:pointer}
.pickGrid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:8px;max-height:calc((245px * 6) + (8px * 5));overflow-y:auto;overflow-x:hidden;padding:2px}.pcell{height:245px;min-width:0}.pcell .pimg{height:210px}.pcell .pinfo{height:35px}.pcell:hover{border-color:var(--accent);transform:translateY(-2px)}
.pcell .pimg{aspect-ratio:5/7;background:#080b12}
/* Zone viewer (trash / deck / life). .pickGrid's six wide 1fr tracks are sized for the big .pcell picker
   tiles, but this grid is filled with .sCard, whose hard 78px width overrides the grid's justify-self:
   stretch - leaving each small card marooned in ~65px of dead track. Scoped here rather than on .sCard,
   which the deck builder and the ability modals also use. */
#zoneGrid{grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:8px}
#zoneGrid .sCard{width:auto;cursor:default}   /* fill the track; the viewer is read-only */
#zoneGrid .sCard span{font-size:10px}
.pcell .pimg img{width:100%;height:100%;object-fit:cover;display:block}
.pcell .pinfo{padding:3px 4px;font-size:9px;line-height:1.2}
.pcell .pinfo .pid{color:var(--muted)} .pcell .pinfo .ppw{color:var(--gold);font-weight:700}
.pickEmpty{grid-column:1/-1;color:var(--muted);text-align:center;padding:30px;font-style:italic}

/* ---------- analysis ---------- */
.analysis{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:12px;position:sticky;top:70px;max-height:calc(100vh - 86px);overflow:auto}
.analysisHead{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}
.analysis h2{font-size:15px}
.objSel{font-size:10px;color:var(--muted);display:flex;flex-direction:column;gap:2px}
.objSel select{background:var(--field);border:1px solid var(--line);color:var(--txt);border-radius:6px;padding:4px 6px;font-size:12px}
.hint{font-size:11px;color:var(--muted);margin:4px 0 10px}
.svBox{background:#1a1a2e;border:1px solid #2f3a6b;border-radius:9px;padding:8px;margin-bottom:8px}
.svTop{font-size:12px;font-weight:700;color:var(--muted);margin-bottom:6px}
.svCtx{display:flex;flex-wrap:wrap;gap:4px 12px;font-size:10.5px;color:var(--muted);padding:4px 6px;margin-bottom:4px;
  background:#0d1122;border:1px solid #262f57;border-radius:6px}
.svCtx b{color:var(--txt);font-weight:700}
.svCard{background:#10152a;border-radius:7px;padding:7px 8px;margin-top:6px}
.svHead{display:flex;align-items:center;gap:8px;font-size:13px}
.svHead .donUse{margin-left:auto;color:var(--gold);font-weight:700;font-size:11px}
.svBlurb{font-size:11px;color:var(--muted);margin:3px 0 5px}
.svAct{font-size:11px;padding:2px 0}
.svAct.dimSum{color:var(--muted)}
.svSum{font-size:11px;margin-top:4px;font-weight:700}
.svSum.dimSum{font-weight:400;color:var(--muted)}
.svSum .safe{color:var(--accent2)} .svSum .cut{color:var(--noko)}
/* Turn Planner v2 (engine-backed coach) */
.pAnalysing{font-weight:400;color:var(--accent);font-size:10px;animation:pPulse 1s ease-in-out infinite}
@keyframes pPulse{0%,100%{opacity:.45}50%{opacity:1}}
.pMeta{margin-left:auto;color:var(--muted);font-weight:400;font-size:10px}
.svHead .pMeta{margin-left:auto}
.pWhy{color:var(--txt)} .pWhy .pGain{color:var(--accent2);font-weight:700}
.svCard.pBest{border:1px solid color-mix(in srgb,var(--accent2) 45%,var(--line));background:#0e1c17}
.svSum .dimSum{font-weight:400;color:var(--muted)}
.svSum .dimSum .safe{color:var(--accent2);opacity:.85} .svSum .dimSum .cut{color:var(--noko);opacity:.85}
.bestBox{background:#10231c;border:1px solid #1f5c43;border-radius:9px;padding:8px;margin-bottom:8px}
.bestTop{display:flex;align-items:center;gap:8px;font-size:13px;margin-bottom:5px}
.bl{font-size:11px;padding:4px 6px}
.csStep,.expBox{font-size:10.5px;padding:6px 7px;margin-top:6px;line-height:1.35}
.bestSum{font-size:11px;margin-top:5px}
.bestSwap{font-size:11px;margin:3px 0 5px;color:var(--gold);font-style:italic}
.bestSwap.dimSwap{color:var(--muted);font-style:normal}
.playTag{color:var(--accent2);font-style:normal;font-size:9px;border:1px solid var(--accent2);border-radius:3px;padding:0 3px;margin:0 2px}
.bestTop b{color:var(--accent2)} .bestTop span{font-size:10px;color:var(--muted)}
.bestTop .donUse{margin-left:auto;color:var(--gold);font-weight:700}
.bl{display:flex;flex-direction:column;gap:1px;padding:5px 7px;border-radius:6px;background:#0c1a15;margin-top:4px;font-size:12px;border-left:3px solid var(--ko)}
.bl.maybe{border-left-color:var(--maybe)} .bl.saved{border-left-color:var(--noko);opacity:.72}
.bl .ba{font-weight:700} .bl .ba i{color:var(--gold);font-style:normal;font-weight:700;font-size:11px}
.bl .bv{color:var(--muted);font-size:11px}
.bl .bv small{opacity:.7}
.bl .bv em{font-style:normal;color:var(--maybe)} .bl .bv em.safe{color:var(--accent2)} .bl .bv em.cut{color:var(--noko)}
.bestSum{margin-top:7px;font-size:12px;font-weight:700;color:var(--txt)}
.bestSum.dimSum{font-weight:400;color:var(--muted);font-size:11px}
.csStep{margin-top:8px;padding:8px;border-radius:7px;background:#1a1426;border:1px solid #3a2b52;font-size:11.5px;color:var(--muted)}
.csStep b{color:var(--txt)} .csNet{margin-top:4px;font-size:12px} .csNet b{color:var(--accent2)}
.expBox{margin-top:8px;padding:8px;border-radius:7px;background:#10202a;border:1px solid #2f5668;font-size:11.5px;color:#cfe6f0;display:flex;flex-direction:column;gap:3px}
.expBox>b{color:#7fd4ff}
.expBox b{color:#fff}
.expBox .dim,.expBox.dim{color:#8fb3c4;font-size:10.5px;font-weight:400;line-height:1.3}
.trigNote{margin-top:6px;font-size:11px;color:#ffce8a;background:#2e2410;border:1px solid #5e472a;border-radius:6px;padding:5px 8px}
.lethal{margin-top:6px;font-weight:800;color:#fff;background:#7a1d2b;border:1px solid var(--red);border-radius:6px;padding:5px 8px;text-align:center;letter-spacing:1px}
.lethal small{font-weight:600;letter-spacing:0;opacity:.85}
.lethalMaybe{background:#6b4a16;border-color:#caa14a;letter-spacing:0;font-weight:700;text-align:left;line-height:1.35}
.notLethal{margin-top:6px;font-weight:600;color:#cfe3c8;background:#1f2e1d;border:1px solid #3f6336;border-radius:6px;padding:5px 8px;line-height:1.35;font-size:11.5px}
.bestEmpty{padding:8px 0;font-style:italic;color:var(--muted);font-size:12px}
.atkBlock{margin-bottom:12px;background:#161d30;border:1px solid var(--line);border-radius:9px;padding:9px}
.atkHead{display:flex;align-items:center;gap:8px;margin-bottom:6px}
.atkHead .anm{font-weight:700;font-size:13px}
.atkHead .apw{margin-left:auto;font-weight:800;color:var(--gold)}
.src{font-size:10px;color:var(--muted)}
.tgt{display:grid;grid-template-columns:1fr auto;gap:4px 8px;padding:5px 7px;border-radius:6px;font-size:12px;margin-top:4px;background:#0f1626}
.tgt .tn{display:flex;flex-direction:column} .tgt .tn small{color:var(--muted);font-size:10px}
.tgt .verdict{text-align:right;font-weight:700;font-size:11px;align-self:center}
.tgt.ko{border-left:3px solid var(--ko)} .tgt.ko .verdict{color:var(--ko)}
.tgt.maybe{border-left:3px solid var(--maybe)} .tgt.maybe .verdict{color:var(--maybe)}
.tgt.noko{border-left:3px solid var(--noko);opacity:.82} .tgt.noko .verdict{color:var(--noko)}
.empty{color:var(--muted);font-style:italic;font-size:12px;padding:10px 0}
.warn{background:#3a2a17;border:1px solid #5e472a;color:#ffce8a;font-size:10px;line-height:1.35;padding:3px 7px;border-radius:5px;margin-bottom:4px}
.warn.warnInfo{background:#152a3a;border-color:#2c4a63;color:#9cc6ff}
.warn.warnInfo b{color:#cfe2ff}

/* ---------- modal / pool ---------- */
.modal{position:fixed;inset:0;background:#000a;display:flex;align-items:center;justify-content:center;z-index:60}
.modal.hidden{display:none}
.modalBox{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:20px;width:min(820px,92vw);max-height:90vh;overflow:auto}
.deckCols{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:12px 0}
.deckCols label{display:flex;flex-direction:column;font-size:12px;color:var(--muted);gap:5px}
.deckCols textarea{background:var(--field);border:1px solid var(--line);color:var(--txt);border-radius:8px;padding:9px;font-family:ui-monospace,Consolas,monospace;font-size:12px;resize:vertical}
.modalBtns{display:flex;gap:10px}
/* Choose one: stacked full-width option buttons (each a bullet from the card) */
.chooseOpts{display:flex;flex-direction:column;gap:8px;margin-top:6px}
.chooseOpt{text-align:left;padding:10px 12px;line-height:1.35;white-space:normal;height:auto}
.netPickBtns.col{flex-direction:column;align-items:stretch}
.netPickBtns.col button{text-align:left;white-space:normal;height:auto;line-height:1.35;padding:9px 11px}
.deckMsg{margin-top:10px;font-size:12px;color:var(--accent2)}
.dwarn{color:#ffce8a;font-size:11px;margin:2px 0 6px 0;background:#2e2410;border:1px solid #5e472a;border-radius:5px;padding:4px 7px}
.deckProfile{font-size:11px;color:var(--muted);display:flex;gap:10px;flex-wrap:wrap;margin-left:8px}
.deckProfile b{color:var(--txt)}
/* ---------- play mode ---------- */
.gamebar{background:linear-gradient(180deg,#16213a,#101726);border:1px solid var(--line);border-radius:10px;padding:8px 12px;margin-top:10px;display:flex;flex-direction:column;gap:5px}
.gamebar.hidden{display:none}
.gstart{display:flex;align-items:center;gap:8px;font-size:13px}
.gstart .gnote{color:var(--muted);font-size:11px;margin-left:6px}
.grow{display:flex;align-items:center;gap:12px;flex-wrap:wrap;font-size:12px}
.gturn{font-weight:800;padding:3px 10px;border-radius:6px}
.gturn.me{background:#16314e;color:#9cc6ff} .gturn.opp{background:#3a1f2c;color:#ffb0bd}
.gdon{color:var(--gold);font-weight:700} .gstat{color:var(--muted)}
.gsel{color:var(--accent2);font-weight:700} .gover{color:#fff;background:#7a1d2b;padding:2px 8px;border-radius:5px;font-weight:800}
.grow .gnote{color:var(--maybe);font-weight:700}
.gbtns{margin-left:auto;display:flex;gap:6px;width:100%;justify-content:flex-end;margin-top:4px}
.gbtns button{padding:5px 10px;font-size:12px}
.slot.dim{opacity:.35;cursor:default;border-style:solid}
.pcard.canatk{box-shadow:0 0 0 2px var(--accent2)} .pcard.canatk .art{cursor:pointer}
.pcard.selected{box-shadow:0 0 0 3px var(--gold)}
.pcard.targetable{box-shadow:0 0 0 2px var(--red);cursor:crosshair} .pcard.targetable .art{cursor:crosshair}
/* ability target-picking (K.O./bounce/buff/grantKw/rest-as-cost/etc): click the REAL board card, own or
   opponent's, instead of only the popup's mini card-grid - orange so it reads distinctly from the
   red attack-targetable ring and the gold "currently selected attacker" ring. */
.pcard.abilTargetable{box-shadow:0 0 0 2px var(--orange,#ff9a2e);cursor:pointer} .pcard.abilTargetable .art{cursor:pointer}
/* play mode: body.playmode .matHalf .pcard (leader-colour glow, above) sets its OWN box-shadow at higher
   specificity (3 classes vs these rules' 2), which was silently swinging every interactive-state ring
   above invisible during play - you could select an attacker, target one to attack, or pick an ability
   target, and nothing on the board ever showed it. Re-declare each state at matching specificity here,
   LAYERING the state ring in FRONT of the leader glow (comma-separated, first shadow paints on top)
   instead of replacing it, so both are visible at once. */
body.playmode .matHalf .pcard.canatk{box-shadow:0 0 0 2px var(--accent2), 0 0 14px -4px color-mix(in srgb,var(--leader-c1) 60%,transparent)}
body.playmode .matHalf .pcard.selected{box-shadow:0 0 0 3px var(--gold), 0 0 14px -4px color-mix(in srgb,var(--leader-c1) 60%,transparent)}
body.playmode .matHalf .pcard.targetable{box-shadow:0 0 0 2px var(--red), 0 0 14px -4px color-mix(in srgb,var(--leader-c1) 60%,transparent)}
body.playmode .matHalf .pcard.abilTargetable{box-shadow:0 0 0 2px var(--orange,#ff9a2e), 0 0 14px -4px color-mix(in srgb,var(--leader-c1) 60%,transparent)}
.pcard.abilTargetable.abilPicked{box-shadow:0 0 0 3px var(--orange,#ff9a2e),0 0 14px 2px #ff9a2e88}
.atkBtn{padding:1px 6px;font-size:10px;background:#2a3450;border:1px solid var(--line);color:var(--gold);border-radius:4px}
.sickTag{position:absolute;top:6px;left:6px;background:#3a2b52;color:#c8b3ff;font-size:9px;font-weight:700;padding:1px 5px;border-radius:3px;z-index:3}
.autoTag{position:absolute;top:20px;left:50%;transform:translateX(-50%);background:#1a3a2c;color:var(--accent2);font-size:9px;font-weight:700;padding:1px 5px;border-radius:3px;z-index:3;border:1px solid #2c5c44;white-space:nowrap}

.handRow{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-bottom:3px}
.hlbl{font-size:11px;color:var(--muted);min-width:120px;font-weight:700}
.hcard{width:84px;border:1px solid var(--line);border-radius:7px;overflow:hidden;background:#0c1018;opacity:.6;transition:transform .12s,box-shadow .12s}
.hcard.play{opacity:1;cursor:pointer;border-color:var(--accent2)} .hcard.play:hover{transform:translateY(-6px) scale(1.04);box-shadow:0 6px 14px #000c;z-index:2}
.hcard.ev{border-style:dashed}
.himg{aspect-ratio:5/7;background:#080b12} .himg img{width:100%;height:100%;object-fit:cover}
.hinfo{display:flex;align-items:center;gap:3px;padding:2px 3px;font-size:8.5px}
.hcost{background:var(--gold);color:#000;font-weight:800;border-radius:3px;padding:0 3px} .hpw{color:var(--muted)} .hctr{color:var(--accent2)}
.hcost.disc{background:var(--accent2);color:#000;box-shadow:0 0 4px var(--accent2)}
.ohand{font-size:11px;color:var(--muted);padding:4px 0} .hempty{font-size:11px;color:var(--muted);font-style:italic}
/* calculator hand builder */
.handRow{display:flex;align-items:flex-end;gap:6px;margin-top:6px;flex-wrap:wrap}
.hbLabel{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;align-self:center;margin-right:2px}
.hbCard{position:relative;width:64px;border:1px solid var(--line);border-radius:5px;overflow:hidden;background:#0b0f17}
.hbCard.playable{border-color:var(--accent2)}
.hbImg{aspect-ratio:5/7;background:#080b12} .hbImg img{width:100%;height:100%;object-fit:cover}
.hbInfo{display:flex;align-items:center;gap:2px;padding:2px;font-size:8px;flex-wrap:wrap}
.hbCost{background:var(--gold);color:#000;font-weight:800;border-radius:3px;padding:0 3px} .hbPw{color:var(--muted)}
.hbRush{color:#000;background:var(--accent2);border-radius:3px;padding:0 3px;font-weight:700} .hbSick{color:var(--muted)} .hbType{color:var(--muted)}
.hbEv{color:#000;background:var(--gold);border-radius:3px;padding:0 3px;font-weight:700}
.hbDon{display:flex;align-items:center;justify-content:center;gap:3px;font-size:8px;padding:1px 0;background:#0e1320}
.hbDon button{width:14px;height:14px;line-height:1;padding:0}
.hbX{position:absolute;top:1px;right:1px;z-index:2;width:14px;height:14px;line-height:1;padding:0;background:#000a;border:none;color:#fff;border-radius:3px;cursor:pointer}
.hbAdd{align-self:center;font-size:11px;padding:4px 8px;border:1px dashed var(--line);background:transparent;color:var(--muted);border-radius:5px;cursor:pointer}

#resolveBox,#trigBox{width:min(520px,92vw)}
.trigText{background:#10231c;border:1px solid #1f5c43;border-radius:7px;padding:9px;font-size:12.5px;color:var(--txt);margin:10px 0;line-height:1.4}
.resRow{display:flex;align-items:center;gap:8px;font-size:14px;margin:10px 0;flex-wrap:wrap}
.resRow .rp{font-weight:800;color:var(--gold)} .resRow .rp.def{color:#ff9fb0} .resRow .varr{color:var(--muted)}
.resOut{padding:7px 10px;border-radius:7px;font-weight:700;text-align:center;margin-bottom:10px}
.resOut.rhit{background:#10231c;border:1px solid #1f5c43;color:var(--accent2)}
.resOut.rmiss{background:#2a1620;border:1px solid #5a2b3a;color:var(--red)}
.resSec{margin:8px 0} .rlabel{font-size:11px;color:var(--muted);margin-bottom:4px}
.ropts{display:flex;gap:5px;flex-wrap:wrap}
.ropts button{padding:5px 9px;font-size:12px;background:var(--btn-bg);border:1px solid var(--line);color:var(--txt)}
.ropts button.on{background:#2a3450;border-color:var(--accent2);color:var(--accent2)}
.rnone{font-size:11px;color:var(--muted);font-style:italic}
.abilBtn{padding:1px 6px;font-size:10px;background:#3a2a50;border:1px solid #7a5fae;color:#d6bdff;border-radius:4px;cursor:pointer}
.pcard.hasabil{box-shadow:0 0 0 1px #7a5fae66}
.abilText{font-size:11.5px;color:#cfe6f0;background:#10202a;border:1px solid #2f5668;border-radius:6px;padding:7px 9px;margin:6px 0;line-height:1.4}
.atag{font-size:11px;color:#7fd4ff;font-weight:600}
.searchGrid{display:flex;gap:7px;flex-wrap:wrap;margin:6px 0;max-height:320px;overflow-y:auto}
.sCard{width:78px;border:1px solid var(--line);border-radius:6px;padding:3px;text-align:center;cursor:pointer;background:var(--field)}
.sCard img{width:100%;border-radius:4px;display:block}
.sCard span{font-size:9px;color:var(--muted)}
.sCard.on{border-color:var(--accent2);box-shadow:0 0 0 2px var(--accent2)}
.sCard.dis{opacity:.4;cursor:not-allowed}

.poolBar{position:fixed;bottom:0;left:0;right:0;background:#0a0f1cee;border-top:1px solid var(--line);display:flex;gap:6px;padding:6px 10px;overflow-x:auto;z-index:35;backdrop-filter:blur(4px)}
.poolBar:empty{display:none}
/* hand is now in-flow inside the board; no fixed-bottom padding needed */
/* play-mode corner/life zones - compact card-sized piles */
body.playing .pile{min-width:0;font-size:8px}
/* hover magnified card preview */
.cardPreview{position:fixed;z-index:80;pointer-events:none;width:340px;max-width:30vw;filter:drop-shadow(0 10px 30px #000e)}
.cardPreview.hidden{display:none}
.cardPreview img{width:100%;border-radius:12px;display:block}
.poolChip{display:flex;align-items:center;gap:6px;background:#0e1626;border:1px solid var(--line);border-radius:6px;padding:3px 7px;font-size:11px;white-space:nowrap;cursor:pointer;flex-shrink:0}
.poolChip:hover{border-color:var(--accent)}
.poolChip .ps{color:var(--gold);font-weight:700} .poolChip .side{font-size:9px;color:var(--muted)}

/* ---- online lobby + banner (client-net.js) ---- */
.netLobby{position:fixed;inset:0;background:#000a;display:none;align-items:center;justify-content:center;z-index:200}
.netLobby.show{display:flex}
.netBox{background:#0e1626;border:1px solid var(--line);border-radius:12px;padding:22px 26px;width:440px;max-width:94vw;box-shadow:0 10px 40px #000a}
.netBox--deck{width:560px}
.netHead{display:flex;align-items:center;justify-content:space-between;font-size:17px;font-weight:700;margin-bottom:10px}
.netHint{font-size:12.5px;color:var(--muted);margin:0 0 14px;line-height:1.5}
.netHint code{background:var(--field);border:1px solid var(--line);border-radius:4px;padding:1px 5px;color:var(--accent2)}
.netRow{display:flex;gap:10px;margin:10px 0}
.netRow--end{justify-content:flex-end}
.netRow>input{flex:1;text-transform:uppercase;letter-spacing:3px;text-align:center;font-size:20px;font-weight:800;background:var(--field);border:1px solid var(--line);color:var(--txt);border-radius:6px;padding:10px}
.netRow button{white-space:nowrap}
.netRow>button.primary{padding:10px 22px;font-size:14px}
.netLabel{display:flex;flex-direction:column;gap:8px;font-size:12px;color:var(--muted)}
.netLabel .libBar{display:flex;gap:6px;margin:2px 0}
.netLabel .libSel{flex:1;font-size:13px;padding:7px 8px;background:var(--field);border:1px solid var(--line);color:var(--txt);border-radius:6px}
.netLabel textarea{width:100%;background:var(--field);border:1px solid var(--line);color:var(--txt);border-radius:6px;padding:9px 11px;font-size:13px;resize:vertical;font-family:inherit}
.netMsg{font-size:12.5px;color:var(--accent2);margin-top:12px;min-height:18px;line-height:1.5}
.netBanner{position:fixed;top:60px;left:50%;transform:translateX(-50%) translateY(-20px);background:#16213a;border:1px solid var(--accent2);color:var(--txt);padding:10px 18px;border-radius:8px;z-index:210;opacity:0;transition:.3s;font-size:13px;pointer-events:none}
.netBanner.show{opacity:1;transform:translateX(-50%) translateY(0)}

/* ---- play landing (pre-game /play): no sandbox to fall back into ---- */

/* ---- online: gate the non-acting player ---- */
body.online #gamebar { display:none!important; }   /* the merged chat+log panel in the left column already shows the log; status is in the header/board */

/* ---- online landing (pre-match, reached via /online): clean screen, never the sandbox board ---- */
/* the online landing/lobby covers the page: hide the board behind it. Header controls are already
   governed by the navigation-model block near the top of this file. */
body.onlineland .table,
body.onlineland .analysis,
body.onlineland #handCol { display:none; }
.netLanding{display:none;position:fixed;top:56px;left:0;right:0;bottom:0;align-items:center;justify-content:center;padding:24px;z-index:60}
.netLanding.show{display:flex}
.netLandBox{background:#0e1626;border:1px solid var(--line);border-radius:14px;padding:32px 36px;max-width:440px;text-align:center;box-shadow:0 10px 40px #0006}
.netLandTitle{font-size:22px;font-weight:800;letter-spacing:.3px;margin-bottom:10px}
.netLandHint{font-size:13px;color:var(--muted);line-height:1.6;margin:0 0 22px}
.netLandBtns{display:flex;gap:12px;justify-content:center;align-items:center}
.netLandHome{text-decoration:none;display:inline-flex;align-items:center}
/* on the opponent's turn: dim slightly + block ACTION buttons, but keep cards hoverable (card preview) */
body.nettheirturn #gamePrompt,
body.nettheirturn #gameTopBtns,
body.nettheirturn .table .atkBtn,
body.nettheirturn .table .abilBtn,
body.nettheirturn .handSide .hcard.play { pointer-events:none; }
body.nettheirturn .table,
body.nettheirturn #handCol { opacity:.78; filter:saturate(.85); }
.netWaiting{position:fixed;bottom:18px;left:50%;transform:translateX(-50%);background:#16213a;border:1px solid var(--accent2);color:var(--accent2);padding:8px 18px;border-radius:20px;font-size:13px;font-weight:600;z-index:150;box-shadow:0 4px 16px #0008;display:none}

/* ---- online pending-choice picker (targeted ability effects) ---- */
/* PC online play: dock every resolve/target picker to the RIGHT of the board (transparent backdrop,
   pointer-events pass through except the box itself) so the board + red targeting arrow stay visible
   during attacks - matches hot-seat's #resolveModal treatment exactly. Mobile reverts to a centered
   overlay (see the max-width:840px override below) since there's no board real estate to spare there. */
.netPick{position:fixed;inset:0;background:transparent;pointer-events:none;display:flex;align-items:center;justify-content:flex-end;padding:56px 112px 10px;z-index:220}
.netPick .netPickBox{pointer-events:auto}
.netPick.pickNudge .netPickBox{animation:pickNudge .4s ease}
@keyframes pickNudge{0%,100%{transform:scale(1)}25%{transform:scale(1.04)}50%{transform:scale(0.98)}75%{transform:scale(1.02)}}
/* capped the SAME way hot-seat's #abilModal/#trigModal are (see that rule's comment) - was previously
   max-width:90vw with no smaller cap, so on a wide desktop viewport a long ability body/wide card grid
   could genuinely cover most of the screen. Content-sized up to the cap, not a fixed width. */
/* same width as the chat/hand column (--hand-col-w, clamp(240px,26vw,520px)) at 100% - the user's own
   reference point for "the size it should be", scaling identically with viewport instead of an
   independently-chosen cap */
.netPickBox{background:#0e1626;border:1px solid var(--accent2);border-radius:12px;padding:18px 20px;width:var(--hand-col-w);max-width:92vw;box-shadow:0 10px 40px #000c}
.netPickHead{font-size:15px;font-weight:700;margin-bottom:12px;color:var(--accent2)}
.netPickGrid{display:flex;flex-wrap:wrap;gap:8px;max-width:100%;justify-content:center}
.netPickCard{width:84px;cursor:pointer;border:2px solid transparent;border-radius:6px;overflow:hidden;line-height:0;background:var(--field)}
.netPickCard img{width:100%;display:block}
.netPickCard span{display:block;padding:24px 4px;color:var(--muted);font-size:11px;text-align:center}
.netPickCard:hover{border-color:var(--accent)}
.netPickCard.sel{border-color:var(--gold);box-shadow:0 0 10px #ffd24a88}
.netPickNone{color:var(--muted);font-style:italic;padding:20px}
.netPickBtns{display:flex;gap:8px;justify-content:flex-end;margin-top:14px}

/* mobile: bottom sheet instead of right-docked (no board real estate to spare beside it) - mirrors
   #resolveModal's identical mobile treatment, board still stays visible above the sheet */
@media (max-width:840px){
  .netPick{align-items:flex-end;justify-content:center;padding:0}
  .netPickBox{width:100vw;max-width:none;max-height:62vh;border-radius:16px 16px 0 0;
    border-left:none;border-right:none;border-bottom:none}
}

/* ---- online: opponent hand shown as face-down card backs in the TOP panel of the left hand column.
   Its sizing/layout comes from the shared `body.playing .handSide` rules - do NOT re-position it here
   (an #id selector would outrank them and float the hand back over the board). ---- */
body.playing #handTop .hcard{border:none}
.netPickCard.dis{opacity:.32;pointer-events:none;filter:grayscale(.6)}
.netPickCard{position:relative}
.netPickBadge{position:absolute;bottom:2px;left:2px;right:2px;background:#000c;color:var(--gold);font-size:10px;font-weight:800;text-align:center;border-radius:3px;padding:1px}
.netPickBadge.ev{color:var(--accent2)}
.donStack img.donRestedTok{transform:rotate(90deg)}   /* rested (given) DON shown rotated */

/* counter step: gold accent border, distinguishing it from other mid-battle pickers (position itself is
   now handled by the base .netPick rule above - right-docked on PC for every pending kind uniformly) */
.netPick.counterStep .netPickBox{background:#0e1626f2;border-color:var(--gold)}
.netPick.midBattle .netPickBox{background:#0e1626f2}

/* opponent hidden-zone counts (hand / deck / life / DON) - persistent badge, top-centre */
.netOppInfo{position:fixed;top:60px;left:50%;transform:translateX(-50%);background:#0e1626d8;border:1px solid var(--line);border-radius:0 0 10px 10px;padding:4px 16px;font-size:13px;font-weight:600;color:var(--txt);z-index:120;pointer-events:none;box-shadow:0 2px 10px #0006}

/* ============================================================
   Modern header + controls (matches the home hub aesthetic).
   Added 2026-07-06. Restyles page/section headers, buttons,
   selects, and the small "option" sub-labels to one consistent
   modern look. Visual only - no box-size changes that reflow.
   ============================================================ */

/* --- top header bar (board app) --- */
header{padding:11px 20px;background:linear-gradient(180deg,var(--head-a),var(--head-b));box-shadow:0 8px 22px -14px #000}
header h1{font-size:16px;letter-spacing:.3px;display:flex;align-items:center;gap:9px}   /* match the builder title (.pickHead span) so headers read the same size on every page */
/* per-mode stroked line icon in the page title (hub icon language) */
header h1>span{display:inline-flex;align-items:center;gap:8px}
header h1 svg.ic{width:18px;height:18px;fill:none;stroke:var(--gold);stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:none;filter:drop-shadow(0 2px 5px #0006)}
/* modal / builder title icons (Deck Builder etc) */
.pickHead svg.ic{width:18px;height:18px;fill:none;stroke:var(--gold);stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;vertical-align:-3.5px;margin-right:7px}
header h1 .sub{font-size:11px;color:var(--accent2);font-weight:700;background:color-mix(in srgb,var(--accent2) 15%,var(--panel2));border:1px solid color-mix(in srgb,var(--accent2) 35%,var(--line));padding:2px 9px;border-radius:999px;margin-left:2px;letter-spacing:.4px}

/* --- buttons: hub-style pills --- */
button{border-radius:9px;padding:8px 13px;transition:transform .15s ease, filter .15s ease, border-color .15s ease, background .15s ease}
button:hover{transform:translateY(-1px)}
button:focus-visible{outline:2px solid color-mix(in srgb,var(--accent) 70%,transparent);outline-offset:2px}
button.ghost{background:var(--btn-bg);border:1px solid var(--line);color:var(--txt)}
button.ghost:hover{border-color:var(--accent)}

/* --- light/dark theme toggle (shared with the hub) --- */
.themeToggle{display:inline-flex;align-items:center;gap:7px;cursor:pointer;padding:8px 11px;border-radius:999px;
  border:1px solid var(--line);background:var(--btn-bg);color:var(--txt);font-weight:700;font-size:13px}
.themeToggle:hover{transform:translateY(-1px);border-color:var(--accent)}
.themeToggle svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.themeToggle .tt-moon,.themeToggle .tt-sun{align-items:center}
.themeToggle .tt-sun{display:none}
.themeToggle .tt-moon{display:inline-flex}
:root[data-theme="light"] .themeToggle .tt-sun{display:inline-flex}
:root[data-theme="light"] .themeToggle .tt-moon{display:none}

/* --- unified header/builder/nav buttons: line-icon + label, clear hierarchy --- */
header .toolbar button, header .toolbar .homeLink, .bldTop button, .bldTop .bldHome{display:inline-flex;align-items:center;gap:7px}
.toolbar button svg, .homeLink svg, .bldTop button svg, .bldHome svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;opacity:.85;flex:none}
button.make{background:var(--gold);color:#2b2408;font-weight:800;border:1px solid transparent}
button.make svg{opacity:1}
button.make:hover{filter:brightness(1.05)}

/* --- selects: one unified modern control with a custom caret --- */
select,.objSel select,.libSel{
  appearance:none;-webkit-appearance:none;-moz-appearance:none;
  background-color:var(--field);
  background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%238a97b5' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 9px center;
  border:1px solid var(--line);color:var(--txt);border-radius:8px;
  padding:7px 28px 7px 11px;font-size:12.5px;cursor:pointer;transition:border-color .15s ease;
}
select:hover,.libSel:hover{border-color:color-mix(in srgb,var(--accent) 45%,var(--line))}
select:focus-visible,.libSel:focus-visible,input:focus-visible,textarea:focus-visible{outline:none;border-color:var(--accent)}

/* --- small "option" sub-labels (Best line / Opp counters / Opp known counters ...) --- */
.objSel,.ctrl{font-size:9.5px;letter-spacing:.7px;text-transform:uppercase;color:var(--muted);font-weight:700}
.objSel select,.ctrl input{text-transform:none;letter-spacing:normal;font-weight:400}
.chand .chRow label{text-transform:none;letter-spacing:normal;font-weight:400}

/* --- section headers (Attack Analysis, modal + builder titles) --- */
.analysis h2,.analysisHead h2{font-size:13px;text-transform:uppercase;letter-spacing:.7px;color:var(--txt);font-weight:800}
.modalBox h2{font-size:17px;letter-spacing:.3px;font-weight:800}
.pickHead span{font-size:16px;letter-spacing:.3px}
.netHead{font-size:16px;letter-spacing:.3px}

/* --- builder deck-count pill --- */
.bldCount{background:color-mix(in srgb,var(--gold) 16%,var(--panel2));border:1px solid color-mix(in srgb,var(--gold) 40%,var(--line));border-radius:999px;padding:2px 11px;font-size:13px;margin-left:8px}
.bldCount.ok{color:var(--ko);background:color-mix(in srgb,var(--ko) 16%,var(--panel2));border-color:color-mix(in srgb,var(--ko) 40%,var(--line))}

/* ============================================================
   Deck stats rail (composition / cost curve / curve-out prob).
   Third column in the builder. Added 2026-07-06.
   ============================================================ */
.bldBody{grid-template-columns:300px minmax(320px,1fr) 350px}
@media(max-width:1080px){ .bldBody{grid-template-columns:280px 1fr} .bldStats{display:none} }

.bldStats{background:linear-gradient(160deg,var(--panel),var(--panel2));border:1px solid var(--line);border-radius:10px;padding:14px;overflow:auto;display:flex;flex-direction:column;gap:6px}
.dcHead{display:flex;align-items:baseline;justify-content:space-between;gap:8px;font-size:15px;font-weight:800;letter-spacing:.3px;margin-bottom:6px}
.dcTotal{font-size:13px;font-weight:800;color:var(--muted)}
.dcTotal.ok{color:var(--ko)}
.dcGrid{display:flex;flex-direction:column;gap:1px;margin-bottom:4px}
.dcRow{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:3px 0;font-size:12.5px}
.dcRow .dcL{color:var(--muted)}
.dcRow .dcV{color:var(--txt);font-weight:800;font-variant-numeric:tabular-nums}
.dcDiv{height:1px;background:var(--line);margin:5px 0}

.dcSecHead{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:11px;text-transform:uppercase;letter-spacing:.7px;color:var(--txt);font-weight:800;margin:12px 0 8px}

/* cost curve: vertical bars, count on the bar, cost label under the baseline */
.ccChart{display:grid;grid-template-columns:repeat(11,1fr);gap:3px;align-items:end}
.ccCol{display:flex;flex-direction:column;align-items:center;gap:4px;min-width:0}
.ccTrack{width:100%;height:96px;display:flex;align-items:flex-end;justify-content:center}
.ccBar{width:100%;min-height:2px;background:linear-gradient(180deg, color-mix(in srgb,var(--accent) 82%,#fff 6%), var(--accent));border-radius:4px 4px 0 0;position:relative;display:flex;justify-content:center}
.ccBar b{position:absolute;top:-15px;font-size:11px;font-weight:800;color:var(--txt)}
.ccX{font-size:10px;color:var(--muted);font-weight:600}

/* curve-out probability pills */
.coToggle{display:inline-flex;gap:0;border:1px solid var(--line);border-radius:7px;overflow:hidden}
.coToggle button{padding:3px 9px;font-size:11px;font-weight:700;background:var(--field);color:var(--muted);border:none;border-radius:0}
.coToggle button.on{background:var(--accent);color:#04121f}
.coToggle button:hover{transform:none}
.coPills{display:grid;grid-template-columns:1fr 1fr;gap:7px}
.coPill{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:7px 11px;border-radius:8px;border:1px solid;font-size:12px}
.coPill .coT{font-weight:700;opacity:.92}
.coPill .coP{font-weight:800;font-variant-numeric:tabular-nums}
.coPill.good{background:color-mix(in srgb,var(--ko) 16%,var(--panel2));border-color:color-mix(in srgb,var(--ko) 45%,var(--line));color:var(--ko)}
.coPill.ok{background:color-mix(in srgb,var(--gold) 15%,var(--panel2));border-color:color-mix(in srgb,var(--gold) 45%,var(--line));color:var(--gold)}
.coPill.low{background:color-mix(in srgb,var(--red) 14%,var(--panel2));border-color:color-mix(in srgb,var(--red) 42%,var(--line));color:color-mix(in srgb,var(--red) 88%,#fff)}

/* ---------- collapsible analysis/log panel (all play modes) ---------- */
/* slim arrow tab fixed at the panel's left edge; clicking docks the whole analysis+log column away
   to the right and gives the board the full width. State remembered per device (mobile: see below). */
/* drawer-handle circle centred on the panel's left edge (z below modals (60): battle panels cover it) */
.anlToggle{position:fixed;top:50%;margin-top:-13px;right:calc(368px - 13px);z-index:58;width:26px;height:26px;
  border-radius:50%;background:var(--panel);border:1px solid var(--line);
  color:var(--muted);font-size:15px;font-weight:700;display:grid;place-items:center;cursor:pointer;padding:0;
  box-shadow:0 2px 10px #0009;transition:right .18s ease,color .15s ease,border-color .15s ease}
.anlToggle::before{content:'›';margin-top:-2px}
.anlToggle:hover{color:var(--gold);border-color:color-mix(in srgb,var(--gold) 60%,var(--line));transform:none}
body.playing .anlToggle{right:calc(386px - 13px)}
body.anlMin .anlToggle{right:6px!important}
body.anlMin .anlToggle::before{content:'‹'}
body:has(#resolveModal:not(.hidden)) .anlToggle{display:none}   /* never sit over the battle panel */
body:has(#abilModal.docked:not(.hidden)) .anlToggle{display:none}
body:has(#trigModal.docked:not(.hidden)) .anlToggle{display:none}
body:has(#netPick) .anlToggle{display:none}   /* online resolve/target picker sits right where this handle would */
body.onlineland .anlToggle{display:none}   /* online landing/lobby: no board, no analysis panel - no handle */
body.anlMin aside.analysis{display:none!important}
body.anlMin main{grid-template-columns:1fr}
body.playing.anlMin main{grid-template-columns:auto 1fr}

/* "half a monitor" width (a browser window/split-screen narrower than a full desktop but well above the
   840px phone breakpoint, e.g. ~900-1300px): squeezing the analysis panel's fixed 360px column into the
   grid alongside the board/hands at THIS width was overlapping badly, because the board-fit math assumes
   either the full 360px sidebar (wide) or no sidebar at all (mobile's fixed-position drawer) - nothing in
   between. Reuse the SAME "analysis becomes a drawer over the board" treatment mobile already gets
   (below), but gated on a much wider breakpoint - this only changes the analysis panel's own layout, NOT
   the hand-strip/board mobile rules further down (those stay at the narrower 840px breakpoint; a
   half-monitor window still gets the normal desktop hand-fan layout, just with the analysis panel docked
   as an overlay instead of squeezing the grid). Only applies while the panel is actually open (not
   body.anlMin) - a minimised panel already collapses main to 1fr with no drawer needed. */
@media (max-width:1300px){
  body:not(.anlMin) .analysis{position:fixed;right:0;top:70px;bottom:0;width:min(88vw,360px);z-index:66;
    max-height:none;border-radius:12px 0 0 12px;box-shadow:-10px 0 34px #000b}
  body:not(.anlMin) main{grid-template-columns:1fr}
  body.playing:not(.anlMin) main{grid-template-columns:auto 1fr}
  body:not(.anlMin) .anlToggle{right:calc(min(88vw,360px) - 13px);z-index:67}
}

/* ---------- mobile first pass ---------- */
@media (max-width:840px){
  /* the desktop board offsets/scale are tuned for a wide monitor - neutralise them on phones */
  :root{--board-x:0px;--board-y:0px;--board-scale:1}
  body.playing .table{transform:none}
  /* cards: art + a small power pill only (big chrome hidden) */
  .pcard .pill, body.playing .matHalf .pcard .pill{font-size:10px;padding:1px 6px;top:-8px}
  .pcard .pill small, body.playing .matHalf .pcard .pill small{display:none}
  .pcard .autoTag, .pcard .ctrTag, .pcard .kwbar, .pcard .nm{display:none!important}
  .pcard .foot{min-height:0}
  .pcard,.slot{width:clamp(54px,10.5vw,84px);height:calc(clamp(54px,10.5vw,84px)*1.4)}
  body{--cardW:clamp(46px,6.6vh,70px)}
  /* the board keeps full width; if it is still wider than the screen it scrolls sideways */
  .table{overflow-x:auto;max-width:100vw}
  main{grid-template-columns:1fr}
  /* the half-monitor block above (max-width:1300px) sets body.playing:not(.anlMin) main to auto 1fr so
     a DESKTOP window keeps its side hand column while the analysis drawer overlays - but that selector
     out-specifies this block's plain single-column rules, so on an actual PHONE opening the Turn
     Planner re-gridded main into two columns and crushed the board to a sliver behind the drawer
     (reported live: "the turn planner is supposed to overlay, not move the game"). Phones are single
     column in BOTH drawer states - re-assert at matching specificity (this block is later in the file). */
  body.playing:not(.anlMin) main{grid-template-columns:1fr}
  /* PLAY MODE on a phone: single column - board on top (full width), hands docked BOTTOM as
     horizontal scrolling strips (opp card backs small, your hand bigger), analysis stays a drawer */
  /* compact header: the desktop toolbar wraps to ~180px tall on a phone and starves the board */
  header{padding:5px 8px;gap:8px;flex-wrap:nowrap}
  header h1, header h1{font-size:13px;letter-spacing:0}
  header h1 .sub{display:none}
  .toolbar{gap:5px;flex-wrap:nowrap;min-width:0}
  .toolbar button, .toolbar .homeLink{padding:5px 9px;font-size:11px}
  .toolbar .ctrl{font-size:10px}
  /* icon-only buttons on narrow screens: labels were the reason the toolbar wrapped onto a 2nd row -
     dropping the TEXT (icon + aria-label/title still carry the meaning) keeps the whole header to one
     row. The label is a bare text node after the <svg>, not its own element, so font-size:0 on the button
     hides it - the icon's own size is re-asserted explicitly since it would otherwise inherit the 0 too.
     SCOPED to :has(svg) specifically (not a bare :not([data-gact=...]) exclusion list) - the mulligan/
     keep/first-or-second/"New Game" buttons rendered inside #gamePrompt/#gameTopBtns have NO icon at all,
     so an exclusion-list version of this rule (only excluding "End Turn" by name) silently hid them
     completely with nothing left to show (a blank, unusable pill) the first time this shipped. Any
     current or future icon-less game-flow button is automatically safe under this :has() version instead
     of needing to be added to a growing exclusion list by name. */
  header .toolbar button:has(svg), header .toolbar .homeLink{font-size:0;padding:6px 8px;gap:0}
  header .toolbar button:has(svg) svg, header .toolbar .homeLink svg{width:16px;height:16px}
  header .toolbar button:not(:has(svg)){font-size:10px;padding:5px 7px}
  /* #gamePrompt (mulligan keep/redraw, first-or-second choice) was still overflowing the header on a
     phone - "You mulligan?" plus two full-size buttons don't fit next to Home even after the icon-only
     shrink above (these buttons have no icon, so that rule leaves them alone). The descriptive text
     ("<b>You</b> mulligan?") is a mix of an element and a bare text node with no wrapper to target, so
     font-size:0 on the whole prompt (same trick as the icon-only header buttons) hides ALL of it at once -
     the buttons' own labels ("Keep"/"Mulligan"/etc) already say what they do, and re-assert their own
     font-size so only the redundant surrounding text disappears. */
  .gamePrompt{padding:3px 4px;gap:3px;font-size:0;max-width:44vw;overflow:hidden;white-space:nowrap}
  .gamePrompt button{padding:3px 6px;font-size:9px;white-space:nowrap}
  /* the "load decklists" prompt has no button to fall back on - re-assert size on its own wrapper span */
  .gamePrompt .gpMsg{font-size:10px}
  /* content-sized rows (the real header height varies): fitBoard measures and sizes the mats to fit */
  body.playing main, body.playing.anlMin main{grid-template-columns:1fr;grid-template-rows:auto auto;height:auto;overflow:visible}
  body.playing .table{order:1;transform:none;overflow:hidden auto}
  body.playing .handCol{order:2;flex-direction:column;height:auto;max-height:32vh;min-width:0;width:100%}
  body.playing .handSide{flex:none;flex-direction:row;overflow-x:auto;overflow-y:hidden;align-items:center;gap:0;padding:4px}
  /* opponent's hand is NOT a strip on mobile - it's a single highlightable card-back ZONE on the board,
     to the right of the opp leader (#hand-opp, built in renderGame). Hiding the row here keeps the bottom
     (your) hand large and stops opponent card images from being stranded behind the board. */
  body.playing #handTop{display:none!important}
  body.playing .matHalf #hand-opp{display:flex!important}
  /* MULLIGAN is the one exception: the deciding player's real opening hand goes in #handTop (see
     renderGame's Mulligan branch) and MUST be visible to keep/mulligan on - the blanket hide above was
     silently nuking it on mobile, so there was no way to see your hand before deciding. Also needs its
     own height/overflow/card-size treatment (not just display) - #handTop never got #handArea's own
     72px-card/height:auto rules below, so overflow-y:hidden (two rules up) was clipping the top/bottom
     of every card, leaving only about half of each visible. */
  body.playing.mulliganStep #handTop{display:flex!important;height:auto!important;
    overflow-y:visible!important;flex:none!important}
  body.playing.mulliganStep #handTop .hcard{width:72px;max-width:none;flex:0 0 auto;margin-right:-26px}
  body.playing.mulliganStep #handTop .hcard:last-child{margin-right:0}
  /* your hand: bigger cards that OVERLAP (negative margin-right, like the DON strip) so there are no gaps
     between them and no card is pushed off behind the board. --hand-card-w/--hand-gap-eff are set by
     fitHands(); mobile falls back to a fixed size + overlap here since fitHands early-returns on mobile. */
  body.playing #handArea.handSide .hcard{width:72px;max-width:none;flex:0 0 auto;margin-right:-26px}
  body.playing #handArea.handSide .hcard:last-child{margin-right:0}
  /* SINGLE overlapping row that scrolls horizontally - flex-wrap:nowrap is essential (the desktop/base
     .handRow rules set wrap, which made a full hand wrap to 2-3 stacked rows on mobile instead of one
     scrolling strip). width:max-content lets it grow past the viewport so #handArea's overflow-x scrolls. */
  body.playing .handSide .handRow{display:flex!important;flex-direction:row!important;flex-wrap:nowrap!important;gap:0;align-items:center;width:max-content!important}
  /* analysis becomes a right-hand DRAWER over the board when opened (never squeezes the board) */
  .analysis, body.playing .analysis{position:fixed;right:0;top:52px;bottom:0;width:min(88vw,360px);z-index:66;
    max-height:none;border-radius:12px 0 0 12px;box-shadow:-10px 0 34px #000b}
  /* keep the handle ABOVE the analysis drawer (z 66) so the panel can always be closed again */
  .anlToggle, body.playing .anlToggle{right:calc(min(88vw,360px) - 13px);z-index:67}
  /* chat+log panel moves INTO the analysis drawer on mobile (relocateChatLog in game.js) so the hand
     column stays dedicated to card view - .analysis isn't a flex container the way #handCol is, so
     flex:1/min-height:0 (meant for a flex parent) do nothing here; give it a real fixed height instead. */
  .analysis .chatLog{flex:none;height:38vh;margin-bottom:10px}
}

/* body.compactBoard: forces the SAME hand-under-board mobile layout as the 840px media query above, but
   gated on JS (setCompactBoard in game.js) instead of a hard viewport width - triggered whenever the
   Attack Analysis drawer is open AND the window is narrower than ~half a typical desktop monitor
   (COMPACT_BOARD_VW in game.js). At that width the side-docked hand column and the floating analysis
   panel fight for the same space even though the panel is already correctly overlay-docked (not squeezing
   the grid) - switching the HAND to the same bottom-strip treatment mobile uses (instead of a left column)
   is what actually resolves the visual clash, matching what genuine mobile already does. Deliberately
   narrower in scope than the full 840px block: only the properties that move the hand relative to the
   board are duplicated here - header/toolbar/gamePrompt sizing is untouched, since those already read
   fine at this width without any change (per direct user confirmation on the width-only analysis-drawer
   fix earlier). Keep this list in sync with the 840px block above if the hand-layout rules there change. */
/* --board-scale/-x/-y are NOT reset here: fitBoard() (game.js) already sets them inline on :root every
   time compactBoard turns on, computed against the ACTUAL space left beside/below the analysis drawer.
   A body-scoped reset used to shadow that inline value for any .matHalf descendant (custom properties
   resolve from the NEAREST ancestor with a cascaded value, and body is closer than :root), silently
   forcing scale back to 1 and rendering the mat far larger than fitBoard() intended - pushing the hand
   strip off the bottom of the screen even though the JS scale looked correct in devtools. */
body.compactBoard.playing .table{transform:none;order:1;overflow:hidden auto}
body.compactBoard .pcard,body.compactBoard .slot{width:clamp(54px,10.5vw,84px);height:calc(clamp(54px,10.5vw,84px)*1.4)}
body.compactBoard{--cardW:clamp(46px,6.6vh,70px)}
body.compactBoard .table{overflow-x:auto;max-width:100vw}
/* compactBoard only ever applies while the analysis drawer is genuinely OPEN (isCompactBoard() in game.js
   returns false whenever body.anlMin is set) - .analysis is position:fixed there (an overlay, not a grid
   participant), so a bare `1fr` column let .table grow across the FULL remaining viewport width, including
   the strip the fixed panel physically covers. The board rendered its right edge UNDERNEATH the overlay
   instead of being confined to the space actually left of it (reported live: board/hand cut off behind
   the analysis panel instead of shrunk to fit beside it). Reserve the same width the panel occupies via
   padding on main itself (simpler than a 3rd grid track, and it's the SAME fix genuine mobile's own
   analysis-drawer block above already relies on implicitly by never needing to share a grid row with it).
   body.compactBoard.playing.anlMin never actually fires in practice (compactBoard requires the panel to be
   open) but is kept in the selector list as a defensive no-op match for the minimised case, unpadded. */
body.compactBoard:not(.anlMin) main{
  grid-template-columns:1fr;grid-template-rows:auto;height:auto;overflow:visible;
  padding-right:min(88vw,360px)}
body.compactBoard.anlMin main{grid-template-columns:1fr;grid-template-rows:auto;height:auto;overflow:visible}
body.compactBoard.playing .handCol{order:2;flex-direction:column;height:auto;max-height:32vh;min-width:0;width:100%}
body.compactBoard.playing .handSide{flex:none;flex-direction:row;overflow-x:auto;overflow-y:hidden;align-items:center;gap:0;padding:4px}
/* opponent hand becomes the on-board #hand-opp zone here too (see the 840px block above for rationale) */
body.compactBoard.playing #handTop{display:none!important}
body.compactBoard.playing .matHalf #hand-opp{display:flex!important}
/* MULLIGAN exception, same as the genuine-mobile 840px block above - without this, the deciding player's
   real opening hand was fully invisible (not just undersized) at any compactBoard width, since the blanket
   hide above has !important and no carve-out for it. overflow-y:hidden (set on .handSide two rules up)
   was also clipping the top/bottom of each card since #handTop never got #handArea's own card-size/height
   treatment (the two rules below only ever targeted #handArea) - give it the same 72px card width and
   let the row size to its real content height instead of the ambient (opponent-backs-sized) flex share. */
body.compactBoard.playing.mulliganStep #handTop{display:flex!important;max-height:none!important;
  height:auto!important;overflow-y:visible!important;flex:none!important}
body.compactBoard.playing.mulliganStep #handTop .hcard{width:72px;max-width:none;flex:0 0 auto;margin-right:-26px}
body.compactBoard.playing.mulliganStep #handTop .hcard:last-child{margin-right:0}
body.compactBoard.playing #handArea.handSide .hcard{width:72px;max-width:none;flex:0 0 auto;margin-right:-26px}
body.compactBoard.playing #handArea.handSide .hcard:last-child{margin-right:0}
body.compactBoard.playing .handSide .handRow{display:flex!important;flex-direction:row!important;flex-wrap:nowrap!important;gap:0;align-items:center;width:max-content!important}

/* attack-resolve panel: docked to the RIGHT of the board (over the analysis column) instead of a
   full-screen dimmed modal, so the board + red targeting arrow stay visible while blocks/counters
   are decided. The backdrop is gone and click-through, only the panel itself is interactive. */
#resolveModal{background:transparent;pointer-events:none;align-items:center;justify-content:flex-end;padding:56px 112px 10px}
#resolveModal .modalBox{pointer-events:auto;width:min(430px,94vw);max-height:calc(100vh - 76px);border:2px solid #ff2d40;
  animation:resSlideIn .28s ease-out, resPulse 1.5s ease-in-out .3s 3;
  box-shadow:0 12px 44px #000c}
/* battle opens: panel slides in from the right and pulses red three times */
@keyframes resSlideIn{from{transform:translateX(70px);opacity:0}to{transform:none;opacity:1}}
@keyframes resPulse{0%,100%{box-shadow:0 12px 44px #000c,0 0 0 0 #ff2d4000}50%{box-shadow:0 12px 44px #000c,0 0 30px 7px #ff2d4088}}

/* red targeting arrow: attacker -> current target, drawn over the board during a battle */
#atkArrow{position:fixed;inset:0;width:100vw;height:100vh;pointer-events:none;z-index:45;display:none}   /* above the board, BELOW every panel/modal */
#atkArrow .aglow{stroke:#ff2d4044;stroke-width:15;stroke-linecap:round}
#atkArrow .acore{stroke:#ff2d40;stroke-width:5.5;stroke-linecap:round;filter:drop-shadow(0 0 6px #ff2d40aa)}

/* end-of-game watermark: huge translucent WIN/LOSE over the untouched board, text only; clicks pass
   through (pointer-events none) so the players can still read and use the board + game log underneath */
.resultOverlay{position:fixed;inset:0;z-index:800;pointer-events:none;display:flex;align-items:center;justify-content:center;padding:0 16px}
.resultOverlay .rTxt{font-size:clamp(44px,13vw,210px);font-weight:900;letter-spacing:.06em;line-height:1;opacity:.65;user-select:none;text-shadow:0 0 60px currentColor;text-align:center;max-width:100%}
.resultOverlay.win .rTxt{color:#2fe08a}
.resultOverlay.lose .rTxt{color:#ff4d5e}

/* ---------- resolve panel v2: visual duel ---------- */
.duel{display:flex;align-items:center;justify-content:center;gap:16px;margin:0 0 10px}
.duel .duelCard{display:flex;flex-direction:column;align-items:center;gap:3px;min-width:96px}
.duel .duelImg{width:84px;height:118px;border-radius:8px;overflow:hidden;background:#0a0f1c;border:1px solid var(--line);box-shadow:0 3px 10px #0008}
.duel .duelImg img{width:100%;height:100%;object-fit:cover;display:block}
.duel .duelPow{font-size:22px;font-weight:900;line-height:1;font-variant-numeric:tabular-nums}
.duel .duelPow.atk{color:#ff5b64}
.duel .duelPow.def{color:var(--accent2)}
.duel .duelNm{font-size:10px;color:var(--muted);max-width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center}
.duel .duelMid{font-size:22px;opacity:.75}
.resOut{display:flex;flex-direction:column;gap:2px;font-size:14px}
.resOut small{font-weight:600;opacity:.75;font-size:11px}
.ropts .resCard.imgBtn{display:flex;flex-direction:column;align-items:center;gap:3px;padding:5px 6px}
.ropts .resCard.imgBtn img{width:52px;height:73px;object-fit:cover;border-radius:5px;display:block;pointer-events:none}
.ropts .resCard.imgBtn span{font-size:11px;font-weight:800}
#resolveBox .modalBtns{position:sticky;bottom:0;background:var(--panel);padding-top:8px;margin-top:10px}
#resolveBox .modalBtns [data-gact="resolve"]{flex:1;font-size:15px;font-weight:800;padding:10px 12px}

/* mobile: the resolve panel is a BOTTOM SHEET (board + red arrow stay visible above it) */
@media (max-width:840px){
  #resolveModal{align-items:flex-end;justify-content:center;padding:0}
  #resolveModal .modalBox{width:100vw;max-width:none;max-height:62vh;border-radius:16px 16px 0 0;
    border-left:none;border-right:none;border-bottom:none;padding:12px 12px 10px;
    animation:resSlideUp .22s ease-out, resPulse 1.5s ease-in-out .3s 3}
  .duel .duelImg{width:64px;height:90px}
  .duel .duelPow{font-size:19px}
}
@keyframes resSlideUp{from{transform:translateY(70px);opacity:0}to{transform:none;opacity:1}}

/* #abilModal (When Attacking / On Rest / On Battle KO / other ability-queue steps) and #trigModal
   (Life-Trigger prompt on a landed hit) get the SAME docked/transparent treatment as #resolveModal
   whenever they open mid-battle (.docked, toggled in game.js based on state.game.battle) - only then,
   since a non-combat ability prompt (e.g. a plain On Play search) should keep the normal centered feel. */
#abilModal.docked, #trigModal.docked{background:transparent;pointer-events:none;align-items:center;justify-content:flex-end;padding:56px 112px 10px}
#abilModal.docked .modalBox, #trigModal.docked .modalBox{pointer-events:auto;box-shadow:0 12px 44px #000c}
/* width is the SAME as the chat/hand column (--hand-col-w) whether docked (mid-battle) or centred (normal
   Main-phase ability), at 100% scale - matches the user's own reference point exactly, scaling with
   viewport the same way the chat column does, instead of an independently-chosen cap. A long ability's
   body text/search grid was previously stretching the generic 820px .modalBox to cover most of the screen. */
#abilModal .modalBox, #trigModal .modalBox{width:var(--hand-col-w);max-width:92vw;max-height:calc(100vh - 76px)}
@media (max-width:840px){
  #abilModal.docked, #trigModal.docked{align-items:flex-end;justify-content:center;padding:0}
  #abilModal.docked .modalBox, #trigModal.docked .modalBox{width:100vw;max-width:none;max-height:62vh;border-radius:16px 16px 0 0;
    border-left:none;border-right:none;border-bottom:none;padding:12px 12px 10px}
  /* non-docked (normal Main-phase ability, no battle in progress) still gets a real width cap on
     narrow screens, not just on desktop - was previously unbounded on mobile via the generic .modalBox. */
  #abilModal .modalBox, #trigModal .modalBox{width:var(--hand-col-w);max-width:94vw}
}

/* touch devices: cards are game pieces, not images - no save-image callout, no selection, no drag.
   Long-press shows the magnified preview instead (wired in setupPreview). Covers every card-shaped
   surface on the board, not just the main Character/Leader slots - DON tokens (.dt) and Life cards
   (.lifeCard) previously had NO callout/drag protection at all (only the generic .matHalf *{user-select:
   none} rule, which stops text selection but NOT the mobile long-press "open image in new tab" menu,
   since that specifically needs -webkit-touch-callout/-webkit-user-drag on the <img> itself). */
.pcard,.hcard,.pcell,.sCard,.bldCard,.resCard,.hbCard,.dt,.lifeCard{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;-webkit-user-drag:none}
.pcard img,.hcard img,.pcell img,.sCard img,.bldCard img,.resCard img,.hbCard img,.dt,.lifeCard{-webkit-touch-callout:none;-webkit-user-drag:none}
/* BLANKET rule, every page/mode: any <img> anywhere in the app (netPick cards, the magnify preview itself,
   deck-preview thumbnails, resolve/search pickers not covered by the class list above, etc) gets the same
   no-callout/no-drag treatment - a per-class allowlist kept missing new surfaces one at a time, so this is
   the actual fix: nothing in the app should ever offer the mobile "open/save image" long-press menu. */
img{-webkit-touch-callout:none;-webkit-user-drag:none}
/* long-press preview: centred and big (the finger hides anything placed beside the card) */
.cardPreview.touch{left:50%!important;top:44%!important;transform:translate(-50%,-50%);width:min(68vw,320px);max-width:none}

/* ============ deck builder on phones ============
   One column: search + results take the screen, the decklist docks at the bottom
   (thumb-reachable) as its own scrollable strip. Header collapses to icons. */
@media (max-width:840px){
  #builderModal .builderBox{padding:8px 8px calc(8px + env(safe-area-inset-bottom,0px))}
  .pickHead{flex-wrap:wrap;gap:6px}
  .pickHead>span:first-child{font-size:14px}
  .bldCount{font-size:13px;margin-left:6px}
  /* toolbar: full-width row that wraps; nav links shrink to icon-only. Groups wrap onto their own
     lines at this width, so the between-group dividers (meant for a single row) are hidden here. */
  .bldTop{width:100%;gap:5px}
  .bldDivider,.bldToolDivider{display:none}
  .bldTop .libSel{flex:1 1 45%;min-width:0;font-size:12px}
  .bldTop .libName{flex:1 1 40%;min-width:0}
  .bldTop button{padding:7px 10px;font-size:12px}
  .bldTop .themeToggle{display:none}   /* room is scarce; theme still lives on every other page */
  /* My Decks / Home are the primary way OFF this page on a phone (no other nav is reachable once you're
     deep in the builder) - they get the BIGGEST, most prominent buttons on the mobile toolbar: full-width,
     stacked, with their label always visible (not shrunk to icon-only like every other control here). */
  .bldTop .bldGroup:has(.bldHome){order:-1;width:100%;flex-direction:column;gap:6px}
  .bldTop .bldHome{width:100%;justify-content:center;font-size:16px;font-weight:800;padding:14px 10px;gap:10px}
  .bldTop .bldHome svg{width:20px;height:20px;opacity:1}

  /* stack: search on top (keyboard-friendly), decklist docked at the bottom */
  .bldBody{display:flex;flex-direction:column;gap:8px;margin-top:6px}
  .bldSearch{order:1;flex:1 1 auto;min-height:0;gap:6px}
  .bldSearch>input{padding:8px 10px;font-size:16px}   /* 16px stops iOS zoom-on-focus */
  .bldList{order:2;flex:0 0 auto;max-height:26vh;border-radius:10px}
  .bldStats{display:none}

  /* results: 3 cards per row reads well at phone width; tighter cells */
  .bldSearch .pickGrid{grid-template-columns:repeat(3,1fr);gap:2px}

  /* filters: one horizontally-scrollable row instead of a tall wrapped block */
  .pickFilters{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:2px;scrollbar-width:none}
  .pickFilters::-webkit-scrollbar{display:none}
  .pickFilters>*{flex:none}
  .pfSet{font-size:11px;padding:5px 6px;max-width:110px}

  /* decklist rows: bigger tap targets for +/- */
  .bldRow{padding:5px 0}
  .bldRow button{padding:4px 12px;font-size:14px}
}

/* ============ modern header chrome (2026-07-16) ============
   One button language across the app pages, matching the home hub: pill shapes, glass ghost
   surfaces, soft glows on filled actions, hover lift. The header itself now follows the theme
   tokens instead of a hardcoded dark gradient. */
header{background:linear-gradient(180deg,var(--head-a),var(--head-b));border-bottom:1px solid var(--line)}
header .toolbar button, header .toolbar .homeLink, .bldTop button, .bldTop .bldHome, .bldTool{
  border-radius:999px;padding:8px 14px;font-weight:700;font-size:13px;
  transition:transform .15s ease,border-color .15s ease,filter .15s ease,background .15s ease}
header .toolbar button:hover, header .toolbar .homeLink:hover, .bldTop button:hover, .bldTop .bldHome:hover, .bldTool:hover{transform:translateY(-1px)}
header .toolbar button svg, header .toolbar .homeLink svg, .bldTop button svg, .bldTop .bldHome svg, .bldTool svg{
  width:16px;height:16px;opacity:1;stroke-width:2.1}
/* ghost = glass pill */
button.ghost{background:color-mix(in srgb,var(--btn-bg) 62%,transparent);border:1px solid var(--line);color:var(--txt);
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}
button.ghost:hover{border-color:color-mix(in srgb,var(--accent) 55%,var(--line))}
/* filled actions get a soft glow in their own colour */
header .toolbar button.primary{border:1px solid transparent;box-shadow:0 8px 20px -10px var(--accent)}
button.make{border-radius:999px;box-shadow:0 8px 20px -10px var(--gold)}
/* danger = quiet outline that fills red on hover (a big solid red block reads as an alarm, not a control) */
header .toolbar button.danger{background:transparent;border:1px solid color-mix(in srgb,var(--red) 40%,var(--line));color:var(--red)}
header .toolbar button.danger:hover{background:color-mix(in srgb,var(--red) 14%,transparent);filter:none}
.homeLink{border-radius:999px;padding:8px 14px;font-weight:700;color:var(--txt);
  background:color-mix(in srgb,var(--btn-bg) 62%,transparent);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}
.homeLink:hover{border-color:color-mix(in srgb,var(--accent) 55%,var(--line));color:var(--txt)}
.bldTool{border-radius:999px}
#bldClose{border-radius:50%;width:36px;height:36px;padding:0;align-items:center;justify-content:center;font-size:15px}
.libSel,.libName{border-radius:9px;padding:7px 10px}
.ctrl input{border-radius:8px}
/* phone: the pills keep their shape but shed padding so the compact header still fits */
@media (max-width:840px){
  header .toolbar button, header .toolbar .homeLink{padding:6px 10px;font-size:11px}
  header .toolbar button svg, header .toolbar .homeLink svg{width:14px;height:14px}
  .bldTop button{padding:7px 11px;font-size:12px}
  /* My Decks / Home stay the biggest, label-visible buttons on the mobile builder toolbar - see the
     earlier @media (max-width:840px) block above for the full-width/stacked sizing; this later block
     must NOT re-shrink them to icon-only (that was the actual bug: two separate mobile overrides existed
     and this second, later one always won). */
  .bldTop .bldHome{font-size:16px;font-weight:800;padding:14px 10px;gap:10px}
}

/* ============ format legality (Standard rotation / Extra Regulation) ============ */
/* header pill next to the deck counter */
.bldLegal{display:inline-flex;gap:5px;margin-left:8px;vertical-align:middle}
.bldLegal .lgFmt{font-size:11px;font-weight:800;padding:3px 9px;border-radius:999px;border:1px solid var(--line);letter-spacing:.2px}
.bldLegal .lgFmt.ok{color:var(--ko);border-color:color-mix(in srgb,var(--ko) 45%,var(--line));background:color-mix(in srgb,var(--ko) 10%,transparent)}
.bldLegal .lgFmt.bad{color:var(--noko);border-color:color-mix(in srgb,var(--noko) 45%,var(--line));background:color-mix(in srgb,var(--noko) 10%,transparent)}
/* decklist row chip */
.bldRow .lgTag{flex:none;font-size:9px;font-weight:800;padding:1px 6px;border-radius:8px;letter-spacing:.3px}
.bldRow .lgTag.ban{background:var(--noko);color:#fff}
.bldRow .lgTag.rot{background:color-mix(in srgb,var(--gold) 30%,transparent);color:var(--gold);border:1px solid color-mix(in srgb,var(--gold) 50%,transparent)}
/* result-grid corner marker: a red ✗ bubble sized off the CARD (cqw, like the qty bubble) -
   the old tiny B1/B2 text pill was unreadable. Hover the bubble for the why (banned vs rotated). */
.bldCard .bldLgTag{position:absolute;left:4%;top:4%;z-index:2;
  display:grid;place-items:center;box-sizing:border-box;
  width:clamp(12px,13cqw,30px);height:clamp(12px,13cqw,30px);border-radius:50%;
  font-weight:900;font-size:clamp(8px,7.5cqw,17px);line-height:1;
  background:var(--noko);color:#fff;border:max(1px,.7cqw) solid #fff;
  box-shadow:0 1px 5px #000a}
.bldCard .bldLgTag.rot{background:#c8442f}
/* stats-panel legality box */
.legalBox{background:var(--field);border:1px solid var(--line);border-radius:10px;padding:8px 10px;margin-bottom:10px}
.legalBox .lgRow{display:flex;justify-content:space-between;align-items:center;font-size:12.5px;padding:4px 0;font-weight:700}
.legalBox .lgRow .lgVerdict{font-weight:800}
.legalBox .lgRow.ok .lgVerdict{color:var(--ko)}
.legalBox .lgRow.bad .lgVerdict{color:var(--noko)}
.legalBox .lgProbs{margin:2px 0 6px;padding-left:16px;font-size:11px;color:var(--muted);line-height:1.45}
.lgUpd{font-size:9.5px;font-weight:600;color:var(--muted);float:right;opacity:.8}
/* builder decklist rows: hoverable (magnified card preview reads data-img) */
.bldRow{cursor:default}
.bldRow .bln{cursor:zoom-in}

/* printed ability text inside online ability prompts - stretches to the popup's own width (not a fixed
   cap) so it scales with .netPickBox across screen sizes instead of sitting narrower than the panel */
.netPick .netPickText{font-size:12px;color:var(--muted);line-height:1.5;margin:6px 2px 8px;width:100%;box-sizing:border-box;
  background:var(--field);border:1px solid var(--line);border-radius:8px;padding:8px 10px}

/* ============ online lobby: quickplay + public room list (2026-07-17) ============ */
.netBox--rooms{width:560px}
.netModes{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin:4px 0 12px}
.netMode{position:relative;display:flex;flex-direction:column;align-items:center;gap:3px;padding:14px 10px 12px;border-radius:12px;cursor:pointer;
  background:linear-gradient(160deg,var(--panel),var(--panel2));border:1px solid var(--line);color:var(--txt);
  transition:transform .15s ease,border-color .15s ease,box-shadow .15s ease}
.netMode svg{width:22px;height:22px;fill:none;stroke:var(--gold);stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;margin-bottom:3px}
.netMode--priv svg{stroke:var(--accent2)}
.netMode--ranked svg{stroke:#e0483f}
.netMode .nmT{font-size:14px;font-weight:800;letter-spacing:.2px}
.netMode .nmD{font-size:10.5px;color:var(--muted);line-height:1.3;text-align:center}
.netMode:hover{transform:translateY(-2px);border-color:color-mix(in srgb,var(--gold) 55%,var(--line));
  box-shadow:0 10px 24px -12px color-mix(in srgb,var(--gold) 55%,transparent)}
.netMode--priv:hover{border-color:color-mix(in srgb,var(--accent2) 55%,var(--line));
  box-shadow:0 10px 24px -12px color-mix(in srgb,var(--accent2) 55%,transparent)}
.netMode--ranked:hover{border-color:color-mix(in srgb,#e0483f 55%,var(--line));
  box-shadow:0 10px 24px -12px color-mix(in srgb,#e0483f 55%,transparent)}
.netMode:disabled{opacity:.5;cursor:not-allowed;transform:none!important;box-shadow:none!important}
.netMode .nmTier{margin-top:4px;font-size:9.5px;font-weight:800;letter-spacing:.3px;padding:2px 8px;border-radius:999px;
  background:color-mix(in srgb,var(--tier-c,var(--muted)) 20%,var(--panel2));color:var(--tier-c,var(--muted));
  border:1px solid color-mix(in srgb,var(--tier-c,var(--muted)) 40%,var(--line))}
/* tier accent colours: shared by the mode-card badge, the queue-step badge, and the win/lose promo line */
.tier-recruit{--tier-c:#9aa5c4}
.tier-supernova{--tier-c:#5f8ce0}
.tier-warlord{--tier-c:#b565e0}
.tier-yonko{--tier-c:#E1CD43}
/* queue step: pulse the tier badge + a simple spinner while searching */
.netBox--queue{align-items:center;text-align:center}
.netQueueBadge{display:inline-flex;flex-direction:column;align-items:center;gap:2px;padding:10px 22px;border-radius:14px;margin:10px 0 4px;
  background:color-mix(in srgb,var(--tier-c,var(--muted)) 16%,var(--panel2));border:1px solid color-mix(in srgb,var(--tier-c,var(--muted)) 45%,var(--line))}
.netQueueBadge .nqTier{font-size:15px;font-weight:900;color:var(--tier-c,var(--txt))}
.netQueueBadge .nqRating{font-size:11px;color:var(--muted);font-weight:700}
.netQueueSpin{width:34px;height:34px;margin:6px auto;border-radius:50%;border:3px solid var(--line);border-top-color:var(--gold);
  animation:netQueueSpin .9s linear infinite}
@keyframes netQueueSpin{to{transform:rotate(360deg)}}
@media(prefers-reduced-motion:reduce){.netQueueSpin{animation:none}}
.netRow--code input{text-transform:uppercase;letter-spacing:3px;font-weight:800;text-align:center;width:110px}
.netRoomsHead{font-size:10.5px;font-weight:800;letter-spacing:.8px;text-transform:uppercase;color:var(--muted);margin:14px 0 6px}
.netRoomsHead span{color:var(--gold)}
.netRooms{max-height:240px;overflow:auto;display:flex;flex-direction:column;gap:6px}
.netRoomsEmpty{font-size:12.5px;color:var(--muted);padding:14px 10px;text-align:center;border:1px dashed var(--line);border-radius:10px}
.netRoomRow{display:flex;align-items:center;gap:10px;padding:7px 10px;border:1px solid var(--line);border-radius:10px;
  background:color-mix(in srgb,var(--panel2) 70%,transparent)}
.netRoomRow .rrHost{font-size:13px;font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.netRoomRow .rrCode{font-size:11.5px;font-weight:800;letter-spacing:2px;color:var(--gold);
  background:color-mix(in srgb,var(--gold) 12%,transparent);border:1px solid color-mix(in srgb,var(--gold) 35%,var(--line));
  border-radius:6px;padding:2px 7px}
.netRoomRow .rrAge{font-size:10.5px;color:var(--muted);flex:none}
.netRoomRow button{padding:5px 13px;font-size:12px;font-weight:800;border-radius:999px;background:var(--accent);border:1px solid transparent;color:#fff}
.netRoomRow button:hover{filter:brightness(1.12)}

/* ============ phone sizing for this pass's new controls (2026-07-17) ============ */
@media(max-width:840px){
  header h1 svg.ic{width:14px;height:14px}
  /* card pills: slimmer so they hug the tiny mobile cards (coin dropped - the gold count marks DON) */
  .stp{gap:1px;padding:0 2px}
  .stp .stpDon{display:none}
  .stp b{font-size:8px;padding:0 1px}
  .stp button{width:14px;height:14px;font-size:10px}
  .stp .v{font-size:9px;min-width:10px}
  /* DON strip control pill: slightly smaller but still a comfortable tap target */
  .donCtl{gap:3px;padding:2px 4px}
  .donCtl button{width:20px;height:20px;font-size:12px}
  .donCtl .donN{font-size:10px;min-width:26px}
  /* lobby: mode cards stack when the box is really narrow */
  .netBox--rooms{width:min(560px,94vw)}
  .netRooms{max-height:32vh}
  .netModes{grid-template-columns:1fr 1fr}   /* 3 cards -> 2-up before the full single-column stack below */
}
@media(max-width:520px){
  .netModes{grid-template-columns:1fr}
  .netMode{flex-direction:row;justify-content:flex-start;gap:10px;padding:11px 14px}
  .netMode svg{margin:0}
  .netMode .nmD{text-align:left}
  .netMode .nmT{margin-right:4px}
}

/* frozen marker: card cannot attack this turn (freeze effects) - small aqua snowflake, top-left */
.pcard .frozenTag{position:absolute;top:5px;left:5px;z-index:3;display:grid;place-items:center;
  width:18px;height:18px;border-radius:50%;font-size:11px;line-height:1;color:#eafcff;
  background:#1996b4e6;border:1px solid #7fe3f6;box-shadow:0 1px 4px #0009,0 0 8px #37c5e766}
body.playing .matHalf .pcard .frozenTag{width:22px;height:22px;font-size:13px}
@media (max-width:840px){ .pcard .frozenTag{width:14px;height:14px;font-size:9px;top:3px;left:3px} }
/* rest-locked marker: this card cannot be rested by ANY effect (own or opponent's) - amber lock, stacks
   to the right of frozenTag if both are showing (rest-locked chars also can't attack, per the rules -
   but the two states are distinct enough to call out separately: a card can be rest-locked without ever
   having been "frozen" by a debuff, e.g. a printed self-protection). */
.pcard .restLockTag{position:absolute;top:5px;left:27px;z-index:3;display:grid;place-items:center;
  width:18px;height:18px;border-radius:50%;font-size:11px;line-height:1;color:#2a1400;
  background:#e0a536e6;border:1px solid #ffd27f;box-shadow:0 1px 4px #0009,0 0 8px #e0a53666}
body.playing .matHalf .pcard .restLockTag{width:22px;height:22px;font-size:13px;left:31px}
@media (max-width:840px){ .pcard .restLockTag{width:14px;height:14px;font-size:9px;top:3px;left:21px} }
/* stay-rested marker: this card is guaranteed to STAY rested through its controller's next Refresh Phase
   (keepRested/noUnrest) - distinct from restLockTag (can't be rested at all right now) and frozenTag
   (can't attack THIS turn): this is a future-refresh effect, visible now so the player can plan around it. */
.pcard .stayRestedTag{position:absolute;top:5px;left:49px;z-index:3;display:grid;place-items:center;
  width:18px;height:18px;border-radius:50%;font-size:11px;line-height:1;color:#fff;
  background:#8a3ad6e6;border:1px solid #c79bff;box-shadow:0 1px 4px #0009,0 0 8px #8a3ad666}
body.playing .matHalf .pcard .stayRestedTag{width:22px;height:22px;font-size:13px;left:53px}
@media (max-width:840px){ .pcard .stayRestedTag{width:14px;height:14px;font-size:9px;top:3px;left:39px} }
