@font-face{font-family:'Barlow';font-style:normal;font-weight:400;font-display:swap;src:url(/static/Barlow-400.woff2) format('woff2')}
@font-face{font-family:'Barlow';font-style:normal;font-weight:500;font-display:swap;src:url(/static/Barlow-500.woff2) format('woff2')}
@font-face{font-family:'Barlow';font-style:normal;font-weight:600;font-display:swap;src:url(/static/Barlow-600.woff2) format('woff2')}
@font-face{font-family:'Barlow Condensed';font-style:normal;font-weight:600;font-display:swap;src:url(/static/BarlowCondensed-600.woff2) format('woff2')}
@font-face{font-family:'Barlow Condensed';font-style:normal;font-weight:700;font-display:swap;src:url(/static/BarlowCondensed-700.woff2) format('woff2')}
:root{
  --felt:#14171c;--felt2:#191d24;--panel:#1f242c;--panel2:#262c36;--line:#313945;--line2:#3d4654;
  --ink:#e8e2d6;--ink2:#a9aeb6;--mute:#6f7681;--gold:#f2c14e;--gold2:#b98d24;--glow:rgba(242,193,78,.55);--on-gold:#1a1405;
  --hi:var(--gold);--hi2:var(--gold2);--hi-glow:var(--glow);   /* legal-move highlight: gold on felt, blue on the pokeball desk */
  --red:#e0523f;--green:#5fb36a;--blue:#3b86c8;
  --arena-bg:radial-gradient(ellipse at 50% 45%,#1b2028 0%,#14171c 70%);--go-bg:#2a2717;--vs-bg:#221f14;--links-bg:#1d1a12;
  --rule-bg:#1f2a1f;--rule-fg:#b9e0b0;--rule-ln:#2e4a2e;--rule-neg-bg:#2a1f1f;--rule-neg-fg:#e0b0b0;--rule-neg-ln:#4a2e2e;
  --overlay:rgba(20,23,28,.72);--shadow:rgba(0,0,0,.45);--shadow2:rgba(0,0,0,.6);
  --t-Grass:#4d9a4a;--t-Fire:#d9552f;--t-Water:#3b86c8;--t-Lightning:#e2c331;--t-Psychic:#8e5fc4;--t-Fighting:#b6683a;
  --t-Darkness:#3f5a66;--t-Metal:#8b97a3;--t-Dragon:#c19a3b;--t-Colorless:#d6d2c7;--t-Fairy:#d98ac2;--t-Trainer:#c7c1b2;
  --display:"Barlow Condensed","Arial Narrow","Roboto Condensed","Helvetica Neue",Arial,sans-serif;--body:"Barlow","Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  /* board-arena 2026-09-19 section 2: the props (podium, bench slab, deck box, discard tray, prize rack, stadium
     podium, End turn/Undo, the clock) are built ONLY from these 8 tokens -- no literal hex in any prop rule below,
     so the light theme (overridden under :root[data-theme="pokeball"]) repaints every prop at once. */
  --prop:#1c2230;--prop-2:#161b26;--prop-face:#0e1119;--prop-edge:#2a3242;--prop-line:#0a0d13;
  --prop-ink:rgba(255,255,255,.5);--prop-shadow:rgba(0,0,0,.7);--rim-me:rgba(96,200,255,.45);--rim-opp:rgba(255,110,90,.45);
  --edge-a:#4a5162;--edge-b:#262b36;
}
/* Lukas 2026-09-07: the Pokéball theme -- white desk, red primary, blue highlights. Picked on the Profile page, saved per account. */
:root[data-theme="pokeball"]{
  --felt:#f3f0e9;--felt2:#ffffff;--panel:#ffffff;--panel2:#f6f4ef;--line:#e2ddd3;--line2:#cfc9bd;
  --ink:#1b1d22;--ink2:#4f545c;--mute:#7a7f88;--gold:#d8382b;--gold2:#b12e23;--glow:rgba(216,56,43,.35);--on-gold:#ffffff;
  --hi:#2b5fb8;--hi2:#1d4a99;--hi-glow:rgba(43,95,184,.45);
  --red:#c9302c;--green:#2f8f3c;--blue:#2b5fb8;
  --arena-bg:radial-gradient(ellipse at 50% 45%,#ffffff 0%,#f3f0e9 70%);--go-bg:#fff3f1;--vs-bg:#fff7f6;--links-bg:#fff7f6;
  --rule-bg:#e8f3e6;--rule-fg:#256b2c;--rule-ln:#bcdab6;--rule-neg-bg:#fbe9e7;--rule-neg-fg:#9c2a24;--rule-neg-ln:#efc3bf;
  --overlay:rgba(243,240,233,.78);--shadow:rgba(27,29,34,.14);--shadow2:rgba(27,29,34,.22);
  --t-Colorless:#a9a396;--t-Trainer:#9c9585;
  /* board-arena 2026-09-19 section 2: the same props, repainted pale -- rims stay the same (they read fine on white). */
  /* Lukas 2026-09-20 afternoon: "if 3d higher ground than make it better" -- the front face was too pale to read
     as a face in shade next to the top, now clearly darker than --prop. */
  --prop:#f1f3f6;--prop-2:#e2e6ec;--prop-face:#8f99a8;--prop-edge:#ffffff;--prop-line:#9aa3b2;
  --prop-ink:rgba(20,30,50,.55);--prop-shadow:rgba(20,30,50,.35);
  --edge-a:#e8e2d6;--edge-b:#b9b3a6;
}
html,body{background:var(--felt);color:var(--ink);font-family:var(--body);font-size:13px;margin:0;height:100%}
/* the page scrollbar is hidden, scrolling stays (Lukas 2026-09-20: "hide vertical scrollbar in pages it looks ugly. Other sites make it not visible"); the side panel's own scrolling boxes (.pane, #log ul) get the same treatment below (Lukas 2026-09-22: "hide scroll bar in log window also it looks ugly"). */
html{scrollbar-width:none}html::-webkit-scrollbar{width:0;height:0}
button{font:inherit;color:inherit;background:var(--panel);border:1px solid var(--line2);border-radius:3px;padding:4px 10px;cursor:pointer}
button:hover{border-color:var(--gold2)}button:disabled{opacity:.45;cursor:default}
button.gold{background:var(--gold);color:var(--on-gold);border-color:var(--gold);font-family:var(--display);font-weight:600;font-size:15px}
input,textarea,select{font:inherit;color:var(--ink);background:var(--felt2);border:1px solid var(--line2);border-radius:3px;padding:6px 8px}
.foot{position:fixed;bottom:4px;right:10px;font-size:10px;color:var(--mute)}body:has(.shell) .foot{display:none}
[hidden]{display:none!important}
.d{font-family:var(--display);font-weight:600}

/* lobby */
.lobby{max-width:760px;margin:40px auto;padding:0 20px}
.lobby h1{font-family:var(--display);font-size:36px;margin:0 0 4px}
.lobby .sub{color:var(--ink2);margin-bottom:24px}
.lobby .row{display:grid;grid-template-columns:1fr 1fr;gap:18px}
.lobby fieldset{border:1px solid var(--line);padding:12px 14px;margin:0 0 14px}
.lobby legend{font-family:var(--display);font-size:15px;color:var(--gold);padding:0 6px}
.lobby textarea{width:100%;height:150px;box-sizing:border-box;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px}
.lobby select{width:100%}
.lobby .check{font-size:12px;color:var(--ink2);margin-top:6px;min-height:18px}
.lobby .check .bad{color:var(--red)}.lobby .check .ok{color:var(--green)}
.links{margin-top:16px;padding:12px;border:1px solid var(--gold2);background:var(--links-bg);word-break:break-all}
.links input{width:100%;box-sizing:border-box;margin-top:6px}
/* TASK 3 (Lukas, testing the merged build): a copy button next to each link on the waiting page, instead of just
   a readonly input to select by hand. */
.linkrow{display:flex;gap:8px;align-items:center;margin-top:6px}
.linkrow input{flex:1;min-width:0;margin-top:0}
/* fix round A4 (Lukas, real-Chrome render): the waiting page's own felt strip, holding the creator's picked deck
   box open beside the spinner -- same felt background convention as .dcase .stage/.matchup (the open box's
   risen lid/cards need headroom above it).
   fix round B2 (Lukas, real-Chrome render at 1440x900): the strip alone (~525px) pushed the link boxes below
   the fold -- .waitwrap puts it beside them instead (a two-column grid), and the strip itself shrinks (90px top
   padding, a tighter gap, a narrower box) to land around 420px tall. */
.waitwrap{display:grid;grid-template-columns:minmax(0,1fr) minmax(320px,1fr);gap:24px;align-items:start;margin:16px 0}
.waitlinks{display:flex;flex-direction:column;gap:6px}
.waitlinks .links{margin-top:0}
.waitstage{background:url(/static/felt-shelf.webp) center 62%/cover;border-radius:16px;padding:90px 30px 20px;
  display:flex;flex-direction:column;align-items:center;gap:6px;
  box-shadow:inset 0 0 0 1px var(--line),inset 0 24px 40px -20px #000c,0 18px 40px -20px #000}
:root[data-theme="pokeball"] .waitstage{background:url(/static/felt-shelf-light.webp) center 62%/cover}   /* board-arena 2026-09-19 section 3 */
.waitstage .dbox{width:150px}
@media (max-width:640px){.waitwrap{display:block}.waitstage{margin-bottom:16px}}
.lobby label{display:block;margin:6px 0}
.lobby label input{width:100%;box-sizing:border-box;margin-top:2px}
.lobby.users table{width:100%;border-collapse:collapse;font-size:13px}
.lobby.users th,.lobby.users td{padding:6px 8px;border-bottom:1px solid var(--line);text-align:left}
.users .add{display:flex;gap:10px;align-items:center;flex-wrap:wrap}.users .add input,.users .add select{width:180px;height:38px;box-sizing:border-box}
.users .invite-out{display:flex;gap:10px;align-items:center;margin-top:12px}.users .invite-out input{flex:1;height:38px;box-sizing:border-box;font-family:var(--mono,monospace);font-size:13px}
.users .roles{display:flex;gap:10px;white-space:nowrap}.users .roles label{display:inline-flex;align-items:center;gap:3px;margin:0}.users .roles input{width:auto;margin:0}

/* account shell: landing / lobby / profile (design canvas 2026-09-07, 'Tournament desk') */
.shell{min-height:100vh;display:flex;flex-direction:column;background:var(--arena-bg)}
.shell .bar{height:52px;display:flex;align-items:center;justify-content:space-between;padding:0 40px;border-bottom:1px solid var(--line);background:var(--felt2)}
.brand{display:flex;align-items:center;gap:12px;font-family:var(--display);font-size:18px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--ink);text-decoration:none}
.brand svg{stroke:var(--gold);color:var(--gold)}
.shell .nav{display:flex;align-items:center;gap:6px;margin-left:22px;padding-left:22px;margin-right:auto;border-left:1px solid var(--line)}
.shell .who{display:flex;align-items:center;gap:22px;font-size:13px;color:var(--ink2)}
.shell .who a{color:var(--ink2);text-decoration:none}.shell .who a:hover{color:var(--gold)}
.shell .me{display:flex;align-items:center;gap:8px}
.avatar{width:26px;height:26px;border-radius:50%;background:var(--panel2);border:1px solid var(--line2);display:flex;align-items:center;justify-content:center;color:var(--gold);font-weight:600;font-family:var(--display)}
.rolebadge{font-family:var(--display);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--on-gold);background:var(--gold);padding:2px 7px;border-radius:2px}
/* bar dropdowns (Lukas 2026-09-15: account + admin menus, no JS needed to open/close -- native <details>) */
.shell .menu{position:relative}
.shell .menu>summary{list-style:none;cursor:pointer;display:flex;align-items:center}
.shell .menu>summary::-webkit-details-marker{display:none}
.shell .menu.admin>summary svg{stroke:var(--ink2);transition:stroke .15s ease}
.shell .menu.admin>summary:hover svg,.shell .menu.admin[open]>summary svg{stroke:var(--gold)}
.shell .menu.acct>summary{gap:8px}
.shell .menu.acct>summary svg{stroke:var(--ink2);transition:stroke .15s ease}
.shell .menu.acct:hover>summary svg,.shell .menu.acct[open]>summary svg{stroke:var(--gold)}
.shell .menu .dd{position:absolute;top:calc(100% + 10px);right:0;min-width:180px;background:var(--felt2);border:1px solid var(--line);border-radius:6px;box-shadow:0 18px 40px var(--shadow2);padding:6px 0;display:flex;flex-direction:column;z-index:20}
.shell .menu .dd a{display:block;padding:8px 14px;color:var(--ink2);text-decoration:none;font-size:13px;white-space:nowrap}
.shell .menu .dd a:hover{background:var(--panel2);color:var(--gold)}
.shell .menu .dd .dd-sep{height:1px;background:var(--line);margin:6px 0}
.fab{position:fixed;bottom:22px;right:22px;width:44px;height:44px;border-radius:50%;background:var(--gold);color:var(--on-gold);display:flex;align-items:center;justify-content:center;box-shadow:0 8px 20px var(--shadow2);text-decoration:none;transition:transform .15s ease,box-shadow .15s ease;z-index:30}
.fab:hover{transform:translateY(-2px);box-shadow:0 10px 26px var(--shadow2)}
.fab svg{stroke:var(--on-gold)}
.shell .body{padding:36px 40px 40px;max-width:1120px;width:100%;box-sizing:border-box;margin:0 auto;display:flex;flex-direction:column;gap:28px;min-width:0}
/* /legal (open beta, 2026-09-20): plain long-form copy, narrower than the table pages so paragraphs stay readable. */
.site{max-width:720px}
.site h1{font-family:var(--display);font-size:30px;margin:0 0 4px}
.site h2{font-family:var(--display);font-size:18px;letter-spacing:.04em;margin:28px 0 8px}
.site section:first-of-type h2{margin-top:4px}
.site p{line-height:1.6;color:var(--ink);margin:0 0 10px}
.site a{color:var(--gold)}
.shell h1{font-family:var(--display);font-size:34px;font-weight:700;margin:0;letter-spacing:.01em}
.shell .head{display:flex;align-items:baseline;justify-content:space-between;gap:20px}
.browser-note.lobby{margin:0 0 12px;padding:0;font-size:12px;color:var(--mute)}
.shell .hint{font-size:13px;color:var(--mute)}
/* Fable render round 1 (2026-09-18): min-width:0 on the flex/grid chain (.body -> .panel -> .ladder's board panel)
   so a wide table can shrink and ellipsize instead of stretching the phone page past its viewport. */
.panel{background:var(--panel);border:1px solid var(--line);border-radius:4px;padding:16px 18px 18px;display:flex;flex-direction:column;gap:10px;min-width:0}
.panel .k{font-family:var(--display);font-size:15px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--gold)}
.panel .kh{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
.panel input,.panel select,.panel textarea{height:40px;box-sizing:border-box;padding:0 10px;width:100%}
.panel input[type=checkbox]{width:auto;height:auto;margin:0}
.panel textarea{height:120px;padding:8px 10px;resize:vertical;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px}
.panel label{display:flex;flex-direction:column;gap:6px}
.panel label span{font-family:var(--display);font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink2)}
/* Lukas 2026-09-16: lobby "Games in progress" -- return to a game the browser lost track of */
.resume-row{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:8px 0;border-top:1px solid var(--line2);text-decoration:none;color:inherit}
.resume-row:first-of-type{border-top:none;padding-top:0}
.resume-who{font-weight:600}
.resume-meta{color:var(--ink2);font-size:12px;display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap}
.resume-ago{color:var(--ink2);font-size:12px}
.resume-row .chip{font-family:var(--display);font-size:10px;letter-spacing:.08em;text-transform:uppercase;padding:1px 6px;border-radius:8px;border:1px solid var(--line2);color:var(--ink2)}
.resume-row .chip.live{color:var(--green);border-color:var(--green)}
/* Lukas 2026-09-20: the × cancel/concede button on every "Games in progress" row */
.resume-row .resume-x{flex:none;width:28px;height:28px;border-radius:50%;border:1px solid var(--line2);background:transparent;color:var(--ink2);font-size:18px;line-height:1;cursor:pointer}
.resume-row .resume-x:hover{color:var(--red);border-color:var(--red)}
.btn{font-family:var(--display);height:46px;padding:0 30px;display:inline-flex;align-items:center;justify-content:center;background:var(--gold);color:var(--on-gold);border:1px solid var(--gold);border-radius:3px;font-size:17px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;cursor:pointer}
.btn:hover{filter:brightness(1.06)}.btn:disabled{opacity:.5}
.btn.sm{height:30px;padding:0 12px;font-size:13px;white-space:nowrap}
/* a base .btn.quiet (TASK 3, the waiting page's "Back to lobby" link) -- previously only scoped copies existed
   (.queue .btn.quiet, .hero-cta/.community .btn.quiet); those stay, this is just the default everywhere else. */
.btn.quiet{background:transparent;color:var(--ink);border-color:var(--line2)}
.btn.quiet:hover{border-color:var(--gold)}
/* Start's in-flight state (site pages spec section 2, step 4): a 28x40 looping deck stands in for a spinner --
   .btn already centers flex children, this just gives the stack some breathing room from the label. */
.btn.busy{gap:10px}
/* Fable render round 1 (2026-09-18): deck3dHtml() emits a bare <span> -- an inline element ignores width/height,
   so without this the loader was an invisible 0-size box (just a stray dot from .back's own ::after circle).
   inline-block makes the box actually take the size deck3dHtml() set inline; A's motion kit (arena.css, appended
   at the end once merged) makes the `.back` children `position:absolute;inset:0` to fill it properly. */
.queue .deck3d,.btn .deck3d{display:inline-block;line-height:0}
.btn .deck3d{flex:none;vertical-align:middle}
/* ladder (Lukas 2026-09-17): the tabs above the board reuse .tabs (see the game side panel below).
   RANKED LOBBY/LEADERBOARD (Lukas 2026-09-20, "Ladder page leave dedicated to Leader board ... with search and
   filter capabilities"): the queue moved into the Lobby's own Ranked mode (.queue/.queued/.queued-row below are
   now shared with that widget, see arena.js renderLobby) -- this page is the leaderboard alone, a search box plus
   region/country selects over the already-fetched top-100 board (arena.js lbFilter), no fetch on every keystroke. */
#ladder-tabs{margin-bottom:16px}
.lb-bar{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:14px}
.lb-bar input,.lb-bar select{height:38px;padding:0 12px;border-radius:4px;border:1px solid var(--line2);background:var(--panel);color:var(--ink);font-size:14px}
.lb-bar input{flex:1;min-width:160px}
.lb-bar .btn{height:38px;padding:0 20px;font-size:13px}
/* fix round 1 (2026-09-20): the queued deck box's own stage, restored verbatim from the pre-rewrite Ladder page
   (9c46d3a) -- a bare `dboxHtml(pick, "#", {open:true})` has no intrinsic width outside a ring/grid/fixed-width
   ancestor (it collapsed to ~20px), and its risen cards need a bounded box to rise inside instead of straight up
   over whatever sits below the queue widget. Same felt recipe as `.dcase .stage`, one fixed-width box centred by
   flex, no ring/ticker/search/chips. */
.q-stage{position:relative;height:400px;border-radius:14px;overflow:hidden;display:flex;align-items:center;justify-content:center;
  background:url(/static/felt-shelf.webp) center 62%/cover;box-shadow:inset 0 0 0 1px var(--line),inset 0 24px 40px -20px #000c,0 18px 40px -20px #000}
:root[data-theme="pokeball"] .q-stage{background:url(/static/felt-shelf-light.webp) center 62%/cover}
.q-stage::before{content:"";position:absolute;inset:0;background:radial-gradient(ellipse at 50% 70%,transparent 30%,#000a 100%);pointer-events:none}
.q-stage .dbox{position:relative;width:220px}
/* the open box's cards rise -- same reduced rise the deck case's compact form uses, so they stay inside the
   400px stage instead of climbing over whatever sits below the queue widget. */
.q-stage .dbox.open .cards>*{transform:rotateX(-6deg) translateY(-62%) rotate(calc((var(--i) - 1) * 8deg)) translateX(calc((var(--i) - 1) * 24px)) translateZ(calc((var(--i) - 1) * -6px))}
/* Fable render round 1 (2026-09-18): table-layout:fixed + an ellipsizing Player cell so a long name can never push
   the phone page past 390px; the "#" column stays narrow so Rating/Games get the room. */
table.lb{width:100%;border-collapse:collapse;table-layout:fixed}
table.lb th,table.lb td{padding:8px 10px;border-bottom:1px solid var(--line)}
table.lb th:first-child,table.lb td:first-child{width:2.5em}
/* the Player cell now holds an avatar disc + the name + an optional country code (site pages spec section 3) --
   the ellipsis moves onto .pname itself so a long name still truncates inside the flex row instead of the disc
   or the country code getting squeezed. */
table.lb td.p{display:flex;align-items:center;gap:8px;min-width:0}
table.lb td.p .pname{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
table.lb td.n,table.lb td.g,table.lb td.streak,table.lb td.delta{text-align:right;font-variant-numeric:tabular-nums}
.cc{font-family:var(--display);font-size:10px;letter-spacing:.08em;color:var(--mute);margin-left:6px}
tr.you td{background:var(--panel2)}
/* the ladder as an arena (site pages spec section 3, 2026-09-18): a medal disc for ranks 1-3 (the plain .tier
   colour-only text read as an afterthought next to a full redesign), an avatar disc per player (hash-of-name hue,
   arena.js nameHue()), and a delta column (+/- from the newest rated row, ladder.py last_delta). */
.medal{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;
  font-family:var(--display);font-weight:700;font-size:12px;color:var(--on-gold)}
.medal.gold{background:#c9a227}.medal.silver{background:#9aa4ad}.medal.bronze{background:#b87333}
.avatar{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;
  flex:none;font-family:var(--display);font-weight:700;font-size:11px;color:#fff}
table.lb td.delta{color:var(--mute)}
table.lb td.delta.up{color:var(--green)}table.lb td.delta.down{color:var(--red)}
.num{font-family:var(--display);font-variant-numeric:tabular-nums}
/* ranked (Lukas 2026-09-20, ranked lobby/leaderboard spec section 2): the Lobby's Ranked step 4 -- one full-width
   gold button doubles as "Find an opponent" and, while queued, the quiet "Leave queue" -- same #start element
   (the same button the other five modes use to start), no layout jump. Queued state stacks the looping deck
   (deck3dHtml, arena.js) above the message and the mm:ss counter (#queue-elapsed). */
.queue .queued{display:flex;flex-direction:column;align-items:center;gap:6px;margin:10px 0}
/* Fable render (2026-09-20, fix round 1 shot rk2-queued-1440): the column's align-items:center shrank the felt stage to
   the 220px box -- a felt strip. The stage spans the step like the deck case's own stage above it; the box stays centred by
   the stage's own flex. */
.queue .queued>.q-stage{align-self:stretch;width:100%}
.queue .queued .deck3d{margin:0 auto}
/* caption and elapsed timer sit beside each other, not stacked, to fit the narrower widget. */
.queued-row{display:flex;align-items:center;gap:8px}
/* the idle line above the button: rating + the two live counts. */
.ranked-idle{display:flex;flex-direction:column;gap:4px;margin:10px 0}
.queue #start{width:100%}
.queue .btn.quiet{background:transparent;color:var(--ink);border-color:var(--line2)}
.queue .btn.quiet:hover{border-color:var(--gold)}
#start.queued{animation:endpulse 2.4s ease-out infinite}
/* lobby funnel (Lukas 2026-09-18, site pages spec section 3): four numbered steps -- all visible at once on a
   desktop (.steps-nav stays off, .step never hides), the phone media query below turns the funnel on. */
.steps-nav{display:none}
.steps-nav span{font-family:var(--display);font-size:11px;letter-spacing:.06em;text-transform:uppercase;white-space:nowrap;padding:6px 10px;border-radius:12px;border:1px solid var(--line2);color:var(--mute)}
.steps-nav span.done{color:var(--ink2)}
.steps-nav span.on{color:var(--on-gold);background:var(--gold);border-color:var(--gold)}
.step{display:block}
.step+.step{margin-top:18px}
.step>.k{font-family:var(--display);font-size:15px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--gold);margin-bottom:10px}
.step-btns{display:none;align-items:center;justify-content:space-between;gap:10px;margin-top:14px}
/* "Games in progress" (Lukas 2026-09-18): a <details> summary line keeps a long list from pushing the funnel
   off screen -- native marker off, laid out like the account-menu summaries above. */
.panel.resume>summary{cursor:pointer;display:flex;align-items:baseline;justify-content:space-between;gap:12px;list-style:none}
.panel.resume>summary::-webkit-details-marker{display:none}
.panel.resume[open]>summary{margin-bottom:10px}
.modes{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:14px}
/* the lobby as a card table (site pages spec section 2, 2026-09-18): a 56px band per mode card, drawn with the
   arena card-back look (dark panel + lattice) and a round centre disc holding the mode icon. Selected = gold ring + lift; hover (pointer devices only,
   below) = a smaller lift + glow; locked keeps the dashed card, band greyed. */
.mode{min-height:150px;padding:0 18px 16px;box-sizing:border-box;background:var(--panel);border:1px solid var(--line);border-radius:4px;display:flex;flex-direction:column;gap:10px;cursor:pointer;text-align:left;overflow:hidden;position:relative;transition:transform .15s ease,box-shadow .15s ease}
.mode .band{height:56px;margin:0 -18px;flex:none;display:flex;align-items:center;justify-content:center;
  background-color:var(--panel2);background-image:repeating-linear-gradient(45deg,color-mix(in srgb, var(--line) 35%, transparent) 0 1px,transparent 1px 9px),repeating-linear-gradient(-45deg,color-mix(in srgb, var(--line) 35%, transparent) 0 1px,transparent 1px 9px)}
.mode .band .disc{width:34px;height:34px;border-radius:50%;background:var(--panel);border:1px solid var(--gold);box-sizing:border-box;display:flex;align-items:center;justify-content:center}
.mode svg{stroke:var(--ink2)}
.mode .t{font-family:var(--display);font-size:20px;font-weight:600;line-height:1.05;color:var(--ink)}
.mode .s{font-size:12.5px;line-height:1.4;color:var(--ink2)}
.mode.on{background:var(--panel2);border-color:var(--gold);box-shadow:0 0 0 2px var(--gold),0 10px 30px var(--shadow);transform:translateY(-4px) scale(1.02)}
.mode.on svg{stroke:var(--gold)}.mode.on .t{color:var(--gold)}
.mode.locked{background:var(--felt2);border-style:dashed;opacity:.7;cursor:default}
.mode.locked .band{filter:grayscale(1) opacity(.55)}
.mode.locked .t{color:var(--ink2)}.mode.locked .s{color:var(--mute)}.mode.locked svg{stroke:var(--mute)}
@media (hover:hover){.mode:not(.locked):not(.on):hover{transform:translateY(-2px);box-shadow:0 10px 24px var(--shadow2);border-color:var(--gold2)}}
/* the display case is full width (spec 2026-09-19, "the .decks two-column grid becomes one column") -- a case
   needs the room, and the matchup strip above it spans the same column. */
.decks{display:grid;grid-template-columns:1fr;gap:18px}
.dbcard{position:relative;width:72px;height:100px;flex:none;border-radius:4px;overflow:hidden;background:var(--felt2);border:1px solid var(--line2)}
.dbcard img{width:100%;height:100%;object-fit:cover;display:block}
.dbcard .fallback{display:none;position:absolute;inset:0;align-items:center;justify-content:center;text-align:center;
  font-family:var(--display);font-size:11px;padding:4px;color:var(--ink2);background:var(--panel2);box-sizing:border-box}
.dbcard.noimg img{display:none}.dbcard.noimg .fallback{display:flex}
/* a <details> inside any panel (the deck fields' "paste a list", the options step's "advanced") -- one rule for
   every caller, including the ladder's own paste toggle (its old, dead ".queue details" duplicate is gone, see
   above). */
.panel details{margin:6px 0}
.panel details summary{cursor:pointer;color:var(--ink2);font-size:12px}
/* Lukas 2026-09-18 (site pages spec section 2 "The site pages on a phone"): the shell's one phone breakpoint,
   grown from the original two rules (body padding, admin table scroll) to cover the bar/head, the lobby grids and
   the funnel/mode-row/games-in-progress phone layouts from section 3. */
@media (max-width:640px){
  .shell .body{padding:20px 16px 28px}
  .lobby.users table{display:block;overflow-x:auto}
  .shell .bar{padding:0 16px;height:48px}
  .brand-t{display:none}   /* the mark alone still reads at 48px; the wordmark wraps to three lines below it */
  .who .me b,.rolebadge{display:none}   /* the avatar + chevron still open the same account menu */
  .nav a{padding:0 12px}
  .head{flex-direction:column;align-items:flex-start;gap:4px}
  .head h1{font-size:28px}
  .decks{grid-template-columns:1fr}
  .modes{grid-template-columns:1fr}   /* the funnel below restyles .mode itself into a row */
  .panel{padding:14px}
  .inline{flex-wrap:wrap}
  .inline .btn{width:100%}
  /* Fable render round 1 (2026-09-18): ten open rows at three lines + a full-width button each buried the funnel
     under screens of scroll -- two lines max (who+chips over mode/turn, ago pinned top-right), no button (the
     row is already the <a>, a tap returns to the game like the button did). */
  .resume-row{display:grid;grid-template-columns:1fr auto auto;grid-template-areas:"who ago x" "meta ago x";column-gap:8px;row-gap:2px;padding:10px 0}
  .resume-row .resume-who{grid-area:who}
  .resume-row .resume-meta{grid-area:meta}
  .resume-row .resume-ago{grid-area:ago;align-self:start}
  .resume-row .resume-x{grid-area:x;align-self:center}
  .resume-row .btn{display:none}
  .steps-nav{display:flex;gap:6px;overflow-x:auto}
  .step:not(.cur){display:none}
  /* the floating Feedback button sits fixed bottom-right at 44px -- padding-right clears Next from under it */
  .step-btns{display:flex;padding-right:60px}
  .mode{flex-direction:row;align-items:center;min-height:0;padding:12px 14px;gap:12px}
  .mode .t{font-size:17px}
  .mode .s{font-size:12px}
  /* the row layout has no room for a full-bleed top band -- it shrinks to a small square badge on the left,
     the same spot the bare icon held before this card-table pass. */
  .mode .band{width:44px;height:44px;margin:0;flex:none;border-radius:4px}
  .mode .band .disc{width:28px;height:28px}
  /* fix round 1 (R5, reviewer A5 confirmed live): six equal fixed-layout columns at 390px left the Player cell
     with no room -- the avatar showed, the name clipped to nothing and the country code overflowed into Rating.
     Games drops on the phone (Rating/Streak/+/- carry the story).
     fix round 2 (measured on the phone, 390px, table 330px wide, after the em-width round): `em` widths resolve
     against the th's own ~19px display font, not the table's -- the numeric columns ate the table (# 53px,
     Player 73px, Rating 72px, Streak 66px, +/- 66px) and .pname shrank to 0 (avatar 24 + gap 8 + cc 11 + padding
     20 = 63 of the 73). Real px instead, and tighter cell padding, so Player lands ~132px (name ~69px). */
  table.lb th:nth-child(4),table.lb td:nth-child(4){display:none}
  table.lb th,table.lb td{padding:8px 6px;box-sizing:border-box}   /* Fable render: the px widths are border-box, else the 12px of padding rides on top and the name is 13px wide */
  table.lb th:first-child,table.lb td:first-child{width:36px}
  table.lb th:nth-child(3),table.lb td:nth-child(3){width:60px}
  table.lb th:nth-child(5),table.lb td:nth-child(5){width:52px}
  table.lb th:nth-child(6),table.lb td:nth-child(6){width:50px}
}
/* phone redesign spec (2026-09-23) section 2 "the site shell": the top .nav pills (Lobby/Ladder/Decks/Replays)
   are what pushes every logged-in page past 390px wide (four ~70-90px pills, plus the brand and the account
   menu, never fit) -- the actual overflow source the spec names. They move to a fixed bottom tab bar instead,
   icon over a short label (the same stroke-icon style as every other shell/board glyph), and the top bar keeps
   just the brand mark + admin cog + account menu (already true at <=640px above; this widens it to every phone
   width, `phone()`'s own 899px breakpoint, and covers the actual removal). Placed AFTER the <=640px block above
   so its .shell .body padding-bottom (clearing the new bar) wins the cascade at every width <=640px too, not
   just 641-899. */
@media (max-width:899px){
  .shell .nav{display:none}
  .botnav{position:fixed;left:0;right:0;bottom:0;z-index:25;display:flex;height:56px;padding-bottom:env(safe-area-inset-bottom);
    box-sizing:content-box;background:var(--felt2);border-top:1px solid var(--line)}
  .botnav a{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
    color:var(--ink2);text-decoration:none;font-family:var(--display);font-size:11px;letter-spacing:.04em;text-transform:uppercase}
  .botnav a svg{width:20px;height:20px;stroke:currentColor}
  .botnav a.on{color:var(--gold)}
  .shell .body{padding-bottom:calc(28px + 56px + env(safe-area-inset-bottom))}
  .fab{bottom:calc(22px + 56px + env(safe-area-inset-bottom))}
}
/* phone redesign spec (2026-09-23) section 2: "at <=480px hide the Streak and +/- columns; rank, player, rating
   stay" -- additive to the <=640px block above (which already drops Games there and width-tunes these two); at
   <=480 they go from tuned-width to gone outright. Rank/Player/Rating are untouched at any width. */
@media (max-width:480px){
  table.lb th:nth-child(5),table.lb td:nth-child(5){display:none}
  table.lb th:nth-child(6),table.lb td:nth-child(6){display:none}
}
/* ==== client pages (2026-10-14): the shell as a game client's bar, and Ladder, Replays, Profile, Decks as its screens.
   Visual language of the home screen (arena floor under a dark wash, rounded plates, gold glow). Only static glows;
   the one motion is a plate rising once on arrival (opacity + transform), off under html.nofx and reduced motion.
   Text on plates uses --ink / --ink2 only (--mute is under 4.5:1 on the light plates). Phone rules sit in the two
   media blocks at the end of this section, ahead of the board's own phone block. */
.shell{--cp-plate:color-mix(in srgb,var(--panel) 84%,transparent);--cp-plate2:color-mix(in srgb,var(--panel2) 90%,transparent);--cp-edge:var(--line);--cp-acc:var(--gold);--cp-hi:inset 0 1px 0 rgba(255,255,255,.05);--cp-lift:0 12px 30px var(--shadow2);--cp-win:#5fb36a;--cp-win-ink:#08170b;
  --cp-wash:linear-gradient(180deg,rgba(7,10,18,.68),rgba(7,10,18,.88) 55%,rgba(7,10,18,.96)),url(/static/arena-floor.webp) center 34%/cover}
:root[data-theme="pokeball"] .shell{--cp-plate:#ffffff;--cp-plate2:#f4f1e9;--cp-edge:#e3ded3;--cp-acc:#b12e23;--cp-hi:inset 0 1px 0 #fff;--cp-lift:0 12px 28px rgba(27,29,34,.16);--cp-win:#2f8f3c;--cp-win-ink:#fff;
  --cp-wash:linear-gradient(180deg,rgba(244,246,250,.78),rgba(244,246,250,.92) 70%),url(/static/arena-floor-light.webp) center 34%/cover}
/* the backdrop is one static fixed layer of its own (not a fixed background on the page, which is painted again under every moving box); the home screen keeps its own */
.shell:not(:has(.body.athome))::before{content:"";position:fixed;inset:0;z-index:0;background:var(--cp-wash);pointer-events:none}
.shell>.body,.shell>.guestbar,.shell>.top-rule{position:relative}
/* the bar */
.shell .bar{height:68px;padding:0 32px;gap:26px;justify-content:flex-start;position:relative;z-index:5;border-bottom:1px solid var(--line2);
  background:linear-gradient(180deg,color-mix(in srgb,var(--felt2) 97%,transparent),color-mix(in srgb,var(--felt) 94%,transparent));box-shadow:0 10px 30px rgba(0,0,0,.38)}
:root[data-theme="pokeball"] .shell .bar{background:#fff;box-shadow:0 8px 24px rgba(27,29,34,.10)}
.shell .brand{font-size:20px;letter-spacing:.07em}
.shell .brand svg{width:30px;height:30px;filter:none}
.shell .nav{align-self:stretch;align-items:stretch;margin:0 auto 0 14px;padding:0;border:0;gap:4px}
.shell .nav a{position:relative;display:flex;align-items:center;gap:10px;height:auto;padding:0 22px;border:0;border-radius:0;background:transparent;
  font-family:var(--display);font-size:20px;font-weight:700;letter-spacing:.04em;text-transform:none;color:var(--ink2);text-decoration:none;
  transition:transform .15s ease,color .15s ease,background .15s ease}
.shell .nav a svg{width:26px;height:26px;flex:none;transition:filter .15s ease}
.shell .nav a:hover{background:transparent;color:var(--ink);border:0;transform:translateY(-2px)}
.shell .nav a.on{background:linear-gradient(180deg,transparent 30%,color-mix(in srgb,var(--gold) 16%,transparent));color:var(--ink);border:0}
.shell .nav a.on svg{color:var(--gold)}
.shell .nav a.on::after{content:"";position:absolute;left:12px;right:12px;bottom:0;height:4px;border-radius:4px 4px 0 0;background:var(--gold);box-shadow:0 0 16px var(--glow),0 0 4px var(--gold)}
:root[data-theme="pokeball"] .shell .nav a.on{color:var(--cp-acc);background:linear-gradient(180deg,transparent 30%,rgba(216,56,43,.09))}
:root[data-theme="pokeball"] .shell .nav a.on::after{background:var(--red);box-shadow:0 0 12px rgba(216,56,43,.45)}
:root[data-theme="pokeball"] .shell .nav a.on svg{color:var(--red)}
.shell .who{gap:14px;margin-left:auto}
.shell .menu.acct>summary{gap:10px}
.shell .menu.admin>summary{width:42px;height:42px;justify-content:center;border-radius:50%;border:1px solid var(--line2);background:var(--cp-plate)}
.cp-plate{display:flex;align-items:center;gap:12px;padding:6px 16px 6px 7px;border-radius:999px;border:1px solid var(--line2);background:var(--cp-plate);
  transition:border-color .15s ease,box-shadow .15s ease,transform .15s ease}
.shell .menu.acct:hover .cp-plate,.shell .menu.acct[open] .cp-plate{border-color:var(--gold);box-shadow:0 0 20px var(--glow);transform:translateY(-1px)}
.cp-plate-t{display:flex;flex-direction:column;gap:3px;line-height:1.05}
.cp-plate-t b{font-family:var(--display);font-size:18px;font-weight:700;color:var(--ink)}
.cp-plate-t>span{display:flex;align-items:center;gap:9px;font-size:13px;color:var(--ink2)}
.cp-plate-t .rolebadge{font-size:11px;letter-spacing:.04em;text-transform:none;font-weight:600;padding:1px 8px;border-radius:999px}
.cp-plate-t em{font-style:normal;font-family:var(--display);font-weight:700;font-size:15px;color:var(--cp-acc)}
/* avatar: the name's initial in a ring of the name's own hue */
.cp-ring{--hue:210;display:inline-grid;place-items:center;flex:none;width:44px;height:44px;box-sizing:border-box;padding:3px;border-radius:50%;
  background:conic-gradient(from 200deg,hsl(var(--hue) 85% 64%),hsl(calc(var(--hue) + 55) 80% 46%),hsl(var(--hue) 85% 64%));box-shadow:0 0 16px hsl(var(--hue) 80% 55%/.32)}
.cp-av{display:grid;place-items:center;width:100%;height:100%;border-radius:50%;background:var(--panel);color:var(--ink);font-family:var(--display);font-weight:700;font-size:20px;line-height:1}
:root[data-theme="pokeball"] .cp-av{background:#fff}
.cp-ring.sm{width:38px;height:38px;padding:2px}.cp-ring.sm .cp-av{font-size:17px}
.cp-ring.xl{width:72px;height:72px;padding:3px}.cp-ring.xl .cp-av{font-size:32px}
.cp-ring.xxl{width:108px;height:108px;padding:4px}.cp-ring.xxl .cp-av{font-size:50px}
/* page frame, title, shared plates */
.shell .body:has(.cp-page){padding-top:30px}
.cp-page{width:100%;max-width:1240px;margin:0 auto;display:flex;flex-direction:column;gap:24px;min-width:0}
.cp-head{display:flex;align-items:flex-end;justify-content:space-between;gap:24px}
.cp-head h1{font-size:48px;line-height:1;letter-spacing:.01em;text-shadow:0 2px 28px rgba(0,0,0,.55)}
:root[data-theme="pokeball"] .cp-head h1{text-shadow:none}
.cp-head p{margin:10px 0 0;font-size:15px;line-height:1.45;color:var(--ink2);max-width:64ch}
.cp-head .btn{box-sizing:border-box;height:50px;padding:0 34px;font-size:18px;flex:none;box-shadow:0 0 24px var(--glow);transition:transform .15s ease,box-shadow .15s ease,filter .15s ease}
.cp-head .btn:hover{transform:translateY(-2px);box-shadow:0 8px 30px var(--glow)}.cp-head .btn:active{transform:translateY(1px)}
.cp-h{font-family:var(--display);font-size:24px;font-weight:700;letter-spacing:.02em;margin:0 0 12px;color:var(--ink);display:flex;align-items:baseline;gap:10px}
.cp-h span{font-size:16px;font-weight:600;color:var(--ink2)}
.cp-empty{display:flex;flex-direction:column;align-items:center;text-align:center;gap:12px;padding:56px 24px;border-radius:20px;border:1px solid var(--cp-edge);background:var(--cp-plate);box-shadow:var(--cp-hi),var(--cp-lift)}
.cp-empty>svg{width:60px;height:60px;padding:18px;box-sizing:content-box;border-radius:50%;color:var(--gold);border:1.5px solid var(--gold);box-shadow:0 0 30px var(--glow)}
.cp-empty h2{margin:6px 0 0;font-family:var(--display);font-size:30px}
.cp-empty p{margin:0 0 8px;max-width:46ch;font-size:16px;line-height:1.5;color:var(--ink2)}
.cp-empty .btn{height:52px;padding:0 40px;font-size:19px;box-shadow:0 0 26px var(--glow)}
.cp-none{padding:26px;text-align:center;color:var(--ink2);font-size:15px}
/* a plate rising once */
.cp-in{animation:cp-rise .5s cubic-bezier(.2,.8,.3,1) backwards;animation-delay:calc(var(--i,0) * 50ms)}
@keyframes cp-rise{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
html.nofx .cp-in{animation:none}
@media (prefers-reduced-motion:reduce){.cp-in{animation:none}.cp-plate,.cp-lrow,.cp-game,.cp-opt,.cp-pl{transition:none!important}}
/* ladder */
.cp-seg{display:grid;grid-template-columns:1fr 1fr;gap:6px;padding:6px;width:min(100%,560px);box-sizing:border-box;border-radius:18px;border:1px solid var(--cp-edge);background:var(--cp-plate);box-shadow:var(--cp-hi)}
.cp-seg button{height:56px;padding:0;border-radius:12px;border:1px solid transparent;background:transparent;font-family:var(--display);font-size:23px;font-weight:700;letter-spacing:.03em;color:var(--ink2);
  transition:transform .15s ease,background .15s ease,color .15s ease,box-shadow .15s ease}
.cp-seg button:hover{color:var(--ink);transform:translateY(-1px);border-color:var(--line2)}
.cp-seg button:active{transform:translateY(1px)}
.cp-seg button.on{background:linear-gradient(180deg,color-mix(in srgb,var(--gold) 88%,#fff),var(--gold2));color:var(--on-gold);border-color:var(--gold);box-shadow:0 0 24px var(--glow)}
.cp-top{display:grid;grid-template-columns:minmax(300px,400px) minmax(0,1fr);gap:26px;align-items:end}
.cp-top.solo{grid-template-columns:minmax(0,520px)}
.cp-me{display:flex;flex-direction:column;gap:18px;padding:22px;border-radius:20px;border:1px solid var(--gold);background:linear-gradient(160deg,color-mix(in srgb,var(--gold) 14%,var(--panel)),var(--cp-plate) 60%);box-shadow:var(--cp-hi),0 0 34px color-mix(in srgb,var(--gold) 22%,transparent),var(--cp-lift)}
.cp-me-top{display:flex;align-items:center;gap:16px}
.cp-me-n{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}
.cp-me-n span{font-size:14px;color:var(--ink2)}
.cp-me-n b{font-family:var(--display);font-size:30px;line-height:1.05;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cp-me-n em{font-style:normal;font-size:14px;color:var(--cp-acc);font-weight:600}
.cp-me-rk{font-family:var(--display);font-weight:800;font-size:52px;line-height:1;color:var(--cp-acc);text-shadow:0 0 26px var(--glow)}
.cp-me-st{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.cp-me-st span{display:flex;flex-direction:column;gap:2px;padding:10px 12px;border-radius:12px;background:var(--cp-plate2);font-size:13px;color:var(--ink2)}
.cp-me-st b{font-family:var(--display);font-size:28px;font-weight:700;line-height:1.05;color:var(--ink)}
.cp-me-note{margin:0;font-size:14px;line-height:1.45;color:var(--ink2)}
.cp-pod{display:grid;grid-template-columns:1fr 1.18fr 1fr;gap:16px;align-items:end;padding-top:16px}
.cp-pl{--tier:#c9a227;position:relative;display:flex;flex-direction:column;align-items:center;gap:6px;min-width:0;padding:26px 12px 18px;text-align:center;border-radius:20px;border:1px solid var(--tier);
  background:linear-gradient(180deg,color-mix(in srgb,var(--tier) 20%,var(--panel)),var(--cp-plate) 70%);box-shadow:var(--cp-hi),0 0 26px color-mix(in srgb,var(--tier) 26%,transparent),var(--cp-lift);transition:transform .15s ease,box-shadow .15s ease}
.cp-pl:hover{transform:translateY(-3px);box-shadow:var(--cp-hi),0 0 36px color-mix(in srgb,var(--tier) 45%,transparent),var(--cp-lift)}
.cp-pl.p1{padding:40px 14px 26px;--tier:#e0b52e}.cp-pl.p2{--tier:#9aa4ad}.cp-pl.p3{--tier:#b87333}
.cp-pl.you{outline:2px solid var(--gold);outline-offset:3px}
.cp-medal{position:absolute;top:-16px;left:50%;translate:-50% 0;display:grid;place-items:center;width:34px;height:34px;border-radius:50%;background:var(--tier);color:#1a1405;font-family:var(--display);font-weight:800;font-size:19px;box-shadow:0 0 18px color-mix(in srgb,var(--tier) 60%,transparent)}
.cp-pl.p1 .cp-medal{width:42px;height:42px;font-size:23px;top:-20px}
.cp-pn{font-family:var(--display);font-size:26px;line-height:1.05;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cp-pc,.cp-pg{font-size:13px;color:var(--ink2)}
.cp-pr{font-size:42px;font-weight:700;line-height:1;color:var(--cp-acc)}
.cp-pl.p1 .cp-pr{font-size:50px}
.cp-filters{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.cp-find{position:relative;flex:1;min-width:200px;margin:0}
.cp-find svg{position:absolute;left:14px;top:50%;translate:0 -50%;color:var(--ink2);pointer-events:none}
.cp-find input{width:100%;height:46px;box-sizing:border-box;padding:0 14px 0 42px;border-radius:12px;border:1px solid var(--cp-edge);background:var(--cp-plate);color:var(--ink);font-size:15px}
.cp-filters select{height:46px;padding:0 14px;border-radius:12px;border:1px solid var(--cp-edge);background:var(--cp-plate);color:var(--ink);font-size:15px}
.cp-find input:focus,.cp-filters select:focus{outline:none;border-color:var(--gold);box-shadow:0 0 0 3px color-mix(in srgb,var(--gold) 25%,transparent)}
.cp-list{display:flex;flex-direction:column;gap:6px}
.cp-lbody{display:flex;flex-direction:column;gap:6px}
.cp-lh,.cp-lrow{display:grid;grid-template-columns:70px minmax(0,1fr) 110px 110px 150px 90px;align-items:center;gap:14px;padding:0 22px}
.cp-lh{height:26px;font-size:14px;color:var(--ink2)}
.cp-lh span:last-child,.cp-lrow>span:last-child{text-align:right}
.cp-lrow{min-height:66px;border-radius:14px;border:1px solid transparent;background:var(--cp-plate);box-shadow:var(--cp-hi);transition:transform .15s ease,border-color .15s ease,box-shadow .15s ease}
.cp-lbody>.cp-lrow:nth-child(even){background:var(--cp-plate2)}
.cp-lrow:hover{transform:translateY(-2px);border-color:var(--line2);box-shadow:var(--cp-hi),var(--cp-lift)}
.cp-lrow.you{border-color:var(--gold);background:linear-gradient(90deg,color-mix(in srgb,var(--gold) 16%,var(--panel)),var(--cp-plate) 70%)}
.cp-rk{font-family:var(--display);font-size:32px;font-weight:700;color:var(--ink2);line-height:1}
.cp-lp{display:flex;align-items:center;gap:14px;min-width:0}
.cp-ln{display:flex;flex-direction:column;gap:1px;min-width:0}
.cp-ln b{font-family:var(--display);font-size:22px;font-weight:700;line-height:1.1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cp-ln span{font-size:13px;color:var(--ink2)}
.cp-lr{font-size:30px;font-weight:700;color:var(--cp-acc);line-height:1}
.cp-lg,.cp-ls{font-size:15px;color:var(--ink2)}
.cp-ld{font-family:var(--display);font-size:20px;font-weight:700;color:var(--ink2)}
.cp-ld.up{color:var(--cp-win)}.cp-ld.down{color:var(--red)}
:root[data-theme="pokeball"] .cp-ld.down{color:#a8261f}
/* replays */
.cp-tally{display:flex;gap:10px}
.cp-tally span{display:flex;flex-direction:column;align-items:center;min-width:84px;padding:10px 16px;border-radius:14px;border:1px solid var(--cp-edge);background:var(--cp-plate);font-size:13px;color:var(--ink2);box-shadow:var(--cp-hi)}
.cp-tally b{font-family:var(--display);font-size:30px;line-height:1.05;color:var(--ink)}
.cp-tally .won b{color:var(--cp-win)}.cp-tally .lost b{color:var(--red)}
:root[data-theme="pokeball"] .cp-tally .lost b{color:#a8261f}
.cp-hist{display:flex;flex-direction:column;gap:8px}
.cp-day{margin:14px 0 2px;font-family:var(--display);font-size:20px;font-weight:600;color:var(--ink2)}
.cp-day:first-child{margin-top:0}
.cp-game{--rc:var(--mute);--rt:var(--ink);position:relative;display:grid;grid-template-columns:72px minmax(0,1fr) minmax(0,1.2fr) 200px 140px;align-items:center;gap:20px;min-height:92px;padding:10px 24px 10px 12px;box-sizing:border-box;overflow:hidden;
  border-radius:16px;border:1px solid var(--cp-edge);background:var(--cp-plate);box-shadow:var(--cp-hi);color:var(--ink);text-decoration:none;transition:transform .15s ease,border-color .15s ease,box-shadow .15s ease}
.cp-game:nth-child(even){background:var(--cp-plate2)}
.cp-game:hover{transform:translateY(-2px);border-color:var(--rc);box-shadow:var(--cp-hi),0 0 22px color-mix(in srgb,var(--rc) 35%,transparent),var(--cp-lift)}
.cp-game:active{transform:translateY(0)}
.cp-game:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.cp-game.win{--rc:var(--cp-win);--rt:var(--cp-win-ink)}.cp-game.loss{--rc:#d4493a;--rt:#fff}.cp-game.none{--rc:var(--line2)}
:root[data-theme="pokeball"] .cp-game.loss{--rc:#c9302c}
.cp-res{display:grid;place-items:center;width:64px;height:64px;border-radius:14px;font-family:var(--display);font-size:38px;font-weight:800;line-height:1;color:var(--rt);
  background:linear-gradient(160deg,color-mix(in srgb,var(--rc) 82%,#fff),var(--rc));box-shadow:0 0 20px color-mix(in srgb,var(--rc) 40%,transparent)}
.cp-game.none .cp-res{background:var(--line2);box-shadow:none;color:var(--ink)}
.cp-side{display:flex;align-items:center;gap:14px;min-width:0}
.cp-side.them::before{content:"vs";font-family:var(--display);font-size:16px;font-weight:700;color:var(--ink2);margin-right:2px}
.cp-th{position:relative;flex:none;width:50px;aspect-ratio:5/7;border-radius:5px;overflow:hidden;background:linear-gradient(160deg,var(--t-Colorless),color-mix(in srgb,var(--t-Colorless) 55%,#000));box-shadow:0 5px 14px rgba(0,0,0,.45)}
.cp-th img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
.cp-st{display:flex;flex-direction:column;gap:2px;min-width:0}
.cp-st b{font-family:var(--display);font-size:20px;font-weight:700;line-height:1.1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cp-st span{font-size:14px;color:var(--ink2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cp-gm{display:flex;flex-direction:column;gap:4px;font-size:14px;color:var(--ink2)}
.cp-mode{display:flex;align-items:center;gap:8px;font-size:15px;font-weight:500;color:var(--ink)}
.cp-mode svg{width:20px;height:20px;flex:none;color:var(--cp-acc)}
.cp-chip{margin-left:8px;padding:1px 9px;border-radius:999px;border:1px solid var(--line2);font-size:12px;color:var(--ink2)}
.cp-when{text-align:right;font-size:15px;color:var(--ink2)}
/* profile */
.cp-prof{display:grid;grid-template-columns:380px minmax(0,1fr);gap:30px;align-items:start}
.cp-pcard{position:sticky;top:20px;display:flex;flex-direction:column;gap:18px;padding:26px 22px;border-radius:22px;border:1px solid var(--cp-edge);background:linear-gradient(180deg,color-mix(in srgb,var(--gold) 12%,var(--panel)),var(--cp-plate) 45%);box-shadow:var(--cp-hi),var(--cp-lift)}
.cp-pc-top{display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center}
.cp-pc-top .nm{font-family:var(--display);font-size:36px;font-weight:700;line-height:1.05;max-width:100%;overflow:hidden;text-overflow:ellipsis}
.cp-pc-top .sub{font-size:14px;color:var(--ink2)}
.cp-pc-top .rolebadge{display:inline-block;font-size:12px;letter-spacing:.03em;text-transform:none;font-weight:600;padding:2px 12px;border-radius:999px}
.cp-rates{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.cp-rate{display:flex;flex-direction:column;gap:3px;padding:14px;border-radius:14px;background:var(--cp-plate2);border:1px solid var(--cp-edge);font-size:13px;color:var(--ink2)}
.cp-rate-k{font-size:14px;font-weight:600;color:var(--ink)}
.cp-rate b{font-family:var(--display);font-size:38px;font-weight:700;line-height:1.05;color:var(--cp-acc)}
.cp-rate em{font-style:normal;color:var(--ink2)}
.cp-note{margin:0;font-size:13px;line-height:1.5;color:var(--ink2)}
.cp-note.warn{color:var(--red);padding:0 4px}
.cp-set{display:flex;flex-direction:column;gap:28px;min-width:0}
.cp-opts{display:flex;flex-direction:column;gap:6px}
.cp-opt{display:flex;align-items:center;justify-content:space-between;gap:24px;margin:0;padding:16px 22px;border-radius:14px;border:1px solid transparent;background:var(--cp-plate);box-shadow:var(--cp-hi);cursor:pointer;transition:transform .15s ease,border-color .15s ease,box-shadow .15s ease}
.cp-opts>.cp-opt:nth-of-type(even){background:var(--cp-plate2)}
.cp-opt:hover{transform:translateY(-1px);border-color:var(--line2);box-shadow:var(--cp-hi),var(--cp-lift)}
.cp-opt-t{display:flex;flex-direction:column;gap:3px;min-width:0}
.cp-opt-t b{font-family:var(--display);font-size:21px;font-weight:700;letter-spacing:.01em}
.cp-opt-t span{font-size:14px;line-height:1.4;color:var(--ink2)}
.cp-sw{appearance:none;-webkit-appearance:none;position:relative;flex:none;width:56px;height:32px;margin:0;padding:0;border-radius:999px;border:1px solid var(--line2);background:var(--line);cursor:pointer;transition:background .2s ease,border-color .2s ease,box-shadow .2s ease}
.cp-sw::before{content:"";position:absolute;top:3px;left:3px;width:24px;height:24px;border-radius:50%;background:#fff;box-shadow:0 2px 6px rgba(0,0,0,.4);transition:transform .2s ease}
.cp-sw:checked{background:var(--gold);border-color:var(--gold);box-shadow:0 0 18px var(--glow)}
.cp-sw:checked::before{transform:translateX(24px)}
.cp-sw:focus-visible{outline:2px solid var(--hi);outline-offset:3px}
html.nofx .cp-sw::before,html.nofx .cp-sw{transition:none}
.cp-set .themes{gap:16px}
.cp-set .theme{padding:14px;border-radius:18px;border:1px solid var(--cp-edge);background:var(--cp-plate);box-shadow:var(--cp-hi);transition:transform .15s ease,border-color .15s ease,box-shadow .15s ease}
.cp-set .theme:hover{transform:translateY(-2px);border-color:var(--line2)}
.cp-set .theme.on{border-color:var(--gold);box-shadow:0 0 28px var(--glow),var(--cp-hi)}
.cp-set .theme .sw{height:120px;border-radius:12px}
.cp-set .theme .th .t{font-size:24px}
.cp-set .theme .th .c{font-size:14px;letter-spacing:0;text-transform:none;color:var(--ink2)}
.cp-set .theme.on .th .c{color:var(--cp-acc);font-weight:600}
.cp-set .theme .s{font-size:14px;color:var(--ink2)}
.cp-form{padding:20px 22px;border-radius:16px;background:var(--cp-plate);border-color:var(--cp-edge);box-shadow:var(--cp-hi)}
.cp-form label span{font-family:var(--body);font-size:14px;letter-spacing:0;text-transform:none;color:var(--ink2)}
.cp-form .inline{align-items:center}
.cp-sub{font-size:14px;color:var(--ink2)}.cp-sub.ok{color:var(--cp-win)}
/* decks */
.cp-decks{gap:18px}
.boxes.cp-mine{padding-top:70px;margin-top:-40px;grid-template-columns:repeat(auto-fill,150px);gap:24px 34px}
.cp-mine-empty{display:flex;align-items:center;gap:30px}
.boxes.cp-one{flex:none;width:150px;padding-top:0;margin-top:0}
.cp-mine-empty p{margin:0;max-width:34ch;font-size:16px;line-height:1.5;color:var(--ink2)}
/* the shelf: a wooden rail along the top, a lit felt below it, the boxes standing in rows */
.cp-shelf{position:relative;margin-top:30px;padding:34px 36px 40px;border-radius:20px;border:1px solid var(--line2);overflow:visible;
  background:radial-gradient(ellipse 55% 60% at 50% 0%,rgba(255,226,150,.2),transparent 72%),linear-gradient(180deg,#17261d,#101a15);box-shadow:inset 0 0 0 1px rgba(255,255,255,.04),0 20px 44px -18px #000a}
.cp-shelf::before{content:"";position:absolute;left:-1px;right:-1px;top:-1px;height:14px;border-radius:20px 20px 0 0;background:linear-gradient(180deg,#d9a766,#a9733a 60%,#7c5226);box-shadow:0 3px 10px rgba(0,0,0,.45)}
:root[data-theme="pokeball"] .cp-shelf{background:radial-gradient(ellipse 55% 60% at 50% 0%,#fff,transparent 72%),linear-gradient(180deg,#f1ede3,#e5dfd1);border-color:#d8d1c1;box-shadow:inset 0 0 0 1px rgba(255,255,255,.7),0 18px 36px -16px rgba(27,29,34,.3)}
.boxes.cp-row{margin-top:-20px;padding-top:62px}
/* 22 closed boxes each bobbing their cards forever cost a graphics core; on this page the bob runs only in the box that is open */
.cp-decks .dbox:not(.open) .cards.breathe{animation:none}
.cp-decks .dbox.new{border-radius:10px}
/* phone: at 899 the top nav is replaced by the bottom bar (arena.css above); at 640 the bar and every page compact */
@media (max-width:899px){
  .botnav{background:var(--felt2);box-shadow:0 -8px 24px rgba(0,0,0,.3)}
  .botnav a{font-size:12px;letter-spacing:0;text-transform:none;font-weight:600;position:relative}
  .botnav a.on::before{content:"";position:absolute;top:0;left:22%;right:22%;height:3px;border-radius:0 0 3px 3px;background:var(--gold);box-shadow:0 0 12px var(--glow)}
  .cp-top{grid-template-columns:1fr}
  .cp-prof{grid-template-columns:1fr}.cp-pcard{position:static}
  .cp-game{grid-template-columns:64px minmax(0,1fr) minmax(0,1fr) 150px}.cp-when{display:none}
}
@media (max-width:640px){
  .shell .bar{height:56px;padding:0 14px;gap:12px}
  .shell .brand svg{width:28px;height:28px}
  .cp-plate{padding:4px 8px 4px 4px}.cp-plate-t{display:none}
  .cp-ring{width:38px;height:38px}
  .shell .body:has(.cp-page){padding-top:18px}
  .cp-page{gap:18px}
  .cp-head{flex-direction:column;align-items:stretch;gap:14px}.cp-head h1{font-size:36px}.cp-head p{margin-top:6px;font-size:14px}
  .cp-head .btn{width:100%;height:48px}
  .cp-tally{width:100%}.cp-tally span{flex:1;min-width:0}
  .cp-seg button{height:50px;font-size:20px}
  .cp-me{padding:16px;gap:14px}.cp-me-n b{font-size:24px}.cp-me-rk{font-size:40px}.cp-me-st b{font-size:24px}
  .cp-pod{gap:8px}.cp-pl,.cp-pl.p1{padding:24px 6px 14px}.cp-pl.p1{padding-top:30px}.cp-pn{font-size:19px}.cp-pr,.cp-pl.p1 .cp-pr{font-size:28px}
  .cp-pl .cp-ring.xl{width:52px;height:52px}.cp-pl .cp-ring.xl .cp-av{font-size:23px}.cp-pc{display:none}
  .cp-lh{display:none}
  .cp-lh,.cp-lrow{grid-template-columns:34px minmax(0,1fr) 64px;gap:10px;padding:0 14px}
  .cp-lrow>span:nth-child(n+4){display:none}
  .cp-rk{font-size:24px}.cp-ln b{font-size:19px}.cp-lr{font-size:24px}
  .cp-game{grid-template-columns:54px minmax(0,1fr);gap:8px 12px;padding:12px;min-height:0}
  .cp-res{width:54px;height:54px;font-size:30px;grid-row:span 2}
  .cp-side.them{grid-column:2}.cp-gm{grid-column:1/-1;flex-direction:row;flex-wrap:wrap;gap:4px 14px;align-items:center}.cp-side.them::before{display:none}
  .cp-side:not(.them){grid-column:2}.cp-th{width:38px}
  .cp-prof{gap:20px}.cp-pcard{padding:20px 16px}.cp-rate b{font-size:32px}
  .cp-opt{padding:14px 16px;gap:14px}.cp-opt-t b{font-size:19px}
  .cp-set .theme .sw{height:84px}.cp-set .theme .th .t{font-size:20px}
  .boxes.cp-mine{grid-template-columns:repeat(auto-fill,118px);gap:20px 14px}
  .cp-mine-empty{flex-direction:column;align-items:flex-start;gap:0}.cp-mine-empty p{margin:0 0 20px}
  .cp-shelf{padding:14px 12px 24px;border-radius:16px}
  .cp-empty{padding:36px 16px}
}
/* ---- landing redesign 2026-09-16, rebuilt 2026-10-14 as a game client's title screen (Lukas): one viewport with the
   hero video, the pitch and one PLAY; the log-in card is a panel (dialog.lp-auth) over the dimmed page; the modes
   and the filmstrip below. Theme tokens only -- both felt and pokeball desks read correctly. The phone rules are in
   the ONE landing media block below (the board's phone block must stay the last max-width:899px block). ---- */
.hero{position:relative;display:flex;flex-direction:column;min-height:100vh;min-height:100svh;overflow:hidden}
.hero-bg{position:absolute;inset:0;overflow:hidden;z-index:0;pointer-events:none}
/* the hero video (2026-09-20): per-theme source picked in heroBg()/applyTheme(); the scrim (the page background token,
   --felt) is darkest at the left, where the pitch stands, and at the bottom edge, where it meets the page. */
/* the plate: the arena floor, a little larger than the hero so its slow drift never shows an edge (transform only) */
.hero-plate{position:absolute;inset:-5%;background:url(/static/arena-floor.webp) center 40%/cover,var(--felt);transform-origin:30% 50%}
html.nofx .hero-plate{animation:none}
@media (prefers-reduced-motion: reduce){.hero-plate{animation:none}}
.hero-scrim{position:absolute;inset:0;background:
  linear-gradient(90deg,color-mix(in srgb,var(--felt) 94%,transparent) 0%,color-mix(in srgb,var(--felt) 84%,transparent) 30%,color-mix(in srgb,var(--felt) 38%,transparent) 58%,color-mix(in srgb,var(--felt) 10%,transparent) 100%),
  linear-gradient(180deg,color-mix(in srgb,var(--felt) 45%,transparent) 0%,transparent 22%,transparent 58%,color-mix(in srgb,var(--felt) 88%,transparent) 88%,var(--felt) 100%)}
.hero .top-rule{position:absolute;top:0;left:0;right:0;z-index:3}
.lp-bar{position:relative;z-index:2;display:flex;align-items:center;gap:14px;height:72px;margin-top:3px;padding:0 56px;box-sizing:border-box}
.lp-bar .brand{margin-right:auto}
.lp-beta{font-family:var(--display);font-size:12px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--ink2);padding:3px 10px;border:1px solid var(--line2);border-radius:999px;background:color-mix(in srgb,var(--panel) 60%,transparent)}
.lp-btn{font-family:var(--display);font-size:15px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;height:38px;padding:0 20px;border-radius:999px;border:1px solid transparent;background:transparent;color:var(--ink);cursor:pointer}
.lp-btn:hover{color:var(--gold)}
.lp-btn.out{border-color:var(--line2);background:color-mix(in srgb,var(--panel) 60%,transparent)}
.lp-btn.out:hover{border-color:var(--gold)}
.lp-btn:focus-visible,.lp-more:focus-visible,.lp-play:focus-visible,.lp-modebtn:focus-visible,.lp-x:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
.landing{position:relative;z-index:1;flex:1;display:flex;align-items:center;padding:0 56px 56px;max-width:1400px;width:100%;box-sizing:border-box;margin:0 auto}
.landing .pitch{display:flex;flex-direction:column;align-items:flex-start;gap:16px;flex:0 0 40%;max-width:560px}
.landing h1{font-family:var(--display);font-size:clamp(52px,5.8vw,96px);line-height:.94;font-weight:700;margin:0;text-wrap:balance}
.landing .lede{font-size:19px;line-height:1.5;color:var(--ink2);max-width:430px}
.lp-cta{display:flex;flex-direction:column;align-items:flex-start;gap:14px;margin-top:14px}
/* PLAY: the home screen's pill (.home-play), the same look and size; the pulse is a second glow that only fades */
.lp-play{flex:none;position:relative;font-family:var(--display);font-size:34px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;padding:17px 66px;border-radius:999px;border:2px solid #fff0b8;color:#2a1c05;cursor:pointer;white-space:nowrap;
  background:linear-gradient(180deg,#ffe38f,#f2c14e 55%,#d79a25);box-shadow:0 8px 0 #8a5a10,0 0 44px rgba(242,193,78,.5);transition:transform .12s,box-shadow .12s}
.lp-play::after{content:"";position:absolute;inset:-2px;border-radius:inherit;pointer-events:none;opacity:0;will-change:opacity;box-shadow:0 8px 0 #8a5a10,0 0 72px rgba(242,193,78,.85);animation:lpplay 2.6s ease-in-out infinite}
.lp-play:hover::after,.lp-play:active::after{animation:none}
.lp-play:hover{transform:translateY(-2px);box-shadow:0 10px 0 #8a5a10,0 0 70px rgba(242,193,78,.85)}
.lp-play:active{transform:translateY(6px);box-shadow:0 2px 0 #8a5a10,0 0 30px rgba(242,193,78,.5)}
@keyframes lpplay{50%{opacity:1}}
/* the entrance: the pitch settles in once (opacity and transform only) */
.landing .pitch>*{animation:lpin .6s cubic-bezier(.2,.7,.2,1) both}
.landing .pitch>:nth-child(2){animation-delay:.08s}.landing .pitch>:nth-child(3){animation-delay:.18s}
@keyframes lpin{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
html.nofx .lp-play::after,html.nofx .landing .pitch>*{animation:none}
@media (prefers-reduced-motion: reduce){.lp-play::after,.landing .pitch>*{animation:none}.lp-play,.lp-modebtn{transition:none}.lp-view{transition:none}}
.lp-more{position:absolute;z-index:2;left:50%;bottom:14px;transform:translateX(-50%);width:44px;height:44px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--ink2);display:grid;place-items:center;cursor:pointer}
.lp-more:hover{color:var(--gold)}
/* the log-in card: a panel over the dimmed page, the client's frameless look (dark surface, a gold hairline) */
dialog.lp-auth{position:fixed;inset:0;margin:auto;width:min(400px,calc(100vw - 32px));max-height:calc(100svh - 32px);overflow:auto;padding:0;border:0;border-radius:14px;background:var(--panel);color:var(--ink);box-shadow:0 0 0 1px var(--line2),0 30px 80px rgba(0,0,0,.65)}
dialog.lp-auth::backdrop{background:radial-gradient(120% 95% at 50% 46%,rgba(7,11,22,.66),rgba(3,5,11,.9));-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}
dialog.lp-auth[open]{animation:lpin .22s ease-out both}
html.nofx dialog.lp-auth[open]{animation:none}
.lp-x{position:absolute;top:10px;right:10px;width:34px;height:34px;padding:0;border-radius:50%;border:1px solid var(--line2);background:transparent;color:var(--ink2);font-size:20px;line-height:1;cursor:pointer;z-index:1}
.lp-x:hover{color:var(--gold);border-color:var(--gold)}
.landing .login{position:relative;padding:30px 28px 22px;display:flex;flex-direction:column;gap:14px}
.landing .browser-note{margin:10px 0 0;font-size:12px;color:var(--mute);text-align:center}
.landing .login h2{font-family:var(--display);font-size:26px;font-weight:600;margin:0;letter-spacing:.02em}
.landing .login input,.landing .login select{height:44px;box-sizing:border-box;padding:0 12px;width:100%}
.login a{color:var(--gold)}
.landing .login input[type=checkbox]{width:16px;height:16px;margin:0;flex:none;accent-color:var(--gold)}
.landing .login .consent{display:flex;align-items:center;gap:10px;cursor:pointer}
.landing .login .btn{margin-top:6px;width:100%;box-sizing:border-box}
.landing .login .btn.quiet{background:transparent;color:var(--ink);border-color:var(--line2)}
.landing .login .btn.quiet:hover{border-color:var(--gold)}
.landing .login .note{font-size:12px;color:var(--mute);text-align:center}

/* GUEST MODE (Lukas 2026-09-22): the persistent bar under the top bar on every non-arena screen (shell()) --
   "Playing as guest -- create an account to keep your games, save decks and play ranked." */
.guestbar{display:flex;align-items:center;justify-content:center;gap:12px;flex-wrap:wrap;background:var(--panel2);border-bottom:1px solid var(--line2);padding:10px 16px;font-size:13px;color:var(--ink2);text-align:center}
.guestbar .btn{height:32px;padding:0 16px;font-size:13px}

section.features,section.community{position:relative;z-index:1;max-width:1400px;margin:0 auto;padding:88px 56px 0;box-sizing:border-box}
.features h2{font-family:var(--display);font-size:34px;font-weight:700;margin:0 0 26px;text-align:left}
/* the modes: the home screen's mode buttons (ring icon, name, one line), not links -- they only name the ways to play */
/* the filmstrip: one big 16:9 picture (opens the lightbox) over a row of five thumbnails */
.shell.lp>section{min-width:0;width:100%}
/* the hero's fan: five real cards standing on the Active spot's ring of light. Everything that moves moves by transform or
   opacity only (the client-cost rules of 2026-10-13): the deal-in, the idle float, the foil sheen (a static diagonal band
   whose transform slides across), the glow's breathing (opacity), the pointer tilt (rAF, transform). */
.lp-fan{--cw:clamp(140px,min(14.5vw,36vh),300px);--ped-a:#60c8ff;--ped-b:var(--gold);flex:1 1 0;min-width:0;align-self:stretch;position:relative;margin-left:2vw;margin-right:-1vw;perspective:1500px;perspective-origin:50% 38%;min-height:360px}
:root[data-theme="pokeball"] .lp-fan{--ped-a:var(--blue);--ped-b:var(--red)}
.lp-tilt{position:absolute;inset:0;transform-style:preserve-3d;transition:transform .3s ease-out;will-change:transform}
.lp-glow{position:absolute;left:50%;bottom:6%;width:calc(var(--cw)*3.9);height:calc(var(--cw)*2.3);margin-left:calc(var(--cw)*-1.95);border-radius:50%;pointer-events:none;
  background:radial-gradient(closest-side,color-mix(in srgb,var(--ped-a) 55%,transparent),color-mix(in srgb,var(--ped-b) 22%,transparent) 55%,transparent 100%);opacity:.85}
.lp-ped{position:absolute;left:50%;bottom:calc(var(--cw)*.05);width:calc(var(--cw)*3.3);aspect-ratio:1;margin-left:calc(var(--cw)*-1.65);border-radius:50%;pointer-events:none;transform:rotateX(74deg);transform-origin:50% 100%;
  border:3px solid var(--ped-a);box-shadow:0 0 28px var(--ped-a),inset 0 0 40px color-mix(in srgb,var(--ped-a) 50%,transparent);
  background:radial-gradient(closest-side,color-mix(in srgb,var(--ped-a) 30%,transparent),color-mix(in srgb,var(--ped-b) 12%,transparent) 70%,transparent)}
@keyframes lpped{from{opacity:0}}
.lp-ped{animation:lpped .7s ease both}
.lp-ped::before,.lp-ped::after{content:"";position:absolute;border-radius:50%;border:2px solid var(--ped-b)}
.lp-ped::before{inset:11%;opacity:.8}
.lp-ped::after{inset:26%;opacity:.5;border-style:dashed}
.lp-card{--tx:0;--ty:0;--tz:0px;--rz:0deg;--ry:0deg;--sc:1;position:absolute;left:50%;bottom:calc(var(--cw)*.42);width:var(--cw);aspect-ratio:63/88;margin-left:calc(var(--cw)/-2);
  transform:translate3d(calc(var(--cw)*var(--tx)),calc(var(--cw)*var(--ty)),var(--tz)) rotateY(var(--ry)) rotateZ(var(--rz)) scale(var(--sc));transform-origin:50% 100%;
  animation:lpdeal .75s cubic-bezier(.2,.8,.25,1) both;animation-delay:calc(.25s + var(--i)*.07s)}
.lp-card.c2{--tz:40px;z-index:3}
.lp-card.c1{--tx:-.7;--ty:.04;--tz:-70px;--rz:-8deg;--ry:14deg;--sc:.86;z-index:2}
.lp-card.c3{--tx:.7;--ty:.04;--tz:-70px;--rz:8deg;--ry:-14deg;--sc:.86;z-index:2}
.lp-card.c0{--tx:-1.3;--ty:.12;--tz:-170px;--rz:-15deg;--ry:22deg;--sc:.72;z-index:1}
.lp-card.c4{--tx:1.3;--ty:.12;--tz:-170px;--rz:15deg;--ry:-22deg;--sc:.72;z-index:1}
@keyframes lpdeal{from{opacity:0;transform:translate3d(0,calc(var(--cw)*.9),-260px) rotateY(0) rotateZ(0) scale(.5)}}
.lp-card-in{position:relative;width:100%;height:100%;border-radius:4.5%;overflow:hidden;border:1px solid rgba(255,255,255,.55);background:#0e1119;box-shadow:0 24px 40px -14px rgba(0,0,0,.7),0 0 0 1px rgba(0,0,0,.4);
  }
.lp-card-in img{display:block;width:100%;height:100%;object-fit:cover}
/* the foil sheen: one diagonal band, static; only its transform slides across, a card at a time */
.lp-card-in::after{content:"";position:absolute;inset:0;pointer-events:none;mix-blend-mode:screen;
  background:linear-gradient(115deg,transparent 38%,rgba(255,255,255,.34) 47%,rgba(150,225,255,.3) 53%,transparent 62%);
  transform:translateX(-130%);animation:lpsheen 1.6s ease-in-out 1 both;animation-delay:calc(1.2s + var(--i)*.25s)}
@keyframes lpsheen{from{transform:translateX(-130%)}to{transform:translateX(130%)}}
html.nofx .lp-glow,html.nofx .lp-ped,html.nofx .lp-card,html.nofx .lp-card-in,html.nofx .lp-card-in::after{animation:none}
html.nofx .lp-tilt{transition:none}
@media (prefers-reduced-motion: reduce){.lp-glow,.lp-ped,.lp-card,.lp-card-in,.lp-card-in::after{animation:none}.lp-tilt{transition:none}}
/* "Pick how you play": the home screen's mode list beside one big screen of the selected mode */
.lp-pick{display:grid;grid-template-columns:280px minmax(0,1fr);gap:26px;align-items:start}
.lp-modelist{display:flex;flex-direction:column;gap:10px}
.lp-modebtn{display:flex;align-items:center;gap:14px;padding:13px 16px;border-radius:14px;border:1px solid var(--line2);background:color-mix(in srgb,var(--panel) 78%,transparent);color:var(--ink);font:inherit;text-align:left;cursor:pointer;transition:transform .15s,border-color .15s,box-shadow .15s,background .15s}
.lp-modebtn b{font-family:var(--display);font-size:19px;font-weight:700;letter-spacing:.03em}
.lp-modebtn .hdisc{flex:none;width:42px;height:42px;border-radius:50%;display:grid;place-items:center;border:1.5px solid currentColor;color:var(--ink2)}
.lp-modebtn .hdisc svg{stroke:currentColor}
.lp-modebtn:hover{transform:translateX(4px);border-color:var(--gold)}
.lp-modebtn.on{border-color:var(--gold);background:linear-gradient(90deg,color-mix(in srgb,var(--gold) 30%,transparent),color-mix(in srgb,var(--panel) 80%,transparent) 75%);box-shadow:0 0 24px var(--glow)}
.lp-modebtn.on b,.lp-modebtn.on .hdisc{color:var(--gold)}
.lp-view{min-width:0;transition:opacity .2s ease}
/* light theme: the title screen on the pale desk -- a light veil over the light clip, the picker in white and red */
:root[data-theme="pokeball"] .hero-plate{background-image:url(/static/arena-floor-light.webp)}
:root[data-theme="pokeball"] .hero-scrim{background:linear-gradient(90deg,color-mix(in srgb,var(--felt) 88%,transparent) 0%,color-mix(in srgb,var(--felt) 58%,transparent) 30%,color-mix(in srgb,var(--felt) 6%,transparent) 58%,transparent 100%),linear-gradient(180deg,color-mix(in srgb,var(--felt) 40%,transparent) 0%,transparent 24%,transparent 62%,var(--felt) 100%)}
:root[data-theme="pokeball"] .lp-card-in{border-color:rgba(255,255,255,.9);box-shadow:0 26px 40px -16px rgba(27,29,34,.45),0 0 0 1px rgba(27,29,34,.18)}
:root[data-theme="pokeball"] .lp-modebtn{background:#fff;box-shadow:0 2px 10px -4px var(--shadow)}
:root[data-theme="pokeball"] .lp-modebtn.on{background:#fff;border-color:var(--gold);box-shadow:0 0 0 1px var(--gold),0 8px 22px -8px var(--glow)}
.lp-view.swap{opacity:0}
.lp-big{margin:0;border-radius:12px;overflow:hidden;border:1px solid var(--line2);padding:7px;box-sizing:border-box;background:#0a0d14;cursor:zoom-in;box-shadow:0 24px 60px -24px rgba(0,0,0,.7)}   /* a dark bezel: a game screen on any page, light or dark */
.lp-big img{display:block;width:100%;height:auto;aspect-ratio:16/9;object-fit:cover;border-radius:6px}
.lp-line{margin:14px 2px 0;min-height:1.5em;font-size:17px;line-height:1.5;color:var(--ink2)}
.shell.lp>section{min-width:0;width:100%}   /* a flex item of the column: without this the thumbnail row's width stretches the page on a phone */

/* gallery lightbox (Lukas 2026-09-16): native <dialog>, both themes via the tokens */
dialog.lightbox{padding:0;background:transparent;border:0;max-width:none;max-height:none;overflow:visible;width:fit-content}
dialog.lightbox img{display:block;width:auto;height:auto;max-width:min(1240px,94vw);max-height:84vh;border-radius:6px;box-shadow:0 30px 80px rgba(0,0,0,.6)}
dialog.lightbox::backdrop{background:rgba(10,12,16,.85);backdrop-filter:blur(4px)}
.lb-close{position:absolute;top:-18px;right:-18px;width:44px;height:44px;z-index:1;border-radius:50%;background:var(--panel);color:var(--ink);border:1px solid var(--line2);font-size:20px;line-height:1;display:flex;align-items:center;justify-content:center;cursor:pointer}
.lb-cap{margin-top:10px;text-align:center;font-family:var(--display);font-size:14px;color:var(--ink2)}
/* card zoom (2026-09-17 board-panel spec, laid out per Lukas's second review 09-17; single column since the
   2026-09-18 "one face" change -- Lukas: "we don't need a picture and text on the right side", bigFace() -- made
   bigFace return the LG picture OR the text face, never both). #cardzoom is the same dialog.lightbox, ONE column
   sized to its own content (no more fixed minmax(280px,340px) second track): .k is the header, the big picture
   (capped by height, not width, so it isn't cropped by the generic dialog.lightbox img rule -- #cardzoom-body's
   id beats that selector's specificity) or the text face sits under it, and the sheet toggle/attached-Energy
   line/sheet text stack below in plain block flow. Lukas's screenshot 09-18: the old grid's second column still
   reserved a 280-340px-wide, picture-tall empty box once there was nothing left to put beside the picture --
   plain flex-column flow can't leave a phantom column behind the way a grid track can. */
#cardzoom-body{display:flex;flex-direction:column;align-items:flex-start;gap:8px;width:max-content;max-width:min(960px,94vw);box-sizing:border-box;background:var(--panel);border-radius:6px;padding:16px 18px;box-shadow:0 30px 80px rgba(0,0,0,.6);max-height:86vh;overflow:auto}
/* INSPECTOR FAN OVERFLOW (Lukas 2026-09-18 evening, screenshot: a horizontal scrollbar under the big picture): the
   .att fan is absolutely positioned, so nothing in flow reserved its width -- with `.bigpic` sized to the picture
   alone, every peek past the picture's right edge overflowed #cardzoom-body (overflow:auto) into a scrollbar.
   Now the picture's width is ONE value, --pw, that the <img>, the peek and the reserved room all read:
   height-bound on desktop (72vh/540px, same numbers as before, converted to a width at the card's 220:307),
   width-bound where the dialog is narrower (the phone: the dialog's own max-width less its 18px padding, shared
   between the picture and the whole fan -- (1 + .14n) picture widths -- so any number of energies fits without
   a scrollbar; the picture simply gets a little smaller as the fan grows). --peek is 14% of --pw in px, and
   margin-right reserves the fan's full width in flow (bigFace, arena.js, writes --n-att on .bigpic), so
   #cardzoom-body{width:max-content} widens to hold picture + fan. --reach is the fan's total peek (the Tool is the corner mini, not part of the fan). box-sizing:border-box on #cardzoom-body (same
   fix, phone render): its 94vw max-width used to exclude the 18px padding, so the dialog measured 403px on a 390px
   phone and clipped its own close button -- now the max-width is the whole box, and the `- 36px` here is exact. The phone block's old
   `.bigpic img{height:auto;max-width:100%}` override is gone: this single rule is already width-bound there. */
#cardzoom-body .bigpic{width:auto;margin:0;--reach:calc(.14 * var(--n-att,0));--pw:min(calc(min(72vh,540px) * .7166),calc((min(960px,94vw) - 36px) / (1 + var(--reach))));--peek:calc(var(--pw) * .14);margin-right:calc(var(--pw) * var(--reach))}
#cardzoom-body .bigpic img{width:var(--pw);max-width:none;height:auto}
#cardzoom-body .card.big{width:min(420px,90vw);max-height:72vh;overflow:auto}
/* the sheet toggle reads as a small, quiet text link under the picture now, not a full button beside it. */
#cardzoom-body .sheetbtn{display:block;background:transparent;border-color:transparent;color:var(--ink2);font-size:12px;padding:4px 0}
#cardzoom-body .sheetbtn:hover{color:var(--gold);border-color:transparent}


.community{display:flex;align-items:center;justify-content:space-between;gap:32px;padding-bottom:8px}
.community p{margin:0;font-family:var(--display);font-size:30px;font-weight:600;line-height:1.2;color:var(--ink);max-width:760px;text-wrap:balance}

.landing-foot{position:relative;z-index:1;max-width:1200px;margin:56px auto 0;padding:0 56px 40px;text-align:center;font-size:12px;color:var(--mute);box-sizing:border-box}

/* the landing's phone rules (under 900px). Kept in this block, earlier in the file than the board's own
   max-width:899px block, which must stay the LAST one (tests/web/test_static.py reads it by position). */
@media (max-width:899px){
  .lp-bar{padding:0 16px;height:60px}
  .lp-beta,.lp-btn.out{display:none}
  .landing{padding:0 16px 64px;flex-direction:column;align-items:stretch;justify-content:center;gap:28px}
  .landing .pitch{flex:none;max-width:none;width:100%;gap:12px}
  .landing h1{font-size:clamp(44px,13vw,64px)}
  .landing .lede{font-size:17px}
  .lp-cta{width:100%;align-items:stretch;text-align:center}
  .lp-play{width:100%;font-size:30px;padding:16px 20px}
  .hero{min-height:0}
  .landing{padding-top:24px;padding-bottom:8px}
  .lp-more{display:none}
  .lp-fan{flex:none;margin:0;width:100%;height:min(88vw,370px);min-height:0;--cw:min(36vw,160px);perspective:900px}
  .lp-card.c0,.lp-card.c4{display:none}
  .lp-card.c1{--tx:-.72;--rz:-9deg}.lp-card.c3{--tx:.72;--rz:9deg}
  .lp-ped{width:calc(var(--cw)*3.1);margin-left:calc(var(--cw)*-1.55)}
  .lp-tilt{transform:none!important;transition:none}
  section.features,section.community{padding:56px 16px 0}
  .features h2{font-size:28px;margin-bottom:18px}
  .lp-pick{grid-template-columns:1fr;gap:16px}
  .lp-modelist{flex-direction:row;flex-wrap:wrap;gap:8px}
  .lp-modebtn{padding:6px 14px 6px 6px;gap:8px;border-radius:999px}
  .lp-modebtn:hover{transform:none}
  .lp-modebtn .hdisc{width:30px;height:30px}
  .lp-modebtn .hdisc svg{width:16px;height:16px}
  .lp-modebtn b{font-size:16px}
  .community{flex-direction:column;align-items:stretch;gap:20px}
  .community p{font-size:24px}
  .landing-foot{padding:0 16px 32px}
}
.shell .top-rule{height:3px;display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}
.shell .top-rule i:first-child{background:var(--red)}.shell .top-rule i:last-child{background:var(--blue)}
:root[data-theme="pokeball"] .shell .top-rule{height:5px}
.shell .foot2{padding:0 56px 22px;display:flex;justify-content:space-between;font-size:11px;color:var(--mute)}
.profile{display:grid;grid-template-columns:340px 1fr;gap:28px;align-items:start}
/* Lukas 2026-09-18 (site pages): the id card column collapses above the phone breakpoint -- 340px + 1fr is
   already too tight for the tablet width where the funnel/ladder switch (860px), not just the phone one. */
@media (max-width:860px){.profile{grid-template-columns:1fr}}
.idcard{align-items:center;text-align:center;padding:26px 24px;gap:12px}
.idcard .big{width:76px;height:76px;font-size:34px;font-weight:700;border:2px solid var(--gold)}
.idcard .nm{font-family:var(--display);font-size:28px;font-weight:600;line-height:1;color:var(--ink)}
.idcard .facts{width:100%;border-top:1px solid var(--line);margin-top:6px;padding-top:14px;display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--ink2);text-align:left}
.idcard .facts div{display:flex;justify-content:space-between}.idcard .facts b{color:var(--ink);font-weight:500}
.idcard .facts a{color:var(--gold)}
.themes{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
.theme{border:1px solid var(--line);border-radius:4px;padding:12px;display:flex;flex-direction:column;gap:10px;background:var(--panel);cursor:pointer;text-align:left;font:inherit;color:inherit}
.theme.on{border-color:var(--gold);background:var(--panel2)}
.theme .sw{height:64px;border-radius:3px;position:relative;overflow:hidden;border:1px solid var(--line)}
.theme .sw i{position:absolute;left:10px;height:6px;border-radius:2px}
.theme .sw.felt{background:radial-gradient(ellipse at 40% 40%,#1b2028 0%,#14171c 70%)}
.theme .sw.felt i:nth-child(1){top:10px;width:60px;height:8px;background:#f2c14e}.theme .sw.felt i:nth-child(2){top:24px;width:110px;background:#3d4654}.theme .sw.felt i:nth-child(3){top:36px;width:80px;background:#3d4654}
.theme .sw.pokeball{background:#f6f4ef}
.theme .sw.pokeball::before{content:"";position:absolute;left:0;top:0;right:50%;height:4px;background:#d8382b}.theme .sw.pokeball::after{content:"";position:absolute;left:50%;top:0;right:0;height:4px;background:#2b5fb8}
.theme .sw.pokeball i:nth-child(1){top:14px;width:60px;height:8px;background:#d8382b}.theme .sw.pokeball i:nth-child(2){top:28px;width:110px;background:#cfc9bd}.theme .sw.pokeball i:nth-child(3){top:40px;width:80px;background:#cfc9bd}
.theme .th{display:flex;justify-content:space-between;align-items:center}
.theme .th .t{font-family:var(--display);font-size:17px;font-weight:600}
.theme .th .c{font-family:var(--display);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--mute)}
.theme.on .th .c{color:var(--gold)}
.theme .s{font-size:12px;color:var(--ink2)}
.tier{font-weight:700}
/* what plays a list as the bot (2026-10-05): small label, kind by theme token only; the detail is its title= hover */
.train{font-weight:600}.train-net{color:var(--gold)}.train-rules{color:var(--ink)}.train-general{color:var(--mute)}
.panel .check{font-size:12px;color:var(--ink2);min-height:18px}.panel .check .bad{color:var(--red)}.panel .check .ok{color:var(--green)}
/* Fable spacing fix (2026-09-18): the min-height above exists to stop layout jumping when a validation message
   shows up -- but with no message (the common case: a preloaded/saved list needs no check line) it still
   reserved 18px of blank space, part of the "~90px hole" between "paste a list" and the ladder's button.
   :empty drops that reservation outright; the moment deckCheckLine() writes real text in, min-height is back. */
.panel .check:empty{display:none}
.pw{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}
@media (max-width:640px){.pw{grid-template-columns:1fr}}   /* .themes stays 2-up (Lukas 2026-09-18): its swatches still read at half width */
/* saved decks (Lukas 2026-09-12): the lobby's save row under a clean paste, and the Profile "My decks" list */
.saverow{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.saverow input{width:auto;flex:1;min-width:140px;height:32px}
.mydecks{display:flex;flex-direction:column}
.mdrow{display:flex;align-items:center;gap:10px;padding:8px 0;border-top:1px solid var(--line)}
.mdrow:first-child{border-top:0}
.mdinfo{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}
.mdinfo .nm{font-weight:600;color:var(--ink)}
.mdactions{display:flex;gap:8px;flex-shrink:0}
.mdrename{flex:1;width:auto;height:36px}
.mdadd{display:flex;flex-direction:column;gap:8px;margin-top:4px}
.mdadd .inline input{flex:1;width:auto;height:36px}
.inline{display:flex;align-items:center;gap:14px}

/* feedback dialog + inbox (Lukas 2026-09-07) */
#fb{position:fixed;inset:0;z-index:70;background:var(--overlay);display:flex;align-items:center;justify-content:center}
#fb .fbbox{width:520px;max-width:92vw;background:var(--panel);border:1px solid var(--line2);border-radius:4px;box-shadow:0 18px 50px var(--shadow2);padding:18px 20px 16px;display:flex;flex-direction:column;gap:12px}
#fb .fbhead{display:flex;align-items:center;justify-content:space-between}
#fb .fbtitle{font-family:var(--display);font-size:22px;font-weight:600}
#fb .fbx{background:none;border:0;font-size:22px;line-height:1;color:var(--mute);padding:0 4px}#fb .fbx:hover{color:var(--ink)}
#fb .fbkinds{display:flex;gap:8px}
#fb .fbkinds label{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:1px solid var(--line2);border-radius:3px;cursor:pointer;font-family:var(--display);font-size:14px;letter-spacing:.04em}
#fb .fbkinds label.on{border-color:var(--gold);color:var(--gold);background:var(--panel2)}
#fb .fbkinds input{display:none}
#fb textarea{width:100%;box-sizing:border-box;height:150px;resize:vertical;padding:8px 10px;font-size:13px;line-height:1.4}
#fb .fbfoot{display:flex;align-items:center;gap:12px}#fb .fbfoot .btn{margin-left:auto}

/* GUEST CLAIM (Lukas 2026-09-22): same fixed-overlay dialog pattern as #fb above, its own id since both can in
   theory be open independently -- taller (a name/password/email/country/consent form), so it scrolls. */
#claimbox{position:fixed;inset:0;z-index:70;background:var(--overlay);display:flex;align-items:center;justify-content:center}
#claimbox .fbbox{width:420px;max-width:92vw;max-height:88vh;overflow:auto;background:var(--panel);border:1px solid var(--line2);border-radius:4px;box-shadow:0 18px 50px var(--shadow2);padding:18px 20px 16px;display:flex;flex-direction:column;gap:12px}
#claimbox label{display:block}
#claimbox input,#claimbox select{height:40px;box-sizing:border-box;padding:0 10px;width:100%;margin-top:2px}
#claimbox input[type=checkbox]{width:16px;height:16px;margin:0;flex:none;accent-color:var(--gold)}
#claimbox .consent{display:flex;align-items:center;gap:10px;cursor:pointer}
.fbtable .kind{font-family:var(--display);font-size:11px;letter-spacing:.1em;text-transform:uppercase;padding:2px 7px;border-radius:2px;border:1px solid var(--line2)}
.fbtable .kind.bug{color:var(--red);border-color:var(--red)}.fbtable .kind.idea{color:var(--hi);border-color:var(--hi)}
.fbtable td.txt{white-space:pre-wrap;max-width:520px}.fbtable tr.done td{opacity:.5}.fbtable td a{color:var(--gold)}
.fbtable.replays tbody tr{cursor:pointer}

/* arena grid */
/* replay round 9: no document scrollbar while the arena is up (the tilted mat overshoots 100vh by ~2 px); desktop
   only -- the phone grid is 100dvh and a focused chat input may still need the viewport to move */
@media (min-width:900px){html.arena-on{overflow:hidden}}
/* table-feel spec section 4 fix round (real-Chrome review, 900px viewport: document.scrollHeight 926, the whole
   arena shifted up 20px on hover/log-open): .hand's own transform:translateY (the bottom-edge crop, section 4)
   paints past .mat's/.arena's laid-out box -- transform never changes an element's LAYOUT size (the `auto` hand
   row above measures the card's true, pre-transform box, unaffected), but the PAINTED pixels still extend past
   .arena's edge with nothing there to clip them, so the total page content is taller than the viewport regardless
   of html.arena-on{overflow:hidden} above. overflow:hidden is not enough on its own: it hides the scrollbar and
   blocks a user's own wheel/drag scroll, but it still leaves .html/.arena a real SCROLL CONTAINER, which a
   hover/click's implicit "scroll the target into view" browser behavior can still move programmatically (its
   scrollTop is a live, settable number) -- that's the 20px shift. overflow:clip is the fix, not a bigger hammer:
   it removes the scroll container itself, so there is no scrollTop for anything to move at all, ever. Desktop
   only, on .arena itself (the box whose own edge is where the paint needs to stop, one level inside html.arena-on
   above) -- the phone's own overflow story (the comment above) is untouched. */
@media (min-width:900px){.arena{overflow:clip}}
/* Lukas 2026-09-18 (phone board): 1180 -> 900 -- an iPad landscape at 1024 was cut off too, and the width-fit zoom
   (updateMatZoom, arena.js) already handles 900-1180 by scaling the board down; below 900 the phone board
   (@media max-width:899px, this file's end) regrids the whole mat into one column instead. */
/* table-feel spec section 1 (2026-09-21): the desktop top bar folds into a permanent 84px rail -- three columns,
   one row. --panelw is 330px open (html[data-side=on] below) or 0 closed (the var() fallback); .topbar itself is
   phone-only now (hidden here, restored by the phone media query below with its own grid-column/row). */
.arena{position:relative;display:grid;grid-template-columns:1fr 84px var(--panelw,0px);grid-template-rows:1fr;height:100vh;min-width:900px;
  background:url(/static/arena-floor.webp) center/cover no-repeat,#070a10}
:root[data-theme="pokeball"] .arena{background:url(/static/arena-floor-light.webp) center/cover no-repeat,#e9e6df}
/* Lukas 2026-09-21 (screenshot): "I still see this edge not full to the edges" -- a strip of the plain page
   background showed above/beside the mat at the mat's own padding and at the wedge the perspective tilt leaves
   at the top corners. The same floor plate now paints full-bleed and UNTILTED on .arena itself, right behind
   the mat's own tilted copy -- .rail/.side keep their own opaque backgrounds (below, unchanged) so the plate
   only ever shows through .mat's own edges/corners, never under the rail or an open panel. */
/* table-feel spec section 6 fix round: position:relative here (not z-index, which would make .arena its own
   stacking context and reopen the exact trap #coinstage just escaped) is the containing block the desktop
   #coinstage needs. Every existing position:absolute descendant already has a CLOSER positioned ancestor of its
   own (.mat/.side/individual elements, all already position:relative) so none of them are affected by this. */
html[data-side=on]{--panelw:330px}
@media (min-width:900px){.topbar{display:none}}
.topbar{grid-column:1/3;display:flex;align-items:center;gap:14px;padding:0 14px;border-bottom:1px solid var(--line);background:var(--felt2)}
.topbar .name{font-family:var(--display);font-size:17px;font-weight:600}
.topbar .dot{width:8px;height:8px;border-radius:50%;background:var(--green);display:inline-block;margin-right:6px}
.topbar .dot.away{background:var(--mute)}
.topbar .turn{margin-left:auto;font-family:var(--display);font-size:16px;color:var(--gold);letter-spacing:.04em}
/* table-feel spec section 1 fix round (real-Chrome review): the wait-state class is "idle", not "wait" -- a
   plain, unscoped .wait{position:absolute;left:50%;top:50%;...} rule already exists (the pending-Choice waitBox,
   below) and ANY element carrying that literal class picks it up regardless of ancestor, cascade rules being
   per-property not per-rule: .topbar .turn.wait/.rail-turn .tt.wait never declared their own position, so the
   bare .wait rule's absolute positioning was the only one in force -- silently teleporting the "OPPONENT'S TURN"
   text out of the topbar/rail to the centre of its nearest positioned ancestor instead of sitting where it
   visually belongs. This affected the phone topbar too (not just the rail the review caught), fixed here. */
.topbar .turn.idle{color:var(--ink2)}

/* Lukas 2026-09-21 (screenshot): padding dropped to 0 (was 8px 12px 0) so the mat's own tilted plate reaches the
   columns' edges -- the nameplates/corner props are positioned inside the grid columns themselves (.nameplate is
   position:absolute against .mat's own box, the corner props are grid items with their own column widths), not
   by this padding, so none of them lose their inset. */
.mat{position:relative;display:grid;grid-template-columns:130px 1fr 130px;grid-template-rows:auto auto 1fr 1fr auto 150px;padding:0;column-gap:8px;row-gap:4px;min-width:0;min-height:0;
  background:url(/static/arena-floor.webp) center/cover no-repeat,#070a10;   /* board-arena 2026-09-19 section 1: the stadium floor plate, replaces the felt (the old playmat image, git rm'd) */
  transform:perspective(2600px) rotateX(7deg);transform-origin:50% 70%}   /* desktop only -- the phone block below resets this to none */
:root[data-theme="pokeball"] .mat{background:url(/static/arena-floor-light.webp) center/cover no-repeat,#070a10}
.mat .r2{border-bottom:0}   /* the row boundary is the seam below, not a dashed line */
/* Lukas 2026-09-22: removed the ring glitter ("doesn't look good"). The abyss ring will come back baked
   into a regenerated arena plate later. */
/* the nameplates (table-feel spec section 1): the player names move off the removed desktop top bar and onto the
   mat itself, opponent top-left / mine bottom-left above the hand -- desktop only, the phone keeps its top bar. */
.nameplate{position:absolute;z-index:5;display:flex;align-items:center;gap:6px;font-size:13px;color:var(--ink);
  background:var(--overlay);padding:3px 10px;border-radius:10px;white-space:nowrap}
.nameplate .dot{width:7px;height:7px;border-radius:50%;background:var(--green);display:inline-block}
.nameplate .dot.away{background:var(--mute)}
.nameplate.opp{top:8px;left:10px}
.nameplate.me{bottom:172px;left:10px}
/* the rail (table-feel spec section 1): a permanent 84px column between the mat and the panel -- the turn
   readout, prize counts, End turn/Undo/clock/match badge, the tab buttons (open/close the panel) and the
   watch/concede/more icons. Desktop only -- the phone keeps its own top bar + bottom tab bar (this file's phone
   block, unchanged), so .rail never renders there at all (arena.js render(), isPhone). */
.rail{grid-column:2;display:flex;flex-direction:column;align-items:center;gap:14px;padding:14px 6px;overflow-y:auto;overflow-x:hidden;min-height:0;
  background:var(--felt2);border-left:1px solid var(--line);border-right:1px solid var(--line)}
/* Lukas 2026-09-21 (screenshot): "this part texts are small" -- the turn readout grows a step (11px -> 13px
   stencil, 15px -> 18px state line); two lines are fine for a long name (no white-space:nowrap anywhere here, so
   normal text wrap already does it). */
.rail-turn{text-align:center}
.rail-turn .tn{font-family:var(--display);font-size:13px;letter-spacing:.1em;color:var(--mute)}
.rail-turn .tt{font-family:var(--display);font-size:18px;font-weight:700;letter-spacing:.03em;line-height:1.25;color:var(--gold)}
.rail-turn .tt.idle{color:var(--ink2)}
/* Lukas 2026-09-21 (screenshot): "prize digits could be maybe done vertical in similar circles with color as
   arena active pokemon" -- two rimmed circles stacked vertically, opponent's on top / mine below (the board's
   own order), the same rim colour + soft glow the active podium's own ::after ring uses (12px here, a plainer
   single-value glow than the podium's own 3-part one), a dark inner fill (--prop-face on felt, --prop on the
   pokeball theme, below), the count centred inside. */
.rail-prizes{display:flex;flex-direction:column;align-items:center;gap:6px}
.rail-prizes .rp{width:44px;height:44px;border-radius:50%;box-sizing:border-box;display:flex;align-items:center;justify-content:center;
  font-family:var(--display);font-size:20px;font-weight:700;line-height:1;color:var(--ink);background:var(--prop-face);border:2px solid}
.rail-prizes .rp.opp{border-color:var(--rim-opp);box-shadow:0 0 12px var(--rim-opp)}
.rail-prizes .rp.me{border-color:var(--rim-me);box-shadow:0 0 12px var(--rim-me)}
:root[data-theme="pokeball"] .rail-prizes .rp{background:var(--prop)}
/* whose turn it is brightens that side's rim, same html[data-turn] convention as the podium's own glow. */
html[data-turn=opp] .rail-prizes .rp.opp{box-shadow:0 0 20px var(--rim-opp)}
html[data-turn=me] .rail-prizes .rp.me{box-shadow:0 0 20px var(--rim-me)}
.rail-prizes .rpl{font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--mute)}
.rail-ctl{display:flex;flex-direction:column;align-items:center;gap:8px}
/* table-feel spec section 1 fix round (real-Chrome review, 1440px: document scrollWidth 1441, 1px over): the
   84px rail column has no slack for a child wider than its own 84px - 12px padding (72px) once a border is
   added on top of a content-box width -- .rail-ctl .endturn was exactly 72px content + 1px*2 border = 74px,
   2px over that 72px budget (rounding/subpixel layout is what turned it into the 1px actually seen). box-sizing:
   border-box on every fixed-width bordered rail child (here and .undo/.rail-bottom .icon/#tabs button, already
   set) keeps each one's OWN width the full width, border included, instead of adding to it; overflow-x:hidden
   above is the belt-and-suspenders backstop. */
.rail-ctl .endturn{width:72px;height:72px;border-radius:50%;box-sizing:border-box;background:var(--gold);color:var(--on-gold);border:1px solid var(--gold);
  font-family:var(--display);font-weight:700;font-size:13px;letter-spacing:.02em;text-transform:uppercase;padding:2px;
  box-shadow:0 7px 0 var(--gold2),0 14px 22px rgba(0,0,0,.6),inset 0 1px 0 rgba(255,255,255,.35)}
.rail-ctl .endturn:active:not(:disabled){transform:translateY(3px);box-shadow:0 1px 0 var(--gold2),0 4px 8px rgba(0,0,0,.5)}
.rail-ctl .endturn:disabled{background:var(--prop);color:var(--prop-ink);border-color:var(--prop-line);opacity:1;box-shadow:0 7px 0 var(--prop-face),0 14px 22px rgba(0,0,0,.6)}
.rail-ctl .endturn.live{animation:none;box-shadow:0 7px 0 var(--gold2),0 14px 22px rgba(0,0,0,.6),0 0 30px var(--glow)}
.rail-ctl .endturn.live:hover{filter:brightness(1.08)}
.rail-ctl .undo{width:40px;height:40px;border-radius:50%;box-sizing:border-box;font-size:20px;line-height:1;padding:0;display:flex;align-items:center;justify-content:center;
  background:var(--prop);border:1px solid var(--prop-line);box-shadow:0 5px 0 var(--prop-face),0 10px 16px rgba(0,0,0,.6);color:var(--ink2)}
.rail-ctl .undo:hover:not(:disabled){color:var(--gold);border-color:var(--gold2)}
.rail #tabs{display:flex;flex-direction:column;gap:6px;border-bottom:0}
/* table-feel spec section 1 fix round (real-Chrome review: "empty 40px squares with no glyph"): each button now
   holds an inline SVG (TAB_ICON, arena.js) instead of a font-size:0 label -- the old ::first-letter trick is
   gone with it. stroke="currentColor" on the icon picks up this button's own color, so .on's gold applies to
   the icon too, no separate glyph rule needed. */
.rail #tabs button{width:40px;height:40px;border-radius:8px;position:relative;display:flex;align-items:center;justify-content:center;
  box-sizing:border-box;color:var(--mute);background:var(--panel);border:1px solid var(--line2);border-left:3px solid var(--line2);cursor:pointer}
.rail #tabs button.on{color:var(--gold);border-color:var(--gold2);border-left-color:var(--gold)}
.rail #tabs button .dot{position:absolute;top:2px;right:2px;font-size:9px;color:var(--gold)}
.rail-bottom{margin-top:auto;display:flex;flex-direction:column;gap:8px;align-items:center}
/* Lukas 2026-09-21 (screenshot): #report (Feedback) joins this stack as its last item, an <a> not a <button> --
   text-decoration:none clears the anchor default (nothing else changes, .icon already sets its own colour). */
.rail-bottom .icon{width:36px;height:36px;border-radius:8px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;
  background:var(--panel);border:1px solid var(--line2);color:var(--ink2);cursor:pointer;font-size:14px;text-decoration:none}
.rail-bottom .icon:hover:not(:disabled){color:var(--gold);border-color:var(--gold2)}
.rail-bottom .icon:disabled{opacity:.4;cursor:default}
@media (max-width:899px){.nameplate,.rail{display:none}}
/* Lukas 2026-09-20 afternoon: "problems with spacing" -- the discard tray rode up over the deck box. min-height:100px
   forced the row-5 track below its real content (139px, the discard's own min contribution) whenever updateMatZoom's
   --matz over-constrained the grid; every column always holds a real prop now, so the fixed floor is gone. */
.col{display:flex;flex-direction:column;align-items:center;gap:16px;justify-content:center}
.col.top{justify-content:flex-start;padding-top:4px}.col.bot{justify-content:flex-end;padding-bottom:4px}
/* motion-2 section 7b (Lukas 2026-09-22, two crops: "they're not the same the oponent side - mine side"): the opponent's
   discard sat at the TOP of the bench row while the deck box sat at the top of the Active row, so whatever slack the
   opponent's bench row had (three benched Pokémon with HP pills make it ~40px taller than an empty own bench) opened
   up between the two props; the own side has no slack (the deck at the bottom of its row, the discard filling the
   bench row above the fixed hand), so its two props read packed. Mirror = pack the opponent's too: the discard
   column aligns to the bottom of its row, against the deck row, and the slack moves up under the opponent's hand. */
.col.top.z-disc{justify-content:flex-end;padding-bottom:4px}
/* table-feel spec section 2 (2026-09-21): the .col zone stencils ("BOT · DISCARD" etc.) are gone outright -- the
   .col itself now carries title= instead. .actrow .hint (phone only) is the one survivor of this rule's old
   .col .who,.hand.opp .who,.actrow .hint selector (.hand.opp .who was the opponent's "hand N" word chip, itself
   gone in the section 1 fix round below -- a bare number badge, .hand.opp .cnt, replaces it). */
.actrow .hint{background:var(--overlay);color:var(--ink2);padding:2px 8px;border-radius:9px;line-height:14px}
/* the lobby's game options row, between the decks and Start game (Lukas 2026-09-13) */
/* options as rows (site pages spec section 2, step 3): label + one-line hint left (.optl), the control right --
   same ids/handlers as before, just laid out differently. */
.options{display:flex;flex-direction:column;gap:2px;margin:14px 0 6px}
.options .opt{display:flex;align-items:center;justify-content:space-between;gap:18px;flex-wrap:wrap;padding:8px 0;border-bottom:1px solid var(--line2)}
.options .opt:last-of-type{border-bottom:0}
.options .optl{display:flex;flex-direction:column;gap:2px;min-width:0}
.options .optr{display:flex;gap:14px;flex-wrap:wrap;align-items:center}
.options .k{font-family:var(--display);font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--mute)}
.options .consent{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;color:var(--ink)}
.options .consent.off{color:var(--mute);cursor:default}
.options input[type=checkbox],.options input[type=radio]{width:16px;height:16px;margin:0;accent-color:var(--gold)}
.options details.adv{margin-top:6px}
.options details.adv summary{cursor:pointer;color:var(--ink2);font-size:12px}
.options details.adv .opt{border-bottom:0}
/* the tournament clock (Lukas 2026-09-13) under the End turn button.
   board-arena 2026-09-19 section 2: a scoreboard prop -- a --prop-line panel, gold digits with a glow. */
.clock{display:flex;flex-direction:column;align-items:center;gap:4px;min-width:96px;margin-top:6px;min-height:58px;
  padding:8px 12px;border-radius:8px;background:var(--prop-line);border:1px solid var(--prop-edge);
  box-shadow:0 6px 0 var(--prop-face),0 12px 20px rgba(0,0,0,.6),inset 0 0 20px rgba(0,0,0,.8)}
.clock .who{min-height:13px;line-height:13px;white-space:nowrap;color:var(--prop-ink)}
.clock .t{font-family:var(--display);font-size:26px;font-weight:600;letter-spacing:.04em;line-height:1;color:var(--gold);text-shadow:0 0 10px var(--glow);font-variant-numeric:tabular-nums}
/* replay bar (spec 2026-09-20): a fixed strip at the very top (table-feel spec section 1: no more top bar above
   it on the desktop), mounted once outside #app so render()'s innerHTML rebuild never detaches it (Chrome drops a
   click whose mousedown target left the DOM); .rpspace reserves its row, now spanning all three rail columns. */
.arena.replay{grid-template-rows:40px minmax(0,1fr)}
.rpspace{grid-column:1/4}
.rpbar{position:fixed;top:0;left:0;right:0;height:40px;box-sizing:border-box;z-index:11;display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding:0 14px;border-bottom:1px solid var(--line);background:var(--felt2)}
.rpbar button,.rpbar .who a{font:inherit;line-height:1;padding:4px 10px;border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--ink);cursor:pointer}
.rpbar button:disabled{opacity:.4;cursor:default}
.rpbar select{font:inherit;padding:3px 6px;border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--ink)}
.rpbar .t{font-family:var(--display);font-variant-numeric:tabular-nums;font-size:15px;color:var(--gold);min-width:6ch;text-align:center}
.rpbar .who{margin-left:auto;display:flex;gap:8px}
.rpbar .who a{text-decoration:none}
.clock.low .t{color:var(--red);text-shadow:none}.clock.called .t{color:var(--gold);letter-spacing:.12em}
.clock .strikes{color:var(--red)}
/* best of 3 (Lukas 2026-09-13): the game/score badge, next to the clock pill (or alone without a clock) */
.matchbadge{font-family:var(--display);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute);font-variant-numeric:tabular-nums;white-space:nowrap}
/* the decision timer: a pill at the top centre of the playmat, above the overlay (z 10) so it shows over a deck search */
/* table-feel spec section 1: no more 44px top bar on the desktop, and the right-hand reserve is now the 84px
   rail plus the panel (--panelw) instead of --sidew. */
#tempo{position:fixed;top:12px;left:calc((100vw - 84px - var(--panelw,0px)) / 2);transform:translateX(-50%);z-index:11;display:flex;align-items:center;gap:12px;padding:8px 18px;border-radius:24px;background:var(--panel);border:1px solid var(--line2);box-shadow:0 6px 24px rgba(0,0,0,.35);overflow:hidden;min-width:220px}
#tempo .w{font-family:var(--display);font-size:13px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink2)}
#tempo .n{font-family:var(--display);font-size:26px;font-weight:700;line-height:1;color:var(--ink);font-variant-numeric:tabular-nums;margin-left:auto}
#tempo i{position:absolute;left:0;bottom:0;height:3px;width:100%;background:var(--gold);transition:width .9s linear}
#tempo.me{border-color:var(--gold2)}
#tempo.low .n{color:var(--red)}#tempo.low i{background:var(--red)}
/* motion-2 section 5: the deck box and the discard pile share this base size (.stack.back/.stack.disc, neither
   overrides width/height of its own) -- scaled from --side (.mat, above) instead of a flat 70x94, the same
   70/130 and 94/130 fractions the old desktop value was at the old flat 130px side column. */
.stack{width:calc(var(--side, 130px) * .54);height:calc(var(--side, 130px) * .72);border-radius:5px;position:relative;display:flex;align-items:flex-end;justify-content:center;padding-bottom:6px;box-shadow:0 3px 8px var(--shadow);cursor:pointer;box-sizing:border-box}
.stack .n{font-family:var(--display);font-size:26px;font-weight:600;line-height:1;text-shadow:0 1px 2px #000}
.stack.back .n{color:#e8e2d6}   /* card backs stay dark in every theme */
.stack.disc{background:var(--panel);border:1px solid var(--line);align-items:stretch;padding:0;flex-direction:column}
.stack.disc .strip{height:5px;border-radius:5px 5px 0 0}
.stack.disc .top{font-family:var(--display);font-size:12px;padding:4px 6px 0;line-height:1.05;flex:1}
.stack.disc .cnt{position:absolute;right:-6px;bottom:-6px;background:var(--panel2);border:1px solid var(--line2);border-radius:10px;font-family:var(--display);font-size:12px;font-weight:600;padding:0 7px;transform:rotate(5deg)}   /* board-table-motion-design 2026-09-19 A: counter-rotated against the pile's own tilt */
/* board-arena 2026-09-19 section 2: the lost zone is a slot cut into the floor, not a card -- prop tokens, no hex.
   table-feel spec section 2 (2026-09-21): no "lost zone" stencil any more -- empty is a bare dark slot, .n is the
   count only when non-empty (title= carries the word). */
.stack.lost{width:70px;height:26px;border:1px solid var(--prop-edge);border-radius:4px;box-shadow:inset 0 3px 8px rgba(0,0,0,.5);align-items:center;padding:0;justify-content:center;background:var(--prop-line)}
.stack.lost .n{font-family:var(--display);font-weight:600;font-size:14px;color:var(--prop-ink)}
/* Lukas 2026-09-11: bench Pokemon were touching (or nearly) once faces became pictures -- 14px between them.
   board-arena 2026-09-19 section 2: the bench slab -- a raised prop (front face + lit edge), width:fit-content so
   it hugs the row instead of stretching to the column; max-width:100% + box-sizing keep it inside the middle
   column at any bench size (.row{flex-wrap:nowrap} on desktop, above, already stops an 8-card bench wrapping). */
.row{display:flex;align-items:center;justify-content:center;gap:14px;flex-wrap:wrap;border-radius:12px;
  width:fit-content;min-width:560px;max-width:100%;box-sizing:border-box;margin:0 auto;padding:12px 22px;
  background:linear-gradient(180deg,var(--prop),var(--prop-2));
  /* Lukas 2026-09-20 afternoon ("if 3d higher ground than make it better"): dropped the pale-edge box-shadow layer
     under the face -- on the light theme it read as a second, lighter strip, not one raised slab. */
  box-shadow:0 0 0 1px var(--prop-line),0 10px 0 var(--prop-face),0 26px 34px var(--prop-shadow),inset 0 1px 0 rgba(255,255,255,.08)}
.row.opp{align-items:flex-start}
/* BIGGER CARDS, TIGHTER BOARD render round 3 (Lukas: "bench can be up to 8 don't forget, with Area Zero" -- the
   engine allows an 8-Pokemon bench). Desktop only (phone's bench sits in its own named grid-template-area,
   `.mat .row.opp{grid-area:obench}`/`.mat .row:not(.opp){grid-area:mbench}`, further down, with no horizontal
   overflow mechanism -- wrapping stays the safe default there): eight cards must never wrap onto a second line,
   or the row's own height (and so the whole mat's natural height, per updateMatZoom's max-content measurement
   below) would silently double. Eight 100px cards + 7x14px gaps = 898px, comfortably under the capped middle
   column's 1200px at 2000x1088 -- no gap trim needed there. At a narrower viewport (1440x900) the row's own
   scrollWidth can exceed the shrunk column's clientWidth; that's updateMatZoom's width ratio doing its job
   (shrinking the whole mat to fit), not a bug. */
@media (min-width:900px){.row{flex-wrap:nowrap}}
/* Undo + End turn live on the playmat, at the divider next to your side (Lukas 2026-09-11: the topbar was too far) */
.mat .ctl{justify-content:flex-end;align-items:center;gap:10px;padding-bottom:12px}
/* board-arena 2026-09-19 section 2: Undo -- the same physical-button prop as End turn below, in --prop instead of gold. */
.mat .ctl .undo{width:40px;height:40px;border-radius:50%;font-size:22px;line-height:1;padding:0;display:flex;align-items:center;justify-content:center;
  background:var(--prop);border:1px solid var(--prop-line);box-shadow:0 5px 0 var(--prop-face),0 10px 16px rgba(0,0,0,.6);color:var(--ink2)}
.mat .ctl .undo:hover:not(:disabled){color:var(--gold);border-color:var(--gold2)}
/* End turn: a pressable physical button -- raised on a gold shadow, sinks on :active. */
.mat .ctl .endturn{width:126px;height:40px;border-radius:20px;background:var(--gold);color:var(--on-gold);border:1px solid var(--gold);font-family:var(--display);font-weight:700;font-size:16px;letter-spacing:.06em;text-transform:uppercase;padding:0;
  box-shadow:0 7px 0 var(--gold2),0 14px 22px rgba(0,0,0,.6),inset 0 1px 0 rgba(255,255,255,.35);transform:translateY(-4px)}
.mat .ctl .endturn:active:not(:disabled){transform:translateY(2px);box-shadow:0 1px 0 var(--gold2),0 4px 8px rgba(0,0,0,.5)}
.mat .ctl .endturn:disabled{background:var(--prop);color:var(--prop-ink);border-color:var(--prop-line);opacity:1;box-shadow:0 7px 0 var(--prop-face),0 14px 22px rgba(0,0,0,.6)}
.mat .ctl .endturn.live{animation:none;box-shadow:0 7px 0 var(--gold2),0 14px 22px rgba(0,0,0,.6),0 0 30px var(--glow)}
.mat .ctl .endturn.live:hover{filter:brightness(1.08)}
@keyframes endpulse{0%{box-shadow:0 0 0 0 var(--glow)}70%{box-shadow:0 0 0 9px rgba(0,0,0,0)}100%{box-shadow:0 0 0 0 rgba(0,0,0,0)}}
.actrow{display:grid;grid-template-columns:1fr auto 1fr;align-items:center}
/* the Active and the third cell keep an explicit column each -- once the Stadium (first cell) goes
   position:absolute for the centreline below, auto-placement would otherwise slide them both one column left
   to fill the gap it leaves. Left unset on the first cell on purpose: an explicit grid-column would make that
   cell's own grid area (inset by the container's padding) the Stadium's containing block instead of the
   padding box, and the whole point of position:absolute here is bottom:0 landing exactly on the border. */
.actrow>*:nth-child(2){grid-column:2}.actrow>*:nth-child(3){grid-column:3}
/* Lukas 2026-09-11: the Active was touching the Stadium/bench once it grew into a picture -- more clearance. */
.actrow.opp{align-items:end;padding:28px 0 20px}.actrow.me{align-items:start;padding:20px 0 28px}   /* Lukas 2026-09-11: clear air between an Active and its Bench */
/* Lukas 2026-09-20 afternoon: "Spacing" on the click-a-card hint -- centred in its own grid cell (between the
   podium and the deck column) instead of hugging the left edge. */
.actrow .hint{justify-self:center;margin-left:0;color:var(--prop-ink);font-size:11px;letter-spacing:.08em;text-transform:uppercase}
.actrow .hint:empty{display:none}   /* the pill above would otherwise show as an empty blob when the hint has no text (not your turn / a pending Choice) */
:root:not([data-theme="pokeball"]) .actrow .hint{text-shadow:0 1px 0 rgba(0,0,0,.6)}
/* board-arena 2026-09-19 section 2: the battle podium under each Active -- a thick raised disc with a lit rim
   (arena.js pokemon()'s wrapper in both .actrow's gets <div class="podium"></div> as its first child, even when
   the spot is empty -- the podium is the spot). Hex translated into the 8 prop tokens above. */
.actrow>div:nth-child(2){position:relative}
/* table-feel spec section 7 (2026-09-21), animated LEFT/RIGHT lobes (2026-09-22, Lukas verbatim): "Now just
   remove and I want to try animations on the active pokemon background effects which we have on the sides from
   the circle. Make those zones not oval but more around Big circle sides left and right and with animation" /
   "acitve pokemon circles not one as it's now but animated and shape not oval but from Circle sides not from top
   or bottom sides." The stage plate still follows the Active's own type, each side independently (.actrow[data-
   type], render() -- arena.js activeType()), but the zone is now two crescent lobes hugging the podium's own
   circle on its left and right, nothing above or below it: a radial-gradient hole (126px = the podium's 125px
   radius + 1) cut out of the centre removes the top/bottom of the ellipse outright, because the ellipse's own
   vertical half-extent (130px) is inside the podium, so only the left/right crescents survive the intersection.
   Same box + mask on both the still image (::before, always present -- the base for the phone/reduced-motion/
   before-the-clip-loads case) and the looping video (.stagefx, desktop only, JS-managed so it survives a
   render() rebuild -- see syncStageFx() in arena.js) so the clip covers the still exactly. z-index:-1 unchanged
   (behind the podium, above the floor). Same-type mirror check: podium centres are ~210px apart vertically; at
   x=+-200 the ellipse's own vertical fade already ends around y=+-101px, so two lobes never merge across the
   seam. */
.actrow>div:nth-child(2)::before,.actrow>div:nth-child(2)>.stagefx{position:absolute;left:50%;top:50%;width:640px;height:260px;
  transform:translate(-50%,-50%);z-index:-1;pointer-events:none;
  -webkit-mask:radial-gradient(circle at center,transparent 126px,#000 148px),radial-gradient(ellipse closest-side at center,#000 55%,transparent 100%);
  -webkit-mask-composite:source-in;
  mask:radial-gradient(circle at center,transparent 126px,#000 148px),radial-gradient(ellipse closest-side at center,#000 55%,transparent 100%);
  mask-composite:intersect;opacity:.85;transition:opacity .6s}
.actrow>div:nth-child(2)::before{content:"";background-position:center;background-size:cover}
.stagefx{object-fit:cover;background:transparent}
html.nofx .stagefx{display:none}
@media (max-width:899px){.stagefx{display:none}}
/* one rule per type, both themes -- the pale twin only exists for the four types Higgsfield generated one for
   (fire/lightning/darkness/psychic); the other six reuse the same file on both themes (the session's own asset
   commit, 007a4d2). No Fairy rule: activeType() (arena.js) already folds Fairy into Colorless before it ever
   reaches a data-type attribute. */
.actrow[data-type=Grass]>div:nth-child(2)::before{background-image:url(/static/stage/grass.webp)}
.actrow[data-type=Fire]>div:nth-child(2)::before{background-image:url(/static/stage/fire.webp)}
.actrow[data-type=Water]>div:nth-child(2)::before{background-image:url(/static/stage/water.webp)}
.actrow[data-type=Lightning]>div:nth-child(2)::before{background-image:url(/static/stage/lightning.webp)}
.actrow[data-type=Psychic]>div:nth-child(2)::before{background-image:url(/static/stage/psychic.webp)}
.actrow[data-type=Fighting]>div:nth-child(2)::before{background-image:url(/static/stage/fighting.webp)}
.actrow[data-type=Darkness]>div:nth-child(2)::before{background-image:url(/static/stage/darkness.webp)}
.actrow[data-type=Metal]>div:nth-child(2)::before{background-image:url(/static/stage/metal.webp)}
.actrow[data-type=Dragon]>div:nth-child(2)::before{background-image:url(/static/stage/dragon.webp)}
.actrow[data-type=Colorless]>div:nth-child(2)::before{background-image:url(/static/stage/colorless.webp)}
:root[data-theme="pokeball"] .actrow[data-type=Grass]>div:nth-child(2)::before{background-image:url(/static/stage/grass-light.webp)}
:root[data-theme="pokeball"] .actrow[data-type=Fire]>div:nth-child(2)::before{background-image:url(/static/stage/fire-light.webp)}
:root[data-theme="pokeball"] .actrow[data-type=Water]>div:nth-child(2)::before{background-image:url(/static/stage/water-light.webp)}
:root[data-theme="pokeball"] .actrow[data-type=Lightning]>div:nth-child(2)::before{background-image:url(/static/stage/lightning-light.webp)}
:root[data-theme="pokeball"] .actrow[data-type=Psychic]>div:nth-child(2)::before{background-image:url(/static/stage/psychic-light.webp)}
:root[data-theme="pokeball"] .actrow[data-type=Fighting]>div:nth-child(2)::before{background-image:url(/static/stage/fighting-light.webp)}
:root[data-theme="pokeball"] .actrow[data-type=Darkness]>div:nth-child(2)::before{background-image:url(/static/stage/darkness-light.webp)}
:root[data-theme="pokeball"] .actrow[data-type=Metal]>div:nth-child(2)::before{background-image:url(/static/stage/metal-light.webp)}
:root[data-theme="pokeball"] .actrow[data-type=Dragon]>div:nth-child(2)::before{background-image:url(/static/stage/dragon-light.webp)}
:root[data-theme="pokeball"] .actrow[data-type=Colorless]>div:nth-child(2)::before{background-image:url(/static/stage/colorless-light.webp)}
.podium{position:absolute;left:50%;top:50%;width:250px;height:250px;transform:translate(-50%,-50%);border-radius:50%;z-index:0;pointer-events:none;
  background:radial-gradient(circle at 50% 42%,var(--prop-edge) 0,var(--prop) 55%,var(--prop-face) 100%);
  /* Lukas 2026-09-20 afternoon: dropped the pale-edge box-shadow layer -- see the .row comment above. */
  box-shadow:0 0 0 3px var(--prop-line),0 0 0 5px var(--prop-edge),0 0 0 6px var(--prop-line),0 10px 0 2px var(--prop-face),0 30px 40px 6px var(--prop-shadow),inset 0 0 40px var(--prop-shadow)}
.podium::after{content:"";position:absolute;inset:14px;border-radius:50%;border:2px solid var(--rim-me);
  box-shadow:0 0 18px var(--rim-me),inset 0 0 18px var(--rim-me);opacity:.4;transition:opacity .6s,box-shadow .6s}
.actrow.opp .podium::after{border-color:var(--rim-opp);box-shadow:0 0 18px var(--rim-opp),inset 0 0 18px var(--rim-opp)}
html[data-turn=me] .actrow.me .podium::after{opacity:1;box-shadow:0 0 28px var(--rim-me),inset 0 0 18px var(--rim-me)}
html[data-turn=opp] .actrow.opp .podium::after{opacity:1;box-shadow:0 0 28px var(--rim-opp),inset 0 0 18px var(--rim-opp)}
.actrow .pk.active{position:relative;z-index:1}
/* the empty active spot ("no active") is position:static like every other .slot -- CSS paints a positioned
   descendant (the podium, however low its z-index) above in-flow static content regardless of DOM order, so
   without this the placeholder text would sit invisibly under the disc. */
.actrow .slot{position:relative;z-index:1}
/* Lukas 2026-09-23: "When games not started and there're no active pokemon UI collapses" -- the empty Active
   spot rendered at .slot's bench size (118x64), so the mat's content-sized rows shrank and the 250px absolute
   podiums overlapped. Give it the Active card's own box instead. */
.actrow .slot{width:136px;height:190px}
/* Lukas 2026-09-11 (table centreline): on a real table the Stadium sits half on each side -- centred on the r2
   boundary, straddling into the player's actrow below. .actrow.r2's own border-bottom (dashed) IS that boundary;
   position:relative here makes it the containing block for the Stadium's position:absolute, so bottom:0 lands
   flush on the border no matter what padding the row itself carries above it. translateY(50%) then drops the
   box by half its own height, so its centre -- not its edge -- sits on the line. right:calc(50% + 105px) keeps
   a fixed 30px clear of the Active (always 150px wide, centred in this row) at any viewport width, pics on or
   off; .actrow.me's first cell is an empty <div> in the same column, so the box has room to spill into it. */
.actrow.r2{position:relative}
.actrow.r2 .stad{position:absolute;right:calc(50% + 98px);bottom:0;transform:translateY(50%);z-index:3;
  padding:8px;border-radius:8px;background:linear-gradient(180deg,var(--prop),var(--prop-2));
  /* Lukas 2026-09-20 afternoon: dropped the pale-edge box-shadow layer -- see the .row comment above. */
  box-shadow:0 0 0 1px var(--prop-line),0 8px 0 var(--prop-face),0 22px 28px var(--prop-shadow)}   /* board-arena 2026-09-19 section 2: the stadium podium */
/* board-arena 2026-09-19 fix round: with no Stadium in play, stadiumBox() (arena.js) still renders a plain
   `<div class="stadium" style="opacity:.4">` placeholder (Stadium/none) inside .stad -- always a real .stadium
   element either way, so a bare :not(:has(.stadium)) never matches; [data-stadium] is what a REAL Stadium in play
   carries (the placeholder has none) -- this is the one that actually tells the two apart. Bare slab, no floating
   box off the Active/off the phone's left edge, when there's nothing in it. */
.actrow.r2 .stad:not(:has(.stadium[data-stadium])){background:none;box-shadow:none;padding:0}
.stadium{width:150px;background:var(--panel);border:1px solid var(--line);border-radius:4px;padding:6px 8px;font-size:11px;color:var(--ink2);cursor:pointer}
/* table-feel spec section 2 fix round (Lukas, screenshot): no Stadium in play was a "Stadium / none" text box at
   40% opacity -- an empty dashed slot now, the same look as the empty bench slot (.slot below); title= carries
   the word. [data-stadium] (a real Stadium always carries it, stadiumBox() arena.js) is what distinguishes this
   from the .stadium.pic/text rules above/below it, same convention .actrow.r2 .stad:not(:has(...)) already uses. */
.stadium:not([data-stadium]){width:118px;height:64px;background:none;border:1px dashed var(--prop-edge);border-radius:6px;
  padding:0;box-sizing:border-box;box-shadow:inset 0 0 30px rgba(0,0,0,.25);cursor:default}
.stadium .nm{font-family:var(--display);font-size:14px;color:var(--ink);font-weight:600}
.stadium .k{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--mute)}
.stadium .act{margin-top:6px;padding:4px 8px}.stadium .act .n{font-size:13px}
/* Lukas 2026-09-11: with pictures on, the Stadium is its own SM card image, roughly a bench Pokemon's size (118
   wide); the owner + "use" row (Academy at Night's Ability) sits as an overlay strip at the foot, like a Pokemon's
   -- one copy of that strip (.ov), shown whenever picsOn regardless of whether the image itself loaded, so there is
   never a second, hidden .act[data-index] for the same action. .txt is the name/rule text, the on-error fallback. */
/* table-feel spec section 2 (2026-09-21): the "Stadium · <name>'s" stencil is gone -- the owner's rim colour
   (data-owner, same tokens as the podium's turn glow) is the tell now, title= carries the words. */
.stadium.pic{width:100px;height:140px;padding:0;border:1px solid var(--line);position:relative;overflow:hidden}
.stadium.pic[data-owner="me"]{border-color:var(--rim-me)}
.stadium.pic[data-owner="opp"]{border-color:var(--rim-opp)}
.stadium.pic img{width:100%;height:100%;object-fit:cover;display:block;border-radius:4px}
.stadium.pic .txt{display:none}
.stadium.pic.noimg .txt{display:block;padding:6px 8px}
.stadium.pic.noimg img{display:none}
.stadium.pic .ov{position:absolute;left:0;right:0;bottom:0;background:var(--overlay);backdrop-filter:blur(1px);
  display:flex;flex-direction:column;gap:3px;padding:4px 6px 5px;border-radius:0 0 3px 3px}
.stadium.pic .ov .act{margin:0;padding:3px 6px;background:var(--go-bg)}
.stadium.pic .ov .act .n{font-size:11px}
/* board-table-motion-design 2026-09-19 section A: the Stadium lies in the centre with a soft type-tinted glow --
   own full box-shadow (own ambient drop shadow baked in), so it needs no separate contact shadow (the mock's
   ".stad .card" was written for a bare card face; the real element here is .stadium, not .card). */
.stad .stadium.pic{box-shadow:0 0 0 1px rgba(255,255,255,.08),0 0 44px 4px rgba(120,160,255,.12),0 10px 20px -6px rgba(0,0,0,.7)}

/* pokemon */
.pk{width:118px;background:var(--panel);border:1px solid var(--line);border-radius:4px;position:relative;font-size:12px;box-shadow:0 2px 6px var(--shadow);cursor:pointer;box-sizing:border-box;overflow:visible}
.pk .strip{height:5px;border-radius:4px 4px 0 0}
.pk .hd{display:flex;justify-content:space-between;align-items:baseline;padding:5px 7px 0;gap:4px}
.pk .nm{font-family:var(--display);font-size:15px;font-weight:600;line-height:1.05}
.pk .hp{font-family:var(--display);font-size:16px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--ink2);white-space:nowrap}
.pk .hp b{color:var(--ink)}.pk .hp.hurt b{color:var(--red)}
.pk .stage{font-size:10px;color:var(--mute);letter-spacing:.08em;text-transform:uppercase;padding:0 7px}
.pk .en{display:flex;gap:3px;padding:6px 7px 7px;min-height:14px;flex-wrap:wrap}
.pk .st{position:absolute;top:-8px;right:-6px;display:flex;gap:3px}
/* attached Tool, Lukas 2026-09-12: the real Tool card tucked under the Pokémon, its top 24px peeking out above the
   left corner (the right corner carries damage + conditions). z-index:-1 puts it under the Pokémon's own face, and
   .pk isolates so "under" means under this card, not under the table. Off (Jamming Tower): grey + a red strike. */
.pk{isolation:isolate}
.pk .tool{position:absolute;left:10px;top:-24px;width:44px;height:60px;border-radius:3px;background:var(--panel2);border:1px solid var(--line2);
  box-shadow:0 -1px 4px var(--shadow);rotate:-6deg;transform-origin:bottom left;z-index:-1;overflow:hidden;box-sizing:border-box;
  font-family:var(--display);font-size:8px;font-weight:600;line-height:1.1;padding:3px 3px 0;text-transform:uppercase;letter-spacing:.03em;color:var(--ink2)}
.pk .tool img{width:100%;height:100%;object-fit:cover;object-position:top;display:block}
.pk .tool.off{color:var(--mute)}
.pk .tool.off img{filter:grayscale(1) brightness(.75)}
.pk .tool.off::after{content:"";position:absolute;left:0;right:0;top:0;height:26px;
  background:linear-gradient(to top right,transparent calc(50% - 1.5px),var(--red) calc(50% - 1.5px),var(--red) calc(50% + 1.5px),transparent calc(50% + 1.5px))}
.badge{font-family:var(--display);font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;padding:1px 5px;border-radius:2px;background:var(--panel2);border:1px solid var(--line2)}
.badge.dmg{background:var(--red);border-color:var(--red);color:#fff}
.badge.cond{background:#5a8c3a;border-color:#5a8c3a;color:#fff}
/* Lukas 2026-09-20 afternoon: "Instead of wording PSN just put poison markers, burn markers" -- round TCG-style
   markers (condHtml() in arena.js) in place of the text badge for those two conditions. */
.mark{width:20px;height:20px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;border:1px solid rgba(0,0,0,.45);box-shadow:0 1px 2px rgba(0,0,0,.5);box-sizing:border-box}
.mark.psn{background:#7b3fb3}.mark.brn{background:#e0642a}
.pk.active{width:150px}.pk.active .nm{font-size:18px}.pk.active .hp{font-size:20px}
/* board-table-motion-design 2026-09-19 section A: the active spot -- a lit ring on the felt under the card.
   Real specificity on purpose (not :where()) -- the .legal/.sel/.dim state shadows below carry !important and
   always win over it regardless. */
.actrow .pk.active{box-shadow:0 0 0 2px rgba(255,255,255,.10),0 0 0 10px rgba(0,0,0,.18),0 0 60px 8px rgba(232,180,60,.16),0 14px 26px -8px rgba(0,0,0,.7)}
.rc{color:var(--gold);font-size:9px;letter-spacing:1px;line-height:1}
.pk .rc{position:absolute;right:5px;bottom:4px}
.card.big .rc{font-size:11px}
.pop{position:absolute;z-index:6;width:250px;background:var(--panel);border:1px solid var(--gold2);border-radius:4px;padding:8px 10px;box-shadow:0 12px 40px var(--shadow2)}
.pop .k{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--mute)}
.pop .actions{margin-top:6px}
/* board-table-motion-design 2026-09-19 section A: cards sit ON the felt -- a real contact shadow. :where() so
   this carries ZERO specificity and every state shadow below (.legal/.sel/.dim, .hit, the tool/attachment
   rules) keeps winning unchanged; .stad's own glow (near .stadium above) already gives the Stadium its own
   full box-shadow, so it is not repeated here. */
:where(.pk.pic,.hand .card,.stack.disc.pic){box-shadow:0 1px 0 rgba(255,255,255,.12) inset,0 1px 0 rgba(236,228,206,.4),0 2px 0 rgba(0,0,0,.55),0 3px 0 rgba(0,0,0,.3),0 2px 3px rgba(0,0,0,.5),0 12px 22px -6px rgba(0,0,0,.65)}   /* Lukas 2026-10-07 card stock: a paper edge, then a dark edge under the face */
.legal{box-shadow:0 0 0 2px var(--hi),0 0 18px 4px var(--hi-glow)!important;border-color:var(--hi)!important}
/* table-feel spec section 4 (2026-09-21): a hand card with at least one legal action (applyHighlights/targetsFor,
   arena.js) gets a soft cyan outer glow (--rim-me) instead of the board's usual gold .legal ring, so the
   affordance reads at a glance without competing with the Pokémon target highlight it points at once clicked. */
.hand .card.legal{box-shadow:0 0 0 2px var(--rim-me),0 0 16px 6px var(--rim-me)!important;border-color:transparent!important}
.dim{opacity:.5}
.sel{box-shadow:0 0 0 2px var(--ink)!important;border-color:var(--ink)!important}
/* board-arena 2026-09-19 section 2: a slot cut into the bench slab -- dashed, inset-dark, prop-ink label. */
.slot{width:118px;height:64px;border:1px dashed var(--prop-edge);border-radius:6px;display:flex;align-items:center;justify-content:center;color:var(--prop-ink);font-size:11px;letter-spacing:.08em;text-transform:uppercase;box-sizing:border-box;box-shadow:inset 0 0 30px rgba(0,0,0,.25)}
.slot.legal{border-style:solid;color:var(--hi);cursor:pointer}
/* Lukas 2026-10-07: ability ready = a soft gold breathing aura (.can-ability, applyHighlights); ability used = a gold burst
   + name plate (.abil-fire/.abilcue, applyAbilCues). .legal/.sel/.dim carry !important and win over the aura. */
@property --abang{syntax:"<angle>";inherits:false;initial-value:0deg}
.pk.can-ability,.stadium.can-ability,.pk.abil-fire,.stadium.abil-fire{--abr:0 1px 0 rgba(255,255,255,.12) inset,0 2px 3px rgba(0,0,0,.5),0 12px 22px -6px rgba(0,0,0,.65);--abc:#ffd66e;--abh:#fff6d0}
.stad .stadium.pic.can-ability,.stad .stadium.pic.abil-fire{--abr:0 0 0 1px rgba(255,255,255,.08),0 10px 20px -6px rgba(0,0,0,.7),0 0 0 0 transparent}
.stadium.can-ability{animation:abilbreath 2.4s ease-in-out infinite}
/* THE READY GLOW MOVES WITHOUT REPAINTING (2026-10-13, measured in real Chrome: the breath animated box-shadow and
   the comet a gradient angle, both on the card itself, and each frame repainted the whole card layer of the table,
   most of a processor core while a game only stood open). On a Pokémon the two moving parts are elements of their
   own, added by applyHighlights: .abglow carries the strong end of the breath and only its opacity moves, over the
   weak end that stays on the card; .abcomet is the ring-shaped window and the gradient inside it only turns. The
   browser moves both without painting. .legal/.sel still win: the breath is hidden under them.
   ponytail: the Stadium keeps the animated box-shadow and angle (its picture box cuts its children off, and a
   Stadium ability to use is rare); give it the same two elements if a Stadium glow ever shows up in a measurement. */
.pk.can-ability{box-shadow:var(--abr),0 0 0 1px rgba(255,214,110,.45),0 0 10px 2px rgba(255,196,70,.35)}
.abglow{position:absolute;inset:-1px;z-index:-1;border-radius:inherit;pointer-events:none;opacity:0;will-change:opacity;
  box-shadow:0 0 0 1.5px rgba(255,214,110,.9),0 0 26px 8px rgba(255,196,70,.75);animation:abilglow 2.4s ease-in-out infinite}
.pk.legal>.abglow,.pk.sel>.abglow,.pk.abil-fire>.abglow{display:none}
@keyframes abilglow{50%{opacity:1}}
.abcomet{position:absolute;inset:-4px;padding:3px;border-radius:9px;pointer-events:none;z-index:3;box-sizing:border-box;overflow:hidden;
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask:linear-gradient(#000 0 0) content-box exclude,linear-gradient(#000 0 0)}
.abcomet>i{position:absolute;left:50%;top:50%;width:250%;height:250%;translate:-50% -50%;will-change:transform;
  background:conic-gradient(transparent 0 55%,rgba(255,214,110,.15) 62%,var(--abc) 80%,var(--abh) 88%,var(--abc) 94%,transparent 100%);animation:abilturn 2.4s linear infinite}
.pk.evolved>.abcomet{display:none}   /* the evolve flash wins while it runs */
@keyframes abilturn{to{rotate:360deg}}
:root[data-theme="pokeball"] .pk.can-ability,:root[data-theme="pokeball"] .stadium.can-ability{--abc:#e08a00;--abh:#ffc040;outline:1px solid rgba(214,134,0,.9);outline-offset:0}
@keyframes abilbreath{0%,100%{box-shadow:var(--abr),0 0 0 1px rgba(255,214,110,.45),0 0 10px 2px rgba(255,196,70,.35)}50%{box-shadow:var(--abr),0 0 0 1.5px rgba(255,214,110,.9),0 0 26px 8px rgba(255,196,70,.75)}}
/* the running light: a gold comet around the card's edge (.pk::after is the evolve flash too, which wins while it runs) */
.stadium.can-ability::after{content:"";position:absolute;inset:-4px;padding:3px;border-radius:9px;pointer-events:none;z-index:3;box-sizing:border-box;
  background:conic-gradient(from var(--abang),transparent 0 55%,rgba(255,214,110,.15) 62%,var(--abc) 80%,var(--abh) 88%,var(--abc) 94%,transparent 100%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask:linear-gradient(#000 0 0) content-box exclude,linear-gradient(#000 0 0);
  animation:abilcomet 2.4s linear infinite}
.stadium.pic.can-ability::after{inset:0;border-radius:4px}
.stadium.can-ability:not(.pic){position:relative}
@keyframes abilcomet{to{--abang:360deg}}
/* the used cue: the card pops for 1.5 s with a gold aura, two rings bloom (outline, then a spread shadow 120 ms later), a plate names the ability */
.pk.abil-fire,.stadium.abil-fire{animation:abilpop 1.5s ease-out both,abilring .7s ease-out both;animation-delay:var(--abil-delay,0s),var(--abil-delay,0s)}
.stadium.abil-fire:not(.pic){position:relative}   /* the raise (z-index:60) lives inside abilpop, so it ends with the animation even if the class lingers */
@keyframes abilpop{
  0%{z-index:60;scale:1;box-shadow:var(--abr),0 0 0 2px rgba(255,214,110,.95),0 0 34px 12px rgba(255,196,70,.8)}
  12%{z-index:60;scale:1.22}
  80%{z-index:60;scale:1.22;box-shadow:var(--abr),0 0 0 2px rgba(255,214,110,.95),0 0 34px 12px rgba(255,196,70,.8)}
  100%{scale:1;box-shadow:var(--abr),0 0 0 2px rgba(255,214,110,0),0 0 34px 12px rgba(255,196,70,0)}}
.abilspark{position:absolute;left:50%;top:50%;width:300%;aspect-ratio:1;translate:-50% -50%;pointer-events:none;z-index:4;background:url(/static/fx/ability-burst.webp) 0 0/1400% 100% no-repeat;animation:abilspark .95s steps(14,jump-none) both,abilsparkfade .95s linear both}
@keyframes abilspark{from{background-position:0 0}to{background-position:100% 0}}
@keyframes abilsparkfade{0%,85%{opacity:1}100%{opacity:0}}
html.nofx .abilspark{display:none}
@keyframes abilring{0%{outline:3px solid rgba(255,214,90,.95);outline-offset:0}100%{outline:3px solid rgba(255,214,90,0);outline-offset:28px}}
.abilcue{position:absolute;left:50%;bottom:100%;margin-bottom:6px;z-index:5;pointer-events:none;white-space:nowrap;padding:4px 12px;border-radius:999px;background:linear-gradient(#ffe08a,#f2b83a);border:1px solid #8a5a00;color:#1a1405;font-family:var(--display);font-size:15px;font-weight:600;line-height:1.3;letter-spacing:.03em;box-shadow:0 2px 8px rgba(0,0,0,.5),0 0 14px rgba(242,193,78,.8);transform:translateX(-50%);animation:abilplate 1.5s ease-out both}
.row.opp .pk .abilcue,.actrow.opp .pk .abilcue{bottom:auto;top:100%;margin:6px 0 0;animation-name:abilplate-dn}
.stadium.pic .abilcue{bottom:auto;top:4px;margin:0;max-width:92%;overflow:hidden;text-overflow:ellipsis}
.pk.active.c-asl .abilcue{rotate:90deg}
.pk.active.c-par .abilcue{rotate:-90deg}
.pk.active.c-cnf .abilcue{rotate:180deg}
@keyframes abilplate{0%{opacity:0;transform:translate(-50%,6px)}14%{opacity:1;transform:translate(-50%,-3px)}80%{opacity:1;transform:translate(-50%,-4px)}100%{opacity:0;transform:translate(-50%,-6px)}}
@keyframes abilplate-dn{0%{opacity:0;transform:translate(-50%,-6px)}14%{opacity:1;transform:translate(-50%,3px)}80%{opacity:1;transform:translate(-50%,4px)}100%{opacity:0;transform:translate(-50%,6px)}}
@keyframes abilplate-still{0%,85%{opacity:1}100%{opacity:0}}
html.nofx .pk.can-ability,html.nofx .stadium.can-ability{animation:none;box-shadow:var(--abr),0 0 0 1.5px rgba(255,214,110,.9),0 0 26px 8px rgba(255,196,70,.75)}
html.nofx .stadium.can-ability::after,html.nofx .abcomet>i{animation:none;background:rgba(255,214,110,.8)}
html.nofx .abglow{display:none}
html.nofx .pk.abil-fire,html.nofx .stadium.abil-fire{animation:none}
html.nofx .abilcue{animation:abilplate-still 1.5s linear both!important;transform:translateX(-50%)}
@media (prefers-reduced-motion: reduce){
  .pk.can-ability,.stadium.can-ability{animation:none;box-shadow:var(--abr),0 0 0 1.5px rgba(255,214,110,.9),0 0 26px 8px rgba(255,196,70,.75)}
  .stadium.can-ability::after,.abcomet>i{animation:none;background:rgba(255,214,110,.8)}
  .abglow{display:none}
  .pk.abil-fire,.stadium.abil-fire{animation:none}
  .abilcue{animation:abilplate-still 1.5s linear both!important;transform:translateX(-50%)}
  .abilspark{display:none}
}
.pip{width:14px;height:14px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-family:var(--display);font-size:10px;font-weight:700;color:#0e1013;border:1px solid rgba(0,0,0,.35);flex:none}
.pip.dark{color:#fff}

/* Special Conditions + locks visual pass (Lukas-approved design, 2026-09-11; visibility pass same day after
   Lukas's screenshot review -- the first cut read as smudges, every effect below was rebuilt to be unmistakable
   at a glance for a non-pro player, not just "technically present"). Rotation is the official TCG rule (Asleep
   counter-clockwise, Paralyzed clockwise, Confused upside-down), on the Active only, via the CSS `rotate`
   property -- a property distinct from `transform`, so it never fights .pk.lunge-up/.lunge-down/.pk.hit or the
   card-flight vtlift keyframe (all of which animate `transform`); the two compose instead of clobbering each
   other. Only one of the three can be on a Pokemon at once (engine rule), so no ordering needed between them.
   Stays on with motion off (html.nofx below) -- it is information, not flourish. */
.pk.active.c-asl{rotate:-90deg}
.pk.active.c-par{rotate:90deg}
.pk.active.c-cnf{rotate:180deg}
.pk.active{transition:rotate .4s ease}
@media (prefers-reduced-motion: reduce){.pk.active{transition:none}}

/* One .fx overlay div per active effect, appended inside .pk after the picture/text body (arena.js pokemon()).
   No view-transition-name here ever (a duplicate name anywhere on the page aborts the whole card-flight motion).
   z-index:1 keeps every .fx below .pk.pic .badges (z-index:2) so the HP/damage/energy/condition badges on a
   pictured card stay readable under gas/flames/chains; .card already has overflow:hidden so a hand card's dust
   fits inside it too. */
.fx{position:absolute;inset:0;pointer-events:none;border-radius:inherit;overflow:hidden;z-index:1}
.pk.pic .badges{z-index:2}
/* readability under the 180 (Confused) rotation only: the badges would otherwise read upside down. +-90
   (Asleep/Paralyzed) is left sideways on purpose -- that's how a physical card lies when it's turned. */
.pk.active.c-cnf.pic .badges{rotate:180deg}
.pk.active.c-cnf:not(.pic)>*:not(.fx):not(.strip){rotate:180deg}

/* lock (retreat_locked, Active only), Lukas 2026-09-11 (chain redo #2 -- the gradient band read as sticks, not
   chains; and red, not gold). Exactly two ~16px bands, corner to corner in an X: each its own child <i> (arena.js
   fxDiv()) so the two rotate independently, painted with an inline-SVG chain-link tile (28x16, repeat-x along the
   band) instead of a gradient -- a hollow oval link (bevelled: a 4px dark #7a1010 stroke pass, then a 2px #ff6a5a
   highlight pass on top) with a smaller side-on link (a stroked rounded rect, plain #d42a2a) overlapping its right
   end, so the repeat reads as one chain's links alternating flat/edge-on. A red CSS padlock (#d42a2a body,
   #7a1010 shackle -- no emoji, it can't be recoloured) sits top-left; the short rattle every ~4s shakes the whole
   overlay (padlock + both chains) together. */
.fx.lock{animation:lockrattle 4s ease-in-out infinite}
.fx.lock i{position:absolute;left:50%;top:50%;width:230%;height:16px;
  background-repeat:repeat-x;background-size:28px 16px;
  /* the xmlns URL's scheme is percent-encoded (colon+slashes) -- test_static.py's self-contained check greps the
     raw CSS text for a literal web-scheme substring, which this data: URI is not. */
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width='28' height='16'><ellipse cx='11' cy='8' rx='11' ry='6' fill='none' stroke='%237a1010' stroke-width='4'/><ellipse cx='11' cy='8' rx='11' ry='6' fill='none' stroke='%23ff6a5a' stroke-width='2'/><rect x='20' y='5.5' width='8' height='5' rx='2' ry='2' fill='none' stroke='%23d42a2a' stroke-width='3'/></svg>");
  filter:drop-shadow(0 2px 3px rgba(0,0,0,.65))}
.fx.lock i:first-child{transform:translate(-50%,-50%) rotate(45deg)}
.fx.lock i:last-child{transform:translate(-50%,-50%) rotate(-45deg)}
.fx.lock::before{content:"";position:absolute;left:5px;top:3px;width:8px;height:7px;
  border:2px solid #7a1010;border-bottom:none;border-radius:5px 5px 0 0;
  filter:drop-shadow(0 1px 1px rgba(0,0,0,.6))}
.fx.lock::after{content:"";position:absolute;left:3px;top:8px;width:12px;height:9px;border-radius:2px;
  background:#d42a2a;box-shadow:0 1px 2px rgba(0,0,0,.6)}
@keyframes lockrattle{0%,90%,100%{transform:translateX(0)}91%{transform:translateX(-1.5px)}92%{transform:translateX(1.5px)}
  93%{transform:translateX(-1.5px)}94%{transform:translateX(1.5px)}95%{transform:translateX(0)}}

/* psn (poisoned): a steady violet tint + inner glow (always on, so the condition reads even with motion off),
   plus 4 violet wisps that rise continuously from the bottom edge to the top and fade -- multi-layer
   radial-gradients on one pseudo-element, animated by walking their shared background-position upward (a
   duplicate ::after at a phase offset keeps wisps rising continuously rather than in one pulse). */
.fx.psn{background:rgba(150,60,220,.18);box-shadow:inset 0 0 18px rgba(150,60,220,.8)}
/* THE GAS AND THE DUST RISE WITHOUT REPAINTING (2026-10-13): both moved their puffs by background-position, which
   repainted the whole card layer of the table on every frame for as long as a Pokémon was Poisoned or the hand was
   locked. Now the puffs stand still on a layer twice the card's height and the layer itself rises (translate and
   opacity only); .fx cuts it to the card. Sizes and places are the old ones re-counted for the taller box. The gas
   lost its 1px blur (the browser blurred both layers again on every frame; the puffs are soft gradients already).
   The dust moves 12 times a second, every card's dust in the same frames (one delay step = one twelfth of a second):
   each frame in which anything on the tilted table changes costs the same, so fourteen layers out of step cost a
   frame each. */
.fx.psn::before,.fx.psn::after{content:"";position:absolute;inset:0 0 -100% 0;
  background-repeat:no-repeat;
  background-image:radial-gradient(circle, rgba(165,75,235,.85), transparent 72%),
                    radial-gradient(circle, rgba(185,100,245,.8), transparent 72%),
                    radial-gradient(circle, rgba(150,55,220,.85), transparent 72%),
                    radial-gradient(circle, rgba(195,120,250,.75), transparent 72%);
  background-size:60% 23%,52% 20%,64% 25%,46% 19%;
  background-position:14% 41.4%,40% 47.3%,64% 37.3%,86% 46.7%;
  will-change:transform,opacity;animation:psngas 3s ease-in infinite}
.fx.psn::after{animation-delay:-1.5s;animation-duration:3.2s}
@keyframes psngas{
  0%{translate:0 0;opacity:0}
  22%{opacity:1}
  85%{opacity:.85}
  100%{translate:0 -48.5%;opacity:0}
}

/* brn (burned): flame tongues covering the bottom third (several radial-gradients at different x/height,
   flickering by shifting their background-size every ~.35s), an orange inner glow, a steady warm tint. */
.fx.brn{background:rgba(255,90,0,.15);box-shadow:inset 0 0 18px rgba(255,90,0,.85)}
.fx.brn::before{content:"";position:absolute;left:0;right:0;bottom:0;height:42%;
  background-repeat:no-repeat;
  background-image:radial-gradient(ellipse at 50% 100%, rgba(255,215,100,.95), transparent 62%),
                    radial-gradient(ellipse at 50% 100%, rgba(255,120,20,.95), transparent 55%),
                    radial-gradient(ellipse at 50% 100%, rgba(255,55,0,1), transparent 68%),
                    radial-gradient(ellipse at 50% 100%, rgba(255,140,30,.9), transparent 55%),
                    radial-gradient(ellipse at 50% 100%, rgba(255,195,70,.9), transparent 50%);
  background-position:50% 100%,18% 100%,50% 100%,82% 100%,66% 100%;
  background-size:62% 100%,42% 85%,72% 100%,42% 85%,34% 72%;
  animation:brnflicker .35s steps(2) infinite alternate}
@keyframes brnflicker{
  0%{background-size:62% 92%,42% 78%,72% 90%,42% 78%,34% 64%;opacity:.85}
  100%{background-size:70% 118%,48% 98%,80% 112%,48% 98%,40% 86%;opacity:1}
}

/* asl (asleep): a steady blue-grey dim + three Zs (sizes 14/18/22, bold, white on a dark stroke) rising staggered
   from the picture's centre. Only 2 pseudo-elements exist per node, so the third Z sits on .pk itself (mutually
   exclusive with confused's own .pk::before -- the engine never puts both conditions on one Pokemon at once). */
.fx.asl{background:rgba(70,90,120,.35)}
.fx.asl::before,.fx.asl::after,.pk.c-asl::before{content:"Z";position:absolute;left:46%;top:40%;font-weight:700;
  color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.85),0 0 5px rgba(255,255,255,.6);
  pointer-events:none;animation:zfloat 2.5s ease-in infinite}
.fx.asl::before{font-size:14px}
.fx.asl::after{font-size:18px;left:54%;top:34%;animation-delay:.8s}
.pk.c-asl::before{font-size:22px;left:50%;top:28%;animation-delay:1.6s;z-index:1}
@keyframes zfloat{0%{transform:translate(-50%,0) scale(.75);opacity:0}15%{opacity:1}100%{transform:translate(-50%,-38px) scale(1.2);opacity:0}}

/* par (paralyzed): 5 gold lightning glyphs scattered over the card (2 pseudo-elements, each a base glyph plus
   solid+glow text-shadow duplicates so one element renders several bolts at once), blinking in two out-of-phase
   groups, a yellow inner glow, and a whole-card flash between .05 and .25 tint. */
.fx.par{background:rgba(226,195,49,.1);box-shadow:inset 0 0 18px rgba(255,214,58,.8);animation:parflash .4s steps(1) infinite}
.fx.par::before,.fx.par::after{content:"\26A1";position:absolute;font-size:16px;line-height:1;color:#ffd83a;
  animation:parblink .4s steps(1) infinite}
.fx.par::before{left:12%;top:10%;
  text-shadow:0 0 6px rgba(255,216,58,1),0 1px 1px rgba(0,0,0,.7),
    44px 34px 0 #ffd83a,44px 34px 6px rgba(255,216,58,1),
    16px 78px 0 #ffd83a,16px 78px 6px rgba(255,216,58,1)}
.fx.par::after{left:62%;top:16%;animation-delay:.2s;
  text-shadow:0 0 6px rgba(255,216,58,1),0 1px 1px rgba(0,0,0,.7),
    -32px 56px 0 #ffd83a,-32px 56px 6px rgba(255,216,58,1)}
@keyframes parflash{0%,100%{background-color:rgba(226,195,49,.05)}50%{background-color:rgba(226,195,49,.25)}}
@keyframes parblink{0%,49%{opacity:1}50%,100%{opacity:.15}}

/* cnf (confused): three gold stars orbiting the top of the card, ALWAYS visible (the orbit keyframe only spins
   them -- opacity is never toggled, so there's no "off" phase to be caught mid-way in a screenshot); one
   shared position + orbit, three different (negative) animation-delays split them evenly around the circle. The
   third star sits on .pk itself (mutually exclusive with asleep's own .pk::before). Plus a slight wobble on the
   picture itself -- the wobble sits on the .pic img (a `transform`), never on .pk's `rotate` (the rule rotation). */
.fx.cnf{background:rgba(255,255,255,.06)}
.fx.cnf::before,.fx.cnf::after,.pk.c-cnf::before{content:"\2605";position:absolute;left:50%;top:16%;font-size:14px;
  line-height:1;color:#ffe066;text-shadow:0 0 2px rgba(0,0,0,.9),0 0 5px rgba(255,224,102,1),0 0 9px rgba(255,224,102,.65);
  pointer-events:none;transform-origin:0 22px;animation:orbit 2.1s linear infinite}
.fx.cnf::after{animation-delay:-.7s}
.pk.c-cnf::before{animation-delay:-1.4s;z-index:1}
@keyframes orbit{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
.pk.c-cnf.pic img{animation:cnfwobble 2.2s ease-in-out infinite}
@keyframes cnfwobble{0%,100%{transform:rotate(0deg)}25%{transform:rotate(-2deg)}75%{transform:rotate(2deg)}}

/* Budew / item lock (Lukas 2026-09-11): ~10 bright yellow-green sparkle dots per card (2 pseudo-elements, 5
   radial-gradient dots each) rising and twinkling, staggered by --i between cards, a green inner glow, distinct
   from poison's violet. .fx.dust on a specific Item/Tool card in your own hand (arena.js render()); one .fx.dust
   over the whole opponent hand row (face-down, so per-card is not possible -- oppHandRow()). */
.fx.dust{background:rgba(120,220,80,.08);box-shadow:inset 0 0 14px rgba(120,220,80,.8)}
.fx.dust::before,.fx.dust::after{content:"";position:absolute;inset:0 0 -100% 0;background-repeat:no-repeat;will-change:transform,opacity;
  animation:dustrise 2s infinite;animation-delay:calc(var(--i, 0) * 1s / 12)}
.fx.dust::before{
  background-image:radial-gradient(circle, rgba(170,255,90,1), transparent 68%),
                    radial-gradient(circle, rgba(170,255,90,1), transparent 68%),
                    radial-gradient(circle, rgba(170,255,90,1), transparent 68%),
                    radial-gradient(circle, rgba(170,255,90,1), transparent 68%),
                    radial-gradient(circle, rgba(170,255,90,1), transparent 68%);
  background-size:9% 3.5%,7% 3%,10% 3.5%,7% 3%,8% 3%;background-position:8% 48.2%,26% 51.3%,46% 46.3%,66% 52.3%,86% 48.5%}
.fx.dust::after{animation-name:dustrise2;animation-duration:2.5s;animation-delay:calc(var(--i, 0) * 1s / 12 + .5s);
  background-image:radial-gradient(circle, rgba(195,255,130,1), transparent 68%),
                    radial-gradient(circle, rgba(195,255,130,1), transparent 68%),
                    radial-gradient(circle, rgba(195,255,130,1), transparent 68%),
                    radial-gradient(circle, rgba(195,255,130,1), transparent 68%),
                    radial-gradient(circle, rgba(195,255,130,1), transparent 68%);
  background-size:7% 3%,9% 3.5%,7% 3%,8% 3%,7% 3%;background-position:8% 48.5%,26% 51.1%,46% 46.5%,66% 52.3%,86% 48.5%}
@keyframes dustrise{   /* 2 s = 24 steps */
  0%{opacity:0;translate:0 0;animation-timing-function:steps(6)}
  25%{opacity:1;translate:0 -16.75%;animation-timing-function:steps(18)}
  100%{opacity:0;translate:0 -67%}
}
@keyframes dustrise2{   /* 2.5 s = 30 steps */
  0%{opacity:0;translate:0 0;animation-timing-function:steps(6)}
  20%{opacity:1;translate:0 -13.4%;animation-timing-function:steps(24)}
  100%{opacity:0;translate:0 -67%}
}
.hand.opp .fx.dust{z-index:40}   /* above every fanned .oc back (z-index up to hand size), below .who (50) */
.itemlock{position:relative}
.locktag{position:absolute;top:3px;right:8px;font-family:var(--display);font-size:10px;letter-spacing:.1em;
  text-transform:uppercase;color:#9be26a;background:rgba(20,23,28,.6);border:1px solid rgba(120,220,80,.75);
  padding:1px 6px;border-radius:3px;pointer-events:none;z-index:5}

/* motion off (Profile "Animations" switch or the system's reduced-motion setting -- arena.js render() sets
   html.nofx from motionOn()): every .fx keyframe stops (the two .pk::before Zs/stars included), but the static
   tints/glows/glyphs/chains all stay (nothing here uses display:none) -- the condition must still read at a
   glance with motion off. The rule rotation above is untouched either way -- it is information, not flourish. */
html.nofx .fx,html.nofx .fx::before,html.nofx .fx::after,
html.nofx .pk.c-asl::before,html.nofx .pk.c-cnf::before{animation:none!important}
html.nofx .pk.pic img{animation:none!important}
@media (prefers-reduced-motion: reduce){
  .fx,.fx::before,.fx::after,.pk.c-asl::before,.pk.c-cnf::before{animation:none!important}
  .pk.pic img{animation:none!important}
}

/* hand + cards */
/* Lukas 2026-09-17 (mat polish): 30px bottom air -- the fan used to sit flush on the viewport's bottom edge, its
   own drop shadow cut off; the hand row (mat's last grid row) grows to match, see .mat below. */
.hand{grid-column:1/4;display:flex;align-items:flex-end;justify-content:center;padding:8px 0 30px;border-top:1px solid var(--line);perspective:900px}   /* the fan's arc drops the outer cards ~10px: the bottom padding keeps them inside the viewport; perspective (board-table-motion-design B.1) is what makes the draw's rotateY(180deg) flip read as a card turning over, not squashing flat */
.card{width:104px;height:132px;background:var(--panel);border:1px solid var(--line);border-radius:5px;position:relative;font-size:11px;box-shadow:0 3px 8px var(--shadow);transition:transform .12s ease;cursor:pointer;flex:none;box-sizing:border-box;overflow:hidden}
.card .strip{height:5px;border-radius:5px 5px 0 0}
.card .hd{display:flex;justify-content:space-between;align-items:baseline;padding:5px 7px 0;gap:4px}
.card .nm{font-family:var(--display);font-size:14px;font-weight:600;line-height:1.05}
.card .hp{font-family:var(--display);font-size:14px;font-weight:600;color:var(--ink2)}
.card .sub{font-size:10px;color:var(--mute);letter-spacing:.08em;text-transform:uppercase;padding:1px 7px}
.card .body{padding:5px 7px;color:var(--ink2);line-height:1.3;font-size:10.5px}
.card .atk{display:flex;justify-content:space-between;gap:4px;margin-top:3px;align-items:center}
.card .atk .c{display:flex;gap:2px}.card .atk .dmg{font-family:var(--display);font-weight:600;color:var(--ink)}
.card.sel{transform:translateY(-14px)}
.card .foot{position:absolute;left:7px;right:7px;bottom:5px;display:flex;justify-content:space-between;font-size:9px;color:var(--mute);letter-spacing:.06em;text-transform:uppercase}
.card .energyface{display:flex;align-items:center;justify-content:center;height:60px}
.card.big{width:220px;margin:0 auto;height:auto;min-height:0;font-size:13px;cursor:default;transform:none}
.card.big .nm{font-size:22px}.card.big .hp{font-size:22px}.card.big .sub{font-size:11px}.card.big .body{font-size:12.5px;padding:8px 10px;white-space:pre-wrap}
.card.big .atk{margin-top:8px}.card.big .atk .dmg{font-size:16px}
.card.big .foot{position:static;padding:8px 10px 6px;border-top:1px solid var(--line);margin-top:8px}
.card.big .body b{color:var(--ink)}

/* right panel (table-feel spec section 1, 2026-09-21): 330px open / 0 closed, the desktop rail's own tab buttons
   (below) are the only open/close control now -- the old .sidetoggle chevron and 36px collapsed-rail tab strip
   are gone outright. overflow:hidden so a closed (0-width) panel never spills its content into the mat. */
.side{position:relative;border-left:1px solid var(--line);display:grid;grid-template-rows:auto 1fr;min-height:0;background:var(--felt2);overflow:hidden}
.panelhead{padding:8px 12px;border-bottom:1px solid var(--line)}
.panelhead:empty{padding:0;border-bottom:0}
.panelhead .wp{display:flex;align-items:center;gap:8px}
.panelhead .wpn{font-family:var(--display);font-variant-numeric:tabular-nums;font-size:14px;color:var(--gold);min-width:38px;text-align:right}
.panelhead .wpbar{display:block;width:140px;height:10px;background:var(--panel2);border:1px solid var(--line2);border-radius:2px;overflow:hidden}
.panelhead .wpbar .me{display:block;height:100%;background:linear-gradient(90deg,var(--gold2),var(--gold));transition:width .6s ease}
.panelhead .wp.stale{opacity:.55}
/* the phone's own bottom tab bar (its .tabs still lives inside .side, unlike the desktop rail's) -- unchanged. */
.tabs{display:flex;border-bottom:1px solid var(--line)}
.tabs span{display:inline-block;padding:8px 14px;font-family:var(--display);font-size:14px;font-weight:600;letter-spacing:.04em;color:var(--mute);border-bottom:2px solid transparent;cursor:pointer}
.tabs span.on{color:var(--gold);border-color:var(--gold)}
/* a Log-only game (human vs human, or an observer) still renders #tabs -- .single only matters on the phone
   (below, always shown there); the desktop rail shows its one tab button same as any other. */
.tabs.single{display:none}
html[data-side=off] .side .pane{display:none}
.pane{overflow:auto;min-height:0;padding:12px;display:flex;flex-direction:column;scrollbar-width:none}
.pane::-webkit-scrollbar{width:0;height:0}
.pane .k{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--mute);margin-bottom:8px}
.actions{display:flex;flex-direction:column;gap:6px;margin-top:10px}
.act{display:flex;justify-content:space-between;align-items:center;gap:8px;background:var(--panel);border:1px solid var(--line2);padding:9px 10px;border-radius:3px;cursor:pointer}
.act.go{border-color:var(--gold2);background:var(--go-bg)}.act.no{opacity:.45;cursor:default}
/* Lukas 2026-09-20: Ability / Attacks / Retreat headers so a mis-click doesn't fire the wrong section (Ability,
   Attacks, Retreat, in that order -- pokemonActs in arena.js). */
.actions .sec{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--mute);margin:8px 0 -2px 2px}
.actions .sec:first-child{margin-top:0}
.act .n{font-family:var(--display);font-size:15px;font-weight:600}
.act .c{display:flex;gap:3px;align-items:center;font-size:11px;color:var(--ink2)}
.act .dmg{font-family:var(--display);font-size:16px;font-weight:600}
#coach .act{flex-direction:column;align-items:stretch;gap:4px}
#coach .act .row1{display:flex;align-items:center;gap:8px}
#coach .act .rank{min-width:18px;color:var(--gold2);font-family:var(--display);font-weight:600}
#coach .act .score{margin-left:auto;font-family:var(--display);font-variant-numeric:tabular-nums;color:var(--ink2)}
#coach .act.veto{opacity:.55}
#coach .act.ro{cursor:default;opacity:.95}
#coach .act .rules{display:flex;flex-wrap:wrap;gap:4px}
.pv{font-size:11px;color:var(--mute)}
.tag.search{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--gold);border:1px solid var(--gold2);border-radius:3px;padding:0 4px}
#coach .rule,#trainer .rule{font-size:10.5px;padding:1px 5px;border-radius:3px;background:var(--rule-bg);color:var(--rule-fg);border:1px solid var(--rule-ln)}
#coach .rule.neg,#trainer .rule.neg{background:var(--rule-neg-bg);color:var(--rule-neg-fg);border-color:var(--rule-neg-ln)}
#coach .rule,#trainer .rule{cursor:help}
#tip{position:fixed;z-index:60;max-width:370px;background:var(--panel2);border:1px solid var(--gold2);border-radius:4px;padding:8px 10px;font-size:12px;line-height:1.4;color:var(--ink);pointer-events:none;box-shadow:0 6px 20px var(--shadow2)}
#tip b{color:var(--gold)}#tip .fired{color:var(--mute);font-size:11px;margin:2px 0 4px}
#trainer .act{flex-direction:column;align-items:stretch;gap:4px;cursor:default}
#trainer .act .row1{display:flex;align-items:center;gap:8px}
#trainer .act .rank{min-width:24px;color:var(--gold2);font-family:var(--display);font-weight:600;font-size:12px}
#trainer .act .score{margin-left:auto;font-family:var(--display);font-variant-numeric:tabular-nums;color:var(--ink2);white-space:nowrap}
#trainer .act .rules{display:flex;flex-wrap:wrap;gap:4px}
#trainer .vs{border:1px solid var(--gold2);background:var(--vs-bg);padding:10px;margin-bottom:12px;display:flex;flex-direction:column;gap:6px}
#trainer label{display:block;font-size:12px;color:var(--ink2);margin-top:6px}
#trainer textarea{display:block;width:100%;box-sizing:border-box;margin-top:3px;font-size:12.5px;line-height:1.35;resize:vertical}
#trainer .btns{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}
#trainer .grade{display:flex;gap:6px;align-items:center;margin-top:2px}
#trainer .grade select{font-size:11.5px;max-width:46%}
#trainer .grade input{flex:1;font-size:11.5px;min-width:0}
.tabs span .dot{color:var(--gold);font-size:10px;vertical-align:middle}
#coach .sub.caps{font-variant:small-caps;font-size:12px;color:var(--mute);margin-bottom:8px}
.grid{display:flex;gap:8px;flex-wrap:wrap}
.grid .card{width:84px;height:106px}
.grp{font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--mute);margin:10px 0 4px}
#log ul{list-style:none;margin:0;padding:0;flex:1;overflow:auto;font-size:12.5px;scrollbar-width:none}
#log ul::-webkit-scrollbar{width:0;height:0}
#log li{padding:4px 0;border-bottom:1px solid var(--line);color:var(--ink2);display:flex;gap:8px}
#log li .t{font-family:var(--display);color:var(--mute);min-width:26px;font-variant-numeric:tabular-nums}
#log li.me{color:var(--ink)}#log li.k-chat{color:var(--gold)}#log li.k-undo,#log li.k-concede{font-style:italic}#log li.k-clock{color:var(--red);font-style:italic}
#log form{display:flex;gap:6px;margin-top:8px}#log input{flex:1}
/* the log as a match report (2026-09-17 board-panel spec): a sticky turn header, a thumbnail on a row that names a
   card, the attack row bold with its damage folded in red, a Knock Out's red left border, noise in the mute style.
   Class names (Lukas's review, 09-17): k-* on every kind -- "row"/"clock"/"attack"/"ko"/"mute"/"turnhead" without
   the prefix are live classes elsewhere on the page (the bench .row, the topbar .clock pill) or plausible future
   ones, and would otherwise silently pick up unrelated rules the moment they matched. */
#log li.k-turnhead{border-bottom:0;padding:10px 0 3px;font-family:var(--display);font-size:12px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--ink2);position:sticky;top:0;background:var(--felt2);z-index:1}
#log li.k-turnhead.me{color:var(--gold)}
#log li.play{align-items:flex-start}
#log li .thumb{width:20px;height:27px;object-fit:cover;border-radius:2px;flex:none;box-shadow:0 1px 3px var(--shadow)}
#log li.k-attack b{font-family:var(--display);font-size:13px;color:var(--ink)}
#log .dmg{color:var(--red);font-weight:600}
#log li.k-attack .k-mute{margin-top:2px}
#log li.k-ko{border-left:3px solid var(--red);padding-left:6px}
#log li.k-win{border-left:3px solid var(--gold);padding-left:6px;font-weight:600;color:var(--ink)}
#log li.k-mute,#log li .k-mute{color:var(--mute);font-size:11.5px}
#log li .k-thumbs{display:flex;flex-wrap:wrap;gap:2px;margin-top:3px}   /* a mulligan's revealed hand (Lukas 2026-09-25) */
.sheet{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;white-space:pre-wrap;color:var(--ink2);margin-top:8px;border-top:1px solid var(--line);padding-top:8px}

/* overlays */
#overlay{position:fixed;inset:0 calc(84px + var(--panelw,0px)) 0 0;background:var(--overlay);display:flex;align-items:center;justify-content:center;z-index:10} /* covers the playmat only: the rail (84px) and the panel (--panelw) stay clickable during a Choice and after game over */
#overlay .box{background:var(--panel);border:1px solid var(--gold2);padding:16px 18px;max-width:96%;max-height:calc(100vh - 64px);overflow:auto;box-shadow:0 12px 40px var(--shadow2);display:flex;flex-direction:column}   /* the deck check must fit the screen (Lukas 2026-09-11): wider, taller, smaller cards (.grid .card) */
#overlay .grid{overflow:auto;min-height:0;flex:1 1 auto;align-content:flex-start;padding:2px}   /* only the card grid scrolls (2px keeps a .card.picked glow from clipping; align-content packs wrapped rows at the top) */
#overlay .prompt{font-family:var(--display);font-size:20px;font-weight:600}
#overlay .sub{color:var(--ink2);font-size:13px;margin:2px 0 12px}
#overlay .ctr{display:flex;justify-content:space-between;align-items:center;margin-top:12px;gap:10px}
#overlay .btns{display:flex;gap:8px;flex-wrap:wrap}
.card.pick{box-shadow:0 0 0 2px var(--hi)}
.card.picked{box-shadow:0 0 0 3px var(--green),0 0 14px 2px rgba(95,179,106,.5)!important;border-color:var(--green)!important}
.card.picked::after{content:"✓";position:absolute;top:4px;right:5px;width:18px;height:18px;border-radius:50%;background:var(--green);color:#fff;font-weight:700;font-size:12px;display:flex;align-items:center;justify-content:center}
#overlay .sub.caps{font-variant:small-caps;letter-spacing:.06em;font-size:13px;color:var(--ink2)}
/* Lukas 2026-09-20 afternoon: "fix these modal panels for choosing first or second or pokemon ones -- their
   textboxes overlap pokemon card". The old sticky footer sat over the last card row. #overlay .box is now a flex
   column: only #overlay .grid scrolls, this footer just sits below it in normal flow and never covers a card. */
#overlay .ctr{flex:none;padding-top:10px}
.wait button{margin-left:10px}
/* WATCH LINK AS A TOP-BAR ICON (Lukas 2026-09-18): "Watch link make an icon and on top." A plain icon-only button
   beside Concede -- SHOW OPPONENT'S PLAYS moved to Profile and Feedback moved to the floating .fab (2026-09-18),
   so .topbar .opt (their old checkbox/button spot) has nothing left to style. */
.topbar .tools button.icon{display:flex;align-items:center;justify-content:center;padding:4px 8px}

/* opponent reveal, Lukas 2026-09-11: the opponent's fresh plays from their hand (bench/evolve/attach/play a
   Trainer), shown enlarged and face up in a fan at centre mat for a beat before the ordinary card-flight motion
   carries them out to their zones (arena.js "opponent reveal", REVEAL_HOLD). Sits above the mat (the top-left
   action feed this used to also sit above is gone, Lukas 2026-09-22), below the Choice modal (#overlay 10) and
   #toast (20); a click on it (or anywhere) cuts the hold short. */
#reveal{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:9;display:flex;flex-wrap:wrap;justify-content:center;gap:14px;max-width:94vw;pointer-events:auto;cursor:pointer}
#reveal .card.reveal{width:190px;height:256px}
#reveal .card.reveal:not(.pic){zoom:1.8}
/* 2026-10-09: many cards at once (Professor's Research, an Ultra Ball and its find): --rc a row, --rr rows (arena.js
   refreshRevealFan picks them) -- the width is the smallest of 190px, a row's share of the viewport and a row's share of the
   height; the box gets its own width because at left:50% it would otherwise wrap at half the window. A card without a
   picture (pictures off) keeps the old fixed size. */
#reveal[style*="--rc"]{--w:min(190px,calc((92vw - (var(--rc) - 1)*var(--rg))/var(--rc)),calc((78vh - (var(--rr) - 1)*var(--rg))/var(--rr)*.742));gap:var(--rg);width:calc(var(--rc)*var(--w) + (var(--rc) - 1)*var(--rg) + 1px);max-width:none}
#reveal[style*="--rc"] .card.reveal.pic{width:var(--w);height:calc(var(--w)/.742)}
/* Lukas 2026-10-12: a played Trainer and the cards that left the hand with it (Ultra Ball's two): the Trainer to the side,
   those cards in a panel next to it (refreshRevealFan: --rc is the panel's columns, the Trainer is one column more) */
#reveal.fanlead[style*="--rc"]{flex-wrap:nowrap;align-items:center;gap:24px;width:max-content;
  --w:min(190px,calc((92vw - 50px - var(--rc)*var(--rg))/(var(--rc) + 1)),calc((78vh - (var(--rr) - 1)*var(--rg))/var(--rr)*.742))}
.fancost{display:grid;grid-template-columns:repeat(var(--rc),auto);gap:var(--rg);padding:12px;border-radius:16px;background:rgba(8,12,24,.78);border:1px solid var(--line2);box-shadow:0 18px 40px -14px #000}

#toast{position:fixed;left:50%;top:60px;transform:translateX(-50%);z-index:20;display:flex;align-items:center;gap:16px;background:var(--panel);border:1px solid var(--gold2);padding:12px 18px;box-shadow:0 12px 40px var(--shadow2)}
#toast .what{font-family:var(--display);font-size:18px;font-weight:600}
#toast .res{font-size:13px;color:var(--ink2)}#toast .res b{color:var(--ink)}
/* YOUR-TURN FLASH (Lukas 2026-09-18): "When your turn comes show the message in the middle with fade out Your
   turn or something. It's important with timer especially." Centred over the mat, pop in / hold / fade out --
   arena.js (applySnapshot's edge guard) mentions `prev` and `to_act`; removes itself on animationend.
   Render round (Lukas, real Chrome): the first pass read as ~30px, easy to miss behind a pending prompt -- the
   display face at clamp(40px, 5vw, 72px) (~64px on a desktop viewport, never smaller than 40px on a narrow one,
   never bigger than 72px on a huge one) on a soft dark pill so it reads over any card underneath, and z-index
   well above .pop (6) so a pending action box never covers it. */
/* table-feel spec section 5 (2026-09-21): the old floating YOUR-TURN pill is now a full-width band across the
   mat's middle, for both edges (arena.js turnFlashMine/turnFlashTxt) -- mine in --gold, theirs in --ink2, an
   --overlay band (the same translucent strip every other stencil pill on the mat already uses) instead of the
   pill's own dark/blur background. */
.turnflash{position:absolute;left:0;right:0;top:50%;transform:translateY(-50%);height:64px;pointer-events:none;z-index:30;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  font-family:var(--display);font-weight:700;letter-spacing:.08em;font-size:26px;background:var(--overlay);
  text-shadow:0 4px 16px var(--shadow2);animation:turnband 1.4s ease-out forwards}
.turnflash.mine{color:var(--gold)}
.turnflash.theirs{color:var(--ink2)}
.turnflash small{font-size:11px;font-weight:500;letter-spacing:.1em;color:var(--ink2);text-shadow:none}
/* slides in from the left (the clip window grows from the left edge) and out to the right (the window then
   shrinks away toward the right) over the full 1.4s -- clip-path, not translateX, since the band is already
   full-width (left:0;right:0) so there's nothing to translate into place. */
@keyframes turnband{
  0%{clip-path:inset(0 100% 0 0)}
  15%{clip-path:inset(0 0 0 0)}
  75%{clip-path:inset(0 0 0 0)}
  100%{clip-path:inset(0 0 0 100%)}
}
/* html.nofx (Profile "Animations" off, or the system's reduced-motion setting -- motionOn() folds both) still
   shows the band, just without the slide: a flat hold then a 0.6s fade (render()'s turnFlashDur matches this). */
html.nofx .turnflash{animation:turnband-reduced .6s linear forwards}
@keyframes turnband-reduced{0%,70%{opacity:1}100%{opacity:0}}
/* COIN (table-feel spec section 6, 2026-09-21, superseding the 2026-09-18 3D-flip pass): centred over the mat
   now instead of a corner #toast box, and a real coin picture (Higgsfield, static/coin-heads.webp/
   coin-tails.webp) instead of a CSS disc with WON/LOST or HEADS/TAILS text on it -- "no text on the coin".
   z-index 30, same as .turnflash, but pointer-events:none -- purely decorative, never a click target.
   Fix round (real-Chrome review): #coinstage is a child of .arena on the desktop now, not .mat -- .mat's own
   transform (the tilt) makes it a stacking context nothing inside it can ever paint above regardless of its own
   z-index, so the coin was stuck under #overlay's pending-Choice dim (10) even at z-index 30. left:0/right
   (mirroring the rail+panel width, not a fixed 50%/translate pair) spans exactly the mat's own column inside
   .arena's wider box, so the coin still centres over the BOARD, not the whole viewport including the rail; top:
   30% (not 50%) so the centred "You won the flip. Go first?" choice box, which stays at 50%, never covers it. */
.coinstage{position:absolute;left:0;right:calc(84px + var(--panelw,0px));top:30%;transform:translateY(-50%);z-index:30;
  display:flex;flex-direction:column;align-items:center;gap:8px;pointer-events:none}
.coinstage[hidden]{display:none}
.coinrow{display:flex;gap:12px}
/* .coin keeps its preserve-3d stacking-context role so its two .f faces (below) can be spun through to the
   called one; 132px for a single flip, 56px each in a .coinrow for a multi-flip (was 56px / 32px inline). the
   gradient discs (.coin.h/.coin.t) move here, UNDER the <img> faces, as the fallback background a failed image
   (data-onerror=".coin" below) still shows -- "keep the current gradient discs as the fallback background under
   the <img> so a 404 still shows a coin". */
.coin{width:132px;height:132px;border-radius:50%;flex:none;transform-style:preserve-3d;position:relative;perspective:600px}
.coinrow .coin{width:56px;height:56px}
.coin.h{background:radial-gradient(circle at 35% 30%,#ffe08a,#c99a2e 70%);box-shadow:0 0 18px 4px var(--glow)}
.coin.t{background:radial-gradient(circle at 35% 30%,#8f96a3,#4d5563 70%)}
.coin .f{position:absolute;inset:0;width:100%;height:100%;border-radius:50%;backface-visibility:hidden;object-fit:cover}
.coin .f.t{transform:rotateY(180deg)}
/* a face that 404s hides itself (the delegated error listener, arena.js -- data-onerror=".coin" adds .noimg to
   the closest .coin), leaving the gradient disc above visible clean instead of a broken-image glyph. */
.coin.noimg .f{display:none}
/* a coin that is NOT spinning (showCoins drops the "flip" class under reduced motion / html.nofx) must still show
   its result face: the tails face is turned 180deg locally, so the whole coin turns 180deg (same axis as the
   spin below) to bring it to the front (Fable review, 2026-09-18: without this a tails result showed the HEADS
   face over the flat silver .coin.t disc). */
.coin.t:not(.flip){transform:rotateX(180deg)}
/* the spin: heads settles after a full 5 turns (1800deg), tails after 5.5 (1980deg) so the .f.t face (already
   locally turned 180deg above) ends up pointing at the viewer instead of the .f.h one -- rotateX now, not
   rotateY (table-feel spec section 6: "tumbles end over end like a real toss" instead of a face-to-face spin;
   the same degree math still lands the right face -- a 180deg turn about any axis perpendicular to the viewer
   flips a face's front/back the same way, X or Y). A translateY rise peaks at 45%, a rotateZ drift reaches 25deg
   by the landing, then a 2-bounce settle (0 -> -8 -> 0 -> -3 -> 0) over the last ~300ms of whatever duration is
   in force (the percentages below are duration-agnostic, so the multi-flip row's own shorter inline
   animation-duration, unchanged from before, compresses the same sequence instead of needing its own keyframes).
   Multi-flip coins (showCoins() in arena.js) override animation-duration/-delay inline per coin so a row lands
   staggered, one after another, by --i. */
@keyframes coin-h{
  0%{transform:translateY(0) rotateX(0) rotateZ(0)}
  45%{transform:translateY(-90px) rotateX(900deg) rotateZ(12deg)}
  83.3%{transform:translateY(0) rotateX(1800deg) rotateZ(25deg)}
  87.5%{transform:translateY(-8px) rotateX(1800deg) rotateZ(25deg)}
  91.7%{transform:translateY(0) rotateX(1800deg) rotateZ(25deg)}
  95.8%{transform:translateY(-3px) rotateX(1800deg) rotateZ(25deg)}
  100%{transform:translateY(0) rotateX(1800deg) rotateZ(25deg)}
}
@keyframes coin-t{
  0%{transform:translateY(0) rotateX(0) rotateZ(0)}
  45%{transform:translateY(-90px) rotateX(990deg) rotateZ(12deg)}
  83.3%{transform:translateY(0) rotateX(1980deg) rotateZ(25deg)}
  87.5%{transform:translateY(-8px) rotateX(1980deg) rotateZ(25deg)}
  91.7%{transform:translateY(0) rotateX(1980deg) rotateZ(25deg)}
  95.8%{transform:translateY(-3px) rotateX(1980deg) rotateZ(25deg)}
  100%{transform:translateY(0) rotateX(1980deg) rotateZ(25deg)}
}
.coin.flip.h{animation:coin-h 1.8s cubic-bezier(.2,.7,.2,1) both}
.coin.flip.t{animation:coin-t 1.8s cubic-bezier(.2,.7,.2,1) both}
/* the shadow disc under the coin -- shrinks while the coin is up, matching the same rise (45% peak), back to
   full while the coin is down (landed/static). Static (no "flip") shows the resting size throughout. */
.coinshadow{width:70px;height:16px;border-radius:50%;background:rgba(0,0,0,.45);filter:blur(3px)}
.coinshadow.flip{animation:coinshadow 1.8s ease-out both}
@keyframes coinshadow{0%{transform:scale(1);opacity:.7}45%{transform:scale(.5);opacity:.3}100%{transform:scale(1);opacity:.7}}
/* the toast's result line fades in once the (last) coin lands -- arena.js sets --land on .cointoast to the exact
   landing time (0s under reduced motion, see below), so .res never reads before the coin does. Scoped to
   .cointoast (not a bare .res) so flash()/showMill()'s own toasts, which reuse .res, are unaffected. */
@keyframes coin-res{from{opacity:0;transform:translateY(4px)} to{opacity:1;transform:none}}
.cointoast{text-align:center}
.cointoast .what{font-family:var(--display);font-size:14px;color:var(--ink2)}
.cointoast .res{font-family:var(--display);font-size:16px;font-weight:600;animation:coin-res .3s both;animation-delay:var(--land,1.5s)}
/* reduced motion (system setting or html.nofx -- both conventions already used elsewhere in this file, e.g. the
   .turnflash/.deck3d rules): no spin -- .coin.flip.t is re-posed statically (its .f.t face is already turned
   180deg locally, so turning the whole coin another 180deg on the same axis brings that face to the front), the
   shadow holds its resting size, and .res shows at once instead of fading in. */
@media (prefers-reduced-motion: reduce){
  .coin.flip{animation:none}
  .coin.flip.t{transform:rotateX(180deg)}
  .coinshadow.flip{animation:none}
  .cointoast .res{animation:none}
}
html.nofx .coin.flip{animation:none!important}
html.nofx .coin.flip.t{transform:rotateX(180deg)}
html.nofx .coinshadow.flip{animation:none!important}
html.nofx .cointoast .res{animation:none!important}
.wait{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background:var(--panel);border:1px solid var(--line2);padding:8px 14px;color:var(--ink2);z-index:5}
.result{text-align:center}.result h2{font-family:var(--display);font-size:32px;margin:0 0 6px}
.err{color:var(--red);font-size:12px}
@media (prefers-reduced-motion: reduce){.card{transition:none}}

/* motion: only the named cards animate; the page itself swaps instantly (no whole-board crossfade).
   A travelling card lifts (scale + shadow) and keeps its old face for most of the flight, swapping late so the
   move reads as a card being carried, not a blur. Own plays .5 s, the opponent's .75 s. */
/* replay (spec 2026-09-20, real-Chrome round 4): the transition's pseudo-element tree covers the page for 0.5 s per
   animated step and swallowed clicks on the replay bar (and any live button) -- let pointer events through it */
::view-transition{pointer-events:none}
::view-transition-group(*),::view-transition-old(*),::view-transition-new(*),::view-transition-image-pair(*){animation-duration:.65s;animation-timing-function:cubic-bezier(.35,.9,.25,1)}
html[data-vt=opp]::view-transition-group(*),html[data-vt=opp]::view-transition-old(*),html[data-vt=opp]::view-transition-new(*),html[data-vt=opp]::view-transition-image-pair(*){animation-duration:.9s}
::view-transition-image-pair(.move){animation-name:vtlift}
::view-transition-old(*){animation-name:vtold}
::view-transition-new(*){animation-name:vtnew}
html[data-vt]::view-transition-new(*):only-child{animation:vtin .25s ease-out}   /* a card that only appears (no old spot): a quick fade in, not the flight's late face swap */
html[data-vt]::view-transition-old(*):only-child{animation:vtout .2s ease-in}    /* a card that only leaves: a quick fade out */
@keyframes vtout{from{opacity:1}to{opacity:0}}
/* board-table-motion-design 2026-09-19 B.2: an arc with a settle -- the same lift + drop-shadow ramp as before,
   retimed (peak at 35% not 40%) with an 80% undershoot (scale .97) before the card settles back to its resting
   size, so a played/attached card reads as being set down rather than just snapping back. */
@keyframes vtlift{
  0%{transform:scale(1);filter:drop-shadow(0 0 0 rgba(0,0,0,0))}
  35%{transform:scale(1.2) translateY(-14px) rotate(-3deg);filter:drop-shadow(0 26px 30px rgba(0,0,0,.7))}
  80%{transform:scale(.97)}
  100%{transform:scale(1);filter:drop-shadow(0 0 0 rgba(0,0,0,0))}
}
@keyframes vtold{0%,72%{opacity:1}100%{opacity:0}}
@keyframes vtnew{0%,72%{opacity:0}100%{opacity:1}}
::view-transition-old(root),::view-transition-new(root),::view-transition-image-pair(root){animation:none;mix-blend-mode:normal}
/* the opponent-reveal fan-in (arena.js refreshRevealFan): a quick fade + scale in, a plain animation */
#reveal .card{animation:vtin .2s ease-out}
/* Lukas 2026-10-10: a fanned card flies in from where it was -- the opponent's hand, deck or discard pile (--from-x/y
   by dealFrom) -- and its fly-out lands on a stand-in at the pile, hand or deck (.fanland, arena.js fanLanding). */
#reveal .card.fly{animation:fanin .42s cubic-bezier(.2,.7,.2,1) both;animation-delay:calc(var(--i,0)*40ms)}
@keyframes fanin{from{opacity:.4;transform:translate(var(--from-x,0px),var(--from-y,0px)) scale(.3)}}
.fanland{position:fixed;margin:0;z-index:9;pointer-events:none;animation:none}
/* origin ghosts (motion-2 section 2, Lukas 2026-09-22): a placeholder card-back withMotion() drops into a card's
   honest origin (a deck stack, the opponent's hand row) when it first appears somewhere other than the hand, so
   the view transition has an old element to fly FROM. Sized to fill whatever it lands in (width/height:auto
   overrides .card's own fixed 104x132 so inset:0 can stretch it), removed again the instant the real render runs. */
.card.back.ghost{position:absolute;inset:0;width:auto;height:auto;z-index:999;pointer-events:none}
@keyframes vtin{from{opacity:0;transform:scale(.85)}to{opacity:1;transform:scale(1)}}
/* the page itself is NOT snapshotted (Lukas 2026-09-11, 'the whole screen flickers' during the bot's turn): only the
   named cards are captured and tweened; the board underneath stays live and swaps instantly */
:root{view-transition-name:none}
.vtoff{view-transition-name:none!important}   /* arena.js withMotion prune(): a card that neither moves nor appears this step is not captured at all */

/* combat: a lunge toward the opponent when a Pokemon attacks, a shake + a floating damage number on anything hit
   this snapshot (whether or not there was an attack -- e.g. counters from an Ability), a Knock Out ghost that
   greys out then slides into the discard pile. Off under prefers-reduced-motion (arena.js also gates it on the
   Profile "Animations" toggle). Lukas 2026-09-11. */
/* ATTACK STAGING (Lukas 2026-10-12, step 1 of the game-feel study: in PTCGL and MTG Arena the acting card lifts
   off its slot before it strikes, and the table takes the colour of the blow). The attacker rises and glows in its
   own type colour (--atk, set by combatRun) for a fifth of a second, strikes, and settles; the blow lands at 58% =
   ATTACK_LAND ms in arena.js, where the hits, the jolt and the table wash start. */
.pk.lunge-up{animation:lungeup .62s cubic-bezier(.3,.7,.3,1);z-index:8}   /* attacker on the bottom (your) side: toward the opponent */
.pk.lunge-down{animation:lungedown .62s cubic-bezier(.3,.7,.3,1);z-index:8}   /* attacker on the top (opponent) side */
@keyframes lungeup{0%{transform:none;filter:none}30%,42%{transform:translateY(9px) scale(1.13);filter:brightness(1.25) drop-shadow(0 0 18px var(--atk,var(--gold)))}58%{transform:translateY(-44px) scale(1.06);filter:brightness(1.1)}100%{transform:none;filter:none}}
@keyframes lungedown{0%{transform:none;filter:none}30%,42%{transform:translateY(-9px) scale(1.13);filter:brightness(1.25) drop-shadow(0 0 18px var(--atk,var(--gold)))}58%{transform:translateY(44px) scale(1.06);filter:brightness(1.1)}100%{transform:none;filter:none}}
/* the table wash: one element over the plate and under the cards (z-index -1 in the mat's own stacking context,
   desktop only: washTable skips the phone), lit from where the blow landed. A Knock Out floods the whole table red. */
.mat>.wash{position:absolute;inset:0;z-index:-1;pointer-events:none;opacity:0;mix-blend-mode:screen;
  background:radial-gradient(46% 62% at var(--wx,50%) var(--wy,50%),var(--c,var(--gold)),transparent 72%);animation:wash .55s ease-out forwards}
.mat>.wash.ko{background:radial-gradient(95% 120% at var(--wx,50%) var(--wy,50%),#ff4a36,rgba(255,52,36,.4) 55%,rgba(255,52,36,.12) 100%);animation:washko .75s ease-out forwards}
:root[data-theme="pokeball"] .mat>.wash{mix-blend-mode:multiply}   /* a white desk: the colour darkens it instead of lighting it */
@keyframes wash{0%{opacity:0}18%{opacity:.55}100%{opacity:0}}
@keyframes washko{0%{opacity:0}10%{opacity:.85}40%{opacity:.55}100%{opacity:0}}
/* KNOCK OUT STAGING: the Knocked-Out card's double (.ghost, koGhost) takes the blow before it leaves: it shakes,
   flashes white and throws a red ring, and only then greys out and slides to the discard pile (about 1.5 s with
   the attack before it, where it was 0.75 s and started with the lunge). */
.ghost.struck{animation:hitshake .24s ease-in-out 2}
.ghost .koflash{position:absolute;inset:0;border-radius:8px;background:#fff;opacity:0;pointer-events:none;z-index:7;animation:koflash .48s ease-out forwards}
.ghost .koring{position:absolute;left:50%;top:50%;width:130%;aspect-ratio:1;border-radius:50%;border:4px solid #ff5a48;box-shadow:0 0 22px #ff5a48,inset 0 0 22px rgba(255,90,72,.6);
  transform:translate(-50%,-50%) scale(.3);opacity:0;pointer-events:none;z-index:7;animation:koring .6s ease-out forwards}
@keyframes koflash{0%{opacity:0}15%{opacity:.85}100%{opacity:0}}
@keyframes koring{0%{transform:translate(-50%,-50%) scale(.3);opacity:.95}100%{transform:translate(-50%,-50%) scale(1.5);opacity:0}}
.pk.hit{animation:hitshake .3s ease-in-out}   /* a few px of shake on anything that took damage this snapshot */
@keyframes hitshake{0%,100%{transform:translateX(0)}20%{transform:translateX(-6px)}40%{transform:translateX(5px)}60%{transform:translateX(-4px)}80%{transform:translateX(3px)}}
/* board-table-motion-design 2026-09-19 B.5: the floating damage number is a chip now, not bare text -- same rise. */
.dmgfloat{position:absolute;top:-10px;left:50%;background:var(--red);color:#fff;font-family:var(--display);font-weight:700;font-size:30px;padding:2px 10px;border-radius:14px;text-shadow:0 1px 2px var(--shadow);pointer-events:none;animation:dmgrise .95s ease-out forwards;z-index:5}   /* centred above the card -- the .st corner already holds the running damage total */
@keyframes dmgrise{0%{transform:translate(-50%,6px) scale(.4);opacity:0}14%{transform:translate(-50%,-4px) scale(1.3);opacity:1}26%,62%{transform:translate(-50%,-4px) scale(1);opacity:1}100%{transform:translate(-50%,-44px) scale(1);opacity:0}}   /* it pops, stands to be read, then rises (the size comes from the damage: hitOne) */
.ghost{position:absolute;pointer-events:none;z-index:9;box-sizing:border-box}   /* a Knocked-Out Pokemon's fading, sliding double (koGhost() drives the animation in JS -- timings live in one place) */
/* board-table-motion-design 2026-09-19 B.5: a type-tinted flash right at the point of impact (hitOne(), arena.js
   writes --c from the attacker's own card) plus a jolt on the whole mat when any hit lands this snapshot. */
.pk .impact{position:absolute;left:50%;top:45%;width:60%;aspect-ratio:1;border-radius:50%;
  transform:translate(-50%,-50%) scale(0);pointer-events:none;z-index:6;mix-blend-mode:screen;
  background:radial-gradient(circle,color-mix(in srgb,var(--c) 70%,#fff),var(--c) 40%,transparent 70%);
  animation:impact .45s ease-out forwards}
@keyframes impact{30%{transform:translate(-50%,-50%) scale(1.3);opacity:1}100%{transform:translate(-50%,-50%) scale(1.7);opacity:0}}
.mat.jolt{animation:jolt .25s ease-out}
@keyframes jolt{20%{transform:translate(-3px,2px)}50%{transform:translate(3px,-2px)}80%{transform:translate(-1px,1px)}}
/* board-table-motion-design 2026-09-19 B.4: the evolve flash -- a white ring that blooms and fades. .pk::after is
   otherwise unused (fxClasses()'s conditions only ever reach .pk.c-asl::before/.pk.c-cnf::before, never ::after
   -- checked by grep before adding this), so the pseudo is free here. */
.pk.evolved::after{content:"";position:absolute;inset:-4px;border-radius:8px;border:2px solid #fff;pointer-events:none;animation:evoflash .6s ease-out forwards}
@keyframes evoflash{0%{opacity:.9;transform:scale(.9)}100%{opacity:0;transform:scale(1.25)}}
@media (prefers-reduced-motion: reduce){.pk.lunge-up,.pk.lunge-down,.pk.hit,.dmgfloat,.pk .impact,.mat.jolt,.pk.evolved::after{animation:none}}
html.nofx .pk .impact,html.nofx .mat.jolt,html.nofx .pk.evolved::after{animation:none!important}

/* table look, Lukas 2026-09-11: a CSS-only card back for every face-down card, the opponent's hand and both
   prize rows drawn as rows of that back, stack thickness by count, and a hand fan. Own emblem 2026-10-01: the
   back is the arena's own (dark panel, gold frame, lattice, the hexagon-and-card emblem in the centre). */

/* one card-back class, used by .stack.back (deck), the opponent's hand row and both prize rows. ::before draws
   the frame + lattice, ::after the emblem (a gold CSS mask). */
.back{position:relative;overflow:hidden;box-sizing:border-box;background:var(--panel2)}
.stack.back{overflow:visible}.stack.back::before{border-radius:4px}
.back::before{content:"";position:absolute;inset:6%;box-sizing:border-box;border:2px solid var(--gold);border-radius:3px;
  background-image:
    repeating-linear-gradient(45deg,color-mix(in srgb, var(--line) 35%, transparent) 0 1px,transparent 1px 9px),
    repeating-linear-gradient(-45deg,color-mix(in srgb, var(--line) 35%, transparent) 0 1px,transparent 1px 9px)}
.back::after{content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:40%;aspect-ratio:1/1;background:var(--gold);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'%3E%3Cpath d='M14 2l10.39 6v12L14 26l-10.39-6V8z'/%3E%3Crect x='10' y='8.5' width='8' height='11' rx='1.4'/%3E%3Cpath d='M14 11.5l1.8 2.5-1.8 2.5-1.8-2.5z' fill='black' stroke='none'/%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28' fill='none' stroke='black' stroke-width='2' stroke-linejoin='round'%3E%3Cpath d='M14 2l10.39 6v12L14 26l-10.39-6V8z'/%3E%3Crect x='10' y='8.5' width='8' height='11' rx='1.4'/%3E%3Cpath d='M14 11.5l1.8 2.5-1.8 2.5-1.8-2.5z' fill='black' stroke='none'/%3E%3C/svg%3E") center/contain no-repeat}
/* Lukas 2026-09-11: backs stay quiet next to picture faces -- the small prize/hand backs draw frame and emblem softer */
.back.mini::before,.hand.opp .oc::before{border-width:1px;border-color:color-mix(in srgb, var(--gold) 60%, transparent)}
.back.mini::after,.hand.opp .oc::after{background:color-mix(in srgb, var(--gold) 60%, transparent)}
.stack.back{background:#1a1d24;border:1px solid #3a3f4a;border-radius:5px}   /* board-table-motion-design 2026-09-19 A: painted under the back ::before (frame inset 6%) */
.stack.back .n{position:relative;z-index:1}   /* paint over the ::before/::after */
/* BIGGER CARDS, TIGHTER BOARD render round 2 (Lukas, real Chrome): 40x56 backs in a 2-wide grid needed 3 rows for
   6 prizes (176px tall) -- once item 7 shrank the opponent's bench card to 112px, the prizes column (unchanged)
   became the new bottleneck for that whole grid row (184px measured for a 122px bench card). 36x50 backs, 3 wide
   (.stack.prizes below), fit 6 prizes in 2 rows instead of 3 -- 114px wide (still well inside the 130px column),
   ~103px tall, back in line with the bench card it now sits beside. */
/* motion-2 section 5: 36x50 was fixed at the old flat 130px side column -- scaled from --side the same way,
   36/130 and 50/130, so a prize mini grows right along with the column instead of staying pinned at 36x50. */
.back.mini{width:calc(var(--side, 130px) * .28);height:calc(var(--side, 130px) * .39);flex:none;border-radius:4px;box-shadow:0 2px 4px var(--shadow)}
/* Lukas 2026-09-13: Prize cards set aside by a clock penalty stay on the board, struck with two red lines */
.back.mini.aside{opacity:.75}
.back.mini.aside i{position:absolute;inset:0;z-index:2;background:
  linear-gradient(to top right,transparent calc(50% - 1.5px),var(--red) calc(50% - 1.5px),var(--red) calc(50% + 1.5px),transparent calc(50% + 1.5px)),
  linear-gradient(to top left,transparent calc(50% - 1.5px),var(--red) calc(50% - 1.5px),var(--red) calc(50% + 1.5px),transparent calc(50% + 1.5px))}

/* opponent's hand (Lukas 2026-09-11): a fanned row of face-down cards across the top of their side, the mirror of
   your fan -- pivot above, outer cards higher, the row clips them so you see the backs held over the table edge. */
.hand.opp{grid-row:1;height:44px;overflow:hidden;align-items:flex-start;padding:0;border-top:0;border-bottom:0;position:relative;
  background:linear-gradient(180deg,var(--prop-face),transparent);border-radius:0 0 12px 12px}   /* board-arena 2026-09-19 section 2: the opponent's hand rail */
/* Lukas 2026-09-17 (mat polish): prize-mini scale (44x60) and a capped row width (fanOut, arena.js) -- 13 backs
   used to read as a wall of card backs across the whole table; now a tidy fan centred over their side.
   Lukas 2026-09-18 evening: "opponent's hand cards are also quite small" -- 60x84 backs in a 44px-tall row
   (the same -8px tuck, so 36px of each back shows instead of 22). */
.hand.opp .oc{width:60px;height:84px;margin-top:-8px;border-radius:4px;flex:none;border:1px solid #3a3f4a;
  box-shadow:0 1px 0 rgba(255,255,255,.1) inset,0 4px 10px rgba(0,0,0,.6);
  --mid:calc((var(--n) - 1) / 2);--off:calc(var(--i) - var(--mid));
  transform-origin:50% -100px;
  transform:translateY(calc(abs(var(--off)) * -6px / max(var(--mid),1))) rotate(calc(var(--off) * -12deg / max(var(--n) - 1,1)));
  z-index:calc(var(--i) + 1)}
/* table-feel spec section 1 fix round (real-Chrome review): "hand 4" was a word chip at the top-right corner --
   a bare number badge now, the same small-pill style as the deck's own corner count (.stack.back .n).
   Fix round 2 (real-Chrome review): position:absolute;right:8px put it at the ROW's own right edge, which spans
   the whole mat width -- far from the fan itself (.hand{justify-content:center} centres the fan in the middle).
   An ordinary flex child now (still after every .oc in source order, so it lands right after the fan's last
   back), align-self:flex-end to sit at the row's bottom like the fan's own backs. */
.hand.opp .cnt{align-self:flex-end;z-index:50;margin:0 0 4px 4px;font-family:var(--display);font-size:12px;font-weight:600;
  line-height:20px;background:var(--panel2);border:1px solid var(--line2);border-radius:10px;padding:0 7px;color:var(--ink)}

/* observer (2026-09-13): both hands open -- the opponent's row shows faces, so it un-clips and fans like your own */
.hand.opp.up{height:108px;overflow:hidden;align-items:flex-start;padding:6px 0 0;border-bottom:1px solid var(--line)}
.hand.opp.up .card{width:70px;height:94px}
.hand.opp.up .card.pic{height:98px}

/* replay (spec 2026-09-20, render round 1 F2): Turn player mode hides your own hand too -- the same face-down fan
   as the opponent's row, but sitting where your own open hand sits (bottom of .mat), matching .hand's own row/height/
   align/padding/border-top instead of .hand.opp's top-rail ones. */
.hand.opp.mine{grid-row:auto;height:auto;align-items:flex-end;padding:8px 0 30px;border-top:1px solid var(--line)}

/* prizes: the remaining Prize cards as face-down minis, three per row, two rows -- a taken prize leaves a gap.
   Lukas 2026-09-11: room for bigger minis -- was 40x56, 2x3, ~84px wide, still well inside the 130px column;
   3x2 at 36x50 since the render-round-2 opponent-bench fix above (.back.mini's own comment). */
.stack.prizes{width:auto;height:auto;background:none;box-shadow:none;padding:0;display:grid;grid-template-columns:repeat(3,calc(var(--side, 130px) * .28));gap:3px}
/* board-table-motion-design 2026-09-19 A: six face-down cards set aside on the felt, each a touch askew */
.stack.prizes .back.mini{box-shadow:0 1px 0 rgba(255,255,255,.08) inset,0 3px 6px rgba(0,0,0,.6);border:1px solid #3a3f4a}
.stack.prizes .back.mini:nth-child(1){transform:rotate(-3deg)}
.stack.prizes .back.mini:nth-child(2){transform:rotate(2deg) translateY(1px)}
.stack.prizes .back.mini:nth-child(3){transform:rotate(-1deg)}
.stack.prizes .back.mini:nth-child(4){transform:rotate(3deg)}
.stack.prizes .back.mini:nth-child(5){transform:rotate(-2deg) translateY(-1px)}
.stack.prizes .back.mini:nth-child(6){transform:rotate(1deg)}

/* board-arena 2026-09-19 section 2: the three "prop" wrappers -- stackBack()/discardStack()/prizesRow() (arena.js)
   each wrap their existing .stack markup in one of these; every selector reaching the .stack itself (data-zone,
   data-seat, the deal/nth-child rules) is a descendant selector and keeps matching unchanged (grep-confirmed).
   Lukas 2026-09-20 afternoon, on the deck box: "still don't get this visualization ... change the idea here
   because visually it doesn't look what you say." New idea: no box at all -- the deck is a plain thick stack of
   cards on the floor, its own stepped paper edges plus a soft contact shadow carry the thickness. */
.prop.box{position:relative;margin:14px 12px 12px 0}
.prop.box .stack.back{margin:0;box-shadow:1px 1px 0 var(--edge-a),2px 2px 0 var(--edge-b),3px 3px 0 var(--edge-a),4px 4px 0 var(--edge-b),5px 5px 0 var(--edge-a),6px 6px 0 var(--edge-b),7px 7px 0 var(--edge-a),8px 8px 0 var(--edge-b),9px 9px 0 var(--edge-a),10px 10px 0 var(--edge-b),8px 18px 22px -4px rgba(0,0,0,.65)}
.prop.tray{position:relative;padding:10px;border-radius:8px;background:radial-gradient(circle at 50% 30%,var(--prop),var(--prop-face));
  box-shadow:0 0 0 2px var(--prop-edge),0 0 0 3px var(--prop-line),0 8px 0 3px var(--prop-face),0 22px 28px var(--prop-shadow),inset 0 6px 14px rgba(0,0,0,.8)}
.prop.tray .stack.disc{margin:0}
.prop.rack{padding:8px;border-radius:8px;background:linear-gradient(180deg,var(--prop),var(--prop-2));margin:6px 0;
  /* Lukas 2026-09-20 afternoon: dropped the pale-edge box-shadow layer -- see the .row comment above. */
  box-shadow:0 0 0 1px var(--prop-line),0 8px 0 var(--prop-face),0 22px 28px var(--prop-shadow),inset 0 1px 0 rgba(255,255,255,.08)}
.prop.rack .back.mini{box-shadow:inset 0 0 0 1px rgba(255,255,255,.08),0 3px 5px rgba(0,0,0,.7);transform:none!important}

/* stack thickness: layered edges behind deck / discard / lost-zone stacks, by count -- t1 1-9, t2 10-29, t3 30+.
   thick() in arena.js is the one place the count -> class mapping lives. */
/* Lukas 2026-09-11: the stacked edges (up to 9px right+down) and the corner badge (6px) reach past the box -- keep that
   much clear below and to the right so a deck never touches the lost zone or the discard next to it */
.stack.t1,.stack.t2,.stack.t3{margin:0 10px 12px 0}
/* board-table-motion-design 2026-09-19 A: the deck is a real pile -- alternating light/dark edges stepped
   down-right, plus a soft drop shadow. Scoped to .stack.back.tN (the deck): .stack.disc.tN/.stack.lost.tN below
   already override any bare .stack.tN rule at equal-or-higher specificity, so this was always the deck's rule
   alone in practice -- naming it .stack.back.tN makes that explicit instead of relying on override order. */
.stack.back.t1{box-shadow:1px 1px 0 #262a33,2px 2px 0 #14161b,3px 3px 0 #262a33,0 12px 18px -4px rgba(0,0,0,.7)}
.stack.back.t2{box-shadow:1px 1px 0 #262a33,2px 2px 0 #14161b,3px 3px 0 #262a33,4px 4px 0 #14161b,5px 5px 0 #262a33,6px 6px 0 #14161b,0 14px 20px -4px rgba(0,0,0,.75)}
.stack.back.t3{box-shadow:1px 1px 0 #262a33,2px 2px 0 #14161b,3px 3px 0 #262a33,4px 4px 0 #14161b,5px 5px 0 #262a33,6px 6px 0 #14161b,7px 7px 0 #262a33,8px 8px 0 #14161b,9px 9px 0 #262a33,10px 10px 0 #14161b,0 18px 24px -4px rgba(0,0,0,.8)}
/* the discard's thickness now lives on its ::before (the peeking second card, below, pic mode) -- these stay as
   the text-mode (pictures off) fallback edge only, so a thick discard still reads as a pile with no picture. */
.stack.disc:not(.pic).t1{box-shadow:3px 3px 0 0 var(--line2)}
.stack.disc:not(.pic).t2{box-shadow:3px 3px 0 0 var(--line2),6px 6px 0 0 var(--line2)}
.stack.disc:not(.pic).t3{box-shadow:3px 3px 0 0 var(--line2),6px 6px 0 0 var(--line2),9px 9px 0 0 var(--line2)}
/* the lost zone keeps one 3px edge regardless of thickness -- it is a small dashed box, not a pile. */
.stack.lost.t1,.stack.lost.t2,.stack.lost.t3{box-shadow:3px 3px 0 0 var(--line2)}

/* hand fan: -2deg (leftmost) to +2deg (rightmost) around a low pivot, with a slight vertical arc (outer cards a
   few px lower) -- both driven purely by --i/--n (set per card in arena.js render()). The overlap that keeps the
   hand from ever scrolling is a JS-computed margin-left (layoutHandFan()), so it fits the actual mat width.
   Hover and .sel straighten the card, lift it and bring it to the front; the transform stays on .card itself so
   a card flight (view-transition-name) still carries it.
   table-feel spec section 4 (2026-09-21): was -6deg/+6deg with a 10px arc -- a flatter fan (2deg max, ~4px arc)
   reads closer to PTCGL's own hand than the wider table-look fan did; fanOut()'s own rotBuffer (arena.js) is
   kept in sync with the 2deg max. */
.hand .card{
  --mid:calc((var(--n) - 1) / 2);
  --off:calc(var(--i) - var(--mid));
  transform-origin:50% 160px;   /* a low pivot below the card -- layoutHandFan()'s ROT_BUFFER assumes this distance */
  transform:translateY(calc(abs(var(--off)) * 4px / max(var(--mid),1))) rotate(calc(var(--off) * 4deg / max(var(--n) - 1,1)));
  z-index:calc(var(--i) + 1);
  transition:transform .18s ease-out,box-shadow .18s;   /* board-table-motion-design 2026-09-19 A */
}
/* Lukas 2026-09-18 (phone board): a hover-only lift -- on a touch device (no real :hover) a tap used to leave the
   card visually stuck lifted until something else was tapped; @media (hover:hover) is true only where a pointer
   can actually hover, so touch never matches it. .sel (the real "this card is selected" state) is unaffected.
   board-table-motion-design 2026-09-19 A: a deeper lift + lean toward you, a gold ring in place of the plain lift. */
/* board-table-motion-design 2026-09-19 B.7: --rx/--ry (a lean toward the pointer) and --mx/--my (the sheen's own
   centre) are written by a delegated pointermove listener (arena.js, near the hand's click handler); the rule
   keeps working with none of that -- var(...,0deg) is the flat, centred default whenever the vars are unset
   (touch, or before the first move). */
@media (hover:hover){.hand .card:hover{transform:translateY(-34px) scale(1.06) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg));z-index:100;transition:transform .12s;
  box-shadow:0 1px 0 rgba(255,255,255,.2) inset,0 24px 34px -10px rgba(0,0,0,.8),0 0 0 2px rgba(232,180,60,.5)}}
/* table-feel spec section 4: .sel now lifts the same -34px as hover (was -14px), same .12s -- a selected card
   reads exactly like a hovered one instead of a shorter, slower lift. */
.hand .card.sel{transform:translateY(-34px);z-index:100;transition:transform .12s}
@media (prefers-reduced-motion: reduce){.hand .card:hover,.hand .card.sel{transition:none}}
/* board-table-motion-design 2026-09-19 B.1: a draw flips face-up out of the deck. --from-x/--from-y are written
   by dealFrom() (arena.js) right before this runs; `both` fill holds 0% until the animation starts and 100%
   (`none`) after it ends, so the fan's own transform (above) is what the card lands on and keeps afterwards.
   motion-2 section 3 (Lukas 2026-09-22, "buddy poffin from the deck" -- too fast to read): .45s -> .6s, and a
   multi-card draw (a mulligan redraw, several prizes) now staggers by --i (the fan already sets it per card) --
   `both`'s BACKWARDS fill already applies the 0% keyframe (the card sitting at the deck, rotated away) for the
   whole animation-delay, so a delayed card holds at the deck instead of flashing at rest; no opacity trick needed. */
.hand .card.drawn{animation:drawin .6s cubic-bezier(.2,.7,.2,1) both;animation-delay:calc(var(--i, 0) * 70ms)}
@keyframes drawin{
  0%{transform:translate(var(--from-x,0px),var(--from-y,0px)) rotateY(180deg) scale(.7)}
  60%{transform:translate(calc(var(--from-x,0px) * .2),calc(var(--from-y,0px) * .2 - 20px)) rotateY(0) scale(1.05)}
  100%{transform:none}
}
@media (prefers-reduced-motion: reduce){.hand .card.drawn{animation:none}}
html.nofx .hand .card.drawn{animation:none!important}
/* the foil sheen: hover only, tracks the pointer via the same --mx/--my. No z-index tricks needed -- ::after
   paints after every real child by default, and .card already clips to its own rounded corners (overflow:hidden). */
@media (hover:hover){.hand .card.pic:hover::after{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:radial-gradient(circle at var(--mx,50%) var(--my,50%),rgba(255,255,255,.35),transparent 60%)}}
/* deck: the count sits in a corner badge (like the discard's) -- table-feel spec section 2 (2026-09-21) dropped
   the "deck" stencil under it, title= carries the word instead. */
.stack.back .n{position:absolute;right:-6px;bottom:-6px;line-height:20px;font-size:12px;background:var(--panel2);border:1px solid var(--line2);border-radius:10px;padding:0 7px;color:var(--ink);text-shadow:none;z-index:2}

/* card pictures, Lukas 2026-09-11: real card art from TCGdex (picUrl() in arena.js), on by default per
   device (Profile "Card pictures" toggle, localStorage "pics"). Almost every printing resolves, but a
   single picture can still fail to load:
   - cardFace()/discardStack() dual-render the text face behind the picture (no nested view-transition-names in
     either, so a hidden duplicate is harmless) -- .noimg is set by the <img>'s onerror and just swaps which one
     shows.
   - pokemon() never dual-renders: its Energy pips each carry a per-card view-transition-name (pip(..., iid)), and
     a second copy of that name anywhere on the page aborts the whole card-flight transition. pkImgError() (arena.js)
     swaps the .pk's whole body back to the plain text face instead. */
.card.pic{padding:0}
.card.pic .face{display:none}
.card.pic.noimg .face{display:block}
.card.pic.noimg img{display:none}
.card.pic img{width:100%;height:100%;object-fit:cover;display:block}
/* a real card is 63:88 -- at the hand's 104px width that wants ~145px tall (132px reads as too cropped); the hand
   row grows to fit (150px -> 185px, harmless slack above the cards when pictures are off). */
/* Lukas 2026-09-17 (mat polish): 140 -> 145 is the true 63:88 ratio at 104px wide -- the old 140 cropped ~5px off
   the printed frame's top and bottom (object-fit:cover), which is why the yellow border never showed. */
.hand .card.pic{height:145px}
/* table-feel spec section 4 (2026-09-21): the hand is a full-size overlapping row now -- 124x166 (63:88 at the
   new width), own hand only (the observer's opponent-open row, .hand.opp.up .card{width:70px;height:94px} plus
   its own .card.pic override just below it in the file, is more specific and wins regardless of source order;
   the opponent's face-down fan uses .back/.oc, a different class, untouched either way). .card.big and the
   zoom (#cardzoom-body) read their own separate rules, untouched. */
.hand:not(.opp) .card{width:124px}
.hand:not(.opp) .card.pic{height:166px}
/* Lukas 2026-09-17 (mat polish): the hand row grew 145 (card) + 8 + 30 (.hand's own padding) = 183 -- 185 here
   leaves the same 2px slack the old 165 had over the old 140+8+18=166 stack; MAT_NATURAL_H (arena.js) grew to match. */
.mat{grid-template-rows:auto auto 1fr 1fr auto 185px}
/* BIGGER CARDS, TIGHTER BOARD render round 4 (Lukas, real Chrome): a "1fr 1fr" pair, once .mat is sized to
   max-content (updateMatZoom's release-and-measure, arena.js), has no leftover space to distribute -- both
   tracks get sized to the LARGER of the two tracks' own content minimums (CSS Grid's fr-sizing under an
   intrinsic container), so the opponent's Active row (row 3: their deck+lost column, their Active, the ctl
   column -- undo/end turn/the clock, which lives here purely from auto-placement order, not because it's the
   opponent's) was silently padded out to match my own Active row's taller minimum (214px, for the ctl column's
   clock stack + the "hint" line under my own Active). The opponent's Active card is 0.8x (item 7) and needs far
   less. Row 3 -> its own `auto` track, sized only to what row 3 actually needs (the ctl column's own ~170px
   comfortably fits under that); row 4 (mine) keeps its `1fr` -- a lone `1fr` track in an intrinsically-sized
   grid just equals its own content's minimum, so its own height is unaffected by this change. */
.mat{grid-template-rows:auto auto auto 1fr auto 185px}
/* table-feel spec section 4 (2026-09-21): the last row (the hand) is `auto` now -- updateMatZoom() (arena.js)
   already measures the mat's own real content height live (mat.style.height="max-content", the release-and-
   measure trick above), so a hand-coded px guess for a row whose own content just grew (166px card,
   transform:translateY) is no longer needed; `auto` lets the grid size the row to its own content directly.
   This is also WHY the fix-round transform bump (18px -> 26px, below) and the .arena{overflow:clip} fix need no
   row-height change of their own: transform doesn't affect layout size, so neither one touches what this `auto`
   row measures -- only what paints past its edge, which overflow:clip (not this row) is responsible for clipping. */
.mat{grid-template-rows:auto auto auto 1fr auto auto}

/* BIGGER CARDS, TIGHTER BOARD (Lukas 2026-09-18, item 7 -- its own commit): "We have still a lot of space, maybe
   cards should be bigger especially on big resolutions, everything is far apart." Three levers, desktop only --
   the phone block (@media max-width:899px, further down) is untouched and overrides every one of these again.
   Lever (c), part 1: the play area stops being fluid -- 130 + a middle column sized to its own content + 130,
   centred, so the prize/deck/discard columns hug the play area instead of stretching to the window edges on a
   wide monitor. The middle column was minmax(0,1200px) first -- Lukas 2026-09-18 evening, with the log panel
   collapsed on a 2000px screen: "why we have on right and left side empty space when log panel shrinks, it's not
   nice" -- the track took its whole 1200px whenever the window allowed, and the rows (justify-content:center)
   left dead air outside the piles too. A max-content middle (775f795) hugged the cards and pushed the whole board
   into a 1120px island -- Lukas again: "the gaps on the right and left sides increased even more". What he wants
   is the board reaching the window edges, so the middle is fluid (1fr: the piles sit at the arena's edges like a
   real table's, the rows centre in whatever is between) with a 700px floor -- a window narrower than 130+700+130
   overflows .mat by design and updateMatZoom's width term (arena.js) scales the whole board down to fit, lever
   (c)'s original point. The cards themselves cannot grow past the height fit (--matz is height-bound on a 16:9
   screen); bigger cards on wide screens would need a shorter vertical stack, a design pass of its own. */
/* motion-2 section 5 (Lukas 2026-09-22: "prize cards, deck box and discard can be bigger i think or we don't
   have space?"): --side scales the two prop columns with the window instead of a flat 130px. .back.mini/.stack
   (deck + discard, both share the base .stack size)/.stack.prizes below are all sized as a fraction of it, so the
   props grow right along with the column. Phone (@media max-width:899px, further down) sets --side back to 130px
   and keeps its own separate, unscoped-from-this-var sizes untouched.
   motion-2 section 7 (Lukas 2026-09-22, screenshot at ~1600x900: "everything is crammed", the lost slot riding
   over the floor plate's gate before this section's own move fixed that): clamp(160px,10.5vw,210px) grew the
   props by WIDTH alone, so a merely-wide-but-not-tall window (900px tall) grew them past the room a 150px-tall
   fixed hand row actually leaves above the mat's own vertical fit. min(10.5vw,16.5vh) caps growth by height too
   -- a 900px-tall window keeps close to today's size, only a taller screen grows the props; the 140px floor also
   drops 20px below the old 160px, since height is now the tighter of the two constraints on most monitors. */
.mat{--side:clamp(140px,min(10.5vw,16.5vh),210px);grid-template-columns:var(--side) minmax(700px,1fr) var(--side);justify-content:center}
/* lever (c), part 2: trim the vertical padding/gaps so the rows approach their card heights instead of the air
   Lukas 2026-09-11's "clear air between an Active and its Bench" pass left around them -- roughly halved, not
   removed, since the fanned/lifted hand and the Stadium's centreline straddle still need some room. */
.col{gap:8px}
.actrow.opp{padding:14px 0 10px}
.actrow.me{padding:10px 0 14px}
/* lever (b): your own hand overlaps the mat's bottom edge by about a third of a card -- position:relative + an
   explicit z-index (above the bench row's default stacking, below .pop/.wait/#tempo which must still show over
   it during a choice) so the overlap reads as the hand sitting ON the table's edge, not floating separately.
   .slot/the bench .pk stay clickable in the part the hand does not cover -- the negative margin only moves this
   box's own rendered position, it does not touch .row's layout or .slot's own hit area. fanOut()/layoutHandFan()
   only read .hand's WIDTH (clientWidth) for the fan math, untouched by a vertical margin. */
.hand:not(.opp){margin-top:-52px;position:relative;z-index:2}
/* table-feel spec section 4 (2026-09-21): a full-size overlapping row cropped by the viewport's bottom edge, like
   PTCGL -- was padding:8px 0 30px (the base .hand rule, still true for .hand.opp/.hand.opp.up, both of which
   already reset their own padding and never pick up transform); the row itself now sits lower so the viewport
   crops it instead of the old bottom padding holding every card fully in view.
   Fix round 2 (real-Chrome review, 1440x900: card tops touching the bench slab's own bottom edge, y=750 vs
   slab ~760): 18px -> 26px, 8px more clearance under the slab; the hover lift (-34px, arena.css .hand .card:hover)
   still rises well above it either way. */
.hand:not(.opp){padding:6px 0 0;transform:translateY(26px)}
.hand:not(.opp){--hang:26px}   /* the same 26px, for arena.js handSink: what the hand hangs below its place when no scene is open */
/* the table-edge line (Lukas 2026-09-18 evening, "the line crossing Bench area"): .hand's own border-top rode up
   with the -52px overlap and cut straight through the bench row. The hand loses its border here; the edge is drawn
   under the bench row instead (the .mat has no full-width child to carry it -- the middle track's own width is
   enough: the pile columns end above it anyway), where the overlapping hand covers it the way cards on a table's
   edge would. */
.hand:not(.opp){border-top:0}
/* board-arena 2026-09-19 section 2: the hand ledge -- your hand rests on a dark shelf at the near edge of the
   arena; a shadow gradient, not a prop surface, so it stays the same translucent black in both themes (like the
   card contact shadows above). */
.hand:not(.opp){background:linear-gradient(180deg,transparent 0,rgba(6,8,12,.55) 30%,rgba(6,8,12,.85) 100%);border-radius:18px 18px 0 0;box-shadow:inset 0 1px 0 rgba(255,255,255,.06)}
.row:not(.opp){border-bottom:0}   /* board-arena 2026-09-19: the seam under the bench row */
/* CENTRING, bench (same Lukas note): your bench row carries the dashed "bench" drop slot after the last card, so
   the CARDS sat left of the centre line by half a slot while the opponent's row (no slot) sat on it. A ghost of
   the slot's width before the first card mirrors it, so the cards centre; both vanish once eight are benched
   (Area Zero -- the 8th child being a .pk means the row is full and nothing can be dropped anyway). */
.row:not(.opp):has(.slot)::before{content:"";width:118px;flex:none}
/* Lukas 2026-09-20 afternoon: "bench word box not centered" -- with no Pokemon benched, the ghost above plus the
   row's gap pushed the lone dashed slot 66px right of true centre; kill the ghost when the row holds no .pk at all. */
.row:not(.opp):not(:has(.pk))::before{display:none}
.row:not(.opp):has(.pk:nth-child(8)) .slot,.row:not(.opp):has(.pk:nth-child(8))::before{display:none}
/* lever (a) -- the opponent's side at 0.8x -- is GONE (Lukas 2026-09-18 evening: "Active pokemon are not equal
   now in sizes", then "bench sizes are different"): both sides use the same card sizes (.pk.pic / .pk.active.pic
   below), no .opp overrides. */

.pk.pic{padding:0;width:100px;height:140px;border-radius:6px}   /* board-table-motion-design 2026-09-19 A */
.pk.active.pic{width:136px;height:190px}
.pk.pic img{width:100%;height:100%;object-fit:cover;display:block;border-radius:4px}
/* the printed card carries its own name and set number in its own corners -- no name label is added here, it
   would only smear over the art (Lukas playtest 2026-09-11). */
/* table-feel spec section 3 (2026-09-21): PTCGL's own card-badge convention replaces the old bottom readout
   strip -- an HP pill overlapping the top-right corner, a damage badge hanging under it, energy pips bottom-left
   with no background bar (the peeks from attHTML() are the real attachment display, untouched), conditions
   bottom-right. #uid drops off the face entirely (it stays in renderZoom/renderPopover/the log/trainer notes).
   .badges is one absolutely-positioned layer over the whole picture so every badge shares a single counter-
   rotation for the Confused active (below, was .pk.active.c-cnf.pic .ov). */
.pk.pic .badges{position:absolute;inset:0;pointer-events:none}
.pk.pic .hppill{position:absolute;top:-6px;right:-6px;display:flex;align-items:center;gap:3px;
  background:#fff;border-radius:10px;padding:2px 6px;box-shadow:0 2px 6px rgba(0,0,0,.5)}
.pk.pic .hppill b{font-family:var(--display);font-weight:700;font-size:13px;color:#111;line-height:1}
.pk.pic .hppill b.hurt{color:var(--red)}
.pk.pic .hppill i{width:9px;height:9px;border-radius:50%;display:block;box-shadow:inset 0 0 0 1px rgba(0,0,0,.25)}
.pk.active.pic .hppill{top:-8px;right:-8px}
.pk.active.pic .hppill b{font-size:15px}
/* the damage badge: reuses the existing .badge.dmg element/class (pkPicHTML, arena.js), restyled as PTCGL's
   round orange chip hanging just under the HP pill's right end instead of the old red rounded-rect text chip. */
.pk.pic .badge.dmg{position:absolute;top:20px;right:-4px;width:22px;height:22px;border-radius:50%;
  background:#f0862a;border:0;color:#fff;display:flex;align-items:center;justify-content:center;
  font-family:var(--display);font-weight:700;font-size:11px;letter-spacing:0;text-transform:none;padding:0;box-shadow:0 2px 6px rgba(0,0,0,.5)}
.pk.active.pic .badge.dmg{top:26px;right:-6px}
.pk.pic .en{position:absolute;left:3px;bottom:3px;display:flex;gap:2px;padding:0;min-height:0}
.pk.pic .conds{position:absolute;right:3px;bottom:3px;display:flex;gap:2px;flex-wrap:wrap;justify-content:flex-end;max-width:60%}
.pk.pic .rc{display:none}   /* retreat-cost stars are printed on the card and shown in the zoom dialog only */

/* attached energies + Tool, "the IRL way" (Lukas 2026-09-18, quoted in full above .att/attHTML, arena.js):
   structural rules shared by the board (.pk .att, desktop only -- arena.css min-width:900px block below) and the
   inspector's big picture (#cardzoom-body .att, any width -- "full view of attachment you'd see by double
   clicking"). pointer-events:none -- a peek is a picture, not a hit target, so a click through it still reaches
   the card/Pokemon underneath.
   Z-ORDER (Fable render round 1, real Chrome: the energies painted IN FRONT of the picture on Alakazam/opp
   Active) -- .att's negative z-index alone is not reliable here: the main picture <img> was never itself a
   POSITIONED element, so within .pk's isolated stacking context it painted as an ordinary in-flow box rather
   than at a z-index layer, and .att (a later sibling, once its own position:absolute stopped applying -- see the
   BENCH SQUEEZE note below) could end up drawn after it. Fixed with an explicit, unambiguous z-index ladder
   inside .pk.pic's own isolated context: .att=0 (below), the picture <img>=1 (the card face), .ov=2 (existing
   rule, the HP strip always on top), .fx=1 (existing rule, appended last in the DOM so it still wins ties with
   the picture -- conditions/lock overlays must read over the art). Reasoned through every child of .pk.pic: the
   old top-left .tool is display:none on desktop pic mode so its own z-index:-1 is moot; .strip only exists in
   text mode (pkFaceHTML); the .legal/.sel/.dim selection treatments are box-shadow/opacity on the .pk element
   itself, not separately stacked children, so this ladder can't drop them out of view. */
.pk.pic,#cardzoom-body .bigpic{isolation:isolate}
.pk.pic>img,#cardzoom-body .bigpic>img{position:relative;z-index:1}
.pk .att,#cardzoom-body .att{position:absolute;left:0;top:0;width:100%;height:100%;z-index:0;pointer-events:none}
/* BENCH SQUEEZE (Fable render round 1: the opponent's bench energies measured 42x130, displaced below the card --
   the legacy `.pk .en{display:flex;gap:3px;padding:6px 7px 7px;min-height:14px;flex-wrap:wrap}` pip-container
   rule (line ~605, written for the .ov's energy-pip row) was still reaching this element by class name alone).
   `.pk .att .en` (3 classes) already outranks `.pk .en` (2 classes) on paper, but this makes it unambiguous on
   every axis that actually matters (an element selector plus one more class, both the type and the position
   family) rather than relying on a single specificity tier: nothing else in the file combines an element
   selector with `.att img.en`, so this cannot lose a tie-break regardless of source order. TOO WIDE (Fable render
   round 1: three peeks at 22% read as a whole second card beside the Pokemon) -- --peek drops to 14% (below,
   matching the inspector's own value) and the peeking art dims a touch so it reads as tucked under the
   Pokemon, not beside it. */
.pk.pic .att img.en,#cardzoom-body .att img.en{display:block;position:absolute;top:0;width:100%;height:100%;
  padding:0;margin:0;object-fit:cover;border-radius:4px;background:var(--tc,var(--t-Colorless));
  box-shadow:0 2px 6px var(--shadow);filter:brightness(.82)}
/* the "+N" badge (attHTML caps the board's fan at 3): parked at the last visible energy's own top-right corner --
   its `left` is written inline (arena.js) with the same formula as that energy plus its own width, so it tracks
   whichever peek width (--peek) is in force. */
.pk .att .more,#cardzoom-body .att .more{position:absolute;top:-6px;background:var(--panel);border:1px solid var(--line2);border-radius:8px;
  padding:0 5px;font-family:var(--display);font-size:10px;font-weight:700;color:var(--ink);z-index:2}
/* the Tool is the small top-left mini (.pk .tool, above) -- "the corner, as initially" (Lukas 2026-09-18
   evening, his last word after five other placements were rendered: tucked sideways bottom-right, a full
   sideways card 22% past the fan, half a sideways card past the fan, and upright behind the top edge --
   each either grew the stack "too much compared to opponent's active card", threatened that "bench will
   explode", or covered the row above). RULING: the Tool stays in the corner; energies keep the IRL fan. */
/* desktop only (Lukas: "desktop his way with a cap, peeking right" -- the phone keeps the strip's .pip row, see
   the @media max-width:899px block further down).
   --peek IN PIXELS, not a percentage (Fable render round 2, real Chrome on 2740881): margin-right's
   `calc(var(--peek) * var(--n-att,0))` is set on `.pk.pic`, a FLEX ITEM of `.row` -- and per the flexbox spec, a
   percentage on a flex item's margin resolves against the FLEX CONTAINER's content width (.row, inside .mat's
   own capped middle column), not the card's own width. --peek:14% measured margin-right:367px at 1440 and 504px
   at 2000 for a 3-energy own bench card (14% of .row's ~874px/~1200px content width) -- nowhere near the ~42px
   (14% of a 100px card) the fan itself actually needs. The `left` offsets on the `.en` images never had this bug
   (they're percentages on an ABSOLUTELY POSITIONED descendant of `.pk`, which resolves against `.pk`'s own box,
   not `.row`'s) -- only the flex-item margin did. Fix: --peek is now a literal px value, sized to 14% of each
   card-size variant's own width and set alongside that variant's own width rule, so both the `.en` left offset
   formula and margin-right read the same real, card-relative number regardless of which containing block a given
   property resolves against. The inspector's `.bigpic` keeps its own 14% (further down this file) -- it has no
   flex-item margin at all, so the percentage there was never wrong. */
@media (min-width:900px){
  .pk.pic{--peek:14px;margin-right:calc(var(--peek) * var(--n-att,0))}   /* own bench, 100px card: 14% of 100 */
  .pk.active.pic{--peek:19px}   /* own active, 136px card: 14% of 136 = 19.04 */
  /* CENTRING (Lukas 2026-09-18 evening, "everything is off center"): the clearance margin is part of the flex
     item's box, so a stack of card + fan + Tool was centred as a whole and the Pokemon itself drifted left of
     the Active above/below it. An Active has nothing to its right but the hint column (.actrow's 1fr), so it
     needs no clearance at all -- the fan simply overlaps that column and the card stays on the centre line. */
  .actrow .pk.active.pic{margin-right:0}
  /* CLEARANCE (Fable render round 1 ask, re-confirmed round 2): pokemon() (arena.js) writes --n-att on every
     .pk unconditionally (own/opponent, bench/active), the margin-right rule above is unscoped beyond .pk.pic so
     it reaches every one of them, and margin-right on a flex item adds to .row's own gap:14px rather than
     replacing it -- all still true now that --peek is px per variant instead of one shared %. */
  /* table-feel spec section 3 (2026-09-21): the pip row is now shown on the desktop too, same as the phone
     always did -- "the pips are the at-a-glance type readout" alongside the peeks (attHTML), not a fallback for
     them. Was .pk.pic .ov .en/.ov .pip{display:none} here (peeks-replace-pips); the hiding rule is gone. */
  .pk.pic .en .pip{width:12px;height:12px;font-size:8px}
  .pk.active.pic .en .pip{width:14px;height:14px;font-size:10px}
}

/* board-table-motion-design 2026-09-19 A: the discard is a loose pile -- the top card a few degrees off (--r,
   written per-render by discardStack() in arena.js so each landing drifts a touch differently), one more card
   peeking under it (::before). .stack already carries position:relative (line ~648), so the pseudo's inset:0
   needs no positioning context of its own. */
/* isolation:isolate -- the same reason .pk carries it for its z-index:-1 Tool (above): without a stacking context
   of its own, the ::before's z-index:-1 would paint behind the whole .mat, not just behind this pile's top card. */
.stack.disc.pic{padding:0;transform:rotate(calc(-6deg + var(--r,0deg)));border-radius:5px;overflow:visible;isolation:isolate}
.stack.disc.pic::before{content:"";position:absolute;inset:0;border-radius:5px;background:#2a2e38;border:1px solid #444a56;
  transform:rotate(9deg) translate(3px,2px);z-index:-1;box-shadow:0 6px 12px rgba(0,0,0,.5)}
.stack.disc.pic.t2::before{transform:rotate(11deg) translate(5px,3px);box-shadow:0 6px 12px rgba(0,0,0,.5),3px 3px 0 #1d2028}
.stack.disc.pic.t3::before{transform:rotate(13deg) translate(6px,4px);box-shadow:0 6px 12px rgba(0,0,0,.5),3px 3px 0 #1d2028,6px 6px 0 #2a2e38}
.stack.disc.pic .strip,.stack.disc.pic .top{display:none}
.stack.disc.pic.noimg .strip{display:block}
.stack.disc.pic.noimg .top{display:block}
.stack.disc.pic.noimg img{display:none}
.stack.disc.pic img{width:100%;height:100%;object-fit:cover;display:block}

.bigpic{width:220px;margin:0 auto 10px;display:flex;justify-content:center;position:relative;isolation:isolate}
.bigpic img{max-width:100%;border-radius:5px;box-shadow:0 3px 8px var(--shadow)}
/* CARD INSPECTOR ONE FACE (Lukas 2026-09-18): a failed load no longer just hides the picture (.noimg) -- bigPicFail
   (arena.js) replaces .bigpic outright with the text face, so there is never a dual-render to reveal underneath. */
/* IRL attachments, inspector (Lukas 2026-09-18): "full view of attachment you'd see by double clicking" -- the
   uncapped fan (attHTML(pk, 0), arena.js) behind the big picture, its own --peek (14% of the picture's width in
   px -- #cardzoom-body .bigpic, near the top of this file, the INSPECTOR FAN OVERFLOW comment). position:relative
   + isolation above give .att's z-index:0 a containing box that is just this picture, not the whole dialog, and
   the `.bigpic>img{z-index:1}` rule up there keeps the big picture itself above the fan. */

/* fit-to-viewport, Lukas 2026-09-11: the mat is built from fixed-px cards, so on a short screen (laptop-height,
   below ~1050px available under the topbar with a full board) it outgrows 100vh and the page scrolls. --matz
   (updateMatZoom() in arena.js, set on <html>, recomputed on resize and after every render) is
   min(1, available-height / the mat's own worst-case min-content height) -- 1 on anything tall enough, shrinking
   below that so the whole board scales down together and always fits, never scrolls. zoom (not transform:scale)
   so it's a real layout size: click hit-testing lands correctly with no extra work. getBoundingClientRect() on a
   zoomed element still answers in real screen px, though -- anything that reads it and writes the result back as
   a literal px style *inside* the zoomed .mat (the popover, the KO ghost) has to divide by matZoom() once at the
   point of writing, or the browser zooms that number a second time; see matZoom() in arena.js. */
.mat{zoom:var(--matz,1)}

/* phone board, 2026-09-18 */
/* Lukas 2026-09-18, spec docs/superpowers/specs/2026-09-18-phone-board-and-site-pages-design.md section 1: on a
   phone held upright the whole two-column table regrids into one portrait column, top to bottom in the same order
   as the desktop board -- the opponent's hand, their zones, their bench, their Active, then your Active, your
   bench, your zones, your hand. The DOM from render() (arena.js) is untouched; only the grid changes, driven by the
   zone classes (z-disc/z-prizes/z-deck/z-ctl/z-seed) render() adds to each .col. .topbar .tools/.more's UNSCOPED
   default (desktop: nothing changes) lives here too, right before the breakpoint, so both halves of that rule sit
   together. */
.topbar .tools{display:contents}
.topbar .more{display:none}
@media (max-width:899px){
  /* phone redesign spec (2026-09-23) section 1: "no bottom tab bar in the game" -- the third, permanently
     reserved 44px row (the old always-on .side tab strip) is gone; the Coach/Log sheet is a fixed overlay now
     (.side .pane below), not a row of its own, so .arena is back to the two rows a bare topbar+board needs. */
  .arena{overflow:clip;min-width:0;grid-template-columns:1fr;grid-template-rows:40px 1fr;height:100dvh}
  /* table-feel spec section 6 fix round: #coinstage stays a child of .mat on the phone (render(), arena.js --
     the phone .mat below resets transform:none, so it was never trapped in a stacking context the way the
     desktop tilted .mat was) -- back to centred-in-mat, the pre-fix-round rule, since the desktop rule above
     (left:0/right:calc(...)/top:30%, sized for .arena's wider box) means nothing inside .mat here. */
  .coinstage{left:50%;right:auto;top:50%;transform:translate(-50%,-50%)}
  /* Lukas 2026-09-18: 100dvh, not 100vh -- the address bar collapsing/expanding as the page scrolls would otherwise
     leave the hand strip under the browser chrome for a moment on every scroll. */
  /* render round 1 (Fable, real Chrome 390x844): the desktop .topbar{grid-column:1/3} was still spanning both
     .arena columns, which -- since .arena now has only ONE explicit column (1fr above) -- forced an *implicit*
     second column into existence for it to span into, and .side (the 3rd child, no explicit placement) auto-placed
     into that phantom column beside .mat instead of falling into its own row below. Pin all three children to the
     single real column and their own row explicitly. */
  .topbar{grid-column:1;grid-row:1}
  .mat{grid-column:1;grid-row:2}
  /* .side no longer reserves its own row (no bottom tab bar) -- it shares the mat's row; its only visible content
     while closed is nothing at all, and while open is the fixed .pane sheet below, so there's nothing to overlap. */
  .side{grid-column:1;grid-row:2}
  .arena.replay{grid-template-rows:40px 64px minmax(0,1fr)}
  .arena.replay .rpspace{grid-column:1;grid-row:2}
  .arena.replay .mat{grid-row:3}
  .arena.replay .side{grid-row:3}
  .rpbar{top:40px;height:64px;padding:2px 8px;gap:6px;align-content:center}
  .rpbar .who{margin-left:auto}
  .rpbar button{padding:4px 7px}
  .rpbar select{padding:2px 4px;font-size:13px}
  /* replay bar overflow (spec section 1: "the game page is 515px wide ... the .rpbar" is one of the named
     sources) -- five buttons + a count + three selects + the share controls never fit 390px; a horizontally
     scrollable strip keeps it contained instead of widening the page. */
  .rpbar{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;max-width:100vw;box-sizing:border-box}
  .rpbar::-webkit-scrollbar{display:none}
  .rpbar select{max-width:92px}
  /* render round 1: the topbar's min-content (both names + ratings + the turn text + the more button) was wider
     than 390px and widened the whole layout viewport instead of wrapping/eliding -- min-width:0 lets a flex item
     shrink below its content size, the name ellipsizes, and the turn/more stay a fixed size beside it instead of
     also being squeezed. The "Coaching game" tag (only shown in coach mode) is dropped outright: at 40px tall
     there's no good place left for a fourth text chunk beside two player names. */
  .topbar{height:40px;min-width:0;overflow:hidden;gap:8px;padding:0 8px}
  /* render round 2: min-width:0 shrank both names to nothing (only the admin badge + "vs" showed) once the win bar
     stopped taking the blame -- min-width:48px keeps a readable sliver of each name before it ellipsizes. The turn
     text gets the same shrink-and-ellipsize treatment (flex:0 1 auto, not flex:none) instead of staying a fixed
     size that could itself push .more off the 390px edge. */
  .topbar .name{font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:48px;flex:0 1 auto}
  /* player plates (table-feel2): on the phone only the portrait, small, in front of the name; the ring's light shows who acts */
  .topbar .name .pl-ring{width:24px;height:24px;border-width:2px;display:inline-block;vertical-align:middle;margin-right:5px}
  .topbar .name .pl-ring .pl-prz,.topbar .name .pl-ring .pl-halo{display:none}
  html[data-turn=me] .topbar .name .pl-ring.me,html[data-turn=opp] .topbar .name .pl-ring.opp{box-shadow:0 0 0 1px rgba(0,0,0,.5),0 0 9px rgba(var(--pc),.9)}
  .topbar .turn{font-size:12px;margin-left:auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:0 1 auto}
  .topbar .more{flex:none}
  .topbar .tag.search{display:none}
  .topbar .tools{display:none}
  .topbar .more{display:block}
  .topbar.tools-open .tools{display:flex;flex-direction:column;gap:8px;position:fixed;right:8px;top:44px;z-index:12;
    background:var(--panel);border:1px solid var(--line2);border-radius:4px;padding:10px 12px}
  /* phone redesign spec (2026-09-23) section 1: the Coach/Log sheet's own tab strip, relocated from a permanent
     44px bottom bar into small icon buttons here -- always visible (it's the open/close control itself, same
     click target as before, #tabs [data-tab]), never the desktop base's .tabs.single{display:none}. */
  .topbar .tabs{height:auto;flex:none;border-bottom:0;gap:2px}
  .topbar .tabs.single{display:flex}
  .topbar .tabs span{padding:4px 6px;font-size:0;border-bottom:0;display:flex;align-items:center}
  .topbar .tabs span svg{width:16px;height:16px}
  .topbar .tabs span.on{color:var(--gold)}

  .mat{
    transform:none;   /* board-arena 2026-09-19 section 1: the desktop tilt is off on the phone board */
    --side:130px;   /* motion-2 section 5: today's phone sizes stay put -- .stack/.back.mini/.stack.prizes below already carry their own separate literal overrides regardless, this just keeps --side sane if anything else ever reads it here */
    grid-template-columns:64px 1fr 64px;
    /* render round 1: the desktop .mat{grid-template-rows:auto auto 1fr 1fr auto 185px} (mat-polish, unscoped) was
       still cascading in underneath this rule -- it never sets grid-template-rows itself, so the 6-row desktop
       template kept applying to this grid's 8 named-area rows (the fr rows ate the slack, the last named row had
       no explicit size of its own left to inherit and collapsed). Eight explicit auto rows, one per named area,
       neutralises it for good -- every row is exactly its own content's height, which updateMatZoom() (arena.js)
       now reads live instead of a hand-summed constant. */
    grid-template-rows:auto auto auto auto auto auto auto auto auto;
    row-gap:6px;column-gap:6px;padding:4px 8px 0;
    /* phone redesign spec (2026-09-23) section 1: the real mat's own geometry -- your prizes on your left and
       deck on your right, the opponent's mirrored -- instead of the old full-width ohand/oact/mbench rows. The
       opponent's deck+discard (odisc/odeck) and mine (mdeck/mdisc) each stack in their own column across two
       sub-rows, spanned on the other side by the single Active (oact/mact) and, for the opponent, by prizes
       (oprz, both rows); the opponent's hand is the fan across the top row (ohand, Lukas's review). The
       Stadium (stad) leaves the Active row for its own centreline cell, beside Undo/End turn/clock (ctl). */
    grid-template-areas:
      "ohand  ohand  ohand"
      "obench obench obench"
      "odisc  oact   oprz"
      "odeck  oact   oprz"
      "stad   .      ctl"
      "mprz   mact   mdeck"
      "mprz   mact   mdisc"
      "mbench mbench mbench"
      "hand   hand   hand";
  }
  .mat .r2{border-bottom:0}   /* the row boundary is the grid gap now, not a dashed line */
  /* .mat-scoped throughout this block (not bare .row/.hand/.actrow/.col) -- .row and .hand are also used outside
     the playmat (the lobby's two-up grid, the account bar) and must not pick up a grid-area meant for this grid. */
  .mat .hand.opp{grid-area:ohand}   /* Lukas 2026-09-23 review: "plenty of space to fit opponent's hand on top" -- the fan across the top edge, like desktop */
  .mat .hand.opp.mine{grid-area:hand;height:auto;align-items:flex-end}
  .mat .col.top.z-disc{grid-area:odisc}
  .mat .col.top.z-deck{grid-area:odeck}
  .mat .col.top.z-prizes{grid-area:oprz}
  .mat .row.opp{grid-area:obench}
  .mat .actrow.opp{grid-area:oact}
  .mat .actrow.me{grid-area:mact}
  .mat .z-ctl{grid-area:ctl}
  .mat .stad.standalone{grid-area:stad}   /* the Stadium, pulled out of .actrow.opp.r2 on the phone -- see render() */
  .mat .row:not(.opp){grid-area:mbench}
  .mat .col.bot.z-deck{grid-area:mdeck}
  .mat .col.bot.z-prizes{grid-area:mprz}
  .mat .col.bot.z-disc{grid-area:mdisc}
  .mat .z-seed{display:none}
  .mat .hand:not(.opp){grid-area:hand}
  /* the opponent's hand: the desktop fan (fanOut, arena.js, fits it to the row width), at phone size */
  .mat .hand.opp:not(.up){height:34px;min-width:0}
  .mat .hand.opp .oc{width:40px;height:56px;margin-top:-10px}
  .mat .hand.opp .cnt{font-size:10px;line-height:15px;padding:0 5px;margin:0 0 2px 4px}
  .mat .hand.opp.up{height:70px;min-width:0;padding:4px 0 0}
  .mat .hand.opp.up .card{width:44px;height:62px;flex:none}
  /* one-row benches, never wraps (a wrapped second row would silently double the row's height, same failure
     mode the desktop's own flex-wrap:nowrap rule exists to avoid) -- up to 8 with Area Zero on either side does
     not fit 390px at these card sizes, so the bench itself scrolls horizontally instead of the page. */
  .mat .row{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none}
  .mat .row>*{flex:none}   /* real-Chrome review: a nowrap row shrank the own bench cards to ~35px instead of scrolling */
  .mat .row::-webkit-scrollbar{display:none}

  /* sizes: everything shrinks to fit the 64px side columns -- deck/discard/lost keep their count badge and
     thickness-edge styling, just smaller. */
  .pk.pic{width:64px;height:90px}
  /* phone redesign spec (2026-09-23) section 1: trimmed from 96x134 -- the Active card's own contribution to the
     podium overflow fix above (podium := this size's larger dimension + 12px). Never grown past this. */
  .pk.active.pic{width:92px;height:128px}
  .stack{width:36px;height:50px}
  /* render round 2: a fixed 36px was too narrow for "LOST ZONE 0" -- it wrapped onto three lines and spilled over
     the deck stack below it. width:auto (min 36px, so it never gets narrower than the deck/discard stack above
     it) plus a smaller one-line label lets the box grow to fit its own text instead. */
  .stack.lost{width:auto;min-width:36px;height:auto;padding:3px 5px;font-size:9px;line-height:1.1;white-space:nowrap}
  .stack.prizes{grid-template-columns:repeat(3,20px)}
  .back.mini{width:20px;height:28px}
  .stadium.pic{width:60px;height:84px}
  .slot{width:64px;height:40px}
  .actrow .slot{width:92px;height:128px}   /* matches .pk.active.pic above -- the empty-Active placeholder is the same box */

  /* the picture strip at 64px: HP shrinks to fit (#uid is already off the face on every width, table-feel spec
     section 3, 2026-09-21). */
  .pk.pic .hppill b{font-size:11px}
  .pk.active.pic .hppill b{font-size:13px}

  /* IRL attachments, phone (Lukas 2026-09-18: "phone keeps pips"): no room for a fanned peek at 64px, so .att
     (the energy fan, arena.js attHTML) is dropped outright;
     the strip keeps its .pip row (arena.js pkPicHTML never stopped rendering it) but redrawn as tiny energy-CARD
     silhouettes -- rounded rects in the type colour, no letter (too small to read one) -- instead of the 14px
     circles used everywhere else on the page. Both size variants (own side and the 0.8x opponent row/active,
     item 7) get the same phone size; the existing "one pip per energy, never a count badge" behaviour is
     untouched, only the shape. */
  .pk .att{display:none}
  /* Lukas 2026-09-23: "switch energy attachments as circle energy symbols as it is in PTCGL and was initially in our
     client. Those square rectangle small ones are not recognisable" -- the round lettered symbol, phone-sized. */
  .pk.pic .en .pip,.row.opp .pk.pic .en .pip,.actrow.opp .pk.active.pic .en .pip{width:14px;height:14px;font-size:9px;border-radius:50%}
  .pk.active.pic .en .pip,.actrow.opp .pk.active.pic .en .pip{width:16px;height:16px;font-size:10px}

  /* the Stadium used to leave the table-centreline overlay (.actrow.r2 .stad, desktop) for an ordinary cell
     INSIDE the opponent's Active row (.actrow.r2{position:static} below, dead now but harmless -- .stad no
     longer nests inside .actrow.r2 on the phone at all, see render()/arena.js and .mat .stad.standalone below).
     Phone redesign spec (2026-09-23) section 1: the opponent's Active row is only the mat's middle column now
     (deck/discard/prizes flank it, they don't share its row), so the Stadium needs its own top-level cell --
     it's a real centreline row again, matching the real mat's own geometry, beside Undo/End turn/clock. */
  .actrow.r2{position:static}
  .actrow.r2 .stad{position:static;transform:none}
  /* phone arena fix 2026-09-20 (Lukas: "mobile arena stadium part seams off"): the plate zooms so the seating tiers are cropped away (the tall phone strip showed them as grey blocks behind the hand and deck rows), and the Stadium is a small corner prop, not a 146px slab over the podium. The pokeball selector is needed: the light-theme .mat rule outranks a bare .mat here. */
  .mat,:root[data-theme="pokeball"] .mat{background-size:auto 165%}
  .actrow.r2 .stad{align-self:end;justify-self:start;width:fit-content;padding:4px;box-shadow:0 4px 0 var(--prop-face),0 10px 16px var(--prop-shadow)}
  .actrow.r2 .stad:not(:has(.stadium[data-stadium])){padding:0;box-shadow:none}
  .mat .stad.standalone{align-self:center;justify-self:center;width:fit-content;padding:4px;box-shadow:0 4px 0 var(--prop-face),0 10px 16px var(--prop-shadow)}
  .mat .stad.standalone:not(:has(.stadium[data-stadium])){padding:0;box-shadow:none}
  /* board-arena 2026-09-19 fix round: the props, cut down to fit a 390px screen. Phone redesign spec (2026-09-23)
     section 1: "podiums shrink to the Active's size + 12px" -- one of the named overflow sources (a 150px circle
     centred on a ~90px Active box bled well past its own cell); 92x128 is this file's phone Active size below. */
  .podium{width:140px;height:140px}
  .podium::after{inset:8px}
  .prop.box,.prop.tray,.prop.rack{padding:4px;margin:0;box-shadow:0 4px 0 var(--prop-face),0 10px 16px var(--prop-shadow)}
  .prop.box{padding:0;box-shadow:none}
  .stack.back .n{right:-4px;bottom:-4px}   /* the deck's count badge, scaled with the smaller box padding above -- no longer over the DECK label */
  .mat .row{min-width:0;width:auto;padding:6px 8px}   /* .mat-scoped like every other .row override in this block -- .row is also the (currently unused) .lobby .row */
  .actrow.opp,.actrow.me{padding:4px 0}
  /* the hint drops under the Active instead of sitting beside it (there is no third cell to put it in on a
     64px/1fr/64px row) -- grid-column:1/4 pulls it out of the 3-column actrow onto its own implicit row below;
     see render() (arena.js) for the word "click" -> "tap" on the phone. */
  .actrow.me .hint{display:none}   /* phone redesign 2026-09-23: no text on the table (standing rule) */

  /* undo/end turn/clock/match badge, beside your Active */
  .mat .ctl{padding:0;gap:6px;justify-content:center}
  .mat .ctl .undo{width:32px;height:32px}
  .mat .ctl .endturn{width:64px;height:36px;font-size:12px;border-radius:18px}
  .clock{min-width:0;margin:0;min-height:0}
  .clock .t{font-size:16px}

  /* the hand is a horizontal-scroll strip, not a fan (fanOut() in arena.js returns early for it on the phone,
     after clearing any marginLeft a wider layout left behind) -- :not(.opp) keeps the opponent's folded
     face-down hand (ohand, above) untouched. scroll-snap keeps a swipe landing on a whole card instead of half of two.
     render round 1: an auto-row grid area with an overflow:auto axis has no intrinsic size of its own to report
     (the classic "min-content of a scroll container is 0" case) -- min-height pins it to its own card's box (100
     card + 12+8 padding + 1 border) so the "hand" row can't collapse to 0 the way it did before this fix. */
  /* Lukas 2026-09-20 afternoon, "fix the phone deck under the hand too": the desktop .hand:not(.opp){margin-top:
     -52px} rule (lever (b), ~line 1534) is unscoped, so it also drags this strip up over the "mdisc mdeck mprz"
     row that sits directly above it in the phone grid -- margin-top:0 here cancels that desktop-only overlap.
     table-feel spec section 4 (2026-09-21): same story for the desktop's own transform:translateY (the
     bottom-edge crop, 18px -> 26px in the fix round) -- transform:none cancels it here so the phone strip isn't
     dragged down out of its own row. */
  .hand:not(.opp){grid-area:hand;overflow-x:auto;overflow-y:hidden;justify-content:flex-start;gap:6px;
    padding:12px 8px 8px;border-top:1px solid var(--line);scroll-snap-type:x proximity;min-height:121px;box-sizing:border-box;margin-top:0;transform:none}
  /* phone redesign spec (2026-09-23) section 1: viewport-fit=cover (arena.html) lets content reach the notch/home
     bar area -- the hand strip is the one thing that must never sit under it, so its own bottom padding grows by
     the safe-area inset. A separate, more specific rule (not folded into the shorthand `padding:` above) so it
     always wins regardless of source order. */
  .mat .hand:not(.opp){padding-bottom:calc(8px + env(safe-area-inset-bottom))}
  .hand:not(.opp) .card{flex:none;transform:none;scroll-snap-align:start;width:104px}   /* table-feel spec section 4: the desktop's 124px bump is cancelled here, same story as .pic below */
  .hand:not(.opp) .card.pic{width:72px;height:100px}
  .hand:not(.opp) .card.sel{transform:translateY(-8px)}   /* the 12px top padding above is the lift room */

  /* the action box is a bottom sheet ABOVE the hand strip (not over it -- render round 2, Fable: it used to cover
     the other hand cards, and the selected card's own lift (z-index 100) poked up through it) -- arena.js
     renderPopover adds the .sheet class and skips its own left/top math on the phone. .pop lives inside the zoomed
     .mat (`.mat{zoom:var(--matz,1)}`), so every fixed length written here gets multiplied by --matz same as
     everything else inside it. Phone redesign spec (2026-09-23) section 1: "recompute .pop.sheet's bottom for
     the removed tab bar" -- there is no longer a real, unzoomed 44px strip below the hand to cancel out, just
     the hand strip's own 121px min-height (already inside .mat, so no --matz division needed for it either). */
  .pop.sheet{position:fixed;left:8px;right:8px;bottom:calc(121px + env(safe-area-inset-bottom));width:auto;top:auto;max-height:40dvh;overflow:auto;z-index:120}

  /* the right panel is a sheet that opens over the lower part of the board -- phone redesign spec (2026-09-23)
     section 1: "no bottom tab bar in the game". Its own tab strip (tabsHtml, arena.js) moved into the topbar
     entirely (a row of small icon buttons there, id="tabs" unchanged so the existing click handler still
     matches) -- .side itself renders nothing while closed any more. */
  .side{border-left:0;border-top:0;display:block;background:transparent;overflow:visible}
  /* Lukas 2026-09-23 review: "if i want to get rid of it i try to click on the board but it doesn't disappear" --
     .side shares .mat's grid cell and sat over the whole board, so a tap on empty board landed on .side, which the
     click handler exempts from select(null). The container lets taps through; only its open sheet takes them. */
  .side{pointer-events:none}
  .side .pane{pointer-events:auto}
  /* html[data-side=off] .side .pane{display:none} (unscoped, above) already hides this while closed -- initSide()/
     the #tabs click handler (arena.js) keep it closed by default and open it on a tap. */
  .side .pane{position:fixed;left:0;right:0;bottom:0;height:60dvh;z-index:11;background:var(--felt2);
    border-top:1px solid var(--line2);box-shadow:0 -12px 40px var(--shadow2);padding-bottom:env(safe-area-inset-bottom)}

  #overlay{inset:40px 0 0 0}
  #overlay .box{max-width:94%}
  /* the desktop rule above is already a single flex column -- no grid-column bookkeeping needed here any more,
     just the phone-only sizing: scale the picture by width instead of the desktop's height cap. */
  #cardzoom-body{max-height:90dvh}
  /* the frameless scenes on the phone (Lukas 2026-10-12, the block at the end of this file): the bar is taller here
     (the sentence wraps, the buttons take a row), so the cards get less of the height and the line under the bar
     stays on the screen; a pile's cards are the hand's size. `html` in front: the general rules stand later in
     the file and would win at equal weight. */
  html #overlay .scene .grid{gap:8px;padding:10px 4% 14px;max-height:calc(100dvh - 330px)}
  html #overlay .scene .sbar,html #ask .scene .sbar{padding:12px 5%;gap:10px 14px}
  html #overlay .scene .prompt,html #ask .prompt{font-size:21px;flex-basis:100%}
  html #overlay .scene .sub{padding:0 4%;font-size:12px}
  :root:not([data-theme="pokeball"]) #cardzoom #cardzoom-body .grid .card{width:104px;height:145px}
  :root:not([data-theme="pokeball"]) #cardzoom #cardzoom-body .grid .card:not(.pic){zoom:1}
}

/* ---- card motion kit (spec 2026-09-18 card motion) ----
   Lukas 2026-09-18: "make an effect of wow and be immersive ... feel that you play cards not just digital squares,
   feel of shuffle". Shared by the landing hero (section 1, above) and the game-open shuffle (section 4, below);
   the lobby/ladder (section 2/3, a parallel worktree) use .deck3d/.deal by class name only -- their own CSS lives
   in their own regions, untouched here. Transforms + opacity only (compositor), so a slow device never drops a
   frame it can't recover from mid-shuffle. */

/* .deck3d: a stack of N face-down cards with real thickness -- --i (0..N-1, set per <i> by the caller) offsets
   each one 0.6px up so the pile reads as a pile, not one flat card. The caller sizes the container (width/height);
   every card fills it. */
.deck3d{position:relative;perspective:900px}
.deck3d>*{position:absolute;inset:0;transform:translateY(calc(var(--i,0) * -.6px))}

/* .back.brand: kept as an alias -- every .back now carries the arena's own look (see the table-look block above). */
.back.brand{background:var(--panel2)}

/* @keyframes riffle: even/odd cards split to opposite halves (--dir, set by :nth-child parity -- falls back to 1
   for a single element, e.g. the game board's one-piece deck stack, so it still "hops" in place), fall back to
   centre with a small forward lift so it reads as cards riffling rather than sliding, then square up. --i still
   drives the stack's own resting thickness offset throughout so the pile never flattens mid-riffle. */
.deck3d>*:nth-child(even){--dir:1}
.deck3d>*:nth-child(odd){--dir:-1}
@keyframes riffle{
  0%,100%{transform:translateY(calc(var(--i,0) * -.6px)) translateX(0) rotateZ(0) rotateX(0)}
  30%{transform:translateY(calc(var(--i,0) * -.6px)) translateX(calc(var(--dir,1) * 45%)) rotateZ(calc(var(--dir,1) * 6deg))}
  60%{transform:translateY(calc(var(--i,0) * -.6px)) translateX(0) rotateZ(0) rotateX(-35deg)}
  85%{transform:translateY(calc(var(--i,0) * -.6px)) translateX(0) rotateZ(0)}
}
.deck3d.riffling>*{animation:riffle 1.4s ease-in-out both;animation-delay:calc(var(--i,0) * 40ms)}
/* .loop (the queue/lobby loader, section 2/3): the same riffle stretched over 2s so ~600ms of it reads as a rest
   between passes -- the keyframe's own 85%/100% state (squared up, no lift) already IS the rest position. */
.deck3d.loop>*{animation:riffle 2s ease-in-out infinite;animation-delay:calc(var(--i,0) * 40ms)}
.deck3d.found>*{animation:none;transform:rotateY(180deg)}   /* the ladder queue "an opponent was found" flip (section 3) -- 400ms via .deck3d.found's own transition, B's CSS */

/* @keyframes deal: from the deck's position to the card's own resting place -- translate(--from-x,--from-y) (set
   once by dealFrom() below) plus a face-down flip + a slight shrink, unwinding to --rest (each caller's own normal
   transform, "none" when the card has none to begin with, e.g. a prize back). backface-visibility:hidden + the
   back drawn on ::after (.card.flip) is what makes the flip actually SHOW a back and then a face, not a blank
   card, mid-turn. */
@keyframes deal{
  0%{transform:translate(var(--from-x,0px),var(--from-y,0px)) rotateY(180deg) scale(.7)}
  100%{transform:var(--rest,none)}
}
.deal>*{animation:deal .52s cubic-bezier(.2,.8,.2,1) both;animation-delay:calc(var(--i,0) * 90ms)}
.card.flip{position:relative;backface-visibility:hidden}
.card.flip::after{content:"";position:absolute;inset:0;border-radius:5px;backface-visibility:hidden;transform:rotateY(180deg);
  background-color:var(--panel2);background-position:center,0 0,0 0;background-repeat:no-repeat,repeat,repeat;background-size:40%,auto,auto;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 28 28' fill='none' stroke='%23f2c14e' stroke-width='2' stroke-linejoin='round'%3E%3Cpath d='M14 2l10.39 6v12L14 26l-10.39-6V8z'/%3E%3Crect x='10' y='8.5' width='8' height='11' rx='1.4'/%3E%3Cpath d='M14 11.5l1.8 2.5-1.8 2.5-1.8-2.5z' fill='%23f2c14e' stroke='none'/%3E%3C/svg%3E"),repeating-linear-gradient(45deg,rgba(128,128,128,.14) 0 1px,transparent 1px 9px),repeating-linear-gradient(-45deg,rgba(128,128,128,.14) 0 1px,transparent 1px 9px);
  box-shadow:inset 0 0 0 4px var(--panel2),inset 0 0 0 6px var(--gold)}   /* the back look with background layers only (a mask cannot be used here), shown while rotated past 90deg */

/* @keyframes breathe: the idle state of any finished, dealt fan -- a slow shared bob, never a per-card one (it
   would fight a fanned card's own rotate/lift transform, see .hand .card). */
@keyframes breathe{0%{transform:translateY(0)}100%{transform:translateY(-4px)}}
.breathe{animation:breathe 6s ease-in-out infinite alternate}

/* section 4 "The game opens with a shuffle and a draw": .arena.opening is added by render() from the first render
   THIS PAGE LOAD where the own hand actually has cards while still in setup (arena.js), and stays on for every
   render within the next 4500ms (state.openingStart). Every selector below reaches render()'s ORDINARY markup
   (z-deck/hand, unchanged) -- dealFrom() (arena.js, next to fanOut) is the only thing that touches these
   elements, setting --from-x/--from-y after each such render from the real deck rects; nothing in the engine,
   the snapshot or render()'s own HTML changes for this.
   render round 2 (real Chrome, probed after the go-first click): a following render (a later snapshot, or
   renderPending) used to rebuild the board WITHOUT the class mid-flight, killing the animation before its own
   delay had even elapsed. --t (set by render() on .arena, the elapsed ms since openingStart) is subtracted from
   every delay below, so a torn-down-and-rebuilt element's animation starts with a NEGATIVE delay -- CSS runs an
   animation with a negative delay as if it had already been playing that long, i.e. it resumes mid-flight
   instead of restarting (or, once --t exceeds a given delay, simply renders that keyframe's already-past state). */
.arena.opening .col.bot.z-deck .stack.back{animation:riffle 1.4s ease-in-out both;animation-delay:calc(0s - var(--t,0ms))}
/* the hand deals AFTER the own deck's riffle (1.4s) finishes, 90ms apart, face-up (backface-visibility:hidden +
   .card.flip::after handles the reveal -- render() adds class "flip" to each hand card only while opening).
   --rest reuses the hand fan's OWN resting formula (arena.css ~966 .hand .card, untouched) so the card lands
   exactly on its fanned-out slot, never a hard cut once the animation ends. */
.arena.opening .hand .card{
  --rest:translateY(calc(abs(var(--off)) * 10px / max(var(--mid),1))) rotate(calc(var(--off) * 12deg / max(var(--n) - 1,1)));
  backface-visibility:hidden;animation:deal .52s cubic-bezier(.2,.8,.2,1) both;animation-delay:calc(1.4s + var(--i,0) * 90ms - var(--t,0ms))}

/* motion-2 section 4 (Lukas 2026-09-22, "Deck shuffle animation is missing"): the SAME @keyframes riffle the
   opening deal already plays on the viewer's own deck above, now triggered by class instead of the opening's
   one-shot timeline -- playLogFx (arena.js) adds it to either seat's deck stack on a fresh log "shuffle" line
   (a search shuffling its deck back, a mulligan, a Boss's-Orders-style effect) and removes it on animationend. */
.stack.back.riffling{animation:riffle 1.4s ease-in-out both}
@media (prefers-reduced-motion: reduce){.stack.back.riffling{animation:none}}
html.nofx .stack.back.riffling{animation:none!important}

/* render round 2: prizes read 0 during the whole hand deal (a fresh game sets them aside only once both actives
   are placed, well after this), so they can't ride .opening's timeline -- .arena.prizing is its own one-shot,
   fired the first render THIS PAGE LOAD the own prize count is witnessed going from 0 to >0 (state.prizingStart,
   arena.js), a 1200ms window of its own with its own clock (--pt, same resume trick as --t above). Six own and
   six opponent backs slide out of their OWN deck (dealFrom, per side) 60ms apart, at the same time; they stay
   face down throughout (no .flip class, no backface-visibility -- the emblem back reads fine mirrored). */
.arena.prizing .z-prizes .stack.prizes .back.mini{animation:deal .52s cubic-bezier(.2,.8,.2,1) both}
.arena.prizing .z-prizes .stack.prizes .back.mini:nth-child(1){animation-delay:calc(0ms - var(--pt,0ms))}
.arena.prizing .z-prizes .stack.prizes .back.mini:nth-child(2){animation-delay:calc(60ms - var(--pt,0ms))}
.arena.prizing .z-prizes .stack.prizes .back.mini:nth-child(3){animation-delay:calc(120ms - var(--pt,0ms))}
.arena.prizing .z-prizes .stack.prizes .back.mini:nth-child(4){animation-delay:calc(180ms - var(--pt,0ms))}
.arena.prizing .z-prizes .stack.prizes .back.mini:nth-child(5){animation-delay:calc(240ms - var(--pt,0ms))}
.arena.prizing .z-prizes .stack.prizes .back.mini:nth-child(6){animation-delay:calc(300ms - var(--pt,0ms))}

/* render round 1: the riffle animation lands on the CONTAINER's children (.deck3d.riffling>*), not the container
   itself -- targeting only .deck3d.riffling/.deck3d.loop left every card's computed animation-name still "riffle"
   under reduced motion. */
@media (prefers-reduced-motion: reduce){.deck3d.riffling>*,.deck3d.loop>*,.deal>*,.breathe{animation:none}}

/* ---- decks: boxes + builder (2026-09-18 night, box rebuilt 2026-09-19) ---------------------------------------
   Spec docs/superpowers/specs/2026-09-18-deck-builder-design.md. Tokens only, both themes read through them;
   transforms + opacity + filter only in every motion rule below, so prefers-reduced-motion/html.nofx just needs
   to zero the transition/animation, never restyle anything. Card tiles are 5:7 everywhere (84x117, 92x128,
   72x100 already exists as .dbcard). */

/* the gallery: one real CSS 3D flip-top deck box per list (Lukas 2026-09-19: "make more like deck and opening
   more realistic now it more like crappy envelope ... check how Magic arena does that animation but use pokemon
   deck boxes" -- three-quarter view standing on a shelf; hover/focus/tap turns it toward the viewer and an
   Elite-Trainer-style lid, hinged at the BACK top edge, swings up and away while the three headline cards slide
   up out of the open top). .box3d is one preserve-3d group (--d the box's depth) holding every face, the lid and
   the cards, so they all share the box's own three-quarter rotation -- .dbox itself stays the flat grid cell/
   link, sized by aspect-ratio, giving .box3d (positioned bottom:0, 86% of the cell's height) headroom above for
   the lid swing and the cards. */
/* the lid and the cards rise above the cell on .open -- nothing along the chain may clip them. */
.shell .body{overflow:visible}
.boxes{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:36px 40px;overflow:visible;
  padding-top:120px;margin-top:-70px}   /* headroom for the open lid/cards without moving the layout */
@media (max-width:640px){.boxes{grid-template-columns:repeat(auto-fill,minmax(118px,1fr));gap:26px 12px}}
.dbox{--tc:var(--t-Colorless);--d:44px;position:relative;display:block;aspect-ratio:5/7;text-decoration:none;color:inherit;
  perspective:1100px;transition:transform .25s ease,box-shadow .25s ease;outline:none;overflow:visible}
.dbox:hover,.dbox:focus-visible,.dbox.open{transform:translateY(-6px)}
/* a `filter` (or overflow:hidden/opacity<1/backdrop-filter/mix-blend-mode/clip-path) on any element in this
   preserve-3d chain forces its used transform-style to flat, un-depth-sorting its children -- none of box3d/lid/
   cards may carry one (real-Chrome render, 2026-09-19: "the box is FLAT ... cards paint on top of the closed
   front face"). The box's own shadow lives on the leaf .f-front instead. Round 5 (Lukas: "make that deck more
   3d" -- the old rest/open poses turned nearly face-on, so the side face and the open rim vanished and the box
   read flat): a bigger depth (--d 34->44) and both poses turned further so a side face and the open top's rim
   stay visible at rest AND open; open tips forward (rotateX 12, up from 4) so you look slightly INTO the box
   rather than dead-on. */
.box3d{position:absolute;left:0;bottom:0;width:100%;height:86%;transform-style:preserve-3d;container-type:size;
  transform:rotateX(10deg) rotateY(-22deg);transition:transform .5s cubic-bezier(.2,.8,.2,1)}
.dbox.open .box3d{transform:rotateX(12deg) rotateY(-14deg) translateY(-8px)}
/* the seven faces: front/back centred and pushed +-d/2 along z (the classic closed-cube technique); f-inner is a
   second back wall 1px in front of f-back (round 5) so the open top looks into a real cavity, not straight
   through to a flat back; left/right are d-wide panels centred on the box's own left/right edge line then folded
   in with rotateY(+-90deg) so they land flush on that edge, spanning the same z-depth as front/back; top is the
   box's OPEN top rim, folded flat with rotateX(90deg) the same way. */
.f-front,.f-back,.f-inner,.f-right,.f-left,.f-top{position:absolute;backface-visibility:hidden}
.f-front{inset:0;border-radius:6px;overflow:hidden;background:var(--panel);border:1px solid var(--line);
  display:flex;flex-direction:column;transform:translateZ(calc(var(--d) / 2));
  box-shadow:inset 1px 1px 0 rgba(255,255,255,.12),0 14px 24px var(--shadow2)}
/* round 5 lighting (gradients only -- no filter/opacity<1 on this preserve-3d chain, see the note above): a
   115deg sheen over the front reads as a light source; the right face is darker than the front, the left face
   darker still, so the box reads as a lit solid rather than three flat tinted rectangles. */
.f-front::before{content:"";position:absolute;inset:0;pointer-events:none;z-index:1;
  background:linear-gradient(115deg, rgba(255,255,255,.10) 0%, transparent 45%, rgba(0,0,0,.18) 100%)}
.f-back{inset:0;border-radius:6px;background:color-mix(in srgb, var(--tc) 55%, black);
  transform:rotateY(180deg) translateZ(calc(var(--d) / 2))}
.f-inner{inset:0;border-radius:6px;background:color-mix(in srgb, var(--tc) 25%, black);
  transform:rotateY(180deg) translateZ(calc(var(--d) / 2 - 1px))}
.f-right,.f-left{top:0;width:var(--d);height:100%;
  background-image:repeating-linear-gradient(45deg,transparent 0 6px,rgba(0,0,0,.12) 6px 7px)}
.f-right{left:calc(100% - var(--d) / 2);transform:rotateY(90deg);
  background-color:color-mix(in srgb, var(--tc) 55%, black);box-shadow:inset -1px 0 0 rgba(0,0,0,.4)}
.f-left{left:calc(var(--d) / -2);transform:rotateY(-90deg);background-color:color-mix(in srgb, var(--tc) 45%, black)}
/* the box's inner cavity, only seen once the lid opens -- opacity stays 1 (opacity<1 on a preserve-3d node
   flattens it and its siblings, see the note above); translateY(2px) keeps it off the closed lid's own plane
   (z-fighting) rather than exactly coplanar with it. */
.f-top{left:0;top:calc(var(--d) / -2);width:100%;height:var(--d);
  background:linear-gradient(180deg, #000 0%, rgba(0,0,0,.6) 100%);opacity:1;
  transform:translateY(2px) rotateX(90deg)}
/* Lukas 2026-10-05 (screenshot, the label cut off at the box bottom): the art gives way, not the text -- a two-line
   name plus a wrapped label row ran past the fixed 58%, so the art shrinks (flex-shrink) until the chips fit -- never
   under 30%, the closed lid's .lip (30cqh) covers that much and the name would slide under it. */
.dbox-art{position:relative;height:58%;min-height:30%;background:var(--panel2);overflow:hidden;flex:0 1 auto}
/* round 3: object-fit:cover alone still showed the WHOLE card scaled down (name bar, HP, attack text all
   readable) -- the illustration is only the band from ~12% to ~48% of a card's height, so a scale (not a crop
   via new markup) magnifies that band to fill the window; transform-origin keeps it centred on that band.
   Round 4 (Lukas 2026-09-19: "Pult deck boxes have text visual which shouldn't be"): the old, narrower scale
   still let a regular/ex/Tera/JP-alt layout's name bar or attack text creep into a corner -- scale(2) around 26%
   narrows the visible band to ~14-39% of the card, safely inside every layout's own illustration box.
   Round 5 (Lukas, screenshot of an open Dragapult ex / Blaziken ex box, its Tera rule line across the art): a
   Tera print's OWN rule text sits ABOVE the illustration (name bar ~3-9%, Tera text ~9-17%, art ~17-55%), so the
   fixed 26% origin lands on it -- dboxHtml sets --arty per card (35% for a Tera lead, 26% otherwise). */
.f-front img{width:100%;height:100%;object-fit:cover;object-position:50% 14%;display:block;transform:scale(2);transform-origin:50% var(--arty, 26%)}
.dbox-art::after{content:"";position:absolute;inset:0;
  background:linear-gradient(to top,var(--panel) 0,transparent 60%),linear-gradient(0deg,color-mix(in srgb, var(--tc) 35%, transparent),color-mix(in srgb, var(--tc) 35%, transparent))}
.dbox-info{position:relative;z-index:2;flex:1;padding:10px 12px 12px;display:flex;flex-direction:column;gap:3px;min-width:0}
.dbox-info .arch{font-family:var(--display);font-size:18px;font-weight:700;text-transform:uppercase;letter-spacing:.02em;color:var(--ink);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;line-height:1.15}
.dbox-info .nm{font-size:12px;color:var(--ink2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dbox-info .chips{margin-top:auto;font-size:10px;letter-spacing:.04em}
.dbox-info .chips .nl{color:var(--red)}
/* a narrow box (phone ring 126px, phone matchup 120px) takes the gallery's compact text (.boxes below), so the
   info fits above the 30% art floor; .box3d is the size container. */
@container (max-width:140px){
  .dbox-info{padding:8px 9px 9px}
  .dbox-info .arch{font-size:14px;line-height:1.1}
  .dbox-info .nm{font-size:11px}
  .dbox-info .chips{font-size:9px}
}
/* the lid: a flip-top plate the size of the top face, hinged at the BACK top edge. transform-origin:50% 0 alone
   pivots at the box's TOP MID-DEPTH line (y=0,z=0), which would lay rotateX(90deg) flat spanning z 0..+d --
   sticking d/2 past the front face and leaving the back half of the top uncovered; translateZ(-d/2 + 1px) (the
   +1px keeps the lid a hair in front of the coplanar .f-top rather than z-fighting it) shifts the hinge back to
   the true back-top edge first, so closed the lid spans z -d/2..+d/2, flush with front/back, covering .f-top.
   rotateX(-125deg) swings it up and AWAY from the viewer, toward the back, on .open (round 5: was -115, a
   further swing so the lit top plate -- not its underside -- is what shows behind the cards). The .lip is the
   cover's front flap, folded a further -90deg off the lid's own front edge -- rigid relative to the lid, always
   perpendicular to it -- so it hangs down over the front face's top when the lid is closed, the way a real
   flip-top (Elite Trainer Box) lid's cover does; it carries the brand ring. Round 5: the plate's own gradient
   flips to read as a LIT top surface (lighter near the hinge, --tc at the front) rather than a shaded one. */
.lid{position:absolute;top:0;left:0;width:100%;height:var(--d);transform-origin:50% 0%;transform-style:preserve-3d;
  background:linear-gradient(180deg, color-mix(in srgb, var(--tc) 85%, white) 0%, var(--tc) 100%);
  border-radius:6px 6px 0 0;transform:translateZ(calc(var(--d) / -2 + 1px)) rotateX(90deg);
  transition:transform .5s cubic-bezier(.2,.8,.2,1)}
.dbox.open .lid{transform:translateZ(calc(var(--d) / -2 + 1px)) rotateX(-125deg)}
/* round 4 (Lukas 2026-09-19: "make cover maybe bigger so that it would feel more like box opening"): the lip
   grows from a thin 28px flap to a real flip-top cover wrapping the top ~30% of the front face (Elite Trainer
   deck box proportions) -- height:30cqh reads it against .box3d's own (container-type:size) height, since the
   lip's literal containing block (.lid) is only --d tall. Same diagonal lattice as the side faces plus the same
   115deg sheen as the front (round 5), layered as a second background-image; a 1px darker seam at the bottom
   edge and a soft shadow line (::after) hanging onto the front read as the cover's own edge once it lifts clear. */
.lip{position:absolute;left:0;top:100%;width:100%;height:30cqh;transform-origin:50% 0%;
  background:var(--tc);
  background-image:repeating-linear-gradient(45deg,transparent 0 6px,rgba(0,0,0,.12) 6px 7px),
    linear-gradient(115deg, rgba(255,255,255,.10) 0%, transparent 45%, rgba(0,0,0,.18) 100%);
  border-radius:0 0 4px 4px;border-bottom:1px solid color-mix(in srgb, var(--tc) 55%, black);transform:rotateX(-90deg)}
.lip::after{content:"";position:absolute;left:0;top:100%;width:100%;height:6px;
  background:linear-gradient(rgba(0,0,0,.35),transparent)}
.dbox-mark{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:20px;height:20px;display:flex;align-items:center;justify-content:center;color:var(--gold)}
.dbox-mark svg{width:100%;height:100%;stroke:var(--gold)}
/* the cards: the three headline dbCard tiles standing upright inside the box, bottoms at 55% of the box height
   (hidden behind the opaque .f-front when closed); .open lifts them up through the open top with the existing
   fan/stagger, waiting for the lid to clear (120ms + a per-card delay), then idles on the shared .breathe bob.
   GEOMETRY (2026-09-20, fix round 1 -- the real leak was `.ring .dbox` at 160px, a real-Chrome measurement of
   card top vs the front-face edge; the gallery grid never leaked, see the note above `.boxes .cards` below).
   .dbox is aspect-ratio:5/7 (height = 1.4 x its own width W), .box3d is 86% of that height = 1.204W, and .box3d
   is the cq container (container-type:size) so 1cqh = 1.204 x 1cqw. bottom:55% leaves a room of (100-55) = 45cqh
   above the card's own bottom edge before it pokes out past the box3d top (into the lid), i.e. 0.5418W px of
   room. .dbcard also has a 1px border (both edges, content-box) adding a flat +2px to its rendered height on
   top of whatever aspect-ratio:5/7 gives its content box -- at the first pass's 32cqh cap this left under 1px
   of room at 160px (measured, essentially zero); width:min(72px,30cqh) leaves real margin everywhere real boxes
   go: content height = 30cqh x 1.4 = 42cqh, +2px border = 0.50568W + 2px total, which fits 0.5418W room for any
   W >= ~55px (all real boxes are 120px+) -- e.g. at 160px (the ring): 57.9x81px content + 2px border = 83px
   fits the 86.7px room (3.8px to spare). The min(72px, ...) keeps the original 72px size (100.8px content +2px
   border = 102.8px) on any box big enough (~199px+) not to need the cap -- matchup 190px/q-stage 220px both
   still fit that size against their own larger room. */
.cards{position:absolute;left:8%;right:8%;bottom:55%;height:0;transform-style:preserve-3d}
/* round 5 ("make that deck more 3d"): the cards tilt back into the box's own depth (rotateX(-6deg)) so they read
   as standing IN it rather than pasted flat on top, and each sits at its own depth (translateZ, the --i middle
   card at 0, the outer two behind it) rather than all three sharing one plane. Cards keep their own drop-shadow
   filter -- they're leaves, so a filter here doesn't flatten anything. `translate` (not margin-left) centres the
   card on its own axis -- it composes with `transform` (per spec, translate applies before transform), so every
   .open rule below keeps its own translateY/rotate/translateX unchanged. */
.cards>*{position:absolute;left:50%;bottom:0;translate:-50% 0;width:min(72px,30cqh);height:auto;aspect-ratio:5/7;
  transform:rotateX(-6deg) translateZ(calc((var(--i) - 1) * -6px));
  filter:drop-shadow(0 8px 10px rgba(0,0,0,.45));
  transition:transform .45s ease-out;transition-delay:calc(120ms + var(--i) * 70ms);transform-origin:50% 100%}
.dbox.open .cards>*{transform:rotateX(-6deg) translateY(-92%) rotate(calc((var(--i) - 1) * 8deg)) translateX(calc((var(--i) - 1) * 24px)) translateZ(calc((var(--i) - 1) * -6px))}
/* the shelf shadow: an ellipse under the box, skewed to match the box's own turned angle, wider and a touch
   softer once open. */
.dbox::after{content:"";position:absolute;left:50%;bottom:-6px;width:60%;height:12px;border-radius:50%;
  background:radial-gradient(ellipse at center, var(--shadow2) 0%, transparent 70%);
  transform:translateX(-46%) scaleX(1.15);transition:transform .3s ease;z-index:-1}
.dbox.open::after{transform:translateX(-46%) scale(1.25)}
/* THE CLIPBOARD BOX (Lukas 2026-09-19 afternoon): the ghost's dashed placeholder look is scoped to :not(.filled)
   -- wireDeckCase's setGhostTop adds .filled the moment a paste (clipboard or manual) resolves to a real top-3,
   swapping the ghost's OWN inner markup to the standard dboxHtml/.box3d face (spec: "the ghost renders as a REAL
   box"); the SAME outer <a class="dbox new clip"> element stays (the ring's own references), so the dashed/flat
   layout below must fall away the instant a real .box3d face is inside it, or that face would be squashed into a
   flex column instead of filling the cell the way every other box does. */
.dbox.new:not(.filled){display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;border:2px dashed var(--line2);
  border-radius:10px;background:transparent;color:var(--ink2);font-family:var(--display);text-transform:uppercase;font-size:13px;letter-spacing:.06em;
  box-shadow:0 8px 18px var(--shadow)}
.dbox.new:not(.filled) .plus{font-size:40px;line-height:1;color:var(--mute)}
.dbox.new:not(.filled) .hint{font-size:10px;text-transform:none;letter-spacing:.02em;max-width:110px;text-align:center;color:var(--mute)}
.dbox.new:not(.filled):hover,.dbox.new:not(.filled):focus-visible{border-color:var(--gold);color:var(--gold);box-shadow:0 20px 40px var(--shadow2)}
/* the ghost box's own cards row (spec: "a non-empty pasted list shows its top on the ghost") -- overrides the
   real box's .cards/.cards>* (absolute, inside the .box3d preserve-3d chain) with a plain static row, since the
   UNFILLED ghost has no .box3d wrapper at all (a .filled one does -- its .cards is the real box's own). */
.dbox.new:not(.filled) .cards{position:static;height:auto;display:flex;gap:4px;justify-content:center;margin-top:4px;transform-style:flat}
/* no .box3d ancestor here (the comment above), so .cards>*'s cqh-based width has no query container to resolve
   against -- pinned back to the original flat 72x100 static flex-row tile, and translate:-50% 0 (a centring
   offset meant for the absolutely-positioned real box) is turned off or it would shift every static tile left. */
.dbox.new:not(.filled) .cards>*{position:static;margin-left:0;translate:none;width:72px;height:100px;aspect-ratio:auto;
  transform:none;transition:none;transform-origin:center}
/* the shake (Lukas: a failed/empty clipboard read, or a check that came back with no top, right after a click)
   -- transform only, and only on the OUTER .dbox: the preserve-3d chain (.box3d/.lid/.cards, see the note above
   .box3d) must never carry a transform of its own mid-animation, or it silently flattens. */
@keyframes dcase-shake{10%,90%{transform:translateX(-1px)}20%,80%{transform:translateX(2px)}30%,50%,70%{transform:translateX(-4px)}40%,60%{transform:translateX(4px)}}
.dbox.shake{animation:dcase-shake .5s}
@media (prefers-reduced-motion: reduce){.dbox.shake{animation:none}}
html.nofx .dbox.shake{animation:none}

/* the Decks gallery only (Lukas 2026-09-19 evening: "make those deck boxes smaller and more 3d") -- every rule
   below is scoped under .boxes so the shared .dbox/.box3d rules above, reused by the ring/matchup/waiting-page/
   ladder, stay untouched. A tighter perspective (1100px->640px) is real foreshortening: the same --d depth reads
   as a deeper box at a smaller size. Lighting stays gradients only -- nothing added to the filter/opacity/
   overflow/clip-path list the preserve-3d chain note above forbids.
   Fable render round 2 (1440, /decks): --d:66px at rotateY(-34deg) pushed each box's right side face ~25px past
   its own grid cell into the next column (Dragapult's side sitting on the Alakazam box beside it) -- 58px depth
   plus the wider 40px column gap (.boxes{gap:36px 40px} above) keeps the rotated box's projected footprint
   inside cell+gap at the ~177px cell width 1440 renders. Cards also climbed over the "My decks"/"Worlds 2026
   top-32" h2 above the grid -- padding-top:120px (net +30px) plus a shorter open rise (-92% -> -78%, gallery
   only) keeps the risen cards' top below the heading's baseline. The "+ New deck" ghost tile filled the WHOLE
   5/7 cell while a real box only occupies the bottom 86% of it, foreshortened -- sized and bottom-anchored like
   a closed box so its outline matches its neighbours' footprint. */
.boxes .dbox{perspective:640px;--d:58px}
.boxes .box3d{transform:rotateX(16deg) rotateY(-34deg)}
.boxes .dbox.open .box3d{transform:rotateX(14deg) rotateY(-22deg) translateY(-10px)}
.boxes .f-left{background-image:linear-gradient(90deg,rgba(0,0,0,.4),rgba(0,0,0,.13)),
    repeating-linear-gradient(45deg,transparent 0 6px,rgba(0,0,0,.12) 6px 7px)}
.boxes .f-right{background-image:linear-gradient(90deg,rgba(255,255,255,.08),transparent),
    repeating-linear-gradient(45deg,transparent 0 6px,rgba(0,0,0,.12) 6px 7px)}
.boxes .dbox::after{width:74%;height:16px;bottom:-9px;transform:translateX(-40%) scaleX(1.25)}
.boxes .dbox.open::after{transform:translateX(-40%) scale(1.3)}
.boxes .dbox-info{padding:8px 9px 9px}
.boxes .dbox-info .arch{font-size:14px;line-height:1.1}
.boxes .dbox-info .nm{font-size:11px}
.boxes .dbox-info .chips{font-size:9px}
.boxes .dbox-mark{width:16px;height:16px}
/* 2026-09-20 (Lukas: "shouldn't those popping up cards be bigger compared to deckbox? ... on PC looks a little bit
   strange"): the gallery's cards were a fixed 56px inside a fluid box (33% of a 191px box at 1440, vs ~50% in the
   case/matchup) -- now 45cqw of .box3d (container-type:size), like the other stages' ratio; seated lower (bottom:44%)
   so the taller card still hides behind the closed front, risen a touch less (-80%) so it clears the top without
   reaching the row above. Measured 49-50% at 1440/2000/390.
   FIX ROUND 1 (2026-09-20, real-Chrome measurement of this branch vs main): the gallery grid was never the box
   Lukas screenshotted -- .boxes card tops sat 16-22px BELOW the front edge when closed at 137px/191px (the
   rotateX(16deg) tilt pushes them down, unlike the ring's rotateX(0)), so bottom:44%/-80% never leaked and this
   whole rule block reverts to the values above. (The actual leak was `.ring .dbox` -- fixed in the base
   `.cards>*` rule below, which the gallery also inherits.) The old `.boxes .cards>*{margin-left:-22.5cqw}` stays
   gone: the base rule's `translate:-50% 0` shifts by 50% of the element's OWN width, so for this 45cqw card that
   is exactly -22.5cqw already -- identical centring, no override needed. */
.boxes .cards{bottom:44%}
.boxes .cards .dbcard{width:45cqw;height:auto;aspect-ratio:5/7}
.boxes .dbcard img{width:100%;height:100%}
.boxes .dbox.open .cards>*{transform:rotateX(-6deg) translateY(-80%) rotate(calc((var(--i) - 1) * 8deg)) translateX(calc((var(--i) - 1) * 24px)) translateZ(calc((var(--i) - 1) * -6px))}
.boxes .dbox.new{height:86%;width:82%;align-self:end;margin:auto 0 0 4px}

/* ---- the deck display case: the matchup deck selector (Lukas 2026-09-19, docs/superpowers/specs/
   2026-09-19-deck-display-case-design.md -- "I need you to rethink deck selectors for the matchups ... something
   better than just a simple picklist"). A 3D arc of the SAME .dbox boxes above (arena.js dboxHtml/dcaseBoxHtml),
   laid out by JS (wireDeckCase) -- .stage is the perspective container (its OWN overflow:hidden is fine, it sits
   outside the preserve-3d chain), .ring is the preserve-3d group. NEVER put filter/opacity<1/overflow:hidden/
   clip-path/mix-blend-mode on .ring, .ring .dbox, .box3d or any face -- see the note above .box3d, it silently
   flattens the whole chain (2026-09-19 lesson, docs/HANDOFF.md). Geometry (STEP/DEPTH/MAXK) is measured from the
   stage's own width in JS, not a breakpoint, so the ladder's 320px queue column compacts itself the same way. */
.dcase{display:flex;flex-direction:column;gap:8px;margin-top:10px}
.dcase-head{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px}
.dcase-q{flex:1;min-width:140px;height:32px;padding:0 10px;border:1px solid var(--line);border-radius:8px;background:var(--panel2);color:var(--ink)}
.dcase-chips{display:flex;gap:6px;flex-wrap:wrap}
.dcase-chips .fchip{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 10px;border-radius:13px;border:1px solid var(--line);background:var(--panel);color:var(--ink2);font:inherit;font-size:12px;cursor:pointer}
.dcase-chips .fchip i{width:9px;height:9px;border-radius:50%;background:var(--tc)}
.dcase-chips .fchip.on{border-color:var(--tc);color:var(--ink);box-shadow:inset 0 0 0 1px var(--tc)}
/* fable-check round 3 (compact = the ladder's 320px queue column): three rows of type chips ate the column --
   the search box alone stays (a phone-width lobby case keeps its chips, plenty of width there). */
.dcase.compact .dcase-chips{display:none}
.dcase .stage{position:relative;height:400px;border-radius:14px;overflow:hidden;perspective:1400px;perspective-origin:50% 40%;
  background:url(/static/felt-shelf.webp) center 62%/cover;box-shadow:inset 0 0 0 1px var(--line),inset 0 24px 40px -20px #000c,0 18px 40px -20px #000}
:root[data-theme="pokeball"] .dcase .stage{background:url(/static/felt-shelf-light.webp) center 62%/cover}   /* board-arena 2026-09-19 section 3 */
.dcase .stage::before{content:"";position:absolute;inset:0;background:radial-gradient(ellipse at 50% 70%,transparent 30%,#000a 100%);pointer-events:none}
.dcase.compact .stage{height:300px}
.ring{position:absolute;left:50%;bottom:34px;width:0;height:0;transform-style:preserve-3d}
.ring .dbox{position:absolute;left:-80px;bottom:0;width:160px;transition:transform .45s cubic-bezier(.2,.7,.2,1);cursor:pointer}
.ring .dbox.far{visibility:hidden}
.ring .dbox.new:not(.filled){width:130px;aspect-ratio:5/7;font-size:13px;left:-65px}
/* fable-check round 3: in the compact/phone stages the centred open box's risen cards reached the stage's top
   edge and printed through .stage-group -- the same open-pose formula (fan/rotate/translateZ unchanged) but a
   shorter rise (-62% instead of the gallery's -92%, there is far less headroom above a 300-330px stage). */
.dcase.compact .dbox.open .cards>*{transform:rotateX(-6deg) translateY(-62%) rotate(calc((var(--i) - 1) * 8deg)) translateX(calc((var(--i) - 1) * 24px)) translateZ(calc((var(--i) - 1) * -6px))}
.dcase .arrow{position:absolute;top:50%;transform:translateY(-50%);z-index:5;width:44px;height:44px;border-radius:50%;
  border:1px solid #ffffff33;background:#0008;color:var(--ink);font-size:30px;line-height:1;cursor:pointer;backdrop-filter:blur(4px)}
.dcase .arrow.l{left:14px}.dcase .arrow.r{right:14px}
.dcase .arrow:hover{border-color:var(--gold);color:var(--gold)}
.stage-group{position:absolute;left:0;right:0;top:14px;text-align:center;font-family:var(--display);font-size:12px;
  letter-spacing:.16em;text-transform:uppercase;color:#f3e9c9cc;text-shadow:0 1px 2px #000;pointer-events:none}
:root[data-theme="pokeball"] .stage-group{color:var(--ink);text-shadow:none}   /* board-arena 2026-09-19 section 3: dark ink on the pale felt */
.stage-group b{color:var(--gold)}
.dcase.compact .stage-group{top:auto;bottom:8px}   /* out from under the risen cards (round 3) */
/* the ticker always fits its own column (round 3: 27 ticks spilled past the ladder's 282px stage both sides) --
   ticks shrink instead of overflowing; the .gap divider pins its own flex-basis so it stays a true 1px hairline. */
.ticker{display:flex;gap:4px;justify-content:center;align-items:flex-end;height:26px;padding:0 8px;max-width:100%;overflow:hidden}
.ticker[hidden]{display:none}
.ticker i{display:block;width:10px;height:10px;border-radius:2px;background:var(--tc);opacity:.35;transform:skewX(-14deg);transition:all .25s;cursor:pointer;flex:0 1 10px;min-width:3px}
.ticker i.on{opacity:1;height:22px;box-shadow:0 0 12px var(--tc)}
.ticker i.near{opacity:.7;height:14px}
.ticker i.gap{width:1px;flex:0 0 1px;min-width:1px;background:#ffffff55;height:18px;opacity:1;margin:0 4px;transform:none;cursor:default}
.dcase-pick{display:flex;gap:10px;align-items:baseline;font-size:14px}
.dcase-pick b{font-family:var(--display);font-size:18px;color:var(--ink)}
/* compact pick line (round 3): "Selected  Dragapult ex  60 cards · Trained · 17 Sept" wrapped into a mess at
   282px -- stacked instead, one line per part. */
.dcase.compact .dcase-pick{flex-direction:column;align-items:flex-start;gap:2px}
.dcase.compact .dcase-pick b{font-size:16px}
.dcase-paste{font-size:13px}
@media (prefers-reduced-motion: reduce){.ring .dbox{transition:none}}
html.nofx .ring .dbox{transition:none}
@media (max-width:640px){
  .dcase .stage{height:330px;perspective:900px}
  .q-stage{height:330px}
  .ring .dbox{width:126px;left:-63px}
  .dcase .dbox.open .cards>*{transform:rotateX(-6deg) translateY(-62%) rotate(calc((var(--i) - 1) * 8deg)) translateX(calc((var(--i) - 1) * 24px)) translateZ(calc((var(--i) - 1) * -6px))}
  .dcase .stage-group{top:auto;bottom:8px}
  .dcase .arrow{width:36px;height:36px;font-size:24px}
  .dcase-q{width:100%}
}

/* C -- the matchup: a compact facing pair above the two cases (spec section C), one open .dbox each side, in bot/
   solo/trainer modes. NOT `.side` -- that class is the game board's own side panel. */
.matchup{display:grid;grid-template-columns:1fr auto 1fr;align-items:end;gap:24px;margin:0 0 4px;padding:110px 30px 30px;
  border-radius:16px;background:url(/static/felt-shelf.webp) center 40%/cover;box-shadow:inset 0 0 0 1px var(--line),inset 0 30px 60px -30px #000}
:root[data-theme="pokeball"] .matchup{background:url(/static/felt-shelf-light.webp) center 40%/cover}   /* board-arena 2026-09-19 section 3 */
.matchup .mside{display:flex;flex-direction:column;align-items:center;gap:10px;position:relative}
/* fable-check round 2, real-Chrome render (1440): .who sat in normal flow ABOVE the .dbox, in the same screen
   area the open box's risen lid/cards fly into (the .matchup 110px top padding exists for exactly that) -- the
   two painted over each other and the label read as missing. Absolute + a fixed offset lifts it clear, into the
   top of that padding strip, above the risen cards' own reach. */
.matchup .mside .who{position:absolute;top:-96px;left:50%;transform:translateX(-50%);white-space:nowrap;
  font-family:var(--display);font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:#f3e9c9cc;text-shadow:0 1px 2px #000}
:root[data-theme="pokeball"] .matchup .mside .who{color:var(--ink);text-shadow:none}   /* board-arena 2026-09-19 section 3 */
/* light stages fix 2026-09-20 (Lukas: "background seams off in light theme, some text difficult to read"): the ivory felt plate, no black vignettes/tiles under the pokeball theme */
:root[data-theme="pokeball"] .q-stage::before,:root[data-theme="pokeball"] .dcase .stage::before{background:radial-gradient(ellipse at 50% 70%,transparent 45%,rgba(20,30,50,.10) 100%)}
:root[data-theme="pokeball"] .q-stage,:root[data-theme="pokeball"] .dcase .stage,:root[data-theme="pokeball"] .waitstage{box-shadow:inset 0 0 0 1px var(--line2),inset 0 24px 40px -20px rgba(20,30,50,.18),0 18px 40px -20px rgba(20,30,50,.3)}
:root[data-theme="pokeball"] .matchup{box-shadow:inset 0 0 0 1px var(--line2),inset 0 30px 60px -30px rgba(20,30,50,.18)}
:root[data-theme="pokeball"] .dcase .arrow{background:#ffffffd9;border-color:rgba(20,30,50,.18)}
:root[data-theme="pokeball"] .matchup .vs{text-shadow:0 2px 0 #fff}
.matchup .dbox{width:190px}
.matchup .mside.them .box3d{transform:rotateX(10deg) rotateY(22deg)}
.matchup .mside.them .dbox.open .box3d{transform:rotateX(12deg) rotateY(14deg) translateY(-8px)}
.matchup .vs{align-self:center;font-family:var(--display);font-weight:800;font-size:44px;letter-spacing:.06em;color:var(--gold);
  text-shadow:0 2px 0 #000,0 0 30px #d4a84b88;padding-bottom:70px}
.matchup .vs small{display:block;font-size:11px;letter-spacing:.2em;color:var(--ink2);text-shadow:none;margin-top:4px}
@media (max-width:640px){
  .matchup{grid-template-columns:1fr auto 1fr;gap:10px;padding:80px 12px 20px}
  .matchup .dbox{width:120px}
  .matchup .mside .who{top:-70px}   /* the phone padding strip above shrinks (110px -> 80px) with it */
  .matchup .vs{font-size:26px;padding-bottom:40px}
}

/* the deck pane: real piles of copies (Lukas: "feel that you play cards not just digital squares") -- .pile's own
   width and each .pcard's offset both read --n/--k (set per pile/copy in JS), the highest --k drawn on top and
   fully visible. */
.dk-group{margin-top:14px}
.dk-group h3{font-family:var(--display);font-size:14px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink2);margin:0 0 8px}
.piles{display:flex;flex-wrap:wrap;gap:22px 14px}
.pile{position:relative;width:calc(92px + (var(--n) - 1) * 16px);flex:none}
.pile-stack{position:relative;height:128px}
.pcard{position:absolute;top:0;left:calc(var(--k) * 16px);width:92px;height:128px;border-radius:5px;overflow:hidden;
  background:var(--panel2);border:1px solid var(--line2);box-shadow:0 3px 8px var(--shadow)}
.pcard img{width:100%;height:100%;object-fit:cover;display:block}
.pcard .fallback{display:none;position:relative;width:100%;height:100%;box-sizing:border-box;padding:6px;font-family:var(--display);font-size:11px;color:var(--ink2)}
.pcard .fallback i{display:block;width:100%;height:5px;border-radius:5px;margin-bottom:5px}
.pcard.noimg img{display:none}.pcard.noimg .fallback{display:block}
.pile .badge{position:absolute;right:2px;bottom:2px;z-index:2;font-family:var(--display);font-weight:700;font-size:13px;
  color:var(--ink);background:var(--panel);border:1px solid var(--line2);border-radius:10px;padding:1px 7px}
@keyframes bump{0%{transform:scale(1)}40%{transform:scale(1.35)}100%{transform:scale(1)}}
.pile .badge.bump{animation:bump .3s ease-out}
/* fix (Lukas 2026-09-19, "difficult to hover check because they disappear"): the controls used to sit 30px BELOW
   the pile (bottom:-30px), so moving the mouse down off the pile to reach them closed the hover and they vanished.
   Centred on the TOP card instead (the same --n/--k formula the cards themselves use: the top card, k = n-1, sits
   at left (n-1)*16px, width 92px, so its centre x is (n-1)*16px + 46px) -- the mouse never has to leave the pile
   to reach them.
   Fable render round 2 (real Chrome, 2000px, n=4): `transform:translate(-50%,-50%)` measured as a no-op -- the
   rendered rect's LEFT EDGE landed exactly on the target centre (307 of 308) instead of straddling it, clipping
   the "+" off the pile's right edge. Rather than chase why the percentage transform wasn't taking, the box is
   sized explicitly now (2 * 34px buttons + 18px gap + 2 * 10px horizontal padding = 106px; 34px buttons + 2 * 6px
   vertical padding = 46px) and centred with plain calc() on left/top -- no transform, nothing left to not-apply. */
.pile-ctl{position:absolute;top:calc(50% - 23px);left:calc((var(--n) - 1) * 16px - 7px);width:106px;height:46px;box-sizing:border-box;
  display:none;align-items:center;justify-content:center;gap:18px;padding:6px 10px;border-radius:999px;background:#000000b3;backdrop-filter:blur(3px);z-index:3}
.pile:hover .pile-ctl,.pile:focus-within .pile-ctl{display:flex}
.pile-ctl button{width:34px;height:34px;border-radius:50%;padding:0;font-family:var(--display);font-size:18px;line-height:1;display:flex;align-items:center;justify-content:center;flex:none}
.pile-ctl button:hover{color:var(--gold)}
.pile-ctl button:disabled{opacity:.4;cursor:not-allowed}

/* the catalogue pane: search + toggle-chip filters (OR within a group, AND across groups -- dkFilterOk in
   arena.js), 84x117 tiles. */
.dk-cat{position:sticky;top:12px;align-self:start;max-height:calc(100vh - 140px);overflow:auto;scrollbar-width:none;display:flex;flex-direction:column;gap:10px}
.dk-cat::-webkit-scrollbar{display:none}   /* Lukas 2026-10-16: "hide this scroll bar" (the wheel and touch still scroll) */
#dk-filters{display:flex;flex-wrap:wrap;gap:6px}
.fgroup{display:flex;flex-wrap:wrap;gap:4px}
.fchip{height:24px;padding:0 9px;font-size:11px;border-radius:12px;background:var(--panel2);border:1px solid var(--line2);color:var(--ink2)}
.fchip.on{background:var(--gold);border-color:var(--gold);color:var(--on-gold)}
.fchip.dim{opacity:.35}
.fchip.pipchip{padding:0;width:24px;display:flex;align-items:center;justify-content:center;background:transparent;border:1px solid transparent}
.fchip.pipchip .pip{width:18px;height:18px;font-size:10px}
.fchip.pipchip.on{border-color:var(--gold)}
.cat{position:relative;width:84px;height:117px;flex:none;border-radius:5px;overflow:hidden;background:var(--panel2);border:1px solid var(--line2);cursor:pointer}
.cat img{width:100%;height:100%;object-fit:cover;display:block}
.cat .fallback{display:none;position:relative;width:100%;height:100%;box-sizing:border-box;padding:5px;font-family:var(--display);font-size:10px;color:var(--ink2)}
.cat .fallback i{display:block;width:100%;height:4px;border-radius:4px;margin-bottom:4px}
.cat.noimg img{display:none}.cat.noimg .fallback{display:block}
.cat .badge{position:absolute;right:2px;bottom:2px;font-family:var(--display);font-weight:700;font-size:11px;color:var(--on-gold);background:var(--gold);border-radius:8px;padding:0 5px}
.cat.capped{opacity:.55}

/* the deck head + two-column builder */
.dk-head{position:sticky;top:0;z-index:4;display:flex;align-items:center;gap:14px;flex-wrap:wrap;background:var(--felt);padding:10px 0;border-bottom:1px solid var(--line)}
/* round 2 fix 4: NOT ".back" -- that class is the board's card-back (emblem); a shared class name here
   made the header show a little card back instead of "← Decks". */
.dk-back{color:var(--ink2);text-decoration:none;font-size:13px;white-space:nowrap}
.dk-back:hover{color:var(--gold)}
#dk-name{height:38px;font-family:var(--display);font-size:18px;font-weight:600;background:transparent;border:1px solid transparent;border-radius:3px;padding:0 8px;flex:1;min-width:120px;color:var(--ink)}
#dk-name:hover,#dk-name:focus{border-color:var(--line2);background:var(--panel)}
#dk-name[readonly]{cursor:default}
#dk-ring{flex:none;line-height:0}
.dk-headbtns{display:flex;gap:8px;flex:none}
#dk-check{flex-basis:100%;font-size:12px;color:var(--ink2)}
@media (min-width:900px){#dk-check{flex-basis:auto;margin-left:8px;order:0}}
.dk-dcheck,#dk-piles{--dc-amber:#f2c14e;--dc-g:var(--green);--dc-a:var(--dc-amber);--dc-r:var(--red)}
.dk-dcheck{margin:10px 0;padding:12px 14px;background:var(--panel);border:1px solid var(--line2);border-radius:6px;color:var(--ink2)}
:root[data-theme="pokeball"] .dk-dcheck,:root[data-theme="pokeball"] #dk-piles{--dc-amber:#d9930d}
.dk-dcheck[hidden]{display:none}
.dc-top{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;margin-bottom:10px}
.dc-top h3{margin:0;font-family:var(--display);font-size:22px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--ink)}
.dc-pill{font-family:var(--display);font-size:16px;font-weight:600;padding:2px 12px;border-radius:999px;color:#10130f}
.dc-pill.lvl-ok{background:var(--dc-g)}.dc-pill.lvl-low{background:var(--dc-a)}.dc-pill.lvl-alarm{background:var(--dc-r);color:#fff}
.dc-goal{font-size:14px;color:var(--ink2)}
.dc-toggle{margin-left:auto;font-family:var(--body);font-size:14px;padding:5px 12px;border-radius:4px;background:transparent;border:1px solid var(--line2);color:var(--ink);cursor:pointer}
.dc-toggle[aria-pressed="true"]{border-color:var(--dc-a);box-shadow:inset 0 0 0 1px var(--dc-a)}
.dc-fold{flex:none;width:32px;height:32px;padding:0;border-radius:4px;background:transparent;border:1px solid var(--line2);color:var(--ink);font-size:16px;line-height:1;cursor:pointer}
.dc-fold span[aria-hidden]{display:inline-block;transition:transform .2s}
.dc-fold[aria-expanded="false"] span[aria-hidden]{transform:rotate(-90deg)}
.dc-fold:focus-visible{outline:2px solid var(--hi,var(--ink));outline-offset:2px}
.dk-dcheck.dc-shut .dc-top{margin-bottom:0}
#dc-body[hidden]{display:none}
@media (prefers-reduced-motion:reduce){.dc-fold span[aria-hidden]{transition:none}}
.dc-bar{display:flex;gap:2px}
.dc-slot{position:relative;flex:1;min-width:0;height:14px;border-radius:3px;background:var(--dc-g);opacity:.7}
.dc-slot.r-both,.dc-legend .r-both{background:var(--dc-a)}.dc-slot.r-power,.dc-legend .r-power{background:var(--dc-r)}
.dc-slot.op::after{content:"";position:absolute;left:50%;top:2px;width:4px;height:4px;margin-left:-2px;border-radius:50%;background:#fff;opacity:.9}
.dc-legend{display:flex;flex-wrap:wrap;gap:4px 16px;margin:6px 0 12px;font-size:13px;color:var(--ink2)}
.dc-legend i{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:6px;background:var(--dc-g);opacity:.7;vertical-align:-1px}
.dc-cells{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}
.dc-cell{display:flex;gap:10px;align-items:flex-start;text-align:left;padding:10px 12px;background:var(--panel2);border:1px solid var(--line2);border-left:3px solid var(--lc);border-radius:4px;color:var(--ink);font-family:var(--body);cursor:pointer}
.dc-cell.lvl-ok{--lc:var(--dc-g)}.dc-cell.lvl-low{--lc:var(--dc-a)}.dc-cell.lvl-alarm{--lc:var(--dc-r)}
.dc-cell[aria-pressed="true"]{box-shadow:0 0 0 1px var(--lc)}
.dc-lamp{flex:none;width:18px;height:18px;margin-top:1px;border-radius:50%;background:var(--lc);color:#10130f;font-size:11px;font-weight:700;line-height:18px;text-align:center}
.dc-cell.lvl-alarm .dc-lamp{color:#fff}
.dc-txt{display:flex;flex-direction:column;gap:3px;min-width:0}
.dc-head{font-size:17px;font-weight:600;line-height:1.2;overflow-wrap:anywhere}
.dc-hint{font-size:13px;line-height:1.35;color:var(--ink2);overflow-wrap:anywhere}
.dc-cell:focus-visible,.dc-toggle:focus-visible{outline:2px solid var(--hi,var(--ink));outline-offset:2px}
.dc-field{margin-top:14px;padding-top:12px;border-top:1px solid var(--line2);display:flex;flex-direction:column;gap:8px}
.dc-ftop{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 12px}
.dc-ftop h4{margin:0;font-family:var(--display);font-size:17px;font-weight:600;letter-spacing:.03em;color:var(--ink)}
.dc-fsub{margin:0;font-size:13px;color:var(--ink2)}
.dc-fgroup{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;padding:4px 6px;margin:0 -6px;border-radius:4px}
.dc-fgroup[data-key]{cursor:pointer}
.dc-fgroup[aria-pressed="true"]{box-shadow:inset 0 0 0 1px var(--line2);background:var(--panel2)}
.dc-fhead{flex:0 0 190px;font-size:14px;font-weight:600;color:var(--ink)}
.dc-chips{display:flex;flex-wrap:wrap;gap:6px;flex:1;min-width:0}
.dc-chip{font-family:var(--body);font-size:13px;line-height:1.2;padding:4px 9px;border-radius:999px;background:var(--panel2);border:1px solid var(--line2);color:var(--ink)}
.dc-chip b{font-weight:600;color:var(--ink2)}
button.dc-chip{cursor:pointer}button.dc-chip:hover{border-color:var(--ink2)}
.dc-chip:focus-visible,.dc-fgroup:focus-visible{outline:2px solid var(--hi,var(--ink));outline-offset:2px}
@media (max-width:700px){.dc-cells{grid-template-columns:1fr 1fr;gap:8px}.dc-slot{height:10px}.dc-head{font-size:16px}.dc-fhead{flex-basis:100%}}
#dk-piles .pile{transition:opacity .2s}
#dk-piles .pile.dc-dim{opacity:.3}
#dk-piles .pile[data-role]::after{content:"";position:absolute;left:0;top:0;width:calc(100% - 0px);height:128px;box-sizing:border-box;border-radius:6px;pointer-events:none;z-index:1;box-shadow:inset 0 0 0 3px var(--rc)}
#dk-piles .pile[data-role]::before{content:"";position:absolute;left:0;top:0;z-index:2;border-style:solid;border-width:16px 16px 0 0;border-color:var(--rc) transparent transparent transparent;pointer-events:none}
#dk-piles .pile[data-role="consistency"]{--rc:var(--dc-g)}#dk-piles .pile[data-role="both"]{--rc:var(--dc-a)}#dk-piles .pile[data-role="power"]{--rc:var(--dc-r)}
#dk-piles .pile.dc-hit .pile-stack{filter:drop-shadow(0 0 6px var(--ink2))}
@media (prefers-reduced-motion:reduce){#dk-piles .pile{transition:none}}

.dk-tabs{display:none;gap:8px;margin-top:12px}
.dk-tabs button{height:32px;padding:0 14px;border-radius:16px;background:var(--panel);border:1px solid var(--line2);color:var(--ink2);font-family:var(--display);font-size:12px;text-transform:uppercase;letter-spacing:.06em}
.dk-tabs button.on{background:var(--gold);border-color:var(--gold);color:var(--on-gold)}

.builder{display:grid;grid-template-columns:minmax(0,1fr) 420px;gap:18px;align-items:start;margin-top:14px}
@media (max-width:640px){
  .dk-tabs{display:flex}
  .builder{grid-template-columns:1fr}
  .dk-deck.tabhidden,.dk-cat.tabhidden{display:none}
  .dk-cat{position:static;max-height:none}
}

/* round 2 fix 3: on Lukas's 2000px screen the shell's own ~1040px column (.shell .body's max-width) left 470px
   dead on each side -- both new pages add body.wide (JS, never removed elsewhere: a page load here is always a
   full reload) so the gallery's box grid and the builder's two columns get real room. Higher specificity than
   ".shell .body" (an extra class + element) so it wins without touching every other page's width. */
body.wide .shell .body{max-width:1640px}

.dk-importpane{width:min(560px,92vw);background:var(--panel);border-radius:8px;padding:20px;display:flex;flex-direction:column;gap:10px;box-sizing:border-box}
.dk-importpane textarea{width:100%;box-sizing:border-box;height:180px;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;padding:8px}

@media (prefers-reduced-motion: reduce){
  .dbox,.box3d,.lid,.cards>*,.pile-ctl{transition:none}
  .pile .badge.bump{animation:none}
}
html.nofx .dbox,html.nofx .box3d,html.nofx .lid,html.nofx .cards>*,html.nofx .pile-ctl{transition:none}
html.nofx .pile .badge.bump{animation:none}

/* Questions page (Lukas 2026-10-03): topic sections, one card per question */
.qtopic{border-top:1px solid var(--line2);padding:8px 0}.qtopic:first-child{border-top:0}
.qtopic summary{cursor:pointer;display:flex;align-items:baseline;justify-content:space-between;gap:12px;font-family:var(--display);font-size:17px;padding:4px 0}
.qcard{margin:10px 0 14px;padding:12px 14px;background:var(--panel2);border:1px solid var(--line2);border-radius:3px}
.qcard.answered{border-left:3px solid var(--gold)}
.qpanel:not(.showans) .qcard.answered:not(.fresh){display:none}
.qtitle{font-family:var(--display);font-size:16px;font-weight:600;margin-bottom:6px;overflow-wrap:anywhere}
.qbody,.qsug{white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px;line-height:1.45}
.qbody code,.qsug code{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px}
.qsug{margin-top:8px;padding:6px 10px;border-left:3px solid var(--gold2);color:var(--ink2)}
.panel .qcard textarea{height:auto;min-height:70px;margin-top:10px;padding:8px 10px;font-family:inherit;font-size:13px}
.qfoot{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:8px}
.qmark{font-size:12px;color:var(--gold)}

/* ---- Lukas 2026-10-07: drag and drop from the hand (Profile switch) + cards that feel like cards ---------------
   arena.js "drag" block. The ghost is a clone of the hand card on <body>; its transform is built from the vars the
   rAF loop writes (--gx/--gy position, --tx/--ty lean, --gs scale = 1.08 x the mat zoom, --mx/--my sheen). */
.dragghost{position:fixed;left:0;top:0;margin:0;z-index:9999;pointer-events:none;transform-origin:50% 50%;will-change:transform;
  --gx:0px;--gy:0px;--tx:0deg;--ty:0deg;--gs:1.08;
  transform:translate3d(var(--gx),var(--gy),0) scale(var(--gs)) perspective(700px) rotateX(var(--ty)) rotateY(var(--tx));
  transition:none;
  box-shadow:0 1px 0 rgba(236,228,206,.5),0 3px 0 rgba(0,0,0,.5),0 14px 18px -4px rgba(0,0,0,.5),0 34px 46px -10px rgba(0,0,0,.65)!important;
  border-color:transparent!important;opacity:1}
.dragghost.gliding{transition:transform .15s ease-out}
.dragghost::after{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:radial-gradient(circle at var(--mx,50%) var(--my,50%),rgba(255,255,255,.38),transparent 60%)}
html.drop-armed .dragghost{box-shadow:0 1px 0 rgba(236,228,206,.5),0 3px 0 rgba(0,0,0,.5),0 0 0 2px rgba(232,180,60,.9),0 14px 18px -4px rgba(0,0,0,.5),0 34px 46px -10px rgba(0,0,0,.65)!important}
html.nofx .dragghost::after{display:none}
@media (prefers-reduced-motion:reduce){.dragghost::after{display:none}.dragghost.gliding{transition:none}}
.drag-src{visibility:hidden}   /* the card keeps its place in the fan */
html.dragging,html.dragging *{cursor:grabbing!important;user-select:none}
html.dragging .hand .card{pointer-events:none}   /* so elementFromPoint sees what is under the ghost */
/* Lukas 2026-10-07 touch drag: a sideways swipe still scrolls the phone's hand strip natively; an upward pull reaches arena.js as pointer events instead of scrolling the page */
html.dragok .hand:not(.opp) .card.legal{touch-action:pan-x}
.pk.drop-hover{box-shadow:0 0 0 3px #fff,0 0 26px 8px var(--hi-glow)!important;border-color:var(--hi)!important;filter:brightness(1.12)}
/* a card with one targetless action can go anywhere on the table above the hand: a faint rim while it is held,
   clear once the pointer is where a release would play it (drop-armed, set by the same loop) */
.mat{transition:box-shadow .15s}
html.drop-any .mat{box-shadow:inset 0 0 36px 2px rgba(232,180,60,.1)}
html.drop-any.drop-armed .mat{box-shadow:inset 0 0 0 3px rgba(240,190,70,.85),inset 0 0 90px 12px rgba(232,180,60,.30)}
:root[data-theme="pokeball"].drop-any .mat{box-shadow:inset 0 0 36px 2px rgba(214,134,0,.12)}
:root[data-theme="pokeball"].drop-any.drop-armed .mat{box-shadow:inset 0 0 0 3px rgba(214,134,0,.8),inset 0 0 90px 12px rgba(214,134,0,.22)}
.dragghost{animation:ghostlift .14s ease-out}   /* a 140 ms pick-up; the individual scale property, the transform is driven by vars */
@keyframes ghostlift{from{scale:.93}}
html.nofx .dragghost{animation:none}
@media (prefers-reduced-motion:reduce){.dragghost{animation:none}}
/* card stock: a paper-tone edge then a dark edge under the face (the :where() resting rule above never applied --
   .pk/.card out-specify it -- so the same edge sits on the real selectors, over the shadow they already had) */
.pk.pic,.hand .card{box-shadow:0 1px 0 rgba(236,228,206,.38),0 2px 0 rgba(0,0,0,.55),0 3px 0 rgba(0,0,0,.28),0 3px 8px var(--shadow)}
:root[data-theme="pokeball"] .pk.pic,:root[data-theme="pokeball"] .hand .card{box-shadow:0 1px 0 rgba(255,255,255,.95),0 2px 0 rgba(27,29,34,.3),0 3px 0 rgba(27,29,34,.16),0 3px 8px var(--shadow)}
/* picture Pokémon lean toward the mouse (arena.js writes --rx/--ry, at most 6 degrees, and --mx/--my) */
@media (hover:hover) and (prefers-reduced-motion:no-preference){
  html:not(.nofx):not(.dragging) .pk.pic{transition:transform .12s ease-out,rotate .4s ease}
  html:not(.nofx):not(.dragging) .pk.pic:hover{transform:perspective(700px) translateY(-4px) scale(1.07) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg))}
  html:not(.nofx):not(.dragging) .pk.pic:hover:not(.evolved):not(.can-ability)::after{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:1;
    background:radial-gradient(circle at var(--mx,50%) var(--my,50%),rgba(255,255,255,.2),transparent 60%)}
  html:not(.nofx):not(.dragging) .pk.pic:hover .en,html:not(.nofx):not(.dragging) .pk.pic:hover .conds{z-index:3}   /* the sheen never covers the attached Energy or conditions */
}

/* ================================================================================================================
   THE LIVING TABLE AND THE FRAMELESS SCENES (Lukas 2026-10-12, ledger: "let first make our arena more alive and as
   a client ... make card selection pop panels more designed not simple windows with close buttons but more like
   part of arena maybe frameless. Now it's more feel like of office windows."). Read off PTCGL and MTG Arena
   footage (memory project-table-feel.md): a lit field in a dark arena, a rim that shows whose turn it is, a
   table that moves a little when nothing happens; and a choice or a look at cards is a scene over the dimmed
   table: big cards, one bar, no frame.
   ================================================================================================================ */

/* == 1. the table. .amb is ONE element arena.js keeps and puts back under the cards after every render
   (syncAmbient). Its two masks are cut from the plate itself (arena-field.webp = the inside of the cyan line,
   arena-rim.webp = the line, widened) and sized exactly like the plate (center/cover), so they follow it at any
   window shape. Desktop and the dark theme only. */
.amb{position:absolute;inset:0;z-index:-2;pointer-events:none;overflow:hidden;--amb-opp:255,110,90;--amb-me:92,170,255;--fx0:17%;--fw:66%;--fy0:23%;--fy1:76%;--mid:50%;--seam:52px}   /* -2: under the type plates of the two Actives, which sit at -1 in the same stacking context. The variables are set by syncAmbient: each side's Active type colour, the field's own box on the plate and the line between the two Active rows. */
.amb>*{position:absolute;inset:0;display:block}
.amb canvas{width:100%;height:100%}
.amb canvas.amb-edge{inset:auto 0;top:calc(var(--mid) - var(--band,140px));height:calc(var(--band,140px) * 2)}   /* the border's light, the hits and the particles: a band along the border only (ambTick cuts the light to the field's shape itself) */
html.nofx .amb canvas:not(.amb-rim){display:none}
/* each half of the field is its Active's own theme (Lukas 2026-10-12: no hexagon pattern, "too close to PTCGL
   theme"; "Just extend the theme of active pokemon"): the type picture that lies under the Active
   (static/stage/<type>.webp, set by syncAmbient) across the whole half, a little darker toward the far ends so the
   cards stay the brightest thing. The halves overlap by --seam on each side of the line between the Actives; with
   motion on, ambTick decides how they meet (a jagged crack by clip-path, the straight cut, or .amb-soft: a fade
   over the overlap) and draws the fight on the canvas ("make it more naturation colission ... like fight between
   those themes"; "can we make it not like waves"). With motion off the cut is straight and .amb-line is the
   border. */
.amb-field{-webkit-mask:url(/static/arena-field.webp) center/cover no-repeat;mask:url(/static/arena-field.webp) center/cover no-repeat;background:linear-gradient(180deg,rgba(26,34,60,.7),rgba(22,30,56,.7))}
.amb-half{position:absolute;left:var(--fx0);width:var(--fw);background:rgba(var(--amb-opp),.3) center/cover no-repeat;transition:background-image .5s}
.amb-half.opp{top:var(--fy0);height:calc(var(--mid) - var(--fy0) + var(--seam))}
.amb-half.me{top:calc(var(--mid) - var(--seam));height:calc(var(--fy1) - var(--mid) + var(--seam));clip-path:inset(var(--seam) 0 0 0);background-color:rgba(var(--amb-me),.3)}
.amb-half.me.amb-soft{clip-path:none;-webkit-mask:linear-gradient(180deg,transparent,#000 calc(var(--seam) * 2));mask:linear-gradient(180deg,transparent,#000 calc(var(--seam) * 2))}
.amb-half::after{content:"";position:absolute;inset:0;background:radial-gradient(60% 150% at 50% 50%,rgba(6,9,20,.26) 30%,rgba(6,9,20,.64) 100%)}
.amb-line{display:none;position:absolute;left:var(--fx0);width:var(--fw);top:calc(var(--mid) - 1px);height:2px;
  background:linear-gradient(90deg,rgba(255,250,238,0),rgba(255,250,238,.8) 10%,rgba(255,250,238,.8) 90%,rgba(255,250,238,0));
  box-shadow:0 -7px 18px 1px rgba(var(--amb-opp),.55),0 7px 18px 1px rgba(var(--amb-me),.55),0 0 8px rgba(255,250,238,.5)}
html.nofx .amb-line{display:block}
/* the rim: two canvases, each side's colour down to the border, cut to the rim's shape (arena-rim.webp) by arena.js
   ambRim. Not a CSS mask: the browser applied it again on every update of anything on the table (2026-10-13). */
.amb-rim{opacity:.34;will-change:opacity}
/* the breath of the side to act is set by arena.js ambTick, 4 times a second in the same frames as the dust: the rim is
   as large as the screen, so each change of its opacity makes the browser put the whole tilted table together again
   (a smooth CSS breath did that 60 times a second, 2026-10-13; 8 steps a second on their own clock until 2026-10-17) */
html[data-turn=opp] .amb-rim.opp,html[data-turn=me] .amb-rim.me{opacity:1}
@media (prefers-reduced-motion:reduce){.amb canvas:not(.amb-rim){display:none}.amb-line{display:block}}
/* a choice scene, the end of a game and the concede question blur the table: the table's endless loops stand still
   behind them (a table moving under the blur cost the graphics chip about 30 points, 2026-10-17, measured; arena.js
   ambTick stops the border, the dust and the stage clips with the same test) */
html:has(#overlay:not([hidden]) :is(.scene,.fin),dialog#ask[open]) .mat :is(.abglow,.abcomet>i,.fx,.pl-halo,.stadium.can-ability),
html:has(#overlay:not([hidden]) :is(.scene,.fin),dialog#ask[open]) .mat :is(.fx,.stadium.can-ability)::after,
html:has(#overlay:not([hidden]) :is(.scene,.fin),dialog#ask[open]) .mat .fx::before{animation-play-state:paused}
/* a machine that falls behind (arena.js LITE): 1 = no dust and flashes, 2 = the border stands still and the rim does not breathe */
html.lite1 .amb canvas.amb-dust,html.lite2 .amb canvas.amb-edge{display:none}
html.lite2 .amb-line{display:block}
/* the bench slabs: dark glass with a light on the edge that faces the field, in the side's own colour */
@media (min-width:900px){
  :root:not([data-theme="pokeball"]) .mat .row{background:linear-gradient(180deg,rgba(34,46,74,.86),rgba(18,25,42,.9));
    box-shadow:0 0 0 1px rgba(8,11,18,.9),0 10px 0 var(--prop-face),0 26px 34px var(--prop-shadow),inset 0 1px 0 rgba(255,255,255,.10)}
  :root:not([data-theme="pokeball"]) .mat .row.opp{box-shadow:0 0 0 1px rgba(8,11,18,.9),0 10px 0 var(--prop-face),0 26px 34px var(--prop-shadow),inset 0 -2px 0 rgba(255,122,92,.55),0 14px 26px -12px rgba(255,110,90,.45)}
  :root:not([data-theme="pokeball"]) .mat .row:not(.opp){box-shadow:0 0 0 1px rgba(8,11,18,.9),0 10px 0 var(--prop-face),0 26px 34px var(--prop-shadow),inset 0 2px 0 rgba(92,200,255,.6),0 -14px 26px -12px rgba(92,200,255,.4)}
}

/* == 2. a Choice is a scene (#overlay .box.scene, arena.js renderPending). The .box frame of 2026-09-11 is
   gone: the table dims to about a third and softens, the cards stand at --cw (sceneCardW: as large as their
   number allows), one bar across the table carries the sentence and the buttons. */
#overlay:has(.scene){background:radial-gradient(120% 95% at 50% 46%,rgba(7,11,22,.66),rgba(3,5,11,.9));-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}
#overlay .box.scene{background:none;border:0;box-shadow:none;padding:0;width:100%;max-width:100%;max-height:100vh;overflow:visible;align-items:center;justify-content:center}
#overlay .scene .grid{flex:0 1 auto;display:flex;flex-wrap:wrap;justify-content:center;align-content:flex-start;gap:14px;width:100%;box-sizing:border-box;
  padding:16px 5% 20px;max-height:calc(100vh - 170px);overflow:auto;scrollbar-width:thin}
#overlay .scene .grid .card{width:var(--cw);height:calc(var(--cw) * 1.397);border:0;border-radius:calc(var(--cw) * .045);background:none;
  box-shadow:0 10px 22px rgba(0,0,0,.6);transition:transform .14s ease,filter .14s ease,box-shadow .14s ease}
#overlay .scene .grid .card:not(.pic){zoom:calc(var(--cw) / 104px)}   /* pictures off: the text face is drawn at its own size and scaled */
#overlay .scene .grid .card.dim{filter:brightness(.4) saturate(.55);opacity:1;cursor:default;box-shadow:none}
#overlay .scene .grid .card.legal{box-shadow:0 0 0 2px var(--hi),0 0 22px 2px var(--hi-glow),0 12px 24px rgba(0,0,0,.6)}
#overlay .scene .grid .card.picked{transform:translateY(-10px)}
@media (hover:hover){#overlay .scene .grid .card.legal:hover{transform:translateY(-8px) scale(1.03);box-shadow:0 0 0 3px var(--hi),0 0 34px 6px var(--hi-glow),0 18px 30px rgba(0,0,0,.65)}}
#overlay .scene .sbar,#ask .scene .sbar{position:relative;flex:none;width:100%;box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:12px 26px;flex-wrap:wrap;padding:13px 8%;
  background:linear-gradient(90deg,rgba(12,20,40,0),rgba(13,22,44,.94) 16%,rgba(13,22,44,.94) 84%,rgba(12,20,40,0))}
#overlay .scene .sbar,#overlay .scene .sub,#ask .scene .sbar{--ink:#e8e2d6;--ink2:#a9aeb6;--gold:#f2c14e;--gold2:#b98d24;--on-gold:#1a1405;--glow:rgba(242,193,78,.55);color:#e8e2d6}   /* the scene is dark on the light theme too */
#overlay .scene .sbar::before,#overlay .scene .sbar::after,#ask .scene .sbar::before,#ask .scene .sbar::after{content:"";position:absolute;left:0;right:0;height:1px;
  background:linear-gradient(90deg,transparent,var(--gold2) 22%,var(--gold) 50%,var(--gold2) 78%,transparent)}
#overlay .scene .sbar::before,#ask .scene .sbar::before{top:0}#overlay .scene .sbar::after,#ask .scene .sbar::after{bottom:0}
#overlay .scene .prompt,#ask .prompt{font-size:25px;letter-spacing:.02em;text-align:center;text-shadow:0 2px 10px rgba(0,0,0,.6)}
#overlay .scene .ctr{margin:0;padding:0;gap:14px;flex:none}
#overlay .scene .ctr .c{display:none}   /* the line under the bar already says how many; only "nothing to take" is said here */
#overlay .scene.nothing .ctr .c{display:inline;color:var(--ink2);font-size:14px}
#overlay .scene .sbar>.btns{justify-content:center}
#overlay .scene .sbar button,#ask .scene .sbar button{border-radius:999px;padding:8px 20px;font-family:var(--display);font-size:16px;font-weight:600;letter-spacing:.04em;
  background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.22);color:var(--ink)}
#overlay .scene .sbar button:hover:not(:disabled),#ask .scene .sbar button:hover{border-color:var(--gold);color:var(--gold)}
#overlay .scene .sbar button.gold,#overlay .scene .sbar button.act.go,#ask .scene .sbar button.gold{background:var(--gold);border-color:var(--gold);color:var(--on-gold);box-shadow:0 0 22px var(--glow)}
#overlay .scene .sbar button.gold:hover:not(:disabled),#overlay .scene .sbar button.act.go:hover,#ask .scene .sbar button.gold:hover{color:var(--on-gold);filter:brightness(1.08)}
#overlay .scene .sbar button:disabled{opacity:.4;box-shadow:none}
#overlay .scene .sub{flex:none;margin:10px 0 0;text-align:center;color:rgba(232,226,214,.62)}
/* the entrance, once per Choice (.scin): the cards rise into place, the bar opens from its middle */
#overlay .scene.scin .grid{animation:scenecards .26s cubic-bezier(.2,.7,.2,1) both}
#overlay .scene.scin .bar{animation:scenebar .24s ease-out both}
#overlay .scene.scin .sub{animation:vtin .3s ease-out .12s both}
@keyframes scenecards{from{opacity:0;transform:translateY(26px) scale(.94)}to{opacity:1;transform:none}}
@keyframes scenebar{from{opacity:0;clip-path:inset(0 46%)}to{opacity:1;clip-path:inset(0 0)}}
html.nofx #overlay .scene.scin *{animation:none!important}
/* a question (arena.js ask, Lukas 2026-10-13): the scene's bar alone over the dimmed page, in the browser's own modal
   layer so it also stands over a scene, the result and full screen. No window: no frame, no title, no close button. */
dialog#ask{padding:0;border:0;background:none;width:100vw;max-width:100vw;margin:auto 0;overflow:visible}
dialog#ask::backdrop{background:radial-gradient(120% 95% at 50% 46%,rgba(7,11,22,.66),rgba(3,5,11,.9));-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px)}
#ask .prompt{font-family:var(--display);font-weight:600}
#ask .btns{display:flex;gap:14px;flex-wrap:wrap;justify-content:center}
#ask .scene .sbar button{cursor:pointer}
#ask .scene .sbar button:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
dialog#ask[open] .scene .sbar{animation:scenebar .24s ease-out both}
html.nofx dialog#ask[open] .scene .sbar{animation:none}
/* the hand steps down out of the scene */
.hand{transition:transform .22s ease,opacity .22s ease}
body:has(#overlay:not([hidden]) .scene) .mat .hand:not(.opp){transform:translateY(46%);opacity:.25}
/* the on-board prompt ("click a glowing Pokémon") is the same bar, small (the dark theme only) */
:root:not([data-theme="pokeball"]) .wait{background:linear-gradient(90deg,rgba(13,22,44,0),rgba(13,22,44,.92) 14%,rgba(13,22,44,.92) 86%,rgba(13,22,44,0));border:0;padding:9px 46px;color:var(--ink);
  font-family:var(--display);font-size:17px;letter-spacing:.03em;white-space:nowrap;
  border-image:linear-gradient(90deg,transparent,var(--gold2) 22%,var(--gold) 50%,var(--gold2) 78%,transparent) 1;border-top:1px solid;border-bottom:1px solid}
:root:not([data-theme="pokeball"]) .wait button{border-radius:999px;padding:5px 16px;font-family:var(--display);font-weight:600}

/* == 3. looking at a pile or at one card (#cardzoom): no panel, no round close button. The cards float over the
   dimmed table; a click beside them, Escape or the pill under them goes back.
   The dark theme only: on the light desk the panel stays (its text is dark). */
:root:not([data-theme="pokeball"]) #cardzoom::backdrop{background:radial-gradient(120% 95% at 50% 46%,rgba(7,11,22,.7),rgba(3,5,11,.92))}
:root:not([data-theme="pokeball"]) #cardzoom-body{background:none;box-shadow:none;border-radius:0;padding:0 0 64px;align-items:center;max-width:94vw;overflow:visible}
:root:not([data-theme="pokeball"]) #cardzoom-body .k{align-self:center;font-family:var(--display);font-size:17px;letter-spacing:.14em;color:var(--ink);text-shadow:0 2px 10px rgba(0,0,0,.7);margin-bottom:6px}
:root:not([data-theme="pokeball"]) #cardzoom-body .grid{justify-content:center;gap:14px;max-width:min(1280px,92vw);max-height:72vh;overflow:auto;padding:6px;scrollbar-width:thin}
:root:not([data-theme="pokeball"]) #cardzoom-body .grid .card{width:150px;height:210px;border:0;border-radius:7px;box-shadow:0 10px 22px rgba(0,0,0,.6)}
:root:not([data-theme="pokeball"]) #cardzoom-body .grid .card:not(.pic){zoom:1.44}
:root:not([data-theme="pokeball"]) #cardzoom-body .grid:has(.card:nth-child(17)) .card{width:118px;height:165px}
:root:not([data-theme="pokeball"]) #cardzoom .lb-close{position:fixed;top:auto;right:auto;left:50%;bottom:26px;transform:translateX(-50%);width:auto;height:auto;border-radius:999px;padding:8px 22px;
  background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.22);font-size:0}
:root:not([data-theme="pokeball"]) #cardzoom .lb-close::after{content:"Back to the table";font-family:var(--display);font-size:16px;font-weight:600;letter-spacing:.04em;color:var(--ink)}
:root:not([data-theme="pokeball"]) #cardzoom .lb-close:hover{border-color:var(--gold)}:root:not([data-theme="pokeball"]) #cardzoom .lb-close:hover::after{color:var(--gold)}

/* == 4. the menu of a Pokémon on the table (.pop: its attacks, ability, retreat): dark glass that hangs beside
   the card, no gold window frame; a row you can use carries the gold. Desktop only (the phone's .pop.sheet is
   its own bottom sheet). */
@media (min-width:900px){
  :root:not([data-theme="pokeball"]) .pop{background:linear-gradient(180deg,rgba(18,28,52,.9),rgba(10,16,32,.92));border:0;border-radius:14px;padding:10px 10px 8px;width:262px;
    -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);box-shadow:0 0 0 1px rgba(150,200,255,.16),0 18px 44px rgba(0,0,0,.65)}
  :root:not([data-theme="pokeball"]) .pop .k{padding:0 4px;color:rgba(232,226,214,.55)}
  :root:not([data-theme="pokeball"]) .pop .act{border-radius:10px;background:rgba(255,255,255,.05);border-color:rgba(255,255,255,.12)}
  :root:not([data-theme="pokeball"]) .pop .act.go{background:linear-gradient(90deg,rgba(242,193,78,.24),rgba(242,193,78,.07));border-color:rgba(242,193,78,.6)}
  :root:not([data-theme="pokeball"]) .pop .act.go:hover{background:linear-gradient(90deg,rgba(242,193,78,.4),rgba(242,193,78,.14));border-color:var(--gold);box-shadow:0 0 18px var(--glow)}
}

/* == 5. the end of a game is a ceremony (renderResult; Lukas 2026-10-12, step 2): the table floods in the result's
   colour, the word lands, the prize race stands as two rows of six cards (lit = taken), then the buttons. Dark on
   both themes, so it carries its own ink. .finin = the entrance, once per game. */
#overlay:has(.fin){background:radial-gradient(120% 95% at 50% 40%,rgba(30,58,120,.8),rgba(4,6,14,.95));-webkit-backdrop-filter:blur(4px) saturate(1.15);backdrop-filter:blur(4px) saturate(1.15)}
#overlay:has(.fin.loss){background:radial-gradient(120% 95% at 50% 40%,rgba(104,24,22,.78),rgba(9,4,6,.96))}
#overlay:has(.fin.tie){background:radial-gradient(120% 95% at 50% 40%,rgba(52,60,84,.8),rgba(5,6,12,.95))}
#overlay .box.result.fin{position:relative;background:none;border:0;box-shadow:none;overflow:visible;max-width:none;width:min(820px,94%);padding:0;align-items:center;
  --ink:#f4efe2;--ink2:#b9bcc6;--mute:#b9bcc6;--gold:#f2c14e;--gold2:#b98d24;--on-gold:#1a1405;--glow:rgba(242,193,78,.55);--panel:rgba(12,18,34,.72);--line:rgba(255,255,255,.16);color:#f4efe2}
.fin .fin-rays{position:absolute;left:50%;top:64px;width:1100px;height:1100px;margin:-550px 0 0 -550px;border-radius:50%;pointer-events:none;z-index:-1;
  background:repeating-conic-gradient(from 0deg,rgba(255,214,110,.17) 0 6deg,transparent 6deg 18deg);
  -webkit-mask:radial-gradient(circle,#000 6%,transparent 66%);mask:radial-gradient(circle,#000 6%,transparent 66%);animation:finrays 50s linear infinite}
.fin.loss .fin-rays,.fin.tie .fin-rays{display:none}
@keyframes finrays{to{transform:rotate(360deg)}}
.fin h2.fin-word{font-family:var(--display);font-size:clamp(58px,10.5vw,136px);line-height:1;letter-spacing:.06em;text-transform:uppercase;margin:0 0 22px;white-space:nowrap;
  background:linear-gradient(180deg,#fffbe6 8%,#f6cf63 52%,#b47418 100%);-webkit-background-clip:text;background-clip:text;color:transparent;
  filter:drop-shadow(0 5px 0 rgba(0,0,0,.5)) drop-shadow(0 0 34px rgba(242,193,78,.55))}
.fin h2.fin-word.long{font-size:clamp(30px,5.4vw,68px);white-space:normal}
.fin.loss h2.fin-word{background-image:linear-gradient(180deg,#f6dedb 8%,#cd5f52 56%,#6a1d18 100%);filter:drop-shadow(0 5px 0 rgba(0,0,0,.55)) drop-shadow(0 0 28px rgba(205,70,58,.4))}
.fin.tie h2.fin-word{background-image:linear-gradient(180deg,#fff 8%,#c3cbdb 56%,#77819a 100%);filter:drop-shadow(0 5px 0 rgba(0,0,0,.5))}
.fin-score{display:flex;align-items:center;justify-content:center;gap:clamp(12px,3vw,34px);margin:0 0 18px;font-family:var(--display)}
.fin-side{display:flex;flex-direction:column;align-items:center;gap:9px;min-width:0}
.fin-side b{font-size:16px;letter-spacing:.1em;text-transform:uppercase;max-width:30vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.fin-pips{display:flex;gap:clamp(3px,.8vw,7px)}
.fin-pips i{width:clamp(15px,3.6vw,24px);aspect-ratio:5/7;border-radius:4px;border:1.5px solid rgba(255,255,255,.28);background:rgba(255,255,255,.05)}
.fin-pips i.on{background:linear-gradient(160deg,#fff0b8,#f2c14e 55%,#c5861a);border-color:#fff0b8;box-shadow:0 0 12px rgba(242,193,78,.7)}
.fin-turn{font-size:13px;letter-spacing:.16em;text-transform:uppercase;color:rgba(244,239,226,.66);white-space:nowrap}
.fin .sub{color:rgba(244,239,226,.82)}
.fin .btns{gap:12px;margin-top:20px;flex-wrap:wrap}
.fin .btns>button{border-radius:999px;padding:11px 28px;font-size:15px;letter-spacing:.04em;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.3);color:#f4efe2;cursor:pointer}
.fin .btns>button:hover{background:rgba(255,255,255,.16);border-color:#fff}
.fin .btns>button.gold{background:linear-gradient(180deg,#ffdf86,#f2c14e);color:#2a1c05;border-color:#fff0b8;box-shadow:0 0 22px rgba(242,193,78,.45);font-weight:700}
.fin .btns>button.gold:hover{box-shadow:0 0 34px rgba(242,193,78,.75)}
#overlay:has(.fin.finin){animation:finflood .7s ease-out both}
.fin.finin h2.fin-word{animation:finword .85s cubic-bezier(.2,1.35,.35,1) .15s both}
.fin.finin .fin-score{animation:finup .5s ease-out .85s both}
.fin.finin .fin-pips i.on{animation:finpip .34s cubic-bezier(.2,1.7,.4,1) both;animation-delay:calc(1.05s + var(--i,0) * .09s)}
.fin.finin>.sub,.fin.finin>.btns,.fin.finin>.laddelta,.fin.finin>div:not(.fin-score){animation:finup .5s ease-out 1.5s both}
.fin.finin .fin-rays{animation:finrays 50s linear infinite,finflood 1.4s ease-out .3s both}
@keyframes finflood{from{opacity:0}}
@keyframes finword{0%{transform:scale(2.3);opacity:0;letter-spacing:.42em}55%{opacity:1}100%{transform:none;letter-spacing:.06em}}
@keyframes finup{from{transform:translateY(16px);opacity:0}}
@keyframes finpip{from{transform:scale(.2) rotate(-25deg);opacity:0}}
html.nofx .fin .fin-rays{animation:none}

/* == 6. the versus screen and the deal (showVersus, dealRun; Lukas 2026-10-12, step 3): two halves over the whole
   window, the opponent above in red and the player below in blue, cut on a slant; the halves slide in, VS lands,
   and after VS_MS they part like doors (.out). */
#versus{position:fixed;inset:0;z-index:9000;overflow:hidden;cursor:pointer;font-family:var(--display);color:#fff;-webkit-user-select:none;user-select:none}
#versus .vs-side{position:absolute;left:0;right:0;height:58%;display:flex;align-items:center;transition:transform .5s cubic-bezier(.6,0,.9,.4)}
#versus .vs-side.opp{top:0;justify-content:flex-end;padding-right:9%;background:radial-gradient(90% 130% at 80% 40%,#b5302a,#5a1114 55%,#1c0508);
  clip-path:polygon(0 0,100% 0,100% 72%,0 100%);animation:vsin-r .5s cubic-bezier(.15,.85,.3,1) both}
#versus .vs-side.me{bottom:0;justify-content:flex-start;padding-left:9%;background:radial-gradient(90% 130% at 20% 60%,#2a66c8,#12305f 55%,#060d1e);
  clip-path:polygon(0 28%,100% 0,100% 100%,0 100%);animation:vsin-l .5s cubic-bezier(.15,.85,.3,1) both}
#versus .vs-side::after{content:"";position:absolute;inset:0;background:repeating-linear-gradient(115deg,rgba(255,255,255,.05) 0 2px,transparent 2px 22px);pointer-events:none}
#versus .vs-who{display:flex;align-items:center;gap:22px;position:relative;z-index:1;max-width:76vw}
#versus .vs-side.opp .vs-who{flex-direction:row-reverse;margin-top:-6vh}
#versus .vs-side.me .vs-who{margin-bottom:-6vh}
#versus .vs-who b{font-size:clamp(34px,6vw,84px);line-height:1.05;letter-spacing:.03em;text-transform:uppercase;text-shadow:0 5px 0 rgba(0,0,0,.4),0 0 40px rgba(0,0,0,.5);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#versus .vs-badge{flex:none;width:clamp(64px,9vw,128px);aspect-ratio:1;border-radius:50%;display:grid;place-items:center;font-style:normal;font-size:clamp(34px,5vw,70px);
  background:rgba(255,255,255,.14);border:3px solid rgba(255,255,255,.75);box-shadow:0 0 0 8px rgba(255,255,255,.1),0 14px 30px rgba(0,0,0,.45)}
#versus .vs-mark{position:absolute;left:50%;top:50%;font-style:normal;font-size:clamp(70px,13vw,190px);line-height:1;letter-spacing:.02em;transform:translate(-50%,-50%);z-index:2;
  background:linear-gradient(180deg,#fffbe6 8%,#f6cf63 52%,#b47418 100%);-webkit-background-clip:text;background-clip:text;color:transparent;
  filter:drop-shadow(0 6px 0 rgba(0,0,0,.55)) drop-shadow(0 0 44px rgba(242,193,78,.7));animation:vsmark .55s cubic-bezier(.2,1.5,.35,1) .32s both;transition:opacity .25s,transform .35s}
#versus .vs-note{position:absolute;left:50%;top:calc(50% + clamp(54px,8.5vw,120px));transform:translateX(-50%);z-index:2;font-size:15px;letter-spacing:.18em;text-transform:uppercase;
  padding:6px 16px;border-radius:999px;background:rgba(0,0,0,.5);animation:finup .4s ease-out .8s both;transition:opacity .2s}
#versus.out{pointer-events:none}
#versus.out .vs-side.opp{transform:translateY(-104%)}
#versus.out .vs-side.me{transform:translateY(104%)}
#versus.out .vs-mark{opacity:0;transform:translate(-50%,-50%) scale(1.8)}
#versus.out .vs-note{opacity:0}
@keyframes vsin-r{from{transform:translateX(100%)}}
@keyframes vsin-l{from{transform:translateX(-100%)}}
@keyframes vsmark{0%{transform:translate(-50%,-50%) scale(3.4);opacity:0}60%{opacity:1}100%{transform:translate(-50%,-50%) scale(1)}}
/* the deal: a card flies from its deck (--dx, --dy, set by dealRun) to its place in the hand. Individual transform
   properties, so the hand fan's own transform on the card is left alone. */
.mat .hand .card.dealt{animation:dealt .44s cubic-bezier(.2,.8,.3,1) both}
@keyframes dealt{0%{translate:var(--dx,0) var(--dy,0);rotate:-26deg;scale:.45;opacity:0}25%{opacity:1}100%{translate:0 0;rotate:0deg;scale:1;opacity:1}}

/* == 7. the lobby is a home screen (renderLobby homeHtml; Lukas 2026-10-12, step 6). Desktop only: under 900 px
   none of this applies and the four steps show as before. .body.athome = the home; .body.setup = the steps, with
   a way back. The arena itself is the backdrop of the page. */
.home,.home-back{display:none}
@media (min-width:900px){
  .shell:has(.body.athome){background:linear-gradient(180deg,rgba(7,10,18,.62),rgba(7,10,18,.86) 70%,rgba(7,10,18,.95)),url(/static/arena-floor.webp) center 34%/cover fixed,var(--arena-bg)}
  :root[data-theme="pokeball"] .shell:has(.body.athome){background:linear-gradient(180deg,rgba(244,246,250,.72),rgba(244,246,250,.9) 70%),url(/static/arena-floor-light.webp) center 34%/cover fixed,var(--arena-bg)}
  .shell .body.athome{max-width:1400px;padding-top:26px;gap:22px}
  .shell .body.athome>.head,.shell .body.athome>.steps-nav,.shell .body.athome>.step{display:none}
  .shell .body.setup>.home{display:none}
  .shell .body.setup .home-back{display:inline-flex;margin-left:16px}
  .shell .body.athome>.home{display:grid;grid-template-columns:264px minmax(0,1fr);grid-template-rows:minmax(0,1fr) auto;gap:20px 26px;height:calc(100vh - 200px);min-height:500px}   /* the whole home fits one screen: PLAY is never below the fold */
  .home-modes{grid-row:1/3;display:flex;flex-direction:column;gap:10px;align-self:start;position:relative;z-index:2}   /* above the stage: a mode's panel opens over it */
  .hmode{display:flex;align-items:center;gap:14px;padding:13px 16px;border-radius:14px;border:1px solid var(--line2);background:color-mix(in srgb,var(--panel) 78%,transparent);
    -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);color:var(--ink);font:inherit;text-align:left;cursor:pointer;transition:transform .15s,border-color .15s,box-shadow .15s,background .15s}
  .hmode b{font-family:var(--display);font-size:19px;font-weight:700;letter-spacing:.03em}
  .hmode .hdisc{flex:none;width:42px;height:42px;border-radius:50%;display:grid;place-items:center;border:1.5px solid currentColor;color:var(--ink2)}
  .hmode .hdisc svg{stroke:currentColor}
  button.hmode:hover{transform:translateX(4px);border-color:var(--gold)}
  .hmode.on{border-color:var(--gold);background:linear-gradient(90deg,color-mix(in srgb,var(--gold) 30%,transparent),color-mix(in srgb,var(--panel) 80%,transparent) 75%);box-shadow:0 0 24px var(--glow)}
  .hmode.on b,.hmode.on .hdisc{color:var(--gold)}
  .hmode.locked{cursor:default;border-color:var(--line)}
  .hmode.locked>:not(.htip){opacity:.45}   /* not the whole button: its panel stays readable */
  .hmode{position:relative}
  .htip{position:absolute;left:calc(100% + 14px);top:50%;width:280px;padding:12px 15px;border-radius:12px;border:1px solid var(--gold);background:var(--panel);color:var(--ink);
    font-size:14px;line-height:1.4;box-shadow:0 14px 34px -10px #000;opacity:0;visibility:hidden;transform:translate(-6px,-50%);transition:opacity .15s,transform .15s,visibility .15s;pointer-events:none}
  .hmode:hover .htip,.hmode:focus-visible .htip{opacity:1;visibility:visible;transform:translate(0,-50%)}
  .home-stage{min-width:0;display:flex}
  .home-stage .matchup{flex:1;margin:0;align-items:stretch;padding:clamp(64px,13vh,126px) 30px clamp(18px,4vh,40px);min-height:0;box-sizing:border-box;border-radius:20px;
    box-shadow:inset 0 0 0 1px var(--line),inset 0 30px 60px -30px #000,0 30px 60px -30px rgba(0,0,0,.8)}
  .home-stage .matchup .mside{justify-content:center}
  .home-stage .matchup .dbox{width:clamp(140px,min(17vw,27vh),250px);cursor:pointer}
  .home-stage .matchup .vs{font-size:64px}
  .home-stage .mystery{width:clamp(130px,min(15vw,24vh),220px);aspect-ratio:5/7;border-radius:14px;border:2px dashed color-mix(in srgb,var(--ink2) 60%,transparent);display:grid;place-items:center;text-align:center;
    font-family:var(--display);font-size:22px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink2);background:rgba(0,0,0,.25);cursor:pointer}
  .home-stage .mside.them .mystery{font-size:clamp(22px,2vw,28px)}
  .home-stage .mside.them .mystery.q{font-size:110px;line-height:1}
  .home-go{display:flex;align-items:center;justify-content:flex-end;gap:20px;min-width:0;padding:2px 78px 12px 6px}   /* the right padding keeps PLAY clear of the round feedback button */
  .home-go .err{color:var(--red)}
  .home-setup{font:inherit;font-family:var(--display);font-size:15px;letter-spacing:.08em;text-transform:uppercase;padding:13px 22px;border-radius:999px;border:1px solid var(--line2);
    background:color-mix(in srgb,var(--panel) 78%,transparent);color:var(--ink);cursor:pointer;white-space:nowrap}
  .home-setup:hover{border-color:var(--gold);color:var(--gold)}
  .home-play{flex:none;font-family:var(--display);font-size:34px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;padding:17px 66px;border-radius:999px;border:2px solid #fff0b8;color:#2a1c05;cursor:pointer;white-space:nowrap;
    background:linear-gradient(180deg,#ffe38f,#f2c14e 55%,#d79a25);box-shadow:0 8px 0 #8a5a10,0 0 44px rgba(242,193,78,.5);transition:transform .12s,box-shadow .12s;position:relative}
  /* the pulse is a second glow over the button's own, and only its opacity moves: an animated box-shadow on the button
     repainted the home screen on every frame (2026-10-13, measured: over half a processor core while the lobby stood open) */
  .home-play::after{content:"";position:absolute;inset:-2px;border-radius:inherit;pointer-events:none;opacity:0;will-change:opacity;
    box-shadow:0 8px 0 #8a5a10,0 0 72px rgba(242,193,78,.85);animation:homeplay 2.6s ease-in-out infinite}
  .home-play:hover::after,.home-play:active::after,.home-play:disabled::after{animation:none}
  .home-play:hover{transform:translateY(-2px);box-shadow:0 10px 0 #8a5a10,0 0 70px rgba(242,193,78,.85)}
  .home-play:active{transform:translateY(6px);box-shadow:0 2px 0 #8a5a10,0 0 30px rgba(242,193,78,.5)}
  .home-play:disabled{filter:saturate(.35) brightness(.8);animation:none;cursor:default;transform:none}
  @keyframes homeplay{50%{opacity:1}}
  html.nofx .home-play::after{animation:none}
}

/* == table-feel2 / 1 player plates (Lukas 2026-10-14, "a game client"). Desktop: a round portrait of the side's Active
   (the art window of its picture, public), the prize count in a disc on the ring, the name beside it; the side to act
   has a lit ring and a halo that breathes (own element, opacity only, on the border rim's own 3.6 s / 14-step clock and phase so it adds no frame of its own; static on Daylight, which has no rim). */
.pl-ring{--pc:92,170,255;position:relative;flex:none;width:66px;height:66px;border-radius:50%;border:3px solid rgb(var(--pc));
  background:#0d1220;box-shadow:0 0 0 2px rgba(0,0,0,.55),0 0 12px rgba(var(--pc),.45),0 6px 12px rgba(0,0,0,.55)}
.pl-ring.opp{--pc:255,110,90}
.pl-art{position:absolute;inset:0;border-radius:50%;overflow:hidden;display:block}
.pl-art b{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font:700 28px var(--display);color:rgb(var(--pc))}
.pl-pic{position:absolute;inset:0;display:block;background:#0d1220 no-repeat;background-size:176% auto;background-position:50% 17%}
.pl-prz{position:absolute;right:-8px;bottom:-6px;min-width:25px;height:25px;border-radius:50%;background:#10151f;color:#fff;border:2px solid rgb(var(--pc));
  font:700 14px/21px var(--display);text-align:center;box-shadow:0 2px 5px rgba(0,0,0,.6)}
.pl-halo{position:absolute;inset:-12px;border-radius:50%;pointer-events:none;opacity:0;
  background:radial-gradient(circle,rgba(var(--pc),0) 52%,rgba(var(--pc),.6) 66%,rgba(var(--pc),0) 100%)}
html[data-turn=me] .pl-ring.me,html[data-turn=opp] .pl-ring.opp{box-shadow:0 0 0 2px rgba(0,0,0,.55),0 0 20px rgba(var(--pc),.85),0 6px 12px rgba(0,0,0,.55)}
html[data-turn=me] .pl-ring.me .pl-halo,html[data-turn=opp] .pl-ring.opp .pl-halo{opacity:.8}
:root:not([data-theme="pokeball"])[data-turn=me] .pl-ring.me .pl-halo,:root:not([data-theme="pokeball"])[data-turn=opp] .pl-ring.opp .pl-halo{animation:plhalo 3.6s steps(14,jump-none) infinite}
html.lite2 .pl-halo{animation:none!important}
@keyframes plhalo{0%,100%{opacity:.35}50%{opacity:1}}
html.nofx .pl-halo{animation:none!important;opacity:.7}
.mat .nameplate{gap:0;padding:0;background:none;border-radius:0;font-size:15px;align-items:center}
.mat .nameplate .pl-txt{display:flex;align-items:center;gap:6px;margin-left:-6px;padding:5px 14px 5px 14px;border-radius:0 20px 20px 0;background:var(--overlay);
  border:1px solid rgba(255,255,255,.08);border-left:0;box-shadow:0 4px 10px rgba(0,0,0,.45)}
.mat .nameplate .pl-clk{font:600 13px var(--display);color:var(--gold);letter-spacing:.04em;padding-left:8px;border-left:1px solid rgba(255,255,255,.18)}
.mat .nameplate.opp{top:10px;left:20px}
.mat .nameplate.me{bottom:150px;left:20px}

/* == table-feel2 / 2 benches (Lukas 2026-10-14: "Active is a focal point, bench is as in all games just bench"). The navy slab is
   gone: the bench is a light, slim zone (a faint band, a thin side-coloured edge) with five quiet markers on the field, each card
   standing on its own. The Active keeps its pedestal; the bench never gets one. Static only: no animation anywhere here. */
.bslot,.bm{display:none}
@media (min-width:900px){
  .row{--bc:92,170,255}
  .row.opp{--bc:255,110,90}
  :root:root .mat .row.row.row.row{min-width:0;padding:12px 26px 12px;gap:16px;border-radius:22px;
    background:linear-gradient(180deg,rgba(12,18,32,.46),rgba(12,18,32,.26));
    box-shadow:0 0 0 1px rgba(var(--bc),.14),inset 0 -2px 0 rgba(var(--bc),.55),0 8px 22px -8px rgba(0,0,0,.5)}
  .mat .row .slot,.mat .row .bslot{position:relative;display:block;flex:none;width:100px;height:64px;align-self:flex-end;border:0;background:none;box-shadow:none;color:transparent;pointer-events:none}
  .mat .row .slot{pointer-events:auto}
  .mat .row .slot::before,.mat .row .bslot::before,.mat .row .pk>.bm{content:"";position:absolute;left:-8px;right:-8px;bottom:-12px;height:34px;border-radius:50%;pointer-events:none;
    background:radial-gradient(ellipse at 50% 50%,rgba(var(--bc),.55) 0,rgba(var(--bc),.22) 52%,rgba(var(--bc),0) 72%);
    box-shadow:0 0 0 1px rgba(var(--bc),.45)}
  .mat .row .bslot::before{opacity:.8}
  .mat .row .pk>.bm{z-index:-1;display:block}
  .mat .row .slot.legal::before,html.drop-any .mat .row:not(.opp) .slot::before,html.drop-any .mat .row:not(.opp) .bslot::before{opacity:1;
    background:radial-gradient(ellipse at 50% 50%,rgba(var(--bc),.7) 0,rgba(var(--bc),.28) 55%,rgba(var(--bc),0) 74%);box-shadow:0 0 0 2px rgba(var(--bc),.8),0 0 18px rgba(var(--bc),.6)}
  html.drop-any .mat .row:not(.opp) .bslot{display:block}
  .row:not(.opp):has(.slot)::before{display:none}
  .row:not(.opp):has(.pk:nth-child(5)):has(.slot)::before{display:block;width:100px}
  .row:not(.opp):has(.pk:nth-child(8)) .slot,.row:not(.opp):has(.pk:nth-child(8))::before{display:none}
  .mat .row .bslot{display:block}
}

/* == table-feel2 / 3 depth (MTGA: "height is shown by scale and by a shadow that moves away"). Every Pokemon on the table has
   its own shadow element (.shd, behind the card, a STATIC box-shadow); resting it sits close and dark, a hovered, dragged-over
   or attacking card scales up and its shadow element moves away and fades (transform + opacity transitions only, no animated
   box-shadow or filter). Hand cards keep their own lift (.hand .card:hover) and get a deeper static shadow. */
.pk>.shd{position:absolute;inset:0;z-index:-1;border-radius:5px;pointer-events:none;opacity:.9;
  box-shadow:0 7px 12px -3px rgba(0,0,0,.62),0 1px 3px rgba(0,0,0,.55);transition:transform .14s ease-out,opacity .14s ease-out}
.hand:not(.opp) .card{box-shadow:0 8px 14px -4px rgba(0,0,0,.6),0 1px 3px rgba(0,0,0,.5)}
@media (min-width:900px) and (hover:hover){
  .mat .pk{transition:transform .14s ease-out}
  .mat .pk:hover:not(.lunge-up):not(.lunge-down){z-index:20}
  .mat .pk:hover>.shd{transform:translate(6px,16px) scale(1.05);opacity:.5}
}
.pk.lunge-up>.shd,.pk.lunge-down>.shd{transform:translate(8px,20px) scale(1.08);opacity:.5}
html.nofx .mat .pk,html.nofx .pk>.shd{transition:none}

/* == table-feel2 / 4 hover preview: one element on <body>, never takes the pointer; the face is the card's own large picture. */
#hvp{position:fixed;z-index:9000;pointer-events:none;border-radius:4.2%/3%;box-shadow:0 18px 44px rgba(0,0,0,.65),0 0 0 1px rgba(255,255,255,.14)}
#hvp[hidden]{display:none}
#hvp img{display:block;width:100%;height:100%;border-radius:inherit;object-fit:cover}
#hvp .tf{width:100%;height:100%;overflow:hidden;border-radius:inherit;background:var(--panel);font-size:13px}
#hvp img[hidden],#hvp .tf[hidden]{display:none}

/* == table-feel2 / 5 the played card: one fixed spot at the table's right edge, above the choice scene's dim (z 10), so the card
   stays in view through the cost panel and the search scene. A static lift (item 3's shadow), no animation of its own. */
#played{position:fixed;z-index:12;pointer-events:none;width:var(--pw,140px);height:var(--ph,196px)}
#played[hidden]{display:none}
#played .card{position:absolute;inset:0;width:100%;height:100%;border-radius:8px;transform:translateY(-6px) scale(1.04);
  box-shadow:0 0 0 2px rgba(var(--pc,92,170,255),.85),0 0 22px rgba(var(--pc,92,170,255),.45),0 22px 28px -6px rgba(0,0,0,.7)}
#played.me{--pc:92,170,255}
#played.opp{--pc:255,110,90}
#played .card img{width:100%;height:100%;object-fit:cover}

/* == table-feel2 / 6 the Daylight table: real contrast. The field and the stands are toned to a steel blue (the floor picture
   multiplied by a colour: one static paint, nothing animated), cards cast bluish shadows that read on a light field, the bench zone,
   its markers and the plates take the side colours at full strength, the HP pill has an edge. The dark theme is untouched. */
:root[data-theme="pokeball"] .mat{background:url(/static/arena-floor-light.webp) center/cover no-repeat,#9fb4d0;background-blend-mode:multiply}
:root[data-theme="pokeball"] .pk>.shd{box-shadow:0 9px 14px -3px rgba(18,32,66,.62),0 1px 3px rgba(18,32,66,.55)}
:root[data-theme="pokeball"] .hand:not(.opp) .card{box-shadow:0 8px 14px -4px rgba(18,32,66,.55),0 1px 3px rgba(18,32,66,.5)}
:root[data-theme="pokeball"] .pk.pic .hppill{box-shadow:0 0 0 1px rgba(18,32,66,.35),0 3px 7px rgba(18,32,66,.45)}
:root[data-theme="pokeball"] .nameplate.opp{--pc:230,60,52}
:root[data-theme="pokeball"] .nameplate.me{--pc:36,118,214}
:root[data-theme="pokeball"] .pl-ring.opp{--pc:230,60,52}
:root[data-theme="pokeball"] .pl-ring.me{--pc:36,118,214}
:root[data-theme="pokeball"] .pl-ring{background:#fff}
:root[data-theme="pokeball"] .mat .nameplate .pl-txt{background:#fff;color:#1b1d22;border:1px solid rgba(var(--pc),.7);border-left:0;box-shadow:0 4px 10px rgba(18,32,66,.35)}
:root[data-theme="pokeball"] .mat .nameplate .pl-clk{color:#b3541a;border-left-color:rgba(18,32,66,.25)}
@media (min-width:900px){
  :root[data-theme="pokeball"] .row{--bc:36,118,214}
  :root[data-theme="pokeball"] .row.opp{--bc:230,60,52}
  :root[data-theme="pokeball"]:root .mat .row.row.row.row{background:linear-gradient(180deg,rgba(255,255,255,.62),rgba(255,255,255,.34));
    box-shadow:0 0 0 1px rgba(var(--bc),.5),inset 0 -2px 0 rgba(var(--bc),.8),0 8px 18px -8px rgba(18,32,66,.5)}
  :root[data-theme="pokeball"] .mat .row .slot::before,:root[data-theme="pokeball"] .mat .row .bslot::before,:root[data-theme="pokeball"] .mat .row .pk>.bm{
    background:radial-gradient(ellipse at 50% 50%,rgba(var(--bc),.5) 0,rgba(var(--bc),.2) 55%,rgba(var(--bc),0) 74%);box-shadow:0 0 0 1px rgba(var(--bc),.7)}
}

/* friends and groups (Lukas 2026-10-15): the Social page, a group's page, the chat, the challenge strip, the panels. Built from the
   client pages' own parts: .cp-head/.cp-tally (header and tiles), .cp-h (section heading), .cp-form (the Profile plate), .cp-opt/.cp-sw
   (toggle row), .cp-empty (empty state), .cp-ring (avatar), the client bar's .menu; the .sc-* rules add only what none of them has. */
.shell .nav a,.botnav a{position:relative}
.sc-badge{display:inline-grid;place-items:center;min-width:18px;height:18px;padding:0 5px;box-sizing:border-box;border-radius:9px;background:var(--red);color:#fff;font:700 11px/1 var(--body);letter-spacing:0}
.nav .sc-badge-slot .sc-badge{margin-left:6px}
.botnav .sc-badge-slot{position:absolute;top:4px;left:calc(50% + 6px)}
.cp-ring.xs{width:28px;height:28px;padding:2px;box-shadow:none}.cp-ring.xs .cp-av{font-size:13px}
.btn.nou,.btn.nou:hover{text-decoration:none}
.btn.quiet.danger,.sc-m .dd button.danger{color:var(--red);border-color:color-mix(in srgb,var(--red) 55%,var(--line2))}
.btn.quiet.danger:hover{border-color:var(--red)}
/* layout */
.sc-cols{display:grid;grid-template-columns:minmax(0,3fr) minmax(0,2fr);gap:28px;align-items:start}
.sc-cols.sc-grp{grid-template-columns:minmax(0,2fr) minmax(0,3fr)}
.sc-stack{display:flex;flex-direction:column;gap:28px;min-width:0}
.sc-list{display:flex;flex-direction:column;gap:10px}
.sc-subh{margin:18px 0 10px;font-family:var(--display);font-size:17px;font-weight:600;color:var(--ink2)}
.sc-subh span{font-family:var(--body);font-size:14px}
.cp-h.sc-hd{display:block}
.cp-h .sc-n{margin-left:.4em;font-family:var(--body);font-size:.6em;font-weight:600;color:var(--ink2)}
#gr-new{width:100%;height:44px;font-size:16px}
.sc-newwrap{margin-top:12px}
.sc-back{align-self:flex-start;margin-bottom:-12px;color:var(--ink2);font-size:14px;text-decoration:none}.sc-back:hover{color:var(--gold)}
.sc-scroll{max-height:458px;overflow-y:auto;padding-right:4px}
.sc-row .sc-quiet{flex:none}
.sc-quiet a{color:var(--gold)}
.sc-quiet{margin:0;font-size:14px;color:var(--ink2)}
section>.cp-form,section>.sc-list,section>.sc-quiet{margin-bottom:0}
section>.cp-form+.sc-subh{margin-top:20px}
/* fields: a sentence-case label above a 44 px field; a field and its button on one row */
.sc-f{display:flex;flex-direction:column;gap:8px;flex:1;min-width:0;margin:0}
.sc-f>span{font-family:var(--body);font-size:14px;letter-spacing:0;text-transform:none;color:var(--ink2)}
.sc-f input,.sc-f textarea{width:100%;box-sizing:border-box;padding:0 14px;font-size:16px;border:1px solid var(--line);border-radius:12px;background:var(--panel2);color:var(--ink)}
.sc-f input{height:44px}.sc-f textarea{height:auto;padding:11px 14px;resize:vertical;line-height:1.4;font-family:inherit}
.sc-f input:focus,.sc-f textarea:focus{outline:none;border-color:var(--gold);box-shadow:0 0 0 3px color-mix(in srgb,var(--gold) 28%,transparent)}
.sc-f input[readonly]{color:var(--ink2);text-overflow:ellipsis}
.sc-fr{display:flex;gap:10px;align-items:flex-end}
.sc-fr .btn,.sc-btns .btn,.sc-form .btn{height:44px;padding:0 24px;font-size:16px;flex:none}
.cp-form{display:flex;flex-direction:column;gap:16px}
.sc-newg,.sc-sf{display:flex;flex-direction:column;gap:16px}
.sc-newg{margin-top:12px}.sc-newg[hidden]{display:none}
.sc-btns{display:flex;gap:10px;margin-top:4px}.sc-btns.end{justify-content:flex-end;margin-top:12px}
.sc-tlink{align-self:flex-start;margin:-6px 0 6px;padding:0;border:0;background:none;color:var(--ink2);font:inherit;font-size:14px;text-decoration:underline;cursor:pointer}
.sc-tlink:hover{color:var(--gold)}
.sc-sep{height:1px;background:var(--line);margin:6px 0}
.sc-opt{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:6px 0}
/* a list row: the Replays plate (avatar, name and a line under it, one action, the rest in a menu) */
.sc-row{display:flex;align-items:center;gap:14px;min-height:68px;padding:10px 14px 10px 12px;box-sizing:border-box;border-radius:14px;border:1px solid var(--cp-edge);background:var(--cp-plate);box-shadow:var(--cp-hi);color:var(--ink);text-decoration:none;min-width:0;transition:border-color .15s ease,transform .15s ease}
.sc-row.link:hover{border-color:var(--gold);transform:translateY(-1px)}
.sc-row.req{border-color:var(--gold);box-shadow:var(--cp-hi),0 0 22px color-mix(in srgb,var(--gold) 16%,transparent)}
.sc-who{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}
.sc-who b{display:flex;align-items:center;font-family:var(--display);font-size:20px;font-weight:700;letter-spacing:.01em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sc-sub{font-size:14px;color:var(--ink2)}
.sc-acts{display:flex;align-items:center;gap:8px;flex:none}
.sc-acts .btn{gap:6px}
.sc-gi{display:grid;place-items:center;width:44px;height:44px;border-radius:50%;border:1.5px solid var(--gold);color:var(--gold);flex:none}
.sc-gi svg{width:22px;height:22px}
.sc-pav{position:relative;display:inline-flex;flex:none}
.sc-pd{position:absolute;right:0;bottom:1px;width:11px;height:11px;border-radius:50%;background:var(--mute);border:2px solid var(--cp-plate)}
.sc-pd.online{background:var(--green)}.sc-pd.playing{background:var(--gold)}
.sc-m{margin:0}
.shell .menu.sc-m>summary{justify-content:center;width:36px;height:36px;border-radius:10px;border:1px solid var(--line2);color:var(--ink2);font-size:18px;line-height:1;background:transparent}
.shell .menu.sc-m>summary:hover,.shell .menu.sc-m[open]>summary{border-color:var(--gold);color:var(--gold)}
.shell .menu.sc-m .dd{top:calc(100% + 6px);min-width:170px}
.shell .menu.sc-m .dd button{display:block;width:100%;text-align:left;padding:9px 14px;border:0;background:none;color:var(--ink2);font:inherit;font-size:14px;cursor:pointer;white-space:nowrap}
.shell .menu.sc-m .dd button:hover{background:var(--panel2);color:var(--gold)}
.shell .menu.sc-m .dd button.danger{color:var(--red);border:0}
.cp-chip{margin-left:10px;font-family:var(--body);font-size:12px;font-weight:600}
/* the group page's header */
.sc-gt{display:flex;align-items:center;gap:20px;min-width:0}
.sc-hr{display:flex;align-items:center;gap:14px;flex-wrap:wrap;justify-content:flex-end}
.sc-hr>.btn{height:50px;padding:0 30px;font-size:18px}
.sc-hr .menu.sc-m>summary{height:50px;width:44px}
/* the chat: one component in the channel and in the drawer */
.sc-chat{display:flex;flex-direction:column;min-height:0}
.sc-chatplate{height:560px;padding:18px;box-sizing:border-box;border-radius:16px;border:1px solid var(--cp-edge);background:var(--cp-plate);box-shadow:var(--cp-hi)}
.sc-log{flex:1;display:flex;flex-direction:column;gap:14px;overflow-y:auto;padding:2px 4px 2px 0;min-height:0}
.sc-mg{display:flex;gap:10px;align-items:flex-start;max-width:88%}
.sc-mg.own{align-self:flex-end;flex-direction:row-reverse}
.sc-mb{display:flex;flex-direction:column;gap:3px;min-width:0}
.sc-mg.own .sc-mb{align-items:flex-end}
.sc-mh{display:flex;gap:8px;align-items:baseline;font-size:13px;color:var(--ink2)}
.sc-mh b{font-family:var(--display);font-size:15px;color:var(--ink)}.sc-mh em{font-style:normal;font-size:12px;color:var(--mute)}
.sc-mt{color:var(--ink);padding:8px 12px;border-radius:12px;background:var(--cp-plate2);overflow-wrap:anywhere;font-size:15px;line-height:1.4}
.sc-mg.own .sc-mt{background:color-mix(in srgb,var(--gold) 18%,var(--cp-plate2))}
.sc-hello{margin:auto;display:flex;flex-direction:column;align-items:center;gap:6px;text-align:center;color:var(--ink2)}
.sc-hello svg{width:54px;height:54px;padding:15px;box-sizing:content-box;border-radius:50%;color:var(--gold);border:1.5px solid var(--gold)}
.sc-hello b{font-family:var(--display);font-size:22px;color:var(--ink)}
.sc-form{display:flex;gap:10px;margin-top:14px}
.sc-form input{flex:1;height:44px;box-sizing:border-box;padding:0 14px;font-size:16px;border:1px solid var(--line);border-radius:12px;background:var(--panel2);color:var(--ink)}
.sc-form input:focus{outline:none;border-color:var(--gold);box-shadow:0 0 0 3px color-mix(in srgb,var(--gold) 28%,transparent)}
.sc-x{width:34px;height:34px;border-radius:10px;border:1px solid var(--line2);background:transparent;color:var(--ink2);font-size:20px;line-height:1;cursor:pointer;flex:none}
.sc-x:hover{color:var(--gold);border-color:var(--gold)}
#sc-drawer{position:fixed;top:71px;right:0;bottom:0;z-index:60;width:min(400px,100vw);display:flex;flex-direction:column;background:var(--panel);border-left:1px solid var(--line2);box-shadow:-14px 0 40px var(--shadow);color:var(--ink)}
#sc-drawer .sc-dh{display:flex;align-items:center;gap:12px;padding:14px 16px;border-bottom:1px solid var(--line)}
#sc-drawer .sc-dh .sc-pd{border-color:var(--panel)}
#sc-drawer .sc-dbody{flex:1;padding:14px 16px 16px}
/* the challenge strip under the client bar */
#chbar{position:relative;z-index:4;display:flex;align-items:center;justify-content:center;gap:8px 18px;flex-wrap:wrap;padding:8px 16px;box-sizing:border-box;
  color:#e8e2d6;background:linear-gradient(90deg,rgba(12,20,40,.9),rgba(13,22,44,.98) 14%,rgba(13,22,44,.98) 86%,rgba(12,20,40,.9));border-bottom:1px solid #b98d24;--gold:#f2c14e;--gold2:#b98d24;--on-gold:#1a1405}
#chbar .prompt{font-family:var(--display);font-size:19px;letter-spacing:.02em}
#chbar .btns{display:flex;gap:10px}
#chbar button{height:30px;padding:0 16px;border-radius:6px;border:1px solid #3d4654;background:transparent;color:#e8e2d6;font:700 14px var(--display);letter-spacing:.04em;cursor:pointer}
#chbar button.gold{background:var(--gold);color:var(--on-gold);border-color:var(--gold)}
/* panels (Invite, Group settings, Share a replay) */
#sc-aud,#sc-drawer{--cp-plate:var(--panel2);--cp-plate2:var(--panel2);--cp-edge:var(--line);--cp-hi:none;--cp-acc:var(--gold)}
#sc-aud{position:fixed;inset:0;z-index:80;display:grid;place-items:center;background:var(--overlay);padding:16px;box-sizing:border-box}
.sc-audbox{width:min(480px,100%);max-height:86vh;overflow-y:auto;display:flex;flex-direction:column;gap:16px;padding:24px;box-sizing:border-box;border-radius:18px;background:var(--panel);border:1px solid var(--line2);color:var(--ink);box-shadow:0 18px 50px var(--shadow2)}
.sc-audbox h2{margin:0;font-family:var(--display);font-size:26px;font-weight:700;color:var(--ink)}
.sc-audbox .sc-subh{margin:4px 0 0}
.sc-audbox .cp-empty{padding:24px 12px;border:0;background:none;box-shadow:none}
.sc-chk{cursor:pointer;min-height:56px}
.sc-chk .cp-sw{margin-left:auto}
:root[data-theme="pokeball"] .sc-mg.own .sc-mt{background:color-mix(in srgb,var(--gold) 12%,#fff)}   /* light theme: the Profile's tokens already reach rows, fields and plates; the own bubble was a gold mix on dark */
:root[data-theme="pokeball"] #sc-drawer,:root[data-theme="pokeball"] #sc-aud .sc-audbox{box-shadow:0 18px 50px rgba(27,29,34,.18)}
/* the invite card */
.sc-stk{display:flex;flex-direction:column;gap:10px;width:min(100%,320px)}.sc-stk .btn{width:100%;height:50px;font-size:17px;box-shadow:none}
.sc-next{margin:0 0 4px;padding:8px 10px;border-radius:8px;border:1px solid var(--gold);color:var(--ink);font-size:14px}
.sc-invite{max-width:560px;margin:8vh auto;padding:0 16px}
.sc-invite .shell>*{position:relative}   /* above the shell's fixed backdrop (.shell::before), as .shell>.body is */
.sc-brand{font-family:var(--display);text-align:center;margin:0 0 18px}
@media (max-width:899.98px){
  .sc-cols,.sc-cols.sc-grp{grid-template-columns:1fr;gap:24px}
  .sc-cols.sc-grp{display:flex;flex-direction:column}.sc-cols.sc-grp>.sc-stack{display:contents}   /* the group page: header, Channel, Members, Live now, Shared replays */
  .sc-cols.sc-grp section{width:100%;min-width:0}   /* flex children shrink to their content; the rows fill the column as in the desktop stacks */
  .sc-s-chan{order:1}.sc-s-mem{order:2}.sc-s-live{order:3}.sc-s-rep{order:4}
  .sc-stack{gap:24px}
  .sc-row{flex-wrap:wrap;gap:10px 12px}
  .sc-who{flex:1 1 calc(100% - 120px)}.sc-who b{white-space:normal}
  .sc-row .sc-acts{flex:1 1 100%;justify-content:flex-start}
  .sc-row.link{flex-wrap:nowrap}.sc-row.link .sc-who{flex:1}
  .sc-chk{flex-wrap:nowrap}
  .cp-head{flex-wrap:wrap}.sc-hr{flex-wrap:wrap;justify-content:flex-start;width:100%}
  .sc-hr>.btn{flex:1 1 0;width:auto;min-width:0}.sc-hr .menu.sc-m{flex:none}
  .sc-gt .cp-ring.xl{width:56px;height:56px}.sc-gt .cp-ring.xl .cp-av{font-size:24px}
  .sc-cols .cp-tally span{min-width:0;flex:1}.sc-hr .cp-tally{flex:1 1 100%}
  .sc-chatplate{height:60vh}
  #chbar{padding:6px 12px;gap:6px 12px}#chbar .prompt{font-size:16px}
  #sc-drawer{top:60px;width:100vw;bottom:calc(56px + env(safe-area-inset-bottom))}
}

/* THE OPENING SHOW (Lukas 2026-10-15): the coin call is a panel in the scenes' look (#ask); the hands stay hidden under the
   coin when the bot already answered go-first, and fly in after it (arena.js openingEnd) */
#ask .scene .sbar button.coinbtn{display:inline-flex;align-items:center;gap:10px;padding:6px 20px 6px 8px}
#ask .coinbtn img{width:44px;height:44px;border-radius:50%;object-fit:cover;box-shadow:0 0 12px rgba(242,193,78,.35)}
#ask .coinleft{flex-basis:100%;text-align:center;margin:0;font-size:12px;color:rgba(232,226,214,.62)}
.cointoast .what.small{font-size:12px;color:var(--ink2);margin-top:2px}
body.opening-show .hand{visibility:hidden}   /* both hands: the deal comes after the show */
.coinstage.openstage{position:fixed;left:0;right:0;top:50%;transform:translateY(-50%);z-index:9500}
.cointoast .res.big{font-size:30px}
.cointoast .open2{font-family:var(--display);font-size:22px;font-weight:600;min-height:28px;margin-top:6px}
