/* ==========================================================================
   THAASBAI — design system
   Brand (§3): Ink Navy / Lagoon Teal / Reef Gold / Ivory / Signal Red
   Type: Sora (headings, numerals) · Inter (body/UI)
   Motion: 150–200ms, no bouncy easing
   ========================================================================== */

:root {
  --topbar-h:   58px;
  --bg:         #030510;   /* page background — near-black navy; panels stay brighter blue */
  --ink:        #0B2545;
  --ink-2:      #0E2D53;   /* raised surface on navy */
  --ink-3:      #133662;   /* higher surface / hover */
  --ink-line:   #1E4374;   /* hairline on navy */
  /* liquid glass material */
  --glass-grad: linear-gradient(148deg, rgba(26, 70, 130, .58) 0%, rgba(10, 27, 51, .62) 46%, rgba(3, 8, 16, .72) 100%);
  --glass-grad-solid: linear-gradient(148deg, #143A6B 0%, #0B1F3C 46%, #050B16 100%);
  --glass-border: rgba(186, 214, 255, .16);
  --glass-edge: inset 0 1px 0 rgba(255, 255, 255, .10);
  --glass-shadow: 0 16px 38px rgba(2, 6, 14, .45);
  --teal:       #0E7C7B;
  --teal-hi:    #14A3A1;   /* active/hover accent */
  --teal-soft:  rgba(20, 163, 161, 0.14);
  --gold:       #B8860B;
  --gold-hi:    #D9A521;
  --gold-soft:  rgba(217, 165, 33, 0.12);
  --ivory:      #F2EFE9;
  --ivory-dim:  #C6C9CF;
  --muted:      #8B9BB4;
  --red:        #C1443C;
  --red-soft:   rgba(193, 68, 60, 0.14);
  --green:      #2E8B57;
  /* newer purple/blue accent — currently used on Home + the shared
     bottom tab bar; the rest of the app's chrome (topnav, buttons)
     stays on teal/gold until redesigned later. */
  --home-grad:   linear-gradient(120deg, #8B5CF6 0%, #4F7CFF 55%, #17B5B3 100%);
  --home-purple: #8B5CF6;
  --home-blue:   #4F7CFF;

  --card-face:  #F7F4EE;
  --card-line:  #D8D2C4;
  --suit-dark:  #263042;
  --suit-red:   #B03A32;

  --font-head: "Sora", "Segoe UI", system-ui, sans-serif;
  --font-body: "Inter", "Segoe UI", system-ui, sans-serif;

  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 16px;

  --t-fast: 150ms cubic-bezier(.4, 0, .2, 1);
  --t-med:  200ms cubic-bezier(.4, 0, .2, 1);

  --shadow-1: 0 1px 2px rgba(2, 10, 25, .5);
  --shadow-2: 0 6px 24px rgba(2, 10, 25, .45);

  --tabbar-h: 62px;
  --tabbar-gap: 14px; /* floating gap between the pill bar and the true screen edge */
  --safe-bottom: env(safe-area-inset-bottom);
}
/* iOS reports a noticeably larger safe-area-inset-bottom in the
   installed standalone PWA than it does for the exact same device in a
   regular Safari tab — trusting it at full value there left a visibly
   oversized empty strip under the tab bar (and the same excess baked
   into the content-clearance/toast-position math that depends on it).
   Capped specifically for body.pwa-app (see app.js's isStandalonePwa) —
   a regular browser tab keeps the real, uncapped inset. Still leaves
   real clearance (14px) above the home-indicator gesture area, just not
   the platform's full (and here, overstated) reported value. */
body.pwa-app { --safe-bottom: min(env(safe-area-inset-bottom), 14px); }

* { box-sizing: border-box; }
/* kills double-tap-to-zoom on iOS/Android — the viewport meta tag's
   user-scalable=no covers pinch-zoom, but iOS Safari in particular
   doesn't reliably honor that for the double-tap gesture on its own */
/* `contain` still lets the page itself rubber-band/bounce past its own
   real top and bottom edge (revealing blank background above/below
   content) — it only stops that bounce from chaining into a PARENT
   scroller. `none` kills the elastic overscroll outright: the page
   simply stops exactly at its real start/end, on iOS/Android/desktop
   alike, matching how an installed app behaves rather than a website. */
html, body { touch-action: manipulation; -webkit-text-size-adjust: 100%; overscroll-behavior-y: none; }
/* no visible scrollbar anywhere — an installed app doesn't show one,
   and the floating pill tab bar/topbar blur already read as the "edge"
   of the screen without a scroll track drawn next to them */
html, body { scrollbar-width: none; -ms-overflow-style: none; }
html::-webkit-scrollbar, body::-webkit-scrollbar { display: none; width: 0; height: 0; }
/* installed-app feel, not a webpage — text selection, long-press "save
   image"/copy, and (via boot()'s contextmenu listener) right-click are
   all suppressed. Scoped to body.pwa-app only (see app.js's
   isStandalonePwa) — a regular browser tab is completely unaffected.
   -webkit-user-select/-webkit-touch-callout are the ones that actually
   matter on iOS Safari; the unprefixed versions cover everyone else. */
body.pwa-app {
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
}
/* real form fields must stay selectable/editable, or typing and fixing
   a typo becomes impossible */
body.pwa-app input, body.pwa-app textarea, body.pwa-app [contenteditable] {
  -webkit-user-select: text; user-select: text;
  -webkit-touch-callout: default;
}
body.pwa-app img, body.pwa-app .pcard {
  -webkit-user-drag: none; user-drag: none;
}
body {
  margin: 0;
  background:
    radial-gradient(1100px 420px at 50% -140px, rgba(18, 69, 111, .35), transparent 70%),
    var(--bg);
  color: var(--ivory);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
}
h1, h2, h3, h4 { font-family: var(--font-head); line-height: 1.2; margin: 0 0 .5em; }
h1 { font-size: 1.9rem; font-weight: 700; letter-spacing: -.01em; }
h2 { font-size: 1.35rem; font-weight: 600; }
h3 { font-size: 1.05rem; font-weight: 600; }
p  { margin: 0 0 1em; }
a  { color: var(--home-purple); text-decoration: none; }
a:hover { text-decoration: underline; }
button { font-family: inherit; }
::selection { background: var(--home-purple); color: #fff; }

.num, .mono-num { font-family: var(--font-head); font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }

/* ---------- splash / connection-lost overlay ----------
   Static in index.html (not built by app.js) so it's already painted
   before the module script graph even finishes loading — the very
   first thing anyone sees, every cold load. Two modes, one element:
   #splashLoadingRow while app.js confirms the first real API call
   actually reaches the server; #splashErrorRow any time the connection
   drops (boot OR mid-game — api.js's req() and a plain 'offline'
   listener both drive this, see api.js). No offline fallback, no
   silent limping along on stale data — just a hard stop + Reload. */
.splash-screen {
  position: fixed; inset: 0; z-index: 10000;
  display: flex; align-items: center; justify-content: center;
  background: #050810; /* letterbox color — matches the art's own dark edges */
  overflow: hidden;
  transition: opacity 700ms ease, visibility 700ms;
}
.splash-screen.hide { opacity: 0; visibility: hidden; pointer-events: none; }
/* locked to the real art's own pixel dimensions (852x1847) — width/
   height:100% + max-width/max-height:100% inside this flex-centered
   parent makes the browser scale it as large as possible while
   preserving that ratio, exactly like object-fit:contain, so every
   percentage-positioned overlay below stays pixel-aligned with the art
   (the card cluster sitting right over the stage) on any screen shape
   instead of drifting when a cover-cropped background crops
   differently per device. */
.splash-art {
  position: relative;
  width: 100%; height: 100%; max-width: 100%; max-height: 100%;
  aspect-ratio: 852 / 1847;
  background: url("../icons/loadscrn.PNG") center center / cover no-repeat;
}
/* staged reveal — brand, then cards, then the loading status, each its
   own beat instead of everything popping in at once. Opacity starts at
   0 in the base rule (not just the keyframe's 0%) so there's no flash
   of the un-animated end state on a slow first paint. */
.splash-logo, .splash-brand, .splash-cards-slot, .splash-status { opacity: 0; }
.splash-logo {
  position: absolute; left: 50%; top: 11%; width: 30%; max-width: 140px;
  transform: translate(-50%, -50%);
  animation: splashRiseCentered 900ms cubic-bezier(.2, .8, .3, 1) 80ms forwards;
}
.splash-logo img { display: block; width: 100%; height: auto; filter: drop-shadow(0 6px 18px rgba(0, 0, 0, .45)); }
.splash-brand {
  position: absolute; left: 50%; top: 26%; width: 92%;
  transform: translate(-50%, -50%);
  text-align: center;
  animation: splashRiseCentered 900ms cubic-bezier(.2, .8, .3, 1) 200ms forwards;
}
/* colors only, matching the Home hero's own established palette
   (--home-grad/--muted/--ivory/--teal-hi — same variables .hero-welcome/
   .hero-wordmark/.hero-cardroom/.hero-location already use) — every
   other property (size, spacing, shadow, animation) stays exactly as
   tuned for this screen, untouched. */
.splash-welcome {
  font-family: var(--font-head); font-weight: 700; letter-spacing: .3em;
  font-size: .68rem; color: var(--muted); margin-bottom: 6px;
}
.splash-wordmark {
  font-family: var(--font-head); font-weight: 800; font-size: clamp(1.7rem, 7.2vw, 2.5rem);
  letter-spacing: .05em; margin: 0 0 6px;
  background: var(--home-grad);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  text-shadow: 0 0 30px rgba(140, 110, 255, .35);
}
.splash-tag {
  font-family: var(--font-head); font-weight: 700; letter-spacing: .5em;
  font-size: .72rem; color: var(--ivory); margin-left: .5em; /* optically re-center the letter-spacing */
}
.splash-loc {
  font-family: var(--font-head); font-weight: 700; letter-spacing: .3em;
  font-size: .6rem; color: var(--teal-hi); margin-top: 10px;
}
/* stage's own light-burst center, measured from the real art file —
   see the JS comment in app.js's initSplash() for how. Cards inside
   are sized responsively off this slot's actual rendered width (see
   heroGlowCards(scale) in app.js), not a fixed px guess, so they read
   the same proportionally on a phone and an iPad. */
.splash-cards-slot {
  position: absolute; left: 50%; top: 65%; width: 66%; height: 22%;
  transform: translate(-50%, -50%);
  animation: splashRiseCentered 900ms cubic-bezier(.2, .8, .3, 1) 480ms forwards;
}
.splash-status {
  position: absolute; left: 50%; bottom: 6%; width: 82%; max-width: 300px;
  transform: translateX(-50%);
  text-align: center;
  animation: splashRiseBottom 700ms cubic-bezier(.2, .8, .3, 1) 800ms forwards;
}
@keyframes splashRiseCentered {
  from { opacity: 0; transform: translate(-50%, calc(-50% + 12px)); }
  to   { opacity: 1; transform: translate(-50%, -50%); }
}
@keyframes splashRiseBottom {
  from { opacity: 0; transform: translate(-50%, 12px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .splash-logo, .splash-brand, .splash-cards-slot, .splash-status { animation: none; opacity: 1; }
  .splash-logo, .splash-brand, .splash-cards-slot { transform: translate(-50%, -50%); }
  .splash-status { transform: translateX(-50%); }
}
.splash-loading-label {
  font-family: var(--font-head); font-weight: 700; letter-spacing: .35em;
  font-size: .68rem; color: #D9CFFF; margin-bottom: 10px;
}
.splash-progress {
  height: 3px; border-radius: 2px; background: rgba(255, 255, 255, .14);
  overflow: hidden; position: relative;
}
.splash-progress-bar {
  position: absolute; top: 0; bottom: 0; width: 40%; border-radius: 2px;
  background: linear-gradient(90deg, rgba(109, 63, 201, 0), #9B6DFF, #E8ECFF, #9B6DFF, rgba(109, 63, 201, 0));
  animation: splashProgress 1.7s ease-in-out infinite;
}
@keyframes splashProgress {
  0%   { left: -45%; }
  100% { left: 105%; }
}
@media (prefers-reduced-motion: reduce) { .splash-progress-bar { animation: none; left: 0; width: 100%; } }
.splash-error-row .splash-error-title {
  font-family: var(--font-head); font-weight: 700; font-size: 1.1rem; color: #FFD9D5; margin-bottom: 6px;
}
.splash-error-sub { color: #C7C2E0; font-size: .84rem; margin: 0 0 16px; }
.splash-error-row .btn { min-width: 160px; }

/* ---------- app frame ---------- */
.app-frame { min-height: 100vh; display: flex; flex-direction: column; }
.screen-host {
  flex: 1;
  width: 100%;
  max-width: 1120px;
  margin: 0 auto;
  /* default: just clear the safe-area/notch — the topbar has nothing
     visible on any page except Home (its logo/wallet content is hidden
     everywhere else), so there's nothing to reserve the bar's full
     height for here. Reserving that space anyway is exactly what was
     leaving a huge dead gap at the top of every non-Home page. */
  padding: max(20px, calc(env(safe-area-inset-top) + 8px)) 20px
           calc(var(--tabbar-h) + var(--safe-bottom) + var(--tabbar-gap) + 20px);
  outline: none;
}
/* Home's topbar DOES have visible content (coin button/wallet), so it
   needs the full topbar-height clearance so page content doesn't render
   underneath it. Shop now shows the MVR wallet chip up there too (see
   .topbar-right below), so it needs the same full clearance — every
   other page still gets the short default above (nothing visible to
   protect). */
body.home-topbar .screen-host,
body.shop-topbar .screen-host {
  padding-top: calc(env(safe-area-inset-top) + var(--topbar-h) - 10px);
}
@media (min-width: 880px) {
  .screen-host { padding-bottom: 48px; }
}
/* no topbar/tabbar exists for staff/superadmin (see the display:none rule
   above) — the reserved clearance for both would just be dead space */
body.role-staff .screen-host, body.role-superadmin .screen-host {
  padding-top: max(20px, env(safe-area-inset-top));
  padding-bottom: max(20px, var(--safe-bottom));
}

/* ---------- top bar ---------- */
/* fixed, not sticky — it no longer reserves any space in the page's own
   flow on ANY route (Home included), it just floats over whatever's
   scrolling underneath. That's what lets Home's hero photo bleed to the
   true top edge while the bar still overlays it, and it's what makes
   the same faded blur available on every other page too, not just Home. */
.topbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  display: flex; align-items: center; gap: 20px;
  height: calc(var(--topbar-h) + env(safe-area-inset-top));
  padding: 0 max(20px, env(safe-area-inset-left)) 0 max(20px, env(safe-area-inset-right));
  padding-top: env(safe-area-inset-top);      /* clear the notch / status bar */
}
/* the blur lives on its own layer behind the logo/wallet content, faded
   out via a mask instead of applying backdrop-filter to .topbar itself —
   blurring the WHOLE rectangle uniformly is exactly what read as "a box":
   a hard-edged blurred strip with an abrupt line where it suddenly stops
   and the sharp hero photo begins. This fades blur→nothing top-to-bottom,
   like the reference screenshot's status bar area, so there's no visible
   edge at all. This now renders on EVERY page (fixed positioning, always
   present) — only the logo/wallet CONTENT is Home-only, right below. */
.topbar::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0; z-index: -1;
  /* shorter than .topbar's own box on purpose — .topbar's height still
     matches its old logo/wallet-row size everywhere, but screen-host's
     content clearance on non-Home pages is much shorter now (nothing
     to protect there), so a blur zone matching the FULL old bar height
     was reaching down far enough to fog the page heading text right
     below it. Confined to just the safe-area/status-bar strip instead
     — still a real fade, just not tall enough to touch page content. */
  height: calc(env(safe-area-inset-top) + 4px);
  backdrop-filter: blur(16px) saturate(1.3);
  -webkit-backdrop-filter: blur(16px) saturate(1.3);
  mask-image: linear-gradient(to bottom, #000 45%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 45%, transparent 100%);
  pointer-events: none;
}
/* Home and Shop keep the taller blur zone — the two pages where
   something actually sits inside it and benefits from the fuller fade. */
body.home-topbar .topbar::before,
body.shop-topbar .topbar::before { bottom: 0; height: auto; }
/* the coin button only makes sense on Home (it's tied to Home's own
   missions/claim-reward flow) — every other page keeps just the blur
   scrim above with nothing floating on it. The MVR wallet chip is the
   one piece that also makes sense on Shop (see the Shop redesign's own
   wallet-balance ask), so topbar-right gets its own, wider exception.
   in-game already hides/repads independently (its own screen-host rules
   further down), so this explicitly stays out of that case rather than
   fighting it on specificity. */
body:not(.home-topbar) .topbar .topbar-left { display: none; }
body:not(.home-topbar):not(.shop-topbar) .topbar .topbar-right { display: none; }
/* body.in-game .topbar { display: none; } — already set further down,
   next to the rest of in-game's own screen-host overrides */
/* the logo used to live here — replaced by the identity chip (avatar +
   level ring + name) and the coins/missions button side by side, same
   Home-only scoping as the rule above. */
.topbar-left { display: flex; align-items: center; gap: 10px; min-width: 0; }
.topbar-coin { position: relative; display: flex; align-items: center; }
.topbar .mc-hud-dropdown {
  position: absolute; top: calc(100% + 10px); left: 0;
  width: min(320px, calc(100vw - 40px));
  margin: 0; z-index: 5;
}

/* ---------- topbar identity chip: avatar + level ring + name ---------- */
/* sized to fit inside --topbar-h (58px) with the name hanging just below
   the ring — .topbar has no overflow:hidden, so anything taller would
   visually spill onto the page content right underneath it instead of
   being clipped, hence the tight budget here (40px ring + ~11px name). */
.topbar-identity { position: relative; flex: none; }
.topbar-identity > a { display: block; text-decoration: none; }
.ti-avatar-wrap { position: relative; width: 40px; height: 40px; flex: none; }
.ti-avatar-wrap .ti-ring { position: absolute; inset: 0; transform: rotate(-90deg); }
.ti-ring-track { fill: none; stroke: rgba(255, 255, 255, .1); stroke-width: 2.4; }
.ti-ring-fill {
  fill: none; stroke: url(#tiRingGrad); stroke-width: 2.4; stroke-linecap: round;
  transition: stroke-dashoffset var(--t-med);
  filter: drop-shadow(0 0 4px rgba(139, 92, 246, .5));
}
.ti-avatar-photo {
  position: absolute; inset: 4px; border-radius: 50%; overflow: hidden;
  background: var(--ink-2);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-head); font-weight: 800; font-size: .8rem; color: var(--ivory);
}
.ti-avatar-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ti-level {
  position: absolute; right: -2px; bottom: -2px;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--home-grad); border: 2px solid var(--ink);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-head); font-weight: 800; font-size: .5rem; color: #fff;
}
.ti-name {
  position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  margin-top: 2px;
  font-family: var(--font-head); font-weight: 700; font-size: .56rem;
  color: var(--ivory); letter-spacing: .01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 60px;
}
.topnav { display: none; gap: 8px; margin-left: 12px; }
@media (min-width: 880px) { .topnav { display: flex; } }
.topnav a {
  padding: 7px 16px; border-radius: 999px;
  color: var(--ivory-dim); font-weight: 500; font-size: .92rem;
  /* background/border/backdrop-filter/box-shadow + the sheen streak come
     from the shared LIQUID GLASS SURFACES rule further down — each nav
     link is its own small glass pill, same material as every other card
     in the app */
  transition: color var(--t-fast), border-color var(--t-fast), transform var(--t-fast);
}
.topnav a:hover { color: var(--ivory); border-color: rgba(186, 214, 255, .3); text-decoration: none; }
.topnav a:active { transform: scale(.96); }
.topnav a.active {
  color: #fff; border-color: var(--teal);
  box-shadow: var(--glass-edge), var(--glass-shadow), 0 0 0 1px rgba(23, 181, 179, .35) inset;
}
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }

.env-badge {
  font-size: .66rem; font-weight: 700; letter-spacing: .12em;
  color: var(--gold-hi); border: 1px solid var(--gold);
  border-radius: 4px; padding: 2px 7px;
}
.balance-chip {
  display: flex; align-items: center; gap: 5px;
  height: 32px;
  background: var(--ink-2); border: 1px solid var(--ink-line);
  border-radius: 999px; padding: 0 6px 0 10px;
  color: var(--ivory); transition: border-color var(--t-fast);
}
.balance-chip:hover { border-color: var(--home-purple); text-decoration: none; }
.balance-ico { width: 16px; height: 16px; fill: url(#tabActiveGrad); flex: none; }
.balance-cur { font-size: .64rem; font-weight: 600; color: var(--muted); letter-spacing: .06em; }
.balance-amt { font-family: var(--font-head); font-size: .82rem; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--gold-hi); }
/* the topbar wallet chip's trailing "+" — purely decorative (the whole
   chip is already one link to #/wallet, same add-funds destination) */
.balance-add {
  display: flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%;
  background: rgba(255, 255, 255, .08); color: var(--ivory); flex: none;
}
.topbar-bell {
  width: 32px; height: 32px; border-radius: 50%; flex: none;
  border: 1px solid var(--ink-line); background: var(--ink-2);
  display: flex; align-items: center; justify-content: center;
  color: var(--ivory); cursor: pointer;
  transition: border-color var(--t-fast);
}
.topbar-bell:hover { border-color: var(--home-purple); }
.topbar-bell svg { width: 15px; height: 15px; }

/* ---------- mobile tab bar ---------- */
/* a floating glass pill, detached from the screen edges (iOS/Telegram/
   WhatsApp style) rather than a full-width bar welded to the bottom.
   Sitting `--tabbar-gap` + the safe-area inset above the true edge means
   the whole capsule already clears the home-indicator gesture strip —
   no separate padding-bottom trick needed like the old edge-to-edge bar
   had. Same liquid-glass material as every other card in the app. */
.tabbar {
  position: fixed; z-index: 40;
  left: max(14px, env(safe-area-inset-left));
  right: max(14px, env(safe-area-inset-right));
  bottom: calc(var(--safe-bottom) + var(--tabbar-gap));
  height: var(--tabbar-h);
  display: grid; grid-template-columns: repeat(5, 1fr);
  border-radius: 999px;
  /* deliberately lower-opacity than the standard --glass-grad every
     other card uses — at that opacity the tint dominates and the
     backdrop-filter blur barely reads; this needs to look like content
     is visibly blurring THROUGH the bar as it scrolls underneath, not
     just a solid-ish dark pill that happens to have blur turned on. */
  background:
    radial-gradient(ellipse 120% 60% at 50% -10%, rgba(255, 255, 255, .12), transparent 55%),
    rgba(10, 22, 45, .38);
  border: 1px solid var(--glass-border);
  box-shadow: var(--glass-edge), var(--glass-shadow);
  backdrop-filter: blur(20px) saturate(1.5);
  -webkit-backdrop-filter: blur(20px) saturate(1.5);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tabbar { background: rgba(10, 22, 45, .88); }
}
/* full-width fading blur scrim behind the pill, matching the topbar's
   same treatment — the pill itself only blurs the narrow strip directly
   behind it; this covers the whole screen width (fixed positioning
   ignores the pill's own left/right inset margins) so content fades
   into blur as it nears the bottom edge everywhere, not just inside the
   pill's footprint. Faces the opposite direction from the topbar's
   fade (strongest blur near the true bottom edge, fading to nothing
   going up) and sits behind the pill (z-index:-1 within .tabbar's own
   stacking context) so the pill still reads as its own distinct shape. */
.tabbar::after {
  content: ""; position: fixed; left: 0; right: 0; bottom: 0; z-index: -1;
  height: calc(var(--tabbar-h) + var(--safe-bottom) + var(--tabbar-gap) + 34px);
  backdrop-filter: blur(16px) saturate(1.3);
  -webkit-backdrop-filter: blur(16px) saturate(1.3);
  mask-image: linear-gradient(to top, #000 40%, transparent 100%);
  -webkit-mask-image: linear-gradient(to top, #000 40%, transparent 100%);
  pointer-events: none;
}
@media (min-width: 880px) { .tabbar { display: none; } }
.tabbar a {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  color: var(--muted); font-size: .68rem; font-weight: 600;
  position: relative; -webkit-tap-highlight-color: transparent;
  transition: color var(--t-fast);
}
.tabbar a:hover { text-decoration: none; }
.tabbar a:active { color: var(--ivory); }
.tabbar a svg { transition: transform 140ms cubic-bezier(.4,0,.2,1); }
/* two layers per icon — a plain outline (default) and a solid fill of
   the SAME silhouette (matched geometry, not a redrawn/different icon),
   swapped on .active. The fill uses fill="currentColor" in the markup;
   CSS overrides it to the gradient here so only the active tab lights
   up, everyone else stays a plain muted outline. */
.tabbar a .ico-fill { display: none; }
.tabbar a.active .ico-line { display: none; }
.tabbar a.active .ico-fill { display: block; fill: url(#tabActiveGrad); }
.tabbar a.active { color: #9b8cf7; }
.tabbar a.active svg { transform: translateY(-1px); }

/* ---------- shared components ---------- */
.wave-divider { color: var(--ink-line); width: 100%; height: 12px; margin: 26px 0; display: block; }
.wave-divider.gold { color: var(--gold); opacity: .55; }

.panel {
  background: var(--ink-2);
  border: 1px solid var(--ink-line);
  border-radius: var(--r-lg);
  padding: 20px;
}
.panel + .panel { margin-top: 16px; }

.section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin: 30px 0 14px;
}
.section-head h2 { margin: 0; }
.section-head .side { font-size: .85rem; color: var(--muted); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: 1px solid transparent; border-radius: 12px;
  padding: 11px 22px; font-size: .93rem; font-weight: 700;
  font-family: var(--font-head); letter-spacing: .02em;
  cursor: pointer; text-decoration: none;
  min-height: 44px;                                    /* comfortable touch target */
  transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast),
              opacity var(--t-fast), transform 120ms cubic-bezier(.4,0,.2,1), box-shadow var(--t-fast);
  user-select: none; -webkit-tap-highlight-color: transparent;
}
.btn:hover { text-decoration: none; }
.btn:active:not(:disabled) { transform: translateY(1px) scale(.985); }
.btn:focus-visible { outline: 2px solid var(--home-purple); outline-offset: 2px; }
.btn:disabled { opacity: .45; cursor: not-allowed; }
/* glass buttons — translucent + backdrop-blur (real glassmorphism, not
   just a flat color) with an inset top sheen simulating light catching
   a glass surface, matching the game-card treatment below. Primary
   moved from teal to the purple/blue accent (Home's design, now the
   app-wide UI accent) — gold stays reserved for money-specific actions
   (.btn-gold), untouched. */
.btn-primary {
  background: linear-gradient(180deg, rgba(155, 130, 244, .85), rgba(79, 124, 255, .88));
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .28), inset 0 0 0 1px rgba(255, 255, 255, .06),
    0 2px 0 rgba(15, 12, 40, .45), 0 3px 8px rgba(0, 0, 0, .3);
}
.btn-gold {
  background: linear-gradient(180deg, rgba(242, 198, 76, .84), rgba(168, 121, 8, .88));
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  color: #221803;
  box-shadow: inset 0 1px 0 rgba(255, 245, 210, .4), inset 0 0 0 1px rgba(255, 255, 255, .08),
    0 2px 0 rgba(48, 32, 2, .5), 0 3px 8px rgba(0, 0, 0, .3);
  text-shadow: 0 1px 0 rgba(255, 235, 180, .35);
}
.btn-ghost {
  background: rgba(19, 54, 98, .3);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  color: var(--ivory); border-color: rgba(186, 214, 255, .18);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .08);
}
/* was a fully flat transparent button — the one outlier with none of the
   translucent-glass + backdrop-blur + inset-sheen treatment every other
   .btn-* variant has. Same recipe, red family. */
.btn-danger {
  background: linear-gradient(180deg, rgba(224, 115, 107, .28), rgba(158, 46, 38, .34));
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  color: #FFD9D5; border-color: rgba(224, 115, 107, .55);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);
}
@media (hover: hover) {
  .btn-primary:hover:not(:disabled) { background: linear-gradient(180deg, rgba(168, 145, 250, .9), rgba(94, 138, 255, .92)); }
  .btn-gold:hover:not(:disabled) { background: linear-gradient(180deg, rgba(250, 210, 96, .9), rgba(184, 134, 11, .92)); }
  .btn-ghost:hover:not(:disabled) { border-color: var(--home-purple); color: var(--home-purple); }
  .btn-danger:hover:not(:disabled) { background: linear-gradient(180deg, rgba(224, 115, 107, .4), rgba(158, 46, 38, .46)); }
}
.btn-sm { padding: 7px 14px; font-size: .84rem; min-height: 34px; border-radius: 10px; }
.btn-lg { padding: 14px 30px; font-size: 1.02rem; }
.btn-block { width: 100%; }

/* Friends page row actions (Accept/Decline, Cancel, Challenge/Remove) —
   was ad hoc inline flex per row with no width rule, so "Challenge"
   rendered visibly wider than "Remove". A first fix used flex:1 to even
   out a pair within one row, but flex:1 GROWS to fill whatever space is
   left over next to .k (the name/avatar) — and .k's own width varies by
   name length/wrapping, so different rows ended up with different
   leftover space and therefore different button sizes (e.g. a longer
   name wrapping its online-dot to a second line freed up extra width,
   making that row's buttons visibly bigger than a short-name row's).
   flex: 0 0 <fixed> pins every button to the exact same width no matter
   what's next to it or how long the name is — the actual fix. */
.fr-actions { display: flex; gap: 6px; flex-wrap: wrap; flex: none; }
.fr-actions .btn { flex: 0 0 92px; width: 92px; }
/* the actual fix for a LONG name pushing the actions onto their own
   second line: .k had no width limit at all, so a long name just kept
   growing and starved .fr-actions of the room it needs for both fixed-
   width buttons side by side, forcing fr-actions's own flex-wrap to
   kick in. min-width:0 on the flex child + text-overflow:ellipsis on
   the name specifically (not the avatar/online-dot next to it) caps how
   much room a name can claim, so the actions area always gets its full
   width regardless of name length. Scoped to .friend-row-k specifically,
   not every .kv row app-wide (other .kv usages are label:value pairs
   where truncating a short label would never be wanted). */
.friend-row-k { display: flex; align-items: center; gap: 6px; min-width: 0; flex: 1 1 auto; }
.friend-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

.badge {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  font-size: .7rem; font-weight: 700; letter-spacing: .05em;
}
.badge-teal  { background: var(--teal-soft); color: var(--teal-hi); }
.badge-gold  { background: var(--gold-soft); color: var(--gold-hi); }
.badge-red   { background: var(--red-soft);  color: #E0736B; }
.badge-green { background: rgba(46,139,87,.16); color: #5DC08A; }
.badge-purple { background: color-mix(in srgb, var(--home-purple) 16%, transparent); color: var(--home-purple); }
.badge-grey  { background: rgba(139,155,180,.14); color: var(--muted); }

.kv { display: flex; justify-content: space-between; gap: 14px; padding: 7px 0; font-size: .9rem; }
.kv + .kv { border-top: 1px solid var(--ink-line); }
.kv .k { color: var(--muted); }
.kv-ready { background: rgba(217, 165, 33, .08); margin: 0 -10px; padding: 7px 10px; border-radius: 8px; }
.kv-ready + .kv { border-top: none; }
.kv .v { font-weight: 600; text-align: right; }
.kv .v.gold { color: var(--gold-hi); }

.money { font-family: var(--font-head); font-variant-numeric: tabular-nums; }
.money.gold { color: var(--gold-hi); }
.money.neg  { color: #E0736B; }
.money.pos  { color: #5DC08A; }

.field { margin-bottom: 14px; }
.field label { display: block; font-size: .8rem; font-weight: 600; color: var(--muted); margin-bottom: 6px; letter-spacing: .02em; }
.field input, .field select, .field textarea, .friend-code-input {
  width: 100%; background: var(--ink); color: var(--ivory);
  border: 1px solid var(--ink-line); border-radius: var(--r-md);
  padding: 10px 13px; font-size: .95rem; font-family: inherit;
  transition: border-color var(--t-fast);
}
.field input:focus, .field select:focus, .field textarea:focus, .friend-code-input:focus {
  outline: none; border-color: var(--teal);
}
/* not wrapped in .field (no label needed here) — was left with zero
   custom styling at all, falling back to the browser's plain white
   input face against this otherwise all-dark page. */
.friend-code-input { max-width: 220px; }
.friend-code-input::placeholder { color: var(--muted); }
.field .hint { font-size: .77rem; color: var(--muted); margin-top: 5px; }

.note {
  border-left: 3px solid var(--gold); background: var(--gold-soft);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  padding: 12px 16px; font-size: .86rem; color: var(--ivory-dim);
  margin: 14px 0;
}
.note.red { border-left-color: var(--red); background: var(--red-soft); }
.note.teal { border-left-color: var(--teal); background: var(--teal-soft); }

.empty {
  text-align: center; color: var(--muted); padding: 34px 16px;
}
.empty .wave-divider { max-width: 160px; margin: 0 auto 14px; }
/* a nicer "nothing here yet" hero icon — the empty-state SVG helpers
   (trophyIco etc.) are drawn small (15-18px) for inline use elsewhere,
   so this scales just the icon up rather than the whole block */
.empty-hero-ico { margin-bottom: 10px; opacity: .5; }
.empty-hero-ico svg { width: 40px; height: 40px; }
/* an extra class, not a change to the shared .empty rule (used all over
   the app plain/transparent on purpose) — this scopes a real glass-card
   background to just this one instance instead of floating text on
   black, same card language as the rest of the redesigned page around it. */
.tourney-empty-card { border-radius: var(--r-lg); margin-top: 6px; }

/* compact "tap for the full explanation" row — same idea as Shop's own
   "How it works" button, just as a standalone full-width row instead of
   a small pill, since this one replaces what used to be a permanently
   visible paragraph of prose. */
.tourney-info-row {
  display: flex; align-items: center; gap: 12px; width: 100%;
  margin-top: 16px; padding: 12px 14px; border-radius: var(--r-lg);
  cursor: pointer; text-align: left; font-family: inherit; color: var(--ivory);
}
.tourney-info-row:active { transform: scale(.99); }
.tourney-info-ico {
  width: 36px; height: 36px; border-radius: 10px; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: var(--gold-soft); color: var(--gold-hi);
}
.tourney-info-text { min-width: 0; flex: 1; }
.tourney-info-title { font-family: var(--font-head); font-weight: 700; font-size: .92rem; }
.tourney-info-sub { font-size: .78rem; color: var(--muted); margin-top: 2px; }
.tourney-info-chevron { flex: none; color: var(--muted); }

table.data { width: 100%; border-collapse: collapse; font-size: .88rem; }
table.data th {
  text-align: left; font-size: .72rem; text-transform: uppercase; letter-spacing: .08em;
  color: var(--muted); font-weight: 600; padding: 8px 10px;
  border-bottom: 1px solid var(--ink-line);
}
table.data td { padding: 10px; border-bottom: 1px solid rgba(30, 67, 116, .5); vertical-align: middle; }
table.data tr:last-child td { border-bottom: none; }
table.data td.r, table.data th.r { text-align: right; }

/* ---------- toast ---------- */
.toast-host {
  position: fixed; bottom: calc(var(--tabbar-h) + var(--safe-bottom) + var(--tabbar-gap) + 12px); left: 50%; transform: translateX(-50%);
  z-index: 90; display: flex; flex-direction: column; gap: 8px; align-items: center;
  width: min(440px, calc(100vw - 32px));
}
@media (min-width: 880px) { .toast-host { bottom: 24px; } }
.toast {
  background: var(--ink-3); border: 1px solid var(--ink-line);
  border-radius: var(--r-md); padding: 11px 18px;
  font-size: .88rem; box-shadow: var(--shadow-2);
  opacity: 0; transform: translateY(6px);
  transition: opacity var(--t-med), transform var(--t-med);
  max-width: 100%;
}
.toast.show { opacity: 1; transform: translateY(0); }
.toast.gold { border-color: var(--gold); }
.toast.red { border-color: var(--red); }

/* ---------- modal ---------- */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 80;
  background: rgba(4, 14, 30, .72);
  backdrop-filter: blur(3px);
  display: flex; align-items: center; justify-content: center;
  padding: 18px;
  opacity: 0; transition: opacity var(--t-med);
}
.modal-backdrop.show { opacity: 1; }
/* background/border/box-shadow come from the shared liquid-glass
   selector list further down this file (.modal is in it) — this flat
   version was always dead code, silently overridden by the later
   glass rule. */
.modal {
  border-radius: var(--r-lg);
  width: min(520px, 100%); max-height: 86vh; overflow: auto;
  padding: 24px;
  transform: translateY(8px); transition: transform var(--t-med);
}
.modal-backdrop.show .modal { transform: translateY(0); }
/* phones: modals become bottom sheets */
@media (max-width: 640px) {
  .modal-backdrop { align-items: flex-end; padding: 0; }
  .modal {
    width: 100%; max-height: 88dvh;
    border-radius: 22px 22px 0 0; border-bottom: none;
    padding: 12px 20px calc(20px + env(safe-area-inset-bottom));
    transform: translateY(40px);
  }
  .modal::before {
    content: ""; display: block; width: 42px; height: 4px;
    border-radius: 999px; background: var(--ink-line);
    margin: 0 auto 16px;
  }
  .modal-actions { position: sticky; bottom: 0; background: var(--ink-2); padding-top: 10px; }
  .modal-actions .btn { flex: 1; }
}
.modal h3 { margin-bottom: 4px; }
.modal .modal-sub { color: var(--muted); font-size: .86rem; margin-bottom: 18px; }
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px; }

/* ---------- lobby ---------- */
/* Home only — #app is a SIBLING of .topbar/.tabbar (not their ancestor),
   so clipping overflow here can never touch those fixed-position bars.
   Guards against the hero art/filter-chip row ever making the page
   itself swipeable sideways, without the html/body-level version of
   this rule that broke position:fixed elsewhere (see git history).
   The margin/padding pair re-extends this wrapper all the way out to
   .screen-host's own outer edge (canceling its 20px padding, same trick
   .mode-carousel already uses) so overflow-x:hidden clips at the TRUE
   viewport edge — not 20px early, which was cutting the carousel's own
   edge-to-edge card bleed short and showing a dead strip of background
   on both sides instead of the peeking neighbour cards. */
/* the hero section (photo, welcome text, floating card cluster) that
   used to open this wrapper is gone — the coins/missions button moved
   into the topbar and the wordmark now only lives on the splash screen,
   so Home goes straight from the fixed topbar's clearance into the Shop
   row/filter chips. padding-top here is what used to be the hero's own
   top padding, keeping that same breathing room instead of content
   sitting flush against the topbar's clearance edge. */
.home-clip { overflow-x: hidden; margin: 0 -20px; padding: 14px 20px 0; }

/* floating "neon glass" card cluster, hovering above the glowing podium
   baked into herosecimg.jpg — real cardEl() cards (heroGlowCards() in
   app.js), just restyled: dark translucent glass instead of the normal
   paper face, a glowing border/rank/suit in a per-card accent color.
   pointer-events:none throughout so the cards never block taps on the
   wordmark/shop button underneath. Deliberately no blur anywhere (no
   backdrop-filter, no filter:blur) — every card stays crisp, only
   opacity/glow-strength signal depth, matching the reference art where
   the flanking cards are dim glass, not out of focus.
   The outline itself is a fixed dark-blue/purple/white gradient (same on
   every card, independent of --glow) via the padding-box/border-box
   background trick — border-image would do this too but ignores
   border-radius and squares off the rounded corners. The glow/color is
   confined to the border on purpose: plain neutral dark glass fill,
   no inset color bleeding onto the interior surface — only the border
   itself carries any neon color or glow. */
.hero-glow-cards { pointer-events: none; }
.pcard.neon-card {
  background:
    linear-gradient(165deg, rgba(14, 17, 34, .62) 0%, rgba(7, 9, 20, .7) 100%) padding-box,
    linear-gradient(150deg, #16214F 0%, #6D3FC9 52%, #FFFFFF 100%) border-box;
  border: 1.5px solid transparent;
  box-shadow:
    0 0 3px var(--glow, #4F9CFF),
    0 0 6px color-mix(in srgb, var(--glow, #4F9CFF) 35%, transparent);
  color: var(--glow, #4F9CFF);
}
/* .pcard.dark / .pcard.red (below, further down this file) set `color`
   at the same specificity — without this, source order alone decides the
   winner and the rank/suit color stops following --glow, leaving the
   glow filter (still applied, see below) tinting a color that no longer
   matches the border/shadow. Three classes beats two regardless of
   where each rule sits in the file, so this always wins. */
.pcard.neon-card.dark, .pcard.neon-card.red { color: var(--glow, #4F9CFF); }
/* Four independent corner marks (built in heroGlowCards(), not by
   reusing cardEl()'s single stacked "A / suit" corner — that layout is
   correct for a normal in-game hand, but the cluster wants rank and
   suit split apart): rank top-left, its outline suit top-right at the
   SAME height, then the mirrored pair — rank bottom-right, suit
   bottom-left — rotated 180° so it still reads right-way-up from that
   end. The "A"/"K" was also reading too bold against the thin gradient
   outline — the loaded Sora weights are 400/500/600/700/800, so 400
   (not a synthesized/unavailable lighter weight) is the thinnest real
   option. */
.pcard.neon-card .pc-corner-rank {
  position: absolute; color: #EEF4FF; font-weight: 400;
  font-family: var(--font-head); letter-spacing: -.02em; line-height: 1;
  font-size: calc(var(--cw) * .30);
  filter: drop-shadow(0 0 4px var(--glow, #4F9CFF));
}
.pcard.neon-card .pc-corner-suit {
  position: absolute; display: flex; line-height: 0;
  filter: drop-shadow(0 0 4px var(--glow, #4F9CFF));
}
.pcard.neon-card .pc-corner-suit svg { width: calc(var(--cw) * .22); height: calc(var(--cw) * .22); }
/* real suit-color fill (same real black/red the center pip already
   uses, see .pcard.neon-card.dark/.red .pc-center further down) with a
   thin neon rim on top — same #neonPipOutlineGrad the card border uses.
   Used to be fill:none (outline only, same fixed gradient for every
   suit) — fine on the hero card where the big center pip already
   carries the real color, but the corner marks are what the smaller
   flanking cards mostly show, and those read as colorless/washed out
   at small sizes without a real fill to give them contrast. */
.pcard.neon-card .pc-corner-suit svg path {
  stroke: url(#neonPipOutlineGrad); stroke-width: .8;
}
.pcard.neon-card.dark .pc-corner-suit svg path { fill: #0A0A0C; }
.pcard.neon-card.red  .pc-corner-suit svg path { fill: #D6303F; }
.pcard.neon-card .pc-corner-rank-tl { top: 5%; left: 8%; }
.pcard.neon-card .pc-corner-suit-tr { top: 5%; right: 8%; }
.pcard.neon-card .pc-corner-rank-br { bottom: 5%; right: 8%; transform: rotate(180deg); }
.pcard.neon-card .pc-corner-suit-bl { bottom: 5%; left: 8%; transform: rotate(180deg); }
/* the center pip: base .pcard .pc-center (below, further down this file)
   positions it offset toward the bottom-right — correct for a normal
   card face, but on the neon cluster it collided with the new mirrored
   bottom-right corner. True dead-center on every neon card fixes that
   for the flanking cards too, not just the hero one. */
.pcard.neon-card .pc-center {
  left: 50%; top: 52%; right: auto; bottom: auto;
  transform: translate(-50%, -50%);
}
/* flat solid suit-color fill (real black for spade/club, real red for
   heart/diamond), with a thin dark-blue/purple/white gradient rim — the
   same #neonPipOutlineGrad the corner marks use — instead of a flat
   color, so it reads as the same neon outline as the border. Both the
   center pip and the corner marks are real inline <path> elements (see
   heroGlowCards() in app.js), not <use> refs — CSS fill/stroke can't
   reach into a <use> shadow clone reliably, which is why an earlier
   attempt at this silently did nothing. */
.pcard.neon-card .pc-center svg path { stroke: url(#neonPipOutlineGrad); stroke-width: .8; }
.pcard.neon-card.dark .pc-center svg path { fill: #0A0A0C; }
.pcard.neon-card.red  .pc-center svg path { fill: #D6303F; }
.pcard.neon-card .pc-center svg {
  opacity: 1;
  filter:
    drop-shadow(0 0 4px #FFFFFF)
    drop-shadow(0 0 9px rgba(255, 255, 255, .55));
}
/* the hero card (the big ace) — same fixed gradient outline, just a
   touch thicker/brighter so it still reads as the centerpiece, and the
   same much-reduced outward glow as every other card. Same plain
   neutral interior, no inset color bleed. */
.pcard.neon-card.neon-card-hero {
  border-width: 2px;
  background:
    linear-gradient(165deg, rgba(18, 21, 42, .68) 0%, rgba(7, 9, 20, .78) 100%) padding-box,
    linear-gradient(150deg, #16214F 0%, #6D3FC9 52%, #FFFFFF 100%) border-box;
  box-shadow:
    0 0 4px #EFF6FF,
    0 0 8px var(--glow, #4F9CFF);
}
.pcard.neon-card.neon-card-hero .pc-center svg {
  width: calc(var(--cw) * .58); height: calc(var(--cw) * .58);
  filter:
    drop-shadow(0 0 6px #FFFFFF)
    drop-shadow(0 0 14px rgba(255, 255, 255, .7))
    drop-shadow(0 0 26px rgba(255, 255, 255, .4));
}
/* the smaller flanking cards read as further away — dimmer, never blurred */
.pcard.neon-card.neon-card-back { opacity: .68; }

.game-grid { display: grid; gap: 16px; grid-template-columns: 1fr; }
@media (min-width: 720px) { .game-grid { grid-template-columns: 1fr 1fr; } }

.game-card {
  position: relative; overflow: hidden;
  background: var(--ink-2); border: 1px solid var(--ink-line);
  border-radius: var(--r-lg); padding: 24px;
  transition: border-color var(--t-med), transform var(--t-med);
}
.game-card:hover { border-color: var(--teal); }
.game-card .gc-suits { display: flex; gap: 8px; color: var(--teal-hi); margin-bottom: 14px; }
.game-card .gc-suits svg { width: 22px; height: 22px; }
.game-card h2 { letter-spacing: .06em; }
.game-card .gc-tag { color: var(--muted); font-size: .84rem; margin-bottom: 16px; }
.game-card .gc-meta { display: flex; gap: 16px; font-size: .8rem; color: var(--ivory-dim); margin-bottom: 20px; }
.game-card .gc-meta strong { color: var(--ivory); font-family: var(--font-head); }
.game-card .gc-actions { display: flex; gap: 10px; flex-wrap: wrap; }

.tier-row { display: flex; gap: 8px; flex-wrap: wrap; margin: 14px 0 4px; }
.tier-pill {
  border: 1px solid var(--ink-line); border-radius: 999px;
  padding: 7px 15px; font-size: .8rem; cursor: pointer; min-height: 34px;
  background: rgba(19, 54, 98, .3); color: var(--ivory-dim);
  font-weight: 600; -webkit-tap-highlight-color: transparent;
  transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast), box-shadow var(--t-fast);
}
@media (hover: hover) { .tier-pill:hover { border-color: var(--teal); color: var(--ivory); } }
.tier-pill .tp-fee { font-family: var(--font-head); font-weight: 700; }
/* selected tiers read as metal chips */
.tier-pill.sel { color: #14202E; border-color: transparent; text-shadow: 0 1px 0 rgba(255,255,255,.25); }
.tier-row .tier-pill.sel:nth-child(1) { background: linear-gradient(180deg, #D29A6B, #9A6438); box-shadow: 0 2px 8px rgba(154, 100, 56, .4); }
.tier-row .tier-pill.sel:nth-child(2) { background: linear-gradient(180deg, #DFE3EA, #9BA3B0); box-shadow: 0 2px 8px rgba(155, 163, 176, .35); }
.tier-row .tier-pill.sel:nth-child(3) { background: linear-gradient(180deg, #EFC44F, #B8860B); box-shadow: 0 2px 10px rgba(217, 165, 33, .45); }
.tier-row .tier-pill.sel:nth-child(4) { background: linear-gradient(180deg, #C9A9E8, #7E5BA6); box-shadow: 0 2px 8px rgba(126, 91, 166, .4); }

/* Shop's theme picker. Real .felt/.pcard rules are reused for previews
   (not a hand-duplicated second copy — see the history below), so this
   page has FOUR .felt elements on screen simultaneously, one per theme
   — something no real game page ever does (only one table is ever
   shown at a time there). That "multiple felts, one page" fact is the
   whole reason this has been rewritten four times:
   attempt 1 used [data-theme="royal"] (matches <body> too — an ancestor
   of everything, so it isn't scoped to any specific .felt at all);
   attempt 2 "fixed" that by reverting to body[data-theme="royal"] .felt
   — but that was NEVER actually safe either: body[data-theme="royal"]
   .felt matches EVERY .felt on the page once body carries that
   attribute, i.e. all four preview boxes at once, not just the one
   meant to show Royal;
   attempt 3 hand-duplicated every theme rule under dedicated
   .tp-felt-classic/.tp-felt-royal/.tp-card-* classes, fully independent
   of the real .felt/.pcard — safe from the leak, but a second copy of
   every theme's visual rules that had to be kept in sync by hand, and
   would silently drift the moment someone edited the real rules and
   forgot this one;
   attempt 4 added [data-preview-theme="x"] as a sibling selector on
   every real body[data-theme="x"] rule (so previews reuse the exact
   same declaration block, no copy to drift) — but forgot that the
   ORIGINAL body[data-theme="x"] .felt half of that same rule still
   matches every preview .felt too, for whichever theme is actually
   active, exactly repeating attempt 2's leak on top of the new fix.
   Real fix (this one): body[data-theme="x"] .felt gets
   :not(.shop-preview-felt) added, so it can only ever match the real
   table — [data-preview-theme="x"] .felt is left as the sole selector
   that can match a preview, and previews/real table genuinely can't
   cross-match each other in either direction. */
/* ---------- Shop page: cards-forward, minimal chrome ---------- */
.shop-page-head { margin: 6px 0 20px; }
.shop-page-head h1 { margin: 0 0 4px; font-family: var(--font-head); font-weight: 800; font-size: 1.7rem; }
.shop-page-head p { margin: 0; color: var(--muted); font-size: .92rem; }

.shop-section-head { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.shop-section-ico {
  width: 40px; height: 40px; border-radius: 12px; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: rgba(139, 92, 246, .16); color: var(--home-purple);
}
.shop-section-text { min-width: 0; flex: 1; }
.shop-section-title { font-family: var(--font-head); font-weight: 800; font-size: 1.05rem; color: var(--ivory); }
.shop-section-sub { font-size: .8rem; color: var(--muted); margin-top: 2px; }
.shop-how-btn { flex: none; display: inline-flex; align-items: center; gap: 6px; }

.theme-picker-lg { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; margin-bottom: 26px; }
.theme-card-lg {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  border-radius: var(--r-lg);
  /* background/border/backdrop-filter/box-shadow come from the shared
     LIQUID GLASS SURFACES rule further down */
  padding: 16px 10px 14px; cursor: pointer; text-align: center;
  transition: border-color var(--t-fast), box-shadow var(--t-fast), transform 120ms cubic-bezier(.4,0,.2,1);
}
.theme-card-lg:active { transform: scale(.98); }
@media (hover: hover) { .theme-card-lg:hover { border-color: var(--theme-accent, var(--teal)); } }
.theme-card-lg.active { border-color: var(--gold); box-shadow: 0 0 0 1.5px var(--gold-hi), 0 6px 18px rgba(217, 165, 33, .18); }
/* per-theme accent badge, top-left corner — same color the ☰ in-game
   menu's own theme swatches use (see app.js's THEMES array) */
.theme-card-lg-badge {
  position: absolute; top: 10px; left: 10px; z-index: 1;
  width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(5, 8, 16, .65); color: var(--theme-accent, var(--gold-hi));
  border: 1px solid rgba(255, 255, 255, .12);
}
.theme-card-lg-name { font-family: var(--font-head); font-weight: 800; font-size: 1.05rem; color: var(--ivory); margin-top: 8px; }
.theme-card-lg-tag { font-size: .76rem; color: var(--muted); margin-bottom: 4px; }
.theme-card-lg-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  width: 100%; padding: 9px; border-radius: 999px;
  font-family: var(--font-head); font-weight: 700; font-size: .82rem;
  color: var(--theme-accent, var(--ivory));
  background: color-mix(in srgb, var(--theme-accent, var(--teal)) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--theme-accent, var(--teal)) 45%, transparent);
}
.theme-card-lg-btn svg { flex: none; }
.theme-card-lg-btn.active { color: var(--gold-hi); background: var(--gold-soft); border-color: rgba(217, 165, 33, .5); }
.theme-preview-outer { display: flex; justify-content: center; }

/* horizontal row of round icon tiles — tap the tile itself to toggle,
   no separate Add/Remove button cluttering every card */
.sticker-pack-row { display: flex; gap: 12px; overflow-x: auto; padding: 2px 2px 6px; scrollbar-width: none; }
.sticker-pack-row::-webkit-scrollbar { display: none; }
.sticker-pack-tile {
  position: relative; flex: none; width: 92px;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  border-radius: var(--r-lg); padding: 14px 8px 10px; cursor: pointer;
  transition: border-color var(--t-fast), transform 120ms cubic-bezier(.4,0,.2,1);
}
.sticker-pack-tile:active { transform: scale(.96); }
.sticker-pack-tile.added { border-color: rgba(217, 165, 33, .5); }
.sticker-pack-tile-ico { width: 40px; height: 40px; }
.sticker-pack-tile-ico svg { width: 100%; height: 100%; }
.sticker-pack-tile-badge {
  position: absolute; top: 8px; right: 8px;
  width: 18px; height: 18px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--gold-hi); color: #2A1B02;
}
.sticker-pack-tile-label { font-family: var(--font-head); font-weight: 700; font-size: .82rem; color: var(--ivory); margin-top: 4px; }
.sticker-pack-tile-count { font-size: .68rem; color: var(--muted); }
/* the real theme rules expect data-theme on an ANCESTOR (<body>) with
   .felt as a separate DESCENDANT deep inside it — [data-preview-theme]
   has to mirror that exact shape (a wrapper, not the .felt element
   itself) or "[data-preview-theme=x] .felt" (a descendant selector)
   never matches anything. display:contents means this wrapper has zero
   layout footprint of its own (doesn't disrupt .theme-preview-outer's
   centering) while still existing for CSS attribute matching. */
.shop-preview-scope { display: contents; }

/* ---------- tournaments ---------- */
.tourney-list { display: grid; gap: 12px; }
.tourney-card {
  display: grid; gap: 14px; align-items: center;
  grid-template-columns: 1fr;
  /* background/border come from the shared LIQUID GLASS SURFACES rule
     further down. Desktop column count is set once, near .tc-art below
     (was duplicated here AND there with a conflicting column count,
     only resolved by !important — removed the dead copy). */
  border-radius: var(--r-lg); padding: 18px 20px;
  transition: border-color var(--t-fast);
}
.tourney-card:hover { border-color: var(--teal); }
.tc-title { font-family: var(--font-head); font-weight: 600; font-size: 1rem; }
.tc-sub { color: var(--muted); font-size: .8rem; margin-top: 2px; }
.tc-stat .lbl { font-size: .7rem; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
.tc-stat .val { font-family: var(--font-head); font-weight: 700; font-size: 1.05rem; font-variant-numeric: tabular-nums; }
.tc-stat .val.gold { color: var(--gold-hi); }

.countdown { font-family: var(--font-head); font-variant-numeric: tabular-nums; font-weight: 700; }
.countdown.urgent { color: var(--red); }

/* bracket */
.bracket-scroller { overflow-x: auto; padding-bottom: 10px; }
.bracket { display: flex; gap: 34px; min-width: max-content; padding: 8px 2px; }
.bracket-round { display: flex; flex-direction: column; justify-content: space-around; gap: 14px; min-width: 190px; }
.bracket-round .round-lbl {
  text-align: center; font-size: .72rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); font-weight: 700; margin-bottom: 2px;
}
.match {
  background: var(--ink); border: 1px solid var(--ink-line); border-radius: var(--r-md);
  overflow: hidden; font-size: .84rem;
}
.match .m-side { display: flex; justify-content: space-between; gap: 10px; padding: 8px 12px; color: var(--ivory-dim); }
.match .m-side + .m-side { border-top: 1px solid var(--ink-line); }
.match .m-side.winner { color: var(--ivory); background: var(--teal-soft); font-weight: 600; }
.match .m-side.you { color: var(--gold-hi); }
.match .m-score { font-family: var(--font-head); font-variant-numeric: tabular-nums; }

/* prize table */
.prize-split { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-top: 12px; }
.prize-cell { background: var(--ink); border: 1px solid var(--ink-line); border-radius: var(--r-md); padding: 10px 12px; text-align: center; }
.prize-cell .p-place { font-size: .7rem; color: var(--muted); text-transform: uppercase; letter-spacing: .07em; }
.prize-cell .p-amt { font-family: var(--font-head); font-weight: 700; color: var(--gold-hi); font-variant-numeric: tabular-nums; }

/* ---------- wallet ---------- */
/* background/border come from the shared liquid-glass selector list
   further down this file (.wallet-hero is in it) — this flat gradient
   was always dead code, silently overridden by the later glass rule. */
.wallet-hero {
  display: flex; flex-wrap: wrap; align-items: center; gap: 14px;
  padding: 14px 18px; border-radius: var(--r-lg);
}
.wallet-hero .wh-label { font-size: .68rem; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); font-weight: 700; }
.wallet-hero .wh-amount {
  font-family: var(--font-head); font-weight: 800; font-size: 1.6rem;
  color: var(--gold-hi); font-variant-numeric: tabular-nums; line-height: 1.1;
}
.wallet-hero .wh-cur { font-size: .8rem; color: var(--muted); font-weight: 600; margin-right: 5px; }
.wallet-hero .wh-actions { margin-left: auto; display: flex; gap: 10px; flex-wrap: wrap; }

.ledger-row-in  .money { color: #5DC08A; }
.ledger-row-out .money { color: #E0736B; }

.lb-panel { border: 1px solid var(--ink-line); overflow-x: auto; }
.leaderboard-table { min-width: 420px; } /* scrolls inside .lb-panel rather than squeezing/overlapping below that */
.leaderboard-table thead th { font-size: .74rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); white-space: nowrap; }
.leaderboard-table tbody tr { transition: background var(--t-fast); }
.leaderboard-table .lb-row-podium { background: rgba(217, 165, 33, .05); }
.leaderboard-table .lb-row-me {
  background: linear-gradient(90deg, color-mix(in srgb, var(--home-purple) 16%, transparent), color-mix(in srgb, var(--home-purple) 3%, transparent));
  box-shadow: inset 3px 0 0 var(--home-purple);
}
.rank-medal {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%; flex: none;
  font-family: var(--font-head); font-weight: 800; font-size: .82rem;
  color: #241a06;
}
.rank-medal.gold { background: linear-gradient(160deg, #FFE07A, #D9A521); box-shadow: 0 0 10px rgba(217, 165, 33, .55); }
.rank-medal.silver { background: linear-gradient(160deg, #F1F1F4, #A8ACB4); box-shadow: 0 0 8px rgba(168, 172, 180, .4); }
.rank-medal.bronze { background: linear-gradient(160deg, #E3A97C, #A9673A); box-shadow: 0 0 8px rgba(169, 103, 58, .4); color: #2a1608; }
.rank-num { color: var(--muted); font-weight: 700; font-variant-numeric: tabular-nums; padding-left: 4px; white-space: nowrap; }
/* narrow phones: the medal badge + avatar + name + level badge + XP +
   W-L all fighting for the same row used to visually overlap — drop
   the least-critical column and stop names wrapping into the row
   below instead of just shrinking everything until it collides */
@media (max-width: 480px) {
  .leaderboard-table { min-width: 0; }
  .leaderboard-table th:last-child, .leaderboard-table td:last-child { display: none; }
  .leaderboard-table td, .leaderboard-table th { padding: 8px 6px; font-size: .8rem; }
  .leaderboard-table .led-cell { max-width: 34vw; }
  .leaderboard-table .lb-name {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: inline-block; max-width: 20vw; vertical-align: middle;
  }
}
.lb-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%; overflow: hidden;
  background: var(--ink); font-size: .78rem; font-weight: 700; color: var(--muted);
  flex: none;
}
.lb-avatar img { width: 100%; height: 100%; object-fit: cover; }

.bank-box {
  background: var(--ink); border: 1px dashed var(--ink-line); border-radius: var(--r-md);
  padding: 14px 16px; margin: 12px 0; font-size: .9rem;
}
.bank-box .bb-name { font-weight: 700; font-family: var(--font-head); }
.bank-box .bb-acc { font-family: var(--font-head); font-variant-numeric: tabular-nums; letter-spacing: .04em; color: var(--teal-hi); }
.refcode {
  display: inline-block; background: var(--gold-soft); color: var(--gold-hi);
  font-family: var(--font-head); font-weight: 700; letter-spacing: .12em;
  border-radius: var(--r-sm); padding: 4px 12px; font-size: 1.02rem;
}

.receipt-drop {
  border: 1.5px dashed var(--ink-line); border-radius: var(--r-md);
  padding: 22px; text-align: center; color: var(--muted); font-size: .86rem;
  cursor: pointer; transition: border-color var(--t-fast), color var(--t-fast);
}
.receipt-drop:hover, .receipt-drop.has-file { border-color: var(--teal); color: var(--ivory-dim); }
.receipt-drop.has-file { border-style: solid; }

/* ---------- admin ---------- */
.admin-queue-item {
  display: grid; gap: 12px; grid-template-columns: 1fr;
  background: var(--ink-2); border: 1px solid var(--ink-line); border-radius: var(--r-lg);
  padding: 16px 18px;
}
@media (min-width: 720px) { .admin-queue-item { grid-template-columns: 1fr auto; align-items: center; } }
.aq-actions { display: flex; gap: 8px; }
.receipt-thumb {
  max-width: 130px; max-height: 90px; border-radius: var(--r-sm);
  border: 1px solid var(--ink-line); display: block; margin-top: 8px;
}

/* ==========================================================================
   PLAYING CARDS — flat vector faces, hairline border, no skeuomorphism (§3)
   ========================================================================== */
.pcard {
  --cw: 58px;
  width: var(--cw); height: calc(var(--cw) * 1.42);
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='90' height='90'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='2'/%3E%3CfeColorMatrix values='0 0 0 0 .2 0 0 0 0 .17 0 0 0 0 .1 0 0 0 .035 0'/%3E%3C/filter%3E%3Crect width='90' height='90' filter='url(%23p)'/%3E%3C/svg%3E"),
    linear-gradient(168deg, #FBF8F2, #F1EDE3);
  border: 1px solid var(--card-line);
  border-radius: calc(var(--cw) * .12);
  position: relative;
  display: inline-block;
  flex: none;
  user-select: none;
  box-shadow: 0 1px 2px rgba(9, 20, 38, .35), 0 3px 8px rgba(9, 20, 38, .18);
  transition: transform var(--t-fast), box-shadow var(--t-fast), border-color var(--t-fast);
}
.pcard .pc-corner {
  position: absolute; top: 5%; left: 8%;
  display: flex; flex-direction: column; align-items: center; line-height: 1;
}
.pcard .pc-rank {
  font-family: var(--font-head); font-weight: 700;
  font-size: calc(var(--cw) * .30);
  letter-spacing: -.02em;
}
.pcard .pc-corner svg { width: calc(var(--cw) * .22); height: calc(var(--cw) * .22); margin-top: 2px; }
.pcard .pc-center {
  position: absolute; right: 9%; bottom: 7%;
}
.pcard .pc-center svg { width: calc(var(--cw) * .42); height: calc(var(--cw) * .42); opacity: .9; }
.pcard.red { color: var(--suit-red); }
.pcard.dark { color: var(--suit-dark); }
/* opposite-corner rank/suit marks — off by default (Classic's normal
   top-left-stacked + big-center layout). Royal turns these on and hides
   the stacked corner's suit glyph instead — see cardEl() in cards.js
   for why these are always in the DOM regardless of theme. */
.pcard .pc-corner-alt { display: none; }
/* filled-center alternate — off by default; Classic's real .pc-center
   (outline) stays as-is. Royal swaps to this one instead. */
.pcard .pc-center-alt { display: none; }
.pcard .pc-center-alt svg { width: calc(var(--cw) * .42); height: calc(var(--cw) * .42); opacity: .9; }
/* rednblack theme's own corner/pip/face-letter markup — off by default,
   see the "rednblack" section further down and cardEl()'s comment in
   cards.js for why these are always in the DOM regardless of theme. */
.pcard .pc-corner-mirror { display: none; }
.pcard .pc-pips { display: none; }
.pcard .pc-face-letter { display: none; }

/* THAASBAI card back — deep navy, teal diamond lattice, gold frame +
   compass emblem. Previously used the SAME wave motif and mid-navy tone
   as the felt itself, so a fanned hand barely stood out against the
   table (a user screenshot showed this directly) — the lattice pattern,
   darker base gradient, and gold (not steel-blue) border all exist
   specifically to read as a distinct object sitting ON the felt, not a
   continuation of it. */
.pcard.back {
  background:
    radial-gradient(circle at 50% 38%, rgba(217, 165, 33, .14), transparent 60%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath d='M10 1 L19 10 L10 19 L1 10 Z' fill='none' stroke='%2314A3A1' stroke-opacity='.32' stroke-width='.8'/%3E%3C/svg%3E"),
    linear-gradient(160deg, #0E2E52 0%, #081D38 55%, #051020 100%);
  /* the lattice tile's SVG has explicit width/height="20" attributes, so
     without an explicit background-size it renders at a fixed 20 PHYSICAL
     px regardless of the card's own --cw — dense/cramped on a small card,
     sparse/thin on a big one, never actually matching the frame. Sizing
     it off --cw instead (20/58 ≈ .35, 58px being .pcard's own default
     --cw) keeps the same density this was designed at, at every card
     size. The other two layers stay their natural size (gradients fill
     the box regardless either way). */
  background-size: auto, calc(var(--cw) * .35) calc(var(--cw) * .35), auto;
  border-color: #D9A521;
  overflow: hidden;
}
.pcard.back::after {
  content: "";
  position: absolute; inset: 7.5%;
  border: 1px solid rgba(217, 165, 33, .55);
  border-radius: inherit;
}
.pcard.back::before {
  content: "";
  position: absolute; inset: 2.5%;
  border: 1px solid rgba(217, 165, 33, .22);
  border-radius: inherit;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2 L22 12 L12 22 L2 12 Z' fill='none' stroke='%2314A3A1' stroke-width='1.1'/%3E%3Ccircle cx='12' cy='12' r='3.4' fill='none' stroke='%23D9A521' stroke-width='1'/%3E%3Cpath d='M12 2 L12 6 M12 18 L12 22 M2 12 L6 12 M18 12 L22 12' stroke='%23D9A521' stroke-width='1' stroke-linecap='round'/%3E%3C/svg%3E")
    center / 34% no-repeat;
}

/* stackEl() — sized off --cw (falls back to 64px if nothing in the
   ancestor chain sets one) so it shrinks at every landscape/small-screen
   breakpoint the same way every other pile card already does, instead
   of the fixed-pixel size stackEl() used to hardcode inline (see its own
   header comment in cards.js for the overlap bug this caused). */
.card-stack {
  position: relative;
  width: var(--cw, 64px); height: calc(var(--cw, 64px) * 1.42);
}
/* the offset layers peeking out behind a draw pile's top card */
.stack-shadow {
  position: absolute; inset: 0;
  border-radius: calc(var(--cw, 64px) * .12);
  background: linear-gradient(160deg, #133A66, #0C2B50);
  border: 1px solid #2A527F;
  box-shadow: 0 1px 2px rgba(9, 20, 38, .35);
}
.stack-shadow-1 { transform: translate(3px, 3px); }
.stack-shadow-2 { transform: translate(6px, 6px); }

.pcard.selectable { cursor: pointer; }
.pcard.selectable:hover { transform: translateY(-7px); box-shadow: var(--shadow-1); border-color: var(--teal); }
.pcard.selected { transform: translateY(-11px); border-color: var(--teal); box-shadow: 0 0 0 2px var(--teal-soft), var(--shadow-1); }
.pcard.dim { opacity: .38; }
.pcard.in-meld { box-shadow: inset 0 0 0 2px rgba(20, 163, 161, .55); }      /* group 1: teal  */
.pcard.in-meld.mg1 { box-shadow: inset 0 0 0 2px rgba(217, 165, 33, .6); }  /* group 2: gold  */
.pcard.in-meld.mg2 { box-shadow: inset 0 0 0 2px rgba(155, 109, 214, .65); }/* group 3: violet*/
.pcard.in-meld.mg3 { box-shadow: inset 0 0 0 2px rgba(93, 192, 138, .6); }  /* group 4: green */
/* a laid-off card actually landed inside a meld group — a gold ring on
   top of the group's own color so it's obvious which card extended it */
.pcard.laid-off { box-shadow: inset 0 0 0 2px var(--gold), 0 0 10px rgba(217, 165, 33, .55); }
.pcard.laid-off::after {
  content: "LAID OFF"; position: absolute; left: 50%; bottom: -3px; transform: translate(-50%, 100%);
  font-size: .52rem; font-weight: 800; letter-spacing: .04em; color: var(--gold-hi);
  white-space: nowrap;
}
/* a team-pooled card: DIGU TEAMS lets the two LOSING partners' leftover
   deadwood combine into a brand-new meld neither hand had alone (the
   authoritative rule: "cards may be combined with the ones of the
   partner in the final scoring") — a genuinely different concept from
   .laid-off (one card attaching onto an ALREADY-EXISTING meld), so it
   gets its own ring color, not gold. No per-card text badge: 3 cards in
   one meld group physically overlap (see .meld-group's negative
   margin), so a repeated "LAID OFF"-style label per card rendered as
   garbled overlapping text — the section's own "Team melds — combined
   from both hands" header already explains what the group is. */
.pcard.team-pooled { box-shadow: inset 0 0 0 2px rgba(155, 109, 214, .75), 0 0 10px rgba(155, 109, 214, .4); }
.pcard.just-played { animation: cardIn 180ms cubic-bezier(.4,0,.2,1); }
@keyframes cardIn { from { transform: translateY(-10px); opacity: 0; } to { transform: none; opacity: 1; } }

.card-lbl.red  { color: var(--suit-red); filter: brightness(1.35); }
.card-lbl.dark { color: var(--ivory-dim); }

/* ---------- game table shared ---------- */
.table-screen {
  max-width: 880px; margin: 0 auto;
  display: flex; flex-direction: column;
  min-height: calc(100vh - 220px);
  min-height: calc(100dvh - 220px);
}
@media (min-width: 880px) {
  .table-screen { min-height: calc(100vh - 170px); min-height: calc(100dvh - 170px); }
}
.table-screen .felt { flex: 1; }
.table-top {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-bottom: 14px; flex-wrap: wrap;
}
.table-top .tt-title { font-family: var(--font-head); font-weight: 700; letter-spacing: .06em; }
.table-top .tt-meta { color: var(--muted); font-size: .82rem; }

.felt {
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .05 0'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E"),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='46' height='26' viewBox='0 0 46 26'%3E%3Cpath d='M0 13 Q11.5 3 23 13 T46 13' fill='none' stroke='%2314A3A1' stroke-opacity='.06' stroke-width='1'/%3E%3C/svg%3E"),
    radial-gradient(ellipse at 50% 30%, rgba(23, 181, 179, .10), transparent 55%),
    radial-gradient(ellipse at 50% 32%, #12456F 0%, #0D2E55 52%, #091E3A 96%);
  border: 1px solid #274E7E;
  border-radius: 24px;
  padding: 16px;
  position: relative;
  display: flex; flex-direction: column;
  box-shadow:
    inset 0 0 80px rgba(2, 8, 20, .55),
    inset 0 1px 0 rgba(120, 170, 220, .12),
    0 0 0 1px rgba(217, 165, 33, .08),
    0 16px 44px rgba(2, 8, 20, .6);
  user-select: none;
}
/* Shop preview card: the REAL .felt/.pcard classes and rules, scoped via
   [data-preview-theme="x"] (every theme's own body[data-theme="x"] rule
   was extended with a sibling selector for this — see each theme's own
   block) instead of a hand-duplicated second copy of every rule that
   could silently drift out of sync with the real in-game look (the
   .tp-felt-*/.tp-card-* classes this replaced). This override only
   touches SIZE — never background/border/box-shadow, which must stay
   whatever the real theme rules say — and has to come after .felt's own
   base rule above to win the padding/position ties. */
.felt.shop-preview-felt {
  width: 130px; height: 190px; padding: 0; margin: 0 auto;
  flex: none; display: block;
}
/* Classic-only: contains .felt::after's z-index below to strictly behind
   the felt's own real children, regardless of DOM/generation order, so
   the watermark sits behind the cards. Deliberately scoped OFF Royal —
   isolating .felt changes where a negative-z-index descendant paints (a
   negative z-index still paints ABOVE its containing element's own
   background per the CSS stacking spec, just below normal content).
   Royal's own .felt::after (the padded rail) was built assuming its
   z-index:-1 would escape .felt's stacking context entirely and land
   behind nearly everything — which is what made it read as "not
   showing" before. Isolating it made it instead paint directly over the
   green felt as a wrong-colored, wrong-shaped mess. Leaving Royal
   unisolated restores its original (admittedly still-imperfect, but not
   broken) escaped-stacking behavior. */
body:not([data-theme="royal"]):not([data-theme="rednblack"]) .felt {
  isolation: isolate;
}
/* table rail: layered ring with a soft gold thread */
.felt::before {
  content: "";
  position: absolute; inset: 7px;
  border: 1px solid rgba(44, 88, 140, .9);
  border-radius: 18px;
  box-shadow: inset 0 0 0 2.5px rgba(9, 24, 45, .8), inset 0 0 0 3.5px rgba(184, 134, 11, .3);
  pointer-events: none;
}
/* faint centered watermark — same brand logo/size/opacity treatment as
   the Royal theme's felt (thaaslogofi.PNG at 30% width, center, low
   opacity so it never competes with card legibility). z-index: -1 (see
   isolation above) keeps it behind every real card/piece on the table,
   in front of the felt's own background gradient. */
.felt::after {
  content: "";
  position: absolute; inset: 0;
  background: url("../icons/thaaslogofi.PNG") center 58% / 30% auto no-repeat;
  filter: grayscale(1);
  opacity: .06;
  pointer-events: none;
  z-index: -1;
}
.felt > * { position: relative; }
/* DIGU 1v1's piles-row wrapper (see .piles-row below) sits between .piles
   and .felt now, so this is no longer a direct child — descendant-through-
   the-wrapper selector instead of .felt > .piles. DIGU TEAMS' own .piles
   (nested inside .dh-mid, a completely different container) is untouched,
   it was never matched by this rule to begin with. */
.felt > .piles-row > .piles { margin: auto 0; }
/* invisible by default (portrait: .piles and .status-line render exactly
   as if this wrapper didn't exist, stacked in .felt's own flex column) —
   only becomes a real flex row in DIGU 1v1's landscape layout below, so
   "Stock/Discard" and the status text ("Your turn draw") stop competing
   for the same cramped vertical band. */
.piles-row { display: contents; }

.seat { display: flex; align-items: center; gap: 10px; }
.seat .avatar {
  width: 38px; height: 38px; border-radius: 50%;
  background: var(--ink-3); border: 1px solid var(--ink-line);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-head); font-weight: 700; font-size: .9rem;
  color: var(--teal-hi); flex: none;
  transition: border-color var(--t-med), color var(--t-med), box-shadow var(--t-med);
}
.seat.active .avatar {
  border-color: var(--gold); color: var(--gold-hi);
  animation: turnPulse 1.6s ease-in-out infinite;
}
@keyframes turnPulse {
  0%, 100% { box-shadow: 0 0 0 3px var(--gold-soft); }
  50%      { box-shadow: 0 0 0 6px rgba(217, 165, 33, .22); }
}
.seat .seat-name { font-size: .84rem; font-weight: 600; }
.seat .seat-sub { font-size: .72rem; color: var(--muted); }

.hand-row {
  display: flex; justify-content: center;
  padding: 14px 4px 12px;
  min-height: 108px;
}
.hand-row .pcard { margin-left: calc(var(--cw) * -0.42); touch-action: none; }
.hand-row .pcard:first-child { margin-left: 0; }
@media (min-width: 700px) { .hand-row .pcard { margin-left: calc(var(--cw) * -0.3); } }

/* held-hand bend — --fr/--fy set per card by applyFan(); lifts compose with the curve */
.hand-row.fanned .pcard {
  transform: rotate(var(--fr, 0deg)) translateY(var(--fy, 0px));
  transform-origin: 50% 135%;
}
.hand-row.fanned .pcard.selectable:hover {
  transform: rotate(var(--fr, 0deg)) translateY(calc(var(--fy, 0px) - 9px));
}
.hand-row.fanned .pcard.selected {
  transform: rotate(var(--fr, 0deg)) translateY(calc(var(--fy, 0px) - 13px));
  border-color: var(--teal);
  box-shadow: 0 0 0 2px var(--teal-soft), var(--shadow-1);
}
/* tap-to-arm-then-play (wireArmedPlay() in cards.js) — a first tap lifts
   the card higher and grows it slightly (distinct from .selected's teal
   hover/pick styling above, which stays exactly as it was for its other
   two uses: desktop mouse hover, and BONDI's separate give-multiselect),
   with a light-green neon outline + glow so it clearly reads as "armed,
   tap again to throw." z-index lifted above its fan neighbors — without
   it, a card two positions to the right (later in DOM, painted on top by
   default) would still visually clip the grown/lifted card underneath. */
.pcard.armed { z-index: 5; }
/* box-shadow/border-color for the armed glow itself moved to the very end
   of the file (see the comment there) — Royal and Red & Black each have
   their own .pcard.in-meld / .mg1-3 rules at body[data-theme="x"].in-game
   .pcard.in-meld specificity (4 classes + 1 type), which silently beat a
   plain .hand-row.fanned .pcard.armed rule (4 classes, no type) declared
   here, so an armed card that's also part of a detected meld lost its
   green glow to the theme's gold meld ring. */
.hand-row.fanned .pcard.dragging {
  transform: rotate(0deg) translateY(-16px) scale(1.06);  /* straighten while rearranging */
}

/* manual arrangement (drag & drop) */
.pcard.dragging {
  transform: translateY(-16px) scale(1.06);
  z-index: 30; box-shadow: var(--shadow-2); border-color: var(--teal);
  transition: none; cursor: grabbing;
}

/* drag-to-play */
.pcard.lifting { opacity: .25; }
.drag-ghost {
  position: fixed; left: 0; top: 0; z-index: 120;
  pointer-events: none; transition: none;
  box-shadow: var(--shadow-2);
  border-color: var(--teal);
}
.drop-hot,
.pile.drop-hot .pcard,
.trick-area.drop-hot::after {
  border-color: var(--teal-hi) !important;
}
.pile.drop-hot .pile-lbl { color: var(--teal-hi); }
.trick-area::after {
  content: "";
  position: absolute; inset: 12% 22%;
  border: 1.5px dashed transparent;
  border-radius: 14px;
  transition: border-color var(--t-fast);
  pointer-events: none;
}
.trick-area.drop-hot::after { border-color: var(--teal); }

/* knock decision bubble (drop on discard while ≤10 deadwood) */
.knock-bubble {
  position: fixed; z-index: 130;
  display: flex; gap: 8px;
  background: var(--ink-2); border: 1px solid var(--gold);
  border-radius: var(--r-md); padding: 10px;
  box-shadow: var(--shadow-2);
  animation: bubbleIn 150ms cubic-bezier(.4, 0, .2, 1);
}
@keyframes bubbleIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* opponent fans — sized via --fan-scale so they shrink with the screen */
.felt { --fan-scale: 1; }
.fan { display: flex; justify-content: center; align-items: flex-start; pointer-events: none; }
.fan-arc { padding: 8px 0 14px; }
.fan-arc .pcard { margin-left: calc(var(--cw) * -0.66); transform-origin: 50% 120%; }
.fan-arc .pcard:first-child { margin-left: 0; }
.fan-side { flex-direction: column; align-items: center; padding: 4px 0; flex: none; }
.fan-side .pcard { margin-top: calc(var(--cw) * -1.02); }
.fan-side .pcard:first-child { margin-top: 0; }

/* played-card fly-in — travels from the seat, lands with a settle */
@keyframes flyBottom {
  0%   { transform: translateY(90px)  rotate(4deg)  scale(.9); opacity: .2; }
  75%  { transform: translateY(-4px)  rotate(-1deg) scale(1.03); opacity: 1; }
  100% { transform: none; }
}
@keyframes flyTop {
  0%   { transform: translateY(-90px) rotate(-4deg) scale(.9); opacity: .2; }
  75%  { transform: translateY(4px)   rotate(1deg)  scale(1.03); opacity: 1; }
  100% { transform: none; }
}
@keyframes flyLeft {
  0%   { transform: translateX(-100px) rotate(-5deg) scale(.9); opacity: .2; }
  75%  { transform: translateX(5px)    rotate(1deg)  scale(1.03); opacity: 1; }
  100% { transform: none; }
}
@keyframes flyRight {
  0%   { transform: translateX(100px)  rotate(5deg)  scale(.9); opacity: .2; }
  75%  { transform: translateX(-5px)   rotate(-1deg) scale(1.03); opacity: 1; }
  100% { transform: none; }
}
.fly-bottom { animation: flyBottom 260ms cubic-bezier(.3, 0, .3, 1); }
.fly-top    { animation: flyTop    260ms cubic-bezier(.3, 0, .3, 1); }
.trick-card.fresh.pos-0 .pcard { animation: flyBottom 260ms cubic-bezier(.3, 0, .3, 1); }
.trick-card.fresh.pos-1 .pcard { animation: flyLeft   260ms cubic-bezier(.3, 0, .3, 1); }
.trick-card.fresh.pos-2 .pcard { animation: flyTop    260ms cubic-bezier(.3, 0, .3, 1); }
.trick-card.fresh.pos-3 .pcard { animation: flyRight  260ms cubic-bezier(.3, 0, .3, 1); }

/* freshly drawn card glides into the hand */
@keyframes drawnIn { from { transform: translateY(-46px) rotate(3deg) scale(.92); opacity: .2; } }
.hand-row .pcard.drawn-in { animation: drawnIn 240ms cubic-bezier(.3, 0, .3, 1); }
.hand-row.fanned .pcard.drawn-in {
  animation: drawnIn 240ms cubic-bezier(.3, 0, .3, 1);
}

/* the newest card in a one-at-a-time deal flips face-up as it lands */
@keyframes justDealt {
  0%   { transform: translateY(-60px) rotateY(180deg) scale(.85); opacity: 0; }
  60%  { transform: translateY(4px) rotateY(0deg) scale(1.04); opacity: 1; }
  100% { transform: none; opacity: 1; }
}
.just-dealt { animation: justDealt 280ms cubic-bezier(.25, .65, .3, 1); }

/* trick collection — the four cards sweep to the winner. Was a fast,
   sharp accelerating snap (380ms, cubic-bezier(.5,0,.8,.4)) — switched to
   the same slower, gentler glide DIGU's throw and BONDI's give sweep use,
   since this is DHIHAEH's own version of "the moment everyone watches."
   fx.js's collectTrick() resolves on its own timer sized to match this. */
.trick-card.collect { transition: transform 620ms cubic-bezier(.22, .61, .36, 1), opacity 620ms; opacity: 0; }
.trick-card.collect-0 { transform: translateY(150px)  scale(.5) !important; }
.trick-card.collect-1 { transform: translateX(-190px) scale(.5) !important; }
.trick-card.collect-2 { transform: translateY(-150px) scale(.5) !important; }
.trick-card.collect-3 { transform: translateX(190px)  scale(.5) !important; }
.hand-tools {
  display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  width: 100%; max-width: 720px; margin: 0 auto; padding: 2px 6px 0;
}
.hand-tools .hand-hint { font-size: .7rem; color: var(--muted); letter-spacing: .03em; }
.hand-tools .btn { padding: 3px 12px; font-size: .76rem; }

.piles { --pw: 58px; display: flex; gap: 22px; align-items: center; justify-content: center; padding: 16px 0; }
.piles .pcard, .piles .card-stack { --cw: var(--pw); }
.pile { text-align: center; }
.pile .pile-lbl { font-size: .68rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin-top: 6px; }
.pile .pcard { cursor: pointer; }
.pile.disabled .pcard { cursor: default; }
.pile .pile-slot {
  width: var(--pw); height: calc(var(--pw) * 1.42); border: 1.5px dashed var(--ink-line);
  border-radius: 8px; display: inline-block;
}

/* stock pile once it's genuinely empty — a clear "tap to reshuffle"
   affordance instead of looking like there's nothing there. Only ever
   shown to the current-turn player (see showShuffleUi in
   digu-live.js/digu-teams-live.js), so this always means "you can tap
   this." Styled as a real chunky 3D game button — same raised-glass
   bevel recipe as .btn-gold (inset top sheen + a solid drop "edge" +
   soft shadow) — instead of the old flat dashed outline, with a press
   animation on tap and a pulsing glow ring to draw the eye. */
.pile.pile-shuffle .pile-lbl { color: var(--gold-hi); font-weight: 700; letter-spacing: .06em; }
.pile.pile-shuffle { cursor: pointer; }
.shuffle-slot-ico {
  width: 100%; height: 100%; border-radius: 10px; display: flex; align-items: center; justify-content: center;
  color: #221803; background: linear-gradient(180deg, rgba(250, 210, 96, .96), rgba(168, 121, 8, .96));
  text-shadow: 0 1px 0 rgba(255, 235, 180, .35);
  box-shadow:
    inset 0 1px 0 rgba(255, 245, 210, .45), inset 0 0 0 1px rgba(255, 255, 255, .12),
    0 3px 0 rgba(48, 32, 2, .6), 0 6px 14px rgba(0, 0, 0, .35);
  transition: transform 110ms cubic-bezier(.4,0,.2,1), box-shadow 110ms;
  animation: shufflePulse 1.6s ease-in-out infinite;
}
.pile.pile-shuffle:active .shuffle-slot-ico {
  transform: translateY(2px) scale(.96);
  animation-play-state: paused;
  box-shadow:
    inset 0 1px 0 rgba(255, 245, 210, .45), inset 0 0 0 1px rgba(255, 255, 255, .12),
    0 1px 0 rgba(48, 32, 2, .6), 0 2px 6px rgba(0, 0, 0, .3);
}
@keyframes shufflePulse {
  0%, 100% {
    box-shadow:
      inset 0 1px 0 rgba(255, 245, 210, .45), inset 0 0 0 1px rgba(255, 255, 255, .12),
      0 3px 0 rgba(48, 32, 2, .6), 0 6px 14px rgba(0, 0, 0, .35),
      0 0 0 0 rgba(217, 165, 33, .35);
  }
  50% {
    box-shadow:
      inset 0 1px 0 rgba(255, 245, 210, .45), inset 0 0 0 1px rgba(255, 255, 255, .12),
      0 3px 0 rgba(48, 32, 2, .6), 0 6px 14px rgba(0, 0, 0, .35),
      0 0 0 8px rgba(217, 165, 33, 0);
  }
}

/* GRP-style loose discard spread — the recent throws stay visible, and the
   heap runs BIGGER than the stock (the throw pile is the star) */
.discard-spread {
  position: relative; display: inline-block;
  width: calc(var(--pw) * 1.18); height: calc(var(--pw) * 1.18 * 1.42);
}
.discard-spread .pcard { --cw: calc(var(--pw) * 1.15); }
.discard-spread .pcard.buried { box-shadow: 0 1px 2px rgba(9, 20, 38, .35); }

.action-bar {
  display: flex; gap: 10px; justify-content: center; align-items: center;
  padding: 12px 0 2px; flex-wrap: wrap; min-height: 54px;
}
.status-line {
  text-align: center; font-size: .9rem; color: var(--ivory-dim);
  min-height: 24px; padding: 4px 0;
}
.status-line strong { color: var(--ivory); }
.status-line .deadwood-chip {
  display: inline-block; margin-left: 10px; padding: 2px 10px; border-radius: 999px;
  background: var(--ink-3); border: 1px solid var(--ink-line);
  font-family: var(--font-head); font-size: .8rem; font-variant-numeric: tabular-nums;
}
.status-line .deadwood-chip.knockable { border-color: var(--gold); color: var(--gold-hi); }

.turn-timer {
  font-family: var(--font-head); font-variant-numeric: tabular-nums; font-weight: 700;
  font-size: .95rem; color: var(--ivory-dim);
}
.turn-timer.urgent { color: var(--red); }

/* shared circular countdown (search + ready-check screens) — see
   countdownRingHtml()/updateCountdownRing() in fx.js. Classic's own
   color (teal) lives here as the default; each other theme adds its own
   override right next to its .turn-timer rule further down, matching
   the same per-theme scoping every other in-game element already uses. */
.countdown-ring { position: relative; width: 88px; height: 88px; flex: none; }
.countdown-ring-svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.countdown-ring-track { fill: none; stroke: rgba(255, 255, 255, .08); stroke-width: 7; }
.countdown-ring-progress {
  fill: none; stroke: var(--teal-hi); stroke-width: 7; stroke-linecap: round;
  transition: stroke-dashoffset 1s linear;
  filter: drop-shadow(0 0 5px var(--teal-hi));
}
.countdown-ring-text {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-family: var(--font-head); font-weight: 800; font-size: 1.5rem; color: var(--ivory);
  font-variant-numeric: tabular-nums;
}

/* DHIHAEH header — everything on ONE compact bar:
   title · hukum · Team A · the four 10s · Team B · balance · leave */
.dh-bar {
  display: flex; align-items: center; flex-wrap: nowrap;   /* ONE line, always */
  gap: 8px;
  background: linear-gradient(180deg, var(--ink-3), var(--ink-2));
  border: 1px solid var(--ink-line);
  border-radius: var(--r-lg);
  padding: 6px 12px;
  font-size: .78rem;
  min-width: 0;
  overflow: hidden;   /* unconditional now — was only <420px, letting the
    right cluster's own children (balance chip, timer, Leave) shrink
    below their natural content width on wider-but-still-tight phones,
    which let their own text spill out and visually overlap whatever
    sat next to it, not just clip cleanly at the bar's edge */
  box-shadow: inset 0 1px 0 rgba(217, 165, 33, .12), 0 4px 14px rgba(2, 8, 20, .35);
}
.dh-bar .tt-title {
  font-family: var(--font-head); font-weight: 700; letter-spacing: .05em; font-size: .84rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex-shrink: 1;
}
.dh-bar .trump-chip, .dh-bar .badge { flex: none; }
/* was `flex: none` — a rigid right cluster dumped 100% of any width
   squeeze onto .tt-title alone, which is why real titles/names were
   truncating to "DHASH…"/"DI…" on ordinary phones: not a theme issue,
   the right side just never gave up any room. Letting it shrink too
   (paired with the @media block below, which gives its actual children
   smaller footprints instead of relying on proportional shrink alone)
   is the real fix. */
.dh-bar-right { flex-shrink: 1; min-width: 0; }
.dh-team { color: var(--muted); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dh-team b { font-size: .98rem; margin-left: 3px; font-family: var(--font-head); }
.dh-team.a b { color: var(--teal-hi); }
.dh-team.b b { color: var(--gold-hi); }
.dh-bar .tens-tracker { padding: 0; gap: 4px 5px; flex-shrink: 1; min-width: 0; }
.dh-bar .ten-chip { padding: 1px 8px; font-size: .66rem; flex: none; }
.dh-bar .trump-chip { padding: 1px 10px; font-size: .68rem; }
.dh-bar .badge { font-size: .64rem; }
.dh-bar-right { margin-left: auto; display: flex; align-items: center; gap: 8px; }
/* the right cluster (balance, timer, Leave) never shrinks below its own
   natural content width — .dh-bar-right shrinking as a WHOLE container
   is fine (it just has less room to work with), but letting its
   INDIVIDUAL children shrink let their own text spill out and overlap
   whatever sat next to them instead of just running out of room
   cleanly; .dh-bar's own overflow:hidden above is the real backstop
   now if there's genuinely nowhere left to put everything */
.dh-bar-right > * { flex-shrink: 0; }
.dh-bar .btn-sm { min-height: 28px; padding: 3px 12px; font-size: .74rem; }

/* portrait phones (the width every screenshot came from — the existing
   @media(max-height:540px) block below only tightens the LANDSCAPE case):
   give the bar's own right-side children real breathing room instead of
   letting the title absorb the entire squeeze. */
@media (max-width: 420px) {
  .dh-bar { padding: 5px 10px; gap: 5px; }
  .dh-bar-right { gap: 5px; }
  .dh-bar .balance-chip.mini { padding: 2px 7px; font-size: .72rem; }
  .dh-bar .balance-chip.mini .balance-cur { font-size: .5rem; }
  .dh-bar .turn-timer { font-size: .82rem; }
  .dh-bar .btn-sm { padding: 3px 9px; font-size: .68rem; min-height: 26px; }
  .dh-bar .tt-title { font-size: .76rem; }
  .dh-bar .badge { font-size: .58rem; padding: 1px 7px; }
}
/* the trump chip ("Hukum ♠ Spades") and the right cluster (coin, timer,
   chat, Leave) are BOTH rigid — flex: none / flex-shrink: 0 — with only
   .tt-title able to give up room. On a common ~428px-wide phone (past
   the 420px breakpoint above, so none of its tightening even applies),
   that wasn't enough: the bar's own overflow: hidden silently clipped
   Leave off the right edge instead of shrinking anything. Spelling out
   the suit NAME was the one genuinely droppable chunk of width — the
   suit icon alone already says which suit is trump — so it's hidden
   here, well before content actually has to start disappearing off
   the edge, rather than waiting for the 420px breakpoint. */
@media (max-width: 480px) {
  .dh-bar .trump-suit-name { display: none; }
}

/* DHIHAEH cross layout — three protected zones that can never collide:
   the middle (trick) row keeps a guaranteed minimum, north/south size to
   content, side fans clip inside their own columns.
   Middle row is 1fr — it alone absorbs every pixel of leftover height,
   so dh-south (hand-tools/status-line/hand-row) always sits pinned to
   the very bottom of the felt, at a fixed position, regardless of how
   tall dh-south's own content gets. This used to be auto-sized with the
   whole 3-row block centered instead (a more "comfortable" middle-of-
   felt resting position on tall screens) — reverted because centering
   means ANY height change in dh-south (own hand cards resizing as the
   hand grows/shrinks — see applyFan() in cards.js) reflows the whole
   block and visibly shifts the player's hand up/down mid-match, which
   reads as a glitch. A fixed bottom dock, same as most mobile card
   games, is worth the lost centering. */
.dh-table { display: grid; grid-template-rows: auto minmax(108px, 1fr) auto; gap: 2px; align-content: center; }
/* everything inside the felt (seats, own hand) reads slightly right of
   center — a small, deliberate leftward nudge, requested and confirmed
   against a marked screenshot. .felt's own outer shape/border is
   unaffected (padding never touches the painted background/border box),
   only where its CONTENT sits within that unchanged shape. Total
   left+right stays 32px (matches .felt's own uniform 16px/16px), just
   redistributed — nothing shrinks. Covers DHIHAEH, BONDI's 2/4-player
   layout (both share this exact grid), and DIGU TEAMS (same grid too,
   included deliberately for consistency). */
.dh-table { padding-left: 12px; padding-right: 20px; }
.dh-north, .dh-south { display: flex; justify-content: center; }
.dh-mid {
  display: grid; grid-template-columns: 84px minmax(0, 1fr) 84px;
  align-items: center; gap: 4px;
  min-height: 0;
}
.dh-west, .dh-east {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  max-height: 100%; min-height: 0; overflow: hidden;
}
/* keep the bent side fans inside their own columns, away from the trick */
.dh-west .fan-side { transform: translateX(-4px); }
.dh-east .fan-side { transform: translateX(4px); }
/* side seats: compact vertical chips, card count shown by the fan itself */
.dh-west .seat, .dh-east .seat { flex-direction: column; text-align: center; gap: 4px; }
.dh-west .seat .seat-sub, .dh-east .seat .seat-sub { display: none; }
/* DIGU TEAMS reuses DHIHAEH's dh-mid grid, but its middle column also
   holds the stock+discard piles (DHIHAEH's doesn't) — the shared 84px
   side columns left too little width for both piles' labels, which then
   visually collided into each other ("STO" / "RD" overlapping — actually
   two separate labels smearing together, not a data bug). Narrower side
   columns and a tighter pile width give the piles room to breathe,
   scoped to DIGU TEAMS only so DHIHAEH's own layout is untouched. */
.dt-table.dh-table .dh-mid { grid-template-columns: 62px minmax(0, 1fr) 62px; }
.dt-table .piles { --pw: 48px; gap: 14px; }
.dt-table .pile-lbl { white-space: nowrap; }
.trick-area {
  position: relative;
  height: 100%; min-height: 100px; max-height: 230px;
  display: flex; align-items: center; justify-content: center;
}
.bondi-dead-pile {
  position: absolute; left: 6px; bottom: 6px;
  opacity: .85; pointer-events: none;
}
.bondi-dead-pile .pcard, .bondi-dead-pile .card-stack { --cw: 40px; }
/* 3/5/6/7-player Bondi — 2 and 4 keep the original dh-table cross layout;
   this is a real top/left/right seating instead of a fixed 4-position
   grid, since there's no clean way to force 5-7 seats into that shape.
   Reflows between portrait and landscape via seatSides() re-rendering on
   an orientation-change listener (bondi.js) — the side counts differ per
   player count/orientation per the confirmed spec. */
.bondi-table-n { display: flex; flex-direction: column; }
/* same small leftward content nudge as .dh-table above (BONDI's 2/4-
   player layout) — this is its 3/5/6/7-player counterpart, kept in
   sync so every BONDI seat count gets the identical adjustment. */
.bondi-table-n { padding-left: 12px; padding-right: 20px; }
.bondi-table-n .trick-area { flex: 1; }
.bondi-side-row {
  display: flex; flex-wrap: nowrap; justify-content: center; align-items: flex-start;
  gap: 4px 16px; padding: 4px 0; min-height: 0;
}
.bondi-side-row:empty { display: none; }
.bondi-mid-row { display: flex; align-items: stretch; flex: 1; min-height: 0; }
.bondi-mid-row .trick-area { flex: 1; }
.bondi-side-col {
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  gap: 10px; padding: 4px 2px; flex: none;
}
.bondi-side-col:empty { display: none; }
.bondi-opp-seat { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.bondi-opp-seat .seat { flex-direction: column; text-align: center; gap: 4px; }
.bondi-opp-seat .seat .seat-sub { display: none; }
.trick-card-scatter { transition: transform var(--t-fast); }

/* 13-card DHIHAEH hand: sized so the full fan ALWAYS fits the felt width.
   DIGU TEAMS shares this dh-table cross layout too, but only ever has
   10/11 cards same as 1v1 DIGU — no reason to inherit DHIHAEH's tighter
   13-card cap just because both use the same grid. The .dt-table rule
   right below (TEAMS' own extra class) overrides back up to the same
   ceiling 1v1 DIGU gets (see the unscoped .hand-row .pcard rule further
   down this file) — same specificity, wins by coming later in source. */
.dh-table .hand-row .pcard { --cw: clamp(34px, 12.4vw, 64px); margin-left: calc(var(--cw) * -0.54); }
.dh-table .hand-row .pcard:first-child { margin-left: 0; }
.dt-table .hand-row .pcard { --cw: clamp(46px, 14vw, 70px); }
.trick-card { position: absolute; }
.trick-card.pos-0 { transform: translateY(38px); }   /* south (you) */
.trick-card.pos-1 { transform: translateX(-56px); }  /* west */
.trick-card.pos-2 { transform: translateY(-38px); }  /* north */
.trick-card.pos-3 { transform: translateX(56px); }   /* east */
/* real weight/depth as each card lands in the trick — a card resting on
   the table, not a flat sprite floating in place */
.trick-card .pcard { box-shadow: 0 3px 0 rgba(9, 20, 38, .3), 0 7px 12px rgba(9, 20, 38, .4); }

.tens-tracker {
  /* 2 per row, not one long line — a captured chip is wider than an
     uncaptured one ("10 + suit icon" vs just "10"), so 4 chips in a
     single row could overlap/crowd once some are captured. grid (not
     flex-wrap) guarantees exactly 2 per row regardless of each chip's
     own width, rather than wrapping whenever the row happens to run
     out of space. */
  display: grid; grid-template-columns: repeat(2, auto); gap: 6px 8px;
  justify-content: center; justify-items: center; align-items: center;
  padding: 8px 0 2px; font-size: .78rem; color: var(--muted);
}
.tens-tracker .ten-chip {
  display: inline-flex; align-items: center; gap: 4px;
  border: 1px solid var(--ink-line); border-radius: 999px; padding: 3px 10px;
  font-family: var(--font-head); font-weight: 600;
}
.tens-tracker .ten-chip svg { width: 13px; height: 13px; }
.tens-tracker .ten-chip.team-a { border-color: var(--teal); color: var(--teal-hi); }
.tens-tracker .ten-chip.team-b { border-color: var(--gold); color: var(--gold-hi); }
.trump-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--gold-soft); border: 1px solid var(--gold);
  color: var(--gold-hi); border-radius: 999px; padding: 3px 12px;
  font-size: .78rem; font-weight: 700;
}
.trump-chip svg { width: 14px; height: 14px; }

/* match-card: Gin-Rummy-Plus-style "searching / match found" screen shown
   before an open table activates — our own teal/gold glass treatment,
   not a copy of the reference's blue/tan skin. Shared by renderSearching
   and renderReadyCheck (digu-live.js) so both states feel like one panel. */
.match-card {
  max-width: 340px; margin: 12px auto; padding: 18px 18px 16px; text-align: center;
  border-radius: var(--r-lg);
  /* background/border/backdrop-filter/box-shadow come from the shared
     LIQUID GLASS SURFACES rule further down — this used to be the one
     opaque, non-blurred card in the whole app */
}
.mcard-pills { display: flex; justify-content: center; gap: 6px; flex-wrap: wrap; margin-bottom: 14px; }
.mcard-pill {
  display: inline-flex; align-items: center; gap: 3px; padding: 5px 10px;
  border-radius: 999px; background: rgba(255,255,255,.06); border: 1px solid var(--glass-border);
  font-size: .72rem; font-weight: 700; color: var(--ivory); white-space: nowrap;
}
.mcard-pill.gold { color: var(--gold-hi); background: var(--gold-soft); border-color: rgba(217,165,33,.35); }

/* redesigned search screen (renderMatchmaking in app.js) — real animated
   hero card art instead of a spinning logo, a live "N of M joined" seat
   row, and the shared circular countdown ring instead of plain "23s"
   text. .mm-card just adds a touch more breathing room than the base
   .match-card (its own header/pills/actions are unchanged). */
.mm-card { max-width: 360px; padding-top: 20px; }
.mm-art { display: flex; justify-content: center; margin: 4px 0 2px; }
.mm-art .mc-art { width: 250px; height: 139px; }
.mm-headline {
  font-family: var(--font-head); font-weight: 800; font-size: 1.2rem; color: var(--ivory);
  margin-top: 4px;
}
.mm-seats { display: flex; justify-content: center; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.mm-seat {
  width: 34px; height: 34px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  border: 1.5px solid var(--glass-border); color: var(--muted);
  transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
}
.mm-seat svg { width: 16px; height: 16px; }
.mm-seat.filled {
  border-color: var(--teal-hi); color: var(--teal-hi);
  background: rgba(20, 163, 161, .12); box-shadow: 0 0 8px rgba(20, 163, 161, .35);
}
.mm-seats-label {
  margin-top: 8px; font-size: .78rem; font-weight: 700; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.mm-status-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--glass-border);
}
.mm-status-left { display: flex; align-items: center; gap: 8px; color: var(--ivory); font-weight: 600; }
.mm-status-left svg { flex: none; color: var(--muted); }
.mm-status-text { font-size: .88rem; }
/* Classic default matches .countdown-ring-progress's own teal default —
   the other 3 themes' seat/label accents piggyback on the same overrides
   .turn-timer already gets per theme, see those rules for the exact colors. */
body[data-theme="royal"].in-game .mm-seat.filled, [data-preview-theme="royal"] .mm-seat.filled {
  border-color: #2E8B57; color: #2E8B57; background: rgba(46, 139, 87, .14); box-shadow: 0 0 8px rgba(46, 139, 87, .35);
}
body[data-theme="rednblack"].in-game .mm-seat.filled, [data-preview-theme="rednblack"] .mm-seat.filled {
  border-color: #C43A24; color: #C43A24; background: rgba(196, 58, 36, .14); box-shadow: 0 0 8px rgba(196, 58, 36, .35);
}
body[data-theme="prestige"].in-game .mm-seat.filled, [data-preview-theme="prestige"] .mm-seat.filled {
  border-color: #6D3FC9; color: #B9A3FF; background: rgba(109, 63, 201, .18); box-shadow: 0 0 8px rgba(109, 63, 201, .4);
}

/* redesigned ready-check screen (renderReadyCheck in digu-live.js) — real
   hero art, an avatar-vs-avatar duel with the shared countdown ring as
   the centerpiece, and a readiness badge per side instead of plain text
   chips. .rc-avatar is self-contained (not nested under .match-hud/.seat
   like other avatars) since it renders directly inside .rc-side. */
.rc-card { max-width: 360px; padding-top: 20px; }
.rc-art { display: flex; justify-content: center; margin: 4px 0 2px; }
.rc-art .mc-art { width: 200px; height: 111px; }
.rc-headline {
  font-family: var(--font-head); font-weight: 800; font-size: 1.2rem; color: var(--gold-hi);
  margin-top: 6px;
}
.rc-duel { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 16px; }
.rc-side { display: flex; flex-direction: column; align-items: center; gap: 6px; flex: 1; min-width: 0; }
.rc-avatar {
  width: 54px; height: 54px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-head); font-weight: 700; font-size: 1.2rem;
  background: var(--ink); border: 2px solid var(--glass-border);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.rc-avatar.ready { border-color: var(--teal-hi); box-shadow: 0 0 10px rgba(20, 163, 161, .45); }
.rc-side-name {
  font-weight: 700; font-size: .82rem; color: var(--ivory);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rc-badge {
  padding: 3px 10px; border-radius: 999px; font-size: .72rem; font-weight: 700;
  color: var(--muted); background: rgba(255,255,255,.05); border: 1px solid var(--glass-border);
  white-space: nowrap;
}
.rc-badge.ready { color: var(--teal-hi); background: rgba(20,163,161,.12); border-color: rgba(20,163,161,.35); }
.rc-mid { flex: none; }
.rc-mid .countdown-ring { width: 64px; height: 64px; }
.rc-mid .countdown-ring-text { font-size: 1.05rem; }
body[data-theme="royal"].in-game .rc-avatar.ready, [data-preview-theme="royal"] .rc-avatar.ready {
  border-color: #2E8B57; box-shadow: 0 0 10px rgba(46, 139, 87, .45);
}
body[data-theme="royal"].in-game .rc-badge.ready, [data-preview-theme="royal"] .rc-badge.ready {
  color: #2E8B57; background: rgba(46, 139, 87, .14); border-color: rgba(46, 139, 87, .35);
}
body[data-theme="rednblack"].in-game .rc-avatar.ready, [data-preview-theme="rednblack"] .rc-avatar.ready {
  border-color: #C43A24; box-shadow: 0 0 10px rgba(196, 58, 36, .45);
}
body[data-theme="rednblack"].in-game .rc-badge.ready, [data-preview-theme="rednblack"] .rc-badge.ready {
  color: #C43A24; background: rgba(196, 58, 36, .14); border-color: rgba(196, 58, 36, .35);
}
body[data-theme="prestige"].in-game .rc-avatar.ready, [data-preview-theme="prestige"] .rc-avatar.ready {
  border-color: #6D3FC9; box-shadow: 0 0 10px rgba(109, 63, 201, .5);
}
body[data-theme="prestige"].in-game .rc-badge.ready, [data-preview-theme="prestige"] .rc-badge.ready {
  color: #B9A3FF; background: rgba(109, 63, 201, .18); border-color: rgba(109, 63, 201, .4);
}

.mcard-search { padding: 6px 0 4px; }
.mcard-search-logo {
  width: 92px; height: 92px; margin: 0 auto 10px; position: relative;
  display: flex; align-items: center; justify-content: center;
}
.mcard-search-logo-img {
  width: 100%; height: 100%; object-fit: contain; position: relative;
  animation: mcardZoom 2.2s ease-in-out infinite;
}
@keyframes mcardZoom {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.06); }
}
.mcard-search-text { font-family: var(--font-head); font-weight: 800; font-size: 1.1rem; color: var(--ivory); }
.mcard-search-sub { color: var(--muted); font-size: .78rem; margin-top: 6px; }

/* private room's "waiting" card — same slot renderSearching() shows the
   generic "Searching…" copy in, just swapped for the code to share */
.mcard-room-code { padding: 8px 0 4px; }
.mcard-room-code-label {
  font-size: .68rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 8px;
}
.mcard-room-code-val {
  font-family: var(--font-head); font-weight: 800; font-size: 2.1rem; letter-spacing: .22em;
  color: var(--gold-hi); font-variant-numeric: tabular-nums;
  padding: 10px 8px 10px 18px; /* extra left padding optically re-centers the letter-spacing */
  border-radius: var(--r-md); background: rgba(217,165,33,.1); border: 1px solid rgba(217,165,33,.3);
  margin-bottom: 10px;
}
.mcard-room-code .btn { margin: 0 auto; }

/* fixed 4-seat private room (DHIHAEH/DIGU4/TEAMS) — shows who's already
   in as the code gets used, one row per seat, host's row pinned first */
.mcard-seats { display: grid; gap: 6px; margin: 10px 0 4px; text-align: left; }
.mcard-seat {
  display: flex; align-items: center; gap: 10px; padding: 8px 12px;
  border-radius: var(--r-md); background: rgba(255,255,255,.04); border: 1px solid var(--glass-border);
}
.mcard-seat.filled { background: rgba(20,163,161,.08); border-color: rgba(20,163,161,.3); }
.mcard-seat-avatar {
  width: 30px; height: 30px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-head); font-weight: 800; font-size: .82rem;
  background: rgba(255,255,255,.08); color: var(--ivory); overflow: hidden;
}
.mcard-seat-avatar img { width: 100%; height: 100%; object-fit: cover; }
.mcard-seat-name { font-size: .85rem; font-weight: 700; color: var(--ivory); }
.mcard-seat-name.empty { color: var(--muted); font-weight: 600; }
.mcard-seat-host { font-size: .66rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--gold-hi); margin-left: auto; }

/* ============ ROOM CREATE/JOIN MODALS ============
   Redesigned per a card-forward reference: numbered steps, game boxes
   with real small card art tinted per-game (same --hue MODE_CARDS/Home's
   carousel already use), a live room-summary recap, gold CTA. */
.room-modal-head { position: relative; padding-right: 90px; margin-bottom: 6px; }
.room-modal-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 10px; margin-bottom: 8px;
  background: rgba(139, 92, 246, .18); border: 1px solid rgba(139, 92, 246, .4); color: var(--home-purple);
}
/* pointer-events:none is the real fix (this sits in the same corner as
   .modal-x and is added to the DOM after it, so without this it was
   silently eating the close button's clicks) — shifted further left too
   so it stops visually crowding the X as well, not just functionally. */
.room-modal-art { position: absolute; top: 0; right: 22px; width: 74px; height: 66px; display: block; pointer-events: none; }
.room-modal-head h3 { margin-bottom: 4px; }

.room-step { margin-top: 18px; scroll-margin-top: 14px; }
.room-step-label {
  display: flex; align-items: center; gap: 8px; margin-bottom: 10px;
  font-size: .72rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
}
.room-step-num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 6px; flex: none;
  background: rgba(139, 92, 246, .18); border: 1px solid rgba(139, 92, 246, .4); color: var(--home-purple);
  font-size: .7rem; font-family: var(--font-head);
}

/* game boxes — the SAME art Home's own mode-card carousel uses
   (modeCardArt(): real neon-glow cardEl() cards + the drifting suitField()
   glyphs behind them), zoomed down to fit a small grid tile instead of
   Home's big carousel card — see roomGameBoxArt() in app.js. Background
   gradient and the hue glow ring also literally reuse .mode-card's own
   rules (see those selectors above) rather than a separate flat style,
   so a room box reads as a smaller version of the exact same card, not a
   simplified re-skin. */
.room-game-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.room-game-box {
  position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 10px 12px 12px; border-radius: var(--r-md); text-align: left; cursor: pointer;
  overflow: hidden;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.room-game-box.selected {
  border-color: color-mix(in srgb, var(--hue, var(--home-purple)) 75%, transparent);
  box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--hue, var(--home-purple)) 55%, transparent), var(--glass-edge), var(--glass-shadow);
}
.rgb-check {
  position: absolute; top: 8px; right: 8px; width: 20px; height: 20px; border-radius: 50%; z-index: 1;
  display: none; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--hue, var(--home-purple)) 70%, #1a1030); color: #fff;
}
.room-game-box.selected .rgb-check { display: flex; }
/* zoom shrinks modeCardArt()'s full-size (296x165) layout proportionally
   — cards, suit-field, everything — same technique .mode-card itself
   already uses at the small-phone breakpoint (see that rule), just a
   much smaller factor here since this tile is far narrower than a full
   carousel card. */
/* transform:scale, NOT zoom — zoom is non-standard and rendered
   inconsistently in real testing. The FIRST attempt at this (position:
   absolute + top/left 50% + translate(-50%,-50%)) went invisible instead
   — .rgb-art-wrap is a flex item inside .room-game-box's align-items:
   flex-start column, with no explicit width of its own; an absolutely
   positioned child doesn't count toward a parent's shrink-to-fit width,
   so the wrapper collapsed to ~0 width and clipped everything. Fixed by
   giving the wrapper its own explicit, fully-in-flow size instead of
   depending on an absolutely-positioned child to establish one — .mc-art
   stays in normal flow, scaled down from its top-left corner (no
   percentage-translate centering math to get subtly wrong), and the
   fixed-size wrapper's overflow:hidden just clips the excess. */
.rgb-art-wrap { width: 108px; height: 60px; margin: 0 auto 4px; overflow: hidden; }
.rgb-art-wrap .mc-art {
  width: 296px; height: 165px;
  transform: scale(.365); transform-origin: top left;
}
/* modeCardArt() was built for Home's carousel — roughly one card
   prominently animating at a time. Reused here 6-at-once (Choose Game)
   plus more in Select Rules, all its continuous CSS animations running
   simultaneously was the real cause of the reported stutter/lag, not a
   bug in any one of them. Frozen to the exact same static look already
   defined for prefers-reduced-motion (see each .art-* rule's own
   fallback above) — real cards, real per-game colors, real suit glyphs,
   just not continuously animating six-plus copies of it at once. */
.rgb-art-wrap .art-float { animation: none; }
.rgb-art-wrap .art-cycle { animation: none; opacity: 1; transform: translateX(-50%); }
.rgb-art-wrap .art-meld { animation: none; filter: none; }
.rgb-art-wrap .art-trick { animation: none; opacity: 1; }
.rgb-art-wrap .art-slam { animation: none; filter: none; }
.rgb-art-wrap .art-dump { animation: none; filter: none; }
.rgb-art-wrap .suit-drift { animation: none; opacity: .35; }
.rgb-title { font-family: var(--font-head); font-weight: 800; font-size: .88rem; color: var(--ivory); }
.rgb-sub { font-size: .72rem; color: var(--muted); }
.rgb-players {
  display: inline-flex; align-items: center; gap: 4px; margin-top: 6px;
  font-size: .68rem; font-weight: 700; color: color-mix(in srgb, var(--hue, var(--home-purple)) 70%, var(--muted) 30%);
}

/* rules boxes — deliberately LIGHTER than the game boxes above (plain
   glass background, no big card-art centerpiece), so the two don't read
   as the same repeated component stacked twice. Still hued per MODE, not
   just per game (DIGU's 3 rule variants and DHIHAEH's 2 each get their
   own color, same as Home's carousel — see hueForMode() in app.js), just
   expressed through a small colored icon badge + selected-border accent
   instead of a full gradient card. 2 columns, matching the game grid
   above it. The Join modal's "Side" picker (with the host / against
   them) reuses this same box shell with an explicit purple --hue since
   it has no real mode to color itself off. */
.room-rule-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.room-rule-box {
  position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 10px 12px 12px; border-radius: var(--r-md); text-align: left; cursor: pointer;
  overflow: hidden;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.room-rule-box.selected {
  border-color: color-mix(in srgb, var(--hue, var(--home-purple)) 75%, transparent);
  box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--hue, var(--home-purple)) 55%, transparent), var(--glass-edge), var(--glass-shadow);
}
.rrb-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 9px; margin-bottom: 4px;
  background: color-mix(in srgb, var(--hue, var(--home-purple)) 22%, transparent);
  color: color-mix(in srgb, var(--hue, var(--home-purple)) 75%, #fff 15%);
}

/* BONDI's table-size step — small numbered chips, same selected treatment */
.room-size-row { display: flex; gap: 8px; flex-wrap: wrap; }
.room-size-chip {
  width: 42px; height: 42px; border-radius: var(--r-md);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-head); font-weight: 800; font-size: .95rem; color: var(--ivory);
  background: rgba(255, 255, 255, .03); border: 1px solid var(--glass-border); cursor: pointer;
}
.room-size-chip.selected { border-color: rgba(139, 92, 246, .6); background: rgba(139, 92, 246, .18); color: #fff; }

/* stake cards — vertical coin layout */
.room-tier-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.room-tier-card {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 12px 6px; border-radius: var(--r-md); cursor: pointer;
  background: rgba(255, 255, 255, .03); border: 1px solid var(--glass-border);
}
.room-tier-card.selected { border-color: rgba(217, 165, 33, .55); background: rgba(217, 165, 33, .12); }
.rtc-check {
  position: absolute; top: 6px; right: 6px; width: 16px; height: 16px; border-radius: 50%;
  display: none; align-items: center; justify-content: center; background: var(--gold-hi); color: #221803;
}
.room-tier-card.selected .rtc-check { display: flex; }
.rtc-name { font-size: .68rem; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.room-tier-card.tier-starter .rtc-name { color: color-mix(in srgb, var(--teal-hi) 80%, var(--muted)); }
.room-tier-card.tier-bronze .rtc-name { color: #CD8B4F; }
.room-tier-card.tier-silver .rtc-name { color: #C7CDD6; }
.room-tier-card.tier-gold .rtc-name { color: var(--gold-hi); }
.rtc-amt { font-family: var(--font-head); font-weight: 800; font-size: .82rem; color: var(--ivory); }

/* live recap of every choice above, right before the CTA */
.room-summary {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px;
  margin-top: 18px; padding: 12px 6px; border-radius: var(--r-md);
  background: rgba(255, 255, 255, .03); border: 1px solid var(--glass-border);
}
.rs-item { display: flex; flex-direction: column; align-items: center; gap: 3px; text-align: center; }
.rs-ico { color: var(--home-purple); opacity: .85; }
.rs-label { font-size: .62rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); }
.rs-val { font-family: var(--font-head); font-weight: 800; font-size: .78rem; color: var(--ivory); }

.room-code-note {
  display: flex; align-items: flex-start; gap: 8px; margin-top: 12px;
  font-size: .74rem; color: var(--muted); line-height: 1.4;
}
.room-code-note svg { flex: none; margin-top: 1px; color: var(--home-purple); opacity: .8; }

.room-code-input-wrap { display: flex; justify-content: center; }
.room-code-input {
  width: 100%; max-width: 280px; text-align: center; font-family: var(--font-head);
  font-weight: 800; letter-spacing: .3em; font-size: 1.3rem; text-transform: uppercase;
  padding: 12px 10px 12px 20px; border-radius: var(--r-md);
  background: rgba(255, 255, 255, .04); border: 1px solid var(--glass-border); color: var(--ivory);
}
.room-code-input:focus { outline: none; border-color: var(--home-purple); }

.room-cta { width: 100%; margin-top: 20px; display: flex; align-items: center; justify-content: center; gap: 6px; }

.mcard-actions { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; }
.mcard-actions.row { flex-direction: row; }
.mcard-actions.row .btn { flex: 1; }

/* score sheet / hand-end overlay */
.hand-result { text-align: center; padding: 8px 0; }
/* clamp() + explicit wrap: a long real headline ("UNDERCUT — YOU WIN")
   was overflowing past both edges of .table-sheet's overflow:hidden with
   no ellipsis or wrap at all — this shrinks to fit narrow phones first,
   and can still wrap to a second line as a last resort if it's ever
   still too long at the smallest reasonable size. */
.hand-result .hr-headline {
  font-family: var(--font-head); font-size: clamp(1.15rem, 5.5vw, 1.5rem); font-weight: 800; margin-bottom: 4px;
  overflow-wrap: break-word; word-break: break-word;
}
.hand-result .hr-headline.win { color: var(--teal-hi); }
.hand-result .hr-headline.lose { color: var(--red); }
.hand-result .hr-headline.gold { color: var(--gold-hi); }
.hand-result .hr-sub { color: var(--muted); font-size: .88rem; }

/* each stage of the reveal pops in rather than appearing instantly */
@keyframes hrStageIn {
  0%   { opacity: 0; transform: translateY(10px) scale(.94); }
  100% { opacity: 1; transform: none; }
}
.hr-stage-in { animation: hrStageIn 320ms cubic-bezier(.25, .65, .3, 1); }

/* fireShot() — the winning card(s) streak out with a glowing trail
   before the result panel builds in. Knock gets the small default;
   gin/big gin/digu pass big=true for a bigger, longer burst. */
.fire-shot { display: block; }
.fire-shot .fire-card {
  position: absolute; top: 0; left: 0; transform: translate(-50%, -50%);
  animation: fireCardPop 620ms cubic-bezier(.2, .8, .3, 1) both;
  filter: drop-shadow(0 0 10px rgba(255, 140, 20, .85)) drop-shadow(0 0 22px rgba(255, 80, 20, .55));
}
.fire-shot.big .fire-card {
  animation-duration: 950ms;
  filter: drop-shadow(0 0 16px rgba(255, 180, 40, 1)) drop-shadow(0 0 36px rgba(255, 90, 20, .7));
}
@keyframes fireCardPop {
  0%   { transform: translate(-50%, -50%) scale(.4) rotate(-8deg); opacity: 0; }
  35%  { transform: translate(-50%, -50%) scale(1.15) rotate(3deg); opacity: 1; }
  75%  { transform: translate(-50%, -50%) scale(1.05) rotate(0deg); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(.9) rotate(0deg); opacity: 0; }
}

/* the gin-completing discard as the hero of the crash — the whole
   wrapper is what travels (JS-driven transform), so the card itself
   just needs to be solidly visible for the ride, then punch on landing */
.fire-shot.hero { transition: transform 950ms cubic-bezier(.22, .7, .18, 1); }
.fire-shot.hero .fire-card-hero {
  position: absolute; top: 0; left: 0; transform: translate(-50%, -50%);
  opacity: 1;
  filter: drop-shadow(0 0 18px rgba(255, 185, 45, 1)) drop-shadow(0 0 40px rgba(255, 95, 20, .75));
}
.fire-shot.hero.landed .fire-card-hero { animation: fireHeroImpact 380ms cubic-bezier(.3, .6, .3, 1) both; }
@keyframes fireHeroImpact {
  0%   { transform: translate(-50%, -50%) scale(1.25) rotate(-5deg); }
  45%  { transform: translate(-50%, -50%) scale(.9) rotate(3deg); }
  100% { transform: translate(-50%, -50%) scale(1) rotate(0deg); }
}
.fire-trail {
  position: absolute; left: 0; top: 0; width: 4px; height: 4px;
  transform: translate(-50%, -50%); border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 200, 80, 1), rgba(255, 90, 10, .55) 42%, transparent 72%);
  animation: fireTrailGrow 620ms ease-out both;
}
.fire-trail.big { animation-duration: 950ms; }
@keyframes fireTrailGrow {
  0%   { width: 4px; height: 4px; opacity: 0; }
  32%  { width: 200px; height: 200px; opacity: .55; }
  100% { width: 320px; height: 320px; opacity: 0; }
}

/* the big gin's "crash" impact — two expanding rings timed to land with
   the screen shake in fx.js's fireShot(), giving the card's landing real weight */
.fire-shockwave { position: absolute; left: 0; top: 0; width: 0; height: 0; }
.fire-shockwave i {
  position: absolute; left: 0; top: 0; transform: translate(-50%, -50%);
  width: 10px; height: 10px; border-radius: 50%;
  border: 3px solid rgba(255, 190, 60, .9);
  animation: fireShockRing 620ms cubic-bezier(.2, .7, .3, 1) both;
  animation-delay: 260ms;
}
.fire-shockwave i:nth-child(2) { animation-delay: 320ms; border-color: rgba(255, 120, 30, .7); }
@keyframes fireShockRing {
  0%   { width: 10px; height: 10px; opacity: .9; border-width: 4px; }
  100% { width: 260px; height: 260px; opacity: 0; border-width: 1px; }
}

.meld-strip { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; margin: 12px 0; }
.meld-group { display: flex; }
.meld-group .pcard { --cw: 44px; margin-left: -16px; }
.meld-group .pcard:first-child { margin-left: 0; }

/* animateDeadwoodReveal() — the Gin Rummy Plus deadwood/layoff breakdown */
.dw-host { width: 100%; }
.dw-section + .dw-section { margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(120, 170, 220, .12); }
.dw-row, .dw-row2 { display: flex; gap: 6px; justify-content: center; flex-wrap: wrap; min-height: 44px; margin: 6px 0; }
.dw-total {
  text-align: center; font-size: .82rem; color: var(--muted);
  margin-top: 2px; letter-spacing: .02em;
}
.dw-total .num { font-family: var(--font-head); font-weight: 800; font-size: 1.05rem; color: var(--ivory); }
.dw-laid-note { text-align: center; font-size: .76rem; color: var(--gold-hi); margin-top: 4px; }

/* DIGU TEAMS' pooled team melds — cards from two teammates' hands
   combining into a meld neither had alone, shown in a shared box
   between their two sections (animateTeamPoolCaptures / teamPoolMelds) */
.team-pool-box {
  margin: 14px 0; padding: 10px 12px;
  background: rgba(217, 165, 33, .08);
  border: 1px dashed rgba(217, 165, 33, .45);
  border-radius: var(--r-md);
}
.team-pool-lbl {
  text-align: center; font-size: .74rem; font-weight: 700;
  letter-spacing: .03em; text-transform: uppercase; color: var(--gold-hi);
  margin-bottom: 8px;
}
.team-pool-groups { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }

.score-duel { display: flex; justify-content: center; gap: 40px; padding: 10px 0; }
.score-duel .sd-side { text-align: center; }
.score-duel .sd-name { font-size: .78rem; color: var(--muted); }
.score-duel .sd-pts { font-family: var(--font-head); font-weight: 800; font-size: 1.9rem; font-variant-numeric: tabular-nums; }
.score-duel .sd-pts.you { color: var(--teal-hi); }

/* ==========================================================================
   DESIGN PASS v3 — game-app visual language
   ========================================================================== */

/* section heads get the brand's gold thread */
.section-head h2 {
  display: flex; align-items: center; gap: 10px;
}
.section-head h2::before {
  content: "";
  width: 18px; height: 3px; border-radius: 2px; flex: none;
  background: linear-gradient(90deg, var(--home-purple), rgba(139, 92, 246, .15));
}

/* ===== Home: mode carousel (replaces the old game-grid) ===== */
.mode-filters { display: flex; gap: 8px; overflow-x: auto; padding: 0 2px 14px; margin-top: 14px; scrollbar-width: none; }
.mode-filters::-webkit-scrollbar { display: none; }
.mode-filter-chip {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 15px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--glass-border); background: var(--glass-grad);
  color: var(--ivory-dim); font-size: .82rem; font-weight: 600;
  transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
  -webkit-tap-highlight-color: transparent;
}
.mode-filter-chip svg { flex: none; opacity: .85; }
.mode-filter-chip:hover { border-color: var(--home-purple, var(--gold)); color: var(--ivory); }
.mode-filter-chip.active {
  color: #fff; background: var(--home-grad, var(--gold-hi)); border-color: transparent;
  font-weight: 700;
}
.mode-filter-chip.active svg { opacity: 1; }
.mc-hud-coins {
  display: inline-flex; align-items: center; gap: 6px;
  height: 32px;
  background: var(--glass-grad); border: 1px solid var(--glass-border); border-radius: 999px;
  padding: 0 8px 0 6px; font-family: var(--font-head); color: var(--ivory);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.mc-hud-coins:hover { border-color: var(--home-purple, var(--gold)); }
.mc-hud-coins-text { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.15; }
.mc-hud-coins .num { font-weight: 800; font-size: .82rem; }
.mc-hud-chips-label {
  font-size: .5rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap;
}
/* unread-style notice — a mission finished and is ready to claim */
.mc-hud-dot {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--red); box-shadow: 0 0 0 2px var(--ink-2), 0 0 6px rgba(224, 115, 107, .7);
  animation: mcHudDotPulse 1.8s ease-in-out infinite;
}
@keyframes mcHudDotPulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
.mc-hud-chevron { color: var(--muted); transition: transform var(--t-fast); margin-left: 1px; }
.mc-hud-coins.open .mc-hud-chevron { transform: rotate(180deg); color: var(--gold-hi); }
/* background/border/box-shadow come from the shared liquid-glass
   selector list further down this file (.mc-hud-dropdown is in it) —
   was its own flat --ink-2 panel, made glass to match the rest of the
   app's own liquid-glass surfaces. */
.mc-hud-dropdown {
  border-radius: var(--r-lg);
  padding: 14px; margin: -8px 0 16px;
  animation: hrStageIn 200ms cubic-bezier(.25,.65,.3,1);
}
.lc-missions-toggle {
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: none; border: none; color: var(--ivory); font-weight: 700; font-size: .9rem;
  padding: 0; cursor: pointer; text-align: left;
}
.lc-missions-toggle svg { margin-left: auto; color: var(--muted); transition: transform var(--t-fast); }
.lc-missions-toggle.open svg { transform: rotate(180deg); }

.mode-carousel {
  position: relative;
  display: flex; overflow-x: auto; scroll-snap-type: x mandatory;
  padding: 6px 0 16px; margin: 0 -20px;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.mode-carousel::-webkit-scrollbar { display: none; }
/* peeking neighbour cards used to just get sliced by a hard vertical
   line at the edge of the visible window — if that line landed mid-way
   through the Play Now button or text it read as a stray box, not "more
   card off-screen". These sit on .mode-carousel itself (the scroll
   container), not the scrolled track, so they stay pinned to the visible
   edges regardless of scroll position, blurring+fading whatever card is
   peeking through rather than cutting it off cleanly. */
.mode-carousel::before, .mode-carousel::after {
  content: ""; position: absolute; top: 6px; bottom: 16px; width: 46px; z-index: 2; pointer-events: none;
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
}
.mode-carousel::before {
  left: 0;
  mask-image: linear-gradient(to right, #000 0%, transparent 100%);
  -webkit-mask-image: linear-gradient(to right, #000 0%, transparent 100%);
}
.mode-carousel::after {
  right: 0;
  mask-image: linear-gradient(to left, #000 0%, transparent 100%);
  -webkit-mask-image: linear-gradient(to left, #000 0%, transparent 100%);
}
.mode-track { display: flex; gap: 16px; padding: 0 calc(50% - 148px); }
.mode-card {
  /* wider, more landscape-proportioned card — the old 260px width read
     as "tall and narrow" on phones; this is the same width used at the
     old 640px+ breakpoint, now the mobile-first baseline everywhere
     (phones, tablets, iPads alike), not just wider viewports. */
  scroll-snap-align: center; flex: none; width: 296px; cursor: pointer;
  border-radius: var(--r-lg); overflow: hidden; position: relative;
  /* background/border/backdrop-filter/box-shadow come from the shared
     LIQUID GLASS SURFACES rule further down — one material, not a
     separate simpler glass just for these tiles */
  opacity: .5; transform: scale(.9);
  transition: opacity var(--t-med), transform var(--t-med), box-shadow var(--t-med);
}
.mode-card.focused {
  opacity: 1; transform: scale(1); cursor: default;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .14), var(--shadow-2);
}
/* per-game faded gradient outline, hugging the WHOLE card (not just a
   flat tint) — brighter along the top edge, fading toward the bottom so
   it reads as a soft glow ring rather than a hard-edged rectangle */
.mode-card::after, .room-game-box::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  padding: 1.5px; pointer-events: none;
  background: linear-gradient(165deg,
    color-mix(in srgb, var(--hue, var(--teal-hi)) 90%, transparent) 0%,
    color-mix(in srgb, var(--hue, var(--teal-hi)) 35%, transparent) 45%,
    transparent 78%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}
/* .mc-art/.mc-body deliberately carry NO background of their own now —
   two child elements each painting their own gradient is exactly what
   was reading as "two stacked cards" instead of one seamless surface.
   Both are fully transparent so .mode-card's single gradient (set after
   the shared LIQUID GLASS SURFACES rule, further down) shows through
   uninterrupted from the top of the art zone to the bottom of the text. */
/* cards float close to the bottom edge on purpose (bleeding toward the
   felt), and each one casts its own drop shadow — overflow:hidden alone
   sliced that shadow off dead at the box edge, reading as a hard line
   right where the art meets the text below. The mask fades the last
   ~18% of the zone to transparent, so whatever's crossing that edge
   (card + shadow) dissolves smoothly instead of stopping abruptly.
   overflow:hidden still fully contains anything past the box itself. */
.mc-art {
  position: relative; height: 165px; overflow: hidden; flex: none;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 82%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 82%, transparent 100%);
}
.mc-art .gt-cards .pcard { box-shadow: 0 6px 18px rgba(3, 10, 22, .5); }
/* Every game's Home-tile showcase cards — the exact same neon-glass
   PATTERN as the Home hero's .pcard.neon-card cluster (same liquid-
   glass fill trick, same split rank/suit corner marks built by the
   shared splitCornerMarks() in app.js, same real-inline-path center
   pip instead of a <use> ref), but hue-driven off the SAME --hue each
   .mode-card already sets for its own background (see .mode-card
   above) instead of the hero's fixed blue/purple — each of the 9
   game/variant tiles' cards automatically match their own tile's
   color, including DIGU's 3 variants and DHIHAEH's 2, with zero
   per-variant CSS needed. --hue is inherited straight from the
   ancestor .mode-card, no JS wiring required beyond adding this class.
   A flat --hue stroke (not a gradient, unlike the hero's fixed
   3-stop one) is what makes a single shared rule work for every hue
   — an SVG <linearGradient> can't itself vary per calling instance.
   Scoped to .mc-art .gt-cards, so the Home hero cluster and in-game
   cards are never touched. */
.mc-art .gt-cards .pcard.art-neon {
  background:
    linear-gradient(165deg, rgba(10, 14, 12, .68) 0%, rgba(4, 6, 5, .78) 100%) padding-box,
    linear-gradient(150deg, color-mix(in srgb, var(--hue, var(--teal)) 35%, #050b16) 0%, var(--hue, var(--teal)) 55%, #FFFFFF 100%) border-box;
  border: 1.5px solid transparent;
  box-shadow:
    0 6px 18px rgba(3, 10, 22, .5),
    0 0 4px var(--hue, var(--teal)),
    0 0 10px color-mix(in srgb, var(--hue, var(--teal)) 40%, transparent);
  color: #FFFFFF;
}
.mc-art .gt-cards .pcard.art-neon .pc-corner-rank {
  position: absolute; color: #F5F8F6; font-weight: 400;
  font-family: var(--font-head); letter-spacing: -.02em; line-height: 1;
  font-size: calc(var(--cw) * .30);
  filter: drop-shadow(0 0 4px var(--hue, var(--teal)));
}
.mc-art .gt-cards .pcard.art-neon .pc-corner-suit {
  position: absolute; display: flex; line-height: 0;
  filter: drop-shadow(0 0 4px var(--hue, var(--teal)));
}
.mc-art .gt-cards .pcard.art-neon .pc-corner-suit svg { width: calc(var(--cw) * .22); height: calc(var(--cw) * .22); }
.mc-art .gt-cards .pcard.art-neon .pc-corner-suit svg path {
  fill: none; stroke: var(--hue, var(--teal)); stroke-width: 1;
}
.mc-art .gt-cards .pcard.art-neon .pc-corner-rank-tl { top: 5%; left: 8%; }
.mc-art .gt-cards .pcard.art-neon .pc-corner-suit-tr { top: 5%; right: 8%; }
.mc-art .gt-cards .pcard.art-neon .pc-corner-rank-br { bottom: 5%; right: 8%; transform: rotate(180deg); }
.mc-art .gt-cards .pcard.art-neon .pc-corner-suit-bl { bottom: 5%; left: 8%; transform: rotate(180deg); }
.mc-art .gt-cards .pcard.art-neon .pc-center {
  left: 50%; top: 52%; right: auto; bottom: auto;
  transform: translate(-50%, -50%);
}
.mc-art .gt-cards .pcard.art-neon .pc-center svg path {
  stroke: var(--hue, var(--teal)); stroke-width: .8; fill: #0A0A0C;
}
.mc-art .gt-cards .pcard.art-neon.red .pc-center svg path { fill: #D6303F; }
.mc-art .gt-cards .pcard.art-neon .pc-center svg {
  opacity: 1;
  filter: drop-shadow(0 0 4px #FFFFFF) drop-shadow(0 0 9px rgba(255, 255, 255, .55));
}
.mc-body { padding: 12px 16px 16px; position: relative; }
/* big centered 3D gold/white title — a gradient fill (background-clip:
   text) instead of a flat color, plus a stacked dark-gold bevel shadow
   for the extruded look, same recipe as the DIGU TEAMS poster demo's
   title treatment. Same gold on every card (a shared "brand" element),
   unlike .mc-sub/.mc-fmt-ico/.mc-play-btn which stay keyed to each
   card's own --hue. */
.mc-title {
  font-family: var(--font-head); font-weight: 800; font-size: 1.7rem; letter-spacing: .01em;
  text-align: center; line-height: 1.05;
  background: linear-gradient(180deg, #FFFCF0 0%, #FCE7A6 30%, #E8BB3E 62%, #D9A521 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  text-shadow: 0 1px 0 #A6790F, 0 2px 0 #A6790F, 0 3px 5px rgba(0, 0, 0, .5);
}
.mc-sub {
  font-family: var(--font-head); font-weight: 700; font-size: .8rem; letter-spacing: .08em; text-transform: uppercase;
  text-align: center;
  color: color-mix(in srgb, var(--hue, var(--teal-hi)) 70%, #fff 30%); margin-top: 4px;
}
.mc-fmt {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 8px;
  padding: 4px 10px 4px 8px; border-radius: 999px;
  background: rgba(3, 8, 17, .38); border: 1px solid rgba(255, 255, 255, .1);
  font-size: .76rem; color: var(--ivory-dim);
}
.mc-fmt-ico { display: flex; color: color-mix(in srgb, var(--hue, var(--teal-hi)) 75%, #fff 25%); }
.mc-tag { font-size: .82rem; color: var(--ivory-dim); margin: 8px 0 0; line-height: 1.4; min-height: 2.6em; }
/* extra crispness right at the seam where the photo's darkened tail
   meets .mc-body, on top of the stronger scrim above — plain cards
   (no photo) don't need this, so it's scoped to [data-bg] only.
   .mc-title's own bevel shadow (above) would otherwise get replaced
   outright by this rule (text-shadow lists don't merge across two
   separate rules), so it's repeated here with an extra blur layer
   added rather than swapped out. */
.mode-card[data-bg] .mc-title {
  text-shadow: 0 1px 0 #A6790F, 0 2px 0 #A6790F, 0 3px 5px rgba(0, 0, 0, .5), 0 2px 8px rgba(0, 0, 0, .5);
}
.mode-card[data-bg] .mc-sub,
.mode-card[data-bg] .mc-tag { text-shadow: 0 1px 4px rgba(0, 0, 0, .55); }
.mc-footer { display: flex; margin-top: 12px; }
/* glossy 3D button — same idea as the poster demo's Play Now (a top
   gloss streak + a deep inset shadow at the bottom for a domed look),
   kept on each card's own --hue instead of the demo's fixed purple, so
   every game keeps its own matched color, not one color app-wide. */
.mc-play-btn {
  position: relative; overflow: hidden;
  justify-content: center; width: 100%; padding: 13px 18px; margin-top: 0;
  border-radius: 14px; font-size: 1.02rem;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--hue, var(--gold-hi)) 55%, #fff 45%) 0%,
    color-mix(in srgb, var(--hue, var(--gold-hi)) 92%, #000 8%) 55%,
    color-mix(in srgb, var(--hue, var(--gold-hi)) 78%, #000 22%) 100%);
  color: #fff;
  box-shadow:
    inset 0 1.5px 0 rgba(255, 255, 255, .5), inset 0 -4px 8px rgba(0, 0, 0, .28),
    0 10px 22px color-mix(in srgb, var(--hue, var(--gold-hi)) 40%, transparent),
    0 3px 10px rgba(0, 0, 0, .4);
  text-shadow: 0 1px 2px rgba(0, 0, 0, .4);
}
.mc-play-btn::after {
  content: ""; position: absolute; left: 6%; right: 6%; top: 9%; height: 32%;
  border-radius: 999px; background: linear-gradient(180deg, rgba(255, 255, 255, .45), transparent);
  pointer-events: none;
}
@media (hover: hover) {
  .mc-play-btn:hover:not(:disabled) { filter: brightness(1.08); }
}

.mc-badge {
  position: absolute; top: 12px; left: 12px; right: auto; z-index: 2;
  width: 32px; height: 32px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--hue, var(--gold-hi)) 32%, rgba(6, 14, 28, .78));
  border: 1px solid color-mix(in srgb, var(--hue, var(--gold-hi)) 65%, transparent);
  color: color-mix(in srgb, var(--hue, var(--gold-hi)) 55%, #fff 45%);
  box-shadow: 0 3px 10px rgba(0, 0, 0, .4);
}

.mode-dock { display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; margin-bottom: 24px; }
.mode-dock[hidden] { display: none; margin: 0; }
.mode-dock .btn { min-width: 200px; }

/* landscape: a wider peek with more neighbours visible either side,
   matching the reference's wide-carousel feel — gated to phones/tablets
   actually rotated, not just any wide viewport */
@media (orientation: landscape) and (min-width: 760px) {
  .mode-track { padding: 0 calc(50% - 190px); gap: 20px; }
  .mode-card { width: 380px; }
  .mc-art { height: 170px; }
}
/* smaller phones — the LAST attempt at this resized .mode-card's width
   and .mc-art's height independently, which is exactly what threw the
   DHIHAEH card fan's percentage-based positions out of whack against
   their new smaller box (visible overlap/clipping) and shifted every
   other proportion in the card too, since text/padding sizes didn't
   scale down to match. `zoom` scales the ENTIRE card — art, text,
   padding, badge, button, everything — by one factor uniformly, so the
   layout is pixel-for-pixel identical to full size, just smaller. */
@media (max-width: 480px) {
  .mode-track { padding: 0 calc(50% - 122px); }
  .mode-card { zoom: 0.82; }
}

/* game tiles: felt art zone built from real cards */
.game-tile { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
.gt-art {
  position: relative; height: 128px; overflow: hidden; flex: none;
  background:
    radial-gradient(ellipse at 50% 120%, rgba(23, 181, 179, .18), transparent 60%),
    radial-gradient(ellipse at 50% 30%, #12456F 0%, #0C2B50 70%, #0A2240 100%);
  border-bottom: 1px solid var(--ink-line);
}
.gt-art::after { /* soft vignette so the cards sit IN the scene */
  content: ""; position: absolute; inset: 0;
  box-shadow: inset 0 -26px 34px rgba(7, 18, 36, .55);
  pointer-events: none;
}
.gt-cards { position: absolute; inset: 0; }
.gt-cards .pcard { box-shadow: 0 6px 18px rgba(3, 10, 22, .5); }
.game-tile:hover .gt-cards { transform: translateY(-2px); }
.gt-cards { transition: transform var(--t-med); }
.gt-body { padding: 16px 20px 20px; display: flex; flex-direction: column; gap: 2px; }
.gt-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.gt-head h2 { letter-spacing: .08em; margin: 0; }
.gt-fmt {
  font-size: .72rem; font-weight: 700; letter-spacing: .06em;
  color: var(--teal-hi); text-transform: uppercase; white-space: nowrap;
}
.game-tile .gc-tag { margin: 6px 0 2px; }
.game-tile .gc-actions { margin-top: 12px; }
.game-tile .gc-actions .btn { flex: 1; }
.game-tile .gc-actions .btn svg { margin-right: 2px; }

/* featured event banner (lobby) */
.event-banner {
  display: flex; align-items: center; gap: 14px;
  margin-top: 16px; padding: 13px 18px;
  border-radius: var(--r-lg);
  background: linear-gradient(100deg, rgba(217, 165, 33, .14), rgba(14, 45, 83, .6) 55%);
  border: 1px solid rgba(217, 165, 33, .45);
  color: var(--ivory);
  transition: border-color var(--t-fast), transform var(--t-fast);
}
.event-banner:hover { text-decoration: none; border-color: var(--gold-hi); }
.eb-ico {
  width: 38px; height: 38px; border-radius: 12px; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: var(--gold-soft); color: var(--gold-hi);
}
.eb-ico svg { width: 19px; height: 19px; }
.eb-main { display: flex; flex-direction: column; min-width: 0; }
.eb-name { font-family: var(--font-head); font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.eb-sub { font-size: .76rem; color: var(--muted); }
.eb-right { margin-left: auto; text-align: right; display: flex; flex-direction: column; }
.eb-pool { font-weight: 700; font-size: 1.02rem; }
.eb-count { font-size: .74rem; color: var(--muted); }

/* private-room promo banner (Home, signed-in only) — a real card now
   (roughly the old hero section's own height), not a slim strip: text
   anchored bottom-left, a real cardEl() cluster (heroGlowCards(), same
   neon-glass cards/animation the splash screen uses) floating in its
   own slot on the right — same visual language as everywhere else in
   the app instead of a one-off decoration.
   background/box-shadow/backdrop-filter/sheen come from the shared
   LIQUID GLASS SURFACES rule further down (same material as every other
   card in the app) — this rule only overrides its background/border with
   a purple tint afterward (same technique .mode-card uses for its own
   per-game hue), everything else stays the shared glass recipe. */
.room-banner {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; justify-content: flex-end;
  width: 100%; min-height: 148px;
  margin-top: 12px; padding: 15px 18px;
  border-radius: var(--r-lg);
  color: var(--ivory); text-align: left;
  font-family: inherit;
  transition: border-color var(--t-fast);
}
@media (max-width: 480px) { .room-banner { min-height: 132px; } }
@media (min-width: 720px) { .room-banner { min-height: 182px; } }
.room-banner:hover { border-color: var(--home-purple); }
/* shorter than 100% now — the button row below .rb-main lives in this
   same right-hand zone (see .rb-actions' own comment), so the art stops
   short of the card's bottom edge instead of running underneath it. */
.rb-cards-slot {
  position: absolute; top: 0; right: 0; width: 56%; height: calc(100% - 44px);
  pointer-events: none;
}
.rb-ico {
  position: relative; z-index: 1;
  width: 34px; height: 34px; border-radius: 11px; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: rgba(139, 92, 246, .22); color: var(--home-purple);
  margin-bottom: 8px;
}
.rb-main { position: relative; z-index: 1; display: flex; flex-direction: column; max-width: 60%; }
.rb-name { font-family: var(--font-head); font-weight: 800; font-size: 1.18rem; text-shadow: 0 1px 4px rgba(0, 0, 0, .55); }
.rb-sub { font-size: .8rem; color: var(--muted); margin-top: 4px; line-height: 1.35; text-shadow: 0 1px 4px rgba(0, 0, 0, .55); }
/* two real buttons now, instead of the whole card being one click that
   opened a Host/Join chooser modal — same two destinations
   (openCreateRoomModal/openJoinRoomModal), just reached directly. A
   sibling of .rb-main (NOT nested inside it), so it isn't squeezed by
   .rb-main's own 60% width cap — that cap exists to keep the NAME/SUB
   text clear of the card art on the right, but the buttons sit lower,
   below where the art actually is, so they can use the full width and
   stay side by side in one row instead of wrapping to two. */
.rb-actions { position: relative; z-index: 1; display: flex; gap: 8px; margin-top: 10px; width: 100%; }
.rb-actions .btn { flex: 1; white-space: nowrap; }
/* both buttons pick up the banner's own purple/blue accent instead of
   the app-wide gold/ghost convention, so they read as part of THIS
   card, not a generic action bar — Host a touch bolder (brighter tint,
   the more common/primary action) than Join, which stays darker, so
   the two are unmistakably primary vs. secondary at a glance. Real
   liquid glass now (blur+saturate over the room banner's own photo,
   same specular sheen streak the rest of the app's glass surfaces
   use) instead of a flat solid fill, so they read as part of the same
   glass material as everything else instead of two opaque tiles. */
.rb-btn-host, .rb-btn-join {
  position: relative; overflow: hidden;
  backdrop-filter: blur(14px) saturate(1.4);
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  box-shadow: var(--glass-edge);
}
.rb-btn-host::before, .rb-btn-join::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: linear-gradient(115deg, transparent 28%, rgba(255, 255, 255, .22) 46%, transparent 62%);
}
.rb-btn-host {
  background: linear-gradient(135deg, rgba(148, 105, 251, .58), rgba(59, 130, 246, .48));
  color: #fff; border-color: rgba(255, 255, 255, .28);
}
.rb-btn-host:hover { filter: brightness(1.1); }
.rb-btn-join {
  background: linear-gradient(135deg, rgba(30, 20, 68, .58), rgba(18, 11, 44, .5));
  color: var(--ivory);
  border-color: rgba(139, 92, 246, .4);
}
.rb-btn-join:hover { background: linear-gradient(135deg, rgba(40, 28, 90, .64), rgba(24, 15, 58, .56)); border-color: var(--home-purple); }
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .rb-btn-host { background: linear-gradient(135deg, #6D3FC9, #3B8FC4); }
  .rb-btn-join { background: #1B1240; }
}
/* the room banner's own 3 cards — tinted purple/blue to match the
   banner's own background (see .room-banner above) instead of the
   generic dark-navy fill every other heroGlowCards() consumer (splash,
   formerly Home's own hero) uses, so they read as part of the same card
   material rather than a separate dark rectangle sitting on top of it.
   Only the interior (padding-box) fill changes — border gradient/glow/
   text color stay the shared neon-card recipe. */
.rb-cards-slot .pcard.neon-card {
  background:
    linear-gradient(165deg, rgba(96, 61, 196, .55) 0%, rgba(12, 20, 48, .82) 100%) padding-box,
    linear-gradient(150deg, #16214F 0%, #6D3FC9 52%, #FFFFFF 100%) border-box;
}

/* record chips */
.stat-chips { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
.stat-chip {
  display: flex; align-items: center; gap: 12px;
  background: var(--ink-2); border: 1px solid var(--ink-line);
  border-radius: var(--r-lg); padding: 12px 14px;
}
.sc-ico {
  width: 34px; height: 34px; border-radius: 10px; flex: none;
  display: flex; align-items: center; justify-content: center;
}
.sc-ico svg { width: 17px; height: 17px; }
.sc-ico.teal { background: var(--teal-soft); color: var(--teal-hi); }
.sc-ico.gold { background: var(--gold-soft); color: var(--gold-hi); }
.sc-val { font-family: var(--font-head); font-weight: 700; font-size: 1.05rem; line-height: 1.1; }
.sc-lbl { font-size: .7rem; color: var(--muted); }
.legal-line { font-size: .74rem; color: var(--muted); max-width: 640px; }

/* the balance chip exists only when there is a real balance to show */
#balanceChip { display: none; }
body.demo-local #balanceChip, body.auth-in #balanceChip { display: inline-flex; }

/* staff/superadmin are back-office-only accounts — the whole player chrome
   (nav, wallet, coin pill) never applies to them; the admin screen has its
   own self-contained header (.bo-head) instead. */
body.role-staff .topbar, body.role-superadmin .topbar,
body.role-staff .tabbar, body.role-superadmin .tabbar { display: none; }
.team-row { align-items: center; gap: 10px; }
.team-row .k { flex: 1; }

/* back office (§8) — a control room, not a lobby */
.bo-head {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 12px;
  margin-top: 6px; flex-wrap: wrap;
}
.bo-head .side { color: var(--muted); font-size: .78rem; }
.bo-tabs {
  display: flex; gap: 6px; margin-top: 16px;
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  border-bottom: 1px solid var(--ink-line); padding-bottom: 0;
}
.bo-tab {
  flex: none; padding: 9px 18px; min-height: 40px;
  background: none; border: none; border-bottom: 2px solid transparent;
  color: var(--muted); font-family: var(--font-head); font-weight: 700; font-size: .82rem;
  letter-spacing: .04em; cursor: pointer;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.bo-tab:hover { color: var(--ivory); }
.bo-tab.sel { color: var(--gold-hi); border-bottom-color: var(--gold); }
.bo-host { margin-top: 4px; }
.bo-stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px; margin-top: 16px;
}
.bo-stat {
  background: var(--ink-2); border: 1px solid var(--ink-line); border-radius: var(--r-lg);
  padding: 14px 16px; text-align: left; color: inherit; font: inherit;
}
button.bo-stat { cursor: pointer; transition: border-color var(--t-fast); }
button.bo-stat:hover { border-color: var(--teal); }
.bo-stat.hot { border-color: rgba(217, 165, 33, .6); }
.bo-stat .val { font-family: var(--font-head); font-weight: 800; font-size: 1.35rem; }
.bo-stat .val.gold { color: var(--gold-hi); }
.bo-stat .lbl { color: var(--muted); font-size: .72rem; margin-top: 2px; letter-spacing: .04em; text-transform: uppercase; }

/* DHASHUNDHAMA — two stacks, a pile, and the money */
.ds-table { display: flex; flex-direction: column; gap: 10px; padding: 14px; }
.ds-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.ds-opp { justify-content: space-between; }
.ds-you { margin-top: auto; }
.ds-center { flex: 1; display: flex; align-items: center; justify-content: center; min-height: 120px; }
.ds-pile, .ds-stack {
  position: relative; width: 92px; min-height: 150px; flex: none;
  display: flex; align-items: center; justify-content: center;
}
.ds-pile .pcard, .ds-stack .pcard, .ds-pile .card-stack, .ds-stack .card-stack { --cw: 64px; }
.ds-stack { cursor: pointer; border-radius: 12px; }
/* the opponent's stack sits directly in .ds-row.ds-opp, not inside
   .ds-pile/.ds-stack — its own smaller --cw, same reasoning as above.
   Two IDs: #dslOppStack (live matches) and #dsOppStack (bot table). */
#dslOppStack .pcard, #dslOppStack .card-stack,
#dsOppStack .pcard, #dsOppStack .card-stack { --cw: 40px; }
.ds-stack.ready { box-shadow: 0 0 0 2px var(--gold), 0 0 24px rgba(217, 165, 33, .35); }
/* the label always anchors below the card art — never fights it for the
   center of the box, whether the art is absolutely-positioned pile cards
   or a normal-flow stack icon */
.ds-pile .pile-label, .ds-stack .pile-label {
  position: absolute; left: 50%; bottom: 2px;
  transform: translateX(-50%);
  width: max-content; max-width: 130px;
  text-align: center; white-space: nowrap;
  font-size: .68rem; letter-spacing: .03em; color: var(--muted); text-transform: uppercase;
}
.ds-money { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; min-width: 0; }
.ds-coin {
  width: 26px; height: 26px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-head); font-weight: 800; font-size: .68rem;
  border: 1px solid var(--gold); background: rgba(217, 165, 33, .16); color: var(--gold-hi);
}
.ds-coin.red { color: #E4676B; }
.ds-money-count { margin-left: 6px; color: var(--muted); font-size: .72rem; }
/* a single scrollable row, not a wrapping one — .felt clips overflow
   in-game (body.in-game .felt { overflow: hidden }), so a wrapped
   multi-line button row could get its lower lines clipped off entirely
   with no way to reach them. Buttons keep their natural width
   (flex:none) so they can't get squashed trying to fit. */
.ds-buy {
  display: flex; gap: 8px; flex-wrap: nowrap; margin-top: 8px;
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  padding-bottom: 2px; /* clearance so the scrollbar/edge doesn't sit flush under the buttons */
}
.ds-buy .btn { flex: none; }
body.in-game .ds-table { padding: 10px 14px; }

/* promo vouchers — free entry tickets */
.voucher-row { display: flex; gap: 12px; flex-wrap: wrap; }
.voucher {
  min-width: 150px; padding: 12px 16px; border-radius: 14px;
  background: linear-gradient(140deg, rgba(217, 165, 33, .16), rgba(10, 27, 51, .7));
  border: 1px dashed rgba(217, 165, 33, .55);
}
.voucher .v-val { font-family: var(--font-head); font-weight: 800; font-size: 1.15rem; color: var(--gold-hi); }
.voucher .v-name { font-size: .78rem; color: var(--ivory-dim); margin-top: 2px; }
.voucher .v-exp { font-size: .68rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin-top: 6px; font-weight: 700; }
.voucher.dead { opacity: .45; border-style: solid; filter: grayscale(.6); }

/* avatars can carry a photo anywhere */
.avatar { overflow: hidden; }
.avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; display: block; }

/* profile hero card — a bit more presence than a plain .panel, since
   this is the first thing the page shows */
#pfCard {
  background: linear-gradient(160deg, var(--ink-3) 0%, var(--ink-2) 100%);
  border: 1px solid var(--ink-line);
  box-shadow: inset 0 1px 0 rgba(217, 165, 33, .1);
}

/* profile photo button */
.pf-avatar {
  position: relative; width: 64px; height: 64px; flex: none;
  border-radius: 50%; border: 2px solid var(--gold-hi);
  background: var(--ink-3); color: var(--ivory);
  font-family: var(--font-head); font-weight: 800; font-size: 1.4rem;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; overflow: hidden; padding: 0;
  box-shadow: 0 0 12px rgba(217, 165, 33, .25);
}
.pf-avatar img { width: 100%; height: 100%; object-fit: cover; }
.pf-avatar-edit {
  position: absolute; right: 0; bottom: 0;
  width: 22px; height: 22px; border-radius: 50% 0 0 0;
  background: var(--gold); color: #201503;
  font-size: .72rem; display: flex; align-items: center; justify-content: center;
}

/* auth chip + onboarding */
.auth-chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 3px 12px 3px 4px; border-radius: 999px;
  background: var(--ink-2); border: 1px solid var(--ink-line);
  color: var(--ivory); cursor: pointer;
  transition: border-color var(--t-fast);
}
.auth-chip:hover { border-color: var(--teal); }
.auth-chip .avatar { width: 26px; height: 26px; font-size: .7rem; }
.auth-name { font-weight: 600; font-size: .82rem; max-width: 90px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.phone-row { display: flex; align-items: stretch; }
.phone-cc {
  display: flex; align-items: center; padding: 0 12px;
  background: var(--ink-3); border: 1px solid var(--ink-line); border-right: none;
  border-radius: 10px 0 0 10px; color: var(--muted); font-weight: 600; font-size: .88rem;
}
.phone-row input { border-radius: 0 10px 10px 0 !important; letter-spacing: .12em; }
.ob-note { margin: 6px 0 0; font-size: .74rem; color: var(--gold-hi); }
.ob-err { min-height: 1.1em; font-size: .8rem; color: #E4676B; }
@media (max-width: 480px) { .auth-name { display: none; } .auth-chip { padding: 3px 4px; } }

/* game filter chips (tournaments) */
.filter-chips { display: flex; gap: 8px; margin-top: 14px; }
.fchip {
  padding: 7px 18px; border-radius: 999px; min-height: 34px;
  display: inline-flex; align-items: center;
  border: 1px solid var(--ink-line); background: rgba(19, 54, 98, .3);
  color: var(--ivory-dim); font-weight: 700; font-size: .8rem;
  font-family: var(--font-head); letter-spacing: .04em;
  transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
}
.fchip:hover { text-decoration: none; border-color: var(--teal); color: var(--ivory); }
.fchip.sel { background: var(--teal-soft); border-color: var(--teal); color: #fff; }

/* drifting suit pips — born deep in the felt, floating toward the viewer */
.suit-field { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.suit-drift {
  position: absolute; display: block;
  opacity: 0;
  animation: suitThrough 9s linear infinite;
}
.suit-drift svg { width: 100%; height: 100%; display: block; }
.suit-drift.red { color: var(--suit-red); }
.suit-drift.black {
  color: #10161F;                                          /* card black… */
  filter: drop-shadow(0 0 1px rgba(242, 239, 233, .5));    /* …with a fine ivory rim so it reads on dark felt */
}
/* mode-cards + the Private Room banner: outline-only glyphs (see
   suitField()'s outline param) tinted off the card's own --hue instead
   of real red/black suit colors — falls back to the room banner's own
   purple when --hue isn't set (mode-cards always set it, the banner
   never does). */
.suit-drift.hue { color: var(--hue, var(--home-purple)); }
@keyframes suitThrough {
  0%   { transform: scale(.3)  translateY(14px) rotate(-10deg); opacity: 0; }
  25%  { opacity: .9; }
  60%  { opacity: .75; }
  100% { transform: scale(1.7) translateY(-16px) rotate(8deg);  opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .suit-drift { animation: none; opacity: .35; } }
.tourney-card > .suit-field { border-radius: inherit; }
.tourney-card > .suit-field .suit-drift { animation-name: suitThroughSoft; }
@keyframes suitThroughSoft {
  0%   { transform: scale(.3)  translateY(12px) rotate(-10deg); opacity: 0; }
  25%  { opacity: .5; }
  60%  { opacity: .4; }
  100% { transform: scale(1.6) translateY(-14px) rotate(8deg);  opacity: 0; }
}

/* breathing float for the showcase card art (home tiles + tournament tiles).
   The base fan pose lives in --fl so the float layers on top of it. */
.art-float { animation: artFloat 3.8s ease-in-out infinite; will-change: transform; }
@keyframes artFloat {
  0%, 100% { transform: var(--fl) translateY(0); }
  50%      { transform: var(--fl) translateY(-5px) rotate(1.2deg); }
}
@media (prefers-reduced-motion: reduce) { .art-float { animation: none; } }

/* splash screen's card cluster entrance (heroGlowCards(scale, true) —
   see app.js's own comment on that function for why this is a plain
   keyframe applied via an inline `animation` shorthand rather than a
   class: it has to run ALONGSIDE artFloat, staggered to finish right
   as that takes over, and two animation-bearing classes on one element
   don't combine, the second just replaces the first's shorthand. Each
   card scales up from small or a real "dealt in" moment rather than
   just fading, then settles to its own resting pose (var(--fl)) —
   artFloat picks up smoothly from exactly that same resting transform. */
@keyframes neonCardEntrance {
  0%   { opacity: 0; transform: var(--fl) scale(.4) translateY(20px); }
  65%  { opacity: 1; transform: var(--fl) scale(1.07) translateY(-4px); }
  100% { opacity: 1; transform: var(--fl) scale(1) translateY(0); }
}

/* DHASHUNDHAMA's card-art tile: courts rise up from the face-down stack
   one at a time, hold, then sink back — a continuous loop echoing the
   real flip-and-draw mechanic instead of a static fan */
.art-cycle { animation: artCycle 4.8s ease-in-out infinite; will-change: transform, opacity; }
@keyframes artCycle {
  0%   { transform: translateX(-50%) translateY(46px) scale(.72) rotate(-4deg); opacity: 0; }
  6%   { opacity: 1; }
  18%  { transform: translateX(-50%) translateY(-4px) scale(1) rotate(0deg); opacity: 1; }
  75%  { transform: translateX(-50%) translateY(-4px) scale(1) rotate(0deg); opacity: 1; }
  90%  { opacity: 0; }
  100% { transform: translateX(-50%) translateY(-30px) scale(.85) rotate(3deg); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .art-cycle { animation: none; opacity: 1; transform: translateX(-50%); } }

/* DIGU / DIGU TEAMS / DIGU 4's card-art tile: the four aces hold their
   fanned pose, then ripple a green meld-glow across the row left to
   right and settle — echoes the real "melded 3+ of a rank" moment
   instead of a plain float. filter: drop-shadow (not box-shadow) so it
   layers on top of .art-neon's own permanent hue glow instead of
   replacing it. Staggered by a short 120ms/card delay (vs. art-float's
   wide 420ms) so the ripple reads as one connected sweep. */
.art-meld { animation: artMeld 3.6s ease-in-out infinite; will-change: transform, filter; }
@keyframes artMeld {
  0%, 100% { transform: var(--fl); filter: drop-shadow(0 0 0 transparent); }
  40%      { transform: var(--fl) translateY(-3px); filter: drop-shadow(0 0 0 transparent); }
  55%      { transform: var(--fl) translateY(-3px) scale(1.05); filter: drop-shadow(0 0 6px var(--hue, var(--teal))) drop-shadow(0 0 14px var(--hue, var(--teal))); }
  70%      { transform: var(--fl); filter: drop-shadow(0 0 0 transparent); }
}
@media (prefers-reduced-motion: reduce) { .art-meld { animation: none; filter: none; } }

/* DHIHAEH's card-art tile: the four 10s hold their corner positions,
   converge to the center like cards being played to a trick, fade out
   as if swept up by the winning side, then reset invisibly and fade
   back in — a real trick's play-and-collect motion instead of a plain
   float. --tx/--ty (set per card at the call site) are each card's own
   push toward center, since the four start at different corners. The
   79% keyframe snaps the position back home while opacity is still 0,
   so the reset itself is never visible. */
.art-trick { animation: artTrick 4.2s ease-in-out infinite; will-change: transform, opacity; }
@keyframes artTrick {
  0%, 30%  { transform: var(--fl); opacity: 1; }
  50%, 62% { transform: var(--fl) translate(var(--tx, 0), var(--ty, 0)); opacity: 1; }
  78%      { transform: var(--fl) translate(calc(var(--tx, 0) * 1.3), calc(var(--ty, 0) * 1.3 - 14px)); opacity: 0; }
  79%      { transform: var(--fl); opacity: 0; }
  100%     { transform: var(--fl); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .art-trick { animation: none; opacity: 1; } }

/* BONDI's card-art tile: the lone ace of spades lifts, then slams back
   down past its resting point with a bright flash — the void-card
   "play" moment, the one thing that actually matters in Bondi, instead
   of a plain float. */
.art-slam { animation: artSlam 3s ease-in-out infinite; will-change: transform, filter; }
@keyframes artSlam {
  0%, 100% { transform: var(--fl); filter: drop-shadow(0 0 0 transparent); }
  30%      { transform: var(--fl) translateY(-16px) scale(1.02); filter: drop-shadow(0 0 0 transparent); }
  46%      { transform: var(--fl) translateY(2px) scale(1.08); filter: drop-shadow(0 0 10px var(--hue, var(--teal))) drop-shadow(0 0 20px var(--hue, var(--teal))); }
  58%      { transform: var(--fl); filter: drop-shadow(0 0 0 transparent); }
}
@media (prefers-reduced-motion: reduce) { .art-slam { animation: none; filter: none; } }

/* RANGA BONDI's card-art tile: the 5-card spade run gathers in tight
   like a single stack, then bursts back out to its full fan with a
   glow flash — the "dump a whole suit at once" move, instead of a
   plain float. --gx (set per card at the call site) is each card's own
   pull toward the run's center card. */
.art-dump { animation: artDump 3.8s ease-in-out infinite; will-change: transform, filter; }
@keyframes artDump {
  0%, 38% { transform: var(--fl); filter: drop-shadow(0 0 0 transparent); }
  52%     { transform: var(--fl) translateX(var(--gx, 0)) scale(.88); filter: drop-shadow(0 0 0 transparent); }
  66%     { transform: var(--fl) scale(1.08); filter: drop-shadow(0 0 8px var(--hue, var(--teal))) drop-shadow(0 0 16px var(--hue, var(--teal))); }
  80%, 100% { transform: var(--fl); filter: drop-shadow(0 0 0 transparent); }
}
@media (prefers-reduced-motion: reduce) { .art-dump { animation: none; filter: none; } }

/* mini game-art tile on tournament cards — the home tiles, scaled down */
.tc-art {
  position: relative; width: 58px; height: 58px; flex: none;
  border-radius: 12px; overflow: hidden;
  background:
    radial-gradient(ellipse at 50% 115%, rgba(23, 181, 179, .22), transparent 62%),
    radial-gradient(ellipse at 50% 30%, #12456F 0%, #0B2647 78%);
  border: 1px solid var(--ink-line);
}
.tc-art .pcard { box-shadow: 0 2px 6px rgba(3, 10, 22, .5); }
.tourney-card.featured .tc-art { width: 66px; height: 66px; }
@media (min-width: 760px) {
  /* 5 grid children: art, title, prize-pool stat, starts-in stat, button */
  .tourney-card { grid-template-columns: auto 1.4fr 1fr 1fr auto; }
  /* "My registrations" cards (registrationCard()) only have 4 — no prize-
     pool stat, since that's not the relevant number once you're already
     registered — a leftover empty 5th track otherwise stretched the
     button oddly wide instead of auto-sizing it */
  .tourney-card.tourney-card-reg { grid-template-columns: auto 1.4fr 1fr auto; }
}
@media (max-width: 759px) {
  .tc-art-slot { order: -1; }
  /* the boxes are big on phones — let the four-card fan breathe */
  .tc-art { width: 78px; height: 64px; }
  .tc-art .pcard { --cw: 26px; }
  .tourney-card.featured .tc-art { width: 92px; height: 74px; }
  .tourney-card.featured .tc-art .pcard { --cw: 30px; }
}

/* featured tournament card */
.tourney-card { position: relative; }
.tourney-card .tc-stat .val svg { width: 13px; height: 13px; vertical-align: -1.5px; margin-right: 2px; opacity: .9; }
.tourney-card.featured {
  border-color: rgba(217, 165, 33, .55);
  background: linear-gradient(115deg, rgba(217, 165, 33, .1), var(--ink-2) 45%);
  padding-top: 30px;
}
.tc-flag {
  position: absolute; top: 0; left: 20px;
  display: inline-flex; align-items: center; gap: 5px;
  font-size: .62rem; font-weight: 800; letter-spacing: .14em;
  color: #221803;
  background: linear-gradient(180deg, #EFC44F, #C89210);
  border-radius: 0 0 8px 8px; padding: 3px 10px;
}
.tc-flag svg { width: 11px; height: 11px; }

/* wallet ledger icons */
.led-cell { display: inline-flex; align-items: center; gap: 9px; }
.led-ico {
  width: 26px; height: 26px; border-radius: 8px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(139, 155, 180, .12); color: var(--muted);
}
.led-ico svg { width: 14px; height: 14px; }
.led-ico.in  { background: rgba(46, 139, 87, .16); color: #5DC08A; }
.led-ico.out { background: rgba(193, 68, 60, .13); color: #E0736B; }

/* modal close */
.modal { position: relative; }
.modal-x {
  /* z-index, not just relying on decorative art elsewhere in a modal's
     header staying out of its way via pointer-events/positioning — this
     is the one button every modal absolutely cannot afford to have
     silently blocked, so it always wins the stacking order outright,
     regardless of what else gets added to a header later. */
  position: absolute; top: 12px; right: 12px; z-index: 5;
  width: 32px; height: 32px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(19, 54, 98, .5); border: 1px solid var(--ink-line);
  color: var(--muted); cursor: pointer;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.modal-x:hover { color: var(--ivory); border-color: var(--teal); }
@media (max-width: 640px) { .modal-x { top: 16px; } }

/* match HUD (DIGU) — replaces the plain score strip */
.match-hud {
  display: grid; grid-template-columns: 1fr minmax(0, auto) 1fr;
  align-items: center; gap: 10px;
  background: linear-gradient(180deg, var(--ink-3), var(--ink-2));
  border: 1px solid var(--ink-line); border-radius: var(--r-lg);
  padding: 6px 14px;
  overflow: hidden;
  box-shadow: inset 0 1px 0 rgba(217, 165, 33, .12), 0 4px 14px rgba(2, 8, 20, .35);
}
.mh-side { display: flex; align-items: center; gap: 10px; min-width: 0; }
.mh-side > div { min-width: 0; }
.mh-side.right { justify-content: flex-end; text-align: right; }
.mh-name { font-size: .72rem; font-weight: 600; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mh-pts { font-family: var(--font-head); font-weight: 800; font-size: 1.55rem; line-height: 1.05; }
.mh-pts.you { color: var(--teal-hi); }
/* coin stake line — its own smaller size so the coin icon never crowds the avatar next to it */
.mh-stake {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--font-head); font-weight: 800; font-size: 1.05rem; line-height: 1.2;
}
.mh-stake.you { color: var(--teal-hi); }
.mh-side.right .mh-stake { flex-direction: row-reverse; }
.mh-mid { display: flex; flex-direction: column; align-items: center; gap: 1px; min-width: 0; }
/* 2x2 grid, matching the base .tens-tracker rule — was single-row here
   specifically because match-hud is short on vertical room, but a
   single row also risks overlap once chips vary in width (captured vs
   still-in-play). Revisit spacing here specifically if this reads as
   cramped in practice. */
.mh-mid .tens-tracker { padding: 0; gap: 3px 4px; }
.mh-mid .ten-chip { padding: 1px 7px; font-size: .66rem; white-space: nowrap; }
.mh-vs {
  font-family: var(--font-head); font-weight: 800; font-size: .78rem;
  letter-spacing: .2em; color: var(--gold-hi);
}
.mh-target { font-size: .62rem; color: var(--muted); text-transform: uppercase; letter-spacing: .08em; white-space: nowrap; }
.match-hud .avatar {
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-head); font-weight: 700; font-size: .82rem;
  background: var(--ink); border: 1px solid var(--ink-line);
}

/* team identity rings on avatars (both games) */
.avatar.team-a { color: var(--teal-hi); box-shadow: 0 0 0 2px rgba(20, 163, 161, .35); }
.avatar.team-b { color: var(--gold-hi); box-shadow: 0 0 0 2px rgba(217, 165, 33, .3); }

/* DIGU 4's own match-hud: 4 individual players, not 2 sides — overrides
   the base .match-hud's 3-column team grid with a simple wrapping row of
   compact score chips (name + running score) plus a target/payout
   subtitle underneath, instead of the "You vs Opponent" 2-side layout
   every other match-hud game uses. */
.d4-hud { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 8px 14px; }
.d4-chips { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.d4-chip { display: flex; align-items: center; gap: 6px; min-width: 0; }
.d4-chip .avatar { width: 26px; height: 26px; font-size: .68rem; }
.d4-chip-name { font-size: .68rem; font-weight: 600; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 72px; }
.d4-chip.you .d4-chip-name { color: var(--ivory); }
.d4-chip-pts { font-family: var(--font-head); font-weight: 800; font-size: 1.05rem; line-height: 1.1; }
.d4-chip.you .d4-chip-pts { color: var(--teal-hi); }
.d4-target { font-size: .62rem; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; white-space: nowrap; }
.mh-name.searching { color: var(--gold-hi); }
.avatar.searching-avatar { animation: searchPulse 1.4s ease-in-out infinite; }
@keyframes searchPulse { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }
.seat.active .avatar.team-a, .seat.active .avatar.team-b { color: var(--gold-hi); }

/* ---------- in-game messaging: turn pill · thinking dots · coach hint ---------- */
.turn-pill {
  display: inline-block; padding: 1px 10px; margin-right: 6px;
  border: 1px solid var(--gold); border-radius: 999px;
  color: var(--gold-hi); background: var(--gold-soft);
  font-family: var(--font-head); font-weight: 800;
  font-size: .64rem; letter-spacing: .14em; text-transform: uppercase;
  vertical-align: 1px;
}
.status-line svg { width: 13px; height: 13px; vertical-align: -2px; }
.thinking { color: var(--muted); }
.dots::after {
  display: inline-block; content: "";
  animation: dotty 1.2s steps(1) infinite;
}
@keyframes dotty {
  0%  { content: ""; }
  25% { content: "."; }
  50% { content: ".."; }
  75% { content: "..."; }
}
.coach-tip {
  position: fixed; bottom: calc(var(--tabbar-h) + var(--safe-bottom) + var(--tabbar-gap) + 42px); left: 50%; transform: translate(-50%, 8px);
  z-index: 145; pointer-events: none;
  background: var(--ink-3); border: 1px solid var(--teal);
  border-radius: 999px; padding: 8px 18px;
  font-size: .8rem; font-weight: 600; color: var(--ivory);
  box-shadow: var(--shadow-2);
  opacity: 0; transition: opacity 250ms, transform 250ms;
  white-space: nowrap; max-width: 92vw; overflow: hidden; text-overflow: ellipsis;
}
.coach-tip.show { opacity: 1; transform: translate(-50%, 0); }

/* DHIHAEH trick-winner chip */
.win-chip {
  z-index: 5; pointer-events: none;
  padding: 3px 12px; border-radius: 999px;
  font-family: var(--font-head); font-weight: 700; font-size: .7rem;
  white-space: nowrap;
  background: var(--ink-3); border: 1px solid var(--teal);
  color: var(--teal-hi);
  box-shadow: 0 4px 14px rgba(2, 8, 20, .5);
  animation: bubbleIn 180ms cubic-bezier(.4, 0, .2, 1);
}
.win-chip.team-b { border-color: var(--gold); color: var(--gold-hi); }

/* ---------- bracket v2: match pair boxes, swipeable rounds ---------- */
.bracket-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 12px;
}
.bracket-head h2 { margin: 0; font-size: 1.15rem; min-width: 0; }
.bracket-head .bracket-dismiss { margin-left: auto; }
.bracket-swim {
  display: flex; gap: 16px;
  overflow-x: auto; scroll-snap-type: x proximity;
  padding: 2px 2px 10px;
  -webkit-overflow-scrolling: touch;
}
.round { min-width: 218px; max-width: 250px; flex: none; scroll-snap-align: start; }
.round-h {
  margin: 0 0 8px; text-align: center;
  font-size: .68rem; font-weight: 800; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted);
}
.round.current .round-h { color: var(--gold-hi); }
.round-matches { display: flex; flex-direction: column; gap: 10px; }
.bmatch {
  background: var(--ink); border: 1px solid var(--ink-line);
  border-radius: 12px; overflow: hidden; font-size: .84rem;
}
.bmatch.yours { border-color: rgba(217, 165, 33, .55); box-shadow: 0 0 0 1px rgba(217, 165, 33, .18); }
.bm-side {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 12px; color: var(--ivory-dim); min-width: 0;
}
.bm-side .bm-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bm-side.w { color: var(--ivory); background: var(--teal-soft); font-weight: 600; }
.bm-side.me { color: var(--gold-hi); font-weight: 700; }
.bm-mark {
  flex: none; width: 18px; height: 18px; border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-head); font-weight: 800; font-size: .64rem;
  background: var(--teal); color: #fff;
}
.bm-side:not(.w) .bm-mark { visibility: hidden; }
.bm-div { height: 1px; background: var(--ink-line); }

/* ---------- wallet history on phones: rows become stacked cards ---------- */
@media (max-width: 560px) {
  .ledger-table thead { display: none; }
  .ledger-table tr {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    gap: 1px 12px; padding: 9px 0;
    border-bottom: 1px solid rgba(30, 67, 116, .5);
  }
  .ledger-table tr:last-child { border-bottom: none; }
  .ledger-table td { border: none !important; padding: 0 !important; min-width: 0; }
  .ledger-table td:first-child { grid-column: 1 / -1; order: 3; font-size: .68rem !important; }
  .ledger-table td:nth-child(2) { order: 1; }
  .ledger-table td.r { order: 2; align-self: center; }
  .led-cell { max-width: 100%; }
  .led-cell + * { min-width: 0; }
}
/* wide tables inside panels scroll instead of overflowing the page */
.panel { min-width: 0; }
.table-scroll { overflow-x: auto; }

/* ==========================================================================
   FX LAYER — celebrations, confetti, trump reveal (see js/fx.js)
   Everything here is pointer-events:none and self-removing.
   ========================================================================== */
#fxHost { position: fixed; inset: 0; z-index: 140; pointer-events: none; }

.fx-banner {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; text-align: center;
  opacity: 0; transition: opacity 220ms cubic-bezier(.4,0,.2,1);
}
.fx-banner.show { opacity: 1; }
.fx-banner .fx-title {
  font-family: var(--font-head); font-weight: 800;
  font-size: clamp(2.4rem, 9vw, 4.2rem); letter-spacing: .06em;
  transform: scale(.6); transition: transform 260ms cubic-bezier(.2, .9, .3, 1.2);
}
.fx-banner.show .fx-title { transform: scale(1); }
.fx-banner .fx-sub {
  font-size: .95rem; font-weight: 600; color: var(--ivory);
  text-shadow: 0 1px 8px rgba(2, 8, 20, .8);
}
.fx-banner.tone-gold .fx-title { color: var(--gold-hi); text-shadow: 0 0 34px rgba(217,165,33,.55), 0 2px 0 rgba(60,40,0,.6); }
.fx-banner.tone-teal .fx-title { color: var(--teal-hi); text-shadow: 0 0 34px rgba(20,163,161,.55), 0 2px 0 rgba(0,30,30,.6); }
.fx-banner.tone-red  .fx-title { color: #E0736B;        text-shadow: 0 0 30px rgba(193,68,60,.5),  0 2px 0 rgba(40,5,0,.6); }
.fx-banner .fx-rays {
  position: absolute; width: 130vmax; height: 130vmax;
  background: conic-gradient(from 0deg,
    transparent 0deg 14deg,  rgba(217, 165, 33, .05) 14deg 22deg,
    transparent 22deg 42deg, rgba(217, 165, 33, .05) 42deg 50deg,
    transparent 50deg 74deg, rgba(217, 165, 33, .05) 74deg 82deg,
    transparent 82deg 104deg, rgba(217, 165, 33, .05) 104deg 112deg,
    transparent 112deg 138deg, rgba(217, 165, 33, .05) 138deg 146deg,
    transparent 146deg 168deg, rgba(217, 165, 33, .05) 168deg 176deg,
    transparent 176deg 360deg);
  animation: raysSpin 7s linear infinite;
  opacity: 0; transition: opacity 300ms;
}
.fx-banner.show .fx-rays { opacity: 1; }
.fx-banner.tone-red .fx-rays { display: none; }
@keyframes raysSpin { to { transform: rotate(360deg); } }

.fx-confetti { position: absolute; inset: 0; overflow: hidden; }
.fx-confetti i {
  position: absolute; top: -4vh; display: block;
  animation: confettiFall 1.4s cubic-bezier(.3, 0, .8, .6) forwards;
}
@keyframes confettiFall {
  0%   { transform: translate(0, 0) rotate(0); opacity: 1; }
  100% { transform: translate(var(--dx, 0), 108vh) rotate(var(--rot, 360deg)); opacity: .9; }
}

/* slam style — KNOCK / UNDERCUT smash down */
.fx-banner.slam .fx-rays { display: none; }
.fx-banner.slam .fx-title {
  transform: none;
  animation: slamIn 340ms cubic-bezier(.25, .9, .3, 1) backwards;
}
@keyframes slamIn {
  0%   { transform: scale(2.6); opacity: 0; }
  55%  { transform: scale(.94); opacity: 1; }
  100% { transform: scale(1); }
}

/* KNOCK table thump */
.fx-knock { position: absolute; }
.fx-knock i {
  position: absolute; left: 0; top: 0;
  width: 20px; height: 20px;
  transform: translate(-50%, -50%);
  border: 3px solid rgba(242, 239, 233, .85);
  border-radius: 50%;
  animation: knockRipple 700ms ease-out forwards;
}
.fx-knock i:nth-child(2) { animation-delay: 130ms; border-color: rgba(20, 163, 161, .75); }
@keyframes knockRipple {
  to { width: 230px; height: 230px; opacity: 0; border-width: 1px; }
}
body.fx-shake .app-frame { animation: tableShake 420ms cubic-bezier(.36, .07, .19, .97); }
@keyframes tableShake {
  0%, 100% { transform: none; }
  20% { transform: translate(-5px, 2px); }
  40% { transform: translate(4px, -2px); }
  60% { transform: translate(-3px, 1px); }
  80% { transform: translate(2px, -1px); }
}

/* GIN super sequence */
.fx-gin {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; text-align: center;
  background: rgba(4, 12, 26, .68);
  opacity: 0; transition: opacity 220ms;
  overflow: hidden;
}
.fx-gin.show { opacity: 1; }
.fx-gin .fx-rays { opacity: 1; }
.fx-gin .fx-sub {
  font-size: 1.05rem; font-weight: 700; color: var(--ivory);
  text-shadow: 0 1px 8px rgba(2, 8, 20, .8);
  animation: ginSub 400ms cubic-bezier(.3, 0, .2, 1) backwards;
}
@keyframes ginSub { from { opacity: 0; transform: translateY(14px); } }
.fx-gin-cards { display: flex; position: relative; }
.fx-gin-cards .pcard {
  margin-left: -15px;
  animation: ginCard 520ms cubic-bezier(.2, .9, .3, 1.15) backwards;
}
.fx-gin-cards .pcard:first-child { margin-left: 0; }
@keyframes ginCard {
  from { transform: translateY(70px) rotate(9deg) scale(.7); opacity: 0; }
}
.fx-gin-word { position: relative; line-height: 1; }
.fx-gin-word span {
  display: inline-block;
  font-family: var(--font-head); font-weight: 800;
  font-size: clamp(3.4rem, 17vw, 6.4rem);
  letter-spacing: .04em;
  color: var(--gold-hi);
  text-shadow: 0 0 40px rgba(217, 165, 33, .65), 0 3px 0 rgba(60, 40, 0, .65);
  animation: letterDrop 480ms cubic-bezier(.3, 1.25, .4, 1) backwards;
}
.fx-gin-word span.sp { width: .4em; }
@keyframes letterDrop {
  from { transform: translateY(-110px) scale(1.7); opacity: 0; }
}

/* DHIHAEH: 10 captured */
.fx-ten {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(4, 12, 26, .52);
  opacity: 0; transition: opacity 180ms;
}
.fx-ten.show { opacity: 1; }
.fx-ten-inner {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 6px;
  transform: scale(.72); transition: transform 240ms cubic-bezier(.2, .9, .3, 1.25);
}
.fx-ten.show .fx-ten-inner { transform: scale(1); }
.fx-ten-cards { display: flex; gap: 8px; filter: drop-shadow(0 8px 22px rgba(0, 0, 0, .55)); }
.fx-ten-ring {
  position: absolute; top: 26px; width: 120px; height: 120px; border-radius: 50%;
  border: 2px solid rgba(20, 163, 161, .75);
  animation: trumpRing 1s ease-out infinite;
}
.fx-ten.theirs .fx-ten-ring { border-color: rgba(193, 68, 60, .7); }
.fx-ten-label {
  font-family: var(--font-head); font-weight: 800; font-size: 1.35rem;
  letter-spacing: .18em; margin-top: 8px;
  color: var(--teal-hi); text-shadow: 0 0 22px rgba(20, 163, 161, .55);
}
.fx-ten.theirs .fx-ten-label { color: #E0736B; text-shadow: 0 0 20px rgba(193, 68, 60, .5); }
.fx-ten-team { font-size: .85rem; font-weight: 600; color: var(--ivory); }

/* BONDI's void-interrupt give — the card(s) handed over when a player
   can't follow suit fly with a small ember flourish (fx.js's
   flyDealtCards({fire:true})), independent of whether the packet shows
   real faces or card-backs (Ranga Bondi hides the given suit from
   bystanders, but they still see the same flourish). */
.fx-fireshot {
  animation: fireshotGlow 320ms ease-out;
}
@keyframes fireshotGlow {
  0%   { filter: drop-shadow(0 0 2px rgba(255, 130, 40, .35)); }
  35%  { filter: drop-shadow(0 0 10px rgba(255, 150, 60, 1)) drop-shadow(0 0 22px rgba(255, 90, 20, .75)); }
  100% { filter: drop-shadow(0 0 3px rgba(255, 120, 40, .25)); }
}
.fx-fireshot-spark {
  position: absolute; left: 50%; top: 50%; width: 5px; height: 5px; border-radius: 50%;
  background: radial-gradient(circle, #fff3d0, #ff8a3d 55%, transparent 75%);
  box-shadow: 0 0 7px 1px rgba(255, 140, 50, .9);
  transform: translate(-50%, -50%);
  animation: fireshotSpark 420ms ease-out forwards;
  pointer-events: none;
}
@keyframes fireshotSpark {
  0%   { transform: translate(-50%, -50%) scale(1); opacity: 1; }
  100% { transform: translate(calc(-50% + var(--sx)), calc(-50% + var(--sy))) scale(0); opacity: 0; }
}

/* tens tracker pulse when a 10 lands */
@keyframes chipPulse {
  0% { box-shadow: 0 0 0 0 rgba(20, 163, 161, .55); }
  100% { box-shadow: 0 0 0 12px rgba(20, 163, 161, 0); }
}
.ten-chip.pulse { animation: chipPulse 900ms ease-out 2; }

/* shuffle + deal ritual */
.fx-deal {
  /* position/left/top/width/height are set inline per-instance (fixed to
     the felt's on-screen rect at fire time) — see dealFlourish() in fx.js */
  z-index: 24;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
  transition: opacity 220ms;
}
.fx-deal.done { opacity: 0; }
/* fx-deal-persist (beginDeal) sits still and visible through the whole
   deal loop instead of auto-vanishing — z-index below flying dealt cards
   (200+, see flyDealtCard) so cards visibly leave the top of the deck */
.fx-deal-persist { z-index: 15; }
.fxd-stack { position: relative; width: 54px; height: 77px; }
.fxd-stack .pcard { position: absolute; left: 0; top: 0; transition: opacity 260ms ease; }
/* the riffle itself (playRiffle in fx.js) — a zero-size anchor point at
   ov's centre; every card inside is absolutely positioned off of it via
   left:50%/top:50% + a negative margin half its own size, so `transform`
   alone drives each card's position throughout the whole cascade */
.riffle-wrap { position: relative; width: 0; height: 0; }
.riffle-wrap .pcard { position: absolute; }
.fxd-fly {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
}
.fxd-fly .pcard { box-shadow: 0 4px 12px rgba(3, 10, 22, .5); }
.fxd-fly.fly-b { animation: fxdB 380ms cubic-bezier(.4, 0, .7, .6) forwards; }
.fxd-fly.fly-t { animation: fxdT 380ms cubic-bezier(.4, 0, .7, .6) forwards; }
.fxd-fly.fly-l { animation: fxdL 380ms cubic-bezier(.4, 0, .7, .6) forwards; }
.fxd-fly.fly-r { animation: fxdR 380ms cubic-bezier(.4, 0, .7, .6) forwards; }
.fxd-fly.fly-d { animation: fxdD 380ms cubic-bezier(.4, 0, .7, .6) forwards; }
@keyframes fxdB { to { transform: translate(-50%, -50%) translateY(46vh)  rotate(7deg);  opacity: 0; } }
@keyframes fxdT { to { transform: translate(-50%, -50%) translateY(-42vh) rotate(-7deg); opacity: 0; } }
@keyframes fxdL { to { transform: translate(-50%, -50%) translateX(-40vw) rotate(-9deg); opacity: 0; } }
@keyframes fxdR { to { transform: translate(-50%, -50%) translateX(40vw)  rotate(9deg);  opacity: 0; } }
@keyframes fxdD { to { transform: translate(-50%, -50%) translate(72px, 26px) rotate(12deg); opacity: 0; } }

/* Cut Hukum trump reveal */
.fx-trump {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(4, 12, 26, .62);
  opacity: 0; transition: opacity 200ms;
}
.fx-trump.show { opacity: 1; }
.fx-trump-inner {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 8px;
  transform: scale(.7); transition: transform 260ms cubic-bezier(.2, .9, .3, 1.25);
}
.fx-trump.show .fx-trump-inner { transform: scale(1); }
.fx-trump-card { position: relative; filter: drop-shadow(0 10px 26px rgba(0, 0, 0, .6)); }
.fx-trump-ring {
  position: absolute; top: 38px; width: 150px; height: 150px; border-radius: 50%;
  border: 2px solid rgba(217, 165, 33, .7);
  animation: trumpRing 1.1s ease-out infinite;
}
@keyframes trumpRing {
  from { transform: scale(.7); opacity: .9; }
  to   { transform: scale(1.5); opacity: 0; }
}
.fx-trump-label {
  font-family: var(--font-head); font-weight: 800; font-size: 2rem;
  letter-spacing: .3em; color: var(--gold-hi); margin-top: 10px;
  text-shadow: 0 0 26px rgba(217, 165, 33, .6);
}
.fx-trump-suit {
  display: flex; align-items: center; gap: 8px;
  font-weight: 600; color: var(--ivory);
}
.fx-trump-suit svg { width: 18px; height: 18px; color: var(--gold-hi); }

/* ==========================================================================
   STICKER REACTIONS — no in-game chat, so this is the chat (see js/stickers.js)
   ========================================================================== */

/* floating near the bottom-right of the play area, above the hand —
   used to sit inline in the HUD bar next to the timer/☰ menu, which
   made an already-crowded bar worse on narrow phones, then briefly
   floated vertically centered on the right edge, where the felt's own
   curved oval border swallowed most of it on some layouts. Still
   declared in each game's own .dh-bar-right markup (its DOM position
   doesn't matter once it's fixed), just visually pulled out of that
   flow. Fixed (not absolute) for the same reason the ☰ menu is: nothing
   clips it, and it's theme-agnostic BY CONSTRUCTION — one unscoped
   rule, no body[data-theme="x"] override anywhere touches it, and its
   background/border/blur come from the shared LIQUID GLASS SURFACES
   family (further down this file) same as every other glass control in
   the app, not a one-off color of its own. */
.chat-react-btn {
  position: fixed; right: 14px; z-index: 20;
  /* below the felt's own bottom curve, not overlapping its border/last
     hand card the way a flat bottom:200px did on real devices — sits in
     the reserved safe-area gap outside the table entirely. */
  bottom: calc(10px + env(safe-area-inset-bottom));
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 50%;
  color: var(--ivory-dim);
  cursor: pointer; flex: none;
}
.chat-react-btn svg { width: 21px; height: 21px; }
.chat-react-btn:active { transform: scale(.9); }

/* the ☰ settings menu — replaces every game's old standalone Leave
   button (and, in practice mode, its separate Sound button too) with
   one dropdown: Sound + Leave table. Same 26px circular-icon shape as
   .chat-react-btn just above so the two sit as a matched pair in the
   HUD's right cluster. */
.gm-trigger {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%; border: none;
  background: rgba(255, 255, 255, .08); color: var(--ivory-dim);
  cursor: pointer; flex: none; -webkit-tap-highlight-color: transparent;
}
.gm-trigger:active { transform: scale(.9); }
.gm-trigger.open { background: var(--gold-hi); color: #1A1200; }
/* the dropdown itself is a SINGLETON (see fx.js's ensureMenuDrop()) —
   built once, appended straight to <body>, and reused/repositioned for
   whichever game screen is mounted, never destroyed and recreated. It's
   always present in the DOM; .open toggles it via a plain CSS
   transition, not a @keyframes animation triggered on insert/unhide —
   the earlier version rebuilt a fresh dropdown element from an HTML
   string on every HUD re-render and animated each one in via
   @keyframes, which was confirmed broken on a real device: on Prestige
   specifically (whose render does noticeably more synchronous per-card
   DOM work than the other themes), the trigger correctly went
   gold/open but the panel just never painted — most likely because a
   re-render landed while the menu was open and the brand-new dropdown's
   entrance animation never got scheduled. A transition on a persistent
   element has no equivalent "does the animation replay correctly on a
   new element" failure mode to hit.
   position: fixed (not absolute) — .dh-bar has overflow: hidden for its
   own text-truncation needs, which would otherwise clip this panel since
   it renders below the bar's own box. Fixed escapes that entirely (no
   ancestor between here and the viewport sets a transform/filter that'd
   re-trap it); top/right are set inline by fx.js's positionMenuDrop()
   from the trigger's real position, these are just a same-corner
   fallback for the brief instant before that JS runs. */
.gm-dropdown {
  position: fixed; top: calc(100% + 8px); right: 12px; z-index: 45;
  width: 224px; border-radius: var(--r-lg); padding: 8px;
  opacity: 0; pointer-events: none;
  transform: translateY(6px) scale(.96);
  transition: opacity 160ms, transform 160ms cubic-bezier(.25, .65, .3, 1);
}
.gm-dropdown.open { opacity: 1; pointer-events: auto; transform: none; }
/* section labels + the quick theme-switcher/pack-toggle above the
   original Sound/Leave pair — themes list every theme unconditionally
   for now (see MENU_THEMES's own comment in fx.js: no ownership gate
   exists yet), ready to filter down to "owned only" once one does. */
.gm-section-lbl {
  font-size: .64rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); padding: 8px 10px 4px;
}
.gm-theme-row { display: flex; gap: 8px; padding: 2px 10px 8px; }
.gm-theme-swatch {
  width: 28px; height: 28px; border-radius: 50%; flex: none;
  border: 2px solid transparent; cursor: pointer; padding: 0;
  background: var(--sw, var(--teal)); -webkit-tap-highlight-color: transparent;
  display: flex; align-items: center; justify-content: center;
  transition: border-color var(--t-fast), transform 120ms cubic-bezier(.4,0,.2,1);
}
.gm-theme-swatch:active { transform: scale(.9); }
.gm-theme-swatch.active { border-color: var(--ivory); }
.gm-theme-check { display: none; color: #fff; filter: drop-shadow(0 1px 1px rgba(0,0,0,.5)); }
.gm-theme-swatch.active .gm-theme-check { display: block; }
.gm-divider { height: 1px; background: rgba(255, 255, 255, .08); margin: 4px 6px 6px; }
.gm-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  background: none; border: none; border-radius: 10px;
  padding: 9px 10px; font-size: .85rem; font-weight: 600; color: var(--ivory);
  cursor: pointer; text-align: left; -webkit-tap-highlight-color: transparent;
}
.gm-item:hover, .gm-item:active { background: rgba(255, 255, 255, .08); }
.gm-item + .gm-item { margin-top: 2px; }
.gm-item-ico { flex: none; width: 20px; display: flex; align-items: center; justify-content: center; }
.gm-item-state { margin-left: auto; color: var(--muted); font-size: .76rem; font-weight: 700; }
.gm-item-danger { color: var(--red); }

/* opponent avatars become tap targets once the tray exists — a subtle
   ring on hover/focus is the only visual hint (a poker app's avatar tap
   affordance is expected UI knowledge, not something to over-explain) */
.seat-reactable, .avatar-reactable { cursor: pointer; }

.sticker-tray-backdrop {
  position: fixed; inset: 0; background: rgba(3, 6, 14, .5);
  opacity: 0; pointer-events: none; transition: opacity 160ms;
  z-index: 143;
}
.sticker-tray-backdrop.open { opacity: 1; pointer-events: auto; }
.sticker-tray {
  position: fixed; left: 12px; right: 12px; bottom: 12px;
  max-width: 420px; margin: 0 auto;
  background: linear-gradient(180deg, #16213f, #0c1428);
  border: 1px solid rgba(186, 214, 255, .2);
  border-radius: 20px 20px 16px 16px;
  padding: 14px;
  padding-bottom: calc(14px + var(--safe-bottom));
  transform: translateY(120%);
  transition: transform 220ms cubic-bezier(.3, 0, .2, 1);
  z-index: 144;
  max-height: 60vh; overflow-y: auto;
}
.sticker-tray.open { transform: translateY(0); }
.sticker-tray-title { font-size: .74rem; color: var(--muted); margin-bottom: 10px; text-align: center; }
.sticker-tray-title b { color: var(--gold-hi); }
/* only rendered (display:flex, set in JS) once a player has 2+ packs
   enabled — a single-pack player never sees this row at all */
.sticker-pack-tabs { display: none; gap: 8px; justify-content: center; margin-bottom: 12px; }
.sticker-pack-tabs button {
  border-radius: 999px; border: 1px solid rgba(217, 165, 33, .3);
  background: rgba(217, 165, 33, .08); color: var(--muted);
  font-family: var(--font-head); font-weight: 700; font-size: .74rem;
  padding: 5px 14px; cursor: pointer;
}
.sticker-pack-tabs button.active {
  background: rgba(217, 165, 33, .22); border-color: var(--gold-hi); color: var(--gold-hi);
}
.sticker-tray-section-lbl { font-size: .64rem; text-transform: uppercase; letter-spacing: .06em; color: #6b7688; margin: 10px 0 6px; }
.sticker-tray-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
.sticker-tray-grid button {
  aspect-ratio: 1; border-radius: 12px; border: 1px solid rgba(186, 214, 255, .14);
  background: rgba(186, 214, 255, .06); cursor: pointer;
  display: flex; align-items: center; justify-content: center; padding: 6px;
}
.sticker-tray-grid button:active { transform: scale(.88); background: rgba(217, 165, 33, .18); }
.sticker-tray-grid button svg { width: 100%; height: 100%; }
.sticker-text-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }
.sticker-text-grid button {
  border-radius: 10px; border: 1px solid rgba(217, 165, 33, .35);
  background: rgba(217, 165, 33, .1); cursor: pointer;
  font-family: var(--font-head); font-weight: 800; font-size: .82rem; color: #FFE9A8;
  padding: 10px 6px; letter-spacing: .02em;
}
.sticker-text-grid button:active { transform: scale(.92); }

/* the flying sticker itself + its particle effects — all children of
   stickers.js's own fixed #stickerFxHost, positioned with raw
   getBoundingClientRect() coordinates (no felt-relative offset math) so
   nothing ever gets clipped by a theme's overflow:hidden felt */
#stickerFxHost { position: fixed; inset: 0; z-index: 141; pointer-events: none; }
.sticker-fly { position: absolute; font-size: 34px; will-change: transform, opacity; }
.sticker-fly-text {
  font-family: var(--font-head); font-weight: 800; font-size: 18px;
  color: #FFE9A8; text-shadow: 0 0 6px rgba(217, 165, 33, .8), 0 2px 4px rgba(0, 0, 0, .6);
  white-space: nowrap;
}
.sticker-particle { position: absolute; border-radius: 2px; }
.sticker-flash {
  position: absolute; width: 80px; height: 80px; border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 180, 60, .9), rgba(255, 60, 20, .4) 50%, transparent 72%);
}
.sticker-impact-shake { animation: stickerTableShake 420ms ease-out; }
@keyframes stickerTableShake {
  10%, 90% { transform: translate3d(-1px, 0, 0); }
  20%, 80% { transform: translate3d(2px, 0, 0); }
  30%, 50%, 70% { transform: translate3d(-3px, 0, 0); }
  40%, 60% { transform: translate3d(3px, 0, 0); }
}

/* ==========================================================================
   IN-GAME MODE — the table is a full-screen app view: no page scroll,
   no tab bar, everything fits the viewport exactly (body.in-game set by app.js)
   ========================================================================== */
/* dedicated in-game backdrop, same structure as Royal's own in-game
   background (a radial glow over a near-black base) but navy dark blue
   instead of Royal's red, matching Classic's own palette */
body.in-game {
  height: 100dvh; overflow: hidden;
  background:
    radial-gradient(1200px 460px at 50% -160px, rgba(20, 90, 170, .26), transparent 70%),
    #050B16;
}
body.in-game .tabbar { display: none; }
body.in-game .topbar { display: none; }   /* the table owns the whole screen */
body.in-game .screen-host {
  height: 100dvh;
  padding: calc(8px + env(safe-area-inset-top)) max(12px, env(safe-area-inset-left))
           calc(8px + env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-right));
  /* the table screen is laid out to fit exactly (fan-scale/breakpoints
     shrink content to fit, never grow past it) — locked hard in both
     directions rather than left as an auto scroller, so it can never be
     dragged up/down or side to side no matter what a given theme's
     decorative rail bleed does. */
  overflow: hidden;
  display: flex; flex-direction: column;
}
.balance-chip.mini {
  padding: 3px 10px; font-size: .8rem; pointer-events: none;
}
.balance-chip.mini .balance-cur { font-size: .58rem; }
body.in-game .table-screen { flex: 1; min-height: 0; width: 100%; }
/* NOT overflow:hidden — a screenshot showed the own-hand row's outer
   cards sliced off by the felt's own rounded/oval silhouette wherever a
   wide fanned hand sat close to the bottom edge (the oval narrows most
   right there). border-radius alone already clips the felt's OWN
   background/border into that shape — overflow:hidden was only ever
   clipping CHILD content that happened to extend past it, which is
   exactly the bug. applyFan() (cards.js) already sizes hand-row cards to
   fit the real viewport width, not the felt's own (narrower, curved)
   width, so nothing was ever actually too wide for the SCREEN — it was
   only ever too wide for the felt's decorative shape. #oppHand keeps its
   own separate, deliberate overflow:hidden right below (that one really
   is a "shrink to fit, opponent fan can clip" zone, per this section's
   own header comment). */
body.in-game .felt { min-height: 0; }
body.in-game .table-top { margin-bottom: 10px; }
/* inside the DIGU felt: the hand and controls NEVER shrink or clip —
   decorative zones (opponent fan, piles) give way first */
body.in-game .felt > .hand-row,
body.in-game .felt > .action-bar,
body.in-game .felt > .hand-tools,
body.in-game .felt > .status-line,
body.in-game .felt > .piles-row > .status-line,
body.in-game .felt > .seat { flex: none; }
body.in-game .felt > #oppHand { flex: 0 1 auto; min-height: 0; overflow: hidden; }
body.in-game .felt > .piles,
body.in-game .felt > .piles-row > .piles { flex: 0 1 auto; min-height: 84px; }
/* pin the own-hand row to the felt's bottom edge — same auto-margin
   technique as .piles' own centering above, so hand-row's height
   changing (bigger/smaller cards, see applyFan()) never shifts its
   on-screen position, only grows/shrinks it upward from a fixed floor */
body.in-game .felt > .hand-row { margin-top: auto; }

/* result panels slide up OVER the felt instead of growing the page */
.table-sheet {
  position: absolute; inset: 0; z-index: 26;
  display: flex; align-items: flex-end; justify-content: center;
  background: rgba(5, 14, 28, .62);
  backdrop-filter: blur(3px);
  border-radius: inherit;
  animation: sheetFade 200ms cubic-bezier(.4, 0, .2, 1);
  overflow: hidden;
}
.table-sheet > .panel {
  width: 100%; max-height: 100%; overflow-y: auto;
  margin: 0 !important;
  border-radius: 20px 20px 16px 16px;
  animation: sheetUp 260ms cubic-bezier(.3, 0, .2, 1);
}
@keyframes sheetFade { from { opacity: 0; } }
@keyframes sheetUp { from { transform: translateY(48px); opacity: .4; } }

/* hand cards scale with the screen — small phones fit without overflow.
   applyFan() (cards.js) still shrinks below this ceiling on its own if
   the actual hand size/screen genuinely can't fit it, so raising the
   ceiling here is safe — it only changes how big cards get to be when
   there's room, never removes the overflow guarantee. */
.hand-row .pcard { --cw: clamp(46px, 14vw, 70px); margin-left: calc(var(--cw) * -0.48); }

/* short screens: compress table chrome so everything stays on screen */
@media (max-height: 740px) {
  body.in-game .screen-host { padding-top: 6px; }
  body.in-game .table-top { margin-bottom: 6px; }
  body.in-game .score-duel .sd-pts { font-size: 1.3rem; }
  body.in-game .score-duel { padding: 6px !important; }
  body.in-game .match-hud { padding: 4px 14px; }
  body.in-game .mh-pts { font-size: 1.2rem; }
  body.in-game .match-hud .avatar { width: 28px; height: 28px; font-size: .7rem; }
  body.in-game .hand-tools .hand-hint { display: none; }
  body.in-game .piles { padding: 8px 0; }
  body.in-game .action-bar { min-height: 46px; padding: 6px 0 0; }
  body.in-game .hand-row { min-height: 88px; padding: 8px 4px 10px; }
  body.in-game .fan-arc { padding: 2px 0 6px; }
  body.in-game .felt { --fan-scale: .82; margin-top: 8px !important; }
  body.in-game .trick-area { min-height: 108px; }
  body.in-game .status-line { min-height: 20px; font-size: .82rem; padding: 2px 0; }
}
@media (max-height: 620px) {
  body.in-game .hand-row .pcard { --cw: clamp(42px, 12.5vw, 54px); }
  body.in-game .hand-row { min-height: 72px; }
  body.in-game .table-top .tt-meta { display: none; }
  body.in-game .felt { --fan-scale: .68; }
}

/* landscape phones: rotated view gets its own compact table.
   The header row and score HUD / tens strip merge into ONE slim row,
   so the felt gets nearly the whole height — no stacking, no overlap. */
@media (max-height: 540px) {
  body.in-game .table-screen {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-rows: auto minmax(0, 1fr);
    column-gap: 12px;
  }
  body.in-game .table-screen > .table-top { grid-area: 1 / 1; margin: 0; flex-wrap: nowrap; min-width: 0; }
  body.in-game .table-screen > .match-hud { grid-area: 1 / 2; }
  body.in-game .table-screen > .dh-bar { grid-area: 1 / 1 / 2 / 3; }   /* one full-width line */
  body.in-game .table-screen > .dh-bar.with-hud { grid-area: 1 / 1; }  /* bar left, hud panel right */
  body.in-game .match-hud .ten-chip { padding: 0 6px; font-size: .6rem; }
  .match-hud { margin-top: 8px; }
  body.in-game .table-screen > .match-hud { margin: 0; }
  body.in-game .table-screen > .felt { grid-area: 2 / 1 / 3 / 3; }

  /* landscape table SHAPE (border-radius) lives at the very end of this
     file, not here — it has to be declared after Royal/Red & Black/
     Prestige's own base .felt rules (which also set border-radius, later
     in the file) to actually win the cascade. See that rule's comment
     for the full story. */

  /* DHIHAEH bar: single line, minimal chrome */
  body.in-game .dh-bar {
    flex-wrap: nowrap; overflow: hidden;
    padding: 2px 10px; gap: 4px 9px;
    background: transparent; border: none;
  }
  body.in-game .dh-bar .tt-title { font-size: .72rem; }
  body.in-game .dh-bar .dh-team { font-size: .66rem; }
  body.in-game .dh-bar .dh-team b { font-size: .82rem; }
  body.in-game .dh-bar .ten-chip { padding: 0 6px; font-size: .6rem; }

  body.in-game .felt { --fan-scale: .58; padding: 8px 16px; border-radius: 16px; margin-top: 4px !important; }
  body.in-game .screen-host { padding: 4px max(10px, env(safe-area-inset-left)) calc(4px + env(safe-area-inset-bottom)) max(10px, env(safe-area-inset-right)); }
  body.in-game .table-top { margin-bottom: 0; }
  body.in-game .table-top .tt-meta { display: none; }
  body.in-game .table-top .tt-title { font-size: .8rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

  body.in-game .match-hud { padding: 0 10px; background: transparent; border: none; gap: 8px; }
  body.in-game .mh-pts { font-size: .95rem; }
  body.in-game .mh-target { display: none; }
  body.in-game .mh-name { display: none; }
  body.in-game .match-hud .avatar { width: 24px; height: 24px; font-size: .62rem; }


  /* the opponent's fan is guaranteed space — it can shrink but never vanish */
  body.in-game .felt > #oppHand { min-height: 34px; }
  body.in-game .felt > .seat .avatar { width: 24px; height: 24px; }
  body.in-game .felt > .piles,
  body.in-game .felt > .piles-row > .piles { min-height: 58px; }

  /* DIGU 1v1 landscape: only one opponent and no side seats to offload
     into (unlike DHIHAEH/BONDI/TEAMS' west/east columns below), so the
     generic vertical-stack compaction above still left the pile pair and
     the status line ("Your turn draw") fighting for the same cramped
     middle band and printing on top of each other. Piles and status
     move onto ONE row instead of stacking, spending landscape's spare
     WIDTH instead of its scarce height. */
  body.in-game .dg-table > .piles-row {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
    gap: 10px 18px; flex: 0 1 auto; margin: auto 0;
    min-height: 0; transform: translateY(-10px);
  }
  body.in-game .dg-table > .piles-row > .piles { margin: 0; padding: 0; min-height: 0; gap: 12px; }
  body.in-game .dg-table > .piles-row > .status-line { min-height: 0; padding: 0; }

  /* DHIHAEH landscape: hard-guaranteed trick zone; north clips instead of pushing */
  body.in-game .dh-table { grid-template-rows: auto minmax(118px, 1fr) auto; }
  body.in-game .dh-north { max-height: 60px; overflow: hidden; }
  /* margin-left back to the original -0.42 (portrait's own rule below
     sets -0.54 — more overlap there so cards can grow bigger to stay
     readable at the width portrait actually has). JS's applyFan() always
     overrides this inline anyway (dhHandOverlap() picks the same 0.42
     here), so this only matters for the brief instant before it runs. */
  body.in-game .dh-table .hand-row .pcard { --cw: clamp(36px, 14vh, 54px); margin-left: calc(var(--cw) * -0.42); }
  /* DIGU TEAMS shares dh-table's grid but only has 10/11 cards like 1v1 —
     same ceiling as the unscoped rule below, not DHIHAEH's tighter one.
     Same specificity as the rule above (both 4 classes deep), wins by
     coming later in source. */
  body.in-game .dt-table .hand-row .pcard { --cw: clamp(44px, 16vh, 60px); }
  /* landscape: Sort docks in the table's bottom-right corner, clear of
     fans and hand — nudged up from the very edge (was bottom: 8px) so it
     doesn't crowd the felt's own bottom curve/rail. */
  body.in-game .hand-tools {
    position: absolute; right: 10px; bottom: 18px; z-index: 6;
    width: auto; max-width: none; padding: 0; margin: 0;
  }
  body.in-game .hand-tools .btn { min-height: 24px; padding: 2px 12px; font-size: .68rem; }
  body.in-game .status-line { font-size: .7rem; min-height: 14px; padding: 0; }
  body.in-game .hand-row { min-height: 62px; padding: 2px 4px 10px; }
  body.in-game .hand-row .pcard { --cw: clamp(44px, 16vh, 60px); }
  /* piles nudged up a little too, off the hand-row's fan/status-line
     zone below it, same reasoning as Sort above */
  body.in-game .piles { padding: 2px 0; gap: 16px; min-height: 58px; --pw: 46px; transform: translateY(-10px); }
  body.in-game .pile .pile-lbl { font-size: .56rem; margin-top: 1px; }
  body.in-game .action-bar { min-height: 34px; padding: 2px 0 0; gap: 6px; }
  body.in-game .action-bar .btn { min-height: 30px; padding: 4px 14px; font-size: .78rem; }
  body.in-game .seat .avatar { width: 28px; height: 28px; font-size: .68rem; }
  body.in-game .seat .seat-name { font-size: .7rem; }
  body.in-game .trick-area { min-height: 118px; transform: translateY(-10px); }
  body.in-game .trick-card { margin-top: -12px; }
  body.in-game .trick-card .pcard { --cw: 44px; }
  body.in-game .trick-card.pos-0 { transform: translateY(22px); }
  body.in-game .trick-card.pos-1 { transform: translateX(-38px); }
  body.in-game .trick-card.pos-2 { transform: translateY(-22px); }
  body.in-game .trick-card.pos-3 { transform: translateX(38px); }
  body.in-game .dh-mid { grid-template-columns: 64px minmax(0, 1fr) 64px; }
  body.in-game .btn-ghost.btn-sm { min-height: 30px; padding: 3px 10px; font-size: .74rem; }

  /* BONDI 3/5/6/7-player landscape (2p/4p reuse .dh-table above and are
     already covered). Its top/left/right seating already spends width
     instead of height by design (seatSides() in bondi.js/bondi-live.js
     re-lays the seats out per orientation), but the top row and trick
     zone still had no explicit height ceiling here, unlike DHIHAEH's
     .dh-table above — same hard-guaranteed-trick-zone treatment. */
  body.in-game .bondi-table-n { gap: 2px; }
  body.in-game .bondi-side-row { gap: 2px 10px; padding: 2px 0; max-height: 56px; overflow: hidden; }
  body.in-game .bondi-mid-row { min-height: 118px; }
  body.in-game .bondi-side-col { gap: 6px; padding: 2px; }
  body.in-game .bondi-opp-seat .seat .avatar { width: 24px; height: 24px; font-size: .62rem; }
  body.in-game .bondi-opp-seat .seat-name { font-size: .64rem; }

  /* DHASHUNDHAMA landscape: was completely unhandled before (every other
     game above has its own compact treatment in this block) — its normal
     portrait-sized stack/pile art and money row had nowhere to shrink,
     so the bottom stack label ran off the screen and the money row
     crowded/overflowed toward the right edge. */
  body.in-game .ds-table { gap: 4px; padding: 6px 14px; }
  body.in-game .ds-row { gap: 8px; }
  body.in-game .ds-pile, body.in-game .ds-stack { width: 60px; min-height: 92px; }
  body.in-game .ds-pile .pcard, body.in-game .ds-stack .pcard,
  body.in-game .ds-pile .card-stack, body.in-game .ds-stack .card-stack { --cw: 42px; }
  body.in-game #dslOppStack .pcard, body.in-game #dslOppStack .card-stack,
  body.in-game #dsOppStack .pcard, body.in-game #dsOppStack .card-stack { --cw: 26px; }
  body.in-game .ds-coin { width: 20px; height: 20px; font-size: .58rem; }
  body.in-game .ds-money { gap: 2px; }
  body.in-game .ds-money-count { font-size: .64rem; margin-left: 3px; }
  body.in-game .ds-buy .btn { min-height: 28px; padding: 4px 10px; font-size: .74rem; }
}

/* ==========================================================================
   DEVICE SIZING — phones · tablets · iPads · desktop
   ========================================================================== */
@media (max-width: 480px) {
  .trick-card.pos-1 { transform: translateX(-44px); }
  .trick-card.pos-3 { transform: translateX(44px); }
  h1 { font-size: 1.55rem; }
  .score-duel .sd-pts { font-size: 1.45rem; }
  .action-bar .btn { flex: 1 1 40%; max-width: 220px; }
  .piles { --pw: 52px; }
}
/* tablets & iPads: bigger cards, roomier table */
@media (min-width: 768px) {
  .pcard { --cw: 64px; }
  .hand-row .pcard { --cw: clamp(52px, 7vw, 68px); }
  .felt { padding: 26px; }
  .trick-area { height: 240px; }
  .trick-card.pos-0 { transform: translateY(48px); }
  .trick-card.pos-1 { transform: translateX(-74px); }
  .trick-card.pos-2 { transform: translateY(-48px); }
  .trick-card.pos-3 { transform: translateX(74px); }
  .dh-mid { grid-template-columns: 110px minmax(0, 1fr) 110px; }
  .trick-area { min-height: 200px; max-height: 300px; }
  .meld-group .pcard { --cw: 48px; }
}
/* very short landscape (most phones sideways): everyone's cards stay VISIBLE */
@media (max-height: 460px) {
  body.in-game .hand-row .pcard { --cw: clamp(36px, 13vh, 50px); }
  body.in-game .hand-row { min-height: 58px; }
  body.in-game .trick-area { min-height: 82px; }
  body.in-game .felt { --fan-scale: .44; }   /* fans shrink, never disappear */
  body.in-game .fan-arc { padding: 0 0 2px; }
  body.in-game .piles { --pw: 42px; }
  body.in-game .dh-mid { grid-template-columns: 56px minmax(0, 1fr) 56px; }
}
/* base trick card size (set here, not inline, so breakpoints can scale it) */
.trick-card .pcard { --cw: 52px; }

/* reduced motion */
@media (prefers-reduced-motion: reduce) {
  * { transition-duration: .01ms !important; animation-duration: .01ms !important; }
  .fx-rays, .fx-confetti { display: none !important; }
}


/* ============ LIQUID GLASS SURFACES — one material family ============
   Every card-like surface app-wide shares this exact recipe — three
   layers stacked on top of the base translucent gradient: a curved
   specular glint near the top (like light catching a rounded glass
   edge, not just a flat tint), the diagonal sheen streak, and a bumped
   blur+saturate so what's behind genuinely reads as refracted rather
   than just dimmed. .mode-card/.match-card/.theme-card-lg used to be
   either a plainer, sheen-less glass or a fully flat opaque surface —
   folded into the same family so every card in the app (Home tiles,
   tournaments, matchmaking cards, Shop theme pickers, panels, stat
   chips) genuinely feels like one continuous material. */

.panel, .game-card, .tourney-card, .wallet-hero, .modal,
.bo-stat, .stat-chip, .admin-queue-item, .event-banner,
.mode-card, .match-card, .theme-card-lg, .sticker-pack-tile, #balanceChip, .mc-hud-dropdown, .gm-dropdown, .chat-react-btn, .topnav a, .room-banner, .tourney-info-row, .tourney-empty-card,
.room-game-box, .room-rule-box, .room-tier-card, .room-summary {
  background:
    radial-gradient(ellipse 120% 60% at 50% -10%, rgba(255, 255, 255, .10), transparent 55%),
    var(--glass-grad);
  border: 1px solid var(--glass-border);
  box-shadow: var(--glass-edge), var(--glass-shadow);
  backdrop-filter: blur(16px) saturate(1.35);
  -webkit-backdrop-filter: blur(16px) saturate(1.35);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .panel, .game-card, .tourney-card, .wallet-hero, .modal,
  .bo-stat, .stat-chip, .admin-queue-item, .event-banner,
  .mode-card, .match-card, .theme-card-lg, .sticker-pack-tile, #balanceChip, .mc-hud-dropdown, .gm-dropdown, .chat-react-btn, .topnav a, .room-banner, .tourney-info-row, .tourney-empty-card,
  .room-game-box, .room-rule-box, .room-tier-card, .room-summary {
    background: var(--glass-grad-solid);
  }
}
/* backdrop-filter is genuinely expensive per element — fine for a
   handful of big surfaces (a page's own .panel, the .modal these tiles
   live inside), not for a dozen-plus small ones rendered at once (up to
   6 game boxes + several rule boxes + 4 stake cards + the summary strip,
   all inside an already-blurred .modal). This was the other real
   contributor to the reported lag, alongside the animated card art
   above. Keeps the gradient/border/shadow look from the shared rule,
   just drops the blur layer specifically for these small nested tiles —
   the .modal behind them still blurs whatever's further behind IT once. */
.room-game-box, .room-rule-box, .room-tier-card, .room-summary {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* rule boxes were reading as "all the same box" — the only color signal
   was a small 30px icon badge, easy to miss, and DIGU's Knock & Gin vs.
   Gin Only even share the same icon shape. A light hue wash + tinted
   border works BEFORE selection too (not just on .selected, unlike the
   game boxes' pattern above) — cheap (a flat gradient, no blur/filter/
   animation), so it doesn't reintroduce the perf cost that was just
   fixed. Deliberately lighter than the game boxes' full rich gradient —
   still meant to read as the smaller "detail" decision underneath them. */
.room-rule-box {
  background: linear-gradient(160deg, color-mix(in srgb, var(--hue, var(--home-purple)) 20%, transparent) 0%, transparent 65%), var(--glass-grad);
  border-color: color-mix(in srgb, var(--hue, var(--home-purple)) 38%, var(--glass-border));
}

/* the card itself now carries the rich, saturated per-game gradient —
   .mc-art/.mc-body used to each layer their own separate hue tint on
   top of the shared flat glass background above; now there's one
   continuous colored gradient running the full height of the card, and
   art/body just add light depth (a glow platform up top, a darkening
   vignette down low) instead of competing color washes. Deliberately
   placed AFTER the shared rule above (same specificity, later source
   order wins) so it overrides just its flat background for this one
   element — border/box-shadow/backdrop-filter/sheen still come from it. */
/* Rules boxes deliberately do NOT get this rich per-hue gradient — kept
   on the plain shared glass background instead, so they read as a
   lighter, quicker "detail" decision under the game boxes' much bigger,
   more colorful "the big decision" tiles, not a repeat of the same
   component. Their own color signal is just the icon badge + selected
   border (see .rrb-ico / .room-rule-box.selected below). */
.mode-card, .room-game-box {
  background:
    radial-gradient(ellipse 120% 55% at 50% -6%, rgba(255, 255, 255, .16), transparent 55%),
    linear-gradient(165deg,
      color-mix(in srgb, var(--hue, var(--teal)) 58%, #0a1a34) 0%,
      color-mix(in srgb, var(--hue, var(--teal)) 24%, #071224) 55%,
      #050b16 100%);
}
/* DIGU Knock & Gin / DHIHAEH Hukum only (see MODE_CARDS' own bgImage
   field) — a real photo behind the same hue-tinted gradient, just with
   its color stops made partial instead of opaque so the photo actually
   shows through at the top, darkening down to the text zone for
   legibility. Same --hue glow ring (::after), same animated card art on
   top (.mc-art) — nothing else about the card changed, only this one
   background swaps in for these two. Easy to drop: remove bgImage from
   the mode's MODE_CARDS entry and this rule simply never matches it. */
/* darkens much earlier (52%, right where .mc-art hands off to .mc-body)
   than the original 3-stop version did — that one only reached solid
   dark at the very bottom, leaving the title/subtitle right under the
   art zone sitting on a still-bright, still-detailed patch of photo. */
.mode-card[data-bg] {
  background:
    radial-gradient(ellipse 120% 55% at 50% -6%, rgba(255, 255, 255, .1), transparent 55%),
    linear-gradient(165deg,
      color-mix(in srgb, var(--hue, var(--teal)) 40%, transparent) 0%,
      color-mix(in srgb, var(--hue, var(--teal)) 20%, transparent) 30%,
      rgba(5, 11, 22, .82) 52%,
      rgba(5, 11, 22, .95) 100%),
    var(--card-bg-img) center / cover no-repeat;
}

/* same technique as .mode-card just above — purple-tinted gradient over
   a real photo (the glowing stage), border same as before. Zoomed in
   past plain cover (150%) and pinned toward the bottom-right (68% 100%)
   — the stage sits low/right in the source photo, and at plain cover
   scale it landed right under the Join button row; the extra zoom
   creates enough vertical slack to push it up clear of that row while
   keeping it on the right, matching the photo's own composition.
   Darkest on the LEFT (0%, where the name/sub text actually sits) and
   fading out toward the right (where the stage should stay visible) —
   flipped from the original stops, which were backwards: darkest under
   the photo, lightest right under the text. */
.room-banner {
  background:
    linear-gradient(100deg, rgba(15, 9, 32, .84) 0%, rgba(24, 16, 56, .58) 40%, rgba(14, 45, 83, .22) 100%),
    url('/icons/herosecimg.jpg') 85% 100% / 150% auto no-repeat;
  border-color: rgba(139, 92, 246, .45);
}

/* the liquid sheen — a diagonal light streak across each surface */
.panel, .wallet-hero, .modal, .bo-stat, .match-card, .theme-card-lg,
#balanceChip, .topnav a, .tourney-info-row, .tourney-empty-card { position: relative; }
.game-card::before, .tourney-card::before, .wallet-hero::before,
.panel::before, .modal::before, .bo-stat::before,
.mode-card::before, .match-card::before, .theme-card-lg::before,
#balanceChip::before, .topnav a::before, .room-banner::before,
.tourney-info-row::before, .tourney-empty-card::before, .room-game-box::before, .room-rule-box::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(115deg, transparent 28%, rgba(255, 255, 255, .09) 46%, transparent 62%);
}

/* glass feels physical: interactive surfaces lift — .theme-card-lg is
   deliberately NOT part of this: it already has its own bespoke
   hover/active feel (border-color-only hover, a press-scale on :active)
   defined where it's declared, and this rule's later source position
   would otherwise silently win any tie against it */
.game-card, .tourney-card, button.bo-stat, .stat-chip {
  transition: transform var(--t-med), border-color var(--t-med), box-shadow var(--t-med);
}
.game-card:hover, .tourney-card:hover, button.bo-stat:hover {
  transform: translateY(-2px);
  border-color: rgba(23, 181, 179, .55);
  box-shadow: var(--glass-edge), 0 22px 44px rgba(2, 6, 14, .55);
}
.tourney-card.featured {
  border-color: rgba(217, 165, 33, .55);
  background: linear-gradient(115deg, rgba(217, 165, 33, .14), transparent 40%), var(--glass-grad);
}

/* in-game bars: light glass, no blur — animations stay at full frame rate */
.dh-bar, .match-hud {
  background: linear-gradient(148deg, rgba(22, 60, 112, .5), rgba(6, 15, 30, .6) 100%);
  border-color: var(--glass-border);
  box-shadow: var(--glass-edge);
}

/* suit pips: full card colors with a gold outline */
.suit-drift svg { stroke: var(--gold); stroke-width: 1.4px; paint-order: stroke; }
.suit-drift.red { color: var(--suit-red); }
.suit-drift.black { color: #0A0F16; filter: none; }

/* ==========================================================================
   ROYAL TABLE THEME — opt-in premium reskin, chosen from Profile → Table
   Theme. Pure CSS: every rule below is scoped under body[data-theme="royal"]
   so "Classic" (the default for everyone) stays byte-for-byte untouched —
   nothing above this point is modified by picking a theme. Only ever
   restyles real THAASBAI data (coins, Level, timer, seat name/card count) —
   no invented currencies or decorative-only buttons. Table/background match
   an "oval racetrack on a dark background" reference screenshot the user
   provided (2026-07-14) — an elongated stadium-shaped felt, dark wood rail,
   a red glow at the human player's own (south) seat, and stacked
   avatar+name-tag player chips, adapted from a poker-cash-table reference
   onto THAASBAI's own fixed-seat games (no "Empty" seat placeholders —
   every match's seat count is already fully assigned before the table ever
   renders, so there's no real state one could represent).
   ========================================================================== */

/* dark charcoal backdrop the oval table sits on, replacing Classic's navy —
   scoped to body.in-game (same class the layout rules above already gate
   on) so picking this theme only changes how an actual match looks, not
   the home/lobby/profile screens, matching "Table Theme"'s existing scope */
body[data-theme="royal"].in-game ,
[data-preview-theme="royal"].in-game {
  background:
    radial-gradient(1200px 460px at 50% -160px, rgba(120, 40, 40, .16), transparent 70%),
    #110F13;
}

/* ---- table shape: .felt itself becomes a true stadium/oval via a large
   elliptical border-radius (50% horizontal sweep / a small vertical
   percentage, which — on a box taller than it is wide, as every one of our
   game tables is — rounds fully into the top/bottom caps while leaving the
   sides straight, exactly the reference's silhouette). No overflow:hidden
   is set, so real game content (seat fans, piles, hand row) is NEVER
   clipped — it simply keeps its existing grid/flex position; only the
   felt's own background/border paint as an oval, letting the dark page
   background show through at the corners, same as the reference's
   rail-mounted seats sitting just outside the green surface. ---- */
body[data-theme="royal"] .felt:not(.shop-preview-felt) ,
[data-preview-theme="royal"] .felt {
  border-radius: 50% / 14%;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='rn'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .04 0'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23rn)'/%3E%3C/svg%3E"),
    radial-gradient(ellipse at 50% 44%, rgba(46, 139, 87, .10), transparent 62%),
    radial-gradient(ellipse at 50% 46%, #1E5B3B 0%, #123524 55%, #081911 100%);
  border: none;
  box-shadow:
    inset 0 0 110px rgba(0, 0, 0, .65),
    inset 0 1px 0 rgba(180, 220, 190, .08),
    /* fat neon outline — a real solid-ish ring (the 0-blur, 4px-spread
       layer) with a modest glow halo around it, not a padded rail band
       sitting further out. Single box-shadow directly on .felt, so it
       always traces .felt's own border-radius exactly, at any table
       size, with nothing else to keep in sync. */
    0 0 0 4px rgba(170, 255, 205, .85),
    0 0 16px 3px rgba(120, 230, 170, .45),
    0 20px 50px rgba(0, 0, 0, .55);
}
/* faint centered watermark — the real brand logo (same file already used
   for the matchmaking searching-card logo), ghosted into the felt at low
   opacity so it never competes with card legibility */
body[data-theme="royal"] .felt:not(.shop-preview-felt)::before ,
[data-preview-theme="royal"] .felt::before {
  content: "";
  position: absolute; inset: 0;
  border: none; border-radius: 0; box-shadow: none;
  background: url("../icons/thaaslogofi.PNG") center 58% / 30% auto no-repeat;
  filter: grayscale(1);
  opacity: .06;
  pointer-events: none; z-index: 0;
}
/* no separate padded rail — a screenshot showed the old rope-rail cushion
   (glow blobs + stitched-seam texture sitting further out than the felt)
   reading as an unwanted extra "border" band around the fat neon ring
   above. Disabled outright (content: none, not deleted, so the base
   .felt::after watermark doesn't fall through and double up with
   ::before's watermark above). */
body[data-theme="royal"] .felt:not(.shop-preview-felt)::after ,
[data-preview-theme="royal"] .felt::after {
  content: none;
}

/* ---- top HUD: .dh-bar / .match-hud restyled into gold-bordered chrome
   around the exact same real content already there (title, trump/team info,
   coin stake, turn timer, sound, leave) — nothing new added. Pill-shaped
   (not the plain rounded-rect box every other theme uses) with a real
   inset+outset bevel, closer to the reference screenshot's chip-tray
   chrome than a flat color swap — layout itself is the base fix above
   (item A in the plan), this is purely the "gaming" finish on top of it. */
body[data-theme="royal"] .dh-bar,
[data-preview-theme="royal"] .dh-bar,
body[data-theme="royal"] .match-hud ,
[data-preview-theme="royal"] .match-hud {
  background: linear-gradient(180deg, #211A26 0%, #150F19 55%, #0E0A11 100%);
  border: 1px solid rgba(217, 165, 33, .5);
  border-radius: 999px;
  padding-left: 16px; padding-right: 16px;
  box-shadow:
    inset 0 1px 0 rgba(233, 196, 104, .16),
    inset 0 -1px 0 rgba(0, 0, 0, .5),
    inset 0 0 0 1px rgba(0, 0, 0, .35),
    0 6px 16px rgba(2, 4, 8, .5);
}
body[data-theme="royal"] .turn-timer ,
[data-preview-theme="royal"] .turn-timer {
  display: inline-block; padding: 2px 10px; border-radius: 999px;
  background: linear-gradient(180deg, rgba(233, 196, 104, .18), rgba(122, 83, 17, .10));
  border: 1px solid rgba(217, 165, 33, .5);
  color: var(--gold-hi);
}
body[data-theme="royal"] .turn-timer.urgent ,
[data-preview-theme="royal"] .turn-timer.urgent { color: var(--red); border-color: var(--red); }
body[data-theme="royal"] .countdown-ring-progress ,
[data-preview-theme="royal"] .countdown-ring-progress { stroke: #2E8B57; filter: drop-shadow(0 0 5px #2E8B57); }

/* ---- player chips: stacked avatar + name/count tag, echoing the
   reference's circular avatar with a name+balance pill docked underneath —
   reuses the exact same seat-name/seat-sub data every game already
   renders, no invented per-seat balance (this app has no mid-match stack,
   only a pre-match stake — see the file header). ---- */
body[data-theme="royal"] .seat ,
[data-preview-theme="royal"] .seat {
  flex-direction: column; gap: 3px; text-align: center;
}
body[data-theme="royal"] .seat > div:not(.avatar) ,
[data-preview-theme="royal"] .seat > div:not(.avatar) {
  background: rgba(10, 8, 12, .72);
  border: 1px solid rgba(217, 165, 33, .35);
  border-radius: 8px;
  padding: 1px 8px;
}
body[data-theme="royal"] .seat .avatar,
[data-preview-theme="royal"] .seat .avatar,
body[data-theme="royal"] .match-hud .avatar ,
[data-preview-theme="royal"] .match-hud .avatar {
  border: 2px solid var(--gold); box-shadow: 0 0 0 1px rgba(233, 196, 104, .35), inset 0 0 6px rgba(0, 0, 0, .3);
}
body[data-theme="royal"] .seat.active .avatar ,
[data-preview-theme="royal"] .seat.active .avatar { border-color: var(--gold-hi); }

/* ---- bottom bar: hand-tools / action-bar buttons stay gold-accented —
   removed the status-line's gold scroll-bracket line, which sat in the
   lower-middle of the felt near the watermark and read as a stray line
   on the table itself. ---- */
body[data-theme="royal"] .hand-tools .btn,
[data-preview-theme="royal"] .hand-tools .btn,
body[data-theme="royal"] .action-bar .btn ,
[data-preview-theme="royal"] .action-bar .btn {
  border-color: rgba(217, 165, 33, .4);
}
/* .btn-primary/.btn-gold/.pcard are used APP-WIDE, not just at the table
   (every page's buttons; .pcard also renders as decorative art on Home's
   mode-cards and Tournament cards, via the same real cardEl() code) — so
   unlike .felt/.dh-bar/.match-hud/.seat above (which only ever exist
   inside a live match to begin with, so scoping them barely matters in
   practice), these two genuinely leaked the theme onto pages with no
   game open at all. Explicit .in-game gate added so picking Royal only
   ever changes how an actual table looks, matching this whole section's
   stated intent — was missing here even though the file header always
   claimed it. .btn-primary/.btn-gold were never actually restyled
   before this either — Royal only ever touched action-bar buttons'
   border, which is why Discard/Back to Lobby/etc still showed Classic's
   flat teal in every screenshot. A real casino-chip raised-edge
   treatment (inset highlight top, inset shadow bottom), made translucent
   + backdrop-blurred (real glass, not a flat gradient) to match the same
   treatment Classic's buttons and the game cards got. */
body[data-theme="royal"].in-game .btn-primary ,
[data-preview-theme="royal"].in-game .btn-primary {
  background: linear-gradient(180deg, rgba(30, 107, 103, .82) 0%, rgba(18, 63, 60, .86) 55%, rgba(10, 36, 34, .9) 100%);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(217, 165, 33, .5);
  box-shadow:
    inset 0 1px 0 rgba(233, 196, 104, .22),
    inset 0 -2px 0 rgba(0, 0, 0, .4),
    0 3px 10px rgba(2, 8, 6, .5);
  color: var(--ivory);
}
body[data-theme="royal"].in-game .btn-primary:hover:not(:disabled) ,
[data-preview-theme="royal"].in-game .btn-primary:hover:not(:disabled) {
  background: linear-gradient(180deg, rgba(36, 129, 124, .86) 0%, rgba(22, 74, 70, .9) 55%, rgba(10, 36, 34, .92) 100%);
}
body[data-theme="royal"].in-game .btn-gold ,
[data-preview-theme="royal"].in-game .btn-gold {
  background: linear-gradient(180deg, rgba(240, 204, 106, .84) 0%, rgba(201, 154, 30, .88) 45%, rgba(138, 95, 16, .92) 100%);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(122, 83, 17, .6);
  box-shadow:
    inset 0 1px 0 rgba(255, 240, 200, .58),
    inset 0 -2px 0 rgba(60, 40, 5, .5),
    0 4px 12px rgba(140, 100, 10, .35);
}
body[data-theme="royal"].in-game .btn-gold:hover:not(:disabled) ,
[data-preview-theme="royal"].in-game .btn-gold:hover:not(:disabled) {
  background: linear-gradient(180deg, rgba(247, 218, 133, .88) 0%, rgba(217, 165, 33, .92) 45%, rgba(151, 105, 15, .94) 100%);
}

/* ---- cards: warmer face, thin gold-leaf edge, richer halo states ---- */
/* :not(.neon-card) — the splash/room-banner card cluster carries the
   same base .pcard(+.red/.dark) classes real gameplay cards do, and can
   render while body still carries .in-game+this theme (see api.js's
   showConnectionLost() for when/why) — without this exclusion its own
   neon-glass look would get re-skinned as this theme's real card face,
   which is a look meant for actual gameplay, not the decorative
   cluster. */
body[data-theme="royal"].in-game .pcard:not(.neon-card) ,
[data-preview-theme="royal"].in-game .pcard:not(.neon-card) {
  background: #FFFFFF;
  border-color: #C9A96A;
  box-shadow: 0 1px 2px rgba(9, 20, 38, .35), 0 3px 8px rgba(9, 20, 38, .18), inset 0 0 0 1px rgba(217, 165, 33, .18);
}
/* Royal card FRONT — flat white face (no texture, no gradient, no glow)
   with an opposite-corner rank/suit layout instead of Classic's
   everything-stacked-in-one-corner: rank stays top-left (.pc-corner's
   existing .pc-rank), its suit glyph moves to the top-right corner
   instead of sitting stacked beneath the rank, and that pair repeats
   mirrored at bottom-right/bottom-left (cards.js's pc-corner-alt-*
   spans — always in the DOM, hidden for every other theme, turned on
   here). The corner suits are the outline glyph; the center pip swaps
   to the FILLED alternate (pc-center-alt) instead, re-centered to the
   card's true middle instead of Classic's bottom-right offset. */
body[data-theme="royal"].in-game .pcard:not(.neon-card) .pc-corner svg ,
[data-preview-theme="royal"].in-game .pcard:not(.neon-card) .pc-corner svg { display: none; }
body[data-theme="royal"].in-game .pcard:not(.neon-card) .pc-corner-alt ,
[data-preview-theme="royal"].in-game .pcard:not(.neon-card) .pc-corner-alt {
  display: flex; position: absolute; line-height: 1;
}
body[data-theme="royal"].in-game .pcard:not(.neon-card) .pc-corner-alt-suit svg ,
[data-preview-theme="royal"].in-game .pcard:not(.neon-card) .pc-corner-alt-suit svg {
  width: calc(var(--cw) * .22); height: calc(var(--cw) * .22);
}
body[data-theme="royal"].in-game .pcard:not(.neon-card) .pc-corner-alt-rank ,
[data-preview-theme="royal"].in-game .pcard:not(.neon-card) .pc-corner-alt-rank {
  font-family: var(--font-head); font-weight: 700;
  font-size: calc(var(--cw) * .30); letter-spacing: -.02em;
}
body[data-theme="royal"].in-game .pcard:not(.neon-card) .pc-corner-alt-tr ,
[data-preview-theme="royal"].in-game .pcard:not(.neon-card) .pc-corner-alt-tr { top: 5%; right: 8%; }
body[data-theme="royal"].in-game .pcard:not(.neon-card) .pc-corner-alt-br ,
[data-preview-theme="royal"].in-game .pcard:not(.neon-card) .pc-corner-alt-br { bottom: 5%; right: 8%; transform: rotate(180deg); }
body[data-theme="royal"].in-game .pcard:not(.neon-card) .pc-corner-alt-bl ,
[data-preview-theme="royal"].in-game .pcard:not(.neon-card) .pc-corner-alt-bl { bottom: 5%; left: 8%; transform: rotate(180deg); }
body[data-theme="royal"].in-game .pcard:not(.neon-card) .pc-center ,
[data-preview-theme="royal"].in-game .pcard:not(.neon-card) .pc-center { display: none; }
body[data-theme="royal"].in-game .pcard:not(.neon-card) .pc-center-alt ,
[data-preview-theme="royal"].in-game .pcard:not(.neon-card) .pc-center-alt {
  display: flex; position: absolute;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
}
body[data-theme="royal"].in-game .pcard.selected,
[data-preview-theme="royal"].in-game .pcard.selected,
body[data-theme="royal"].in-game .pcard.selectable:hover ,
[data-preview-theme="royal"].in-game .pcard.selectable:hover {
  border-color: var(--gold);
  box-shadow: 0 0 0 2px var(--gold-soft), 0 0 12px rgba(217, 165, 33, .35), var(--shadow-1);
}
/* one distinct color per meld group (444, 222, 5555, ... each its own
   color), same idea as Classic's .mg1/.mg2/.mg3 — this rule used to only
   override the BASE .in-meld (group 0), but body[data-theme="royal"]
   pushes its specificity above Classic's .mg1/.mg2/.mg3 rules too (extra
   type selector beats them despite equal class count), so EVERY group
   was silently collapsing to this one gold regardless of index. Now
   Royal has its own 4-color set, gold/emerald/violet/copper — still
   needs its own .mg1/.mg2/.mg3 explicitly, inheriting Classic's would
   defeat the purpose. */
body[data-theme="royal"].in-game .pcard.in-meld ,
[data-preview-theme="royal"].in-game .pcard.in-meld { box-shadow: inset 0 0 0 2px rgba(217, 165, 33, .65); }       /* group 1: gold   */
body[data-theme="royal"].in-game .pcard.in-meld.mg1 ,
[data-preview-theme="royal"].in-game .pcard.in-meld.mg1 { box-shadow: inset 0 0 0 2px rgba(63, 163, 107, .6); }    /* group 2: emerald*/
body[data-theme="royal"].in-game .pcard.in-meld.mg2 ,
[data-preview-theme="royal"].in-game .pcard.in-meld.mg2 { box-shadow: inset 0 0 0 2px rgba(155, 109, 214, .65); }  /* group 3: violet */
body[data-theme="royal"].in-game .pcard.in-meld.mg3 ,
[data-preview-theme="royal"].in-game .pcard.in-meld.mg3 { box-shadow: inset 0 0 0 2px rgba(196, 120, 60, .6); }    /* group 4: copper */
body[data-theme="royal"].in-game .pcard.laid-off ,
[data-preview-theme="royal"].in-game .pcard.laid-off {
  box-shadow: inset 0 0 0 2px var(--gold-hi), 0 0 12px rgba(233, 196, 104, .6);
}
body[data-theme="royal"].in-game .pcard.team-pooled ,
[data-preview-theme="royal"].in-game .pcard.team-pooled {
  box-shadow: inset 0 0 0 2px rgba(155, 109, 214, .85), 0 0 12px rgba(155, 109, 214, .5);
}
/* Royal card back — a genuine redesign, not a gold recolor of Classic's
   wave lattice: a deeper emerald-to-black base, a repeating gold diamond
   lattice instead of waves, a double gold frame (the existing inset ring
   plus a second hairline near the card edge), and a small gold compass-
   rosette emblem centered in place of Classic's removed wave squiggle. */
body[data-theme="royal"].in-game .pcard.back ,
[data-preview-theme="royal"].in-game .pcard.back {
  background:
    radial-gradient(circle at 50% 38%, rgba(217, 165, 33, .16), transparent 60%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath d='M10 1 L19 10 L10 19 L1 10 Z' fill='none' stroke='%23D9A521' stroke-opacity='.22' stroke-width='.8'/%3E%3C/svg%3E"),
    linear-gradient(160deg, #123524 0%, #0A1E14 55%, #050D08 100%);
  /* same fixed-tile-size bug as Classic's back — see its own comment */
  background-size: auto, calc(var(--cw) * .35) calc(var(--cw) * .35), auto;
  border-color: #B8860B;
}
body[data-theme="royal"].in-game .pcard.back::after ,
[data-preview-theme="royal"].in-game .pcard.back::after {
  border: 1px solid rgba(217, 165, 33, .55);
}
body[data-theme="royal"].in-game .pcard.back::before ,
[data-preview-theme="royal"].in-game .pcard.back::before {
  content: "";
  position: absolute; inset: 2.5%;
  border: 1px solid rgba(217, 165, 33, .28);
  border-radius: inherit;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2 L22 12 L12 22 L2 12 Z' fill='none' stroke='%23D9A521' stroke-width='1.2'/%3E%3Ccircle cx='12' cy='12' r='3.4' fill='none' stroke='%23D9A521' stroke-width='1'/%3E%3Cpath d='M12 2 L12 6 M12 18 L12 22 M2 12 L6 12 M18 12 L22 12' stroke='%23D9A521' stroke-width='1' stroke-linecap='round'/%3E%3C/svg%3E")
    center / 34% no-repeat;
}

/* ---- overlay panels: result screens and the matchmaking/ready-check card
   were plain rounded-rectangles reaching almost the felt's exact edges —
   under the oval felt's curve, that left the felt visibly poking out past
   the panel's flat corners wherever the oval was actively narrowing (the
   "Team A wins!" and "Searching for a player" screenshots both show this).
   Real fix: keep the panel well inside the oval's boundary at every
   height, rather than trying to match the oval's curve exactly — not
   practical for a flat rectangle, and this is simpler and safer. ---- */
body[data-theme="royal"] .table-sheet > .panel ,
[data-preview-theme="royal"] .table-sheet > .panel {
  width: calc(100% - 44px);
  border-radius: 26px 26px 20px 20px;
}
body[data-theme="royal"] .match-card ,
[data-preview-theme="royal"] .match-card {
  max-width: min(300px, calc(100% - 60px));
  border: 1px solid rgba(217, 165, 33, .45);
  background: linear-gradient(180deg, #1C1720 0%, #131017 100%);
  box-shadow: inset 0 1px 0 rgba(233, 196, 104, .12), 0 10px 26px rgba(2, 6, 4, .5);
}

/* DHASHUNDHAMA is the one game whose own per-seat avatar/name/money
   content lives INSIDE .felt (.ds-table) rather than in .match-hud
   outside it like every other game — under the oval felt's aggressive
   50%/14% corner rounding, content sitting at .ds-table's plain 14px/
   10px edge padding got visibly clipped/overlapped by the curve: the
   opponent avatar at the top-left corner, and the bottom money row
   running past the felt's usable bottom curve. Generous extra inset,
   same "keep well inside the oval, don't try to match its curve"
   approach as .table-sheet > .panel / .match-card above. */
body[data-theme="royal"] .ds-table,
[data-preview-theme="royal"] .ds-table,
body[data-theme="royal"].in-game .ds-table ,
[data-preview-theme="royal"].in-game .ds-table {
  padding: 32px 34px 30px;
}
body[data-theme="royal"] .ds-opp ,
[data-preview-theme="royal"] .ds-opp { padding-top: 8px; }
body[data-theme="royal"] .ds-you ,
[data-preview-theme="royal"] .ds-you { padding-bottom: 12px; }
/* landscape's whole viewport is already much shorter — the portrait
   inset above (padding: 32px 34px 30px) is the same "keep clear of the
   oval curve" fix, but far more vertical padding than landscape has
   room for; same selector specificity as the portrait rule above but
   declared after it, so this correctly wins only when the landscape
   media condition actually matches. */
@media (max-height: 540px) {
  body[data-theme="royal"].in-game .ds-table ,
[data-preview-theme="royal"].in-game .ds-table { padding: 14px 30px 8px; }
  body[data-theme="royal"].in-game .ds-opp ,
[data-preview-theme="royal"].in-game .ds-opp { padding-top: 2px; }
  body[data-theme="royal"].in-game .ds-you ,
[data-preview-theme="royal"].in-game .ds-you { padding-bottom: 4px; }
}
body[data-theme="royal"] .mcard-pill ,
[data-preview-theme="royal"] .mcard-pill {
  background: rgba(217, 165, 33, .08);
  border-color: rgba(217, 165, 33, .3);
  color: var(--gold-hi);
}
body[data-theme="royal"] .mcard-pill.gold ,
[data-preview-theme="royal"] .mcard-pill.gold {
  background: rgba(217, 165, 33, .18);
  border-color: rgba(217, 165, 33, .5);
}

/* theme switch shouldn't hard-snap when picked from Profile — scoped to the
   theme attribute itself (rather than the base classes) so Classic's own
   rules truly stay untouched either way */
body[data-theme] .felt,
body[data-theme] .dh-bar,
body[data-theme] .match-hud,
body[data-theme] .pcard,
body[data-theme] .turn-timer,
body[data-theme] .btn-primary,
body[data-theme] .btn-gold,
body[data-theme] .match-card {
  transition: background var(--t-med), border-color var(--t-med), box-shadow var(--t-med), border-radius var(--t-med);
}

/* ==========================================================================
   RED & BLACK TABLE THEME — flat two-tone pip deck, chosen from Shop →
   Table Theme. A genuinely different card FRONT layout, not just a color
   reskin: real per-rank pip clusters (2-10 each show that many suit
   glyphs in a bespoke arrangement, not one big icon regardless of rank
   like Classic/Royal), a plain mirrored top-left+bottom-right corner
   index, one big centered icon for Ace, and a bold single letter for
   J/Q/K. All new markup lives in cardEl() (cards.js) permanently, hidden
   for every other theme — see its own comment for why. Solid crimson for
   Hearts/Diamonds, solid charcoal for Spades/Clubs, white ink throughout.
   ========================================================================== */
/* :not(.neon-card) throughout this theme's .pcard rules — see Royal's
   identical comment above for why (the splash/room-banner card cluster
   must never get re-skinned as a real table's card face). */
body[data-theme="rednblack"].in-game .pcard:not(.neon-card) ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card) {
  border: none;
  border-radius: calc(var(--cw) * .1);
  /* same liquid-glass edge+shadow recipe every other card-like surface
     in the app uses (see LIQUID GLASS SURFACES) — a glossy POLISH on
     top of this theme's solid color, not the app's usual translucent
     blur-through glass, since the reference art is flat opaque color,
     not see-through. */
  box-shadow: var(--glass-edge), var(--glass-shadow);
  backdrop-filter: saturate(1.2);
  -webkit-backdrop-filter: saturate(1.2);
}
/* the specular highlight — a soft light-catch near the top edge, like a
   rounded glass surface, layered over the flat color instead of
   replacing it (same idea as .mode-card's own per-item gradient
   override further up, just applied to a flat color here). Toned down
   from the first pass — was reading as too strong/glossy. */
body[data-theme="rednblack"].in-game .pcard.red:not(.neon-card) ,
[data-preview-theme="rednblack"].in-game .pcard.red:not(.neon-card) {
  background: radial-gradient(ellipse 120% 55% at 50% -8%, rgba(255, 255, 255, .16), transparent 55%), #B8393E;
  color: #FFFFFF;
}
body[data-theme="rednblack"].in-game .pcard.dark:not(.neon-card) ,
[data-preview-theme="rednblack"].in-game .pcard.dark:not(.neon-card) {
  background: radial-gradient(ellipse 120% 55% at 50% -8%, rgba(255, 255, 255, .12), transparent 55%), #2E3238;
  color: #FFFFFF;
}
/* the armed state's plain border-color (see .hand-row.fanned .pcard.armed
   further up) does nothing here — this theme's cards have border: none,
   relying on box-shadow alone for their edge, so there's no border to
   color in the first place. A real border, just for this state, so the
   neon-outline read stays consistent with every other theme. */
body[data-theme="rednblack"].in-game .pcard.armed ,
[data-preview-theme="rednblack"].in-game .pcard.armed { border: 2px solid #7CFC9A; }
/* the diagonal sheen streak — same ::before recipe as every other glass
   card, scoped off the back (which already has its own lattice
   ::before/::after) so it only ever touches the front face. Also
   toned down along with the highlight above. */
body[data-theme="rednblack"].in-game .pcard:not(.back):not(.neon-card)::before ,
[data-preview-theme="rednblack"].in-game .pcard:not(.back):not(.neon-card)::before {
  content: "";
  position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(115deg, transparent 32%, rgba(255, 255, 255, .09) 46%, transparent 58%);
}

/* corner index — Classic's stacked layout is the right SHAPE for this
   theme, but its size (tuned for a big single center icon) ate up
   nearly 40% of the card, which is exactly why v1 collided with the pip
   cluster — shrunk to a compact index that matches the reference image
   and leaves the whole central area genuinely clear. The reference
   shows the small suit glyph FIRST (closest to the actual corner tip)
   with the rank number/letter below it, the opposite of Classic's own
   rank-then-suit order — column-reverse flips the visual order without
   touching cardEl()'s shared markup (every other theme keeps Classic's
   original order). Gap between them was pushed too far apart in the
   previous pass — pulled back in here. */
body[data-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-corner ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-corner {
  top: 3%; left: 6%;
  flex-direction: column-reverse;
}
body[data-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-corner .pc-rank ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-corner .pc-rank {
  font-size: calc(var(--cw) * .19);
}
body[data-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-corner svg ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-corner svg {
  width: calc(var(--cw) * .15); height: calc(var(--cw) * .15);
  margin-top: 0; margin-bottom: 0;
}
/* mirrored copy at bottom-right, same compact size + order, rotated
   180deg — the same column-reverse is what makes the suit glyph land
   nearest the true bottom-right corner tip too (not the rank), since
   the whole block's content flips again under the rotation: without
   the reverse here, this corner would show the OPPOSITE order from the
   top-left one instead of a true mirror. */
body[data-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-corner-mirror ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-corner-mirror {
  display: flex; flex-direction: column-reverse; align-items: center; line-height: 1;
  position: absolute; bottom: 3%; right: 6%;
  transform: rotate(180deg);
}
body[data-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-corner-mirror .pc-rank ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-corner-mirror .pc-rank {
  font-family: var(--font-head); font-weight: 700;
  font-size: calc(var(--cw) * .19); letter-spacing: -.02em;
}
body[data-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-corner-mirror svg ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-corner-mirror svg {
  width: calc(var(--cw) * .15); height: calc(var(--cw) * .15);
  margin-top: 0; margin-bottom: 0;
}
/* Classic's single big icon is replaced entirely by the pip cluster
   (below) — including for Ace, which is just pip-1 shown oversized. */
body[data-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-center,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-center,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-center-alt ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-center-alt {
  display: none;
}

/* pip cluster — up to 10 positioned slots, one bespoke layout per rank
   (2-10) turned on below via the data-rank attribute cardEl() sets.
   Percentages are relative to the card's own box (pc-pips is inset:0),
   each pip self-centers on its own coordinate via translate(-50%,-50%).
   Every position below sits on a 3-column (30/50/70%) x 5-row
   (16/32/50/68/84%) grid, deliberately kept well clear of BOTH corner
   index boxes above (roughly the outer ~22% at each of those two
   corners) — v1's positions reached right up to the 8-14% mark and
   collided directly with the corner text, which is the actual bug in
   the first version, not a stylistic difference from the reference. */
body[data-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-pips ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card) .pc-pips {
  display: block; position: absolute; inset: 0;
}
body[data-theme="rednblack"].in-game .pcard .pip ,
[data-preview-theme="rednblack"].in-game .pcard .pip {
  position: absolute; display: none; transform: translate(-50%, -50%);
}
body[data-theme="rednblack"].in-game .pcard .pip svg ,
[data-preview-theme="rednblack"].in-game .pcard .pip svg {
  display: block; width: calc(var(--cw) * .16); height: calc(var(--cw) * .16);
}
/* J/Q/K: no reference pip layout for these — own flat design, a single
   bold letter at the same visual weight the Ace's oversized pip gets.
   User-supplied face-card art may replace this later; placeholder for
   now. Pips stay off (face-letter takes over the whole center instead). */
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="J"] .pc-pips,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="J"] .pc-pips,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="Q"] .pc-pips,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="Q"] .pc-pips,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="K"] .pc-pips ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="K"] .pc-pips {
  display: none;
}
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="J"] .pc-face-letter,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="J"] .pc-face-letter,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="Q"] .pc-face-letter,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="Q"] .pc-face-letter,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="K"] .pc-face-letter ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="K"] .pc-face-letter {
  display: block; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  font-family: var(--font-head); font-weight: 800;
  font-size: calc(var(--cw) * .5); line-height: 1; color: #FFFFFF;
}

/* Ace — one big centered pip, same spot every theme's single-icon face used */
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="A"] .pip-1 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="A"] .pip-1 { display: block; left: 50%; top: 50%; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="A"] .pip-1 svg ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="A"] .pip-1 svg { width: calc(var(--cw) * .5); height: calc(var(--cw) * .5); }

/* v5 — true circular/flower arrangement: every rank's pips sit evenly
   spaced around one center point (like petals around a flower), not in
   two columns — this is the actual structural pattern in the reference
   art, not a traditional card layout. Positions are computed from real
   angles (i * 360/N degrees, starting at 12 o'clock, going clockwise),
   with radius scaled per-axis (18.3% vertical for every 26% horizontal)
   so the circle actually renders round despite the card being taller
   than it is wide. Each pip is rotated to its own angle + 90deg, so it
   points outward from center like a real petal — this also naturally
   reproduces proper top/bottom mirroring as a special case (the top
   pip lands at 0deg/upright, the bottom pip at 180deg/upside-down)
   without needing separate rules for it. Pip size shrinks gradually as
   rank count grows, but every size is deliberately picked so adjacent
   petals on the circle actually touch/overlap (chord length between
   neighbors vs pip diameter), producing the dense fused-flower look
   the reference has, instead of separated dots. The circle's radius
   was chosen so its closest approach to either corner index box never
   coincides in both x and y at once — verified per-rank below, not
   just assumed. */
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="2"] .pip-1,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="2"] .pip-1,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="2"] .pip-2 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="2"] .pip-2 { display: block; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="2"] .pip svg ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="2"] .pip svg { width: calc(var(--cw) * .34); height: calc(var(--cw) * .34); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="2"] .pip-1 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="2"] .pip-1 { left: 50%; top: 32%; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="2"] .pip-2 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="2"] .pip-2 { left: 50%; top: 68%; transform: translate(-50%, -50%) rotate(180deg); }

body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="3"] .pip-1,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="3"] .pip-1,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="3"] .pip-2,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="3"] .pip-2,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="3"] .pip-3 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="3"] .pip-3 { display: block; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="3"] .pip svg ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="3"] .pip svg { width: calc(var(--cw) * .3); height: calc(var(--cw) * .3); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="3"] .pip-1 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="3"] .pip-1 { left: 50%; top: 32%; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="3"] .pip-2 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="3"] .pip-2 { left: 72%; top: 59%; transform: translate(-50%, -50%) rotate(120deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="3"] .pip-3 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="3"] .pip-3 { left: 28%; top: 59%; transform: translate(-50%, -50%) rotate(240deg); }

body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-1,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-1,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-2,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-2,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-3,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-3,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-4 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-4 { display: block; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip svg ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip svg { width: calc(var(--cw) * .28); height: calc(var(--cw) * .28); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-1 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-1 { left: 50%; top: 32%; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-2 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-2 { left: 76%; top: 50%; transform: translate(-50%, -50%) rotate(90deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-3 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-3 { left: 50%; top: 68%; transform: translate(-50%, -50%) rotate(180deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-4 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="4"] .pip-4 { left: 24%; top: 50%; transform: translate(-50%, -50%) rotate(270deg); }

body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-1,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-1,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-2,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-2,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-3,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-3,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-4,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-4,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-5 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-5 { display: block; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip svg ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip svg { width: calc(var(--cw) * .26); height: calc(var(--cw) * .26); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-1 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-1 { left: 50%; top: 32%; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-2 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-2 { left: 75%; top: 44%; transform: translate(-50%, -50%) rotate(72deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-3 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-3 { left: 65%; top: 65%; transform: translate(-50%, -50%) rotate(144deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-4 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-4 { left: 35%; top: 65%; transform: translate(-50%, -50%) rotate(216deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-5 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="5"] .pip-5 { left: 25%; top: 44%; transform: translate(-50%, -50%) rotate(288deg); }

body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-1,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-1,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-2,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-2,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-3,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-3,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-4,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-4,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-5,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-5,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-6 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-6 { display: block; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip svg ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip svg { width: calc(var(--cw) * .25); height: calc(var(--cw) * .25); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-1 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-1 { left: 50%; top: 32%; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-2 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-2 { left: 73%; top: 41%; transform: translate(-50%, -50%) rotate(60deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-3 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-3 { left: 73%; top: 59%; transform: translate(-50%, -50%) rotate(120deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-4 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-4 { left: 50%; top: 68%; transform: translate(-50%, -50%) rotate(180deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-5 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-5 { left: 28%; top: 59%; transform: translate(-50%, -50%) rotate(240deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-6 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="6"] .pip-6 { left: 28%; top: 41%; transform: translate(-50%, -50%) rotate(300deg); }

body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-1,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-1,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-2,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-2,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-3,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-3,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-4,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-4,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-5,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-5,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-6,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-6,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-7 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-7 { display: block; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip svg ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip svg { width: calc(var(--cw) * .24); height: calc(var(--cw) * .24); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-1 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-1 { left: 50%; top: 32%; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-2 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-2 { left: 70%; top: 39%; transform: translate(-50%, -50%) rotate(51deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-3 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-3 { left: 75%; top: 54%; transform: translate(-50%, -50%) rotate(103deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-4 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-4 { left: 61%; top: 67%; transform: translate(-50%, -50%) rotate(154deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-5 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-5 { left: 39%; top: 67%; transform: translate(-50%, -50%) rotate(206deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-6 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-6 { left: 25%; top: 54%; transform: translate(-50%, -50%) rotate(257deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-7 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="7"] .pip-7 { left: 30%; top: 39%; transform: translate(-50%, -50%) rotate(309deg); }

body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-1,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-1,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-2,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-2,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-3,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-3,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-4,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-4,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-5,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-5,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-6,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-6,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-7,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-7,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-8 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-8 { display: block; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip svg ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip svg { width: calc(var(--cw) * .23); height: calc(var(--cw) * .23); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-1 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-1 { left: 50%; top: 32%; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-2 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-2 { left: 68%; top: 37%; transform: translate(-50%, -50%) rotate(45deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-3 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-3 { left: 76%; top: 50%; transform: translate(-50%, -50%) rotate(90deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-4 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-4 { left: 68%; top: 63%; transform: translate(-50%, -50%) rotate(135deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-5 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-5 { left: 50%; top: 68%; transform: translate(-50%, -50%) rotate(180deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-6 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-6 { left: 32%; top: 63%; transform: translate(-50%, -50%) rotate(225deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-7 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-7 { left: 24%; top: 50%; transform: translate(-50%, -50%) rotate(270deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-8 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="8"] .pip-8 { left: 32%; top: 37%; transform: translate(-50%, -50%) rotate(315deg); }

body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-1,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-1,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-2,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-2,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-3,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-3,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-4,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-4,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-5,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-5,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-6,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-6,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-7,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-7,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-8,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-8,
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-9 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-9 { display: block; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip svg ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip svg { width: calc(var(--cw) * .22); height: calc(var(--cw) * .22); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-1 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-1 { left: 50%; top: 32%; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-2 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-2 { left: 67%; top: 36%; transform: translate(-50%, -50%) rotate(40deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-3 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-3 { left: 76%; top: 47%; transform: translate(-50%, -50%) rotate(80deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-4 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-4 { left: 73%; top: 59%; transform: translate(-50%, -50%) rotate(120deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-5 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-5 { left: 59%; top: 67%; transform: translate(-50%, -50%) rotate(160deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-6 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-6 { left: 41%; top: 67%; transform: translate(-50%, -50%) rotate(200deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-7 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-7 { left: 28%; top: 59%; transform: translate(-50%, -50%) rotate(240deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-8 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-8 { left: 24%; top: 47%; transform: translate(-50%, -50%) rotate(280deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-9 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="9"] .pip-9 { left: 33%; top: 36%; transform: translate(-50%, -50%) rotate(320deg); }

body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip { display: block; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip svg ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip svg { width: calc(var(--cw) * .21); height: calc(var(--cw) * .21); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-1 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-1 { left: 50%; top: 32%; }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-2 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-2 { left: 65%; top: 35%; transform: translate(-50%, -50%) rotate(36deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-3 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-3 { left: 75%; top: 44%; transform: translate(-50%, -50%) rotate(72deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-4 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-4 { left: 75%; top: 56%; transform: translate(-50%, -50%) rotate(108deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-5 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-5 { left: 65%; top: 65%; transform: translate(-50%, -50%) rotate(144deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-6 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-6 { left: 50%; top: 68%; transform: translate(-50%, -50%) rotate(180deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-7 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-7 { left: 35%; top: 65%; transform: translate(-50%, -50%) rotate(216deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-8 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-8 { left: 25%; top: 56%; transform: translate(-50%, -50%) rotate(252deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-9 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-9 { left: 25%; top: 44%; transform: translate(-50%, -50%) rotate(288deg); }
body[data-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-10 ,
[data-preview-theme="rednblack"].in-game .pcard:not(.neon-card)[data-rank="10"] .pip-10 { left: 35%; top: 35%; transform: translate(-50%, -50%) rotate(324deg); }

body[data-theme="rednblack"].in-game .pcard.selected,
[data-preview-theme="rednblack"].in-game .pcard.selected,
body[data-theme="rednblack"].in-game .pcard.selectable:hover ,
[data-preview-theme="rednblack"].in-game .pcard.selectable:hover {
  box-shadow: 0 0 0 2px #FFFFFF, 0 0 12px rgba(255, 255, 255, .35), var(--shadow-1);
}
body[data-theme="rednblack"].in-game .pcard.in-meld ,
[data-preview-theme="rednblack"].in-game .pcard.in-meld { box-shadow: inset 0 0 0 2px rgba(217, 165, 33, .75); }
body[data-theme="rednblack"].in-game .pcard.in-meld.mg1 ,
[data-preview-theme="rednblack"].in-game .pcard.in-meld.mg1 { box-shadow: inset 0 0 0 2px rgba(63, 163, 107, .75); }
body[data-theme="rednblack"].in-game .pcard.in-meld.mg2 ,
[data-preview-theme="rednblack"].in-game .pcard.in-meld.mg2 { box-shadow: inset 0 0 0 2px rgba(155, 109, 214, .8); }
body[data-theme="rednblack"].in-game .pcard.in-meld.mg3 ,
[data-preview-theme="rednblack"].in-game .pcard.in-meld.mg3 { box-shadow: inset 0 0 0 2px rgba(217, 165, 33, .75); }

/* card back — matches the real Red & Black reference boards directly: a
   bold red-orange stripe frame (every reference sandwiches its motif
   band between solid red/terracotta stripes), a thin gold pinstripe
   just inside it, and a single centered mandala — an 8-fold radially
   symmetric flower medallion, gold outer petals + red-orange inner
   petals + a beaded gold dot rim + a red-orange center. Built as 8
   identical copies of one petal shape rotated by 45°/22.5° around the
   same center point, so the symmetry is exact by construction, not
   hand-balanced — the reliable way to get a genuinely "mirror perfect"
   mandala when this has to be authored without seeing it rendered. */
body[data-theme="rednblack"].in-game .pcard.back ,
[data-preview-theme="rednblack"].in-game .pcard.back {
  background:
    radial-gradient(ellipse 120% 55% at 50% -8%, rgba(217, 165, 33, .16), transparent 55%),
    linear-gradient(160deg, #17110A 0%, #0C0805 55%, #060402 100%);
  border: 3px solid #C43A24;
  overflow: hidden;
}
/* the diagonal sheen streak — same recipe as the front's own
   .pcard:not(.back)::before, just relocated onto ::after since ::before
   is already the mandala background here. Same liquid-glass level as
   the front: one specular highlight (above) + one sheen streak, no more. */
body[data-theme="rednblack"].in-game .pcard.back::after ,
[data-preview-theme="rednblack"].in-game .pcard.back::after {
  content: "";
  position: absolute; inset: 8%;
  border: 1px solid #D9A521;
  border-radius: calc(var(--cw) * .05);
  background: linear-gradient(115deg, transparent 32%, rgba(255, 255, 255, .09) 46%, transparent 58%);
}
body[data-theme="rednblack"].in-game .pcard.back::before ,
[data-preview-theme="rednblack"].in-game .pcard.back::before {
  content: "";
  position: absolute; inset: 11%;
  border-radius: calc(var(--cw) * .04);
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'%3E%3Cg fill='%23D9A521'%3E%3Cellipse cx='20' cy='7' rx='2.6' ry='7' transform='rotate(0 20 20)'/%3E%3Cellipse cx='20' cy='7' rx='2.6' ry='7' transform='rotate(45 20 20)'/%3E%3Cellipse cx='20' cy='7' rx='2.6' ry='7' transform='rotate(90 20 20)'/%3E%3Cellipse cx='20' cy='7' rx='2.6' ry='7' transform='rotate(135 20 20)'/%3E%3Cellipse cx='20' cy='7' rx='2.6' ry='7' transform='rotate(180 20 20)'/%3E%3Cellipse cx='20' cy='7' rx='2.6' ry='7' transform='rotate(225 20 20)'/%3E%3Cellipse cx='20' cy='7' rx='2.6' ry='7' transform='rotate(270 20 20)'/%3E%3Cellipse cx='20' cy='7' rx='2.6' ry='7' transform='rotate(315 20 20)'/%3E%3C/g%3E%3Cg fill='%23C43A24'%3E%3Cellipse cx='20' cy='12' rx='2' ry='5' transform='rotate(22.5 20 20)'/%3E%3Cellipse cx='20' cy='12' rx='2' ry='5' transform='rotate(67.5 20 20)'/%3E%3Cellipse cx='20' cy='12' rx='2' ry='5' transform='rotate(112.5 20 20)'/%3E%3Cellipse cx='20' cy='12' rx='2' ry='5' transform='rotate(157.5 20 20)'/%3E%3Cellipse cx='20' cy='12' rx='2' ry='5' transform='rotate(202.5 20 20)'/%3E%3Cellipse cx='20' cy='12' rx='2' ry='5' transform='rotate(247.5 20 20)'/%3E%3Cellipse cx='20' cy='12' rx='2' ry='5' transform='rotate(292.5 20 20)'/%3E%3Cellipse cx='20' cy='12' rx='2' ry='5' transform='rotate(337.5 20 20)'/%3E%3C/g%3E%3Cg fill='%23D9A521'%3E%3Ccircle cx='20' cy='4' r='1' transform='rotate(0 20 20)'/%3E%3Ccircle cx='20' cy='4' r='1' transform='rotate(45 20 20)'/%3E%3Ccircle cx='20' cy='4' r='1' transform='rotate(90 20 20)'/%3E%3Ccircle cx='20' cy='4' r='1' transform='rotate(135 20 20)'/%3E%3Ccircle cx='20' cy='4' r='1' transform='rotate(180 20 20)'/%3E%3Ccircle cx='20' cy='4' r='1' transform='rotate(225 20 20)'/%3E%3Ccircle cx='20' cy='4' r='1' transform='rotate(270 20 20)'/%3E%3Ccircle cx='20' cy='4' r='1' transform='rotate(315 20 20)'/%3E%3C/g%3E%3Ccircle cx='20' cy='20' r='4.2' fill='none' stroke='%23D9A521' stroke-width='.8'/%3E%3Ccircle cx='20' cy='20' r='2.6' fill='%23C43A24' stroke='%23D9A521' stroke-width='.7'/%3E%3C/svg%3E")
    center / 82% no-repeat,
    #0A0705;
}

/* dark lacquer backdrop the oval table sits on, replacing Classic's navy —
   same in-game-only scoping Royal uses, mirrored here in the theme's own
   red-orange/black-lacquer palette instead of Royal's charcoal/red. */
body[data-theme="rednblack"].in-game ,
[data-preview-theme="rednblack"].in-game {
  background:
    radial-gradient(1200px 460px at 50% -160px, rgba(196, 58, 36, .12), transparent 70%),
    #050301;
}

/* table shape: same true stadium/oval technique as Royal's .felt (a
   large elliptical border-radius that rounds fully into the top/bottom
   caps on a box taller than wide, leaving the sides straight) — plain
   lacquer field, no motif texture, so it never competes with the cards
   sitting on it. Base color kept noticeably warmer/lighter than the
   near-black page backdrop above (needed so the felt reads as a
   distinct object on mobile, not a glow merging into the background).
   Border is a real GRADIENT ring (red → orange → gold → red, drawn via
   the padding-box/border-box layering trick — same technique Prestige's
   card fronts already use) instead of a flat glow-only rim, at a slim
   5px width with one modest glow layer. Two earlier passes both read as
   "joker house": first a thick 3D bevel tube with a hard white
   highlight sweep, then a padded leather rail whose stitched-seam
   texture (a repeating-conic-gradient) showed up as radiating line
   patterns around the border — both removed for good. */
body[data-theme="rednblack"] .felt:not(.shop-preview-felt) ,
[data-preview-theme="rednblack"] .felt {
  border-radius: 50% / 14%;
  background:
    radial-gradient(ellipse at 50% 44%, rgba(217, 165, 33, .1), transparent 62%) padding-box,
    radial-gradient(ellipse at 50% 46%, #33150A 0%, #1C0D06 55%, #0D0603 100%) padding-box,
    linear-gradient(135deg, #7A140F 0%, #E8402C 35%, #D9A521 55%, #E8402C 75%, #7A140F 100%) border-box;
  border: 5px solid transparent;
  box-shadow:
    inset 0 0 110px rgba(0, 0, 0, .65),
    inset 0 1px 0 rgba(255, 160, 130, .08),
    0 0 11px 2px rgba(230, 60, 36, .35),
    0 20px 50px rgba(0, 0, 0, .55);
}
/* faint centered watermark, same treatment/opacity as every other theme */
body[data-theme="rednblack"] .felt:not(.shop-preview-felt)::before ,
[data-preview-theme="rednblack"] .felt::before {
  content: "";
  position: absolute; inset: 0;
  border: none; border-radius: 0; box-shadow: none;
  background: url("../icons/thaaslogofi.PNG") center 58% / 30% auto no-repeat;
  filter: grayscale(1);
  opacity: .06;
  pointer-events: none; z-index: 0;
}
/* second outline: a slim red-only gradient ring sitting just outside the
   felt's own (red/orange/gold) border, separated by a small gap — reads
   as a genuine two-layer 3D groove rather than one flat line, without
   the width/texture that made the old padded rail feel fat. Sits BEHIND
   .felt (z-index: -1) and is only a few px larger on each side, so
   .felt's own opaque body naturally covers its interior — no separate
   padding-box/mask trick needed, unlike .felt's own border above, the
   exposed sliver just IS the ring. Its own modest glow, same weight as
   the inner ring's, so both read as "perfect neon" rather than one
   dominating the other. */
body[data-theme="rednblack"] .felt:not(.shop-preview-felt)::after ,
[data-preview-theme="rednblack"] .felt::after {
  content: "";
  position: absolute;
  inset: -8px;
  border-radius: 50% / 14%;
  background: linear-gradient(135deg, #4A0A08 0%, #C43A24 30%, #FF6A4E 50%, #C43A24 70%, #4A0A08 100%);
  box-shadow: 0 0 9px 2px rgba(255, 90, 60, .4);
  z-index: -1;
  pointer-events: none;
}

/* top HUD: .dh-bar / .match-hud, turn timer, seat chips — same pill
   chrome/structure as Royal's, recolored to this theme's red-orange/
   gold/black-lacquer palette instead of Royal's purple/gold. */
body[data-theme="rednblack"] .dh-bar,
[data-preview-theme="rednblack"] .dh-bar,
body[data-theme="rednblack"] .match-hud ,
[data-preview-theme="rednblack"] .match-hud {
  background: linear-gradient(180deg, #241209 0%, #170C07 55%, #100804 100%);
  border: 1px solid rgba(196, 58, 36, .55);
  border-radius: 999px;
  padding-left: 16px; padding-right: 16px;
  box-shadow:
    inset 0 1px 0 rgba(217, 165, 33, .16),
    inset 0 -1px 0 rgba(0, 0, 0, .5),
    inset 0 0 0 1px rgba(0, 0, 0, .35),
    0 6px 16px rgba(2, 4, 8, .5);
}
body[data-theme="rednblack"] .turn-timer ,
[data-preview-theme="rednblack"] .turn-timer {
  display: inline-block; padding: 2px 10px; border-radius: 999px;
  background: linear-gradient(180deg, rgba(217, 165, 33, .18), rgba(122, 83, 17, .10));
  border: 1px solid rgba(217, 165, 33, .5);
  color: var(--gold-hi);
}
body[data-theme="rednblack"] .turn-timer.urgent ,
[data-preview-theme="rednblack"] .turn-timer.urgent { color: #C43A24; border-color: #C43A24; }
body[data-theme="rednblack"] .countdown-ring-progress ,
[data-preview-theme="rednblack"] .countdown-ring-progress { stroke: #C43A24; filter: drop-shadow(0 0 5px #C43A24); }
body[data-theme="rednblack"] .seat ,
[data-preview-theme="rednblack"] .seat {
  flex-direction: column; gap: 3px; text-align: center;
}
body[data-theme="rednblack"] .seat > div:not(.avatar) ,
[data-preview-theme="rednblack"] .seat > div:not(.avatar) {
  background: rgba(10, 6, 4, .72);
  border: 1px solid rgba(217, 165, 33, .35);
  border-radius: 8px;
  padding: 1px 8px;
}
body[data-theme="rednblack"] .seat .avatar,
[data-preview-theme="rednblack"] .seat .avatar,
body[data-theme="rednblack"] .match-hud .avatar ,
[data-preview-theme="rednblack"] .match-hud .avatar {
  border: 2px solid #C43A24; box-shadow: 0 0 0 1px rgba(217, 165, 33, .35), inset 0 0 6px rgba(0, 0, 0, .3);
}
body[data-theme="rednblack"] .seat.active .avatar ,
[data-preview-theme="rednblack"] .seat.active .avatar { border-color: var(--gold-hi); }

/* bottom bar buttons — same casino-chip raised-edge glass treatment as
   Royal's, recolored: .btn-primary becomes a red-orange lacquer chip
   (was Royal's teal), .btn-gold stays the shared brand gold. */
body[data-theme="rednblack"] .hand-tools .btn,
[data-preview-theme="rednblack"] .hand-tools .btn,
body[data-theme="rednblack"] .action-bar .btn ,
[data-preview-theme="rednblack"] .action-bar .btn {
  border-color: rgba(217, 165, 33, .4);
}
body[data-theme="rednblack"].in-game .btn-primary ,
[data-preview-theme="rednblack"].in-game .btn-primary {
  background: linear-gradient(180deg, rgba(150, 45, 25, .82) 0%, rgba(90, 25, 15, .86) 55%, rgba(50, 15, 10, .9) 100%);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(217, 165, 33, .5);
  box-shadow:
    inset 0 1px 0 rgba(233, 196, 104, .22),
    inset 0 -2px 0 rgba(0, 0, 0, .4),
    0 3px 10px rgba(2, 8, 6, .5);
  color: var(--ivory);
}
body[data-theme="rednblack"].in-game .btn-primary:hover:not(:disabled) ,
[data-preview-theme="rednblack"].in-game .btn-primary:hover:not(:disabled) {
  background: linear-gradient(180deg, rgba(180, 55, 30, .86) 0%, rgba(110, 30, 18, .9) 55%, rgba(50, 15, 10, .92) 100%);
}
body[data-theme="rednblack"].in-game .btn-gold ,
[data-preview-theme="rednblack"].in-game .btn-gold {
  background: linear-gradient(180deg, rgba(240, 204, 106, .84) 0%, rgba(201, 154, 30, .88) 45%, rgba(138, 95, 16, .92) 100%);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(122, 83, 17, .6);
  box-shadow:
    inset 0 1px 0 rgba(255, 240, 200, .58),
    inset 0 -2px 0 rgba(60, 40, 5, .5),
    0 4px 12px rgba(140, 100, 10, .35);
}
body[data-theme="rednblack"].in-game .btn-gold:hover:not(:disabled) ,
[data-preview-theme="rednblack"].in-game .btn-gold:hover:not(:disabled) {
  background: linear-gradient(180deg, rgba(247, 218, 133, .88) 0%, rgba(217, 165, 33, .92) 45%, rgba(151, 105, 15, .94) 100%);
}

/* ==========================================================================
   PRESTIGE TABLE THEME — new 4th theme, opt-in from Profile → Table Theme.
   The Home hero cluster's own liquid-glass split-corner card design,
   brought into REAL gameplay for the first time (every hand, every
   opponent's fan, every discard pile — not just Home's decorative
   showcase), with BOTH suit colors getting the full neon-glow-outline +
   gradient-border treatment — not one glowing and one plain. RED suit
   cards (Hearts/Diamonds) get a genuinely RED gradient/glow (deep red →
   bright red → white — #prestigeRedGrad in index.html), not the hero
   cluster's blue/purple, since a red card glowing blue read as
   mismatched. BLACK suit cards (Spades/Clubs) keep a solid black glass
   fill (still genuinely "black") but now get their OWN monochrome
   black/white/black gradient+glow outline (#prestigeMonoGrad) instead
   of a plain flat border — so both colors are equally "perfected" and
   glowing, just in two different palettes. Card BACK and the table
   itself (felt + rail + background) get their own dedicated
   liquid-glass designs to match. Pure CSS, scoped under
   body[data-theme="prestige"] so no other theme is touched; the
   split-corner MARKUP itself is applied conditionally in cardEl()
   (cards.js) via splitCornerMarks(), gated on this theme being active
   and body.in-game (see cards.js's own comment) so Home's decorative
   cards are completely unaffected. */

/* in-game backdrop: near-black with a cool blue glass glow, replacing
   Classic's navy — sets the mood the glass table/cards sit in */
body[data-theme="prestige"].in-game ,
[data-preview-theme="prestige"].in-game {
  background:
    radial-gradient(1100px 460px at 50% -160px, rgba(80, 130, 220, .16), transparent 70%),
    radial-gradient(900px 500px at 50% 60%, rgba(109, 63, 201, .08), transparent 65%),
    #05070f;
}

/* card base: no paper border/radius — the gradient border-box trick
   below draws the whole edge, same technique as .pcard.neon-card.
   :not(.neon-card) throughout — see Royal's identical comment further
   up for why (the splash/room-banner card cluster carries this exact
   base .pcard(+.red/.dark) class set too, and can render while
   body still has .in-game+this theme — see api.js's
   showConnectionLost() — without this it would get re-skinned as this
   theme's real gameplay card face instead of staying its own neon-glass
   look, which is exactly the bug this comment exists to prevent
   regressing). */
body[data-theme="prestige"].in-game .pcard:not(.neon-card) ,
[data-preview-theme="prestige"].in-game .pcard:not(.neon-card) {
  border: none;
  border-radius: calc(var(--cw) * .1);
}
/* red suits: translucent dark glass fill (neutral — the color lives in
   the border/glow, not bleeding onto the interior, same principle as
   the hero cluster), a real red gradient border, red neon glow. */
body[data-theme="prestige"].in-game .pcard.red:not(.neon-card) ,
[data-preview-theme="prestige"].in-game .pcard.red:not(.neon-card) {
  background:
    linear-gradient(165deg, rgba(34, 10, 12, .62) 0%, rgba(14, 5, 6, .72) 100%) padding-box,
    linear-gradient(150deg, #4A0E0E 0%, #FF3B4E 52%, #FFFFFF 100%) border-box;
  border: 1.5px solid transparent;
  box-shadow: 0 0 3px #FF3B4E, 0 0 7px rgba(255, 59, 78, .45);
  color: #FF3B4E;
  overflow: visible;
}
/* black suits: solid black glass fill stays genuinely black, but now
   gets the same weight of treatment as red — a real monochrome
   black/white gradient border and a proper white neon glow, not the
   plain flat edge this had before. */
body[data-theme="prestige"].in-game .pcard.dark:not(.neon-card) ,
[data-preview-theme="prestige"].in-game .pcard.dark:not(.neon-card) {
  background:
    linear-gradient(160deg, #1c1c1f 0%, #0a0a0c 100%) padding-box,
    linear-gradient(150deg, #0A0A0C 0%, #FFFFFF 52%, #0A0A0C 100%) border-box;
  border: 1.5px solid transparent;
  box-shadow: 0 0 3px #FFFFFF, 0 0 7px rgba(255, 255, 255, .4);
  color: #F2EFE9;
  overflow: visible;
}
/* the armed state's plain border-color (see .hand-row.fanned .pcard.armed
   further up) does nothing here — this theme's border is a GRADIENT
   IMAGE on the border-box layer, not a real border-color, same reason
   Red & Black's table rail needed its own fix earlier. Swap that
   gradient to green instead, on both suit colors, so the neon-outline
   read stays consistent with every other theme. Same specificity as
   .pcard.red/.pcard.dark above (4 classes + 1 type each) — has to come
   after them in the file to actually win. */
body[data-theme="prestige"].in-game .pcard.red.armed,
[data-preview-theme="prestige"].in-game .pcard.red.armed,
body[data-theme="prestige"].in-game .pcard.dark.armed ,
[data-preview-theme="prestige"].in-game .pcard.dark.armed {
  background:
    linear-gradient(165deg, rgba(10, 34, 14, .62) 0%, rgba(5, 14, 6, .72) 100%) padding-box,
    linear-gradient(150deg, #0E4A18 0%, #7CFC9A 52%, #FFFFFF 100%) border-box;
  box-shadow: 0 0 3px #7CFC9A, 0 0 10px rgba(124, 252, 154, .6);
}

/* corner marks: same split layout/positions as the hero cluster
   (.pcard.neon-card in this file) — rank top-left, its outline suit
   top-right at the same height, mirrored bottom pair */
body[data-theme="prestige"].in-game .pcard:not(.neon-card) .pc-corner-rank ,
[data-preview-theme="prestige"].in-game .pcard:not(.neon-card) .pc-corner-rank {
  position: absolute; font-weight: 400;
  font-family: var(--font-head); letter-spacing: -.02em; line-height: 1;
}
body[data-theme="prestige"].in-game .pcard.red:not(.neon-card) .pc-corner-rank ,
[data-preview-theme="prestige"].in-game .pcard.red:not(.neon-card) .pc-corner-rank {
  color: #FFEFEF;
  font-size: calc(var(--cw) * .30);
  filter: drop-shadow(0 0 4px #FF3B4E);
}
body[data-theme="prestige"].in-game .pcard.dark:not(.neon-card) .pc-corner-rank ,
[data-preview-theme="prestige"].in-game .pcard.dark:not(.neon-card) .pc-corner-rank {
  color: #FFFFFF;
  font-size: calc(var(--cw) * .30);
  filter: drop-shadow(0 0 4px #FFFFFF);
}
body[data-theme="prestige"].in-game .pcard:not(.neon-card) .pc-corner-suit ,
[data-preview-theme="prestige"].in-game .pcard:not(.neon-card) .pc-corner-suit {
  position: absolute; display: flex; line-height: 0;
}
body[data-theme="prestige"].in-game .pcard.red:not(.neon-card) .pc-corner-suit ,
[data-preview-theme="prestige"].in-game .pcard.red:not(.neon-card) .pc-corner-suit { filter: drop-shadow(0 0 4px #FF3B4E); }
body[data-theme="prestige"].in-game .pcard.dark:not(.neon-card) .pc-corner-suit ,
[data-preview-theme="prestige"].in-game .pcard.dark:not(.neon-card) .pc-corner-suit { filter: drop-shadow(0 0 4px #FFFFFF); }
body[data-theme="prestige"].in-game .pcard:not(.neon-card) .pc-corner-suit svg ,
[data-preview-theme="prestige"].in-game .pcard:not(.neon-card) .pc-corner-suit svg { width: calc(var(--cw) * .22); height: calc(var(--cw) * .22); }
body[data-theme="prestige"].in-game .pcard.red:not(.neon-card) .pc-corner-suit svg path ,
[data-preview-theme="prestige"].in-game .pcard.red:not(.neon-card) .pc-corner-suit svg path { fill: none; stroke: url(#prestigeRedGrad); stroke-width: 1; }
body[data-theme="prestige"].in-game .pcard.dark:not(.neon-card) .pc-corner-suit svg path ,
[data-preview-theme="prestige"].in-game .pcard.dark:not(.neon-card) .pc-corner-suit svg path { fill: none; stroke: url(#prestigeMonoGrad); stroke-width: 1; }
body[data-theme="prestige"].in-game .pcard:not(.neon-card) .pc-corner-rank-tl ,
[data-preview-theme="prestige"].in-game .pcard:not(.neon-card) .pc-corner-rank-tl { top: 5%; left: 8%; }
body[data-theme="prestige"].in-game .pcard:not(.neon-card) .pc-corner-suit-tr ,
[data-preview-theme="prestige"].in-game .pcard:not(.neon-card) .pc-corner-suit-tr { top: 5%; right: 8%; }
body[data-theme="prestige"].in-game .pcard:not(.neon-card) .pc-corner-rank-br ,
[data-preview-theme="prestige"].in-game .pcard:not(.neon-card) .pc-corner-rank-br { bottom: 5%; right: 8%; transform: rotate(180deg); }
body[data-theme="prestige"].in-game .pcard:not(.neon-card) .pc-corner-suit-bl ,
[data-preview-theme="prestige"].in-game .pcard:not(.neon-card) .pc-corner-suit-bl { bottom: 5%; left: 8%; transform: rotate(180deg); }

/* center pip: true dead-center, same reasoning as .pcard.neon-card —
   the normal off-center position collides with the mirrored corner */
body[data-theme="prestige"].in-game .pcard:not(.neon-card) .pc-center ,
[data-preview-theme="prestige"].in-game .pcard:not(.neon-card) .pc-center {
  left: 50%; top: 52%; right: auto; bottom: auto;
  transform: translate(-50%, -50%);
}
body[data-theme="prestige"].in-game .pcard.red:not(.neon-card) .pc-center svg path ,
[data-preview-theme="prestige"].in-game .pcard.red:not(.neon-card) .pc-center svg path { stroke: url(#prestigeRedGrad); stroke-width: .8; fill: #FF3B4E; }
body[data-theme="prestige"].in-game .pcard.dark:not(.neon-card) .pc-center svg path ,
[data-preview-theme="prestige"].in-game .pcard.dark:not(.neon-card) .pc-center svg path { stroke: url(#prestigeMonoGrad); stroke-width: .8; fill: #0A0A0C; }
body[data-theme="prestige"].in-game .pcard.red:not(.neon-card) .pc-center svg ,
[data-preview-theme="prestige"].in-game .pcard.red:not(.neon-card) .pc-center svg {
  opacity: 1;
  filter: drop-shadow(0 0 4px #FFFFFF) drop-shadow(0 0 9px rgba(255, 59, 78, .6));
}
body[data-theme="prestige"].in-game .pcard.dark:not(.neon-card) .pc-center svg ,
[data-preview-theme="prestige"].in-game .pcard.dark:not(.neon-card) .pc-center svg {
  opacity: 1;
  filter: drop-shadow(0 0 4px #FFFFFF) drop-shadow(0 0 9px rgba(255, 255, 255, .55));
}

/* card back — a genuine third liquid-glass surface, not a recolor of
   the front: a deep PURPLE gradient fill (not the front's navy glass),
   with its own black/white neon gradient border+glow (#prestigeMonoGrad
   — deliberately different from both the front's red and black outline
   colors, so the back reads as its own object), same specular highlight
   + diagonal sheen every other liquid-glass surface in the app uses
   (see LIQUID GLASS SURFACES). */
body[data-theme="prestige"].in-game .pcard.back ,
[data-preview-theme="prestige"].in-game .pcard.back {
  background:
    radial-gradient(ellipse 120% 55% at 50% -8%, rgba(210, 180, 255, .18), transparent 55%) padding-box,
    linear-gradient(148deg, #4B2A8C 0%, #2A1660 46%, #120A2E 100%) padding-box,
    linear-gradient(150deg, #0A0A0C 0%, #FFFFFF 52%, #0A0A0C 100%) border-box;
  border: 1.5px solid transparent;
  box-shadow: var(--glass-edge), var(--glass-shadow), 0 0 8px rgba(255, 255, 255, .35);
  overflow: hidden;
}
body[data-theme="prestige"].in-game .pcard.back::after ,
[data-preview-theme="prestige"].in-game .pcard.back::after {
  content: "";
  position: absolute; inset: 8%;
  border-radius: calc(var(--cw) * .06);
  border: 1px solid rgba(255, 255, 255, .3);
  background: linear-gradient(115deg, transparent 32%, rgba(255, 255, 255, .12) 46%, transparent 58%);
}
body[data-theme="prestige"].in-game .pcard.back::before ,
[data-preview-theme="prestige"].in-game .pcard.back::before {
  content: "";
  position: absolute; inset: 0;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='6.4' fill='none' stroke='url(%23bg)' stroke-width='1'/%3E%3Cpath d='M12 8.4 L14.6 12 12 15.6 9.4 12 Z' fill='none' stroke='url(%23bg)' stroke-width='.9'/%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0%25' stop-color='%230A0A0C'/%3E%3Cstop offset='52%25' stop-color='%23FFFFFF'/%3E%3Cstop offset='100%25' stop-color='%230A0A0C'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E")
    center / 46% no-repeat;
  filter: drop-shadow(0 0 4px rgba(255, 255, 255, .5));
}

/* table felt — true liquid glass: translucent, blurred, catching light
   like frosted glass rather than fabric or lacquer. Same oval shape as
   every other 3D table this session, a glass-tinted fill instead of a
   texture, and the same fat-neon-ring outline Royal/Red & Black use
   (one solid-ish ring + a modest glow halo, directly on .felt, no
   separate padded rail) in this theme's own cool silver/blue instead of
   green or red/gold. */
body[data-theme="prestige"] .felt:not(.shop-preview-felt) ,
[data-preview-theme="prestige"] .felt {
  border-radius: 50% / 14%;
  background:
    radial-gradient(ellipse at 50% 30%, rgba(140, 170, 255, .12), transparent 60%),
    linear-gradient(165deg, rgba(26, 40, 74, .55) 0%, rgba(10, 14, 28, .7) 100%);
  backdrop-filter: blur(6px) saturate(1.3);
  -webkit-backdrop-filter: blur(6px) saturate(1.3);
  border: none;
  box-shadow:
    inset 0 0 90px rgba(0, 0, 0, .55),
    inset 0 1px 0 rgba(200, 215, 255, .14),
    inset 0 0 0 1px rgba(180, 200, 255, .1),
    0 0 0 4px rgba(180, 200, 255, .8),
    0 0 16px 3px rgba(109, 63, 201, .5);
}
body[data-theme="prestige"] .felt:not(.shop-preview-felt)::before ,
[data-preview-theme="prestige"] .felt::before {
  content: "";
  position: absolute; inset: 0;
  border: none; border-radius: 0; box-shadow: none;
  background: url("../icons/thaaslogofi.PNG") center 58% / 30% auto no-repeat;
  filter: grayscale(1);
  opacity: .07;
  pointer-events: none; z-index: 0;
}
/* no separate padded rail — see Royal's own .felt::after for why (same
   fix, same reasoning): content: none rather than deleting the rule, so
   the base .felt::after watermark doesn't fall through and double up
   with ::before's watermark above. */
body[data-theme="prestige"] .felt:not(.shop-preview-felt)::after ,
[data-preview-theme="prestige"] .felt::after {
  content: none;
}

/* HUD: the app's own shared liquid-glass recipe (--glass-grad etc.,
   already used app-wide) applied to the table chrome — this theme IS
   the app's material, just made explicit at the table. */
body[data-theme="prestige"] .dh-bar,
[data-preview-theme="prestige"] .dh-bar,
body[data-theme="prestige"] .match-hud ,
[data-preview-theme="prestige"] .match-hud {
  background: var(--glass-grad);
  border: 1px solid var(--glass-border);
  border-radius: 999px;
  padding-left: 16px; padding-right: 16px;
  backdrop-filter: blur(16px) saturate(1.35);
  -webkit-backdrop-filter: blur(16px) saturate(1.35);
  box-shadow: var(--glass-edge), var(--glass-shadow);
}
body[data-theme="prestige"] .turn-timer ,
[data-preview-theme="prestige"] .turn-timer {
  display: inline-block; padding: 2px 10px; border-radius: 999px;
  background: linear-gradient(180deg, rgba(109, 63, 201, .22), rgba(22, 33, 79, .16));
  border: 1px solid rgba(140, 170, 255, .4);
  color: #B9C6FF;
}
body[data-theme="prestige"] .turn-timer.urgent ,
[data-preview-theme="prestige"] .turn-timer.urgent { color: #FF6A6A; border-color: #FF6A6A; }
body[data-theme="prestige"] .countdown-ring-progress ,
[data-preview-theme="prestige"] .countdown-ring-progress { stroke: #6D3FC9; filter: drop-shadow(0 0 5px #6D3FC9); }
body[data-theme="prestige"] .seat > div:not(.avatar) ,
[data-preview-theme="prestige"] .seat > div:not(.avatar) {
  background: rgba(10, 12, 20, .6);
  border: 1px solid rgba(140, 170, 255, .3);
  border-radius: 8px;
  padding: 1px 8px;
}
body[data-theme="prestige"] .seat .avatar,
[data-preview-theme="prestige"] .seat .avatar,
body[data-theme="prestige"] .match-hud .avatar ,
[data-preview-theme="prestige"] .match-hud .avatar {
  border: 2px solid #6D3FC9; box-shadow: 0 0 0 1px rgba(140, 170, 255, .35), inset 0 0 6px rgba(0, 0, 0, .3);
}
body[data-theme="prestige"].in-game .btn-primary ,
[data-preview-theme="prestige"].in-game .btn-primary {
  background: linear-gradient(180deg, rgba(30, 60, 130, .8) 0%, rgba(16, 30, 74, .85) 55%, rgba(8, 14, 36, .9) 100%);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(140, 170, 255, .4);
  box-shadow: inset 0 1px 0 rgba(200, 215, 255, .22), inset 0 -2px 0 rgba(0, 0, 0, .4), 0 3px 10px rgba(2, 4, 10, .5);
  color: var(--ivory);
}
body[data-theme="prestige"].in-game .btn-gold ,
[data-preview-theme="prestige"].in-game .btn-gold {
  background: linear-gradient(180deg, rgba(109, 63, 201, .82) 0%, rgba(70, 40, 150, .86) 55%, rgba(40, 22, 90, .9) 100%);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(180, 200, 255, .5);
  box-shadow: inset 0 1px 0 rgba(230, 235, 255, .35), inset 0 -2px 0 rgba(20, 10, 45, .5), 0 4px 12px rgba(60, 30, 130, .35);
  color: #FFFFFF;
}

/* landscape table shape (moved here, deliberately last in the file): this
   MUST be declared after all three themes' own base .felt rules above
   (Royal ~line 3640, Red & Black ~4273, Prestige ~4544, each also setting
   border-radius: 50% / 14% unconditionally) — same selector, same
   specificity, so CSS falls back to source order as the tiebreaker
   whenever the media query is also true. Placed earlier in the file (as
   it originally was, right after the other landscape rules around line
   3320) it was silently overridden every time by these later, always-on
   base rules — the exact bug this session's DHASHUNDHAMA/Royal landscape
   fix already ran into once before (see the "declared after it" comment
   near body[data-theme="royal"] .ds-table's own landscape override). See
   that earlier rule's own comment for the full explanation of WHY the
   percentage formula stretches into a capsule in landscape to begin
   with — unchanged here, just relocated so it actually wins. */
@media (max-height: 540px) {
  body[data-theme="royal"] .felt:not(.shop-preview-felt),
[data-preview-theme="royal"] .felt,
  body[data-theme="rednblack"] .felt:not(.shop-preview-felt),
[data-preview-theme="rednblack"] .felt,
  body[data-theme="prestige"] .felt:not(.shop-preview-felt) ,
[data-preview-theme="prestige"] .felt { border-radius: 200px / 80px; }
  body[data-theme="rednblack"] .felt:not(.shop-preview-felt)::after ,
[data-preview-theme="rednblack"] .felt::after { border-radius: 200px / 80px; }
}

/* watermark: 30% of the felt's own width reads fine in portrait (a
   narrow box), but landscape's felt is much wider, so the exact same
   percentage blew the logo up huge. Fixed px size instead, matching
   roughly what 30% actually renders to on a typical portrait phone.
   Deliberately last in the file, same reason as the border-radius rule
   above: Royal/Red & Black/Prestige's own .felt::before watermark rules
   set background-size unconditionally too, at the identical specificity
   (body[data-theme="x"] .felt::before vs body.in-game .felt::before are
   both one type + two classes/pseudo-element deep) — this has to come
   after all three to actually win the cascade. */
@media (max-height: 540px) {
  body.in-game .felt::before, body.in-game .felt::after { background-size: 120px auto; }
}

/* armed-card glow (box-shadow/border-color), moved here from the
   .hand-row.fanned .pcard.armed rule near the top of the file: Royal and
   Red & Black both define body[data-theme="x"].in-game .pcard.in-meld /
   .mg1-3 rules (4 classes + 1 type) that beat a plain, untyped
   .hand-row.fanned .pcard.armed rule (4 classes, no type) on the tied
   class-count/type-selector tiebreak, so an armed card that also happens
   to be part of a currently-detected meld group lost its green glow to
   the theme's gold meld ring instead. Adding body.in-game here matches
   their specificity (4 classes + 1 type each), and being last in the
   file wins the source-order tiebreak against every one of them —
   same fix shape as the border-radius/watermark rules just above. */
body.in-game .hand-row.fanned .pcard.armed {
  transform: rotate(var(--fr, 0deg)) translateY(calc(var(--fy, 0px) - 18px)) scale(1.1);
  border-color: #7CFC9A;
  box-shadow: 0 0 0 2px rgba(124, 252, 154, .5), 0 0 8px 2px rgba(124, 252, 154, .55), 0 0 18px 5px rgba(124, 252, 154, .35), var(--shadow-2);
}
