:root {
  color-scheme: light dark;
  --font-ui: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Noto Sans Devanagari", "Noto Sans Bengali", "Noto Sans Gurmukhi", "Noto Sans Gujarati", "Noto Sans Tamil", "Noto Sans Telugu", "Noto Sans Kannada", "Noto Sans Malayalam", "Noto Sans Thai", "Noto Sans Khmer", "Noto Sans Arabic", "Noto Sans Hebrew", "Noto Sans SC", "Noto Sans JP", "Noto Sans KR", Arial, sans-serif;
  --font-display: "Cooper Black", "Arial Rounded MT Bold", "Arial Black", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", "Noto Sans Devanagari", "Noto Sans Arabic", "Noto Sans SC", "Noto Sans JP", "Noto Sans KR", Arial, sans-serif;
  --font-heavy: "Arial Black", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", "Noto Sans Devanagari", "Noto Sans Arabic", "Noto Sans SC", "Noto Sans JP", "Noto Sans KR", Arial, sans-serif;
  --font-emoji: "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", "Twemoji Mozilla", "Noto Sans Symbols 2", system-ui, sans-serif;
  font-family: var(--font-ui);
  /* Dynamic-viewport unit with a vh fallback: WebViews older than Chromium 108
     cannot parse dvh, and without a fallback the absolute layout collapses to
     height 0 (blank screen). Overridden to 1dvh in the @supports block below. */
  --dvh: 1vh;
  /* Bounded Android font-scale hook. Native reads Configuration.fontScale; the JS bridge validates and
     clamps to [1, 1.3], then writes this on <html>. NEVER consumed raw, and NEVER inside a `font:`
     shorthand — an invalid value there resets family and weight too, changing the typeface rather than
     the size. Every consumer is a `font-size:` LONGHAND wrapped in a per-rule min() ceiling, because the
     safe ceiling depends on the container, not on the setting. Default 1 => byte-identical to today. */
  --ui-scale: 1;
  --red: #e4322d;
  --green: #45a64f;
  --yellow: #ffe34c;
  --blue: #4ea6e5;
  --deep-blue: #163f9a;
  --hud-blue: #173f98;
  --hud-border: #f4d83d;
  --grid: #8d8d8d;
  --white: #f8f8f6;
  --page-bg: #fff3dc;
  --stage-bg: #ffeecb;
  --stage-wash:
    radial-gradient(ellipse at 50% 30%, rgba(255, 255, 255, 0.26), transparent 30%),
    radial-gradient(ellipse at 18% 72%, rgba(255, 205, 96, 0.12), transparent 36%),
    linear-gradient(118deg, rgba(255, 252, 240, 0.12), rgba(255, 214, 130, 0.08) 48%, rgba(196, 120, 30, 0.08));
  --wallpaper-opacity: 0.1;
  --wallpaper-filter: saturate(1.3) contrast(1.12);
  --wallpaper-speed: 16s;
  --immersive-bg-opacity: 0.9;
  --wallpaper-canvas-opacity: 0.28;
  --drawer-bg: rgba(12, 68, 46, 0.9);
  --drawer-border: rgba(255, 255, 255, 0.34);
  --drawer-control-bg: rgba(255, 255, 255, 0.17);
  --lobby-panel: rgba(4, 28, 24, 0.62);
  --lobby-panel-light: rgba(255, 255, 255, 0.58);
  --lobby-ink: #10372e;
  --lobby-title: #fff8bf;
  --lobby-card-top: #346fbe;
  --lobby-card-bottom: #f4c54e;
  --lobby-card-bottom-dark: #db9f25;
  --lobby-locked-top: #637991;
  --lobby-locked-bottom: #d6b45c;
  --lobby-ready: #effff6;
  --lobby-selected: #fff46d;
  --cta-start-top: #fff77f;
  --cta-start-mid: #ffc33a;
  --cta-start-bottom: #f18b23;
  --hud-height: clamp(82px, calc(6.15 * var(--dvh)), 124px);
  --hud-bottom: calc(28 / 2048 * calc(100 * var(--dvh)));
  --status-height: clamp(38px, calc(4.8 * var(--dvh)), 52px);
  /* Shared geometry for the in-game top control row (back · Get Pro · globe ·
     menu): one size, one vertical line, even gaps, so it stays aligned. */
  --top-ctrl: clamp(32px, 8.2vw, 38px);
  --top-edge: clamp(14px, 3.1vw, 32px);
  /* Horizontal safe-area. The payload handled the TOP and BOTTOM insets in 16 places and the left/right
     insets in none — but the notch and Dynamic Island move to a SIDE in landscape, where the top controls
     live. On a 14 Pro at 852x393 that inset is 59px, which is wider than --top-edge ever gets (max 32px),
     so the menu and the back control sat under the hardware.
     max() means this is a NO-OP wherever no inset exists. Android and every non-notched device report 0,
     so their layout is byte-for-byte unchanged. max() is Chromium 79 and env() 69 — both inside the
     Chromium-80 floor that cssFloor.test.cjs enforces. */
  --edge-l: max(clamp(14px, 3.1vw, 32px), env(safe-area-inset-left, 0px));
  --edge-r: max(clamp(14px, 3.1vw, 32px), env(safe-area-inset-right, 0px));
  --top-gap: clamp(8px, 2vw, 12px);
  /* Banner top = 20px edge gap + the safe inset. --ad-safe-top is pushed by native (the AUTHORITATIVE
     display-cutout top in dp); on web/preview it is unset, so env(safe-area-inset-top) is the fallback —
     never both, so no double-counting with the native cutout inset. */
  --top-ad-y: calc(20px + var(--ad-safe-top, env(safe-area-inset-top, 0px)));
  /* No reserved banner strip by default — space is reserved ONLY when an ad has actually loaded during
     gameplay (.banner-filled, set from the native load callback). A no-fill never leaves a gap. */
  --top-ad-h: 0px;
  --top-row-gap: clamp(12px, calc(1.65 * var(--dvh)), 18px);
  --top-y: max(calc(var(--top-ad-y) + var(--top-ad-h) + var(--top-row-gap)), calc(242 / 2048 * 100%));
  --stage-width: min(100vw, 945px);
  --seat-avatar-size: clamp(42px, 9.8vw, 52px);
  /* Dice floor raised 46 -> 54: the old 46px was below the 48px touch-target minimum and
     felt small. 54 is empirically verified safe at the tightest phone (320x568: board stays
     FULL at 316px, ~12px margins; max-safe there is 56). Scales up to 62 on large phones.
     Phones are width-bound, so a bigger dice never shrinks the board (only eats top/bottom
     margin, of which there is plenty). Tablets (>=600px) get their own clamp below. */
  --corner-dice-size: clamp(54px, 12vw, 62px);
  --chrome-bottom: calc(max(84px, calc(226 / 2048 * calc(100 * var(--dvh)))) + clamp(44px, calc(5.2 * var(--dvh)), 60px) + clamp(8px, calc(1.1 * var(--dvh)), 16px));
  --status-top: calc(calc(100 * var(--dvh)) - var(--hud-bottom) - var(--hud-height) - clamp(8px, calc(1.2 * var(--dvh)), 14px) - var(--status-height));
  --board-area: calc(var(--status-top) - var(--chrome-bottom) - clamp(8px, calc(1.2 * var(--dvh)), 16px));
  --board-size: min(calc(var(--stage-width) - 4px), var(--board-area));
  --game-board-shift: 0px;
  --seat-pod-gap: clamp(4px, 1vw, 9px);
  /* The in-game turn-frame border width (single source — the .board-frame border rule consumes it).
     Seat pods/dice anchor to .board-frame's PADDING box, so without compensating for this border
     they visually sat ~2px INSIDE the glowing frame instead of --seat-pod-gap clear of it. */
  --board-frame-border: clamp(7px, 2vw, 12px);
  --board-top-space: calc(var(--chrome-bottom) + (var(--board-area) - var(--board-size)) / 2 + var(--game-board-shift));
  --wallpaper-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260' viewBox='0 0 260 260'%3E%3Cg opacity='.5'%3E%3Crect width='260' height='260' fill='none'/%3E%3Crect x='15' y='15' width='92' height='92' fill='%23f65a4f' opacity='.33'/%3E%3Crect x='153' y='15' width='92' height='92' fill='%234caf67' opacity='.45'/%3E%3Crect x='15' y='153' width='92' height='92' fill='%2350a7dc' opacity='.34'/%3E%3Crect x='153' y='153' width='92' height='92' fill='%23f0d94c' opacity='.42'/%3E%3Crect x='53' y='53' width='38' height='38' fill='%23ffffff' opacity='.42'/%3E%3Crect x='169' y='53' width='38' height='38' fill='%23ffffff' opacity='.35'/%3E%3Crect x='53' y='169' width='38' height='38' fill='%23ffffff' opacity='.35'/%3E%3Crect x='169' y='169' width='38' height='38' fill='%23ffffff' opacity='.34'/%3E%3Cg fill='%23144a35' opacity='.24'%3E%3Ccircle cx='49' cy='49' r='8'/%3E%3Ccircle cx='75' cy='49' r='8'/%3E%3Ccircle cx='49' cy='75' r='8'/%3E%3Ccircle cx='75' cy='75' r='8'/%3E%3Ccircle cx='185' cy='49' r='8'/%3E%3Ccircle cx='211' cy='49' r='8'/%3E%3Ccircle cx='49' cy='185' r='8'/%3E%3Ccircle cx='75' cy='185' r='8'/%3E%3Ccircle cx='185' cy='185' r='8'/%3E%3Ccircle cx='211' cy='185' r='8'/%3E%3C/g%3E%3Cg fill='%23ffffff' opacity='.3'%3E%3Crect x='108' y='15' width='14' height='92'/%3E%3Crect x='124' y='15' width='14' height='92'/%3E%3Crect x='140' y='15' width='14' height='92'/%3E%3Crect x='15' y='108' width='92' height='14'/%3E%3Crect x='15' y='124' width='92' height='14'/%3E%3Crect x='15' y='140' width='92' height='14'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
}

@supports (height: 100dvh) {
  :root {
    --dvh: 1dvh;
  }
}

/* ---- Friends online room ---------------------------------------------- */
.online-lobby {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 16;
  display: grid;
  place-items: stretch center; /* full-screen single-view menu: card fills height, centers width on tablets */
  padding: 0;
  background: rgba(2, 14, 10, 0.62);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.online-lobby[hidden] {
  display: none;
}

.online-card {
  position: relative;
  width: 100%;
  max-width: 520px;
  height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: clamp(7px, 1.3vh, 13px);
  padding: clamp(13px, 2vh, 20px) clamp(16px, 4.6vw, 26px) clamp(13px, 2vh, 20px);
  border-radius: 0;
  border: none;
  background:
    radial-gradient(circle at 86% -8%, rgba(255, 219, 108, 0.16), transparent 42%),
    linear-gradient(168deg, rgba(16, 92, 70, 0.98), rgba(6, 34, 26, 0.99));
  box-shadow: none;
  color: #f4fff8;
  text-align: center;
  animation: home-rise 320ms cubic-bezier(0.18, 0.9, 0.26, 1.06) both;
}

.online-close {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  color: #ffe9a8;
  font: 700 22px/1 var(--font-ui);
}

.online-card h2 {
  margin: 0;
  margin-top: auto; /* with .online-actions margin-bottom:auto → centers the menu block, yet
                       collapses to 0 on overflow so the title stays scrollable (no top-cut) */
  padding: 0 40px; /* clear the absolute × close button (right:12 + 34px) on narrow screens */
  font: 900 clamp(19px, 5.4vw, 26px)/1.1 var(--font-display);
  color: #ffe9a8;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.45);
}

.online-sub {
  margin: 0;
  font: 700 clamp(12px, 3vw, 14px)/1.4 var(--font-ui);
  color: rgba(225, 255, 240, 0.82);
}

.online-actions {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin-bottom: auto; /* pairs with the title's margin-top:auto to center the block (overflow-safe) */
}

.online-actions[hidden] {
  display: none;
}

/* Full-screen single-view friends menu: drop the header sub-line (the Join/Create sections are
   self-explanatory) to save vertical space so everything fits without scrolling. */
.online-sub {
  display: none;
}

.online-primary {
  min-height: clamp(48px, 6.8vw, 58px);
  border-radius: 13px;
  border: 2px solid rgba(255, 247, 153, 0.9);
  background: linear-gradient(180deg, var(--cta-start-top) 0%, var(--cta-start-mid) 50%, var(--cta-start-bottom) 100%);
  color: #14213f;
  font: 900 clamp(15px, 4vw, 19px)/1 var(--font-ui);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  box-shadow: inset 0 2px 0 rgba(255, 255, 255, 0.6), 0 8px 16px rgba(0, 0, 0, 0.3);
}

.online-join {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 9px;
}

.online-join input {
  min-width: 0;
  padding: 0 14px;
  height: clamp(48px, 6.6vw, 58px);
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  background: rgba(2, 20, 16, 0.5);
  color: #fff;
  font: 900 clamp(20px, 5.4vw, 26px)/1 var(--font-display);
  letter-spacing: 0.34em;
  text-align: center;
  text-transform: uppercase;
}

.online-join input::placeholder {
  color: rgba(255, 255, 255, 0.6);
  letter-spacing: 0.28em;
}

/* Friends ONE-screen lobby: JOIN block on top (fastest path, highlighted), an "or start your own"
   divider, then the CREATE block holding the relocated How-to-Win / Players / colour / token rows
   and the Create button at the end. */
.friends-join {
  display: grid;
  gap: 6px;
  padding: 8px 11px;
  border-radius: 14px;
  background: rgba(36, 196, 120, 0.13);
  border: 1.5px solid rgba(36, 196, 120, 0.5);
}
.friends-join-label {
  font: 800 clamp(12px, 3.2vw, 14px)/1 var(--font-ui);
  color: #c4f7da;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.friends-or {
  display: flex;
  align-items: center;
  gap: 10px;
  color: rgba(225, 255, 240, 0.55);
  font: 700 clamp(11px, 2.8vw, 13px)/1 var(--font-ui);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.friends-or::before,
.friends-or::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: rgba(255, 255, 255, 0.14);
}
.friends-create {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.friends-slot {
  display: grid;
}
/* In the friends one-screen, stack the How-to-Win label ABOVE its 2x2 mode grid (cleaner than a
   label floating beside a wrapped grid). Colour/token rows keep their compact inline layout. */
/* The How-to-Win row stacks its label ABOVE the 2x2 mode grid in EVERY setup card (friends lobby
   + the modeConfig dialog) — cleaner than a label floating beside a wrapped grid, and keeps all
   the mode cards looking identical. Two classes so it beats the base .setup-row. */
.setup-row.setup-row-result {
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
}
.setup-row-result .setup-label {
  text-align: left;
}

.online-secondary {
  padding: 0 clamp(16px, 4.4vw, 22px);
  height: clamp(48px, 6.6vw, 58px);
  border-radius: 12px;
  border: 1px solid rgba(189, 250, 235, 0.45);
  background: linear-gradient(160deg, rgba(34, 132, 104, 0.96), rgba(10, 52, 40, 0.96));
  color: #eafff6;
  font: 900 clamp(14px, 3.6vw, 17px)/1 var(--font-ui);
  text-transform: uppercase;
}

.online-room {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  margin-bottom: auto; /* centers the code/QR block with the title's margin-top:auto (overflow-safe) */
}

.online-room[hidden] {
  display: none;
}

.online-room-label {
  font: 800 clamp(10px, 2.6vw, 12px)/1 var(--font-ui);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255, 222, 120, 0.92);
}

.online-code {
  font: 900 clamp(44px, 13vw, 64px)/1 var(--font-display);
  letter-spacing: 0.18em;
  color: #fff;
  text-shadow: 0 3px 0 rgba(0, 0, 0, 0.4), 0 0 18px rgba(255, 216, 92, 0.5);
}

.online-status {
  font: 700 clamp(12px, 3.2vw, 15px)/1.3 var(--font-ui);
  color: rgba(225, 255, 240, 0.85);
}

.online-spinner {
  width: 26px;
  height: 26px;
  margin-top: 4px;
  border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, 0.22);
  border-top-color: #ffd84d;
  animation: spin 0.9s linear infinite;
}

.online-error {
  margin: 0;
  min-height: 1.1em;
  font: 800 clamp(11px, 2.8vw, 13px)/1.3 var(--font-ui);
  color: #ff9a82;
}

.online-error[hidden] {
  display: none;
}

/* ---- Friends invite: room QR, share, toast (rev 109) ---- */
.online-count {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 8px 6px 14px;
  border-radius: 12px;
  background: rgba(2, 20, 16, 0.4);
  border: 1px solid rgba(255, 222, 110, 0.28);
}

.online-count-label {
  font: 800 clamp(11px, 2.8vw, 13px)/1 var(--font-ui);
  letter-spacing: 0.14em;
  color: rgba(255, 222, 120, 0.92);
}

.online-count-opts {
  display: flex;
  gap: 6px;
}

.online-count-btn {
  width: clamp(38px, 10vw, 46px);
  height: clamp(38px, 10vw, 46px);
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.08);
  color: #eafff6;
  font: 900 clamp(15px, 4vw, 18px)/1 var(--font-display);
}

.online-count-btn.is-active {
  background: linear-gradient(180deg, var(--cta-start-top, #ffe27a) 0%, var(--cta-start-bottom, #f0b32f) 100%);
  border-color: rgba(255, 247, 153, 0.9);
  color: #14213f;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6), 0 4px 10px rgba(0, 0, 0, 0.3);
}

.online-qr {
  width: clamp(156px, 46vw, 196px);
  height: auto;
  margin-top: 8px;
  padding: 10px;
  border-radius: 14px;
  background: #ffffff;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4), inset 0 0 0 1px rgba(0, 0, 0, 0.08);
}

.online-qr[hidden] {
  display: none;
}

.online-qr-hint {
  font: 700 clamp(11px, 2.8vw, 13px)/1.35 var(--font-ui);
  color: rgba(225, 255, 240, 0.78);
}

.online-qr-hint[hidden] {
  display: none;
}

.online-share {
  margin-top: 2px;
  min-height: clamp(46px, 6.4vw, 54px);
  padding: 0 clamp(22px, 6vw, 30px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  border-radius: 13px;
  border: 1px solid rgba(120, 255, 196, 0.55);
  background: linear-gradient(160deg, #1fb866 0%, #0e8f4d 100%);
  color: #ffffff;
  font: 900 clamp(14px, 3.8vw, 17px)/1 var(--font-ui);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35), 0 8px 16px rgba(0, 0, 0, 0.32);
}

.online-share[hidden] {
  display: none;
}

.online-share-icon {
  font-size: 1.2em;
}

.online-toast {
  margin: 0;
  padding: 8px 12px;
  border-radius: 10px;
  background: rgba(8, 40, 30, 0.92);
  border: 1px solid rgba(120, 255, 196, 0.4);
  font: 800 clamp(11px, 2.8vw, 13px)/1.3 var(--font-ui);
  color: #d9fff1;
}

.online-toast[hidden] {
  display: none;
}

.gameplay-toast {
  position: absolute;
  left: 50%;
  top: max(calc(var(--top-ad-y) + var(--top-ad-h) + var(--top-gap) + var(--top-ctrl) + 8px), calc(var(--board-top-space) - clamp(48px, 8.5vw, 62px)));
  z-index: 8;
  max-width: min(calc(100% - clamp(104px, 25vw, 150px)), 390px);
  min-height: clamp(36px, 8.6vw, 46px);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 8px;
  padding: 7px 12px 7px 8px;
  border-radius: 999px;
  border: 1px solid rgba(215, 171, 255, 0.42);
  background:
    radial-gradient(circle at 18% 16%, rgba(255, 255, 255, 0.2), transparent 34%),
    linear-gradient(145deg, rgba(45, 17, 92, 0.88), rgba(13, 42, 54, 0.9));
  color: #f9efff;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 9px 24px rgba(20, 6, 44, 0.32),
    0 0 22px rgba(181, 85, 255, 0.28);
  backdrop-filter: blur(14px) saturate(1.15);
  -webkit-backdrop-filter: blur(14px) saturate(1.15);
  transform: translate(-50%, -6px) scale(0.96);
  opacity: 0;
  pointer-events: none;
  transition: opacity 180ms ease, transform 180ms ease;
}

.gameplay-toast[hidden],
.phone-stage.is-home .gameplay-toast,
.phone-stage.call-docked .gameplay-toast {
  display: none;
}
/* Deliberate notices (forceToast) are shown even on the home screen. */
.phone-stage.is-home .gameplay-toast.force-show {
  display: grid;
}

.gameplay-toast.is-visible {
  opacity: 1;
  transform: translate(-50%, 0) scale(1);
}

.gameplay-toast-mark {
  width: clamp(24px, 5.8vw, 30px);
  height: clamp(24px, 5.8vw, 30px);
  border-radius: 50%;
  display: grid;
  place-items: center;
  background:
    radial-gradient(circle at 32% 24%, rgba(255, 255, 255, 0.62), transparent 42%),
    linear-gradient(145deg, #d568ff, #5c2bff 54%, #27c8ff);
  color: #fff;
  font: 950 clamp(9px, 2.4vw, 11px)/1 var(--font-ui);
  letter-spacing: 0;
  box-shadow: 0 0 15px rgba(195, 105, 255, 0.5);
}

.gameplay-toast-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 850 clamp(12px, 3.2vw, 15px)/1.2 var(--font-ui);
}

html[data-theme="light"] .gameplay-toast {
  border-color: rgba(99, 52, 180, 0.28);
  background:
    radial-gradient(circle at 18% 16%, rgba(255, 255, 255, 0.68), transparent 36%),
    linear-gradient(145deg, rgba(245, 236, 255, 0.92), rgba(219, 255, 245, 0.92));
  color: #29184e;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.65),
    0 9px 23px rgba(80, 55, 130, 0.18),
    0 0 20px rgba(164, 93, 255, 0.22);
}

.online-searching {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 17;
  display: grid;
  place-items: center;
  padding: 24px;
  background: rgba(2, 14, 10, 0.72);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.online-searching[hidden] {
  display: none;
}

.searching-card {
  width: min(88%, 340px);
  display: grid;
  justify-items: center;
  gap: 16px;
  padding: clamp(26px, 6vw, 36px) 24px clamp(22px, 5vw, 30px);
  border-radius: 22px;
  border: 1px solid rgba(110, 231, 255, 0.4);
  background:
    radial-gradient(circle at 50% 0%, rgba(110, 231, 255, 0.22), transparent 52%),
    linear-gradient(165deg, rgba(17, 51, 96, 0.97), rgba(6, 22, 44, 0.98));
  box-shadow: 0 22px 50px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.16);
  color: #eaf6ff;
  text-align: center;
  animation: home-rise 360ms cubic-bezier(0.18, 0.9, 0.26, 1.06) both;
}

.searching-orbit {
  position: relative;
  width: 66px;
  height: 66px;
}

.searching-orbit i {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  border-radius: 50%;
  border: 3px solid transparent;
  border-top-color: #56e2ff;
  animation: spin 1.1s linear infinite;
}

.searching-orbit i:nth-child(2) {
  top: 10px; right: 10px; bottom: 10px; left: 10px; /* inset longhands (Chromium<87 WebView support) */
  border-top-color: #ffd84d;
  animation-duration: 1.5s;
  animation-direction: reverse;
}

.searching-orbit i:nth-child(3) {
  top: 20px; right: 20px; bottom: 20px; left: 20px; /* inset longhands (Chromium<87 WebView support) */
  border-top-color: #7af58d;
  animation-duration: 0.9s;
}

.searching-card h2 {
  margin: 0;
  font: 900 clamp(19px, 5vw, 24px)/1.1 var(--font-display);
  color: #d8f6ff;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.45);
}

.searching-card p {
  margin: 0;
  font: 700 clamp(12px, 3vw, 14px)/1.4 var(--font-ui);
  color: rgba(206, 236, 255, 0.84);
}

.searching-cancel {
  margin-top: 4px;
  min-height: clamp(44px, 6vw, 52px);
  padding: 0 clamp(26px, 7vw, 38px);
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  background: linear-gradient(160deg, rgba(120, 40, 40, 0.92), rgba(60, 18, 18, 0.94));
  color: #ffd9d2;
  font: 900 clamp(13px, 3.4vw, 16px)/1 var(--font-ui);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.policy-card {
  width: min(92vw, 430px);
  max-height: min(78vh, 680px);
}

.policy-body {
  max-height: min(48vh, 390px);
  overflow: auto;
  padding: 2px 4px 2px 0;
  color: rgba(239, 255, 248, 0.88);
  font: 750 clamp(12px, 3.1vw, 14px)/1.45 var(--font-ui);
  /* Added, never rewritten: the shorthand above still sets family/weight/line-height. The card's height is
     invariant by construction — .policy-body is max-height + overflow:auto — so this can only change how
     much text is visible before scrolling, never the card. */
  font-size: min(calc(clamp(12px, 3.1vw, 14px) * var(--ui-scale)), 18px);
  text-align: left;
}

.policy-body h3 {
  margin: 12px 0 6px;
  color: #ffe9a8;
  font: 950 clamp(13px, 3.4vw, 16px)/1.15 var(--font-ui);
  /* Headings MUST scale with the body. Scaling only the body would let 18px text sit under 16px headings
     at 1.3 — an inverted hierarchy, inside the legally-required privacy and terms surface. */
  font-size: min(calc(clamp(13px, 3.4vw, 16px) * var(--ui-scale)), 20px);
}

.policy-body p,
.policy-body ul {
  margin: 0 0 9px;
}

.policy-body ul {
  padding-left: 18px;
}

.policy-actions {
  margin-top: 14px;
}

.policy-web-link {
  min-height: 44px;
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 14px;
  border-radius: 999px;
  border: 1px solid rgba(110, 231, 255, 0.24);
  background: rgba(103, 224, 255, 0.1);
  color: #dffaff;
  font: 900 clamp(12px, 3.1vw, 14px)/1 var(--font-ui);
  text-decoration: none;
  box-shadow: var(--rim-top);
}

.policy-web-link[hidden] {
  display: none;
}

.safety-modal {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 18;
  display: grid;
  place-items: center;
  padding: 24px;
  background: rgba(2, 14, 10, 0.78);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.safety-modal[hidden] { display: none; }

/* Premium UX: one-time welcome card, hidden-when-Pro CTA, menu Pro badge. */
.premium-welcome {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 19;
  display: grid;
  place-items: center;
  padding: 24px;
  background: rgba(2, 14, 10, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.premium-welcome[hidden] { display: none; }
.premium-welcome-card {
  width: min(86%, 340px);
  display: grid;
  justify-items: center;
  gap: 10px;
  padding: clamp(22px, 5.5vw, 30px) clamp(20px, 5vw, 26px);
  border-radius: 22px;
  text-align: center;
  border: 1px solid rgba(255, 216, 78, 0.55);
  background: linear-gradient(155deg, rgba(255, 216, 78, 0.16), rgba(255, 255, 255, 0.04) 45%, rgba(34, 150, 110, 0.06)), var(--lobby-panel, rgba(12, 28, 22, 0.92));
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.06), 0 18px 48px rgba(0, 0, 0, 0.5), 0 0 36px rgba(255, 216, 78, 0.22);
  will-change: transform, opacity;
  animation: premiumWelcomeIn 260ms cubic-bezier(0.16, 0.9, 0.24, 1.06) both;
}
.premium-welcome-gem { font-size: 44px; filter: drop-shadow(0 4px 14px rgba(255, 216, 78, 0.5)); }
.premium-welcome-card h2 { margin: 0; font-family: var(--font-ui); font-weight: 800; }
.premium-welcome-card p { margin: 0; opacity: 0.9; line-height: 1.4; max-width: 32ch; }
.premium-welcome-ok {
  margin-top: 6px;
  padding: 10px 22px;
  border-radius: 999px;
  border: 0;
  font-family: var(--font-ui);
  font-weight: 700;
  cursor: pointer;
  background: linear-gradient(135deg, #ffd84e, #f5b500);
  color: #21160a;
  box-shadow: 0 6px 18px rgba(245, 181, 0, 0.36);
}
@keyframes premiumWelcomeIn { from { opacity: 0; transform: scale(0.92); } to { opacity: 1; transform: scale(1); } }
@media (prefers-reduced-motion: reduce) {
  .premium-welcome-card { animation: premiumWelcomeFade 200ms ease both; }
}
@keyframes premiumWelcomeFade { from { opacity: 0; } to { opacity: 1; } }
/* Feature 2: remove the Get Pro CTA entirely for subscribers (2-class specificity beats .no-ads-badge). */
.no-ads-badge.is-premium-hidden { display: none; }
/* Feature 3: passive Pro status badge inside the drawer. */
.drawer-status { display: flex; }
.drawer-status[hidden] { display: none; }
.premium-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: 999px;
  font-weight: 700;
  font-size: 13px;
  color: #ffe9a8;
  background: linear-gradient(135deg, rgba(255, 216, 78, 0.2), rgba(245, 181, 0, 0.1));
  border: 1px solid rgba(255, 216, 78, 0.55);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
.premium-badge-gem { font-size: 14px; line-height: 1; }

/* Living-board home: the real Ludo board IS the mode selector (4 bases + center).
   Two layered canvases (static board + token fx) drawn by the isolated HomeBoard
   module; 5 mode buttons sit over the bases/center and reuse the real mode handler. */
.home-board {
  position: relative;
  margin: 0 auto;
  /* Fill the available width (the lobby caps the stage at 945px, so the board maxes
     there). NOTE: `.home-screen > *` also forces width:100%, so the old
     min(…,58vh,456px) cap never actually applied — made intentional + simple here. */
  width: 100%;
  aspect-ratio: 1 / 1;
  justify-self: center;
  align-self: center;
}
.home-board-layer {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  width: 100%;
  height: 100%;
  border-radius: 16px;
}
.home-board-bg {
  z-index: 0;
  box-shadow:
    0 16px 36px rgba(0, 0, 0, 0.44),
    0 0 0 1px rgba(255, 232, 150, 0.28),
    inset 0 0 0 2px rgba(216, 178, 74, 0.55);
}
/* Tokens are the TOP layer (like the real board) so they pass OVER the centre AI
   box — never hidden under it. pointer-events:none lets taps fall through to the
   mode buttons below. */
.home-board-fx { z-index: 3; pointer-events: none; }
/* The empty board area drives the token swarm, so it owns touch gestures (no
   page scroll-hijack mid-drag); the mode buttons stay normal tap targets. */
.home-board-modes { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 2; touch-action: none; } /* inset longhands (Chromium<87) */
.board-mode {
  position: absolute;
  touch-action: manipulation;
  transform: translate(-50%, -50%);
  width: 27%;
  height: 27%;
  border: 0;
  border-radius: 22%;
  padding: 3.4% 2.8%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3.5%;
  cursor: pointer;
  background:
    radial-gradient(circle at 28% 18%, rgba(255, 255, 255, 0.95), transparent 34%),
    linear-gradient(180deg, #ffffff 0%, #f5f7fb 100%);
  color: #14181f;
  font-family: var(--font-ui);
  box-shadow:
    0 8px 17px rgba(0, 0, 0, 0.34),
    0 2px 0 rgba(255, 255, 255, 0.62),
    inset 0 0 0 2px rgba(0, 0, 0, 0.055);
  transition: transform 130ms ease, box-shadow 130ms ease;
}
.board-mode:active { transform: translate(-50%, -50%) scale(0.94); }
.bm-online { left: 20%; top: 20%; }
.bm-friends { left: 80%; top: 20%; }
.bm-computer { left: 20%; top: 80%; }
.bm-pass { left: 80%; top: 80%; }
.bm-ai {
  left: 50%;
  top: 50%;
  width: 19%;
  height: 19%;
  border-radius: 0;
  padding: 3% 2%;
  gap: 2%;
  background: linear-gradient(160deg, #ffffff 0%, #f1eeff 100%);
  box-shadow: inset 0 0 0 1px #858585;
}
.board-mode .bm-ic {
  font-size: clamp(19px, 6.4vw, 29px);
  line-height: 1;
  flex: none;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.2));
}
.bm-ai .bm-ic { font-size: clamp(14px, 4.9vw, 22px); }
.bm-ai .bm-name { font-size: clamp(9px, 2.9vw, 12px); }
.bm-ai .bm-status { font-size: clamp(6px, 1.85vw, 8px); padding: 1px 5px; }
.board-mode .bm-name {
  font-size: clamp(10px, 3.35vw, 14px);
  font-weight: 900;
  line-height: 1;
  letter-spacing: 0;
  text-align: center;
  white-space: nowrap;
}
.board-mode .bm-status {
  font-size: clamp(7px, 2.05vw, 9px);
  font-weight: 800;
  font-style: normal;
  line-height: 1;
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(60, 150, 40, 0.14);
  color: #2c6112;
  white-space: nowrap;
}
.bm-ai .bm-status { background: rgba(108, 92, 224, 0.16); color: #4a3bc4; }
.board-mode.is-selected { box-shadow: 0 0 0 3px #6c5ce0, 0 8px 20px rgba(0, 0, 0, 0.45); }
.bm-ai.is-selected { box-shadow: inset 0 0 0 2px #6c5ce0; }
.board-mode.is-locked { opacity: 0.62; }
.board-mode.is-locked .bm-status { background: rgba(150, 150, 150, 0.22); color: #555; }
@media (prefers-reduced-motion: reduce) { .board-mode { transition: none; } }

.safety-card {
  width: min(90%, 360px);
  display: grid;
  gap: 14px;
  padding: clamp(22px, 5.5vw, 30px) clamp(20px, 5vw, 26px);
  border-radius: 20px;
  border: 1px solid rgba(255, 222, 110, 0.45);
  background: linear-gradient(165deg, rgba(16, 60, 46, 0.98), rgba(6, 28, 22, 0.99));
  box-shadow: 0 22px 50px rgba(0, 0, 0, 0.5);
  color: #eafff6;
  text-align: center;
}

.safety-card h2 {
  margin: 0;
  font: 900 clamp(19px, 5vw, 24px)/1.1 var(--font-display);
  color: #ffe9a8;
}

.safety-card p {
  margin: 0;
  font: 700 clamp(12px, 3.2vw, 15px)/1.45 var(--font-ui);
  color: rgba(225, 255, 240, 0.88);
}

.safety-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.safety-yes, .safety-no {
  min-height: 48px;
  border-radius: 13px;
  font: 900 clamp(13px, 3.4vw, 15px)/1.1 var(--font-ui);
}

.safety-yes {
  border: 2px solid rgba(255, 247, 153, 0.9);
  background: linear-gradient(180deg, var(--cta-start-top), var(--cta-start-mid) 55%, var(--cta-start-bottom));
  color: #14213f;
}

.safety-no {
  border: 1px solid rgba(189, 250, 235, 0.4);
  background: rgba(255, 255, 255, 0.1);
  color: #eafff6;
}

.online-banner {
  position: absolute;
  left: 50%;
  top: max(80px, calc(150 / 2048 * 100%));
  z-index: 9;
  transform: translateX(-50%);
  max-width: 88%;
  padding: 9px 18px;
  border-radius: 999px;
  border: 1px solid rgba(255, 170, 120, 0.55);
  background: linear-gradient(160deg, rgba(120, 40, 20, 0.95), rgba(70, 20, 10, 0.95));
  color: #ffd9c8;
  font: 800 clamp(12px, 3vw, 14px)/1.2 var(--font-ui);
  text-align: center;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);
}

.online-banner[hidden] {
  display: none;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

* {
  box-sizing: border-box;
}

html,
body {
  width: 100%;
  height: 100%;
}

body {
  margin: 0;
  overflow: hidden;
  background: var(--page-bg);
  color: #fff;
  /* iOS Safari only shipped UNPREFIXED user-select in 17, so on iOS 15/16 (our deployment floor) the bare
     property is ignored and a long press mid-match raises the selection / Look Up callout over the board.
     The prefixed twin is what actually suppresses it there; the input/textarea rule below already pairs both. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
  transition: background 260ms ease;
}

input,
textarea {
  user-select: text;
  -webkit-user-select: text;
}

button,
select {
  font: inherit;
}

button,
select,
.token-hit,
.dice-button {
  /* Suppress the double-tap zoom delay in regular mobile browsers. */
  touch-action: manipulation;
}

button {
  border: 0;
  cursor: pointer;
}

/* R2-035: :focus-visible is Chromium 86+; on the Chromium 80-85 floor an unknown pseudo-class invalidates
   the whole selector list, so this custom ring is a progressive enhancement. Old WebViews fall back to the
   browser's default UA focus outline (still visible + accessible), so no polyfill is warranted. */
button:focus-visible,
select:focus-visible,
input:focus-visible {
  outline: 3px solid #fff46d;
  outline-offset: 3px;
  box-shadow: 0 0 0 6px rgba(35, 203, 255, 0.36);
}

button:disabled {
  cursor: default;
  filter: saturate(0.75) brightness(0.78);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

#wallpaperCanvas {
  /* Retired: the Royal Aurora shader is the single background layer now.
     The element stays for the cached JS reference. */
  display: none;
}

#immersiveBgCanvas {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 0;
  width: 100%;
  height: 100%;
  /* Background stripped to a flat light/dark base (body uses var(--page-bg)) for a
     fresh background redesign. Re-enable / restyle this layer to build the new bg. */
  display: none;
  opacity: 1;
  pointer-events: none;
  background: linear-gradient(180deg, #fff8e6 0%, #ffe7bd 55%, #ffd190 100%);
  transition: background 320ms ease;
}

/* ===== Scenic backdrop: day (sun+clouds) / night (moon+stars), shared land ===== */
#sceneBackdrop {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
html[data-theme="dark"] #sceneBackdrop .scene-day { display: none; }
html[data-theme="light"] #sceneBackdrop .scene-night { display: none; }
/* Mountains + grass are the SAME shapes in both modes — only the fills change. */
html[data-theme="light"] #sceneBackdrop .mtn-back { fill: #93bdc9; }
html[data-theme="light"] #sceneBackdrop .mtn-front { fill: #5fa173; }
html[data-theme="light"] #sceneBackdrop .snow { fill: #ffffff; }
html[data-theme="light"] #sceneBackdrop .grass-back { fill: #7cc566; }
html[data-theme="light"] #sceneBackdrop .grass-front { fill: #57a747; }
html[data-theme="dark"] #sceneBackdrop .mtn-back { fill: #263a5b; }
html[data-theme="dark"] #sceneBackdrop .mtn-front { fill: #182943; }
html[data-theme="dark"] #sceneBackdrop .snow { fill: #b9c8e6; }
html[data-theme="dark"] #sceneBackdrop .grass-back { fill: #20402d; }
html[data-theme="dark"] #sceneBackdrop .grass-front { fill: #142b1d; }
/* Gentle life — clouds sway, halos breathe, stars twinkle (cheap opacity/translate). */
#sceneBackdrop .cloud-a { animation: sceneCloud 26s ease-in-out infinite; }
#sceneBackdrop .cloud-b { animation: sceneCloud 34s ease-in-out infinite reverse; }
#sceneBackdrop .cloud-c { animation: sceneCloud 30s ease-in-out infinite; }
@keyframes sceneCloud { 0%, 100% { transform: translateX(-10px); } 50% { transform: translateX(22px); } }
#sceneBackdrop .sun-halo, #sceneBackdrop .moon-halo { animation: sceneHalo 6s ease-in-out infinite; }
@keyframes sceneHalo { 0%, 100% { opacity: 0.82; } 50% { opacity: 1; } }
#sceneBackdrop .star { animation: sceneTwinkle 3.4s ease-in-out infinite; }
#sceneBackdrop .star.s2 { animation-duration: 4.6s; animation-delay: -1.2s; }
#sceneBackdrop .star.s3 { animation-duration: 2.8s; animation-delay: -0.6s; }
@keyframes sceneTwinkle { 0%, 100% { opacity: 0.35; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { #sceneBackdrop * { animation: none !important; } }
.phone-stage.is-power-paused #sceneBackdrop *,
.phone-stage.is-sustainable-power #sceneBackdrop * { animation: none !important; }

/* ============ 1. AURORA — emerald & gold ============ */
.phone-stage[data-bg="aurora"] #immersiveBgCanvas {
  background:
    radial-gradient(ellipse at 50% 12%, rgba(255, 214, 110, 0.32), transparent 44%) center / cover no-repeat,
    radial-gradient(ellipse at 16% 30%, rgba(40, 200, 140, 0.4), transparent 52%) center / cover no-repeat,
    radial-gradient(ellipse at 84% 78%, rgba(80, 210, 255, 0.3), transparent 48%) center / cover no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='44' height='44'%3E%3Cpath d='M0 44L44 0' stroke='%23ffffff' stroke-opacity='0.07' stroke-width='2'/%3E%3C/svg%3E") 0 0 / 44px 44px repeat,
    linear-gradient(170deg, #eafff3 0%, #bff0d6 50%, #8fe1bb 100%) center / cover no-repeat;
}
html[data-theme="dark"] .phone-stage[data-bg="aurora"] #immersiveBgCanvas {
  background:
    radial-gradient(62% 50% at 50% 42%, rgba(255, 232, 160, 0.18), rgba(255, 210, 120, 0.06) 34%, transparent 58%) center / cover no-repeat,
    radial-gradient(60% 50% at 14% 12%, rgba(34, 150, 110, 0.16), transparent 55%) center / cover no-repeat,
    radial-gradient(60% 50% at 86% 90%, rgba(96, 200, 220, 0.14), transparent 55%) center / cover no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='64' height='64'%3E%3Cg fill='%23ffffff' fill-opacity='0.04'%3E%3Ccircle cx='11' cy='15' r='0.7'/%3E%3Ccircle cx='41' cy='8' r='0.6'/%3E%3Ccircle cx='54' cy='33' r='0.7'/%3E%3Ccircle cx='24' cy='46' r='0.6'/%3E%3C/g%3E%3C/svg%3E") 0 0 / 64px 64px repeat,
    radial-gradient(140% 110% at 50% 30%, #14303a 0%, #0b1f27 44%, #05111a 74%, #030a10 100%) center / cover no-repeat;
}

/* ============ 2. SUNSET GOLD — warm rays ============ */
.phone-stage[data-bg="sunset"] #immersiveBgCanvas {
  background:
    radial-gradient(circle at 50% 2%, rgba(255, 244, 196, 0.85), transparent 38%) center / cover no-repeat,
    radial-gradient(ellipse at 50% 4%, rgba(255, 174, 88, 0.42), transparent 58%) center / cover no-repeat,
    repeating-conic-gradient(from 0deg at 50% -2%, rgba(255, 180, 90, 0.16) 0deg 7deg, transparent 7deg 16deg) center / cover no-repeat,
    linear-gradient(180deg, #ffe7a8 0%, #ffb774 38%, #ff8f9c 70%, #c06bb6 100%) center / cover no-repeat;
}
html[data-theme="dark"] .phone-stage[data-bg="sunset"] #immersiveBgCanvas {
  background:
    radial-gradient(circle at 50% 4%, rgba(255, 198, 120, 0.5), transparent 40%) center / cover no-repeat,
    repeating-conic-gradient(from 0deg at 50% 0%, rgba(255, 150, 80, 0.12) 0deg 7deg, transparent 7deg 16deg) center / cover no-repeat,
    linear-gradient(180deg, #5a2e22 0%, #3a1d34 48%, #1b1430 78%, #0a0a1c 100%) center / cover no-repeat;
}

/* ============ 3. OCEAN DEEP — waves ============ */
.phone-stage[data-bg="ocean"] #immersiveBgCanvas {
  background:
    radial-gradient(ellipse at 50% 10%, rgba(220, 250, 255, 0.6), transparent 48%) center / cover no-repeat,
    radial-gradient(ellipse at 80% 86%, rgba(64, 196, 220, 0.28), transparent 46%) center / cover no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='90' height='28'%3E%3Cpath d='M0 14 Q22 3 45 14 T90 14' fill='none' stroke='%23ffffff' stroke-opacity='0.1' stroke-width='2'/%3E%3C/svg%3E") 0 0 / 90px 28px repeat,
    linear-gradient(180deg, #bfeeff 0%, #7fcfe6 44%, #4aa3c8 100%) center / cover no-repeat;
}
html[data-theme="dark"] .phone-stage[data-bg="ocean"] #immersiveBgCanvas {
  background:
    radial-gradient(ellipse at 50% 12%, rgba(90, 200, 230, 0.26), transparent 50%) center / cover no-repeat,
    radial-gradient(ellipse at 82% 84%, rgba(40, 120, 180, 0.3), transparent 48%) center / cover no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='90' height='28'%3E%3Cpath d='M0 14 Q22 3 45 14 T90 14' fill='none' stroke='%2380e0ff' stroke-opacity='0.1' stroke-width='2'/%3E%3C/svg%3E") 0 0 / 90px 28px repeat,
    linear-gradient(180deg, #04243a 0%, #03182a 52%, #010a16 100%) center / cover no-repeat;
}

/* ============ 4. ROYAL PURPLE — damask diamonds ============ */
.phone-stage[data-bg="royal"] #immersiveBgCanvas {
  background:
    radial-gradient(ellipse at 50% 14%, rgba(255, 232, 168, 0.4), transparent 48%) center / cover no-repeat,
    radial-gradient(ellipse at 18% 82%, rgba(196, 139, 255, 0.34), transparent 46%) center / cover no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='52' height='52'%3E%3Cpath d='M26 6 L46 26 L26 46 L6 26 Z' fill='none' stroke='%23ffe9a8' stroke-opacity='0.16' stroke-width='1.5'/%3E%3C/svg%3E") 0 0 / 52px 52px repeat,
    linear-gradient(170deg, #ece0ff 0%, #d3b8f0 46%, #a877d8 100%) center / cover no-repeat;
}
html[data-theme="dark"] .phone-stage[data-bg="royal"] #immersiveBgCanvas {
  background:
    radial-gradient(ellipse at 50% 12%, rgba(255, 216, 120, 0.22), transparent 50%) center / cover no-repeat,
    radial-gradient(ellipse at 18% 84%, rgba(150, 90, 220, 0.34), transparent 48%) center / cover no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='52' height='52'%3E%3Cpath d='M26 6 L46 26 L26 46 L6 26 Z' fill='none' stroke='%23ffe9a8' stroke-opacity='0.12' stroke-width='1.5'/%3E%3C/svg%3E") 0 0 / 52px 52px repeat,
    linear-gradient(170deg, #2a1346 0%, #1c0d33 52%, #0c0618 100%) center / cover no-repeat;
}

/* ============ 5. LUDO CLASSIC — four-colour board ============ */
.phone-stage[data-bg="ludo"] #immersiveBgCanvas {
  background:
    radial-gradient(ellipse at 50% 12%, rgba(255, 255, 255, 0.5), transparent 46%) center / cover no-repeat,
    var(--wallpaper-image) 0 0 / 150px 150px repeat,
    conic-gradient(from 45deg at 50% 50%, rgba(227, 52, 47, 0.5) 0deg 90deg, rgba(69, 166, 80, 0.5) 90deg 180deg, rgba(255, 228, 80, 0.5) 180deg 270deg, rgba(78, 166, 231, 0.5) 270deg 360deg) center / cover no-repeat,
    linear-gradient(180deg, #fdfaf0, #efe6d2) center / cover no-repeat;
}
html[data-theme="dark"] .phone-stage[data-bg="ludo"] #immersiveBgCanvas {
  background:
    radial-gradient(ellipse at 50% 12%, rgba(255, 255, 255, 0.14), transparent 48%) center / cover no-repeat,
    var(--wallpaper-image) 0 0 / 150px 150px repeat,
    conic-gradient(from 45deg at 50% 50%, rgba(227, 52, 47, 0.32) 0deg 90deg, rgba(69, 166, 80, 0.32) 90deg 180deg, rgba(255, 228, 80, 0.32) 180deg 270deg, rgba(78, 166, 231, 0.32) 270deg 360deg) center / cover no-repeat,
    linear-gradient(180deg, #0a0f1a, #05080f) center / cover no-repeat;
}

.phone-stage {
  position: relative;
  z-index: 1;
  width: min(100vw, 945px);
  height: calc(100 * var(--dvh));
  min-height: 0;
  max-height: 2048px;
  margin: 0 auto;
  /* clip (not hidden): the stage must never become a scroll container,
     otherwise focus/scrollIntoView shifts the absolute layout off-screen.
     `clip` is Chromium 90+ — the `hidden` line before it is the old-engine
     fallback (still clips; modern engines override with clip on the next line). */
  overflow: hidden;
  overflow: clip;
  background: var(--stage-bg);
  transition: background 260ms ease;
}

.phone-stage.call-docked {
  /* Tile size = base * ad-scale, hard-capped. The bottom corner tiles no longer fight
     the control pill (it's now a slim 2x2 grid), so size is bounded by the vertical
     budget (--call-board-limit), not the pill. ad-scale grows the frames when ads are
     removed (the advertised Pro/ad-free perk). */
  --call-tile-base: clamp(82px, 22.6vw, 92px);
  --call-ad-scale: 1;
  --call-tile-size: min(calc(var(--call-tile-base) * var(--call-ad-scale)), 116px);
  --call-tile-edge: clamp(4px, 1.1vw, 8px);
  --call-tile-gap: clamp(6px, 1.6vw, 10px);
  --call-seat-clearance: clamp(48px, 12.6vw, 58px);
  /* How far the corner frames sit from the board edge. --call-frame-spread is an
     EXTRA outward push (top frames up, bottom frames down) that is NOT part of the
     board-size budget, so it spreads the frames without shrinking the board. */
  --call-frame-spread: 0px;
  --call-corner-out: calc(var(--call-tile-size) + var(--call-tile-gap) + var(--call-seat-clearance) + var(--call-frame-spread));
  /* Move the seat pods (avatar + dice) OUT by the same spread, so each frame + its
     pod stay together as one tight corner unit (the spread no longer detaches them). */
  --seat-pod-gap: calc(clamp(4px, 1vw, 9px) + var(--call-frame-spread));
  /* --call-control-size is the labeled button WIDTH; --call-control-pill-height its HEIGHT
     (icon + text). Wide enough for the longest label ("Report"). */
  --call-control-size: clamp(50px, 13.5vw, 60px);
  --call-control-pill-height: clamp(46px, 12.5vw, 52px);
  --call-board-shift: clamp(70px, calc(10 * var(--dvh)), 86px);
  /* Cap the board so the corner tiles always stay on-screen — applied UNCONDITIONALLY
     (was <=700px only), so the bottom tiles never clip off the bottom on ~720-880px
     tall phones. Binds only when the vertical budget is tight; flagships unaffected. */
  --call-board-limit: calc(calc(100 * var(--dvh)) - var(--top-ad-y) - var(--top-ad-h) - var(--top-ctrl) - calc(2 * var(--call-tile-size)) - calc(2 * var(--call-tile-gap)) - calc(2 * var(--call-seat-clearance)) - clamp(54px, calc(8.6 * var(--dvh)), 72px) - env(safe-area-inset-bottom, 0px));
  --board-size: min(calc(var(--stage-width) - 16px), var(--board-area), var(--call-board-limit));
  --board-top-space: calc(var(--chrome-bottom) + (var(--board-area) - var(--board-size)) / 2 + var(--call-board-shift));
  --call-top-tile-y: calc(var(--board-top-space) - var(--call-tile-size) - var(--call-tile-gap) - var(--call-seat-clearance));
  /* Only the menu button sits at the top during a call now (globe -> settings drawer,
     back -> bottom-center chip). The min() pins the top control row just under the
     collapsible ad band but keeps it ABOVE the top corner tiles on short screens. */
  --top-y: min(calc(var(--top-ad-y) + var(--top-ad-h) + clamp(6px, calc(1 * var(--dvh)), 10px)), calc(var(--call-top-tile-y) - var(--top-ctrl) - clamp(8px, calc(1.6 * var(--dvh)), 12px)));
}

/* 360-wide TALL screens (e.g. 360x800) take 100px FREE call frames at ~10px gaps.
   It is the only 360-wide tall size in the target list; wider tall phones (390/412)
   keep the base. min-height:761 keeps this off the short tier (max-height:760 owns those). */
@media (min-height: 761px) and (max-width: 380px) {
  .phone-stage.call-docked {
    --call-tile-size: 100px;
    --call-tile-edge: 0px;
    --call-frame-spread: 31px;
    --call-board-shift: 51px;
    /* Decouple the pods from the big frame spread: the avatar+dice+flag stay together
       as one unit ~2px from the board, while the frames sit out at the 10px screen edge. */
    --seat-pod-gap: clamp(6px, 1.6vw, 8px);
    /* Grow the board to 340 (the budget-cap was a conservative 307; decoupling the pods +
       trimming the spread freed ~33px). Pods are board-relative so they follow it; frames
       re-tuned to stay at 10px edges. Capped against the responsive board-limit (+ the freed
       buffer) so SHORTER 360-wide tall screens shrink the board instead of overflowing. */
    --board-size: min(340px, calc(var(--call-board-limit) + 33px));
  }
}

/* 390-wide TALL screens (e.g. 390x844) — bigger 370px board + 100px FREE frames at 10px
   gaps. min-width:381 keeps this off the 360-wide block above; max-width:400 keeps it off
   the 412-wide phones below. */
@media (min-height: 761px) and (min-width: 381px) and (max-width: 400px) {
  .phone-stage.call-docked {
    --call-tile-size: 110px;
    --call-tile-edge: 0px;
    --call-frame-spread: 26.5px;
    --call-board-shift: 48.7px;
    --seat-pod-gap: clamp(6px, 1.6vw, 8px);
    --board-size: min(370px, calc(var(--call-board-limit) + 45px));
  }
}

/* 412-wide TALL screens — 412x844 specifically (max-height:880 keeps it off 412x915 below;
   min-width:401 keeps it off the 390-wide block above). Board filled to ~392 (width-20) for
   clean 10px L/R; frame left at the base 92 until the user picks a size. */
@media (min-height: 761px) and (max-height: 880px) and (min-width: 401px) {
  .phone-stage.call-docked {
    --call-tile-size: 110px;
    --call-tile-edge: 0px;
    --call-frame-spread: 12.5px;
    --call-board-shift: 39px;
    --seat-pod-gap: clamp(6px, 1.6vw, 8px);
    --board-size: min(392px, calc(var(--call-board-limit) + 73px));
  }
}

/* 412-wide TALL, 412x915 (min-height:881 keeps it off 412x844 above; min-width:401 off the
   390-wide block). Taller screen → board-limit already exceeds 392, so a modest buffer holds
   392 across the band + shrinks gracefully on shorter 412-tall screens. */
@media (min-height: 881px) and (min-width: 401px) {
  .phone-stage.call-docked {
    --call-tile-size: 110px;
    --call-tile-edge: 0px;
    --call-frame-spread: 48px;
    --call-board-shift: 36px;
    --seat-pod-gap: clamp(6px, 1.6vw, 8px);
    --board-size: min(392px, calc(var(--call-board-limit) + 33px));
  }
}

@media (max-height: 760px) {
  .phone-stage.call-docked {
    /* Board is the priority on short screens: shrink the call frames + the dice/avatar
       so --call-board-limit (which caps the board) relaxes and the board grows toward
       its max; the smaller dice also fits in the pod gap (no longer pokes a frame). */
    --call-tile-base: clamp(58px, 16.7vw, 66px);
    --call-tile-edge: -7px; /* frames sit ~10px in from the left/right screen edges (was flush at the edge) */
    --call-tile-gap: clamp(4px, 1.2vw, 7px);
    --call-seat-clearance: clamp(48px, 13.3vw, 56px);
    --call-frame-spread: -1px; /* nudge frames 1px toward the board so the top/bottom edge gap is 10px at board 315 (board untouched) */
    --corner-dice-size: clamp(46px, 10.8vw, 58px);
    --seat-avatar-size: clamp(30px, 8vw, 38px);
    /* Pods are positioned independently of the frame spread here, so the flag stays
       ~2px from the board even while the frames sit near the screen edges. */
    --seat-pod-gap: clamp(6px, 1.6vw, 8px);
    --call-control-size: clamp(46px, 12.8vw, 54px);
    --call-control-pill-height: clamp(25px, 6.9vw, 27px);
    --call-board-shift: clamp(44px, calc(6.8 * var(--dvh)), 56px);
    /* Short tier has small tiles + a single-ROW pill, so it needs a SMALLER board
       reserve than the tall tier — the big tall reserve would over-shrink the board
       and converge the bottom tiles onto the wide row pill. */
    --call-board-limit: calc(calc(100 * var(--dvh)) - var(--top-ad-y) - var(--top-ad-h) - clamp(12px, calc(2.1 * var(--dvh)), 15px) - calc(2 * var(--call-tile-size)) - calc(2 * var(--call-tile-gap)) - calc(2 * var(--call-seat-clearance)) - clamp(4px, calc(0.8 * var(--dvh)), 6px) - env(safe-area-inset-bottom, 0px));
    /* Center the board symmetrically between the banner bottom and the screen bottom,
       so the corner frames get EQUAL empty space above and below (the chrome-bottom
       HUD reserve doesn't apply during a call — the HUD is hidden). */
    --board-top-space: calc((calc(100 * var(--dvh)) + var(--top-ad-y) + var(--top-ad-h) - var(--board-size)) / 2);
  }
}

/* 380px+ wide short screens (e.g. 390x720) take bigger 70px FREE call frames at the
   same 10px edge gaps. 360-wide phones (above) keep their 60px frames untouched.
   The :root.is-ad-free PRO rules outrank this (3 classes vs 2), so PRO stays 85px. */
@media (max-height: 760px) and (min-width: 380px) {
  .phone-stage.call-docked {
    --call-tile-size: 70px;
    --call-tile-edge: 2px;
    --call-frame-spread: 1px;
  }
}

/* ===== Ad-free (Pro or active 24h pass) reclaims the banner strip + grows the
   in-board video frames. Set on <html> (is-ad-free) so the :root-level --top-ad-*
   collapse and EVERY downstream board/dock/top-row var recomputes — the board +
   controls + call tiles all rise into the freed space instead of leaving it dead. */
:root.is-ad-free {
  --top-ad-y: var(--ad-safe-top, env(safe-area-inset-top, 0px));
  --top-ad-h: 0px;
}
:root.is-ad-free .phone-stage.call-docked {
  /* Ads removed -> the whole top band frees (banner gone + Get-Pro badge hidden):
     grow the frames more (the real "bigger video frame in calls" perk) + trim the
     board shift so the higher board leaves room for the bigger bottom tiles. */
  --call-ad-scale: 1.18;
  --call-tile-size: min(calc(var(--call-tile-base) * var(--call-ad-scale)), 128px);
  --call-board-shift: clamp(52px, calc(7.2 * var(--dvh)), 66px);
  --call-control-size: clamp(50px, 13.5vw, 58px);
  --call-tile-gap: clamp(5px, 1.4vw, 8px);
}
@media (max-height: 760px) {
  /* Short screens: the collapsed ad band now pays for a MODEST tile grow (was frozen). */
  :root.is-ad-free .phone-stage.call-docked {
    --call-ad-scale: 1.10;
    --call-tile-size: 85px;
    --call-tile-edge: 2px;
    --call-frame-spread: 1px; /* 85px frame already reaches out; small spread lands it ~10px from top/bottom */
    --call-control-size: clamp(49px, 13.5vw, 56px);
    --call-tile-gap: clamp(4px, 1.2vw, 7px);
    --call-board-shift: clamp(44px, calc(6.8 * var(--dvh)), 56px);
  }
}

/* 380px+ wide short screens (e.g. 390x720) take even bigger 100px PRO frames at the
   same 10px gaps. 360-wide PRO (above) keeps its 85px frames. Same specificity as the
   block above, so source order wins for 380px+; below 380px this never matches. */
@media (max-height: 760px) and (min-width: 380px) {
  :root.is-ad-free .phone-stage.call-docked {
    --call-tile-size: 100px;
    --call-tile-edge: 2px;
    --call-frame-spread: 6px;
  }
}

/* 360-wide TALL screens, PRO (ad-free): the ad-free tile scale makes the frame bigger
   than the FREE 100px AND collapses the banner, so re-tune spread + board-shift to land
   the SAME 10px gaps. Inherits the FREE 360-tall block's tile-edge(0)/board-size(340)/
   decoupled pod-gap. Placed AFTER the PRO base so this board-shift (same specificity) wins. */
@media (min-height: 761px) and (max-width: 380px) {
  :root.is-ad-free .phone-stage.call-docked {
    --call-tile-size: 110px;
    --call-frame-spread: 57px;
    --call-board-shift: 16px;
  }
}

/* 390-wide TALL screens, PRO (ad-free): 120px frames at 10px gaps, board held at 370
   (bigger frame eats more budget, so a larger buffer keeps 370). Re-tunes spread +
   board-shift (ad-free collapses the banner). After the PRO base so board-shift wins. */
@media (min-height: 761px) and (min-width: 381px) and (max-width: 400px) {
  :root.is-ad-free .phone-stage.call-docked {
    --call-tile-size: 120px;
    --call-frame-spread: 51px;
    --call-board-shift: 14.5px;
    --board-size: min(370px, calc(var(--call-board-limit) + 65px));
  }
}

/* 412-wide TALL, PRO (ad-free) — 412x844 (max-height:880, min-width:401). 130px frames at
   10px gaps, board held at 392 (the big frame eats the budget, so a large buffer keeps 392).
   Re-tunes spread + board-shift. After the PRO base so its board-shift wins by source order. */
@media (min-height: 761px) and (max-height: 880px) and (min-width: 401px) {
  :root.is-ad-free .phone-stage.call-docked {
    --call-tile-size: 130px;
    --call-frame-spread: 27.5px;
    --call-board-shift: 4.75px;
    --board-size: min(392px, calc(var(--call-board-limit) + 113px));
  }
}

/* 412-wide TALL, PRO (ad-free) — 412x915 (min-height:881, min-width:401). 130px frames at
   10px gaps, board 392. Taller screen centers the board lower, so the board-shift goes
   NEGATIVE to pull it back up. After the PRO base so its board-shift wins. */
@media (min-height: 881px) and (min-width: 401px) {
  :root.is-ad-free .phone-stage.call-docked {
    --call-tile-size: 130px;
    --call-frame-spread: 63px;
    --call-board-shift: 1.6px;
    --board-size: min(392px, calc(var(--call-board-limit) + 42px));
  }
}

/* ===== Roomy phones have spare vertical budget, so give FREE in-call frames a real
   size bump too (ad-free grows from this same bigger base). Gated to roomy screens
   only — very tall (>=880px any width) OR narrow-and-tall (>=800px on <=400px wide) —
   so the tight ~760-870px mid-tall band keeps the safe base size and never clips. ===== */
@media (min-height: 880px) {
  .phone-stage.call-docked { --call-tile-base: clamp(90px, 25vw, 104px); }
}
@media (min-height: 800px) and (max-width: 400px) {
  .phone-stage.call-docked { --call-tile-base: clamp(90px, 25vw, 104px); }
}

/* In-call the menu button keeps its default top-right spot (globe moved to the
   settings drawer, back moved to the bottom-center chip), so the top declutters to a
   single button and the top corners are free for the video tiles. */

/* Hide the Get-Pro upsell badge during a call: it's a full-width top-center pill that
   would sit under the cluster (and ad-free already hides it). Still reachable from the
   menu drawer (drawerProButton). */
/* In-call top, center column: banner -> Get-Pro CTA -> menu (Exit stays bottom-center).
   The Get-Pro badge shows on FREE only; ad-free/Pro hides it via .is-premium-hidden. */
.phone-stage.call-docked .no-ads-badge {
  top: calc(var(--top-ad-y) + var(--top-ad-h) + clamp(6px, 1.4vw, 10px));
  left: 0;
  right: 0;
}
.phone-stage.call-docked .menu-button {
  left: 50%;
  right: auto;
  transform: translateX(-50%);
  top: calc(var(--top-ad-y) + var(--top-ad-h) + clamp(6px, 1.4vw, 10px) + var(--top-ctrl) + clamp(8px, 2vw, 12px));
}
.phone-stage.call-docked .menu-button:active { transform: translateX(-50%) scale(0.94); }

/* Non-call in-game (any mode): same centred top column as the in-call layout
   (Get-Pro CTA over the Menu button), so call and non-call stay consistent. */
.phone-stage[data-game-mode]:not(.call-docked):not(.is-home) .no-ads-badge {
  top: calc(var(--top-ad-y) + var(--top-ad-h) + clamp(6px, 1.4vw, 10px));
  left: 0;
  right: 0;
  /* audit #8: the Get-Pro upsell is the intended tap target — keep its gem ABOVE the side buttons (Exit z8, Menu)
     as a belt-and-suspenders safety net, so even a residual overlap can never let a neighbor steal its tap. */
  z-index: 9;
}
.phone-stage[data-game-mode]:not(.call-docked):not(.is-home) .menu-button {
  left: auto;
  right: var(--edge-r);
  transform: none;
  top: calc(var(--top-ad-y) + var(--top-ad-h) + clamp(6px, 1.4vw, 10px));
}
.phone-stage[data-game-mode]:not(.call-docked):not(.is-home) .menu-button:active { transform: scale(0.94); }
/* Redesign (board-space): Menu sits TOP-RIGHT and Exit/Leave TOP-LEFT, both on the SAME
   tier as the centered Get-Pro (just below the ad band) — one tidy top row. Frees the
   bottom (Exit was bottom-centre) so the board can use more vertical room. In ad-free the
   --top-ad-* collapse to 0, so the whole row rises to the very top automatically. */
.phone-stage[data-game-mode]:not(.call-docked):not(.is-home) .home-back-button {
  top: calc(var(--top-ad-y) + var(--top-ad-h) + clamp(6px, 1.4vw, 10px));
  left: var(--edge-l);
  right: auto;
  bottom: auto;
  transform: none;
  /* audit #8: CAP the Exit/Leave pill to a bounded LEFT lane so a long-label locale (e.g. Tamil) can't grow it
     into the centered Get-Pro pill and steal taps on its gem. The label ellipsizes; the chevron always shows. */
  max-width: clamp(72px, 26vw, 132px);
}
.phone-stage[data-game-mode]:not(.call-docked):not(.is-home) .home-back-button:active { transform: scale(0.95); }

.phone-stage[data-game-mode="ai"]:not(.call-docked),
.phone-stage[data-game-mode="computer"]:not(.call-docked),
.phone-stage[data-game-mode="pass-play"]:not(.call-docked) {
  --noncall-seat-clearance: calc(var(--corner-dice-size) + var(--seat-pod-gap) + clamp(6px, calc(1.0 * var(--dvh)), 12px));
  /* Bottom reserve must be AT LEAST the top seat-clearance: the bottom pods are the same
     size as the top pods, so reserving less below clips them off-screen (seen on tablets,
     where the corner dice grow to 9vw and the clearance outgrows the dvh clamp). On phones
     the clamp already exceeds the clearance, so this max() is a no-op there. */
  --noncall-bottom-reserve: max(clamp(60px, calc(7.2 * var(--dvh)), 84px), var(--noncall-seat-clearance));
  /* Centre the board for EQUAL empty space above (control row -> top pods) and below
     (bottom pods -> screen edge). Reference the REAL control-row bottom
     (top-ad-y + top-ad-h + gap + top-ctrl), NOT --top-y: on tall screens --top-y resolves
     to the 242/2048*100dvh ratio which over-reserves vs where the buttons actually sit,
     biasing the board low (the bias grew with screen height). board-frame top =
     (100dvh + control-row-bottom - board-size) / 2. */
  --noncall-balanced-board-top: calc((calc(100 * var(--dvh)) + var(--top-ad-y) + var(--top-ad-h) + clamp(6px, 1.4vw, 10px) + var(--top-ctrl) - var(--board-size)) / 2);
  --game-board-shift: 0px;
  --seat-pod-gap: clamp(6px, 1.35vw, 10px);
  /* Size the board against the REAL control-row bottom (not the over-reserving --top-y, whose
     242/2048 ratio wastes ~40px of top space on tall/tablet screens) — consistent with the
     centering above. Lets height-bound portrait tablets reclaim that space for a bigger, centred
     board. Phones are width-bound so board-size is unchanged there. */
  --board-area: calc(calc(100 * var(--dvh)) - var(--top-ad-y) - var(--top-ad-h) - clamp(6px, 1.4vw, 10px) - var(--top-ctrl) - var(--noncall-seat-clearance) - var(--noncall-bottom-reserve) - var(--board-breathing, 0px));
  --board-size: min(calc(var(--stage-width) - 4px), var(--board-area));
  --board-top-space: max(calc(var(--top-ad-y) + var(--top-ad-h) + clamp(6px, 1.4vw, 10px) + var(--top-ctrl) + var(--noncall-seat-clearance)), var(--noncall-balanced-board-top));
}

.phone-stage[data-network-mode="true"]:not(.call-docked) {
  --network-seat-clearance: calc(var(--corner-dice-size) + var(--seat-pod-gap) + clamp(6px, calc(1.0 * var(--dvh)), 12px));
  /* Voice/video/chat removed: the online board now uses the SAME full edge-to-edge bottom
     reserve as the offline modes (no callbar to leave room for). */
  /* Same as offline: bottom reserve >= seat-clearance so the bottom pods never clip. */
  --network-bottom-reserve: max(clamp(60px, calc(7.2 * var(--dvh)), 84px), var(--network-seat-clearance));
  --network-safe-top: calc(var(--top-ad-y) + var(--top-ad-h) + clamp(6px, 1.4vw, 10px) + var(--top-ctrl) + var(--network-seat-clearance));
  /* Same as offline: size against the real control-row bottom (not --top-y) so portrait
     tablets get a bigger, centred board; phones (width-bound) unchanged. */
  --board-area: calc(calc(100 * var(--dvh)) - var(--top-ad-y) - var(--top-ad-h) - clamp(6px, 1.4vw, 10px) - var(--top-ctrl) - var(--network-seat-clearance) - var(--network-bottom-reserve) - var(--board-breathing, 0px));
  --board-size: min(calc(var(--stage-width) - 4px), var(--board-area));
  /* Same balanced centre as the offline tier (online now uses the identical edge-to-edge
     layout, calls removed): equal empty space above and below, referencing the real
     control-row bottom rather than the over-reserving --top-y. */
  --network-balanced-board-top: calc((calc(100 * var(--dvh)) + var(--top-ad-y) + var(--top-ad-h) + clamp(6px, 1.4vw, 10px) + var(--top-ctrl) - var(--board-size)) / 2);
  --game-board-shift: 0px;
  --seat-pod-gap: clamp(6px, 1.35vw, 10px);
  --board-top-space: max(var(--network-safe-top), var(--network-balanced-board-top));
}

.phone-stage::before {
  /* Retired: the tiled wallpaper drift layer is replaced by the aurora shader. */
  content: none;
}

.phone-stage::after {
  /* Stage-wash glow retired with the wallpaper/aurora layers — flat base only now. */
  content: none;
}

.phone-stage.is-power-idle::before {
  animation-duration: calc(var(--wallpaper-speed) * 2.6);
}

.phone-stage.is-power-paused *,
.phone-stage.is-power-paused::before,
.phone-stage.is-power-paused::after {
  animation-play-state: paused !important;
}

.phone-stage.is-sustainable-power::before {
  animation: none;
  opacity: calc(var(--wallpaper-opacity) * 0.82);
}

.phone-stage.is-sustainable-power.is-home *,
.phone-stage.is-sustainable-power.is-home *::before,
.phone-stage.is-sustainable-power.is-home *::after {
  animation: none !important;
}

.phone-stage.is-sustainable-power #immersiveBgCanvas {
  opacity: calc(var(--immersive-bg-opacity) * 0.62);
  filter: saturate(1.02) contrast(1);
}

.phone-stage.is-thermal-power #immersiveBgCanvas {
  opacity: calc(var(--immersive-bg-opacity) * 0.46);
}

.phone-stage.is-sustainable-power .menu-button,
.phone-stage.is-sustainable-power .menu-button::after,
.phone-stage.is-sustainable-power .no-ads-badge,
.phone-stage.is-sustainable-power .no-ads-badge::after,
.phone-stage.is-sustainable-power .mode-card::after,
.phone-stage.is-sustainable-power .start-mode-button,
.phone-stage.is-sustainable-power .start-mode-button::after,
.phone-stage.is-sustainable-power .home-hero::before,
.phone-stage.is-sustainable-power .home-crown,
.phone-stage.is-sustainable-power .home-piece,
.phone-stage.is-sustainable-power .home-dice,
.phone-stage.is-sustainable-power .board-ad-slot::after,
.phone-stage.is-sustainable-power .ad-slot-placeholder::after,
.phone-stage.is-sustainable-power .hud-token,
.phone-stage.is-sustainable-power .hud-name::after,
.phone-stage.is-sustainable-power .turn-badge,
.phone-stage.is-sustainable-power .status-chip::before,
.phone-stage.is-sustainable-power .status-chip.is-busy::after,
.phone-stage.is-sustainable-power .dice-button:not(.rolling):not(.landed),
.phone-stage.is-sustainable-power.turn-green .dice-button:disabled {
  animation: none !important;
}

.phone-stage.is-sustainable-power .bottom-hud,
.phone-stage.is-sustainable-power .bottom-hud::before,
.phone-stage.is-sustainable-power .bottom-hud::after,
.phone-stage.is-sustainable-power .dice-button,
.phone-stage.is-sustainable-power .status-chip,
.phone-stage.is-sustainable-power .drawer,
.phone-stage.is-sustainable-power .profile-card,
.phone-stage.is-sustainable-power .mode-panel,
.phone-stage.is-sustainable-power .home-start-panel,
.phone-stage.is-sustainable-power .hud-token,
.phone-stage.is-sustainable-power .lang-overlay,
.phone-stage.is-sustainable-power .players-strip {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.phone-stage.is-sustainable-power.turn-blue .you-name,
.phone-stage.is-sustainable-power.turn-green .com-name {
  filter: drop-shadow(0 0 5px rgba(255, 222, 64, 0.58));
}

.phone-stage.is-sustainable-power .dice-button:not(:disabled) {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.78),
    inset 0 -8px 14px rgba(56, 71, 90, 0.14),
    0 8px 14px rgba(0, 0, 0, 0.22),
    0 0 14px rgba(255, 226, 87, 0.38);
}

.phone-stage.is-power-idle .hud-name.is-marquee .hud-name-text {
  animation-play-state: paused;
}

/* Idle battery saver: pause the perpetual paint-heavy shine/pulse loops while
   nothing is happening. play-state (not none) resumes them seamlessly on touch. */
.phone-stage.is-power-idle .menu-button,
.phone-stage.is-power-idle .menu-button::after,
.phone-stage.is-power-idle .no-ads-badge,
.phone-stage.is-power-idle .no-ads-badge::after,
.phone-stage.is-power-idle .start-mode-button::after,
.phone-stage.is-power-idle .mode-card::after,
.phone-stage.is-power-idle .board-ad-slot::after,
.phone-stage.is-power-idle .ad-slot-placeholder::after,
.phone-stage.is-power-idle .hud-token,
.phone-stage.is-power-idle .hud-name::after,
.phone-stage.is-power-idle .turn-badge,
.phone-stage.is-power-idle .status-chip::before,
.phone-stage.is-power-idle .status-chip.is-busy::after,
.phone-stage.is-power-idle .dice-button:not(.rolling):not(.landed),
.phone-stage.is-power-idle.turn-green .dice-button:disabled {
  animation-play-state: paused !important;
}

html[data-theme="dark"] {
  --page-bg: #010403;
  --stage-bg: #020705;
  --stage-wash:
    radial-gradient(circle at 50% 34%, rgba(35, 255, 180, 0.085), transparent 29%),
    radial-gradient(ellipse at 82% 73%, rgba(72, 153, 255, 0.05), transparent 32%),
    linear-gradient(118deg, rgba(20, 255, 164, 0.065), rgba(5, 26, 22, 0.5) 48%, rgba(0, 0, 0, 0.74)),
    rgba(0, 0, 0, 0.32);
  --wallpaper-opacity: 0.14;
  --wallpaper-filter: saturate(1.08) contrast(1);
  --wallpaper-speed: 20s;
  --immersive-bg-opacity: 0.96;
  --wallpaper-canvas-opacity: 0.22;
  --drawer-bg: rgba(2, 8, 7, 0.93);
  --drawer-border: rgba(112, 255, 188, 0.2);
  --drawer-control-bg: rgba(255, 255, 255, 0.08);
  --lobby-panel: rgba(2, 13, 11, 0.76);
  --lobby-panel-light: rgba(151, 255, 222, 0.14);
  --lobby-ink: #eafff7;
  --lobby-title: #fff8bf;
  --lobby-card-top: #173c83;
  --lobby-card-bottom: #d8a83c;
  --lobby-card-bottom-dark: #a77418;
  --lobby-locked-top: #263b51;
  --lobby-locked-bottom: #796532;
  --lobby-ready: #e9fff2;
  --lobby-selected: #fff06e;
  --cta-start-top: #fff174;
  --cta-start-mid: #ffbd38;
  --cta-start-bottom: #ef8620;
  --wallpaper-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260' viewBox='0 0 260 260'%3E%3Cg opacity='.5'%3E%3Crect width='260' height='260' fill='none'/%3E%3Crect x='15' y='15' width='92' height='92' fill='%231f5240' opacity='.36'/%3E%3Crect x='153' y='15' width='92' height='92' fill='%2326503e' opacity='.34'/%3E%3Crect x='15' y='153' width='92' height='92' fill='%231c3d56' opacity='.32'/%3E%3Crect x='153' y='153' width='92' height='92' fill='%235a5122' opacity='.24'/%3E%3Crect x='53' y='53' width='38' height='38' fill='%23bfffe2' opacity='.14'/%3E%3Crect x='169' y='53' width='38' height='38' fill='%23bfffe2' opacity='.12'/%3E%3Crect x='53' y='169' width='38' height='38' fill='%23bfffe2' opacity='.12'/%3E%3Crect x='169' y='169' width='38' height='38' fill='%23bfffe2' opacity='.1'/%3E%3Cg fill='%23000000' opacity='.35'%3E%3Ccircle cx='49' cy='49' r='8'/%3E%3Ccircle cx='75' cy='49' r='8'/%3E%3Ccircle cx='49' cy='75' r='8'/%3E%3Ccircle cx='75' cy='75' r='8'/%3E%3Ccircle cx='185' cy='49' r='8'/%3E%3Ccircle cx='211' cy='49' r='8'/%3E%3Ccircle cx='49' cy='185' r='8'/%3E%3Ccircle cx='75' cy='185' r='8'/%3E%3Ccircle cx='185' cy='185' r='8'/%3E%3Ccircle cx='211' cy='185' r='8'/%3E%3C/g%3E%3Cg fill='%23a8ffd8' opacity='.12'%3E%3Crect x='108' y='15' width='14' height='92'/%3E%3Crect x='124' y='15' width='14' height='92'/%3E%3Crect x='140' y='15' width='14' height='92'/%3E%3Crect x='15' y='108' width='92' height='14'/%3E%3Crect x='15' y='124' width='92' height='14'/%3E%3Crect x='15' y='140' width='92' height='14'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
}

html[data-theme="light"] {
  --page-bg: #fff3dc;
  --stage-bg: #ffeecb;
  --stage-wash:
    radial-gradient(ellipse at 50% 30%, rgba(255, 255, 255, 0.26), transparent 30%),
    radial-gradient(ellipse at 18% 72%, rgba(255, 205, 96, 0.12), transparent 36%),
    linear-gradient(118deg, rgba(255, 252, 240, 0.12), rgba(255, 214, 130, 0.08) 48%, rgba(196, 120, 30, 0.08));
  --wallpaper-opacity: 0.1;
  --wallpaper-filter: saturate(1.34) contrast(1.16);
  --wallpaper-speed: 15s;
  --immersive-bg-opacity: 0.9;
  --wallpaper-canvas-opacity: 0.3;
  --lobby-panel: rgba(24, 125, 89, 0.36);
  --lobby-panel-light: rgba(255, 255, 255, 0.58);
  --lobby-ink: #123c33;
  --lobby-title: #fff8bf;
  --lobby-card-top: #3f77c8;
  --lobby-card-bottom: #f5c956;
  --lobby-card-bottom-dark: #dea631;
  --lobby-locked-top: #7890a4;
  --lobby-locked-bottom: #dec06d;
}


.spark-layer {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 7;
  pointer-events: none;
  overflow: hidden;
}

.dice-flight-layer {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 12;
  pointer-events: none;
  overflow: hidden;
  perspective: 900px;
  perspective-origin: 50% 42%;
}

.phone-stage.is-home .no-ads-badge,
.phone-stage.is-home .board-frame,
.phone-stage.is-home .menu-button,
.phone-stage.is-home .home-back-button,
.phone-stage.is-home .lang-button-ingame,
.phone-stage.is-home .status-chip,
.phone-stage.is-home .bottom-hud {
  display: none;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 220ms ease, visibility 220ms ease;
}

.phone-stage.is-home .drawer {
  z-index: 14;
}

/* The home top-bar reuses the animated .menu-button; keep it visible on home
   (the rule above hides the in-game chrome). */
.phone-stage.is-home .menu-button.home-menu {
  display: grid;
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* ===== Onyx & Aurum design tokens (home-screen glow-up) ===== */
:root {
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px;
  --r-card: 16px; --r-chip: 12px; --r-pill: 999px;
  --gold-1: #f7e7b6; --gold-2: #e8c76b; --gold-3: #b8893b; --gold-ink: #1c1305;
  --hairline: rgba(247, 231, 182, 0.22); --hairline-strong: rgba(247, 231, 182, 0.4);
  --ink: #ecefea; --ink-2: rgba(236, 239, 234, 0.64); --ink-3: rgba(236, 239, 234, 0.42);
  --ai-cyan: #6ee7ff;
  --e1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --e2: 0 8px 24px -8px rgba(0, 0, 0, 0.55);
  --e3: 0 20px 48px -12px rgba(0, 0, 0, 0.6);
  --inset-top: inset 0 1px 0 rgba(255, 255, 255, 0.1);
  --surface-1: rgba(255, 255, 255, 0.045); --surface-2: rgba(255, 255, 255, 0.075);
  /* Cinematic Arena — lit-table world (home + gameplay share this lighting) */
  --arena-1: #16323a; --arena-2: #0c2027; --arena-3: #06121a; --arena-edge: #030a10;
  --candle: rgba(255, 228, 150, 0.3); --candle-soft: rgba(255, 210, 120, 0.1);
  --counter-emerald: rgba(34, 150, 110, 0.16); --counter-cyan: rgba(96, 200, 220, 0.12);
  --amb-warm: 0 0 28px rgba(255, 176, 72, 0.14);
  --rim-top: inset 0 1px 0 rgba(255, 231, 170, 0.2);
  --rim-bottom: 0 1px 0 rgba(0, 0, 0, 0.32);
}

.home-screen {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 10;
  display: grid;
  grid-template-rows: auto auto auto auto;
  align-content: safe center;
  gap: clamp(6px, calc(1.1 * var(--dvh)), 13px);
  padding: clamp(9px, calc(1.65 * var(--dvh)), 20px) clamp(8px, 2.8vw, 16px) clamp(9px, calc(1.35 * var(--dvh)), 18px);
  color: #fff;
  font-family: var(--font-ui);
  /* x stays clipped: the featured-card sheen animates beyond the viewport and
     must never create a horizontal scroll area. */
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Transparent so the scenic backdrop (#sceneBackdrop) shows behind the lobby. */
  background: transparent;
}

html[data-theme="light"] .home-screen {
  /* Transparent in light mode too — the day scene shows behind. */
  background: transparent;
}

.home-screen[hidden] {
  display: none;
}

.home-screen > * {
  min-width: 0;
  width: 100%;
  max-width: 100%;
}

/* Tap-a-mode-card config popup: shared token style picker, plus mode-specific setup. */
.mode-config {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 16;
  display: grid;
  place-items: stretch center; /* full-screen single-view (matches the Friends card) */
  padding: 0;
  background: rgba(2, 14, 10, 0.62);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.mode-config[hidden] {
  display: none;
}

.mode-config-card {
  position: relative;
  width: 100%;
  max-width: 520px;
  height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: clamp(7px, 1.3vh, 13px);
  padding: clamp(13px, 2vh, 20px) clamp(16px, 4.6vw, 26px);
  border-radius: 0;
  border: none;
  background:
    radial-gradient(circle at 86% -8%, rgba(255, 219, 108, 0.16), transparent 42%),
    linear-gradient(168deg, rgba(16, 92, 70, 0.98), rgba(6, 34, 26, 0.99));
  box-shadow: none;
  color: #f4fff8;
  text-align: center;
  animation: home-rise 320ms cubic-bezier(0.18, 0.9, 0.26, 1.06) both;
}

.mode-config-close {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  color: var(--ink-2);
  font: 700 22px/1 var(--font-ui);
}

.mode-config-head {
  display: flex;
  align-items: center;
  justify-content: center; /* centred header to match the Friends full-screen card */
  gap: 12px;
  margin-top: auto; /* with the Start button's margin-bottom:auto → centre the block, overflow-safe */
  padding-bottom: 2px;
}

.mode-config-head .dock-mode-text {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.mode-config-head strong {
  font: 900 clamp(18px, 5vw, 22px)/1.05 var(--font-display);
  color: #ffe9a8;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.45);
}

.mode-config-head #selectedModeMeta {
  font: 700 clamp(12px, 3vw, 14px)/1.3 var(--font-ui);
  color: rgba(225, 255, 240, 0.82);
}

.mode-config .start-mode-button {
  width: 100%;
  min-height: clamp(48px, 6.8vw, 58px);
  margin-top: 0;
  margin-bottom: auto; /* pairs with the head's margin-top:auto → centred block on the full card */
}

/* Step 1 → Step 2 "Back" link in the 2-step network (Online/Friends) setup. */
.setup-back-btn {
  display: block;
  width: auto;
  margin: 0 auto 8px;
  padding: 6px 14px;
  background: none;
  border: none;
  color: var(--lobby-panel-light, rgba(255, 255, 255, 0.72));
  font: inherit;
  font-weight: 700;
  font-size: 0.92rem;
  opacity: 0.85;
  cursor: pointer;
}
.setup-back-btn:hover { opacity: 1; }
.setup-back-btn[hidden] { display: none; }

/* Staggered entrance: each band rises into place when the lobby opens. */
.home-topbar,
.home-hero,
.daily-challenge,
.mode-panel,
.home-start-panel {
  animation: home-rise 460ms cubic-bezier(0.18, 0.9, 0.26, 1.06) both;
}

.home-hero { animation-delay: 70ms; }
.daily-challenge { animation-delay: 110ms; }
.mode-panel { animation-delay: 150ms; }
.home-start-panel { animation-delay: 230ms; }

.mode-panel .mode-card {
  animation: home-rise 420ms cubic-bezier(0.18, 0.9, 0.26, 1.06) both;
}

.mode-panel .mode-card-featured { animation-delay: 170ms; }
.mode-grid .mode-card:nth-child(1) { animation-delay: 230ms; }
.mode-grid .mode-card:nth-child(2) { animation-delay: 280ms; }
.mode-grid .mode-card:nth-child(3) { animation-delay: 330ms; }
.mode-grid .mode-card:nth-child(4) { animation-delay: 380ms; }

.home-topbar {
  display: grid;
  /* profile card | 🌐 language | menu — the globe is deliberately OUTSIDE the menu: a user whose
     app auto-detected the WRONG language can't read menu labels, but 🌐 is language-neutral. */
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: clamp(7px, 1.8vw, 10px);
}

/* Daily challenge bar: gold-tinted glass strip; tap = quick play. */
.daily-challenge {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 9px;
  min-height: clamp(42px, calc(5.45 * var(--dvh)), 56px);
  padding: 5px clamp(10px, 2.6vw, 15px);
  border-radius: 13px;
  border: 1px solid rgba(247, 231, 182, 0.3);
  background:
    linear-gradient(120deg, rgba(255, 255, 255, 0.06), rgba(34, 150, 110, 0.04)),
    var(--lobby-panel);
  color: var(--ink);
  text-align: left;
  box-shadow: inset 3px 0 0 var(--gold-2), var(--rim-top), var(--e1), var(--amb-warm);
  transition: transform 130ms cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 180ms ease;
}

.daily-challenge:active {
  transform: scale(0.97);
}

.daily-challenge .dc-icon {
  font-size: clamp(18px, 4.6vw, 24px);
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.4));
}

.daily-challenge .dc-copy {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.daily-challenge .dc-copy strong {
  font: 900 clamp(9px, 2.35vw, 11px)/1 var(--font-ui);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255, 222, 120, 0.95);
}

.daily-challenge .dc-copy small {
  font: 800 clamp(11px, 3vw, 14px)/1.18 var(--font-ui);
  color: #fff8e6;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.daily-challenge .dc-progress {
  padding: 5px 10px;
  border-radius: 999px;
  background: linear-gradient(180deg, #ffe98a, #ffb02e);
  color: #5a3c02;
  font: 900 clamp(11px, 3vw, 14px)/1 var(--font-ui);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55), 0 3px 7px rgba(0, 0, 0, 0.3);
  white-space: nowrap;
}

.daily-challenge.is-done {
  border-color: rgba(132, 255, 178, 0.6);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    0 8px 18px rgba(0, 0, 0, 0.28),
    0 0 22px rgba(96, 244, 160, 0.3);
}

.daily-challenge.is-done .dc-progress {
  background: linear-gradient(180deg, #b6ffd2, #4fdd8d);
  color: #06281a;
}

html[data-theme="light"] .daily-challenge {
  /* Clean frosted white (was warm gold) to match the day sky + white board boxes. */
  border-color: rgba(255, 255, 255, 0.85);
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.84), rgba(255, 255, 255, 0.64));
  color: #28323a;
  box-shadow:
    inset 3px 0 0 var(--gold-2),
    inset 0 1px 0 rgba(255, 255, 255, 0.9),
    0 12px 26px rgba(40, 70, 110, 0.16);
}

html[data-theme="light"] .daily-challenge .dc-copy strong {
  color: #2c6112;
}

html[data-theme="light"] .daily-challenge .dc-copy small {
  color: #28323a;
}

.profile-card,
.home-start-panel,
.mode-panel {
  position: relative;
  border: 1px solid rgba(247, 231, 182, 0.3);
  border-radius: var(--r-card);
  background:
    linear-gradient(155deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02) 42%, rgba(34, 150, 110, 0.04)),
    var(--lobby-panel);
  box-shadow: var(--rim-top), var(--rim-bottom), var(--e2), var(--amb-warm);
  backdrop-filter: blur(14px) saturate(1.12);
  -webkit-backdrop-filter: blur(14px) saturate(1.12);
}

.profile-card::before,
.home-start-panel::before,
.mode-panel::before {
  content: "";
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.1), transparent 42%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16);
}

.profile-card > *,
.home-start-panel > *,
.mode-panel > * {
  position: relative;
  z-index: 1;
}

html[data-theme="light"] .profile-card,
html[data-theme="light"] .home-start-panel,
html[data-theme="light"] .mode-panel {
  /* Clean frosted white to match the day sky + white board boxes (was warm cream). */
  border-color: rgba(255, 255, 255, 0.85);
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.84), rgba(255, 255, 255, 0.64));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.9),
    0 12px 26px rgba(40, 70, 110, 0.16);
}

/* ===== Colourful playful cartoon top section (profile card + daily challenge):
   chunky bright cards, thick white outline, sticker drop-shadow + glossy top sheen —
   matching the colourful board & playful tokens. Same vibe in light and dark. ===== */
.profile-card,
.daily-challenge {
  border: 3px solid #ffffff;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  color: #ffffff;
  box-shadow:
    inset 0 3px 0 rgba(255, 255, 255, 0.42),
    inset 0 -12px 18px rgba(0, 0, 0, 0.18),
    0 5px 0 rgba(0, 0, 0, 0.18),
    0 12px 22px rgba(0, 0, 0, 0.34);
}
html[data-theme="light"] .profile-card,
html[data-theme="dark"] .profile-card {
  background: linear-gradient(165deg, #56b4f3 0%, #2f7fd8 55%, #2266c4 100%);
  color: #ffffff;
}
html[data-theme="light"] .daily-challenge,
html[data-theme="dark"] .daily-challenge {
  /* Premium violet quest card with gold accents, distinct from the blue
     profile card and tied to the title's AI accent + the gold brand. */
  background: linear-gradient(165deg, #8b6cf2 0%, #6a44d8 55%, #4f2fb0 100%);
  color: #ffffff;
}
html[data-theme="light"] .daily-challenge .dc-copy strong,
html[data-theme="dark"] .daily-challenge .dc-copy strong { color: #ffe487; }
html[data-theme="light"] .daily-challenge .dc-copy small,
html[data-theme="dark"] .daily-challenge .dc-copy small { color: #f3edff; }
.daily-challenge .dc-progress {
  background: linear-gradient(180deg, #ffe98a, #f4c24c);
  color: #5a3c02;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7), 0 2px 6px rgba(0, 0, 0, 0.3);
}
/* Quest-card touches: circular icon badge + a bottom progress bar that fills. */
.daily-challenge { overflow: hidden; }
.daily-challenge .dc-icon {
  width: clamp(28px, 7.6vw, 37px);
  height: clamp(28px, 7.6vw, 37px);
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: linear-gradient(160deg, #ffe98a, #f4c24c);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85), 0 2px 6px rgba(0, 0, 0, 0.3);
  font-size: clamp(14px, 3.9vw, 19px);
  filter: none;
}
.dc-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 5px;
  background: rgba(0, 0, 0, 0.22);
}
.dc-bar-fill {
  display: block;
  height: 100%;
  width: 0;
  background: linear-gradient(90deg, #ffe487, #f4c24c);
  box-shadow: 0 0 8px rgba(255, 220, 120, 0.7);
  transition: width 450ms cubic-bezier(0.2, 0.8, 0.3, 1);
}
.daily-challenge.is-done .dc-bar-fill {
  background: linear-gradient(90deg, #aef5cd, #58f29a);
}

.profile-card {
  min-height: clamp(72px, calc(8.8 * var(--dvh)), 92px);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(6px, 1.7vw, 9px);
  padding: clamp(6px, 1.35vw, 9px) clamp(8px, 2.2vw, 11px);
}

/* Avatar button: the conic ring fills with the player's career win rate. */
.profile-avatar-button {
  --win-deg: 0;
  position: relative;
  width: clamp(38px, 9.4vw, 48px);
  height: clamp(38px, 9.4vw, 48px);
  padding: 3px;
  border-radius: 50%;
  background:
    conic-gradient(#ffd84d 0deg, #ffb02e calc(var(--win-deg) * 1deg), rgba(255, 255, 255, 0.16) calc(var(--win-deg) * 1deg) 360deg);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.32), 0 0 14px rgba(255, 216, 77, 0.22);
  transition: transform 140ms ease;
}

.profile-avatar-button:active {
  transform: scale(0.92);
}

.profile-avatar {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background:
    radial-gradient(circle at 32% 26%, rgba(255, 255, 255, 0.3), transparent 42%),
    linear-gradient(160deg, #11406f, #071c33 72%);
  font: 900 clamp(18px, 4.8vw, 25px)/1 var(--font-emoji);
  color: #ffe9a8;
  text-shadow: 0 2px 3px rgba(0, 0, 0, 0.4);
  overflow: hidden;
}

.profile-avatar.avatar-pop {
  animation: avatar-pop 320ms cubic-bezier(0.2, 1.4, 0.4, 1) both;
}

.profile-fields {
  display: grid;
  gap: 4px;
  min-width: 0;
}

.profile-identity-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.profile-identity-row .country-select-wrap,
.profile-identity-row .gender-select-wrap {
  display: block;
  position: relative;
}

.profile-input-wrap,
.country-select-wrap,
.gender-select-wrap,
.opponent-input-wrap {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.country-select-wrap {
  display: inline-grid;
  grid-template-columns: auto auto;
  align-items: center;
  justify-content: start;
  gap: 6px;
}

/* The explicit display above would defeat the hidden attribute without this. */
.opponent-input-wrap[hidden] {
  display: none;
}

.profile-input-wrap span,
.country-select-wrap span,
.gender-select-wrap span,
.opponent-input-wrap span {
  font: 800 clamp(8px, 2vw, 10px)/1 var(--font-ui);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255, 226, 130, 0.92);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
}

.country-select-wrap span {
  white-space: nowrap;
}

.gender-select-wrap {
  display: inline-grid;
  grid-template-columns: auto auto;
  align-items: center;
  justify-content: start;
  gap: 6px;
}

.gender-select-wrap > span {
  white-space: nowrap;
}

.gender-button {
  width: auto;
  min-width: clamp(58px, 15vw, 72px);
  height: clamp(32px, 7.4vw, 38px);
  display: grid;
  align-items: center;
  justify-content: center;
  padding: 0 9px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.32);
  background:
    linear-gradient(135deg, rgba(255, 255, 255, 0.18), transparent 44%),
    rgba(4, 28, 45, 0.38);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    0 4px 10px rgba(0, 0, 0, 0.18);
  overflow: hidden;
  transition: transform 130ms ease, border-color 160ms ease, box-shadow 160ms ease;
}

.gender-button:active {
  transform: scale(0.95);
}

.gender-current {
  --gender-a: rgba(255, 255, 255, 0.2);
  --gender-b: rgba(255, 255, 255, 0.06);
  --gender-ink: #fff6d7;
  --gender-glow: rgba(255, 255, 255, 0.12);
  position: relative;
  min-width: 0;
  width: 100%;
  height: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 0;
  border-radius: inherit;
  color: var(--gender-ink);
  font: 900 clamp(9px, 2.4vw, 11px)/1 var(--font-ui);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  background:
    radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.2), transparent 34%),
    linear-gradient(135deg, var(--gender-a), var(--gender-b));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.2),
    0 0 14px var(--gender-glow);
  isolation: isolate;
}

.gender-current::after {
  content: none;
}

.gender-current-icon {
  display: none;
}

.gender-current-text {
  position: static;
  width: auto;
  height: auto;
  overflow: visible;
  clip: auto;
  white-space: nowrap;
}

.gender-current-private { --gender-a: #fff3a4; --gender-b: #d7a338; --gender-ink: #2b1d04; --gender-glow: rgba(215, 163, 56, 0.5); }
.gender-current-male { --gender-a: #7bdcff; --gender-b: #3078ff; --gender-ink: #041b3b; --gender-glow: rgba(48, 120, 255, 0.52); }
.gender-current-female { --gender-a: #ff9bdc; --gender-b: #ff4fa7; --gender-ink: #3b0422; --gender-glow: rgba(255, 79, 167, 0.52); }
.gender-current-other { --gender-a: #a2ffb6; --gender-b: #996cff; --gender-ink: #16043b; --gender-glow: rgba(153, 108, 255, 0.52); }

.gender-overlay {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 24;
  display: grid;
  place-items: end center;
  padding: 18px clamp(14px, 3.8vw, 22px) calc(18px + env(safe-area-inset-bottom, 0px));
  background: rgba(0, 8, 9, 0.58);
  backdrop-filter: blur(8px) saturate(1.08);
  -webkit-backdrop-filter: blur(8px) saturate(1.08);
}

.gender-overlay[hidden] {
  display: none;
}

.gender-pop {
  width: min(100%, 420px);
  display: grid;
  gap: 12px;
  padding: 14px;
  border-radius: 20px;
  border: 1px solid rgba(247, 231, 182, 0.34);
  background:
    radial-gradient(circle at 18% 0%, rgba(255, 226, 120, 0.16), transparent 34%),
    radial-gradient(circle at 90% 12%, rgba(110, 231, 255, 0.14), transparent 38%),
    linear-gradient(160deg, rgba(8, 36, 31, 0.98), rgba(5, 18, 26, 0.99));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    0 20px 42px rgba(0, 0, 0, 0.48);
  animation: gender-pop-rise 260ms cubic-bezier(0.18, 0.9, 0.26, 1.06) both;
}

.gender-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.gender-head strong {
  color: #ffe9a8;
  font: 900 clamp(17px, 4.5vw, 21px)/1 var(--font-ui);
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.35);
}

.gender-close {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.12);
  color: #eafff6;
  font: 800 22px/1 var(--font-ui);
}

.gender-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.gender-card {
  --gender-a: #ffe98a;
  --gender-b: #ffb02e;
  --gender-ink: #2d1c04;
  --gender-glow: rgba(255, 176, 46, 0.52);
  position: relative;
  min-height: clamp(88px, 18vw, 104px);
  display: grid;
  place-items: center;
  align-content: center;
  gap: 7px;
  padding: 12px 10px;
  border-radius: 17px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: var(--gender-ink);
  background:
    radial-gradient(circle at 28% 18%, rgba(255, 255, 255, 0.58), transparent 33%),
    linear-gradient(145deg, var(--gender-a), var(--gender-b));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.36),
    0 8px 20px rgba(0, 0, 0, 0.24);
  overflow: hidden;
  transition: transform 150ms ease, box-shadow 160ms ease, border-color 160ms ease, filter 160ms ease;
}

.gender-card::after {
  content: "";
  position: absolute;
  top: -35%; right: -70%; bottom: -35%; left: -70%; /* inset longhands (Chromium<87 WebView support) */
  background: linear-gradient(105deg, transparent 34%, rgba(255, 255, 255, 0.34) 50%, transparent 66%);
  transform: translateX(-62%) rotate(9deg);
  animation: gender-shine 3.2s ease-in-out infinite;
  pointer-events: none;
}

.gender-card:active {
  transform: scale(0.97);
}

.gender-card.is-selected {
  border-color: rgba(255, 255, 255, 0.82);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.48),
    0 0 0 3px rgba(255, 231, 130, 0.24),
    0 0 24px var(--gender-glow),
    0 10px 26px rgba(0, 0, 0, 0.34);
  transform: translateY(-2px);
}

.gender-card.is-selected::before {
  content: "";
  position: absolute;
  top: 8px;
  right: 8px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background:
    linear-gradient(180deg, #fff7ad, #ffc63d);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.34);
}

.gender-card-symbol {
  position: relative;
  z-index: 1;
  width: clamp(36px, 9.6vw, 46px);
  height: clamp(36px, 9.6vw, 46px);
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.28);
  color: var(--gender-ink);
  font: 900 clamp(24px, 6vw, 30px)/1 var(--font-emoji);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.48),
    0 5px 12px rgba(0, 0, 0, 0.16);
  animation: gender-icon-bob 2s ease-in-out infinite;
}

.gender-card strong {
  position: relative;
  z-index: 1;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 900 clamp(13px, 3.5vw, 16px)/1 var(--font-ui);
}

.gender-card[data-gender="private"] { --gender-a: #fff3a4; --gender-b: #d7a338; --gender-ink: #2b1d04; --gender-glow: rgba(215, 163, 56, 0.56); }
.gender-card[data-gender="male"] { --gender-a: #7bdcff; --gender-b: #3078ff; --gender-ink: #041b3b; --gender-glow: rgba(48, 120, 255, 0.56); }
.gender-card[data-gender="female"] { --gender-a: #ff9bdc; --gender-b: #ff4fa7; --gender-ink: #3b0422; --gender-glow: rgba(255, 79, 167, 0.56); }
.gender-card[data-gender="other"] { --gender-a: #a2ffb6; --gender-b: #996cff; --gender-ink: #16043b; --gender-glow: rgba(153, 108, 255, 0.56); }

.gender-button {
  position: relative;
  width: auto;
  min-width: clamp(58px, 15vw, 72px);
  height: clamp(32px, 7.4vw, 38px);
  border-color: rgba(255, 255, 255, 0.32);
  background:
    linear-gradient(135deg, rgba(255, 255, 255, 0.18), transparent 44%),
    rgba(4, 28, 45, 0.38);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    0 4px 10px rgba(0, 0, 0, 0.18);
}

.gender-button::before {
  content: none;
}

.gender-current {
  z-index: 1;
  gap: 4px;
  padding: 0;
  background:
    radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.2), transparent 34%),
    linear-gradient(135deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.06));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
  color: #fff6d7;
  font: 900 clamp(9px, 2.4vw, 11px)/1 var(--font-ui);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.gender-current::before {
  content: none;
}

.gender-current::after {
  content: none;
}

.gender-current-icon {
  display: none;
}

.gender-overlay {
  z-index: 55;
  padding: 14px clamp(12px, 3.6vw, 18px) calc(14px + env(safe-area-inset-bottom, 0px));
}

.gender-pop {
  width: min(calc(100% - 16px), 420px);
  gap: 10px;
  padding: 12px;
  border-radius: 16px;
  border-color: rgba(255, 226, 130, 0.34);
  background:
    linear-gradient(155deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.04)),
    rgba(5, 30, 24, 0.96);
}

.gender-head strong {
  color: rgba(255, 226, 130, 0.96);
  font: 900 clamp(13px, 3.4vw, 16px)/1 var(--font-ui);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.gender-grid {
  grid-template-columns: 1fr;
  gap: 8px;
}

.gender-card {
  min-height: clamp(58px, 12.5vw, 66px);
  grid-template-columns: auto minmax(0, 1fr) auto;
  place-items: center start;
  align-content: center;
  gap: 10px;
  padding: 9px 12px;
  border-radius: 14px;
  color: #f7fff9;
  background:
    linear-gradient(155deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.04)),
    rgba(255, 255, 255, 0.065);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.12),
    0 6px 16px rgba(0, 0, 0, 0.18);
}

.gender-card::after {
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  background: linear-gradient(90deg, var(--gender-glow), transparent 62%);
  transform: none;
  opacity: 0;
  animation: none;
}

.gender-card.is-selected {
  border-color: rgba(255, 226, 100, 0.82);
  transform: none;
  box-shadow:
    0 0 0 2px rgba(255, 226, 100, 0.13),
    0 0 16px var(--gender-glow),
    inset 0 1px 0 rgba(255, 255, 255, 0.14);
}

.gender-card.is-selected::before {
  content: "\2713";
  top: 50%;
  right: 12px;
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  transform: translateY(-50%);
  color: #214014;
  font: 900 13px/1 var(--font-ui);
  background: linear-gradient(180deg, #fff7ad, #ffc63d);
}

.gender-card.is-selected::after {
  opacity: 0.38;
}

.gender-card-symbol {
  width: clamp(32px, 8.2vw, 38px);
  height: clamp(32px, 8.2vw, 38px);
  background:
    radial-gradient(circle at 32% 22%, rgba(255, 255, 255, 0.66), transparent 36%),
    linear-gradient(145deg, var(--gender-a), var(--gender-b));
  color: var(--gender-ink);
  font: 900 clamp(20px, 5.2vw, 25px)/1 var(--font-ui), var(--font-emoji);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.46),
    0 0 14px var(--gender-glow),
    0 5px 12px rgba(0, 0, 0, 0.16);
}

.gender-card strong {
  color: #f7fff9;
  text-align: start;
  font: 900 clamp(14px, 3.7vw, 16px)/1 var(--font-ui);
}

html[data-theme="light"] .gender-button {
  border-color: rgba(20, 96, 70, 0.18);
  background:
    linear-gradient(135deg, rgba(255, 255, 255, 0.58), transparent 44%),
    rgba(255, 255, 255, 0.42);
}

html[data-theme="light"] .gender-current {
  --gender-a: rgba(255, 255, 255, 0.62);
  --gender-b: rgba(228, 245, 224, 0.46);
  --gender-ink: #184138;
}

html[data-theme="light"] .gender-pop {
  background:
    radial-gradient(circle at 18% 0%, rgba(255, 214, 120, 0.34), transparent 34%),
    radial-gradient(circle at 90% 12%, rgba(96, 200, 220, 0.22), transparent 38%),
    linear-gradient(160deg, rgba(255, 250, 232, 0.98), rgba(232, 250, 240, 0.98));
}

html[data-theme="light"] .gender-card {
  border-color: rgba(20, 96, 70, 0.18);
  background:
    linear-gradient(155deg, rgba(255, 255, 255, 0.74), rgba(255, 247, 222, 0.52)),
    rgba(255, 255, 255, 0.58);
}

html[data-theme="light"] .gender-card strong {
  color: #17352d;
}

html[data-theme="light"] .profile-input-wrap span,
html[data-theme="light"] .country-select-wrap span,
html[data-theme="light"] .gender-select-wrap span,
html[data-theme="light"] .opponent-input-wrap span {
  color: rgba(112, 75, 6, 0.92);
  text-shadow: none;
}

.profile-name-status { display: block; margin: 4px 2px 0; font: 600 11px/1.35 var(--font-ui); color: #ffd27a; }
.profile-name-status[hidden] { display: none; }
.profile-input-wrap input,
.country-select-wrap select,
.opponent-input-wrap input {
  min-width: 0;
  width: 100%;
  padding: 5px 9px;
  border-radius: 9px;
  border: 1px solid rgba(255, 255, 255, 0.26);
  background: rgba(2, 20, 16, 0.42);
  color: #f6fffb;
  /* 16px FLOOR, not 13. iOS auto-zooms the page whenever a focused input renders below 16px, and it does not
     zoom back out — the player is left on a magnified, horizontally-scrolled board mid-game. Raising the floor
     is the accessible fix; the alternative (user-scalable=no on the viewport) would defeat pinch-zoom for
     everyone, including the users who need it. On a 360px phone this reads 16px instead of 12.4px. */
  font: 800 clamp(16px, 3.45vw, 17px)/1.15 var(--font-ui);
  text-align: start;
  unicode-bidi: plaintext;
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.3);
}

html[data-theme="light"] .profile-input-wrap input,
html[data-theme="light"] .country-select-wrap select,
html[data-theme="light"] .opponent-input-wrap input {
  background: rgba(255, 255, 255, 0.66);
  border-color: rgba(20, 96, 70, 0.3);
  color: var(--lobby-ink);
  box-shadow: inset 0 2px 5px rgba(24, 92, 66, 0.16);
}

.country-select-wrap select {
  height: clamp(32px, 7.2vw, 38px);
  appearance: none;
  -webkit-appearance: none;
  padding-right: 30px;
  background-image:
    linear-gradient(45deg, transparent 50%, rgba(255, 226, 130, 0.95) 50%),
    linear-gradient(135deg, rgba(255, 226, 130, 0.95) 50%, transparent 50%);
  background-position:
    calc(100% - 17px) 50%,
    calc(100% - 11px) 50%;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  font-family: var(--font-emoji);
  /* iOS zooms on focus for <select> as well as text fields. The maximum here was already 16px, so raising
     the floor costs nothing above a 492px viewport and fixes every phone below it. */
  font-size: 16px;
}

.country-select-wrap select[hidden] {
  display: none !important;
}

.country-flag-button {
  width: clamp(42px, 9.6vw, 50px);
  min-width: 40px;
  height: clamp(38px, 8.2vw, 44px);
  display: grid;
  grid-template-columns: auto 7px;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 0 6px;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  background:
    radial-gradient(circle at 28% 18%, rgba(255, 255, 255, 0.26), transparent 34%),
    linear-gradient(160deg, rgba(255, 226, 126, 0.16), rgba(0, 28, 22, 0.5));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    0 4px 10px rgba(0, 0, 0, 0.26);
}

.country-flag-button::after {
  content: "";
  width: 5px;
  height: 5px;
  border-right: 2px solid rgba(255, 232, 170, 0.92);
  border-bottom: 2px solid rgba(255, 232, 170, 0.92);
  transform: rotate(45deg) translateY(-1px);
  opacity: 0.9;
}

.country-flag-button:active {
  transform: scale(0.96);
}

.country-flag-current {
  display: grid;
  place-items: center;
  width: clamp(20px, 4.8vw, 24px);
  height: clamp(16px, 3.9vw, 19px);
  padding: 1px;
  border-radius: 5px;
  background:
    linear-gradient(150deg, rgba(255, 255, 255, 0.96), rgba(216, 242, 232, 0.88));
  font: 900 clamp(11px, 3vw, 14px)/1 var(--font-emoji);
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.34));
  transform-origin: 50% 85%;
  animation: flag-soft-wave 1.55s ease-in-out infinite;
}

.country-flag-img {
  display: block;
  width: 34px;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 4px;
  box-shadow:
    0 2px 5px rgba(0, 0, 0, 0.28),
    inset 0 0 0 1px rgba(255, 255, 255, 0.4);
}
/* styles.css has no global [hidden] reset, and `display:block` above overrides the element.hidden
   default — so toggling the flag img's `hidden` attribute (to fall back to the emoji flag) would NOT
   hide it without this explicit rule. See the css-hidden-attribute gotcha. */
.country-flag-img[hidden] {
  display: none;
}

.country-flag-current .country-flag-img {
  width: clamp(17px, 4.1vw, 22px);
  border-radius: 2px;
}

.country-flag-fallback {
  font: 900 clamp(11px, 3vw, 14px)/1 var(--font-emoji);
}

html[data-theme="light"] .country-select-wrap select {
  background-image:
    linear-gradient(45deg, transparent 50%, rgba(78, 55, 8, 0.85) 50%),
    linear-gradient(135deg, rgba(78, 55, 8, 0.85) 50%, transparent 50%);
}

html[data-theme="light"] .country-flag-button {
  background:
    radial-gradient(circle at 28% 18%, rgba(255, 255, 255, 0.72), transparent 34%),
    linear-gradient(160deg, rgba(255, 255, 255, 0.84), rgba(255, 226, 138, 0.58));
  border-color: rgba(20, 96, 70, 0.3);
}

.country-flag-overlay {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 55;
  display: block;
  padding: clamp(12px, 3vw, 18px);
  background: rgba(1, 13, 10, 0.56);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
}

.country-flag-overlay[hidden] {
  display: none;
}

.country-flag-pop {
  position: absolute;
  left: 50%;
  bottom: clamp(12px, 3vw, 18px);
  width: min(calc(100% - 24px), 420px);
  height: min(520px, calc(100% - 28px));
  transform: translateX(-50%);
  display: grid;
  grid-template-rows: auto auto auto minmax(0, 1fr);
  gap: 9px;
  padding: 10px;
  border-radius: 16px;
  border: 1px solid rgba(255, 226, 130, 0.34);
  background:
    linear-gradient(155deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.04)),
    rgba(5, 30, 24, 0.96);
  box-shadow: 0 18px 38px rgba(0, 0, 0, 0.52), inset 0 1px 0 rgba(255, 255, 255, 0.14);
}

.country-flag-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.country-flag-head strong {
  color: rgba(255, 226, 130, 0.96);
  font: 900 clamp(13px, 3.4vw, 16px)/1 var(--font-ui);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.country-flag-close {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.22);
  background: rgba(255, 255, 255, 0.08);
  color: #fff2bd;
  font: 900 22px/1 var(--font-ui);
}

.country-flag-search-wrap {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 8px;
  min-height: 42px;
  padding: 0 12px;
  border-radius: 12px;
  border: 1px solid rgba(255, 226, 130, 0.24);
  background:
    radial-gradient(circle at 12% 18%, rgba(255, 255, 255, 0.18), transparent 32%),
    rgba(255, 255, 255, 0.07);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.1),
    0 4px 12px rgba(0, 0, 0, 0.18);
}

.country-flag-search-wrap span {
  font: 900 15px/1 var(--font-emoji);
  opacity: 0.88;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35));
}

.country-flag-search-wrap input {
  min-width: 0;
  width: 100%;
  height: 100%;
  border: 0;
  outline: 0;
  background: transparent;
  color: #f7fff9;
  /* 16px floor — see .profile-input-wrap input. This is #countryFlagSearch, a real text field. */
  font: 800 clamp(16px, 3.6vw, 17px)/1.1 var(--font-ui);
  unicode-bidi: plaintext;
}

.country-flag-search-wrap input::placeholder {
  color: rgba(246, 255, 249, 0.58);
}

.country-flag-search-wrap:focus-within {
  border-color: rgba(255, 226, 100, 0.72);
  box-shadow:
    0 0 0 2px rgba(255, 226, 100, 0.13),
    0 0 14px rgba(255, 226, 100, 0.18),
    inset 0 1px 0 rgba(255, 255, 255, 0.14);
}

.country-flag-empty {
  min-height: 32px;
  display: grid;
  place-items: center;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.05);
  color: rgba(255, 240, 190, 0.82);
  font: 800 clamp(12px, 3.1vw, 14px)/1.2 var(--font-ui);
  text-align: center;
}

.country-flag-empty[hidden] {
  display: none;
}

.country-flag-grid {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(42px, 1fr));
  gap: 7px;
  padding: 2px 2px 4px;
  -webkit-overflow-scrolling: touch;
}

.country-flag-option {
  height: 42px;
  min-width: 42px;
  display: grid;
  place-items: center;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.06);
  color: #fff;
  font: 900 14px/1 var(--font-emoji);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}

.country-flag-option[hidden] {
  display: none;
}

.country-flag-option .country-flag-img {
  width: clamp(17px, 4.1vw, 22px);
  border-radius: 2px;
}

.country-flag-option .country-flag-fallback {
  font-size: clamp(11px, 3vw, 14px);
}

.country-flag-option.is-selected {
  border-color: rgba(255, 226, 100, 0.94);
  background:
    radial-gradient(circle at 50% 38%, rgba(255, 235, 150, 0.28), transparent 52%),
    rgba(46, 174, 125, 0.22);
  box-shadow:
    0 0 0 2px rgba(255, 226, 100, 0.18),
    0 0 14px rgba(255, 226, 100, 0.32),
    inset 0 1px 0 rgba(255, 255, 255, 0.16);
  animation: flag-selected-pop 1.45s ease-in-out infinite;
}

.profile-input-wrap input.is-readonly,
.opponent-input-wrap input.is-readonly {
  opacity: 0.72;
  font-style: italic;
}

/* Pre-match setup: token style is shared; color/player rows are mode-specific. */
.setup-picker {
  display: grid;
  gap: 10px;
  margin-top: 2px;
}

.setup-picker[hidden] {
  display: none;
}

.setup-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 30px;
}

.setup-row[hidden],
.setup-token-row[hidden] {
  display: none;
}

.setup-label {
  font: 800 clamp(9px, 2.2vw, 11px)/1 var(--font-ui);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 226, 130, 0.92);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
}

html[data-theme="light"] .setup-label {
  color: rgba(112, 75, 6, 0.92);
  text-shadow: none;
}

.color-swatches {
  display: flex;
  gap: 8px;
}

.color-swatch {
  width: clamp(26px, 7vw, 32px);
  height: clamp(26px, 7vw, 32px);
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.35);
  background: var(--swatch);
  cursor: pointer;
  padding: 0;
  position: relative;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3), inset 0 2px 4px rgba(255, 255, 255, 0.35);
  transition: transform 140ms ease, box-shadow 140ms ease, border-color 140ms ease;
}

/* Expand the tap target to ~44px without enlarging the visual swatch. */
.color-swatch::before {
  content: "";
  position: absolute;
  top: -9px; right: -9px; bottom: -9px; left: -9px; /* inset longhands (Chromium<87 WebView support) */
  border-radius: 50%;
}

.color-swatch:hover {
  transform: translateY(-1px);
}

.color-swatch.is-selected {
  border-color: #fff;
  transform: translateY(-1px) scale(1.08);
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5), 0 3px 9px rgba(0, 0, 0, 0.4);
}

.color-swatch.is-selected::after {
  content: "\2713";
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  display: grid;
  place-items: center;
  font: 900 14px/1 var(--font-ui);
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.65);
}

/* The light gold swatch needs a dark check or it's near-invisible (white on #FFC400). */
.color-swatch[data-color="yellow"].is-selected::after {
  color: #1c1c22;
  text-shadow: 0 1px 2px rgba(255, 255, 255, 0.6);
}

.count-options {
  display: flex;
  gap: 6px;
}

/* Online (world) config: live "N looking for 2P/3P/4P right now" under the count picker. */
.online-waiting {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 7px;
  margin-top: 6px;
  font: 700 12px/1 var(--font-ui);
}
.online-waiting[hidden] { display: none; }
.online-waiting .ow-label { color: #c9d7df; font-weight: 800; }
.online-waiting .ow-c {
  background: rgba(122, 245, 141, 0.13);
  border: 1px solid rgba(122, 245, 141, 0.34);
  border-radius: 8px;
  padding: 3px 8px;
  color: #bfead0;
}
.online-waiting .ow-c b { color: #7af58d; }

.setup-token-row {
  display: grid;
  gap: 7px;
  min-width: 0;
}

/* Pass N Play optional per-seat name inputs: a colour-coded row per active seat. Empty field with
   the localized colour name as placeholder; the swatch is the persistent "who is this" cue. */
.pass-name-rows {
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.name-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.name-swatch {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--swatch, #888);
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.38), 0 1px 3px rgba(0, 0, 0, 0.45);
}
.seat-name-input {
  flex: 1 1 auto;
  min-width: 0;
  height: clamp(40px, 5.6vw, 46px);
  padding: 0 12px;
  border-radius: 11px;
  border: 1px solid rgba(189, 250, 235, 0.3);
  background: rgba(0, 0, 0, 0.22);
  color: #f4fff8;
  /* See .profile-input-wrap input above — 16px is the iOS focus-zoom threshold. This rule's MAXIMUM was
     15px, so it tripped the zoom on every screen size rather than only on narrow ones. */
  font: 700 clamp(16px, 3.4vw, 17px)/1 var(--font-ui);
}
.seat-name-input::placeholder {
  color: rgba(225, 255, 240, 0.5);
  font-weight: 600;
}
.seat-name-input:focus {
  outline: none;
  border-color: rgba(255, 228, 92, 0.7);
  box-shadow: 0 0 0 2px rgba(255, 228, 92, 0.25);
}

.token-style-strip {
  display: flex;
  gap: 7px;
  min-width: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  padding: 2px 2px 7px;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.token-style-strip::-webkit-scrollbar {
  display: none;
}

.token-style-chip {
  position: relative; /* anchor the Pro lock badge */
  flex: 0 0 auto;
  min-width: 98px;
  height: 56px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  scroll-snap-align: start;
  border-radius: 13px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background:
    linear-gradient(160deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.04)),
    rgba(2, 20, 16, 0.42);
  color: #f6fffb;
  padding: 0 11px 0 9px;
  font: 900 12px/1 var(--font-ui);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
  transition: transform 140ms ease, border-color 140ms ease, box-shadow 140ms ease, background 140ms ease;
}

.token-style-chip:active {
  transform: scale(0.97);
}

.token-style-chip.is-selected {
  border-color: rgba(255, 228, 92, 0.9);
  background:
    radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.28), transparent 28%),
    linear-gradient(160deg, rgba(255, 210, 74, 0.28), rgba(35, 125, 93, 0.46));
  box-shadow:
    0 0 0 2px rgba(255, 226, 100, 0.2),
    0 7px 16px rgba(0, 0, 0, 0.34),
    inset 0 1px 0 rgba(255, 255, 255, 0.16);
  transform: translateY(-1px);
}

.token-style-preview {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 70%, rgba(0, 0, 0, 0.22), transparent 58%);
}

.token-style-preview canvas {
  width: 44px;
  height: 44px;
  display: block;
}

/* Locked (Pro) token chip — the artwork stays fully previewable (the tease), marked by a diamond Pro badge
   and a cool premium frame. It is NOT disabled: a tap opens the paywall, so it keeps normal focus/active
   states. Only the NAME dims slightly so the lock reads clearly without hiding the art. RTL-safe via logical
   properties; the diamond badge is decorative (the lock is announced via aria-label). */
.token-style-chip.is-locked {
  border-color: rgba(150, 200, 255, 0.36);
  background:
    linear-gradient(160deg, rgba(150, 200, 255, 0.14), rgba(255, 255, 255, 0.03)),
    rgba(2, 20, 16, 0.42);
}
.token-style-chip.is-locked > span:not(.token-style-preview):not(.token-style-lock) {
  opacity: 0.74;
}
.token-style-lock {
  position: absolute;
  /* INSIDE the chip's top corner (not overhanging) so the scroll strip's overflow-x:auto can never clip it on
     the first/last chip or in RTL. Physical right + the html[dir=rtl] flip below (NOT inset-inline-end,
     which is Chromium 87+ and silently unpins the badge on older WebViews). */
  top: 4px;
  right: 4px;
  z-index: 1; /* always above the chip's selected/locked backgrounds */
  min-width: 18px;
  height: 18px;
  display: none; /* shown ONLY on a locked chip (rule below) — visibility is class-driven, never the [hidden]
                    attribute, since styles.css has no global [hidden] reset and display:grid would override it */
  place-items: center;
  padding: 0 3px;
  border-radius: 9px;
  font-size: 10px;
  line-height: 1;
  background: linear-gradient(160deg, #7ed3ff, #3f7bd8);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.42), inset 0 1px 0 rgba(255, 255, 255, 0.45);
  pointer-events: none; /* taps fall through to the chip -> paywall */
}
.token-style-chip.is-locked .token-style-lock {
  display: grid; /* badge appears exactly when the chip carries is-locked; unlocks hide it with no JS toggle */
}
@media (prefers-reduced-motion: reduce) {
  .token-style-chip, .token-style-lock { transition: none; }
}

/* ---- Dice-theme picker (mirrors the token-style picker chrome) ---- */
.setup-dice-row { display: grid; gap: 7px; min-width: 0; }
.dice-style-strip {
  display: flex; gap: 7px; min-width: 0; overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x proximity; padding: 2px 2px 7px; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.dice-style-strip::-webkit-scrollbar { display: none; }
.dice-style-chip {
  position: relative; flex: 0 0 auto; min-width: 98px; height: 56px; display: inline-flex; align-items: center;
  justify-content: center; gap: 9px; scroll-snap-align: start; border-radius: 13px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.04)), rgba(2, 20, 16, 0.42);
  color: #f6fffb; padding: 0 12px; font: 900 12px/1 var(--font-ui);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
  transition: transform 140ms ease, border-color 140ms ease, box-shadow 140ms ease, background 140ms ease;
}
.dice-style-chip:active { transform: scale(0.97); }
.dice-style-chip.is-selected {
  border-color: rgba(255, 228, 92, 0.9);
  background: radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.28), transparent 28%),
    linear-gradient(160deg, rgba(255, 210, 74, 0.28), rgba(35, 125, 93, 0.46));
  box-shadow: 0 0 0 2px rgba(255, 226, 100, 0.2), 0 7px 16px rgba(0, 0, 0, 0.34), inset 0 1px 0 rgba(255, 255, 255, 0.16);
  transform: translateY(-1px);
}
.dice-style-chip.is-locked {
  border-color: rgba(150, 200, 255, 0.36);
  background: linear-gradient(160deg, rgba(150, 200, 255, 0.14), rgba(255, 255, 255, 0.03)), rgba(2, 20, 16, 0.42);
}
.dice-style-chip.is-locked > span:not(.dice-style-preview):not(.dice-style-lock) { opacity: 0.74; }
.dice-style-lock {
  position: absolute; top: 4px; right: 4px; z-index: 1; min-width: 18px; height: 18px; display: none; /* physical + RTL flip below (inset-inline-end is Chromium 87+) */
  place-items: center; padding: 0 3px; border-radius: 9px; font-size: 10px; line-height: 1;
  background: linear-gradient(160deg, #7ed3ff, #3f7bd8);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.42), inset 0 1px 0 rgba(255, 255, 255, 0.45); pointer-events: none;
}
.dice-style-chip.is-locked .dice-style-lock { display: grid; }
@media (prefers-reduced-motion: reduce) { .dice-style-chip, .dice-style-lock { transition: none; } }
/* The chip preview = a mini static dice face (reuses .dice-face gradients + .pip-* positions), fixed 34px, flat. */
.dice-style-preview.dice-face {
  position: relative; width: 34px; height: 34px; aspect-ratio: 1; flex: 0 0 auto; transform: none;
}

/* ---- WOOD dice theme (Pro) — procedural honey-walnut with curved growth rings, fine fibres, a hand-waxed
   bevel and recessed/charred pips. Everything is CSS-only (no downloaded texture), deterministic and scoped by
   `data-dice-theme="wood"`. Geometry, pip placement, transforms, hitboxes and roll timing remain untouched. ---- */
.dice-cube[data-dice-theme="wood"] {
  --wood-seam-color: #8a5327;
  border-radius: 8%;
  background: var(--wood-seam-color);
}
.dice-cube[data-dice-theme="wood"]::before,
.dice-cube[data-dice-theme="wood"]::after {
  content: "";
  position: absolute;
  /* Longhands (not `inset:`) so the seal survives Chromium <87 WebViews too. */
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  border-radius: 8%;
  background: var(--wood-seam-color);
  pointer-events: none;
  /* Deliberately double-sided: these are uniform opaque fills, and hiding their backfaces
     reopens the corner seams for the whole quadrant of tumble orientations where BOTH
     planes face away (e.g. rotateX(72deg) rotateY(90deg) — an orientation the animations
     really pass through). Double-sided paint is pixel-identical everywhere else. */
  z-index: -1;
}
.dice-cube[data-dice-theme="wood"]::before { transform: rotateY(90deg); }
.dice-cube[data-dice-theme="wood"]::after { transform: rotateX(90deg); }
.dice-face[data-dice-theme="wood"],
.dice-cube[data-dice-theme="wood"] .cube-face {
  --wood-grain-angle: 96deg;
  --wood-seam-color: #8a5327;
  border-color: rgba(73, 35, 12, 0.94);
  background-color: #9b5525;
  background:
    radial-gradient(ellipse 48% 27% at 84% 78%,
      transparent 0 31%, rgba(77, 37, 12, 0.3) 33% 35%, transparent 37% 45%,
      rgba(92, 45, 14, 0.22) 47% 49%, transparent 51% 60%, rgba(71, 31, 9, 0.17) 62% 64%, transparent 66%),
    radial-gradient(ellipse 36% 20% at 8% 15%,
      transparent 0 35%, rgba(255, 224, 166, 0.22) 38% 40%, transparent 43% 54%,
      rgba(91, 43, 13, 0.18) 57% 59%, transparent 62%),
    repeating-linear-gradient(var(--wood-grain-angle),
      rgba(255, 232, 183, 0.09) 0 1px, transparent 1px 5px,
      rgba(75, 32, 9, 0.16) 6px 7px, transparent 8px 13px,
      rgba(255, 218, 151, 0.07) 14px 15px, transparent 16px 22px),
    radial-gradient(circle at 24% 16%, rgba(255, 238, 199, 0.48), transparent 29%),
    linear-gradient(148deg, #d6a05a 0%, #bd7837 34%, #9b5525 66%, #713512 100%);
  background-blend-mode: multiply, soft-light, soft-light, screen, normal;
  filter: saturate(1.04) contrast(1.03);
}
.dice-face[data-dice-theme="wood"] {
  box-shadow:
    inset 4px 5px 8px rgba(255, 225, 169, 0.34),
    inset -7px -8px 13px rgba(52, 22, 5, 0.46),
    inset 0 0 0 max(2px, 0.3vw) rgba(255, 207, 130, 0.16),
    0 7px 9px rgba(0, 0, 0, 0.36),
    0 14px 18px rgba(0, 0, 0, 0.2);
}
.dice-cube[data-dice-theme="wood"] .cube-face {
  /* A 3D cube cannot join strongly-rounded face corners without leaving a
     transparent triangular seam. Keep the carved rounding, but tighten it
     and extend an opaque wood edge behind each face so the board can never
     show through while the die tumbles. */
  border-radius: 10%;
  box-shadow:
    0 0 0 max(2px, calc(var(--dice-size) * 0.03)) var(--wood-seam-color),
    inset max(3px, calc(var(--dice-size) * 0.045)) max(3px, calc(var(--dice-size) * 0.045)) max(8px, calc(var(--dice-size) * 0.09)) rgba(255, 222, 161, 0.3),
    inset max(-8px, calc(var(--dice-size) * -0.09)) max(-9px, calc(var(--dice-size) * -0.1)) max(12px, calc(var(--dice-size) * 0.13)) rgba(48, 19, 4, 0.44),
    inset 0 0 0 max(2px, calc(var(--dice-size) * 0.018)) rgba(255, 203, 119, 0.14);
}
.dice-cube[data-dice-theme="wood"] .cube-front,
.dice-cube[data-dice-theme="wood"] .cube-back { --wood-grain-angle: 96deg; }
.dice-cube[data-dice-theme="wood"] .cube-left,
.dice-cube[data-dice-theme="wood"] .cube-right { --wood-grain-angle: 8deg; }
.dice-cube[data-dice-theme="wood"] .cube-top,
.dice-cube[data-dice-theme="wood"] .cube-bottom { --wood-grain-angle: 172deg; }
.dice-face[data-dice-theme="wood"] i,
.dice-face[data-dice-theme="wood"] i.pip-red,
.dice-cube[data-dice-theme="wood"] .cube-dot,
.dice-cube[data-dice-theme="wood"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 43% 40%, #3f1d09 0 43%, #210d04 64%, #090301 100%);
  box-shadow:
    inset 1px 2px 3px rgba(0, 0, 0, 0.84),
    inset -1px -1px 1px rgba(255, 177, 87, 0.12),
    0 1px 0 rgba(255, 222, 164, 0.26);
}
.dice-face[data-dice-theme="wood"] i.pip-red,
.dice-cube[data-dice-theme="wood"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 42% 38%, #792b20 0 38%, #4a150f 63%, #180604 100%);
}
/* ---- end WOOD dice theme ---- */

/* ---- MARBLE dice theme (Pro) — polished Carrara-white stone: layered grey veining crossing at
   quarry angles, a cool mirror sheen and inky polished-granite pips. CSS-only and deterministic,
   scoped by `data-dice-theme="marble"`. Geometry, pip placement, transforms, hitboxes and roll
   timing remain untouched — marble adds NO pseudo-elements and NO radii of its own; the sealed
   base cube (double-sided centre + cross planes + seam ring) simply recolours via --cube-seam. ---- */
.dice-cube[data-dice-theme="marble"] {
  --cube-seam: #cdd3da; /* light stone tone — blends with the marble face so the seal/edge is invisible */
}
.dice-face[data-dice-theme="marble"],
.dice-cube[data-dice-theme="marble"] .cube-face {
  --marble-vein-angle: 24deg;
  border-color: rgba(122, 130, 140, 0.88);
  background:
    /* Sparse primary veins: soft-edged, unevenly spaced bands — a face shows ~1-2, like cut stone. */
    repeating-linear-gradient(var(--marble-vein-angle),
      transparent 0 21px, rgba(124, 133, 146, 0.24) 27px 30px, transparent 36px 71px,
      rgba(102, 112, 126, 0.14) 76px 78px, transparent 83px 118px),
    /* Near-parallel hairline system: the slight 9deg offset drifts in/out of the primary veins,
       breaking any regular lattice into organic variation. */
    repeating-linear-gradient(calc(var(--marble-vein-angle) + 9deg),
      transparent 0 33px, rgba(138, 146, 158, 0.11) 40px 43px, transparent 50px 97px),
    /* Grey mineral clouds drifting through the white stone. */
    radial-gradient(ellipse 64% 42% at 74% 68%, rgba(158, 166, 178, 0.26), transparent 64%),
    radial-gradient(ellipse 38% 26% at 22% 34%, rgba(145, 154, 167, 0.18), transparent 60%),
    /* Polished mirror sheen. */
    radial-gradient(circle at 26% 16%, rgba(255, 255, 255, 0.97), transparent 36%),
    linear-gradient(148deg, #f9fbfd 0%, #eef1f5 46%, #dde2e9 76%, #c9d0d9 100%);
  background-blend-mode: multiply, multiply, normal, normal, screen, normal;
}
.dice-face[data-dice-theme="marble"] {
  box-shadow:
    inset 4px 5px 8px rgba(255, 255, 255, 0.85),
    inset -7px -8px 13px rgba(96, 106, 120, 0.3),
    inset 0 0 0 max(2px, 0.3vw) rgba(255, 255, 255, 0.5),
    0 7px 9px rgba(0, 0, 0, 0.32),
    0 14px 18px rgba(0, 0, 0, 0.18);
}
.dice-cube[data-dice-theme="marble"] .cube-face {
  box-shadow:
    0 0 0 max(2px, calc(var(--dice-size) * 0.03)) var(--cube-seam),
    inset max(3px, calc(var(--dice-size) * 0.045)) max(3px, calc(var(--dice-size) * 0.045)) max(8px, calc(var(--dice-size) * 0.09)) rgba(255, 255, 255, 0.72),
    inset max(-8px, calc(var(--dice-size) * -0.09)) max(-9px, calc(var(--dice-size) * -0.1)) max(12px, calc(var(--dice-size) * 0.13)) rgba(84, 94, 108, 0.3),
    inset 0 0 0 max(2px, calc(var(--dice-size) * 0.018)) rgba(255, 255, 255, 0.52);
}
/* Vein direction varies per face pair, like real quarried stone (mirrors wood's grain mapping). */
.dice-cube[data-dice-theme="marble"] .cube-front,
.dice-cube[data-dice-theme="marble"] .cube-back { --marble-vein-angle: 24deg; }
.dice-cube[data-dice-theme="marble"] .cube-left,
.dice-cube[data-dice-theme="marble"] .cube-right { --marble-vein-angle: 118deg; }
.dice-cube[data-dice-theme="marble"] .cube-top,
.dice-cube[data-dice-theme="marble"] .cube-bottom { --marble-vein-angle: 64deg; }
/* Pips: polished ink-granite, softly recessed into the stone face. */
.dice-face[data-dice-theme="marble"] i,
.dice-face[data-dice-theme="marble"] i.pip-red,
.dice-cube[data-dice-theme="marble"] .cube-dot,
.dice-cube[data-dice-theme="marble"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 36% 30%, #4a5462 0 16%, #232a34 17% 62%, #0b0f15 63%);
  box-shadow:
    inset 0 2px 3px rgba(0, 0, 0, 0.62),
    inset 0 -1px 1px rgba(255, 255, 255, 0.24),
    0 1px 0 rgba(255, 255, 255, 0.66);
}
.dice-face[data-dice-theme="marble"] i.pip-red,
.dice-cube[data-dice-theme="marble"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 36% 30%, #e0766a 0 15%, #a02620 16% 64%, #4f0b0a 65%);
}
/* ---- end MARBLE dice theme ---- */

/* ---- CHARCOAL dice theme (Pro) — matte graphite-black stone with fine brushed grain, sooty mottling
   and a dim satin sheen, carrying INLAID BONE-WHITE pips (the first dark-faced theme; the light-on-dark
   contrast is the reverse of classic/wood/marble). CSS-only, deterministic, scoped by
   `data-dice-theme="charcoal"`. Like every theme it adds NO pseudo-elements and NO radii/transforms of
   its own — the sealed base cube (double-sided centre + cross planes + seam ring) just recolours dark
   via --cube-seam, so it stays airtight at every tumble angle. ---- */
.dice-cube[data-dice-theme="charcoal"] {
  --cube-seam: #3c4046; /* mid graphite — blends with the charcoal face so the seal/edge is invisible */
}
.dice-face[data-dice-theme="charcoal"],
.dice-cube[data-dice-theme="charcoal"] .cube-face {
  --charcoal-grain-angle: 18deg;
  border-color: rgba(9, 10, 12, 0.92);
  background:
    /* Brushed-graphite fibres: directional, restrained, and never a visible square grid. */
    repeating-linear-gradient(var(--charcoal-grain-angle),
      transparent 0 7px, rgba(212, 220, 232, 0.07) 8px 9px, transparent 10px 19px,
      rgba(4, 5, 7, 0.16) 20px 21px, transparent 22px 32px),
    /* Sooty depth, kept broad so it reads like material instead of dirty blur. */
    radial-gradient(ellipse 64% 46% at 78% 76%, rgba(3, 4, 6, 0.58), transparent 64%),
    /* Clean upper-left graphite catchlight. */
    radial-gradient(ellipse 48% 34% at 27% 21%, rgba(180, 190, 204, 0.3), transparent 58%),
    /* A narrow satin streak keeps the dark die premium without turning glossy. */
    linear-gradient(58deg, transparent 35%, rgba(226, 234, 246, 0.18) 49%, transparent 62%),
    /* Graphite base ramp: brighter edge, dense center, dark sealed sides. */
    linear-gradient(150deg, #51565f 0%, #3b4048 24%, #24282e 52%, #30343b 68%, #454a52 82%, #17191d 100%);
  background-blend-mode: screen, multiply, screen, screen, normal;
}
.dice-face[data-dice-theme="charcoal"] {
  box-shadow:
    inset 4px 5px 8px rgba(184, 194, 208, 0.18),
    inset -7px -8px 13px rgba(0, 0, 0, 0.58),
    inset 0 0 0 max(2px, 0.3vw) rgba(126, 134, 146, 0.18),
    0 7px 9px rgba(0, 0, 0, 0.46),
    0 14px 18px rgba(0, 0, 0, 0.24);
}
.dice-cube[data-dice-theme="charcoal"] .cube-face {
  box-shadow:
    0 0 0 max(2px, calc(var(--dice-size) * 0.03)) var(--cube-seam),
    inset max(3px, calc(var(--dice-size) * 0.045)) max(3px, calc(var(--dice-size) * 0.045)) max(8px, calc(var(--dice-size) * 0.09)) rgba(186, 196, 210, 0.2),
    inset max(-8px, calc(var(--dice-size) * -0.09)) max(-9px, calc(var(--dice-size) * -0.1)) max(12px, calc(var(--dice-size) * 0.13)) rgba(0, 0, 0, 0.62),
    inset 0 0 0 max(2px, calc(var(--dice-size) * 0.018)) rgba(146, 154, 166, 0.18);
}
/* Grain direction varies per face pair, like a brushed metal/graphite block (mirrors wood/marble). */
.dice-cube[data-dice-theme="charcoal"] .cube-front,
.dice-cube[data-dice-theme="charcoal"] .cube-back { --charcoal-grain-angle: 18deg; }
.dice-cube[data-dice-theme="charcoal"] .cube-left,
.dice-cube[data-dice-theme="charcoal"] .cube-right { --charcoal-grain-angle: 100deg; }
.dice-cube[data-dice-theme="charcoal"] .cube-top,
.dice-cube[data-dice-theme="charcoal"] .cube-bottom { --charcoal-grain-angle: 52deg; }
/* Pips: inlaid bone-white, proud of the dark face with a thin dark seat-ring so they pop from the black. */
.dice-face[data-dice-theme="charcoal"] i,
.dice-face[data-dice-theme="charcoal"] i.pip-red,
.dice-cube[data-dice-theme="charcoal"] .cube-dot,
.dice-cube[data-dice-theme="charcoal"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 36% 30%, #ffffff 0 12%, #f7f3e7 13% 34%, #d5d8de 56%, #949aa4 100%);
  box-shadow:
    0 0 0 max(1px, calc(var(--dice-size) * 0.01)) rgba(4, 5, 7, 0.78),
    inset 0 2px 3px rgba(0, 0, 0, 0.24),
    inset 0 -1px 1px rgba(255, 255, 255, 0.26),
    0 1px 1px rgba(0, 0, 0, 0.66);
}
.dice-cube[data-dice-theme="charcoal"] .cube-dot {
  width: 47%;
}
/* The single "1" pip is a casino ember-red that glows against the black face. */
.dice-face[data-dice-theme="charcoal"] i.pip-red,
.dice-cube[data-dice-theme="charcoal"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 36% 30%, #ff9f8f 0 13%, #ff705f 14% 36%, #c72417 60%, #5d0904 100%);
}
/* ---- end CHARCOAL dice theme ---- */

/* ---- SILVER dice theme (Pro) — polished chrome-silver: brushed satin grain at per-face angles, a
   bright diagonal specular band (the polished-metal cue) and cool gunmetal pips. CSS-only, deterministic,
   scoped by `data-dice-theme="silver"`. Surface-only — no pseudo-elements/radii/transforms of its own;
   the sealed base cube recolours via a cool --cube-seam and stays airtight at every tumble angle. ---- */
.dice-cube[data-dice-theme="silver"] {
  --cube-seam: #c4cad2; /* light silver — blends with the chrome face so the seal/edge is invisible */
}
.dice-face[data-dice-theme="silver"],
.dice-cube[data-dice-theme="silver"] .cube-face {
  --silver-grain-angle: 22deg;
  border-color: rgba(118, 126, 137, 0.9);
  background:
    /* Fine brushed-satin grain. */
    repeating-linear-gradient(var(--silver-grain-angle),
      transparent 0 4px, rgba(255, 255, 255, 0.06) 5px 6px, transparent 7px 13px,
      rgba(120, 128, 138, 0.05) 14px 15px, transparent 16px 24px),
    repeating-linear-gradient(calc(var(--silver-grain-angle) + 90deg),
      transparent 0 9px, rgba(255, 255, 255, 0.04) 10px 11px, transparent 12px 22px),
    /* Bright diagonal specular band — reads as polished metal. */
    linear-gradient(58deg, transparent 30%, rgba(255, 255, 255, 0.5) 45%, transparent 58%),
    /* Cool reflection patch. */
    radial-gradient(ellipse 52% 36% at 74% 72%, rgba(90, 98, 110, 0.28), transparent 62%),
    /* Top glint. */
    radial-gradient(circle at 26% 16%, rgba(255, 255, 255, 0.92), transparent 34%),
    /* Chrome base ramp (anisotropic reflection). */
    linear-gradient(150deg, #fdfeff 0%, #d6dbe1 20%, #a8afb9 44%, #ced4db 58%, #eef1f5 76%, #b2bac4 100%);
  background-blend-mode: screen, screen, screen, multiply, screen, normal;
}
.dice-face[data-dice-theme="silver"] {
  box-shadow:
    inset 4px 5px 8px rgba(255, 255, 255, 0.8),
    inset -7px -8px 13px rgba(70, 78, 90, 0.32),
    inset 0 0 0 max(2px, 0.3vw) rgba(255, 255, 255, 0.46),
    0 7px 9px rgba(0, 0, 0, 0.32),
    0 14px 18px rgba(0, 0, 0, 0.18);
}
.dice-cube[data-dice-theme="silver"] .cube-face {
  box-shadow:
    0 0 0 max(2px, calc(var(--dice-size) * 0.03)) var(--cube-seam),
    inset max(3px, calc(var(--dice-size) * 0.045)) max(3px, calc(var(--dice-size) * 0.045)) max(8px, calc(var(--dice-size) * 0.09)) rgba(255, 255, 255, 0.68),
    inset max(-8px, calc(var(--dice-size) * -0.09)) max(-9px, calc(var(--dice-size) * -0.1)) max(12px, calc(var(--dice-size) * 0.13)) rgba(58, 66, 78, 0.34),
    inset 0 0 0 max(2px, calc(var(--dice-size) * 0.018)) rgba(255, 255, 255, 0.5);
}
.dice-cube[data-dice-theme="silver"] .cube-front,
.dice-cube[data-dice-theme="silver"] .cube-back { --silver-grain-angle: 22deg; }
.dice-cube[data-dice-theme="silver"] .cube-left,
.dice-cube[data-dice-theme="silver"] .cube-right { --silver-grain-angle: 112deg; }
.dice-cube[data-dice-theme="silver"] .cube-top,
.dice-cube[data-dice-theme="silver"] .cube-bottom { --silver-grain-angle: 66deg; }
/* Pips: dark gunmetal, softly recessed into the polished face. */
.dice-face[data-dice-theme="silver"] i,
.dice-face[data-dice-theme="silver"] i.pip-red,
.dice-cube[data-dice-theme="silver"] .cube-dot,
.dice-cube[data-dice-theme="silver"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 36% 30%, #5a6270 0 16%, #2b313b 40%, #10141a 66%);
  box-shadow:
    inset 0 2px 3px rgba(0, 0, 0, 0.6),
    inset 0 -1px 1px rgba(255, 255, 255, 0.24),
    0 1px 0 rgba(255, 255, 255, 0.66);
}
.dice-face[data-dice-theme="silver"] i.pip-red,
.dice-cube[data-dice-theme="silver"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 36% 30%, #ff8f80 0 15%, #c62f28 40%, #6e0f0c 66%);
}
/* ---- end SILVER dice theme ---- */

/* ---- BRONZE dice theme (Pro) — warm patinated copper-bronze: brushed grain, a warm specular band and
   dark espresso pips. CSS-only, deterministic, scoped by `data-dice-theme="bronze"`. Surface-only — the
   sealed base cube recolours via a deep bronze --cube-seam and stays airtight at every tumble angle. ---- */
.dice-cube[data-dice-theme="bronze"] {
  --cube-seam: #a0672f; /* mid bronze — blends with the copper face so the seal/edge is invisible */
}
.dice-face[data-dice-theme="bronze"],
.dice-cube[data-dice-theme="bronze"] .cube-face {
  --bronze-grain-angle: 20deg;
  border-color: rgba(78, 44, 20, 0.92);
  background:
    repeating-linear-gradient(var(--bronze-grain-angle),
      transparent 0 4px, rgba(255, 226, 190, 0.06) 5px 6px, transparent 7px 13px,
      rgba(120, 70, 34, 0.06) 14px 15px, transparent 16px 24px),
    repeating-linear-gradient(calc(var(--bronze-grain-angle) + 90deg),
      transparent 0 9px, rgba(255, 220, 182, 0.045) 10px 11px, transparent 12px 22px),
    linear-gradient(58deg, transparent 30%, rgba(255, 228, 196, 0.5) 45%, transparent 58%),
    radial-gradient(ellipse 52% 36% at 74% 72%, rgba(90, 48, 20, 0.3), transparent 62%),
    radial-gradient(circle at 26% 16%, rgba(255, 234, 206, 0.88), transparent 34%),
    linear-gradient(150deg, #f4cfa2 0%, #d89c60 20%, #a4652f 44%, #c68350 58%, #e2ab73 76%, #8c5024 100%);
  background-blend-mode: screen, screen, screen, multiply, screen, normal;
}
.dice-face[data-dice-theme="bronze"] {
  box-shadow:
    inset 4px 5px 8px rgba(255, 228, 196, 0.6),
    inset -7px -8px 13px rgba(64, 34, 12, 0.42),
    inset 0 0 0 max(2px, 0.3vw) rgba(255, 224, 188, 0.32),
    0 7px 9px rgba(0, 0, 0, 0.36),
    0 14px 18px rgba(0, 0, 0, 0.22);
}
.dice-cube[data-dice-theme="bronze"] .cube-face {
  box-shadow:
    0 0 0 max(2px, calc(var(--dice-size) * 0.03)) var(--cube-seam),
    inset max(3px, calc(var(--dice-size) * 0.045)) max(3px, calc(var(--dice-size) * 0.045)) max(8px, calc(var(--dice-size) * 0.09)) rgba(255, 228, 196, 0.46),
    inset max(-8px, calc(var(--dice-size) * -0.09)) max(-9px, calc(var(--dice-size) * -0.1)) max(12px, calc(var(--dice-size) * 0.13)) rgba(58, 30, 10, 0.44),
    inset 0 0 0 max(2px, calc(var(--dice-size) * 0.018)) rgba(255, 220, 180, 0.3);
}
.dice-cube[data-dice-theme="bronze"] .cube-front,
.dice-cube[data-dice-theme="bronze"] .cube-back { --bronze-grain-angle: 20deg; }
.dice-cube[data-dice-theme="bronze"] .cube-left,
.dice-cube[data-dice-theme="bronze"] .cube-right { --bronze-grain-angle: 100deg; }
.dice-cube[data-dice-theme="bronze"] .cube-top,
.dice-cube[data-dice-theme="bronze"] .cube-bottom { --bronze-grain-angle: 62deg; }
/* Pips: dark espresso, recessed into the warm metal. */
.dice-face[data-dice-theme="bronze"] i,
.dice-face[data-dice-theme="bronze"] i.pip-red,
.dice-cube[data-dice-theme="bronze"] .cube-dot,
.dice-cube[data-dice-theme="bronze"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 36% 30%, #5c3a1e 0 16%, #331d0c 40%, #120802 66%);
  box-shadow:
    inset 0 2px 3px rgba(0, 0, 0, 0.58),
    inset 0 -1px 1px rgba(255, 216, 176, 0.24),
    0 1px 0 rgba(255, 224, 184, 0.5);
}
.dice-face[data-dice-theme="bronze"] i.pip-red,
.dice-cube[data-dice-theme="bronze"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 36% 30%, #ff9878 0 15%, #b83320 40%, #5e120a 66%);
}
/* ---- end BRONZE dice theme ---- */

/* ---- GOLD dice theme (Pro) — rich lustrous gold: brushed grain, a warm-white specular band and deep
   walnut-black pips. CSS-only, deterministic, scoped by `data-dice-theme="gold"`. Surface-only — the
   sealed base cube recolours via a dark-gold --cube-seam and stays airtight at every tumble angle. ---- */
.dice-cube[data-dice-theme="gold"] {
  --cube-seam: #cba233; /* mid gold — blends with the lustrous gold face so the seal/edge is invisible */
}
.dice-face[data-dice-theme="gold"],
.dice-cube[data-dice-theme="gold"] .cube-face {
  --gold-grain-angle: 16deg;
  border-color: rgba(120, 88, 20, 0.9);
  background:
    repeating-linear-gradient(var(--gold-grain-angle),
      transparent 0 4px, rgba(255, 248, 210, 0.07) 5px 6px, transparent 7px 13px,
      rgba(150, 112, 30, 0.05) 14px 15px, transparent 16px 24px),
    repeating-linear-gradient(calc(var(--gold-grain-angle) + 90deg),
      transparent 0 9px, rgba(255, 245, 200, 0.05) 10px 11px, transparent 12px 22px),
    linear-gradient(58deg, transparent 30%, rgba(255, 250, 220, 0.55) 45%, transparent 58%),
    radial-gradient(ellipse 52% 36% at 74% 72%, rgba(150, 105, 20, 0.26), transparent 62%),
    radial-gradient(circle at 26% 16%, rgba(255, 252, 232, 0.92), transparent 34%),
    linear-gradient(150deg, #fff6cf 0%, #f2d576 20%, #d5a326 44%, #eecb60 58%, #fbeaa0 76%, #cb9a26 100%);
  background-blend-mode: screen, screen, screen, multiply, screen, normal;
}
.dice-face[data-dice-theme="gold"] {
  box-shadow:
    inset 4px 5px 8px rgba(255, 248, 214, 0.78),
    inset -7px -8px 13px rgba(96, 66, 10, 0.34),
    inset 0 0 0 max(2px, 0.3vw) rgba(255, 246, 206, 0.4),
    0 7px 9px rgba(0, 0, 0, 0.34),
    0 14px 18px rgba(0, 0, 0, 0.2);
}
.dice-cube[data-dice-theme="gold"] .cube-face {
  box-shadow:
    0 0 0 max(2px, calc(var(--dice-size) * 0.03)) var(--cube-seam),
    inset max(3px, calc(var(--dice-size) * 0.045)) max(3px, calc(var(--dice-size) * 0.045)) max(8px, calc(var(--dice-size) * 0.09)) rgba(255, 248, 214, 0.62),
    inset max(-8px, calc(var(--dice-size) * -0.09)) max(-9px, calc(var(--dice-size) * -0.1)) max(12px, calc(var(--dice-size) * 0.13)) rgba(92, 62, 8, 0.36),
    inset 0 0 0 max(2px, calc(var(--dice-size) * 0.018)) rgba(255, 244, 200, 0.44);
}
.dice-cube[data-dice-theme="gold"] .cube-front,
.dice-cube[data-dice-theme="gold"] .cube-back { --gold-grain-angle: 16deg; }
.dice-cube[data-dice-theme="gold"] .cube-left,
.dice-cube[data-dice-theme="gold"] .cube-right { --gold-grain-angle: 104deg; }
.dice-cube[data-dice-theme="gold"] .cube-top,
.dice-cube[data-dice-theme="gold"] .cube-bottom { --gold-grain-angle: 58deg; }
/* Pips: deep walnut-black, recessed into the lustrous gold. */
.dice-face[data-dice-theme="gold"] i,
.dice-face[data-dice-theme="gold"] i.pip-red,
.dice-cube[data-dice-theme="gold"] .cube-dot,
.dice-cube[data-dice-theme="gold"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 36% 30%, #6b5320 0 16%, #3a2a0c 40%, #140d02 66%);
  box-shadow:
    inset 0 2px 3px rgba(0, 0, 0, 0.56),
    inset 0 -1px 1px rgba(255, 240, 190, 0.26),
    0 1px 0 rgba(255, 246, 206, 0.55);
}
.dice-face[data-dice-theme="gold"] i.pip-red,
.dice-cube[data-dice-theme="gold"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 36% 30%, #ff9a86 0 15%, #c22c22 40%, #6a0f09 66%);
}
/* ---- end GOLD dice theme ---- */

/* ---- WATER dice theme (Pro) — cool aqua glass: rippled sheen, caustic glints and a bright surface
   catchlight, with foam-white pips and a coral single "1". CSS-only, deterministic, scoped by
   `data-dice-theme="water"`. Surface-only — no pseudo-elements/radii/transforms; the sealed base cube
   recolours via a die-toned aqua --cube-seam (blends with the face, invisible seal) and stays airtight. ---- */
.dice-cube[data-dice-theme="water"] {
  --cube-seam: #3f86ad; /* mid aqua — blends with the water face so the seal/edge is invisible */
}
.dice-face[data-dice-theme="water"],
.dice-cube[data-dice-theme="water"] .cube-face {
  --water-ripple-angle: 24deg;
  border-color: rgba(28, 88, 118, 0.6);
  background:
    /* Rippled surface sheen — fine wavy light/dark bands. */
    repeating-linear-gradient(var(--water-ripple-angle),
      transparent 0 8px, rgba(224, 246, 255, 0.1) 9px 11px, transparent 12px 22px,
      rgba(88, 158, 198, 0.1) 23px 25px, transparent 26px 40px),
    /* Caustic glints — bright cyan-white pools. */
    radial-gradient(ellipse 40% 30% at 72% 24%, rgba(210, 245, 255, 0.5), transparent 60%),
    radial-gradient(ellipse 50% 36% at 26% 74%, rgba(78, 148, 194, 0.34), transparent 64%),
    /* Bright surface catchlight, upper-left. */
    radial-gradient(circle at 28% 18%, rgba(235, 250, 255, 0.85), transparent 34%),
    /* Deep-water base ramp (light aqua -> deep blue). */
    linear-gradient(150deg, #bfeaf7 0%, #7fc4e4 24%, #3f93c4 52%, #2f79ad 74%, #1d5883 100%);
  background-blend-mode: screen, screen, normal, screen, normal;
}
.dice-face[data-dice-theme="water"] {
  box-shadow:
    inset 4px 5px 8px rgba(232, 250, 255, 0.62),
    inset -7px -8px 13px rgba(18, 66, 98, 0.4),
    inset 0 0 0 max(2px, 0.3vw) rgba(224, 246, 255, 0.36),
    0 7px 9px rgba(0, 0, 0, 0.32),
    0 14px 18px rgba(0, 0, 0, 0.18);
}
.dice-cube[data-dice-theme="water"] .cube-face {
  box-shadow:
    0 0 0 max(2px, calc(var(--dice-size) * 0.03)) var(--cube-seam),
    inset max(3px, calc(var(--dice-size) * 0.045)) max(3px, calc(var(--dice-size) * 0.045)) max(8px, calc(var(--dice-size) * 0.09)) rgba(232, 250, 255, 0.5),
    inset max(-8px, calc(var(--dice-size) * -0.09)) max(-9px, calc(var(--dice-size) * -0.1)) max(12px, calc(var(--dice-size) * 0.13)) rgba(14, 58, 88, 0.42),
    inset 0 0 0 max(2px, calc(var(--dice-size) * 0.018)) rgba(224, 246, 255, 0.34);
}
.dice-cube[data-dice-theme="water"] .cube-front,
.dice-cube[data-dice-theme="water"] .cube-back { --water-ripple-angle: 24deg; }
.dice-cube[data-dice-theme="water"] .cube-left,
.dice-cube[data-dice-theme="water"] .cube-right { --water-ripple-angle: 108deg; }
.dice-cube[data-dice-theme="water"] .cube-top,
.dice-cube[data-dice-theme="water"] .cube-bottom { --water-ripple-angle: 62deg; }
/* Pips: deep-water ink, recessed with a bright rim so they pop on the aqua face. */
.dice-face[data-dice-theme="water"] i,
.dice-face[data-dice-theme="water"] i.pip-red,
.dice-cube[data-dice-theme="water"] .cube-dot,
.dice-cube[data-dice-theme="water"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 36% 30%, #2a6d8f 0 15%, #0e3a52 42%, #05202f 68%);
  box-shadow:
    inset 0 2px 3px rgba(0, 0, 0, 0.5),
    inset 0 -1px 1px rgba(210, 240, 255, 0.28),
    0 1px 0 rgba(230, 248, 255, 0.6);
}
/* The single "1" pip is a warm coral that pops against the cool blue. */
.dice-face[data-dice-theme="water"] i.pip-red,
.dice-cube[data-dice-theme="water"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 36% 30%, #ffcabf 0 15%, #ff7a63 40%, #d83f28 66%, #7c1c10 100%);
}
/* ---- end WATER dice theme ---- */

/* ---- FIRE dice theme (Pro) — glowing ember: flame-flicker streaks, hot glow pools and a bright hot
   core, with charred-black pips and a glowing-yellow single "1". CSS-only, deterministic, scoped by
   `data-dice-theme="fire"`. Surface-only — no pseudo-elements/radii/transforms; the sealed base cube
   recolours via a die-toned ember --cube-seam (blends with the face, invisible seal) and stays airtight. ---- */
.dice-cube[data-dice-theme="fire"] {
  --cube-seam: #c1541f; /* mid ember-orange — blends with the fire face so the seal/edge is invisible */
}
.dice-face[data-dice-theme="fire"],
.dice-cube[data-dice-theme="fire"] .cube-face {
  --fire-flicker-angle: 20deg;
  border-color: rgba(120, 40, 10, 0.7);
  background:
    /* Flame-flicker streaks — hot light + charred dark bands. */
    repeating-linear-gradient(var(--fire-flicker-angle),
      transparent 0 7px, rgba(255, 230, 150, 0.12) 8px 10px, transparent 11px 20px,
      rgba(150, 30, 10, 0.14) 21px 23px, transparent 24px 36px),
    /* Hot glow pools. */
    radial-gradient(ellipse 44% 34% at 30% 30%, rgba(255, 220, 120, 0.55), transparent 60%),
    radial-gradient(ellipse 50% 40% at 74% 76%, rgba(180, 40, 10, 0.4), transparent 64%),
    /* Bright hot core, upper-left. */
    radial-gradient(circle at 34% 24%, rgba(255, 245, 200, 0.8), transparent 36%),
    /* Fire base ramp (yellow -> orange -> deep red). */
    linear-gradient(150deg, #ffd24a 0%, #ff9a2e 26%, #f0561c 54%, #c8321a 76%, #7d1a12 100%);
  background-blend-mode: screen, screen, multiply, screen, normal;
}
.dice-face[data-dice-theme="fire"] {
  box-shadow:
    inset 4px 5px 8px rgba(255, 236, 180, 0.6),
    inset -7px -8px 13px rgba(90, 20, 6, 0.46),
    inset 0 0 0 max(2px, 0.3vw) rgba(255, 224, 150, 0.3),
    0 7px 9px rgba(0, 0, 0, 0.36),
    0 14px 18px rgba(0, 0, 0, 0.22);
}
.dice-cube[data-dice-theme="fire"] .cube-face {
  box-shadow:
    0 0 0 max(2px, calc(var(--dice-size) * 0.03)) var(--cube-seam),
    inset max(3px, calc(var(--dice-size) * 0.045)) max(3px, calc(var(--dice-size) * 0.045)) max(8px, calc(var(--dice-size) * 0.09)) rgba(255, 236, 180, 0.46),
    inset max(-8px, calc(var(--dice-size) * -0.09)) max(-9px, calc(var(--dice-size) * -0.1)) max(12px, calc(var(--dice-size) * 0.13)) rgba(84, 18, 6, 0.46),
    inset 0 0 0 max(2px, calc(var(--dice-size) * 0.018)) rgba(255, 224, 150, 0.28);
}
.dice-cube[data-dice-theme="fire"] .cube-front,
.dice-cube[data-dice-theme="fire"] .cube-back { --fire-flicker-angle: 20deg; }
.dice-cube[data-dice-theme="fire"] .cube-left,
.dice-cube[data-dice-theme="fire"] .cube-right { --fire-flicker-angle: 104deg; }
.dice-cube[data-dice-theme="fire"] .cube-top,
.dice-cube[data-dice-theme="fire"] .cube-bottom { --fire-flicker-angle: 60deg; }
/* Pips: charred coal, recessed into the hot metal-like face. */
.dice-face[data-dice-theme="fire"] i,
.dice-face[data-dice-theme="fire"] i.pip-red,
.dice-cube[data-dice-theme="fire"] .cube-dot,
.dice-cube[data-dice-theme="fire"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 36% 30%, #4a2418 0 16%, #26100a 42%, #0c0402 68%);
  box-shadow:
    inset 0 2px 3px rgba(0, 0, 0, 0.6),
    inset 0 -1px 1px rgba(255, 196, 120, 0.24),
    0 1px 0 rgba(255, 214, 150, 0.5);
}
/* The single "1" pip glows like the hottest ember. */
.dice-face[data-dice-theme="fire"] i.pip-red,
.dice-cube[data-dice-theme="fire"] .cube-dot.is-red-pip {
  background: radial-gradient(circle at 36% 30%, #fff6d0 0 15%, #ffcf4a 40%, #ff8a1e 66%, #c85a12 100%);
}
/* ---- end FIRE dice theme ---- */

.count-option {
  min-width: clamp(40px, 11vw, 46px);
  height: clamp(40px, 11vw, 46px);
  border-radius: 9px;
  border: 1px solid rgba(255, 255, 255, 0.26);
  background: rgba(2, 20, 16, 0.42);
  color: #f6fffb;
  font: 800 clamp(13px, 3.4vw, 16px)/1 var(--font-ui);
  cursor: pointer;
  transition: transform 140ms ease, background 140ms ease, border-color 140ms ease;
}

.count-option.is-selected {
  background: linear-gradient(160deg, #1f9d57, #0c7a3f);
  border-color: rgba(255, 255, 255, 0.5);
  color: #fff;
  transform: translateY(-1px);
  box-shadow: 0 3px 9px rgba(0, 0, 0, 0.35);
}

/* Result-style picker (Full Finish vs First Home Wins): two titled cards, same
   dark-glass / green-selected language as the count + token-style pickers. */
.result-mode-options {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 7px;
  flex: 1;
  min-width: 0;
}
.result-mode-option {
  --mode-accent: 120, 200, 160; /* per-mode accent RGB channels (set per data-result-mode below) */
  position: relative;
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 7px 7px 6px;
  border-radius: 14px;
  border: 1px solid rgba(var(--mode-accent), 0.45);
  background:
    radial-gradient(125% 90% at 50% -12%, rgba(var(--mode-accent), 0.22), rgba(var(--mode-accent), 0) 60%),
    linear-gradient(168deg, rgba(255, 255, 255, 0.05), rgba(3, 16, 13, 0.55) 78%);
  box-shadow: 0 5px 14px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  color: #eaf7f1;
  text-align: center;
  cursor: pointer;
  transition: transform 150ms ease, box-shadow 150ms ease, border-color 150ms ease, background 150ms ease;
}
/* Big circular "mode badge": a coloured disc (per-mode accent) with a bold WHITE silhouette
   inside — game-style, reads instantly + still paired with the text label (accessibility).
   Static SVG (no animation) → renders once, cheap on low-end WebView. */
.result-mode-icon {
  width: clamp(28px, 7.2vw, 42px);
  height: clamp(28px, 7.2vw, 42px);
  border-radius: 50%;
  display: grid;
  place-items: center;
  background:
    radial-gradient(circle at 37% 26%, rgba(255, 255, 255, 0.42), rgba(255, 255, 255, 0) 42%),
    radial-gradient(circle at 50% 118%, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0) 55%),
    radial-gradient(circle at 50% 44%, rgb(var(--mode-accent)), rgba(var(--mode-accent), 0.62));
  box-shadow:
    0 4px 10px rgba(0, 0, 0, 0.42),
    0 0 12px rgba(var(--mode-accent), 0.4),
    inset 0 2px 1.5px rgba(255, 255, 255, 0.55),
    inset 0 -3px 4px rgba(0, 0, 0, 0.25);
  color: #ffffff;
  pointer-events: none;
}
.result-mode-icon svg { width: 60%; height: 60%; display: block; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.3)); }
.result-mode-option[data-result-mode="placements"] { --mode-accent: 255, 193, 77; }  /* gold — rank everyone */
.result-mode-option[data-result-mode="winner"]     { --mode-accent: 46, 213, 115; }  /* green — first wins */
.result-mode-option[data-result-mode="onetoken"]   { --mode-accent: 74, 157, 255; }  /* blue — single token */
.result-mode-option[data-result-mode="knockout"]   { --mode-accent: 255, 91, 91; }   /* red — elimination */
.result-mode-option[data-result-mode="speed"]      { --mode-accent: 176, 107, 255; } /* purple — speed */
/* WebPreview has no global [hidden] reset and .result-mode-option sets display:flex,
   so the attribute alone won't hide a card — this makes hidden cards (e.g. the Party
   card outside Pass N Play) actually disappear. */
.result-mode-option[hidden] { display: none !important; }
.result-mode-option strong { font: 800 clamp(12px, 3.3vw, 14.5px)/1.05 var(--font-ui); text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45); }
.result-mode-option small { font: 700 clamp(9.5px, 2.6vw, 11.5px)/1.1 var(--font-ui); opacity: 0.96; color: #eef9f3; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4); white-space: nowrap; }
.result-mode-option.is-selected {
  border-color: rgba(var(--mode-accent), 0.95);
  background: linear-gradient(168deg, rgba(var(--mode-accent), 0.42), rgba(var(--mode-accent), 0.14) 75%);
  color: #fff;
  transform: translateY(-2px);
  box-shadow:
    0 0 0 1px rgba(var(--mode-accent), 0.55),
    0 7px 18px rgba(var(--mode-accent), 0.32),
    inset 0 1px 0 rgba(255, 255, 255, 0.14);
}
.result-mode-option.is-selected small { opacity: 1; }
/* Elegant "selected" tick badge in the card's accent colour, top-right corner. */
.result-mode-option.is-selected::after {
  content: "✓";
  position: absolute;
  top: 7px;
  right: 8px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: rgb(var(--mode-accent));
  color: #fff;
  font: 900 12px/20px var(--font-ui);
  text-align: center;
  border: 1.5px solid rgba(255, 255, 255, 0.9);
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.4);
}
.result-mode-option.is-selected .result-mode-icon {
  box-shadow:
    0 0 0 2px rgba(255, 255, 255, 0.3),
    0 4px 12px rgba(var(--mode-accent), 0.5),
    inset 0 1.5px 1px rgba(255, 255, 255, 0.5);
}
/* The 5th card (Speed) spans the full width as a tidy bottom row in the 2-col grid. */
.result-mode-option.result-mode-wide { grid-column: 1 / -1; }


html[data-theme="light"] .count-option {
  background: rgba(255, 255, 255, 0.66);
  border-color: rgba(20, 96, 70, 0.3);
  color: var(--lobby-ink);
}

html[data-theme="light"] .count-option.is-selected {
  background: linear-gradient(160deg, #1f9d57, #0c7a3f);
  color: #fff;
}

/* In-game players strip (3-4 player games): a chip per active player at the
   top-centre of the board, highlighting whose turn it is. */
.players-strip {
  position: absolute;
  top: clamp(5px, 1.3vw, 11px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 6;
  display: flex;
  gap: clamp(4px, 1.1vw, 7px);
  padding: clamp(3px, 0.8vw, 5px);
  border-radius: 999px;
  background: rgba(4, 22, 17, 0.64);
  border: 1px solid rgba(178, 240, 255, 0.34);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.36);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  max-width: calc(100% - 20px);
}

.players-strip[hidden] {
  display: none;
}

.player-chip {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px 3px 5px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid transparent;
  font: 800 clamp(10px, 2.3vw, 13px)/1 var(--font-ui);
  color: #eafff7;
  min-width: 0;
}

.player-chip .chip-dot {
  width: clamp(12px, 3vw, 16px);
  height: clamp(12px, 3vw, 16px);
  border-radius: 50%;
  background: var(--chip-color, #ccc);
  box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.45), 0 1px 2px rgba(0, 0, 0, 0.4);
  flex: 0 0 auto;
}

.player-chip .chip-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 18vw;
}

.player-chip.is-turn {
  background: rgba(255, 222, 64, 0.2);
  border-color: rgba(255, 236, 103, 0.92);
  transform: translateY(-1px) scale(1.05);
  box-shadow: 0 0 11px rgba(255, 236, 103, 0.5);
}

html[data-theme="light"] .players-strip {
  background: rgba(255, 255, 255, 0.82);
  border-color: rgba(20, 96, 70, 0.28);
}

html[data-theme="light"] .player-chip {
  color: #143f2c;
}

/* ===== In-game seat pods + corner dice (replaces the bottom HUD) ===== */
.bottom-hud {
  display: none !important;
}

.seat-pods {
  position: absolute;
  /* Span the BORDER box, not the padding box: .board-frame's turn-frame border (7-12px) is inside
     its border-box, so an all-0 overlay made every pod/flag/dice anchor 7-12px too far IN — they
     visually stuck ~2px INTO the glowing frame. Extending by the border width makes 100% = the
     VISIBLE board edge, so every pod sits a true --seat-pod-gap clear of the frame at every size. */
  top: calc(-1 * var(--board-frame-border, 0px));
  right: calc(-1 * var(--board-frame-border, 0px));
  bottom: calc(-1 * var(--board-frame-border, 0px));
  left: calc(-1 * var(--board-frame-border, 0px));
  z-index: 5;
  pointer-events: none;
}

.seat-pod {
  position: absolute;
  display: flex;
  align-items: center;
  gap: clamp(3px, 1vw, 6px);
  opacity: 0.58;
  transition: opacity 220ms ease, filter 220ms ease;
}

.seat-pod[hidden] {
  display: none;
}

/* Pods sit OUTSIDE the board edges (above for top seats, below for bottom),
   so they never cover tokens or the on-board nameplates. */
.seat-pod[data-seat="red"] { bottom: calc(100% + var(--seat-pod-gap)); left: 1%; }
.seat-pod[data-seat="green"] { bottom: calc(100% + var(--seat-pod-gap)); right: 1%; flex-direction: row-reverse; }
.seat-pod[data-seat="blue"] { top: calc(100% + var(--seat-pod-gap)); left: 1%; }
.seat-pod[data-seat="yellow"] { top: calc(100% + var(--seat-pod-gap)); right: 1%; flex-direction: row-reverse; }
.seat-pod[data-view-seat="red"] { top: auto; right: auto; bottom: calc(100% + var(--seat-pod-gap)); left: 1%; flex-direction: row; }
.seat-pod[data-view-seat="green"] { top: auto; left: auto; bottom: calc(100% + var(--seat-pod-gap)); right: 1%; flex-direction: row-reverse; }
.seat-pod[data-view-seat="blue"] { bottom: auto; right: auto; top: calc(100% + var(--seat-pod-gap)); left: 1%; flex-direction: row; }
.seat-pod[data-view-seat="yellow"] { bottom: auto; left: auto; top: calc(100% + var(--seat-pod-gap)); right: 1%; flex-direction: row-reverse; }

.seat-pod.is-active {
  opacity: 1;
  /* Extra space between the avatar (casino chase-light ring) and the parked roll dice
     (roll-ready glow) so the two seat-colour animations stay separated, not overlapping
     into a blob. Only the active pod holds the dice, so only it needs the wider gap. */
  gap: clamp(16px, 5vw, 26px);
}

.seat-avatar {
  position: relative;
  width: var(--seat-avatar-size);
  height: var(--seat-avatar-size);
  border-radius: 50%;
  display: grid;
  place-items: center;
  font: 900 clamp(18px, 4.4vw, 24px)/1 var(--font-emoji);
  color: #fff;
  background:
    radial-gradient(circle at 32% 28%, rgba(255, 255, 255, 0.45), transparent 46%),
    var(--seat-color, #888);
  border: 2px solid rgba(255, 255, 255, 0.8);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

/* Tap-to-roll: the active player can roll by tapping their own avatar too (not just the
   dice). `can-roll` (set on the stage only while a manual roll is actually possible) makes
   the active player's avatar feel tappable. The roll's haptic + sound + dice animation is
   the press feedback. The avatars are aria-hidden, so the dice button stays the canonical
   accessible roll control; this is a pure touch convenience. */
/* #seatPods is decorative (aria-hidden) and set to pointer-events:none, so real finger taps
   fall straight through the avatar/badge to the stage behind. Re-enable taps on the active
   pod (and its children) ONLY while a manual roll is possible, so the player can actually tap
   their badge/avatar/flag to roll. During token-pick or the opponent/AI's turn, can-roll is
   false → the pod returns to pointer-events:none and taps pass through exactly as before. */
.phone-stage.can-roll .seat-pod.is-active,
.phone-stage.can-roll .seat-pod.is-active * {
  pointer-events: auto;
}
.phone-stage.can-roll .seat-pod.is-active {
  cursor: pointer;
}

.seat-avatar.is-bot::after {
  content: "🤖";
  font-size: clamp(14px, 3.6vw, 19px);
}

.seat-flag {
  position: absolute;
  z-index: 4;
  top: -4px;
  left: calc(var(--seat-avatar-size) / 2);
  min-width: clamp(18px, 4.4vw, 23px);
  max-width: calc(var(--seat-avatar-size) + 2px);
  height: clamp(15px, 3.8vw, 19px);
  display: grid;
  place-items: center;
  padding: 0 2px;
  border-radius: 6px;
  background:
    linear-gradient(150deg, rgba(255, 255, 255, 0.96), rgba(216, 242, 232, 0.88));
  border: 1px solid rgba(255, 255, 255, 0.86);
  box-shadow:
    0 2px 5px rgba(0, 0, 0, 0.3),
    0 0 0 1px rgba(8, 34, 25, 0.12),
    0 0 7px rgba(255, 255, 255, 0.16);
  font: 900 clamp(11px, 3vw, 14px)/1 var(--font-emoji);
  pointer-events: none;
  transform: translateX(-50%);
  transform-origin: 50% 50%;
}

.seat-pod[data-seat="green"] .seat-flag,
.seat-pod[data-seat="yellow"] .seat-flag {
  left: auto;
  right: calc(var(--seat-avatar-size) / 2);
  transform: translateX(50%);
}

.seat-pod[data-view-seat="red"] .seat-flag,
.seat-pod[data-view-seat="blue"] .seat-flag {
  left: calc(var(--seat-avatar-size) / 2);
  right: auto;
  transform: translateX(-50%);
}

.seat-pod[data-view-seat="green"] .seat-flag,
.seat-pod[data-view-seat="yellow"] .seat-flag {
  left: auto;
  right: calc(var(--seat-avatar-size) / 2);
  transform: translateX(50%);
}

.seat-flag:empty {
  display: none;
}

/* Persistent per-seat knockout tally on the pod (elimination modes only) — "💀N" tokens out for good. */
.seat-ko-badge {
  position: absolute;
  z-index: 6;
  top: -5px;
  right: -5px;
  font: 800 11px/1 var(--font-display, sans-serif);
  padding: 2px 4px;
  border-radius: 8px;
  background: rgba(22, 6, 8, 0.92);
  color: #ff7a7a;
  border: 1px solid rgba(255, 110, 110, 0.55);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
  pointer-events: none;
  white-space: nowrap;
}

.seat-flag .country-flag-img {
  width: clamp(17px, 4.1vw, 22px);
  border-radius: 2px;
  box-shadow:
    0 1px 4px rgba(0, 0, 0, 0.32),
    inset 0 0 0 1px rgba(255, 255, 255, 0.45);
}

.seat-flag .country-flag-fallback {
  font-size: clamp(11px, 3vw, 14px);
}

.seat-pod.is-active .seat-avatar {
  box-shadow: 0 0 0 2px rgba(255, 224, 96, 0.95), 0 0 8px rgba(255, 216, 80, 0.6);
}

/* Casino-style chasing-lights ring around the WHOSE-TURN avatar — a rotating ring of
   golden bulbs so it's instantly obvious whose turn it is. Child of the circular
   avatar, so it's perfectly centred for every seat (human or AI). Inactive avatars
   keep it display:none (zero animation cost), and it's outside the idle-pause set so
   it keeps spinning as a live turn indicator. */
.seat-lights { display: none; }
.seat-pod.is-active .seat-lights {
  display: block;
  position: absolute;
  top: -9px; right: -9px; bottom: -9px; left: -9px; /* inset longhands (Chromium<87 WebView support) */
  border-radius: 50%;
  /* Alternating bright-white + the player's own SEAT COLOUR bulbs, dark gaps = a casino
     marquee ring tinted to that player's frame colour. --seat-color is set per pod in
     renderSeatPods and inherits down to here; white keeps the "lit bulb" sparkle. */
  background: repeating-conic-gradient(from 0deg,
    #ffffff 0deg 5deg, transparent 5deg 12deg,
    var(--seat-color, #ffc21f) 12deg 17deg, transparent 17deg 24deg);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 5px));
          mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 5px));
  filter: drop-shadow(0 0 3px var(--seat-color, #ffd23f));
  animation: seat-lights-spin 1s linear infinite;
  pointer-events: none;
  z-index: -1;
}
@keyframes seat-lights-spin { to { transform: rotate(360deg); } }
/* Token-pick phase: the avatar's casino ring belongs to the ROLL phase, so hide it
   while a token is being chosen — only the on-board token aura animates then. */
.phone-stage.is-awaiting-token .seat-lights { display: none; }

/* Steady seat-colour breathing HALO on the active avatar — the "still your turn" anchor.
   Opacity-animated (GPU-cheap for low-end Android), ~2.8s/~0.35Hz (seizure-safe). Runs in
   BOTH roll + token phases (fills the gap the roll-only casino ring leaves). Sits behind
   the avatar (z-index:-1) inside the casino ring; colour from the per-pod --seat-color-rgb. */
.seat-halo { display: none; }
.seat-pod.is-active .seat-halo {
  display: block;
  position: absolute;
  top: -6px; right: -6px; bottom: -6px; left: -6px; /* inset longhands (Chromium<87 WebView support) */
  border-radius: 50%;
  box-shadow:
    0 0 10px 2px rgba(var(--seat-color-rgb, 255, 210, 70), 0.55),
    0 0 26px 4px rgba(var(--seat-color-rgb, 255, 210, 70), 0.30),
    0 0 44px 8px rgba(var(--seat-color-rgb, 255, 210, 70), 0.15);
  opacity: 0.4;
  will-change: opacity;
  z-index: -1;
  pointer-events: none;
  animation: seat-frame-breathe 2.8s cubic-bezier(0.4, 0, 0.2, 1) infinite alternate;
}
@keyframes seat-frame-breathe { from { opacity: 0.4; } to { opacity: 1; } }
/* One-shot hand-off pop the instant the turn arrives (grabs the peripheral glance). */
.seat-pod.just-activated .seat-avatar { animation: seat-handoff-pop 380ms cubic-bezier(0.22, 1, 0.36, 1); }
@keyframes seat-handoff-pop { 0% { transform: scale(1); } 45% { transform: scale(1.07); } 100% { transform: scale(1); } }
@media (prefers-reduced-motion: reduce) {
  .seat-pod.is-active .seat-halo { animation: none; opacity: 0.7; } /* static halo stays */
  .seat-pod.just-activated .seat-avatar { animation: none; }
}
@media (prefers-reduced-motion: reduce) {
  .seat-pod.is-active .seat-lights { animation: none; } /* static gold bulb ring, no spin */
}

/* Per-turn countdown ring around the active player's avatar. JS sets
   --timer-frac (1→0) and --ring-color (green→amber→red) each frame; the urgent
   data-state adds a gentle pulse (transform/opacity are Chromium-80 safe). */
.seat-avatar.has-timer::before {
  content: "";
  position: absolute;
  top: -4px; right: -4px; bottom: -4px; left: -4px; /* inset longhands (Chromium<87 WebView support) */
  border-radius: 50%;
  background: conic-gradient(
    var(--ring-color, #4ade80) calc(var(--timer-frac, 1) * 360deg),
    rgba(255, 255, 255, 0.16) 0
  );
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));
          mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));
  pointer-events: none;
  z-index: -1;
}
.seat-avatar.has-timer[data-timer-state="urgent"]::before {
  animation: seat-timer-pulse 1s ease-in-out infinite;
}
@keyframes seat-timer-pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.07); opacity: 0.8; }
}
/* Digital seconds-left readout, centred over the avatar (JS sets --timer-num as
   a quoted string each tick). Uses ::after so renderSeatPods' textContent never
   wipes it; the active human avatar is never .is-bot, so no ::after clash. */
.seat-avatar.has-timer::after {
  content: var(--timer-num, "");
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(4, 14, 10, 0.64);
  color: var(--ring-color, #4ade80);
  font: 900 clamp(17px, 4.6vw, 23px)/1 var(--font-ui);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
  pointer-events: none;
  z-index: 1;
}

.seat-dice {
  position: relative;
  width: var(--corner-dice-size);
  height: var(--corner-dice-size);
  border-radius: 24%;
  background:
    radial-gradient(circle at 30% 30%, #4a4a4a 0 12%, transparent 13%),
    radial-gradient(circle at 70% 30%, #4a4a4a 0 12%, transparent 13%),
    radial-gradient(circle at 50% 50%, #4a4a4a 0 12%, transparent 13%),
    radial-gradient(circle at 30% 70%, #4a4a4a 0 12%, transparent 13%),
    radial-gradient(circle at 70% 70%, #4a4a4a 0 12%, transparent 13%),
    linear-gradient(160deg, #e8e8e8, #b0b0b0);
  border: 1px solid rgba(255, 255, 255, 0.55);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6), 0 2px 6px rgba(0, 0, 0, 0.42);
  opacity: 0.62;
  filter: saturate(0.55);
}

/* The active player's static dice is hidden; the live #rollButton sits there. */
.phone-stage[data-turn-seat="red"] .seat-pod[data-seat="red"] .seat-dice,
.phone-stage[data-turn-seat="green"] .seat-pod[data-seat="green"] .seat-dice,
.phone-stage[data-turn-seat="yellow"] .seat-pod[data-seat="yellow"] .seat-dice,
.phone-stage[data-turn-seat="blue"] .seat-pod[data-seat="blue"] .seat-dice {
  display: none;
}
.phone-stage[data-turn-seat="red"] .seat-pod[data-view-seat="red"] .seat-dice,
.phone-stage[data-turn-seat="green"] .seat-pod[data-view-seat="green"] .seat-dice,
.phone-stage[data-turn-seat="yellow"] .seat-pod[data-view-seat="yellow"] .seat-dice,
.phone-stage[data-turn-seat="blue"] .seat-pod[data-view-seat="blue"] .seat-dice {
  display: none;
}

/* Live dice, now living in the board frame, parked at the active corner. */
/* Bare die (no plate) — used whether the dice is parked inside a pod (offline)
   or on the board frame (online). */
.board-frame .dice-button {
  width: var(--corner-dice-size);
  height: var(--corner-dice-size);
  margin: 0;
  background: none;
  border: none;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* Active (human) turn: the bare die gently pulses to invite the tap. */
.board-frame .dice-button:not(:disabled) {
  box-shadow: none;
  animation: corner-dice-ready 1.2s ease-in-out infinite;
}

.board-frame .dice-button .dice-face,
.seat-pod .dice-button .dice-face {
  /* Drive the die by HEIGHT (aspect-ratio:1 then sets the width) so it fills the
     button box — a width:% alone is constrained by the grid cell and stays small. */
  height: 90% !important;
  width: auto !important;
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5));
}

/* The corner roll-die is the PRIMARY tap action: fill the box and give the
   (previously bare) die light chrome so it reads as a clear, prominent button.
   The box size (--corner-dice-size) is UNCHANGED, so the board stays full at
   every screen size — the die just looks bigger inside the same footprint. */
.board-frame .dice-button:not(.rolling):not(.is-throwing),
.seat-pod .dice-button:not(.rolling):not(.is-throwing) {
  background:
    radial-gradient(circle at 50% 20%, rgba(255, 255, 255, 0.5), transparent 46%),
    rgba(255, 240, 205, 0.16);
  border: 1px solid rgba(255, 236, 165, 0.42);
  border-radius: 28%;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.42), 0 4px 11px rgba(0, 0, 0, 0.28);
}

/* Roll-ready dice: a pulsing glow in the ACTIVE seat's colour so it's obvious the
   player must tap-to-roll (matches the selectable-token aura). :not(:disabled) is
   exactly the roll phase — the local human's turn, not rolling/awaiting-move (the AI's
   dice is :disabled, so it doesn't glow). --seat-color-rgb is inherited from the active
   pod the die is parked in (offline); falls back to gold (e.g. online board-frame). */
.dice-button:not(:disabled):not(.rolling):not(.is-throwing) {
  animation: dice-roll-ready 1.05s ease-in-out infinite;
}
@keyframes dice-roll-ready {
  0%, 100% {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.42), 0 4px 11px rgba(0, 0, 0, 0.28),
      0 0 0 2px rgba(var(--seat-color-rgb, 255, 210, 70), 0.45),
      0 0 7px 0 rgba(var(--seat-color-rgb, 255, 210, 70), 0.45);
  }
  50% {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.42), 0 4px 11px rgba(0, 0, 0, 0.28),
      0 0 0 2.5px rgba(var(--seat-color-rgb, 255, 210, 70), 0.85),
      0 0 12px 1px rgba(var(--seat-color-rgb, 255, 210, 70), 0.75);
  }
}
@media (prefers-reduced-motion: reduce) {
  .dice-button:not(:disabled):not(.rolling):not(.is-throwing) {
    animation: none;
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.42), 0 4px 11px rgba(0, 0, 0, 0.28),
      0 0 0 2px rgba(var(--seat-color-rgb, 255, 210, 70), 0.6),
      0 0 8px 1px rgba(var(--seat-color-rgb, 255, 210, 70), 0.5);
  }
}

/* Offline: the die flows inside the active pod so flex centres it next to the
   avatar (perfect vertical alignment, any size). The pods container is
   pointer-events:none so it never blocks board taps — but the dice itself MUST
   stay tappable, so re-enable pointer events on it. */
.seat-pod .dice-button {
  position: static;
  flex: 0 0 auto;
  pointer-events: auto;
}

/* Online (no pods): the die is a board-frame child, parked at the active corner. */
.board-frame > .dice-button {
  position: absolute;
  z-index: 7;
}

@keyframes corner-dice-ready {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.09); }
}

.phone-stage[data-turn-seat="blue"] .board-frame > .dice-button { top: calc(100% + var(--seat-pod-gap) + var(--board-frame-border, 0px)); left: calc(1% + var(--seat-avatar-size) + clamp(4px, 1vw, 6px)); }
.phone-stage[data-turn-seat="red"] .board-frame > .dice-button { bottom: calc(100% + var(--seat-pod-gap) + var(--board-frame-border, 0px)); left: calc(1% + var(--seat-avatar-size) + clamp(4px, 1vw, 6px)); }
.phone-stage[data-turn-seat="green"] .board-frame > .dice-button { bottom: calc(100% + var(--seat-pod-gap) + var(--board-frame-border, 0px)); right: calc(1% + var(--seat-avatar-size) + clamp(4px, 1vw, 6px)); }
.phone-stage[data-turn-seat="yellow"] .board-frame > .dice-button { top: calc(100% + var(--seat-pod-gap) + var(--board-frame-border, 0px)); right: calc(1% + var(--seat-avatar-size) + clamp(4px, 1vw, 6px)); }

.phone-stage.call-docked[data-turn-seat="blue"] .board-frame > .dice-button {
  left: calc(1% + var(--call-tile-size) + clamp(14px, 3.4vw, 22px));
}

.phone-stage.call-docked[data-turn-seat="red"] .board-frame > .dice-button {
  left: calc(1% + var(--call-tile-size) + clamp(14px, 3.4vw, 22px));
}

.phone-stage.call-docked[data-turn-seat="green"] .board-frame > .dice-button {
  right: calc(1% + var(--call-tile-size) + clamp(14px, 3.4vw, 22px));
}

.phone-stage.call-docked[data-turn-seat="yellow"] .board-frame > .dice-button {
  right: calc(1% + var(--call-tile-size) + clamp(14px, 3.4vw, 22px));
}

.profile-stats {
  display: grid;
  gap: 5px;
  justify-items: end;
}

.stat-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 9px;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: var(--surface-2);
  font: 850 clamp(11px, 2.7vw, 13px)/1 var(--font-ui);
  color: var(--ink);
  box-shadow: var(--e1);
}

.stat-pill b {
  color: var(--gold-1);
}

.stat-pill i {
  font-style: normal;
  font-size: 0.92em;
}
/* Tappable rank chip on the home profile (opens the leaderboard). */
button.stat-pill.stat-rank { cursor: pointer; position: relative; }
/* Expand the tap target to ~44px without enlarging the visible chip (it is only
   ~22px tall on a 320px screen — below the WCAG 2.5.8 / Material touch minimum).
   The chip has no overflow:hidden, so the invisible ::before is not clipped. */
button.stat-pill.stat-rank::before {
  content: "";
  position: absolute;
  top: -11px; right: -11px; bottom: -11px; left: -11px; /* inset longhands (Chromium<87 WebView support) */
  border-radius: 999px;
}
.stat-pill.stat-rank b { color: #ffe487; max-width: 86px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stat-pill.stat-rank:active { transform: scale(0.96); }

html[data-theme="light"] .stat-pill {
  background: linear-gradient(160deg, rgba(255, 244, 196, 0.92), rgba(255, 224, 130, 0.92));
  color: #6d4a02;
  border-color: rgba(186, 132, 14, 0.45);
  text-shadow: none;
}
/* The pill's INNER <b> carries dark-theme gold — var(--gold-1) on every stat, and a hardcoded #ffe487 on
   the rank chip — and both are more specific than the rule above, so the light theme's own ink never
   reached the only text these pills contain. Measured against this pill's own gradient that is 1.05:1 for
   a stat value and 1.03:1 for the word "Ranked", where AA asks 4.5:1; #6d4a02 restores 6.2-7.2:1. It was
   invisible rather than merely faint, and it took an App Store screenshot to notice. */
html[data-theme="light"] .stat-pill b,
html[data-theme="light"] .stat-pill.stat-rank b {
  color: #6d4a02;
}

.home-icon-button {
  width: clamp(44px, 10.4vw, 54px);
  height: clamp(44px, 10.4vw, 54px);
  display: grid;
  place-items: center;
  align-content: center;
  gap: 4px;
  border-radius: 13px;
  border: 1px solid rgba(189, 250, 235, 0.42);
  background:
    linear-gradient(145deg, rgba(255, 255, 255, 0.22), transparent 40%),
    linear-gradient(135deg, rgba(23, 99, 78, 0.94), rgba(7, 42, 33, 0.95));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.32),
    inset 0 -4px 10px rgba(0, 0, 0, 0.26),
    0 8px 18px rgba(0, 0, 0, 0.32),
    0 0 16px rgba(255, 216, 77, 0.18);
  transition: transform 140ms ease;
}

.home-icon-button:active {
  transform: scale(0.92);
}

.home-icon-button span {
  width: 19px;
  height: 3px;
  border-radius: 2px;
  background: #ffe9a8;
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.35);
}

/* ---- Language selector (globe button + popup) ------------------------- */
/* The same glass globe button sits at the top of BOTH the home and game
   screens at an identical size; tapping it opens the language popup. */
.lang-button {
  width: clamp(42px, 9.2vw, 52px);
  height: clamp(42px, 9.2vw, 52px);
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  border-radius: 13px;
  border: 1px solid rgba(189, 250, 235, 0.42);
  background:
    linear-gradient(145deg, rgba(255, 255, 255, 0.22), transparent 40%),
    linear-gradient(135deg, rgba(23, 99, 78, 0.94), rgba(7, 42, 33, 0.95));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.32),
    inset 0 -4px 10px rgba(0, 0, 0, 0.26),
    0 8px 18px rgba(0, 0, 0, 0.32),
    0 0 16px rgba(255, 216, 77, 0.18);
  cursor: pointer;
  transition: transform 140ms ease, filter 140ms ease;
}

.lang-button:active {
  transform: scale(0.92);
  filter: brightness(1.1);
}

/* Cream-gold "文A" translate icon — same #ffe9a8 as the menu button's bars, so the top-bar pair
   reads as one family. (Replaces the 🌐 emoji: Online mode owns the globe, and emoji glyphs carry
   their own colours/disc that clashed with the emerald glass and vary per device.) */
.lang-icon {
  width: clamp(19px, 4.6vw, 25px);
  height: clamp(19px, 4.6vw, 25px);
  display: block;
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.4));
}

/* In-game copy: left of the round menu button, on the same top line — the SAME size
   (var(--top-ctrl)) and the SAME modern top formula as the in-match menu button below,
   so the pair stays even at every screen size (the old var(--top-y) placement predated
   the rev-197 compact top row and floated ~40px too low). */
.lang-button-ingame {
  position: absolute;
  top: var(--top-y);
  right: calc(var(--edge-r) + var(--top-ctrl) + var(--top-gap));
  width: var(--top-ctrl);
  height: var(--top-ctrl);
  z-index: 6;
}
.phone-stage[data-game-mode]:not(.call-docked):not(.is-home) .lang-button-ingame {
  top: calc(var(--top-ad-y) + var(--top-ad-h) + clamp(6px, 1.4vw, 10px)); /* mirrors the in-match .menu-button rule */
}
/* Call layout crowds the top row — the globe yields (language stays reachable via the menu). */
.phone-stage.call-docked .lang-button-ingame { display: none; }

/* No light-mode override for the language button — it keeps the SAME emerald
   glass as the menu button (.round-button) in BOTH themes, so the top-bar pair
   (globe + menu) always match, on the home screen and in-game alike. */

/* Home top-bar buttons (globe + menu) reuse the IN-GAME control look exactly —
   same green gradient + gold glow + cream-gold bars + shine animation (inherited
   from .lang-button / .round-button.menu-button). Only their PLACEMENT differs
   (they sit in the home top-bar grid, not the in-game corner). */
#homeLangButton {
  /* Match the menu button's size exactly so the top-right pair is even. */
  width: var(--top-ctrl);
  height: var(--top-ctrl);
}

/* The top-bar pairs (home AND in-game: 文A + menu) render 32px on small phones — below the ~44px
   touch minimum. Expand the TAP AREA invisibly (same pattern as the rank chip); visuals unchanged.
   ::before is free on all four (the menu shine uses ::after). CONTAINMENT: every one of these
   buttons must be positioned (home pair relative below; in-game pair absolute via .round-button /
   .lang-button-ingame) or the absolute ::before resolves against the stage and steals taps. */
#homeLangButton, .menu-button.home-menu { position: relative; }
/* Tap area. The old rule was a symmetric -7px inset, which was wrong in two ways that only measurement
   showed:
     - .menu-button carried overflow: hidden (for the shine), so ITS expansion was clipped away entirely —
       measured 32x32 against a 44px minimum, i.e. the button that most needed the expansion never had it.
     - 7px per side exceeds the real gaps between the top-bar controls (measured 5.4px to the rank chip and
       6.8px to the settings button at 375px), so neighbouring hit areas overlapped by ~7px and whichever
       painted last silently ate the other's edge taps.
   Vertical space is free — the nearest control above or below is >60px away — so the box is a full 44px
   tall. Horizontal space is NOT: six controls share a 375px row and there is no arrangement in which they
   all reach 44px wide. 2px per side is the most that fits inside the smallest measured gap, so that is what
   it takes. Reaching 44px WIDE needs a top-bar redesign (fewer controls, or a second row), not a bigger
   pseudo-element; see Docs/IOS_MAC_BRINGUP.md.
   Deliberately symmetric: an earlier attempt anchored each box away from its neighbour, which inverted
   under dir="rtl" (the row mirrors) and produced a 20px overlap in Arabic and Urdu. */
#homeLangButton::before, #gameLangButton::before, .menu-button::before {
  content: "";
  position: absolute;
  top: 50%;
  margin-top: -22px;
  height: 44px;
  left: -2px; right: -2px; /* inset longhands (Chromium<87 WebView support) */
  border-radius: 999px;
}

/* ===== Reusable rewarded-ad opt-in dialog (emerald + gold) ===== */
.rw-offer-overlay {
  position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 64; display: grid; place-items: center; /* inset longhands (Chromium<87) */
  background: rgba(2, 14, 11, 0.66);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  animation: lang-fade 160ms ease both;
}
.rw-offer-overlay[hidden] { display: none; }
.rw-offer {
  width: min(86vw, 330px); display: grid; justify-items: center; gap: 8px;
  padding: 22px 20px; border-radius: 18px; text-align: center;
  border: 1px solid rgba(189, 250, 235, 0.4);
  background:
    linear-gradient(150deg, rgba(255, 255, 255, 0.12), transparent 38%),
    linear-gradient(135deg, rgba(18, 86, 67, 0.98), rgba(6, 34, 27, 0.99));
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
  animation: lang-pop-in 200ms cubic-bezier(0.2, 1.2, 0.4, 1) both;
}
.rw-offer-emoji { font-size: 38px; line-height: 1; }
.rw-offer-title { font: 900 clamp(16px, 4.4vw, 20px)/1.15 var(--font-ui); color: #fff; }
.rw-offer-body { font: 700 13px/1.4 var(--font-ui); color: rgba(255, 255, 255, 0.78); }
.rw-offer-actions { display: grid; gap: 8px; width: 100%; margin-top: 6px; }
.rw-offer-watch {
  padding: 13px 16px; border-radius: 12px; border: 1px solid rgba(255, 216, 77, 0.5);
  background: linear-gradient(135deg, #ffe98a, #f4c24c); color: #3a2a05;
  font: 900 clamp(14px, 3.8vw, 16px)/1 var(--font-ui); cursor: pointer;
}
.rw-offer-watch:active { transform: scale(0.96); }
.rw-offer-no {
  padding: 10px; border-radius: 11px; border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.06); color: rgba(255, 255, 255, 0.8);
  font: 700 13px/1 var(--font-ui); cursor: pointer;
}

/* ===== Ranked leaderboard overlay (emerald + gold, matches the lang popup) ===== */
.lb-overlay {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 62;
  display: grid;
  place-items: center;
  background: rgba(2, 14, 11, 0.66);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  animation: lang-fade 160ms ease both;
}
.lb-overlay[hidden] { display: none; }
.lb-pop {
  width: min(92vw, 380px);
  max-height: 86vh;
  display: flex;
  flex-direction: column;
  border-radius: 18px;
  border: 1px solid rgba(189, 250, 235, 0.4);
  background:
    linear-gradient(150deg, rgba(255, 255, 255, 0.12), transparent 38%),
    linear-gradient(135deg, rgba(18, 86, 67, 0.98), rgba(6, 34, 27, 0.99));
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
  overflow: hidden;
  animation: lang-pop-in 200ms cubic-bezier(0.2, 1.2, 0.4, 1) both;
}
.lb-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px 10px;
}
.lb-title { font: 900 clamp(16px, 4.4vw, 20px)/1 var(--font-ui); color: #ffe487; letter-spacing: 0.02em; }
.lb-close {
  width: 32px; height: 32px; border-radius: 9px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  background: rgba(255, 255, 255, 0.08);
  color: #f3fffb; font-size: 15px; cursor: pointer;
  transition: transform 120ms ease;
}
.lb-close:active { transform: scale(0.92); }

/* WCAG 2.5.5 / Material minimum touch target: the dialog close buttons were 32-34px, below 44px. Bump
   every one to a 44px min tap target and flex-centre the × glyph (they use a line-height:1 font, so a
   size bump alone would not keep the glyph centred). Their compact look is preserved by the small font;
   only the hit area grows. Absolute-positioned corners so the larger box doesn't reflow dialog content. */
.country-flag-close,
.gender-close,
.mode-config-close,
.online-close,
.lb-close,
.fair-close {
  min-width: 44px;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.lb-me {
  margin: 0 14px 10px; padding: 12px 14px; border-radius: 14px;
  border: 1px solid rgba(255, 216, 77, 0.35);
  background: linear-gradient(135deg, rgba(255, 226, 130, 0.14), rgba(255, 255, 255, 0.04));
  display: flex; align-items: center; gap: 12px; min-height: 56px;
}
.lb-me-emoji { font-size: 26px; line-height: 1; flex: 0 0 auto; }
.lb-me-text { display: grid; gap: 2px; min-width: 0; }
.lb-me-name { font: 900 clamp(15px, 3.9vw, 18px)/1 var(--font-ui); color: #fff; }
.lb-me-sub { font: 700 12px/1.25 var(--font-ui); color: rgba(255, 255, 255, 0.72); }
.lb-me-rank { margin-left: auto; text-align: right; flex: 0 0 auto; }
.lb-me-pct { font: 900 clamp(16px, 4.6vw, 22px)/1 var(--font-ui); color: #ffe487; }
.lb-me-pct-label { font: 700 9px/1 var(--font-ui); color: rgba(255, 255, 255, 0.6); text-transform: uppercase; letter-spacing: 0.1em; margin-top: 3px; }
.lb-list-head {
  padding: 4px 18px 8px; font: 800 11px/1 var(--font-ui);
  letter-spacing: 0.16em; text-transform: uppercase; color: rgba(255, 226, 130, 0.9);
}
.lb-list { list-style: none; margin: 0; padding: 0 10px 12px; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.lb-row { display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: 11px; }
.lb-row:nth-child(odd) { background: rgba(255, 255, 255, 0.05); }
.lb-row.is-me { background: rgba(255, 216, 77, 0.16); border: 1px solid rgba(255, 216, 77, 0.4); }
.lb-rank { width: 26px; text-align: center; flex: 0 0 auto; font: 900 14px/1 var(--font-ui); color: rgba(255, 255, 255, 0.85); }
.lb-row:nth-child(1) .lb-rank, .lb-row:nth-child(2) .lb-rank, .lb-row:nth-child(3) .lb-rank { color: #ffe487; }
.lb-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 700 14px/1.2 var(--font-ui); color: #f3fffb; unicode-bidi: plaintext; }
.lb-tag { flex: 0 0 auto; margin: 0 6px 0 2px; font: 600 11px/1 var(--font-ui); color: rgba(255, 255, 255, 0.45); letter-spacing: 0.02em; }
.lb-tier { flex: 0 0 auto; padding: 3px 8px; border-radius: 999px; font: 800 10px/1 var(--font-ui); text-transform: uppercase; letter-spacing: 0.04em; color: #1a1205; }
.lb-report { flex: 0 0 auto; margin-left: 8px; padding: 2px 4px; background: none; border: 0; cursor: pointer; font-size: 13px; line-height: 1; color: rgba(255, 255, 255, 0.35); transition: color 0.15s; }
.lb-report:hover { color: rgba(255, 120, 120, 0.85); }
.lb-report:disabled { color: rgba(120, 220, 150, 0.9); cursor: default; }
.lb-tag-hint { margin: 8px 4px 0; text-align: center; font: 600 11px/1.3 var(--font-ui); color: rgba(255, 255, 255, 0.4); }
/* Friends 3-4p room: live who-joined roster */
.online-roster { display: grid; gap: 6px; margin: 10px auto 2px; width: 100%; max-width: 260px; }
.online-roster[hidden] { display: none; }
.roster-seat { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 10px; background: rgba(255, 255, 255, 0.07); font: 700 13px/1 var(--font-ui); color: #eaf4ef; }
.roster-seat.is-empty { opacity: 0.5; }
.roster-dot { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; background: #888; }
.roster-dot[data-seat="blue"] { background: #4a90e2; }
.roster-dot[data-seat="green"] { background: #57b894; }
.roster-dot[data-seat="red"] { background: #e0584f; }
.roster-dot[data-seat="yellow"] { background: #e8b84a; }
.roster-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Persistent end-of-match panel (Play Again / Home + rank change) */
.match-end-panel { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 60; display: flex; align-items: center; justify-content: center; padding: 24px; background: rgba(6, 10, 9, 0.45); } /* inset longhands (Chromium<87) */
.match-end-panel .me-card { width: min(86%, 320px); max-height: calc(100% - 48px); overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; background: linear-gradient(160deg, rgba(22, 32, 28, 0.98), rgba(12, 18, 16, 0.98)); border: 1px solid rgba(255, 255, 255, 0.14); border-radius: 20px; padding: 22px 20px; text-align: center; box-shadow: 0 18px 50px rgba(0, 0, 0, 0.5); animation: me-rise 0.32s ease; }
@keyframes me-rise { from { transform: translateY(16px); opacity: 0; } to { transform: none; opacity: 1; } }
.match-end-panel .me-emoji { font-size: 40px; line-height: 1; }
.match-end-panel .me-title { margin-top: 8px; font: 900 22px/1.12 var(--font-ui); color: #fff; }
.match-end-panel.is-lose .me-title { color: #cfe0ff; }
.match-end-panel .me-rank { margin-top: 8px; font: 800 14px/1.2 var(--font-ui); color: #ffd27a; }
/* Final standings (Full Finish): one row per seat in finishing order; the local
   player's row is gold-highlighted. */
.match-end-panel .me-standings { list-style: none; margin: 12px 0 2px; padding: 0; display: grid; gap: 6px; }
.match-end-panel .me-place { display: flex; align-items: center; gap: 10px; padding: 7px 12px; border-radius: 11px; background: rgba(255, 255, 255, 0.06); font: 700 14px/1 var(--font-ui); color: #eaf4ef; }
.match-end-panel .me-place.is-you { background: rgba(255, 210, 122, 0.18); border: 1px solid rgba(255, 210, 122, 0.5); color: #fff; }
.match-end-panel .me-place.is-out { opacity: 0.6; }
.match-end-panel .me-medal { flex: none; width: 24px; text-align: center; font-size: 16px; }
.match-end-panel .me-pname { flex: 1 1 auto; min-width: 0; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.match-end-panel .me-actions { margin-top: 18px; display: flex; gap: 10px; }
.match-end-panel .me-btn { flex: 1 1 0; padding: 12px 10px; border-radius: 12px; border: 0; font: 800 14px/1 var(--font-ui); cursor: pointer; }
.match-end-panel .me-secondary { background: rgba(255, 255, 255, 0.12); color: #eaf4ef; }
.match-end-panel .me-primary { background: linear-gradient(135deg, #ffe27a, #f4b400); color: #1a1205; }
.match-end-panel .me-danger { background: linear-gradient(135deg, #ff8a8a, #E11D26); color: #fff; }
.match-end-panel .me-body { margin-top: 8px; font: 600 13px/1.4 var(--font-ui); color: #cfe0ff; }
.match-end-panel .pro-benefits { margin: 12px 0 4px; padding: 0; list-style: none; text-align: left; display: grid; gap: 7px; }
.match-end-panel .pro-benefits li { position: relative; padding-left: 22px; font: 600 13px/1.35 var(--font-ui); color: #eaf4ef; }
.match-end-panel .pro-benefits li::before { content: "✓"; position: absolute; left: 0; color: #ffe27a; font-weight: 900; }
/* Subscription DISCLOSURE block on the Go Pro sheet (Apple 3.1.2(c) + Play's subscription rules): the period,
   the FULL recurring price as the most prominent price on the sheet, the auto-renewal statement, and reachable
   Terms / Privacy / Restore. Chromium-80 floor: physical longhands, flex gap only (84 — degrades to tighter
   spacing, never a break), no modern selector lists, no aspect-ratio. */
.match-end-panel .pro-period { margin-top: 2px; font: 700 12px/1.3 var(--font-ui); color: rgba(255, 255, 255, 0.72); letter-spacing: 0.2px; }
.match-end-panel .pro-price { margin-top: 4px; font: 900 26px/1.15 var(--font-ui); color: #ffe27a; }
.match-end-panel .pro-renew { margin: 10px 0 2px; font: 600 11px/1.45 var(--font-ui); color: rgba(255, 255, 255, 0.66); text-align: center; }
.match-end-panel .pro-links { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 8px; margin-top: 6px; color: rgba(255, 255, 255, 0.5); font: 600 11px/1.3 var(--font-ui); }
.match-end-panel .pro-link { min-height: 32px; padding: 4px 2px; background: none; border: 0; color: #bfe6ff; font: 700 11px/1.3 var(--font-ui); text-decoration: underline; cursor: pointer; }
.match-end-panel .pro-link:focus-visible { outline: 2px solid #ffe27a; outline-offset: 2px; border-radius: 4px; }
.lb-tier[data-tier="Bronze"] { background: linear-gradient(135deg, #d7a06a, #a86a36); color: #2a1808; }
.lb-tier[data-tier="Silver"] { background: linear-gradient(135deg, #e6edf3, #aab6c2); }
.lb-tier[data-tier="Gold"] { background: linear-gradient(135deg, #ffe27a, #f4b400); }
.lb-tier[data-tier="Platinum"] { background: linear-gradient(135deg, #a8f0e6, #5fc7c2); }
.lb-tier[data-tier="Crown"] { background: linear-gradient(135deg, #c9a3ff, #8b5cf6); color: #fff; }
.lb-tier[data-tier="Conqueror"] { background: linear-gradient(135deg, #ff8a8a, #E11D26); color: #fff; }
.lb-empty { padding: 22px 20px 26px; text-align: center; color: rgba(255, 255, 255, 0.78); font: 700 14px/1.45 var(--font-ui); }
.lb-empty[hidden] { display: none; }

.lang-overlay {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 60;
  display: grid;
  place-items: center;
  background: rgba(2, 14, 11, 0.62);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  animation: lang-fade 160ms ease both;
}

.lang-overlay[hidden] {
  display: none;
}

@keyframes lang-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

.lang-pop {
  width: min(86vw, 320px);
  display: grid;
  gap: 7px;
  padding: 14px;
  /* 25 languages overflow short viewports (split-screen, phone landscape, even
     tall portrait). Cap to the viewport and scroll the list inside so every
     language stays reachable; place-items:center keeps it centred. */
  max-height: min(calc(100 * var(--dvh) - clamp(24px, 8vh, 56px)), 560px);
  overflow-y: auto;
  overscroll-behavior: contain;
  border-radius: 18px;
  border: 1px solid rgba(189, 250, 235, 0.4);
  background:
    linear-gradient(150deg, rgba(255, 255, 255, 0.14), transparent 40%),
    linear-gradient(135deg, rgba(18, 86, 67, 0.97), rgba(6, 34, 27, 0.98));
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
  animation: lang-pop-in 200ms cubic-bezier(0.2, 1.2, 0.4, 1) both;
}

@keyframes lang-pop-in {
  from { opacity: 0; transform: translateY(10px) scale(0.96); }
  to { opacity: 1; transform: none; }
}

.lang-pop-title {
  font: 800 11px/1 var(--font-ui);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255, 226, 130, 0.92);
  padding: 2px 4px 6px;
}

.lang-pop-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 14px;
  border-radius: 11px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: rgba(255, 255, 255, 0.06);
  color: #f3fffb;
  font: 700 clamp(15px, 3.8vw, 18px)/1.1 var(--font-ui);
  cursor: pointer;
  text-align: left;
  transition: transform 130ms ease, background 130ms ease, border-color 130ms ease;
}

.lang-pop-item:active {
  transform: scale(0.98);
}

.lang-pop-item.is-active {
  background: linear-gradient(160deg, #1f9d57, #0c7a3f);
  border-color: rgba(255, 236, 103, 0.85);
}

.lang-pop-item.is-active::after {
  content: "\2713";
  font-weight: 900;
  color: #ffe9a8;
}

html[data-theme="light"] .lang-pop {
  border-color: rgba(20, 96, 70, 0.3);
  background:
    linear-gradient(150deg, rgba(255, 255, 255, 0.7), transparent 44%),
    linear-gradient(135deg, #d8fff0, #aef0d8);
}

html[data-theme="light"] .lang-pop-item {
  color: #123f2c;
  background: rgba(255, 255, 255, 0.6);
  border-color: rgba(20, 96, 70, 0.2);
}

html[data-theme="light"] .lang-pop-item.is-active {
  background: linear-gradient(160deg, #1f9d57, #0c7a3f);
  color: #fff;
}

/* ---- Hero ------------------------------------------------------------- */

.home-hero {
  position: relative;
  width: 100%;
  min-width: 0;
  display: grid;
  justify-items: center;
  gap: clamp(1px, calc(0.45 * var(--dvh)), 6px);
  padding-top: clamp(2px, calc(0.8 * var(--dvh)), 10px);
}

/* The rotating ray burst is retired with the animated background. */
.home-hero::before {
  content: none;
}

/* A soft, static, neutral halo so the logo lifts off whichever of the five
   backgrounds is chosen, without a coloured tint that would clash. */
.home-hero::after {
  content: "";
  position: absolute;
  top: 8%;
  left: 50%;
  width: min(74vw, 410px);
  height: 80%;
  transform: translateX(-50%);
  background: radial-gradient(ellipse at 50% 42%, rgba(255, 255, 255, 0.26), transparent 62%);
  pointer-events: none;
}

html[data-theme="dark"] .home-hero::after {
  background: radial-gradient(ellipse at 50% 42%, rgba(255, 255, 255, 0.16), transparent 62%);
}

.hero-sparkle {
  position: absolute;
  font-size: clamp(14px, 3.8vw, 21px);
  filter: drop-shadow(0 0 8px rgba(255, 224, 110, 0.8));
  pointer-events: none;
  animation: sparkle-twinkle 2.3s ease-in-out infinite;
}

.hero-sparkle-left {
  left: 11%;
  top: 32%;
  animation-delay: 0.5s;
}

.hero-sparkle-right {
  right: 10%;
  top: 58%;
}

.home-hero h1 {
  margin: 0;
  position: relative;
  display: flex;
  max-width: calc(100% - 4px);
  /* Roomy word spacing: "AI" is scaled 1.12 + padded, so its visual box bleeds
     ~2-3px into each side — a small flex gap left the 3 words nearly touching
     (1-2px). 0.28em keeps a clear, even gap on both sides after the bleed. */
  gap: 0.28em;
  align-items: baseline;
  justify-content: center;
  font: 900 clamp(31px, 9vw, 48px)/0.96 var(--font-display);
  filter: none;
  white-space: nowrap;
}

.home-hero h1 .title-word {
  display: inline-flex;
}

/* Clean, bold game wordmark: flat solid-colour faces with a black cartoon outline
   and a single soft ground shadow — no 3D extrude. Solid faces stay crisp at every
   size on low-DPI WebViews. */
.home-hero h1 .title-word i {
  font-style: normal;
  display: inline-block;
  background: none;
  /* Clean + bold: flat solid face with a black cartoon outline and a single
     soft ground shadow — no 3D extrude. */
  -webkit-text-stroke: 0.8px #000000;
  color: #ffd96b;
  text-shadow: 0 2px 5px rgba(0, 0, 0, 0.32);
}

.home-hero h1 .title-ai {
  padding: 0 0.08em;
  text-transform: uppercase;
  transform: scale(1.12) translateY(-2px);
}

/* Light (sunny) mode: the bright sky lowers the wordmark's contrast, so add a
   soft dark halo behind the whole title for crisp separation. Faces/extrudes
   stay identical — this only lifts it off the sky. */
html[data-theme="light"] .home-hero h1 {
  filter: drop-shadow(0 2px 7px rgba(0, 0, 0, 0.3));
}

/* "Ai" is the hero accent: vivid cyan face on a blue 3D extrude with a soft cyan
   glow — instantly reads as the AI brand and pops against the gold wordmark. */
.home-hero h1 .title-ai i {
  /* "AI" carries the AI accent gradient (blue -> violet -> pink) clipped to the
     glyphs, with the same black cartoon stroke + a soft glow. Chromium-80-safe. */
  -webkit-text-stroke: 1px #000000;
  background: linear-gradient(135deg, #4285f4 0%, #9b72cb 52%, #d96570 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-shadow: none;
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.4)) drop-shadow(0 0 11px rgba(140, 130, 240, 0.5));
}

/* Per-letter Ludo colours on "Real" + "Ludo" (1=red, 2=green, 3=blue, 4=yellow) —
   flat solid faces (no 3D extrude); the soft shadow + black stroke are inherited
   from .title-word i. "AI" is the accent above. */
.home-hero h1 .title-word:not(.title-ai) i:nth-child(1){ color:#E11D26; }
.home-hero h1 .title-word:not(.title-ai) i:nth-child(2){ color:#119B53; }
.home-hero h1 .title-word:not(.title-ai) i:nth-child(3){ color:#1565D8; }
.home-hero h1 .title-word:not(.title-ai) i:nth-child(4){ color:#FFC400; }

.home-dice-scene {
  --hero-piece: clamp(38px, 9.9vw, 54px);
  position: relative;
  isolation: isolate;
  /* 3 equal columns, each piece centered → spacing reads even regardless of
     each piece's width or the dice's 3D spin. */
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  align-items: end;
  justify-items: center;
  width: min(92%, calc(var(--hero-piece) * 5));
  margin: clamp(1px, calc(0.5 * var(--dvh)), 8px) auto 0;
  min-height: calc(var(--hero-piece) * 1.18);
  padding-bottom: calc(var(--hero-piece) * 0.1);
}

/* Soft shared floor shadow so the trio reads as grounded on one surface. */
.home-dice-scene::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: calc(var(--hero-piece) * 0.03);
  width: 82%;
  height: calc(var(--hero-piece) * 0.15);
  transform: translateX(-50%);
  background: radial-gradient(58% 100% at 50% 50%, rgba(0, 0, 0, 0.42), transparent 74%);
  filter: blur(2.5px);
  z-index: -1;
  pointer-events: none;
}

/* Left: the in-game token — ivory pin shell, blue glass head, blue base. */
.hero-token {
  position: relative;
  width: calc(var(--hero-piece) * 0.68);
  height: var(--hero-piece);
  filter: drop-shadow(0 4px 5px rgba(0, 0, 0, 0.38));
  animation: home-piece-bob 3s ease-in-out infinite;
}

.hero-token i,
.hero-robot i {
  position: absolute;
  font-style: normal;
}

.hero-token .ht-base {
  left: 2%;
  right: 2%;
  bottom: 0;
  height: 26%;
  border-radius: 50%;
  background: linear-gradient(180deg, #7cd0ff 0%, #2e8fd8 55%, #14528f 100%);
  border: 1px solid rgba(10, 40, 70, 0.55);
  box-shadow:
    inset 0 2px 2px rgba(255, 255, 255, 0.55),
    inset 0 -3px 5px rgba(0, 0, 0, 0.35);
}

.hero-token .ht-body {
  left: 18%;
  right: 18%;
  bottom: 10%;
  height: 62%;
  border-radius: 48% 48% 42% 42% / 86% 86% 20% 20%;
  background: linear-gradient(160deg, #ffffff 0%, #fffdf4 24%, #e7e3d7 56%, #b3b0a6 100%);
  border: 1px solid rgba(100, 106, 109, 0.6);
  box-shadow:
    inset 0 3px 4px rgba(255, 255, 255, 0.92),
    inset 2px 0 3px rgba(255, 255, 255, 0.45),
    inset -3px -5px 8px rgba(78, 72, 62, 0.5);
}

.hero-token .ht-head {
  left: 50%;
  top: 0;
  width: 56%;
  height: calc(var(--hero-piece) * 0.38);
  transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(circle at 32% 26%, #f2ffff 0 12%, #7cd0ff 34%, #2e8fd8 64%, #14528f 100%);
  border: max(1px, calc(var(--hero-piece) * 0.028)) solid rgba(22, 43, 49, 0.6);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.35);
}

.hero-token .ht-head::after {
  content: "";
  position: absolute;
  left: 18%;
  top: 13%;
  width: 32%;
  height: 20%;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.88);
  transform: rotate(-26deg);
}

/* Middle: the exact in-game 3D dice cube, slowly tumbling. */
.hero-dice {
  position: relative;
  --dice-size: calc(var(--hero-piece) * 0.9);
  width: var(--dice-size);
  height: var(--dice-size);
  perspective: calc(var(--dice-size) * 5);
  filter: drop-shadow(0 5px 6px rgba(0, 0, 0, 0.4));
}

.hero-dice .dice-cube {
  animation: hero-dice-spin 9s linear infinite;
}

/* Right: the Real AI robot — metallic shell, glowing visor eyes, waving arm. */
.hero-robot {
  position: relative;
  width: calc(var(--hero-piece) * 0.84);
  height: var(--hero-piece);
  filter: drop-shadow(0 4px 5px rgba(0, 0, 0, 0.38));
  transform-origin: 50% 92%;
  animation: robot-perform 13s ease-in-out infinite;
}

.hero-robot .hr-antenna {
  left: 50%;
  top: 2%;
  width: 6%;
  height: 12%;
  transform: translateX(-50%);
  border-radius: 2px;
  background: linear-gradient(180deg, #9fb8cc, #5d7488);
}

.hero-robot .hr-antenna::after {
  content: "";
  position: absolute;
  left: 50%;
  top: calc(var(--hero-piece) * -0.085);
  width: calc(var(--hero-piece) * 0.12);
  height: calc(var(--hero-piece) * 0.12);
  transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #d9fbff, #56e2ff 60%, #1583c9);
  box-shadow: 0 0 7px rgba(86, 226, 255, 0.85);
  animation: robot-pulse 2.2s ease-in-out infinite;
}

.hero-robot .hr-head {
  left: 8%;
  right: 8%;
  top: 13%;
  height: 37%;
  border-radius: 28%;
  background: linear-gradient(165deg, #ffffff 0%, #d2e2ef 36%, #9db4c6 68%, #6d889e 100%);
  border: 1px solid rgba(58, 80, 100, 0.65);
  box-shadow:
    inset 0 2px 2px rgba(255, 255, 255, 0.8),
    inset 0 -3px 5px rgba(40, 62, 84, 0.35);
  transform-origin: 50% 96%;
  animation: robot-head 13s ease-in-out infinite;
}

.hero-robot .hr-head::before {
  content: "";
  position: absolute;
  left: 10%;
  right: 10%;
  top: 24%;
  height: 50%;
  border-radius: 999px;
  background: linear-gradient(180deg, #0b1d2e, #14324c);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.6);
}

.hero-robot .hr-eye {
  top: 30%;
  width: 16%;
  height: 24%;
  border-radius: 50%;
  background: #56e2ff;
  box-shadow: 0 0 6px rgba(86, 226, 255, 0.95);
  transform-origin: 50% 50%;
  animation: robot-eye 13s ease-in-out infinite;
}

.hero-robot .hr-eye-left { left: 24%; }

.hero-robot .hr-eye-right { right: 24%; }

/* Mouth: glowing cyan lip on the visor that morphs smile -> open laugh -> frown. */
.hero-robot .hr-mouth {
  left: 50%;
  top: 62%;
  width: 34%;
  height: 11%;
  transform: translateX(-50%);
  border-radius: 24% 24% 70% 70% / 30% 30% 100% 100%;
  background: linear-gradient(180deg, #7eecff, #1bbdef);
  box-shadow: 0 0 5px rgba(86, 226, 255, 0.85);
  animation: robot-mouth 13s ease-in-out infinite;
}

/* Tear drops: invisible except during the crying beat. */
.hero-robot .hr-tear {
  top: 52%;
  width: 9%;
  height: 13%;
  border-radius: 50% 50% 50% 50% / 62% 62% 40% 40%;
  background: linear-gradient(180deg, #e6fdff, #56e2ff);
  box-shadow: 0 0 5px rgba(86, 226, 255, 0.9);
  opacity: 0;
  animation: robot-tear 13s ease-in-out infinite;
}

.hero-robot .hr-tear-left { left: 22%; }

.hero-robot .hr-tear-right {
  right: 22%;
  animation-name: robot-tear-r;
}

.hero-robot .hr-body {
  left: 16%;
  right: 16%;
  top: 52%;
  height: 37%;
  border-radius: 22% 22% 30% 30%;
  background: linear-gradient(165deg, #e8f3fc 0%, #b0c6d8 50%, #7e98b0 100%);
  border: 1px solid rgba(58, 80, 100, 0.6);
  box-shadow:
    inset 0 2px 2px rgba(255, 255, 255, 0.75),
    inset 0 -3px 5px rgba(40, 62, 84, 0.32);
}

.hero-robot .hr-core {
  left: 50%;
  top: 50%;
  width: 34%;
  height: 40%;
  margin: -20% 0 0 -17%;
  border-radius: 50%;
  background: radial-gradient(circle at 38% 32%, #b9f3ff, #38c7f5 55%, #0d6aa8);
  box-shadow:
    0 0 7px rgba(86, 226, 255, 0.75),
    inset 0 1px 2px rgba(255, 255, 255, 0.7);
  animation: robot-pulse 2.2s ease-in-out 0.5s infinite;
}

.hero-robot .hr-arm {
  top: 54%;
  width: 11%;
  height: 30%;
  border-radius: 999px;
  background: linear-gradient(180deg, #cfdfec, #8aa3ba);
  border: 1px solid rgba(58, 80, 100, 0.55);
  transform-origin: 50% 7%;
}

.hero-robot .hr-arm-left {
  left: 1%;
  transform: rotate(10deg);
  animation: robot-arm-l 13s ease-in-out infinite;
}

.hero-robot .hr-arm-right {
  right: 1%;
  transform: rotate(-10deg);
  animation: robot-arm-r 13s ease-in-out infinite;
}

/* Legs swing from the hip for marching, kicking and the cheer dance. */
.hero-robot .hr-leg {
  top: 84%;
  width: 16%;
  height: 16%;
  border-radius: 4px 4px 6px 6px;
  background: linear-gradient(180deg, #9fb8cc, #5d7488);
  border: 1px solid rgba(58, 80, 100, 0.5);
  transform-origin: 50% 0%;
}

.hero-robot .hr-leg-left {
  left: 28%;
  animation: robot-leg-l 13s ease-in-out infinite;
}

.hero-robot .hr-leg-right {
  right: 28%;
  animation: robot-leg-r 13s ease-in-out infinite;
}

@keyframes hero-dice-spin {
  from { transform: rotateX(-18deg) rotateY(0deg) rotateZ(7deg); }
  to { transform: rotateX(342deg) rotateY(360deg) rotateZ(7deg); }
}

@keyframes robot-pulse {
  0%, 100% { opacity: 0.65; }
  50% { opacity: 1; }
}

/*
 * The robot performs a 13s loop: idle bob -> two-hand greeting wave ->
 * belly laugh (head back, body jitter) -> cheer dance -> a little cry
 * (head droops, tears fall, hands wipe eyes) -> recover. Every part keeps
 * the same 13s duration so the limbs and face stay in sync.
 */
@keyframes robot-perform {
  0% { transform: translateY(0) rotate(0deg); }
  9% { transform: translateY(-4px) rotate(0deg); }
  18% { transform: translateY(0) rotate(0deg); }
  26% { transform: translateY(-6px) rotate(0deg); }
  34% { transform: translateY(-2px) rotate(0deg); }
  /* laugh jitter */
  37% { transform: translateY(-3px) rotate(-5deg); }
  40% { transform: translateY(-1px) rotate(4deg); }
  43% { transform: translateY(-3px) rotate(-4deg); }
  46% { transform: translateY(-1px) rotate(4deg); }
  49% { transform: translateY(-3px) rotate(-3deg); }
  52% { transform: translateY(0) rotate(0deg); }
  /* dance sway */
  57% { transform: translateY(-4px) rotate(6deg); }
  62% { transform: translateY(-4px) rotate(-6deg); }
  68% { transform: translateY(0) rotate(0deg); }
  /* cry slump */
  74% { transform: translateY(4px) rotate(0deg); }
  80% { transform: translateY(5px) rotate(-2deg); }
  86% { transform: translateY(3px) rotate(2deg); }
  /* recover */
  93% { transform: translateY(-3px) rotate(0deg); }
  100% { transform: translateY(0) rotate(0deg); }
}

@keyframes robot-head {
  0%, 34% { transform: rotate(0deg); }
  38% { transform: rotate(-7deg); }
  44% { transform: rotate(-4deg); }
  50% { transform: rotate(-7deg); }
  52% { transform: rotate(0deg); }
  57% { transform: rotate(5deg); }
  62% { transform: rotate(-5deg); }
  68% { transform: rotate(0deg); }
  74% { transform: rotate(7deg); }
  80% { transform: rotate(9deg); }
  86% { transform: rotate(4deg); }
  93%, 100% { transform: rotate(0deg); }
}

@keyframes robot-eye {
  0%, 28% { transform: scaleY(1); }
  30%, 32% { transform: scaleY(0.12); }       /* blink */
  34% { transform: scaleY(1); }
  38%, 50% { transform: scaleY(0.32); }        /* laughing ^_^ squint */
  52% { transform: scaleY(1); }
  60%, 61% { transform: scaleY(0.12); }        /* dance blink */
  62% { transform: scaleY(1); }
  70%, 85% { transform: scaleY(0.55); }        /* sad half-shut */
  88%, 100% { transform: scaleY(1); }
}

@keyframes robot-mouth {
  0%, 18% { border-radius: 24% 24% 70% 70% / 30% 30% 100% 100%; transform: translateX(-50%) scaleY(1); }
  22%, 34% { border-radius: 24% 24% 72% 72% / 28% 28% 100% 100%; transform: translateX(-50%) scaleY(1.1); }
  /* open laughing mouth */
  38%, 50% { border-radius: 46% 46% 54% 54% / 44% 44% 62% 62%; transform: translateX(-50%) scaleY(1.85) translateY(6%); }
  52% { border-radius: 24% 24% 70% 70% / 30% 30% 100% 100%; transform: translateX(-50%) scaleY(1); }
  57%, 67% { border-radius: 22% 22% 74% 74% / 26% 26% 100% 100%; transform: translateX(-50%) scaleY(1.15); }
  /* frown */
  72%, 86% { border-radius: 72% 72% 24% 24% / 100% 100% 30% 30%; transform: translateX(-50%) scaleY(0.78) translateY(26%); }
  93%, 100% { border-radius: 24% 24% 70% 70% / 30% 30% 100% 100%; transform: translateX(-50%) scaleY(1); }
}

@keyframes robot-tear {
  0%, 67% { opacity: 0; transform: translateY(0) scale(0.5); }
  70% { opacity: 1; transform: translateY(0) scale(1); }
  79% { opacity: 1; transform: translateY(190%) scale(1); }
  84% { opacity: 0; transform: translateY(330%) scale(0.85); }
  86%, 100% { opacity: 0; transform: translateY(0) scale(0.5); }
}

@keyframes robot-tear-r {
  0%, 70% { opacity: 0; transform: translateY(0) scale(0.5); }
  73% { opacity: 1; transform: translateY(0) scale(1); }
  82% { opacity: 1; transform: translateY(190%) scale(1); }
  86% { opacity: 0; transform: translateY(330%) scale(0.85); }
  88%, 100% { opacity: 0; transform: translateY(0) scale(0.5); }
}

@keyframes robot-arm-l {
  0% { transform: rotate(10deg); }
  9% { transform: rotate(-2deg); }
  18% { transform: rotate(10deg); }
  /* two-hand greeting wave */
  23% { transform: rotate(-135deg); }
  27% { transform: rotate(-152deg); }
  31% { transform: rotate(-135deg); }
  34% { transform: rotate(-152deg); }
  /* laugh: arms up, jiggle */
  38% { transform: rotate(-70deg); }
  43% { transform: rotate(-86deg); }
  48% { transform: rotate(-70deg); }
  52% { transform: rotate(-60deg); }
  /* dance pump: left leads up */
  57% { transform: rotate(-150deg); }
  62% { transform: rotate(-28deg); }
  67% { transform: rotate(-150deg); }
  /* cry: droop then wipe eye */
  72% { transform: rotate(18deg); }
  79% { transform: rotate(-102deg); }
  83% { transform: rotate(-92deg); }
  92% { transform: rotate(-6deg); }
  100% { transform: rotate(10deg); }
}

@keyframes robot-arm-r {
  0% { transform: rotate(-10deg); }
  9% { transform: rotate(2deg); }
  18% { transform: rotate(-10deg); }
  /* two-hand greeting wave (mirror) */
  23% { transform: rotate(135deg); }
  27% { transform: rotate(152deg); }
  31% { transform: rotate(135deg); }
  34% { transform: rotate(152deg); }
  /* laugh */
  38% { transform: rotate(70deg); }
  43% { transform: rotate(86deg); }
  48% { transform: rotate(70deg); }
  52% { transform: rotate(60deg); }
  /* dance pump: opposite phase to left */
  57% { transform: rotate(28deg); }
  62% { transform: rotate(150deg); }
  67% { transform: rotate(28deg); }
  /* cry: wipe the other eye */
  72% { transform: rotate(-18deg); }
  79% { transform: rotate(102deg); }
  83% { transform: rotate(92deg); }
  92% { transform: rotate(6deg); }
  100% { transform: rotate(-10deg); }
}

@keyframes robot-leg-l {
  0% { transform: rotate(0deg); }
  9% { transform: rotate(-12deg); }
  18% { transform: rotate(0deg); }
  24% { transform: rotate(-10deg); }
  30% { transform: rotate(6deg); }
  34% { transform: rotate(0deg); }
  39% { transform: rotate(-12deg); }
  45% { transform: rotate(8deg); }
  50% { transform: rotate(-8deg); }
  52% { transform: rotate(0deg); }
  /* dance: left big kick */
  58% { transform: rotate(-34deg); }
  64% { transform: rotate(4deg); }
  68% { transform: rotate(0deg); }
  78% { transform: rotate(3deg); }
  86% { transform: rotate(1deg); }
  100% { transform: rotate(0deg); }
}

@keyframes robot-leg-r {
  0% { transform: rotate(0deg); }
  9% { transform: rotate(12deg); }
  18% { transform: rotate(0deg); }
  24% { transform: rotate(10deg); }
  30% { transform: rotate(-6deg); }
  34% { transform: rotate(0deg); }
  39% { transform: rotate(12deg); }
  45% { transform: rotate(-8deg); }
  50% { transform: rotate(8deg); }
  52% { transform: rotate(0deg); }
  /* dance: right big kick on the opposite beat */
  55% { transform: rotate(4deg); }
  61% { transform: rotate(34deg); }
  67% { transform: rotate(2deg); }
  68% { transform: rotate(0deg); }
  78% { transform: rotate(-3deg); }
  86% { transform: rotate(-1deg); }
  100% { transform: rotate(0deg); }
}

/* ---- Start dock -------------------------------------------------------- */

.home-start-panel {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
  padding: clamp(10px, calc(1.8 * var(--dvh)), 16px);
}

.home-start-panel .dock-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
}

.selected-mode-copy {
  min-width: 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 10px;
  align-items: center;
}

.dock-mode-icon {
  width: clamp(34px, 8vw, 44px);
  height: clamp(34px, 8vw, 44px);
}

.dock-mode-icon.mode-icon-computer { --accent: #3fd980; }
.dock-mode-icon.mode-icon-pass { --accent: #ffbc4d; }
.dock-mode-icon.mode-icon-ai { --accent: #6ee7ff; }
.dock-mode-icon.mode-icon-world { --accent: #62c4ff; }
.dock-mode-icon.mode-icon-friends { --accent: #c98bff; }

.dock-mode-text {
  display: grid;
  gap: 3px;
  min-width: 0;
}

/* The status line (#selectedModeStatus, a <small> in .dock-mode-text) toggles
   its [hidden] attribute from JS. There is no global [hidden] reset, so keep an
   explicit safety net targeting the REAL element — the old .selected-mode-copy
   selectors pointed at a container that no longer exists in the markup. */
.dock-mode-text small[hidden] {
  display: none;
}

.home-action-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 9px;
}

.quick-play-button {
  min-height: clamp(46px, calc(6.2 * var(--dvh)), 64px);
  padding: 0 clamp(10px, 2.6vw, 16px);
  border-radius: var(--r-pill);
  border: 1px solid rgba(247, 231, 182, 0.42);
  background: var(--surface-2);
  color: var(--gold-1);
  font: 900 clamp(12px, 3.1vw, 15px)/1.1 var(--font-ui);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.4);
  box-shadow: var(--e1);
  transition: transform 130ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.quick-play-button:active {
  transform: scale(0.94);
}

html[data-theme="light"] .quick-play-button {
  background:
    linear-gradient(145deg, rgba(255, 255, 255, 0.5), transparent 50%),
    linear-gradient(160deg, rgba(255, 240, 188, 0.95), rgba(255, 214, 110, 0.95));
  color: #6d4a02;
  border-color: rgba(186, 132, 14, 0.55);
}

.start-mode-button {
  position: relative;
  min-height: clamp(46px, calc(6.2 * var(--dvh)), 64px);
  padding: 0 16px;
  border-radius: var(--r-pill);
  border: 1px solid #6e4a18;
  background: linear-gradient(145deg, #fff6d8 0%, var(--gold-1) 18%, var(--gold-2) 52%, var(--gold-3) 100%);
  color: var(--gold-ink);
  font: 900 clamp(16px, 4vw, 22px)/1 var(--font-ui);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  box-shadow: var(--inset-top), var(--e3), 0 6px 20px rgba(232, 199, 107, 0.34), 0 0 26px rgba(255, 176, 72, 0.18);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);
  overflow: hidden;
}

.start-mode-button::after {
  content: "";
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  background: linear-gradient(105deg, transparent 0 28%, rgba(255, 255, 255, 0.3) 46%, transparent 64%);
  transform: translateX(-120%);
  animation: cta-shine 5s ease-in-out infinite;
  pointer-events: none;
}

.start-mode-button:not(:disabled) {
  animation: none;
}

.start-mode-button:disabled {
  background: linear-gradient(180deg, #cbd2da, #8d98a3);
  color: #23313c;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.38),
    0 6px 12px rgba(0, 0, 0, 0.22);
}

/* ---- Lobby motion & power guards --------------------------------------- */

@keyframes home-rise {
  from {
    opacity: 0;
    transform: translate3d(0, 26px, 0) scale(0.97);
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1);
  }
}

@keyframes avatar-pop {
  0% { transform: scale(0.6) rotate(-14deg); }
  62% { transform: scale(1.14) rotate(5deg); }
  100% { transform: scale(1) rotate(0deg); }
}

@keyframes ai-glow {
  0%, 100% { filter: drop-shadow(0 0 4px rgba(86, 226, 255, 0.35)); }
  50% { filter: drop-shadow(0 0 13px rgba(86, 226, 255, 0.75)); }
}

@keyframes featured-sheen {
  0%, 56% { transform: translateX(-130%); }
  78%, 100% { transform: translateX(130%); }
}

@keyframes hero-rays {
  from { transform: translate(-50%, -56%) rotate(0deg); }
  to { transform: translate(-50%, -56%) rotate(360deg); }
}

@keyframes sparkle-twinkle {
  0%, 100% {
    transform: scale(0.5) rotate(-10deg);
    opacity: 0.3;
  }
  50% {
    transform: scale(1.15) rotate(12deg);
    opacity: 1;
  }
}

@keyframes featured-icon-breathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.08); }
}

.phone-stage.reduced-motion .home-screen *,
.phone-stage.reduced-motion .home-screen *::before,
.phone-stage.reduced-motion .home-screen *::after {
  animation: none !important;
}

.phone-stage.is-power-idle .home-screen *,
.phone-stage.is-power-idle .home-screen *::before,
.phone-stage.is-power-idle .home-screen *::after {
  animation-play-state: paused !important;
}

/* ...except the signature hero mascots (roaming token, AI bot, spinning dice below the
   title) — they are the home's living centrepiece, like the roaming board tokens, so
   they keep animating even when power-idle. The rest of the home chrome still pauses,
   so most of the idle battery saving remains. (Reduce-motion still wins via animation:
   none, which leaves nothing for animation-play-state to run.) */
.phone-stage.is-power-idle .home-dice-scene,
.phone-stage.is-power-idle .home-dice-scene *,
.phone-stage.is-power-idle .home-dice-scene *::before,
.phone-stage.is-power-idle .home-dice-scene *::after {
  animation-play-state: running !important;
}

.spark {
  position: absolute;
  width: clamp(7px, 1.3vw, 12px);
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle, #fff9a8 0 24%, #ffd13b 25% 54%, transparent 56%);
  filter: drop-shadow(0 0 8px rgba(255, 220, 74, 0.8));
  animation: spark-pop 640ms ease-out forwards;
}

.celebration-layer {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 13;
  pointer-events: none;
  overflow: hidden;
  display: grid;
  place-items: center;
  isolation: isolate;
}

.celebration-glow,
.celebration-burst {
  position: absolute;
  left: 50%;
  top: 46%;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.celebration-glow {
  width: min(88%, 720px);
  aspect-ratio: 1;
  background:
    radial-gradient(circle, rgba(255, 238, 99, 0.5) 0 14%, rgba(255, 201, 51, 0.22) 15% 34%, transparent 62%);
  filter: blur(3px);
  animation: celebration-glow 1.45s ease-out both;
}

.celebration-burst {
  width: min(56%, 430px);
  aspect-ratio: 1;
  border: 4px solid rgba(255, 238, 105, 0.78);
  box-shadow:
    0 0 0 12px rgba(255, 232, 90, 0.16),
    0 0 44px rgba(255, 219, 77, 0.5),
    inset 0 0 38px rgba(255, 255, 255, 0.18);
  animation: celebration-ring 1.05s cubic-bezier(0.16, 0.9, 0.24, 1) both;
}

.celebration-title,
.celebration-subtitle {
  position: absolute;
  left: 50%;
  z-index: 3;
  width: min(84%, 720px);
  text-align: center;
  transform: translateX(-50%);
  text-transform: uppercase;
  letter-spacing: 0;
}

.celebration-title {
  top: 38%;
  color: #ffe75b;
  font: 900 clamp(52px, 14vw, 132px)/0.95 var(--font-display);
  text-shadow:
    0 7px 0 #4f2c00,
    4px 0 0 #4f2c00,
    -4px 0 0 #4f2c00,
    0 -4px 0 #4f2c00,
    0 0 24px rgba(255, 244, 135, 0.86),
    0 14px 30px rgba(0, 0, 0, 0.46);
  animation: celebration-title 1.18s cubic-bezier(0.16, 0.9, 0.24, 1.18) both;
}

.celebration-subtitle {
  top: calc(38% + clamp(66px, 14vw, 142px));
  color: #ffffff;
  font: 900 clamp(17px, 4.2vw, 34px)/1.05 var(--font-ui);
  text-shadow: 0 3px 0 rgba(0, 0, 0, 0.52), 0 0 14px rgba(255, 232, 99, 0.6);
  animation: celebration-subtitle 1.1s cubic-bezier(0.16, 0.9, 0.24, 1) 180ms both;
}

.celebration-dim {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 0;
  background: radial-gradient(circle at 50% 42%, rgba(6, 14, 11, 0) 30%, rgba(3, 9, 7, 0.5) 82%);
  animation: celebration-dim-in 680ms ease-out both;
}

.celebration-trophy {
  position: absolute;
  left: 50%;
  top: 17%;
  z-index: 3;
  font-size: clamp(64px, 19vw, 150px);
  line-height: 1;
  transform: translateX(-50%);
  filter:
    drop-shadow(0 10px 22px rgba(0, 0, 0, 0.42))
    drop-shadow(0 0 26px rgba(255, 214, 64, 0.55));
  animation:
    celebration-trophy-pop 860ms cubic-bezier(0.18, 1.4, 0.4, 1) 120ms both,
    celebration-trophy-float 2.4s ease-in-out 1.1s infinite;
}

.celebration-layer.is-winner .celebration-title {
  top: 42%;
}

.celebration-layer.is-winner .celebration-subtitle {
  top: calc(42% + clamp(66px, 14vw, 142px));
}

.celebration-firework {
  position: absolute;
  z-index: 2;
  width: 0;
  height: 0;
  pointer-events: none;
}

.celebration-flash {
  position: absolute;
  left: 0;
  top: 0;
  width: clamp(46px, 9vw, 86px);
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle, currentColor 0 18%, transparent 62%);
  filter: drop-shadow(0 0 14px currentColor);
  animation: firework-flash 520ms ease-out forwards;
}

.celebration-fspark {
  position: absolute;
  left: 0;
  top: 0;
  width: clamp(5px, 1.1vw, 9px);
  aspect-ratio: 1;
  border-radius: 50%;
  box-shadow: 0 0 8px currentColor;
  animation: firework-spark var(--fd, 760ms) cubic-bezier(0.12, 0.8, 0.32, 1) forwards;
}

.celebration-piece {
  position: absolute;
  z-index: 2;
  width: clamp(8px, 2vw, 18px);
  height: clamp(12px, 3vw, 28px);
  border-radius: 3px;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.28);
  animation: celebration-fall var(--duration) linear var(--delay) forwards;
  transform: translate3d(0, 0, 0) rotate(0deg);
}

.celebration-piece.piece-1 {
  border-radius: 50%;
}

.celebration-piece.piece-2 {
  width: clamp(14px, 3vw, 30px);
  height: clamp(6px, 1.6vw, 14px);
  border-radius: 999px;
}

.celebration-piece.piece-3 {
  clip-path: polygon(50% 0, 62% 34%, 99% 34%, 69% 55%, 82% 100%, 50% 73%, 18% 100%, 31% 55%, 1% 34%, 38% 34%);
}

.celebration-layer.is-loser .celebration-title {
  color: #b7d1ff;
  text-shadow:
    0 6px 0 #14213d,
    4px 0 0 #14213d,
    -4px 0 0 #14213d,
    0 -4px 0 #14213d,
    0 12px 24px rgba(0, 0, 0, 0.48);
}

.celebration-layer.is-loser .celebration-glow {
  background: radial-gradient(circle, rgba(136, 178, 255, 0.32), rgba(66, 83, 122, 0.14) 35%, transparent 62%);
}

.celebration-layer.is-loser .celebration-burst {
  border-color: rgba(158, 190, 255, 0.46);
  box-shadow: 0 0 30px rgba(120, 160, 255, 0.22), inset 0 0 28px rgba(255, 255, 255, 0.1);
}

.dice-impact-ring {
  position: absolute;
  width: clamp(72px, 12vw, 118px);
  aspect-ratio: 1;
  border-radius: 50%;
  border: 3px solid rgba(255, 238, 96, 0.78);
  background: radial-gradient(circle, rgba(255, 245, 135, 0.24), rgba(255, 245, 135, 0.06) 44%, transparent 68%);
  transform: translate(-50%, -50%) scale(0.42);
  filter: drop-shadow(0 0 12px rgba(255, 226, 69, 0.52));
  animation: dice-impact-ring 620ms cubic-bezier(0, 0, 0.2, 1) forwards;
}

/* Reserve the top strip ONLY when an ad has actually loaded during gameplay (set from the native AdMob
   load callback via syncBannerVisibility). The height is the ACTUAL native (anchored-adaptive) banner height
   pushed by native (--ad-banner-h, dp); 50px is only a pre-push fallback. No-fill/load-failure keeps it 0 ->
   no blank gap. There is NO web ad element — the native AdView (outside the WebView) is the only banner. */
.phone-stage.banner-filled {
  --top-ad-h: var(--ad-banner-h, 50px);
}

/* Small premium "Remove Ads" pill: a compact gold lozenge (crown + label)
   with a soft glow-pulse and a periodic shine sweep to invite the tap. It is
   centred in the top bar between the back button (left) and the globe + menu
   (right) regardless of screen width. */
.no-ads-badge {
  position: absolute;
  top: var(--top-y);
  left: calc(var(--edge-l) + var(--top-ctrl) + var(--top-gap));
  right: calc(var(--edge-r) + 2 * var(--top-ctrl) + 2 * var(--top-gap));
  z-index: 6;
  height: var(--top-ctrl);
  width: fit-content;
  max-width: 100%;
  margin-left: auto; margin-right: auto; /* physical (margin-inline is Chromium 87+) */
  display: flex;
  align-items: center;
  gap: clamp(5px, 1.3vw, 8px);
  padding: clamp(6px, 1.3vw, 9px) clamp(13px, 3vw, 18px);
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.6);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.55), transparent 42%),
    linear-gradient(135deg, #ffe480 0%, #ffc12e 50%, #f7991a 100%);
  color: #3a2300;
  transform: none;
  overflow: hidden;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.8),
    inset 0 -2px 5px rgba(150, 84, 0, 0.32),
    0 5px 14px rgba(0, 0, 0, 0.32),
    0 0 16px rgba(255, 193, 46, 0.5);
  cursor: pointer;
  animation:
    cta-enter-compact 720ms cubic-bezier(0.16, 0.9, 0.24, 1.12) 320ms both,
    premium-glow 2.8s ease-in-out 1.4s infinite;
  transition: transform 170ms ease, filter 170ms ease, box-shadow 170ms ease;
}

.no-ads-badge::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -40%;
  width: 38%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.85), transparent);
  transform: translateX(0) skewX(-18deg);
  animation: premium-shine 4.6s ease-in-out 2.4s infinite;
  pointer-events: none;
}

@keyframes premium-glow {
  0%, 100% {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.8),
      inset 0 -2px 5px rgba(150, 84, 0, 0.32),
      0 5px 14px rgba(0, 0, 0, 0.32),
      0 0 14px rgba(255, 193, 46, 0.42);
    filter: brightness(1);
  }
  50% {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.85),
      inset 0 -2px 5px rgba(150, 84, 0, 0.32),
      0 6px 18px rgba(0, 0, 0, 0.34),
      0 0 24px rgba(255, 208, 74, 0.82);
    filter: brightness(1.05);
  }
}

@keyframes premium-shine {
  0%, 55% { transform: translateX(0) skewX(-18deg); }
  80%, 100% { transform: translateX(440%) skewX(-18deg); }
}

.no-ads-badge:active {
  transform: scale(0.96);
  filter: brightness(1.12);
}

.no-ads-badge.is-active {
  background:
    linear-gradient(115deg, rgba(255, 255, 255, 0.18), transparent 35%),
    linear-gradient(135deg, #0b7f58 0%, #1b8a5c 45%, #153b64 100%);
  border-color: rgba(123, 239, 168, 0.78);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.28),
    0 8px 22px rgba(0, 0, 0, 0.34),
    0 0 24px rgba(83, 224, 139, 0.34);
}

.phone-stage.turn-blue .no-ads-badge,
.phone-stage.turn-green .no-ads-badge {
  filter: saturate(0.92) brightness(0.94);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    inset 0 -2px 0 rgba(0, 0, 0, 0.2),
    0 0 0 1px rgba(26, 66, 135, 0.42),
    0 8px 18px rgba(0, 0, 0, 0.32),
    0 0 14px rgba(255, 211, 75, 0.18);
  animation: none;
}

.phone-stage.turn-blue .no-ads-badge::after,
.phone-stage.turn-green .no-ads-badge::after {
  opacity: 0.36;
  animation-duration: 5.8s;
}

.no-ads-icon {
  position: relative;
  z-index: 1;
  display: block;
  font-size: clamp(15px, 3.7vw, 19px);
  line-height: 1;
  filter: drop-shadow(0 1px 1px rgba(120, 70, 0, 0.55));
}

.no-ads-icon::before {
  content: none;
}

.no-ads-copy {
  position: relative;
  z-index: 1;
  min-width: 0;
  display: grid;
  gap: 1px;
  text-align: left;
}

.no-ads-kicker,
.no-ads-action {
  font-family: var(--font-ui);
  letter-spacing: 0;
  text-transform: uppercase;
  /* Compact premium pill shows just the crown + label; the kicker and the
     separate Subscribe button are folded away (the whole pill subscribes). */
  display: none;
}

.no-ads-kicker {
  color: #c8fff0;
  font-size: clamp(9px, 1.7vw, 13px);
  font-weight: 900;
  line-height: 1;
}

.no-ads-title {
  position: relative;
  z-index: 1;
  color: #3a2300;
  font-size: clamp(13px, 3.3vw, 16px);
  font-weight: 900;
  line-height: 1;
  white-space: nowrap;
  letter-spacing: 0.01em;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.45);
}

.no-ads-action {
  position: relative;
  z-index: 1;
  min-width: clamp(72px, 16vw, 108px);
  padding: clamp(7px, 1.5vw, 10px) clamp(8px, 1.7vw, 13px);
  border-radius: 8px;
  background: linear-gradient(180deg, #fff178, #ffbd2e 54%, #f07518);
  color: #14233d;
  font-size: clamp(10px, 2vw, 14px);
  font-weight: 900;
  line-height: 1;
  text-align: center;
  text-shadow: none;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.7),
    0 3px 7px rgba(0, 0, 0, 0.28);
}

/* Chromium 80–87 (a documented WebView floor) lacks `aspect-ratio` (Chrome 88+).
   The squares below derive their HEIGHT only from aspect-ratio, so on those
   engines give an explicit height fallback — otherwise the board distorts and the
   token tap-targets collapse to height 0 (un-tappable, match unplayable). Modern
   engines skip this @supports block and keep using aspect-ratio (no conflict). */
@supports not (aspect-ratio: 1 / 1) {
  .board-frame { height: var(--board-size); }
  .token-hit { height: max(44px, 9.6%); }
  .round-button { height: var(--top-ctrl); }
  /* Dice pips + resting face: all square boxes inside square parents, so the
     width percentage doubles as the height (cube pips sit in a 3x3 grid of a
     square face; flat-face pips are absolute inside the square face). Without
     these the dice render as blank pipless slabs on aspect-ratio-less engines. */
  .cube-dot { height: 52%; }
  .dice-face { height: 62%; }
  .dice-face i { height: 16%; }
  /* ---- Chromium 80-87 WebView sweep (the Vivo "invisible dice" report) ----
     aspect-ratio shipped in Chromium 88; on older engines every square below
     otherwise collapses to height 0 (or width auto) and disappears. */
  /* Home screen: the living-board is width:100% + aspect-ratio square; all its
     children (.home-board-layer, .board-mode) are absolute, so the classic
     padding-bottom square hack is safe. (The tablet/landscape media variants
     are left as-is: the 80-87 fleet is budget PORTRAIT phones.) */
  .home-board { height: 0; padding-bottom: 100%; }
  /* Corner roll-die: the base rule drives width from height via aspect-ratio;
     without it width:auto goes rectangular. The button box is already square
     (--corner-dice-size), so 90%/90% restores the square die. */
  .board-frame .dice-button .dice-face,
  .seat-pod .dice-button .dice-face { width: 90% !important; height: 90% !important; }
  /* Roll-feedback + celebration squares: mirror each rule's width as height. */
  .dice-impact-ring { height: clamp(72px, 12vw, 118px); }
  .spark { height: clamp(7px, 1.3vw, 12px); }
  .celebration-glow { height: min(88%, 720px); }
  .celebration-burst { height: min(56%, 430px); }
  .celebration-flash { height: clamp(46px, 9vw, 86px); }
  .celebration-fspark { height: clamp(5px, 1.1vw, 9px); }
  .status-chip::before { height: clamp(8px, 1.4vw, 12px); }
}

/* Tablets / large screens: the board grows but the phone-px control ceilings froze
   (the vw terms saturate well below tablet widths), leaving tiny controls beside a
   big board. Raise the ceilings at >=600px so the chrome stays proportional. */
@media (min-width: 600px) {
  :root {
    --top-ctrl: clamp(38px, 5.4vw, 52px);
    --seat-avatar-size: clamp(52px, 8vw, 72px);
    /* Dice on tablets: floor 58 -> 62 (continuous with the phone clamp's 62 max at the
       600px boundary, no shrink at the breakpoint) and scaled up a touch. Tablets are
       HEIGHT-bound so a bigger dice trades against the board (~2px board per dice-px);
       verified no-overflow with comfortable margins (e.g. 768x1024: 75px dice -> board ~664,
       still 87% of full). Kept modest so the board stays the hero on big screens. */
    --corner-dice-size: clamp(62px, 9.8vw, 82px);
    /* Portrait tablets: the square board is HEIGHT-bound and otherwise fills the vertical
       budget so tightly the bottom pods kiss the screen edge. Reserve a little breathing
       room so the board can centre with margins. Phones (<600px) are width-bound and use
       the 0px fallback in board-area, so they're untouched. */
    --board-breathing: clamp(28px, calc(3.6 * var(--dvh)), 52px);
  }
}

.board-frame {
  position: absolute;
  top: var(--board-top-space);
  left: 50%;
  width: var(--board-size);
  max-width: calc(100% - 4px);
  aspect-ratio: 1;
  box-sizing: border-box; /* in-game turn-frame border sits INSIDE this box → the canvas (board + tokens) insets, so the frame never overlaps tokens on the outer track */
  transform: translateX(-50%);
  z-index: 3;
  /* box-shadow, NOT filter: drop-shadow.
     This element wraps the LIVE board canvas, which repaints on every token slide and every dice throw. A
     `filter` forces the whole subtree back through the GPU filter pass on each of those repaints — two blur
     passes, one of them 26px, over the full board — during exactly the frames the player is watching.
     box-shadow is painted with the element's own background/border and is NOT re-run when a child canvas
     repaints. The shape is a rounded rect with a solid background, so the two render equivalently here;
     the glow blur below is widened because the old filter blurred an already-glowing element and the
     compounding is what produced the bloom. z-index above still establishes the stacking context that keeps
     ::before (the contact shadow, z-index: -1) behind the frame. */
  box-shadow: 0 18px 26px rgba(0, 0, 0, 0.55), 0 2px 4px rgba(0, 0, 0, 0.4);
  animation: board-rise 720ms cubic-bezier(0.16, 0.9, 0.24, 1.06) 140ms both;
}

/* Grounded elliptical contact shadow — the single richest "premium" tell. */
.board-frame::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -3%;
  width: 86%;
  height: 14%;
  transform: translateX(-50%);
  z-index: -1;
  background: radial-gradient(60% 70% at 50% 50%, rgba(0, 0, 0, 0.5), transparent 72%);
  pointer-events: none;
}

/* The board's outer frame tints to WHOSE TURN it is (--turn-rgb = the active seat's colour
   channels, set per turn in renderSeatPods) — a big, glanceable "your turn" cue. It's a REAL
   border on .board-frame (box-sizing:border-box above), so the canvas — board cells AND tokens —
   insets INSIDE it: the frame is OUTSIDE the playable area and never overlaps a token on the
   outer track. Rounded so the outer corner is concentric with the canvas's own 10px corners
   (outer radius = border width + 10). The matching background fills any sub-pixel gap between the
   border and the canvas so it never shows a dark line. Soft outer glow halo. In-game only. */
.phone-stage:not(.is-home) .board-frame {
  border: var(--board-frame-border) solid rgb(var(--turn-rgb-vivid, 174, 83, 255));
  border-radius: calc(var(--board-frame-border) + 10px);
  background: rgb(var(--turn-rgb-vivid, 174, 83, 255));
  /* Glow + depth in ONE box-shadow list, since this rule overrides the base. The third, widest glow pass
     replaces the bloom the old drop-shadow filter produced by blurring the glow itself. */
  box-shadow:
    0 0 clamp(8px, 2.2vw, 14px) rgba(var(--turn-rgb-vivid, 174, 83, 255), 0.95),
    0 0 clamp(20px, 5vw, 34px) rgba(var(--turn-rgb-vivid, 174, 83, 255), 0.5),
    0 0 clamp(32px, 8vw, 54px) rgba(var(--turn-rgb-vivid, 174, 83, 255), 0.22),
    0 18px 26px rgba(0, 0, 0, 0.55),
    0 2px 4px rgba(0, 0, 0, 0.4);
  transition: border-color 280ms ease, box-shadow 280ms ease, background-color 280ms ease;
}

.board-frame.dice-impact {
  animation: board-hit 360ms cubic-bezier(0.22, 1, 0.36, 1);
}

.board-ad-slot {
  position: absolute;
  z-index: 2;
  width: calc(3.95 / 15 * 100%);
  height: calc(3.95 / 15 * 100%);
  display: grid;
  place-items: center;
  padding: clamp(4px, 0.85vw, 10px);
  overflow: hidden;
  border-radius: clamp(8px, 1.4vw, 14px);
  background: rgba(8, 10, 18, 0.95);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.08),
    0 3px 10px rgba(0, 0, 0, 0.28);
  opacity: 1;
  transform: scale(1);
  transition: opacity 180ms ease, transform 180ms ease, filter 180ms ease;
}

.board-ad-slot[hidden] {
  display: none;
}

.board-ad-slot::after {
  content: "";
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  background: linear-gradient(110deg, transparent 0 30%, rgba(255, 255, 255, 0.16) 45%, transparent 60%);
  transform: translateX(-112%);
  animation: ad-shine 5.2s ease-in-out 900ms infinite;
  pointer-events: none;
}

.board-ad-slot .ad-slot-placeholder {
  position: relative;
  z-index: 1;
  border-color: rgba(255, 255, 255, 0.18);
  font-size: clamp(8px, 1.8vw, 15px);
}

.phone-stage.ads-off .board-ad-slot {
  opacity: 0;
  transform: scale(0.96);
  filter: saturate(0.7);
}

.phone-stage.match-restart .board-frame {
  animation: board-rise 620ms cubic-bezier(0.16, 0.9, 0.24, 1.06) both;
}

.phone-stage.match-restart .bottom-hud {
  animation: hud-rise 520ms cubic-bezier(0.16, 0.9, 0.24, 1.06) 100ms both;
}

.phone-stage.match-restart .hud-token {
  animation: token-pop 380ms cubic-bezier(0.18, 0.9, 0.24, 1.18) 220ms both;
}

.board-frame.win-celebrating {
  animation: board-win-pop 1.08s cubic-bezier(0.16, 0.9, 0.24, 1.12) both;
  box-shadow:
    0 0 0 5px rgba(255, 232, 91, 0.38),
    0 0 46px rgba(255, 221, 58, 0.54),
    0 18px 40px rgba(0, 0, 0, 0.34);
}

.board-frame.lose-celebrating {
  animation: board-lose-settle 640ms ease-out both;
  box-shadow:
    0 0 0 3px rgba(132, 170, 255, 0.24),
    0 0 28px rgba(105, 134, 205, 0.24);
}

#ludoBoard {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: clamp(10px, 1.9vw, 20px);
  box-shadow:
    0 0 0 clamp(2px, 0.42vw, 4px) rgba(252, 250, 240, 0.92),
    0 0 0 calc(clamp(2px, 0.42vw, 4px) + 1.5px) rgba(10, 26, 48, 0.4);
}

html[data-theme="dark"] #ludoBoard {
  box-shadow:
    0 0 0 clamp(2px, 0.42vw, 4px) rgba(212, 224, 222, 0.55),
    0 0 0 calc(clamp(2px, 0.42vw, 4px) + 1.5px) rgba(0, 0, 0, 0.55),
    0 0 0 calc(clamp(2px, 0.42vw, 4px) + 3px) rgba(184, 137, 59, 0.55),
    0 0 30px rgba(255, 176, 72, 0.16);
}

.token-hit-layer {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 4;
  pointer-events: none;
}

.token-hit {
  position: absolute;
  width: max(44px, 9.6%);
  aspect-ratio: 1;
  border-radius: 50%;
  border: 3px solid rgba(var(--turn-rgb, 174, 83, 255), 0);
  background: rgba(var(--turn-rgb, 35, 11, 70), 0.01);
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.86);
  pointer-events: none;
  transition: opacity 150ms ease, transform 150ms ease, border-color 150ms ease, box-shadow 150ms ease, background 150ms ease;
}

.token-hit.is-selectable {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
  pointer-events: auto;
  border-color: rgba(var(--turn-rgb, 209, 96, 255), 0.82);
  background:
    radial-gradient(circle, rgba(var(--turn-rgb, 131, 49, 255), 0.14) 0 36%, rgba(var(--turn-rgb, 29, 8, 61), 0.1) 37% 68%, transparent 70%);
  box-shadow:
    0 0 0 6px rgba(var(--turn-rgb, 116, 37, 255), 0.2),
    0 0 20px rgba(var(--turn-rgb, 205, 91, 255), 0.58),
    inset 0 0 18px rgba(255, 255, 255, 0.16);
  animation: token-hit-tech-pulse 1.08s ease-in-out infinite;
}

/* Same glass language as the lobby's settings button: emerald + gold. */
.round-button {
  width: var(--top-ctrl);
  aspect-ratio: 1;
  border-radius: 13px;
  background:
    linear-gradient(145deg, rgba(255, 255, 255, 0.22), transparent 40%),
    linear-gradient(135deg, rgba(23, 99, 78, 0.94), rgba(7, 42, 33, 0.95));
  border: 1px solid rgba(189, 250, 235, 0.42);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.32),
    inset 0 -4px 10px rgba(0, 0, 0, 0.26),
    0 8px 18px rgba(0, 0, 0, 0.32),
    0 0 16px rgba(255, 216, 77, 0.18);
  transition: transform 150ms ease, filter 150ms ease, box-shadow 150ms ease;
}

.round-button:active {
  transform: scale(0.94);
  filter: brightness(1.12);
}

.menu-button {
  position: absolute;
  top: var(--top-y);
  right: var(--edge-r);
  z-index: 7;
  display: grid;
  place-content: center;
  gap: clamp(4px, 0.7vw, 6px);
  /* NO overflow: hidden. It was here to clip the ::after shine sweep, and it also clipped the ::before
     tap-area expansion below — measured 32x32 hit area on a 375px viewport against a 44px minimum, i.e.
     the expansion did nothing at all on the one button that most needed it. The shine now clips itself
     (border-radius on ::after bounds its own background), so the parent does not have to. */
}

/* Home top-bar reuses the same animated menu button, but sits in the grid
   flow instead of the in-game absolute corner placement. */
.menu-button.home-menu {
  /* Same SIZE as the in-game control (width var(--top-ctrl) + aspect-ratio:1 from
     .round-button). Only the PLACEMENT changes — it sits in the home top-bar grid
     instead of the in-game absolute corner. RELATIVE, not static: the button's own
     ::before (invisible 46px tap-expansion) and ::after (shine) are position:absolute
     and MUST contain to the button — on a static button they resolved against the
     stage and became a giant invisible hit-area that stole the language button's taps
     (the "picker sometimes doesn't open" report). */
  position: relative;
  top: auto;
  right: auto;
  justify-self: end;
  align-self: center;
}

/* Visible escape hatch from a running match: mirrors the menu button on the
   left and funnels into the same double-tap-to-leave flow as Android back. */
/* Leave/back is now a compact chip pinned at the bottom-CENTER edge in all in-game
   modes (below the dice / HUD / call controls), thumb-reachable + frees the top bar.
   Hidden on home. The .round-button glass is reused; we just unset its square shape. */
.home-back-button {
  position: absolute;
  top: auto;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom, 0px) + clamp(3px, 0.9vw, 7px));
  transform: translateX(-50%);
  z-index: 8;
  width: auto;
  height: var(--top-ctrl);
  aspect-ratio: auto;
  min-height: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 0 clamp(12px, 3vw, 16px);
  border-radius: 999px;
  overflow: hidden;
}
.home-back-button .back-label {
  font: 800 clamp(11px, 3vw, 13px)/1 var(--font-ui);
  color: #ffe9a8;
  letter-spacing: 0.2px;
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.42);
  /* audit #8: within the capped pill, a long label truncates (…) instead of sprawling. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.home-back-button .back-chevron {
  flex: none; /* audit #8: the chevron is the primary affordance — never shrink/hide it when the label truncates. */
  width: clamp(12px, 2.4vw, 16px);
  height: clamp(12px, 2.4vw, 16px);
  border-left: clamp(4px, 0.7vw, 5px) solid #ffe9a8;
  border-bottom: clamp(4px, 0.7vw, 5px) solid #ffe9a8;
  border-radius: 2px;
  transform: rotate(45deg) translate(1px, -1px);
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.42));
}

.home-back-button:active {
  transform: translateX(-50%) scale(0.93);
}

/* During a call the Exit chip sits 10px above the screen bottom; the 2x2 call pill is
   anchored just 2px above it (see .call-overlay.active .call-controls short tier). */
.phone-stage.call-docked .home-back-button {
  bottom: calc(env(safe-area-inset-bottom, 0px) + 10px);
}

.menu-button::after {
  content: "";
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  border-radius: inherit;
  /* The sweep moves the BACKGROUND, not the element (cta-shine translates its ::after, which is why the
     parent needed overflow: hidden — and that clipped the tap-area ::before away to nothing). border-radius
     bounds a background in the element's own box, and the box never moves, so the shine stays inside the
     disc with no clipping from the parent. Verified side-by-side against the old rendering at 6x. */
  /* Stops are thirds of cta-shine's (34/48/62 -> 43.3/48/52.7) because the image is 300% of the box: a
     band that is 28% of a 3x image is the same absolute width on screen as 28% of a 1x one was. */
  background: linear-gradient(115deg, transparent 0 43.3%, rgba(255, 255, 255, 0.42) 48%, transparent 52.7%);
  background-size: 300% 100%;
  background-repeat: no-repeat;
  animation: menu-shine 4.2s ease-in-out 1.6s infinite;
  pointer-events: none;
}
/* Same 4.2s beat and the same hold-sweep-hold shape as cta-shine, expressed as a background offset.
   At 300% width the band sits off the left edge at 100% and off the right edge at 0%, so the sweep runs
   left-to-right exactly as translateX(-118%) -> translateX(118%) did. */
@keyframes menu-shine {
  0%, 48% { background-position: 100% 0; }
  70%, 100% { background-position: 0% 0; }
}

.menu-button span {
  position: relative;
  z-index: 1;
  width: clamp(22px, 3.8vw, 34px);
  height: clamp(3px, 0.48vw, 5px);
  border-radius: 8px;
  background: #ffe9a8;
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.4), 0 0 8px rgba(255, 222, 110, 0.45);
  transition: transform 180ms ease, opacity 180ms ease;
}

.menu-button.open span:nth-child(1) {
  transform: translateY(9px) rotate(45deg);
}

.menu-button.open span:nth-child(2) {
  opacity: 0;
}

.menu-button.open span:nth-child(3) {
  transform: translateY(-9px) rotate(-45deg);
}

.bottom-hud {
  position: absolute;
  left: 50%;
  bottom: var(--hud-bottom);
  z-index: 8;
  width: min(calc(910 / 945 * 100%), calc(100% - 18px));
  height: var(--hud-height);
  transform: translateX(-50%);
  display: grid;
  grid-template-columns: clamp(58px, 9.5vw, 100px) minmax(0, 1fr) clamp(76px, 14.1vw, 132px) minmax(0, 1fr) clamp(58px, 9.5vw, 100px);
  align-items: center;
  column-gap: clamp(2px, 0.8vw, 8px);
  overflow: visible;
  isolation: isolate;
  background:
    linear-gradient(115deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.05) 31%, rgba(255, 255, 255, 0.12) 72%, rgba(255, 255, 255, 0.04)),
    radial-gradient(circle at 88% -20%, rgba(255, 219, 108, 0.16), transparent 42%),
    linear-gradient(90deg, rgba(13, 74, 110, 0.5), rgba(8, 44, 34, 0.52) 46%, rgba(16, 84, 56, 0.5));
  border: 1px solid rgba(255, 222, 110, 0.42);
  border-radius: clamp(14px, 2.4vw, 24px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.5),
    inset 0 -1px 0 rgba(255, 255, 255, 0.12),
    inset 0 0 24px rgba(255, 244, 200, 0.06),
    0 -10px 26px rgba(0, 0, 0, 0.26),
    0 -1px 18px rgba(255, 216, 92, 0.12),
    0 0 0 1px rgba(8, 26, 18, 0.32);
  backdrop-filter: blur(18px) saturate(1.28);
  -webkit-backdrop-filter: blur(18px) saturate(1.28);
  animation: hud-rise 620ms cubic-bezier(0.16, 0.9, 0.24, 1.06) 260ms both;
}

.bottom-hud.turn-blue {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.62),
    inset 0 0 28px rgba(255, 120, 110, 0.16),
    0 -10px 28px rgba(0, 0, 0, 0.26),
    0 0 24px rgba(240, 90, 78, 0.42),
    0 0 0 1px rgba(255, 150, 140, 0.5);
}

.bottom-hud.turn-green {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.58),
    inset 0 0 28px rgba(101, 240, 141, 0.14),
    0 -10px 28px rgba(0, 0, 0, 0.26),
    0 0 24px rgba(76, 222, 104, 0.36),
    0 0 0 1px rgba(116, 245, 150, 0.44);
}

.phone-stage.win-celebrating .bottom-hud {
  box-shadow:
    inset 0 0 0 2px rgba(255, 255, 255, 0.34),
    0 0 0 4px rgba(255, 232, 91, 0.45),
    0 0 40px rgba(255, 221, 58, 0.62);
}

.bottom-hud::before,
.bottom-hud::after {
  content: "";
  position: absolute;
  z-index: 0;
  top: 13%;
  bottom: 13%;
  width: 28%;
  border: 1px solid rgba(255, 234, 160, 0.2);
  border-radius: clamp(8px, 1.6vw, 16px);
  background:
    linear-gradient(140deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.04) 43%, rgba(255, 255, 255, 0.08)),
    rgba(6, 34, 24, 0.26);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.26),
    inset 0 -12px 18px rgba(3, 12, 32, 0.18),
    0 8px 18px rgba(0, 0, 0, 0.12);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

.bottom-hud::before {
  left: 16%;
}

.bottom-hud::after {
  right: 16%;
}

.hud-name {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ffe76a;
  font-size: clamp(20px, 4vw, 38px);
  min-width: 0;
  line-height: 1;
  letter-spacing: 0;
  overflow: visible;
  text-align: center;
  padding-left: clamp(3px, 0.9vw, 9px); padding-right: clamp(3px, 0.9vw, 9px); /* physical (padding-inline is Chromium 87+) */
  transform-origin: center bottom;
  transition: transform 180ms ease, filter 180ms ease;
}

.hud-name-viewport {
  position: relative;
  display: flex;
  width: 100%;
  min-width: 0;
  height: 1.18em;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: 9px;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 11px, #000 calc(100% - 11px), transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 11px, #000 calc(100% - 11px), transparent 100%);
}

.hud-name-text {
  flex: 0 0 auto;
  display: inline-block;
  max-width: none;
  font-family: var(--font-display);
  font-size: var(--hud-name-size, clamp(20px, 4vw, 38px));
  font-weight: 900;
  line-height: 1;
  letter-spacing: 0;
  white-space: nowrap;
  unicode-bidi: plaintext;
  text-shadow:
    0 2px 0 rgba(10, 13, 23, 0.95),
    2px 0 0 rgba(10, 13, 23, 0.86),
    -2px 0 0 rgba(10, 13, 23, 0.86),
    0 -1px 0 rgba(10, 13, 23, 0.82),
    0 0 14px rgba(255, 226, 80, 0.22);
}

.hud-name.is-marquee .hud-name-viewport {
  justify-content: flex-start;
}

.hud-name.is-marquee .hud-name-text {
  animation: hud-name-scroll var(--hud-name-duration, 7.5s) cubic-bezier(0.42, 0, 0.2, 1) 700ms infinite;
  will-change: transform;
}

.turn-blue .you-name,
.turn-green .com-name {
  transform: translateY(2px) scale(1.035);
  filter: drop-shadow(0 0 8px rgba(255, 222, 64, 0.8));
}

.hud-name::after {
  content: attr(data-turn-label);
  position: absolute;
  left: 50%;
  top: auto;
  bottom: calc(100% + clamp(8px, 1.25vw, 13px));
  z-index: 4;
  min-width: max-content;
  padding: 4px 10px 5px;
  border-radius: 999px;
  border: 1px solid rgba(255, 248, 200, 0.85);
  background: linear-gradient(180deg, #ffe98a, #ffb02e);
  color: #5a3c02;
  font: 900 clamp(9px, 1.65vw, 14px)/1 var(--font-ui);
  text-transform: uppercase;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);
  letter-spacing: 0;
  opacity: 0;
  visibility: hidden;
  transform: translateX(-50%) translateY(4px) scale(0.9);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.32);
  transition: opacity 160ms ease, transform 160ms ease, visibility 160ms ease;
  pointer-events: none;
}

.turn-blue .you-name::after,
.turn-green .com-name::after {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0) scale(1);
  animation: turn-badge-pulse 1.2s ease-in-out infinite;
}

.bottom-hud.turn-blue .com-name::after,
.bottom-hud.turn-green .you-name::after {
  opacity: 0 !important;
  visibility: hidden !important;
  animation: none !important;
}

.turn-blue .you-name::after {
  box-shadow: 0 0 16px rgba(255, 110, 96, 0.62), 0 5px 12px rgba(0, 0, 0, 0.34);
}

.turn-green .com-name::after {
  box-shadow: 0 0 16px rgba(84, 239, 122, 0.56), 0 5px 12px rgba(0, 0, 0, 0.34);
}

.dice-button {
  position: relative;
  z-index: 3;
  width: clamp(76px, 14.1vw, 132px);
  height: clamp(76px, 14.1vw, 132px);
  justify-self: center;
  display: grid;
  place-items: center;
  border-radius: clamp(14px, 2.6vw, 24px);
  background:
    radial-gradient(circle at 50% 18%, rgba(255, 255, 255, 0.66), transparent 34%),
    linear-gradient(140deg, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.12) 42%, rgba(255, 244, 208, 0.26)),
    rgba(255, 240, 200, 0.32);
  border: 1px solid rgba(255, 234, 160, 0.6);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.66),
    inset 0 -10px 18px rgba(96, 76, 30, 0.18),
    0 10px 20px rgba(0, 0, 0, 0.26),
    0 0 0 4px rgba(255, 236, 160, 0.08);
  backdrop-filter: blur(16px) saturate(1.16);
  -webkit-backdrop-filter: blur(16px) saturate(1.16);
  transition: filter 160ms ease, transform 160ms ease;
}

.dice-button::before {
  content: "";
  position: absolute;
  top: clamp(-7px, -1.25vw, -5px); right: clamp(-7px, -1.25vw, -5px); bottom: clamp(-7px, -1.25vw, -5px); left: clamp(-7px, -1.25vw, -5px); /* inset longhands (Chromium<87 WebView support) */
  z-index: 0;
  border-radius: calc(clamp(14px, 2.6vw, 24px) + 7px);
  border: 2px solid rgba(var(--turn-rgb, 209, 96, 255), 0);
  background:
    radial-gradient(circle, rgba(var(--turn-rgb, 131, 49, 255), 0.14) 0 36%, rgba(var(--turn-rgb, 29, 8, 61), 0.1) 37% 68%, transparent 70%);
  box-shadow:
    0 0 0 5px rgba(var(--turn-rgb, 116, 37, 255), 0),
    0 0 18px rgba(var(--turn-rgb, 205, 91, 255), 0),
    inset 0 0 16px rgba(255, 255, 255, 0);
  opacity: 0;
  pointer-events: none;
  transition: opacity 150ms ease, border-color 150ms ease;
}

.dice-button:not(:disabled):not(.rolling):not(.landed)::before {
  opacity: 1;
  border-color: rgba(var(--turn-rgb, 209, 96, 255), 0.82);
  animation: token-hit-tech-pulse 1.08s ease-in-out infinite;
}

.dice-button:not(:disabled) {
  animation: dice-ready 1.15s ease-in-out infinite;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.78),
    inset 0 -10px 18px rgba(56, 71, 90, 0.16),
    0 10px 20px rgba(0, 0, 0, 0.26),
    0 0 0 4px rgba(255, 255, 255, 0.08),
    0 0 20px rgba(var(--turn-rgb, 205, 91, 255), 0.5);
}

.dice-button:disabled {
  filter: saturate(0.72) brightness(0.9);
}

.turn-green .dice-button:disabled {
  animation: computer-dice-wait 1.25s ease-in-out infinite;
}

.dice-button.is-throwing .dice-face {
  opacity: 0.38;
  transform: scale(0.82);
  filter: saturate(0.65);
}

.dice-button.rolling {
  animation: dice-button-kick 420ms cubic-bezier(0.16, 0.9, 0.24, 1.08);
}

.dice-button.landed {
  animation: dice-land 300ms ease-out;
}

.dice-face {
  position: relative;
  z-index: 1;
  width: 62%;
  aspect-ratio: 1;
  border-radius: 17%;
  background:
    radial-gradient(circle at 26% 20%, rgba(255, 255, 255, 0.96) 0 13%, transparent 14%),
    radial-gradient(circle at 72% 76%, rgba(216, 203, 184, 0.45), transparent 30%),
    linear-gradient(145deg, #fffdf3 0%, #f3ead8 42%, #d8c7ad 100%);
  border: max(2px, 0.22vw) solid rgba(132, 115, 91, 0.78);
  box-shadow:
    inset 4px 5px 8px rgba(255, 255, 255, 0.78),
    inset -7px -8px 13px rgba(104, 83, 55, 0.26),
    inset 0 0 0 max(2px, 0.3vw) rgba(255, 255, 255, 0.38),
    0 7px 9px rgba(0, 0, 0, 0.32),
    0 14px 18px rgba(0, 0, 0, 0.18);
  transform: rotateX(10deg) rotateZ(-2deg);
  transition: opacity 170ms ease, transform 170ms ease, filter 170ms ease;
}

.dice-face i {
  position: absolute;
  width: 16%;
  aspect-ratio: 1;
  border-radius: 50%;
  background:
    radial-gradient(circle at 34% 28%, #5c6470 0 14%, #1b2028 15% 64%, #07090d 65%);
  box-shadow:
    inset 0 2px 3px rgba(0, 0, 0, 0.52),
    inset 0 -1px 1px rgba(255, 255, 255, 0.22),
    0 1px 0 rgba(255, 255, 255, 0.48);
}

.dice-face i.pip-red {
  background:
    radial-gradient(circle at 34% 28%, #ff9d8b 0 14%, #d5342f 15% 66%, #7c0f12 67%);
}

.pip-1 { left: 19%; top: 19%; }
.pip-2 { right: 19%; top: 19%; }
.pip-3 { left: 19%; top: 42%; }
.pip-4 { right: 19%; top: 42%; }
.pip-5 { left: 19%; bottom: 19%; }
.pip-6 { right: 19%; bottom: 19%; }
.pip-center { left: 42%; top: 42%; }

.flying-dice {
  --dice-size: 92px;
  position: absolute;
  left: 0;
  top: 0;
  width: var(--dice-size);
  height: var(--dice-size);
  transform: translate(-50%, -50%);
  transform-style: preserve-3d;
  will-change: transform, opacity;
  /* NO `filter` here. A drop-shadow on this element was three problems at once:
     - REDUNDANT: ::after below already draws the die's shadow, as a ground ellipse, which is the more
       truthful shadow for a die lying on a board anyway.
     - A BUG: `filter` is a grouping property, so it flattened this element's own 3D rendering context.
       The ::after comment records what that cost — the ground ellipse stopped compositing in 3D and
       painted straight over the die face, a grey smear on every throw, patched with z-index:-1.
     - EXPENSIVE: a 20px blur recomputed every frame over a subtree that is rotating 1440deg, and whose
       silhouette therefore changes on every one of those frames. This is the same class of cost that made
       .board-frame swap its filter for a box-shadow.
     Removing it does NOT lose the stacking context that keeps the ::after contained: `transform` above
     creates one on its own. diceFlight.test.cjs pins that, because it is the one thing whose loss would
     put the shadow behind the board instead of under the die. */
}

.flying-dice::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 72%;
  width: 88%;
  height: 24%;
  border-radius: 50%;
  background: radial-gradient(ellipse, rgba(0, 0, 0, 0.52), rgba(0, 0, 0, 0.12) 62%, transparent 70%);
  transform: translate(-50%, -50%) rotateX(58deg);
  transform-origin: 50% 50%;
  opacity: 0.42;
  /* BEHIND the cube, explicitly. The rotateX above was meant to lay this ellipse flat on the ground so the
     cube composites above it in 3D — but `filter` on .flying-dice forces that element's transform-style to
     `flat` (a grouping property flattens its own 3D rendering context), so the 3D relationship is gone and
     paint order decides instead. .dice-cube carries a transform and therefore a z-index:auto stacking
     context, so this pseudo-element, later in tree order, painted straight OVER the die face: a grey
     elliptical smear across the middle of every thrown die, on every roll.
     z-index:-1 puts it back underneath. It stays contained because the filter already makes .flying-dice a
     stacking context, so this can never escape behind the board. */
  z-index: -1;
  animation: dice-shadow-pulse 920ms cubic-bezier(0.16, 0.9, 0.24, 1) both;
}

/* Zero-motion dice reveal (reduced-motion / no-WAAPI / light-dice engines): the final face
   simply appears at the board centre — NO animations anywhere on it (WCAG reduced-motion),
   just a readable settled tilt so the top face presents clearly. */
.flying-dice.static-reveal,
.flying-dice.static-reveal::after {
  animation: none;
}
.flying-dice.static-reveal .dice-cube {
  transform: rotateX(-16deg) rotateY(-16deg);
  animation: none;
  transition: none;
}

.dice-cube {
  position: absolute;
  /* Longhands (not `inset:`) so the cube keeps its size on Chromium <87 WebViews. */
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  transform-style: preserve-3d;
  will-change: transform;
  /* The interior seal fill is coloured to BLEND with the face (a mid-tone of the ivory die), NOT a dark
     contrast. So the cross-axis planes and the edge ring are invisible: no dark seam/cross lines, and any
     hair-thin corner gap during a tumble shows a die-toned sliver instead of the board. Per-theme override. */
  --cube-seam: #e7d8bd;
  background: var(--cube-seam);
  /* Rounder-but-small radius keeps the corner gaps tiny; the plane stays inside the face silhouette. */
  border-radius: 10%;
}

/* Seal the 3D cube edges for EVERY dice theme: two more opaque cross-axis planes (plus the .dice-cube
   background plane above) fill the hollow cube centre on all three axes, so the board can NEVER show through
   the rounded-corner seams while the die tumbles. Cosmetic only — geometry, pip placement, transforms,
   hitboxes and roll timing are untouched. Per-theme rules (e.g. data-dice-theme="wood") override the colour
   + their own core with higher specificity, so this leaves Wood pixel-identical and only fixes Classic. */
.dice-cube::before,
.dice-cube::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  border-radius: 10%;
  background: var(--cube-seam);
  pointer-events: none;
  /* Deliberately double-sided (no backface culling): these are uniform opaque fills, and
     culling their backfaces reopens the corner seams for the whole quadrant of tumble
     orientations where both planes face away — e.g. rotateX(72deg) rotateY(90deg), which
     the lobby hero spin passes through EVERY revolution. Double-sided paint is
     pixel-identical at every orientation where the planes already painted. */
  z-index: -1;
}
.dice-cube::before { transform: rotateY(90deg); }
.dice-cube::after { transform: rotateX(90deg); }

.cube-face {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  place-items: center;
  padding: 17%;
  border-radius: 10%;
  border: max(1px, calc(var(--dice-size) * 0.012)) solid rgba(150, 132, 104, 0.5);
  background:
    radial-gradient(circle at 24% 18%, rgba(255, 255, 255, 0.98), transparent 17%),
    radial-gradient(circle at 78% 78%, rgba(198, 176, 144, 0.42), transparent 31%),
    linear-gradient(145deg, #fffef7 0%, #f3ead7 45%, #d4bea0 100%);
  /* A thin die-toned seal ring fills the hair-line gap between adjacent faces INVISIBLY (var(--cube-seam)
     blends with the face), then a soft top-left highlight + bottom-right shade sculpt a smooth rounded
     BEVEL at every edge — so the die reads as one solid rounded block, never a panelled box with dark grout. */
  box-shadow:
    0 0 0 max(1px, calc(var(--dice-size) * 0.016)) var(--cube-seam),
    inset max(5px, calc(var(--dice-size) * 0.07)) max(6px, calc(var(--dice-size) * 0.08)) max(11px, calc(var(--dice-size) * 0.14)) rgba(255, 255, 255, 0.72),
    inset max(-7px, calc(var(--dice-size) * -0.085)) max(-9px, calc(var(--dice-size) * -0.11)) max(13px, calc(var(--dice-size) * 0.16)) rgba(120, 96, 60, 0.34),
    inset 0 0 0 max(1px, calc(var(--dice-size) * 0.012)) rgba(255, 255, 255, 0.4);
  backface-visibility: hidden;
}

.cube-dot {
  width: 52%;
  aspect-ratio: 1;
  border-radius: 50%;
  background:
    radial-gradient(circle at 34% 28%, #606975 0 14%, #1a1f27 15% 64%, #06080c 65%);
  box-shadow:
    inset 0 max(1px, calc(var(--dice-size) * 0.018)) max(3px, calc(var(--dice-size) * 0.035)) rgba(0, 0, 0, 0.58),
    inset 0 max(-1px, calc(var(--dice-size) * -0.012)) 1px rgba(255, 255, 255, 0.2),
    0 1px 0 rgba(255, 255, 255, 0.5);
}

.cube-dot.is-red-pip {
  background:
    radial-gradient(circle at 34% 28%, #ff9d8b 0 14%, #d63631 15% 66%, #7b1012 67%);
}

.cube-front { transform: translateZ(calc(var(--dice-size) / 2)); }
.cube-back { transform: rotateY(180deg) translateZ(calc(var(--dice-size) / 2)); }
.cube-right { transform: rotateY(90deg) translateZ(calc(var(--dice-size) / 2)); }
.cube-left { transform: rotateY(-90deg) translateZ(calc(var(--dice-size) / 2)); }
.cube-top { transform: rotateX(90deg) translateZ(calc(var(--dice-size) / 2)); }
.cube-bottom { transform: rotateX(-90deg) translateZ(calc(var(--dice-size) / 2)); }

/* Player seats: lobby-style emerald glass chips. The left seat shows the
   player's avatar emoji (same one picked in the lobby), the right seat shows
   the Real AI robot face — or the second player's initial in Pass N Play. */
.hud-token {
  position: relative;
  z-index: 3;
  width: clamp(56px, 9.2vw, 96px);
  height: clamp(56px, 9.2vw, 96px);
  justify-self: center;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font: 900 clamp(26px, 5vw, 46px)/1 var(--font-emoji);
  color: #ffe9a8;
  text-shadow: 0 2px 3px rgba(0, 0, 0, 0.45);
  background:
    radial-gradient(circle at 32% 24%, rgba(255, 255, 255, 0.32), transparent 42%),
    linear-gradient(160deg, rgba(20, 92, 70, 0.94), rgba(5, 34, 26, 0.95));
  border: clamp(2px, 0.4vw, 3px) solid rgba(255, 222, 110, 0.5);
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, 0.32),
    inset 0 -7px 14px rgba(0, 0, 0, 0.3),
    0 10px 20px rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: transform 180ms ease, filter 180ms ease, border-color 180ms ease;
}

/* Human seat is red now (board orientation: red at the bottom). */
.blue-token { --token-color: #ef4a44; }
.green-token { --token-color: #3fd980; }

/* Seat-color ring just outside the gold rim. */
.hud-token::before {
  content: "";
  position: absolute;
  top: -9%; right: -9%; bottom: -9%; left: -9%; /* inset longhands (Chromium<87 WebView support) */
  border-radius: 50%;
  border: clamp(2px, 0.4vw, 3px) solid var(--token-color);
  opacity: 0.55;
  transition: opacity 180ms ease;
}

.turn-blue .blue-token,
.turn-green .green-token {
  transform: translateY(-6px) scale(1.06);
  border-color: rgba(211, 96, 255, 0.95);
  filter:
    drop-shadow(0 0 10px rgba(184, 73, 255, 0.78))
    drop-shadow(0 0 5px rgba(80, 217, 255, 0.48));
  animation: active-token-pulse 1.08s ease-in-out infinite;
}

.turn-blue .blue-token::before,
.turn-green .green-token::before {
  opacity: 0.95;
}

/* Robot face for the computer / Real AI seat: head, visor and glowing eyes
   painted as layered gradients on one pseudo-element. */
.hud-token.is-bot::after {
  content: "";
  position: absolute;
  left: 19%;
  right: 19%;
  top: 23%;
  height: 48%;
  border-radius: 30%;
  background:
    radial-gradient(circle at 31% 57%, #d9fbff 0 4%, #56e2ff 5% 11%, transparent 12%),
    radial-gradient(circle at 69% 57%, #d9fbff 0 4%, #56e2ff 5% 11%, transparent 12%),
    linear-gradient(180deg, transparent 0 36%, #0b1d2e 36% 80%, transparent 80%),
    linear-gradient(165deg, #f4fbff 0%, #c2d6e6 50%, #8aa3ba 100%);
  border: 1px solid rgba(58, 80, 100, 0.65);
  box-shadow:
    inset 0 2px 2px rgba(255, 255, 255, 0.75),
    inset 0 -2px 4px rgba(40, 62, 84, 0.32),
    0 2px 5px rgba(0, 0, 0, 0.32);
}

/* No message to show (e.g. a plain turn — the glow + corner dice cover it):
   the strip fades out so the screen stays clean. */
.status-chip.is-empty {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.status-chip {
  position: absolute;
  left: 50%;
  bottom: calc(var(--hud-height) + var(--hud-bottom) + clamp(8px, calc(1.2 * var(--dvh)), 14px));
  z-index: 7;
  width: min(520px, calc(100% - 32px));
  min-height: var(--status-height);
  transform: translateX(-50%);
  display: grid;
  place-items: center;
  padding: 8px 16px;
  border-radius: clamp(10px, 1.8vw, 18px);
  border: 1px solid rgba(178, 240, 255, 0.48);
  background:
    linear-gradient(110deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.05) 39%, rgba(255, 255, 255, 0.1)),
    linear-gradient(90deg, rgba(22, 85, 122, 0.46), rgba(8, 48, 50, 0.42));
  color: #effff8;
  font: 800 clamp(12px, 2.4vw, 17px)/1.25 var(--font-ui);
  text-align: center;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.44);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.38),
    inset 0 -1px 0 rgba(255, 255, 255, 0.08),
    0 8px 22px rgba(0, 0, 0, 0.24),
    0 0 18px rgba(89, 204, 255, 0.12);
  backdrop-filter: blur(16px) saturate(1.18);
  -webkit-backdrop-filter: blur(16px) saturate(1.18);
}

.status-chip[hidden] {
  display: none !important;
}

.status-chip::before {
  content: "";
  width: clamp(8px, 1.4vw, 12px);
  aspect-ratio: 1;
  border-radius: 50%;
  position: absolute;
  left: clamp(12px, 2.2vw, 18px);
  top: 50%;
  transform: translateY(-50%);
  background: #72d9ff;
  box-shadow: 0 0 12px rgba(114, 217, 255, 0.72);
}

.status-chip span {
  /* Physical on purpose: the chip's ::before dot is pinned with a physical `left:`, so the text
     must clear it on the LEFT in BOTH directions (and padding-inline is Chromium 87+ anyway). */
  padding-left: 12px;
  padding-right: 0;
}

.status-chip.turn-blue {
  border-color: rgba(255, 140, 130, 0.6);
  background:
    linear-gradient(110deg, rgba(255, 255, 255, 0.24), rgba(255, 255, 255, 0.05) 39%, rgba(255, 255, 255, 0.12)),
    linear-gradient(90deg, rgba(150, 44, 38, 0.5), rgba(74, 14, 12, 0.46));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.2),
    0 8px 22px rgba(0, 0, 0, 0.28),
    0 0 18px rgba(240, 90, 78, 0.34);
}

.status-chip.turn-green {
  border-color: rgba(99, 238, 130, 0.54);
  background:
    linear-gradient(110deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.045) 39%, rgba(255, 255, 255, 0.1)),
    linear-gradient(90deg, rgba(15, 92, 55, 0.54), rgba(7, 45, 33, 0.48));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 8px 22px rgba(0, 0, 0, 0.28),
    0 0 18px rgba(80, 235, 119, 0.28);
}

.status-chip.turn-green::before {
  background: #6ff08d;
  box-shadow: 0 0 12px rgba(111, 240, 141, 0.7);
}

.status-chip.is-busy::after {
  content: "";
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  border-radius: inherit;
  background: linear-gradient(115deg, transparent 0 32%, rgba(255, 255, 255, 0.2) 48%, transparent 64%);
  transform: translateX(-112%);
  animation: status-scan 1.55s ease-in-out infinite;
  pointer-events: none;
}

.drawer {
  position: absolute;
  top: max(148px, calc(336 / 2048 * 100%));
  right: clamp(14px, 3.1vw, 32px);
  width: min(440px, calc(100% - 28px));
  max-height: min(calc(58 * var(--dvh)), 440px);
  z-index: 9;
  display: grid;
  gap: 10px;
  padding: 14px;
  border-radius: var(--r-card);
  border: 1px solid rgba(247, 231, 182, 0.3);
  background:
    linear-gradient(155deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02) 42%, rgba(34, 150, 110, 0.04)),
    var(--lobby-panel);
  box-shadow: var(--rim-top), var(--rim-bottom), var(--e3), var(--amb-warm);
  font-family: var(--font-ui);
  overflow: auto;
  backdrop-filter: blur(16px) saturate(1.12);
  -webkit-backdrop-filter: blur(16px) saturate(1.12);
  transform-origin: 100% 0;
  opacity: 0;
  transform: translateY(-8px) scale(0.96);
  visibility: hidden;
  transition: opacity 180ms ease, transform 180ms ease, visibility 180ms ease;
}

.drawer.open {
  opacity: 1;
  transform: translateY(0) scale(1);
  visibility: visible;
}

.drawer-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.drawer button,
.drawer select,
.drawer-toggles label {
  min-height: 46px;
  min-width: min(132px, 100%);
  flex: 1;
  border-radius: var(--r-chip);
  border: 1px solid var(--hairline);
  background: var(--surface-2);
  color: var(--ink);
  padding: 0 12px;
  font: 800 14px/1 var(--font-ui);
  box-shadow: var(--rim-top);
  transition: transform 130ms cubic-bezier(0.34, 1.56, 0.64, 1), border-color 160ms ease, color 160ms ease;
}

/* Only the <select> needs the 16px iOS focus-zoom floor — buttons and labels never raise a keyboard, so they
   keep the drawer's 14px rhythm. Same specificity as the shared rule above, so source order decides. */
.drawer select {
  font-size: 16px;
}

/* Bounded font-scale: buttons and toggle labels ONLY.
 *
 * `select` is excluded on purpose, and it is not a style preference. It carries the 16px iOS focus-zoom
 * floor, and iosKeyboardZoom.test.cjs models a field's size by parsing this stylesheet — its smallestPx()
 * reads the first px preceded by whitespace, so `min(calc(16px * ...), 21px)` would make it report the
 * CEILING and the guard would pass while blind. uiScale.test.cjs forbids --ui-scale on any field subject.
 *
 * Width here is font-INDEPENDENT: flex-basis is 0 with an explicit min-width, so content size never enters
 * the flex calculation. This can therefore only change row HEIGHT, which .drawer { overflow: auto } absorbs. */
.drawer button,
.drawer-toggles label {
  font-size: min(calc(14px * var(--ui-scale)), 18px);
}

.drawer button:active,
.drawer select:active,
.drawer-toggles label:active {
  transform: scale(0.96);
  border-color: rgba(247, 231, 182, 0.42);
  color: var(--gold-1);
}

.drawer select option {
  background: #0c2027;
  color: var(--ink);
}

html[data-theme="dark"] .drawer select option {
  background: #06121a;
}

.drawer-toggles label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.drawer input[type="checkbox"] {
  width: 17px;
  height: 17px;
  accent-color: var(--gold-2);
}

.drawer p {
  margin: 2px 0 0;
  min-height: 22px;
  padding-top: 10px;
  border-top: 1px solid var(--hairline);
  color: var(--ink-2);
  font: 700 13px/1.3 var(--font-ui);
  text-align: center;
}

/* Light theme drawer readability (WCAG). The drawer is the IN-GAME menu, so it can sit over the light
   home scene OR the navy game board. Its text/controls use the dark-baseline light inks (--ink/--ink-2),
   which sat at ~1.5:1 on the translucent (--lobby-panel) panel over the light home scene. A single ink
   cannot stay >=4.5:1 over BOTH a light and a navy backdrop through a translucent panel, so make the
   light-theme panel NEAR-OPAQUE — the composited background is then a stable light sage regardless of
   what is behind — and use the dark panel ink (--lobby-ink) for every drawer text + control. Gives
   ~8-10:1 backdrop-independently (a bare .drawer-p color swap alone would have regressed the navy
   in-game case to ~1.35:1). Dark theme keeps its translucent panel + light ink (unaffected). */
html[data-theme="light"] .drawer {
  background:
    linear-gradient(155deg, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.4) 42%, rgba(34, 150, 110, 0.12)),
    rgba(226, 242, 233, 0.96);
  border-color: rgba(24, 125, 89, 0.3);
}
html[data-theme="light"] .drawer p,
html[data-theme="light"] .drawer button,
html[data-theme="light"] .drawer select,
html[data-theme="light"] .drawer-toggles label {
  color: var(--lobby-ink);
}
html[data-theme="light"] .drawer button,
html[data-theme="light"] .drawer select,
html[data-theme="light"] .drawer-toggles label {
  border-color: rgba(24, 125, 89, 0.28);
}
html[data-theme="light"] .drawer button:active,
html[data-theme="light"] .drawer select:active,
html[data-theme="light"] .drawer-toggles label:active {
  color: #0e3327; /* keep the pressed state dark/readable on the light panel (was --gold-1, low-contrast) */
}
/* The Pro badge is light-gold ink on a translucent gold fill — ~1.1:1 on the now-light sage panel. Give
   it a SOLID gold chip + dark ink in light theme so its OWN background governs contrast (backdrop-
   independent) and the gold Pro identity is kept (~7:1). Dark theme badge is unchanged. */
html[data-theme="light"] .drawer .premium-badge {
  background: linear-gradient(135deg, #ffd24a, #f0b400);
  color: #4a3400;
  border-color: rgba(150, 100, 0, 0.55);
}

@media (hover: hover) {
  .no-ads-badge:hover {
    transform: scale(1.018);
    filter: brightness(1.08) saturate(1.05);
  }

  .round-button:hover,
  .dice-button:not(:disabled):hover,
  .drawer button:hover,
  .drawer select:hover,
  .drawer-toggles label:hover {
    filter: brightness(1.08) saturate(1.05);
  }
}

@media (max-width: 400px) {
  :root {
    --hud-height: clamp(82px, calc(14 * var(--dvh)), 100px);
    --status-height: clamp(36px, calc(6.4 * var(--dvh)), 42px);
  }

  .bottom-hud {
    width: calc(100% - 10px);
    grid-template-columns: clamp(44px, 13.5vw, 56px) minmax(0, 1fr) clamp(76px, 23vw, 94px) minmax(0, 1fr) clamp(44px, 13.5vw, 56px);
    column-gap: 0;
    border-width: 3px;
  }

  .hud-name {
    line-height: 1.05;
  }

  .hud-name-text {
    text-shadow:
      0 2px 0 #101018,
      2px 0 0 #101018,
      -2px 0 0 #101018,
      0 -1px 0 #101018;
  }

  .hud-token {
    width: clamp(44px, 13.5vw, 56px);
  }

  .dice-button {
    width: clamp(76px, 23vw, 94px);
    height: clamp(76px, 23vw, 94px);
  }
}

@media (max-height: 760px) {
  .home-screen {
    grid-template-rows: auto auto auto auto;
    gap: 8px;
    padding-top: 12px;
    padding-bottom: 10px;
  }

  .profile-card {
    min-height: 82px;
    padding: 7px 10px;
  }

  .home-hero {
    min-height: 92px;
  }

  .home-hero h1 {
    font-size: clamp(30px, 8.9vw, 46px);
  }

  .home-dice-scene {
    height: 32px;
  }

  .mode-panel {
    gap: 8px;
    padding: 10px;
  }

  .mode-card {
    min-height: 70px;
  }

  .home-start-panel {
    padding: 10px;
  }

  .start-mode-button {
    min-height: 56px;
  }
}

@media (max-height: 680px) {
  .home-screen {
    gap: 6px;
    padding-top: 8px;
    padding-bottom: 8px;
  }

  .home-topbar {
    gap: 7px;
  }

  .profile-card {
    min-height: 88px;
    gap: 6px;
    padding: 6px 8px;
  }

  .profile-avatar-button {
    width: 44px;
    height: 44px;
  }

  .profile-fields {
    gap: 3px;
  }

  .profile-input-wrap,
  .country-select-wrap,
  .gender-select-wrap,
  .opponent-input-wrap {
    gap: 2px;
  }

  .profile-input-wrap input,
  .opponent-input-wrap input {
    padding: 5px 8px;
  }

  .country-flag-button {
    min-width: 44px;
    height: 44px;
    padding: 0 7px;
  }

  .country-flag-current {
    width: 24px;
    height: 16px;
  }

  .gender-button {
    width: auto;
    min-width: 56px;
    height: 36px;
  }

  .gender-current {
    gap: 4px;
    padding: 0;
    font-size: 10px;
  }

  .gender-current-icon {
    width: 26px;
    height: 26px;
    font-size: 18px;
  }

  .gender-pop {
    padding: 12px;
  }

  .gender-grid {
    gap: 8px;
  }

  .gender-card {
    min-height: 76px;
    gap: 5px;
    padding: 10px 8px;
  }

  .gender-card-symbol {
    width: 34px;
    height: 34px;
    font-size: 22px;
  }

  .gender-card strong {
    font-size: 12px;
  }

  .profile-stats {
    gap: 3px;
  }

  .stat-pill {
    min-width: 39px;
    min-height: 22px;
    padding: 3px 5px;
  }

  #homeLangButton,
  .home-menu {
    /* home pair only — the in-game pair sizes via var(--top-ctrl) so it always matches the menu */
    width: 46px;
    height: 46px;
  }

  .home-hero {
    min-height: 74px;
  }

  .home-hero h1 {
    font-size: clamp(25px, 7.6vw, 36px);
  }

  .home-dice-scene {
    height: 24px;
  }

  .daily-challenge {
    min-height: 40px;
    gap: 8px;
    padding: 5px 9px;
  }

  .daily-challenge .dc-icon {
    font-size: 18px;
  }

  .daily-challenge .dc-copy strong {
    font-size: 9px;
  }

  .daily-challenge .dc-copy small {
    font-size: 11px;
  }

  .daily-challenge .dc-progress {
    padding: 4px 9px;
    font-size: 11px;
  }

  .mode-panel {
    gap: 6px;
    padding: 8px;
  }

  .mode-card-featured {
    min-height: 52px;
    gap: 8px;
    padding: 7px 10px;
  }

  .featured-ribbon {
    padding: 2px 8px 3px;
    font-size: 8px;
  }

  .featured-copy {
    gap: 2px;
    padding-top: 7px;
  }

  .featured-copy strong {
    font-size: clamp(14px, 4.2vw, 18px);
  }

  .featured-copy small {
    font-size: clamp(9px, 2.4vw, 11px);
  }

  .mode-grid {
    grid-auto-rows: clamp(88px, calc(14 * var(--dvh)), 102px);
    gap: 7px;
  }

  .mode-grid .mode-card {
    min-height: 0;
    gap: 2px;
    padding: 6px;
  }

  .mode-icon {
    width: 32px;
    height: 32px;
  }

  .mode-card-featured .mode-icon {
    width: 38px;
    height: 38px;
  }

  .mode-icon::before {
    font-size: 12px;
  }

  .mode-card strong {
    font-size: clamp(12px, 3.4vw, 16px);
  }

  .mode-card small {
    font-size: clamp(8px, 2.2vw, 10px);
  }

  .mode-card em {
    padding: 2px 8px;
    font-size: 8px;
  }
}

/* Very short PORTRAIT viewports (320x480 down through phone split-screen/freeform, BELOW the 320x568
   baseline). The living board is a width-driven square (aspect-ratio 1/1), so on a short viewport it
   grows taller than the screen and the bottom mode row (Computer / Pass N Play) clips or scrolls.
   Reuse the near-square-Fold pattern: the board ROW becomes the remainder (minmax(236px, 1fr)) and the
   board is the largest square that fits BOTH the remaining row height and the available width (height
   drives, aspect-ratio derives width -> never distorts). The board therefore ABSORBS the chrome height,
   so the column total always equals the viewport (robust to 25-language chrome variance) with NO scroll
   until the board hits its 236px TRACK floor, which keeps the 19% centre "AI" mode button >= 44dp
   (corner modes 27% -> ~64px). The floor is on the TRACK (not the board), so the board never paints
   upward over the rows above. vh/vw + min()/calc() only (Chromium-80 safe, no bare dvh unit).
   Portrait-scoped so the landscape 2-column home grid is untouched. */
@media (orientation: portrait) and (max-height: 560px) {
  .home-screen { gap: 4px; }
  .home-hero { min-height: 60px; }
  .home-dice-scene { height: 18px; }
  .home-board {
    width: min(100%, calc(100 * var(--dvh) - 238px));
    min-width: 236px;
  }
}

/* Short PORTRAIT split-screen / freeform (<= 473px: e.g. 320x426 & 360x426, PLUS the 441-473 band the
   560px tier can't fit once the board reaches its 236px floor — this closes that transition scroll gap).
   ACCESSIBILITY: every interactive control keeps an effective >= 44x44dp touch target — avatar, the name
   field (its <label> wrap IS the input's hit area), country + gender, the rank chip (a transparent
   ::after hit-expander so the compact pill keeps its look), and the daily-challenge button; the menu +
   mode buttons are already >= 44. Space is reclaimed ONLY from decorative / non-interactive chrome (the
   roaming dice scene is hidden; the title band + spacing are trimmed). 25-language labels + RTL are
   untouched, and the remainder-row board above simply absorbs the reclaimed space. */
/* Sub-360px phones (SE-class at Display Zoom, old small Androids): let the identity row WRAP.
   The wrappers already shrink correctly (min-width: 0), but the controls inside them keep a 44px/56px
   min-width and overflow: visible, so below ~360px they spilled OUT of their shrunk wrappers and collided
   with the stats chip and each other — measured at 320px: six real overlaps, the flag button spilling 16px
   and the gender button 21px. Verified clean at 360px and above, so this is deliberately scoped to 359px
   and no wider.
   Wrapping rather than shrinking: capping the controls to their wrapper also removes the overlap, but it
   drops them to 33x33 and 27x27 — under the tap minimum. Wrapping keeps them at 43x43 / 54x35 and costs
   ~39px of card height, which is the better trade on the one screen size where they cannot all fit. */
@media (max-width: 359px) {
  .profile-identity-row {
    flex-wrap: wrap;
    row-gap: 4px;
  }
}

@media (orientation: portrait) and (max-height: 473px) {
  /* Board sizing switches to the near-square-Fold REMAINDER-ROW pattern here: the board row is the
     remainder (minmax(236px,1fr)) and the board is the largest square fitting BOTH the row height and the
     width (height drives, aspect-ratio derives width -> never distorts), so it ABSORBS the compacted
     chrome (total = viewport, robust to 25-language chrome variance) while staying floored at 236px on
     the TRACK (AI 19% >= 44dp). The 560px tier above keeps its width-cap sizing so 320x480 is unchanged. */
  .home-screen {
    grid-template-rows: auto auto auto minmax(236px, 1fr);
    align-content: stretch;
    gap: 2px;
    padding-top: 4px;
    padding-bottom: 4px;
  }
  .home-board {
    width: auto;
    height: min(100%, calc(min(100vw, 945px) - 2 * clamp(8px, 2.8vw, 16px)));
    aspect-ratio: 1 / 1;
    min-width: 0;
    margin: 0 auto;
    justify-self: center;
    align-self: center;
  }
  .profile-card { min-height: 0; gap: 6px; padding: 2px 8px; }
  .profile-fields { gap: 3px; }
  .profile-avatar-button { width: 44px; height: 44px; }
  .profile-input-wrap { min-height: 44px; }
  .profile-input-wrap input,
  .opponent-input-wrap input { padding: 6px 8px; }
  .profile-identity-row { gap: 6px; }
  .country-flag-button { min-width: 44px; height: 44px; }
  .gender-button { min-width: 52px; height: 44px; }
  .gender-current-icon { width: 24px; height: 24px; font-size: 16px; }
  /* rank chip: keep the compact pill but expand its clickable area to a full 44x44 (overflows only into
     the adjacent NON-interactive win/streak pills + card padding, so it steals no other control's taps) */
  .stat-rank { position: relative; overflow: visible; }
  .stat-rank::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
  }
  #homeLangButton, .home-menu { width: 46px; height: 46px; } /* HOME pair stays even (ID beats the base var(--top-ctrl) rule); in-game pair sizes via --top-ctrl */
  .daily-challenge { min-height: 44px; padding: 5px 9px; }
  .home-hero { min-height: 24px; }
  .home-hero h1 { font-size: clamp(19px, 6vw, 26px); }
  .home-dice-scene { display: none; }
}

@media (min-width: 720px) and (max-height: 1000px) {
  .bottom-hud {
    grid-template-columns: clamp(56px, calc(8 * var(--dvh)), 82px) minmax(0, 1fr) clamp(86px, calc(12 * var(--dvh)), 104px) minmax(0, 1fr) clamp(56px, calc(8 * var(--dvh)), 82px);
  }

  .hud-token {
    width: clamp(54px, calc(8 * var(--dvh)), 76px);
  }

  .dice-button {
    width: clamp(82px, calc(11 * var(--dvh)), 94px);
    height: clamp(82px, calc(11 * var(--dvh)), 94px);
  }

  .hud-name {
    font-size: clamp(21px, calc(4.4 * var(--dvh)), 36px);
  }
}

@media (max-width: 380px) {
  .drawer {
    left: 12px;
    right: 12px;
    width: auto;
  }
}

@media (hover: hover) and (max-width: 380px) {
  .no-ads-badge:hover {
    transform: scale(1.018);
  }
}

.phone-stage.reduced-motion *,
.phone-stage.reduced-motion::before,
.phone-stage.reduced-motion::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

#statusChip {
  display: none !important;
}

@keyframes gender-shine {
  0%, 54% { transform: translateX(-62%) rotate(8deg); opacity: 0; }
  68% { opacity: 1; }
  88%, 100% { transform: translateX(62%) rotate(8deg); opacity: 0; }
}

@keyframes gender-icon-bob {
  0%, 100% { transform: translateY(0) scale(1); }
  50% { transform: translateY(-2px) scale(1.05); }
}

@keyframes gender-pop-rise {
  from { opacity: 0; transform: translateY(18px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes gender-ring-sweep {
  to { transform: rotate(1turn); }
}

@keyframes home-enter {
  from { opacity: 0; transform: translateY(14px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes home-glow {
  0%, 100% { opacity: 0.72; transform: scale(0.96); }
  50% { opacity: 1; transform: scale(1.06); }
}

@keyframes crown-float {
  0%, 100% { transform: translateY(0) rotate(-2deg); }
  50% { transform: translateY(-5px) rotate(2deg); }
}

@keyframes home-piece-bob {
  0%, 100% { transform: translateX(-50%) translateY(0) rotate(-5deg); }
  50% { transform: translateX(-50%) translateY(-7px) rotate(4deg); }
}

@keyframes home-dice-pop {
  0%, 100% { transform: translateX(-50%) translateY(0) rotate(-8deg); }
  50% { transform: translateX(-50%) translateY(-8px) rotate(8deg); }
}

@keyframes mode-card-shine {
  0%, 58% { transform: translateX(-116%); }
  78%, 100% { transform: translateX(116%); }
}

@keyframes start-button-breathe {
  0%, 100% { filter: brightness(1); transform: translateY(0); }
  50% { filter: brightness(1.08); transform: translateY(-2px); }
}

@keyframes wallpaper-drift {
  from { background-position: 0 0; }
  to { background-position: 260px 260px; }
}

@keyframes ad-shine {
  0%, 58% { transform: translateX(-115%); }
  76%, 100% { transform: translateX(115%); }
}

@keyframes cta-enter {
  from { opacity: 0; transform: translateX(-50%) translateY(-20px) scale(0.92); }
  to { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}

@keyframes cta-enter-compact {
  from { opacity: 0; transform: translateY(-20px) scale(0.92); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes cta-breathe {
  0%, 100% {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.28),
      inset 0 -2px 0 rgba(0, 0, 0, 0.24),
      0 0 0 2px rgba(26, 66, 135, 0.5),
      0 10px 24px rgba(0, 0, 0, 0.4),
      0 0 22px rgba(255, 211, 75, 0.26);
  }
  50% {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.34),
      inset 0 -2px 0 rgba(0, 0, 0, 0.2),
      0 0 0 2px rgba(255, 219, 83, 0.56),
      0 12px 28px rgba(0, 0, 0, 0.42),
      0 0 34px rgba(255, 211, 75, 0.44);
  }
}

@keyframes cta-shine {
  0%, 48% { transform: translateX(-118%); }
  70%, 100% { transform: translateX(118%); }
}

@keyframes board-rise {
  from { opacity: 0; transform: translateX(-50%) translateY(28px) scale(0.965); }
  to { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}

@keyframes board-hit {
  0%, 100% { transform: translateX(-50%) translateY(0) scale(1); }
  28% { transform: translateX(-50%) translateY(3px) scale(0.994); }
  58% { transform: translateX(-50%) translateY(-2px) scale(1.004); }
}

@keyframes menu-orbit {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-3px) rotate(1.5deg); }
}

@keyframes badge-count {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.16); }
}

@keyframes hud-rise {
  from { opacity: 0; transform: translateX(-50%) translateY(34px); }
  to { opacity: 1; transform: translateX(-50%) translateY(0); }
}

@keyframes hud-name-scroll {
  0%, 18% { transform: translateX(0); }
  54%, 72% { transform: translateX(calc(-1 * var(--hud-name-travel, 0px))); }
  100% { transform: translateX(0); }
}

@keyframes dice-ready {
  0%, 100% { filter: brightness(1); transform: translateY(0); }
  50% { filter: brightness(1.08); transform: translateY(-3px); }
}

@keyframes computer-dice-wait {
  0%, 100% { transform: translateY(0) scale(0.98); filter: saturate(0.72) brightness(0.9); }
  50% { transform: translateY(-2px) scale(1.01); filter: saturate(0.9) brightness(1.02); }
}

@keyframes turn-badge-pulse {
  0%, 100% { filter: brightness(1); }
  50% { filter: brightness(1.18); }
}

@keyframes active-token-pulse {
  0%, 100% { transform: translateY(-6px) scale(1.06); }
  50% { transform: translateY(-9px) scale(1.1); }
}

@keyframes token-hit-tech-pulse {
  0%, 100% {
    box-shadow:
      0 0 0 5px rgba(var(--turn-rgb, 116, 37, 255), 0.18),
      0 0 18px rgba(var(--turn-rgb, 205, 91, 255), 0.5),
      inset 0 0 16px rgba(255, 255, 255, 0.14);
  }
  50% {
    box-shadow:
      0 0 0 9px rgba(var(--turn-rgb, 132, 48, 255), 0.12),
      0 0 28px rgba(var(--turn-rgb, 224, 111, 255), 0.76),
      inset 0 0 24px rgba(255, 255, 255, 0.22);
  }
}

@keyframes flag-soft-wave {
  0%, 100% { transform: rotate(-2deg) skewX(-2deg) translateY(0) scale(1); }
  50% { transform: rotate(3deg) skewX(3deg) translateY(-1px) scale(1.05); }
}

@keyframes flag-selected-pop {
  0%, 100% { transform: translateY(0) scale(1); }
  50% { transform: translateY(-2px) scale(1.08); }
}

@keyframes flag-badge-wave {
  0%, 100% { transform: rotate(-3deg) translateY(0) scale(1); }
  50% { transform: rotate(3deg) translateY(-1px) scale(1.05); }
}

.phone-stage.reduced-motion .country-flag-current,
.phone-stage.reduced-motion .country-flag-option.is-selected,
.phone-stage.reduced-motion .gender-button::before,
.phone-stage.reduced-motion .gender-current::after,
.phone-stage.reduced-motion .gender-current-icon,
.phone-stage.reduced-motion .gender-card::after,
.phone-stage.reduced-motion .gender-card-symbol,
.phone-stage.reduced-motion .seat-flag {
  animation: none;
}

@keyframes status-scan {
  0% { transform: translateX(-112%); }
  100% { transform: translateX(112%); }
}

@keyframes dice-button-kick {
  0% { transform: rotate(0deg) scale(1); }
  42% { transform: translateY(-5px) rotate(-8deg) scale(1.06); }
  100% { transform: translateY(0) rotate(0deg) scale(1); }
}

@keyframes dice-land {
  0% { transform: scale(1.12); }
  55% { transform: scale(0.94); }
  100% { transform: scale(1); }
}

@keyframes drawer-in {
  from { opacity: 0; transform: translateY(12px) scale(0.96); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes spark-pop {
  from { opacity: 1; transform: translate(-50%, -50%) scale(0.5); }
  to { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(1.55); }
}

/* Knockout / One-Token: a captured token is gone FOR GOOD — a 💀 pops at the cut spot (clearly
   distinct from a normal send-home) over a dark ash burst. Reduce-motion → a static skull only. */
.knockout-mark {
  position: absolute;
  transform: translate(-50%, -50%);
  font-size: clamp(26px, 7.2vw, 42px);
  line-height: 1;
  pointer-events: none;
  z-index: 9;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.6));
  animation: knockout-mark-pop 780ms cubic-bezier(0.2, 0.9, 0.3, 1.35) forwards;
}
@keyframes knockout-mark-pop {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.3) rotate(-14deg); }
  28%  { opacity: 1; transform: translate(-50%, -66%) scale(1.32) rotate(7deg); }
  55%  { opacity: 1; transform: translate(-50%, -74%) scale(1.04) rotate(-3deg); }
  100% { opacity: 0; transform: translate(-50%, -104%) scale(1) rotate(0); }
}
.knockout-mark.is-static { animation: knockout-mark-static 950ms ease-out forwards; }
@keyframes knockout-mark-static {
  0% { opacity: 0; } 16% { opacity: 1; } 78% { opacity: 1; } 100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .knockout-mark { animation: knockout-mark-static 950ms ease-out forwards; }
}

@keyframes celebration-glow {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0.3); }
  42% { opacity: 1; transform: translate(-50%, -50%) scale(1.04); }
  100% { opacity: 0.9; transform: translate(-50%, -50%) scale(1); }
}

@keyframes celebration-ring {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0.22); }
  54% { opacity: 1; transform: translate(-50%, -50%) scale(1.08); }
  100% { opacity: 0.55; transform: translate(-50%, -50%) scale(1.38); }
}

@keyframes celebration-title {
  0% { opacity: 0; transform: translateX(-50%) translateY(28px) scale(0.58) rotate(-4deg); }
  58% { opacity: 1; transform: translateX(-50%) translateY(-8px) scale(1.08) rotate(1deg); }
  100% { opacity: 1; transform: translateX(-50%) translateY(0) scale(1) rotate(0deg); }
}

@keyframes celebration-subtitle {
  from { opacity: 0; transform: translateX(-50%) translateY(18px) scale(0.9); }
  to { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}

@keyframes celebration-dim-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* ===== Preset quick-chat (Online + Friends) — button + tray + seat bubbles ===== */
.chat-dock { position: absolute; left: 50%; transform: translateX(-50%); bottom: 9px; z-index: 40; display: flex; flex-direction: column; align-items: center; gap: 8px; }
.chat-dock[hidden] { display: none; }
.chat-toggle { width: 46px; height: 46px; border: none; padding: 0; cursor: pointer; display: flex; align-items: center; justify-content: center; background: transparent; -webkit-tap-highlight-color: transparent; filter: drop-shadow(0 1px 3px rgba(0,0,0,.5)); transition: transform .12s ease; }
.chat-toggle svg { width: 32px; height: 32px; display: block; }
.chat-toggle:active { transform: scale(.94); }
.chat-tray { width: min(248px, 78vw); max-height: 48vh; overflow-y: auto; background: #ffffff; border: 1px solid #ece6d4; border-radius: 16px; padding: 10px; box-shadow: 0 12px 32px rgba(0,0,0,.28); -webkit-overflow-scrolling: touch; }
.chat-tray[hidden] { display: none; }
.chat-tabs { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
.chat-tab { border: none; cursor: pointer; font-size: 12px; font-weight: 700; padding: 5px 12px; border-radius: 999px; background: transparent; color: #7a8a82; }
.chat-tab.is-on { background: #10372e; color: #fff8e6; }
.chat-mute { margin-left: auto; border: none; background: transparent; cursor: pointer; font-size: 18px; line-height: 1; padding: 2px; }
.chat-chips { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.chat-chips.emoji-grid { grid-template-columns: repeat(4, 1fr); }
/* Quick-chat is the ONLY player-to-player channel in the game, so a preset that reads as "நல்லா விளை…" is a
   feature the player cannot use. nowrap + ellipsis clipped Tamil at 320px by 16px (measured against the
   real chip: 125px of text space). Wrapping to two lines costs ~12px of chip height and, because
   .chat-chips is a 1fr 1fr grid, both chips in a row grow together so nothing goes ragged.
   word-break BEFORE overflow-wrap on purpose: overflow-wrap:anywhere landed in Chromium 80 itself,
   which is exactly the floor, so the older declaration is the one that must carry an old WebView. */
.chat-chip { background: #f3f7f3; border: 1px solid #e2ebe4; border-radius: 10px; padding: 8px; font-size: 12.5px; font-weight: 600; color: #1d4b3c; text-align: center; cursor: pointer; white-space: normal; line-height: 1.25; min-height: 34px; display: grid; place-items: center; word-break: break-word; overflow-wrap: anywhere; }
.chat-chip:active { background: #e6f0e8; }
.chat-emoji { background: #f3f7f3; border: 1px solid #e2ebe4; border-radius: 10px; padding: 7px 0; font-size: 22px; line-height: 1; cursor: pointer; }
.chat-emoji:active { background: #e6f0e8; }
/* a sent message floats over the SENDER's own seat pod (research: own side, never over the opponent) */
/* Receiver bubble: same reasoning. 150px left 130px of text and clipped Tamil by 11px; 190px plus two
   lines clears every shipped preset. The ::after arrow is positioned from the bubble's own bottom, so
   it follows a taller bubble without adjustment. */
.seat-chat-bubble { position: absolute; left: 50%; bottom: calc(100% + 6px); transform: translateX(-50%); background: #fff; color: #10372e; border: 1px solid #e3decb; border-radius: 13px; padding: 5px 10px; font-size: 12.5px; font-weight: 600; white-space: normal; line-height: 1.3; text-align: center; box-shadow: 0 4px 14px rgba(0,0,0,.22); z-index: 60; pointer-events: none; max-width: 190px; word-break: break-word; overflow-wrap: anywhere; }
.seat-chat-bubble::after { content: ""; position: absolute; left: 50%; bottom: -6px; width: 10px; height: 10px; background: #fff; border-right: 1px solid #e3decb; border-bottom: 1px solid #e3decb; transform: translateX(-50%) rotate(45deg); }
.seat-chat-bubble.emoji-only { font-size: 20px; padding: 4px 9px; }
.seat-chat-bubble:not(.no-anim) { animation: chat-pop .22s cubic-bezier(.2,1.2,.4,1) both; }
.seat-chat-bubble.leaving { opacity: 0; transform: translateX(-50%) translateY(-4px); transition: opacity .28s ease, transform .28s ease; }
@keyframes chat-pop { from { opacity: 0; transform: translateX(-50%) translateY(6px) scale(.8); } to { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); } }

@keyframes celebration-trophy-pop {
  0% { opacity: 0; transform: translateX(-50%) translateY(44px) scale(0.3) rotate(-14deg); }
  60% { opacity: 1; transform: translateX(-50%) translateY(-12px) scale(1.12) rotate(5deg); }
  100% { opacity: 1; transform: translateX(-50%) translateY(0) scale(1) rotate(0deg); }
}

@keyframes celebration-trophy-float {
  0%, 100% { transform: translateX(-50%) translateY(0) scale(1); }
  50% { transform: translateX(-50%) translateY(-8px) scale(1.02); }
}

@keyframes firework-flash {
  0% { opacity: 0.95; transform: translate(-50%, -50%) scale(0.3); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.55); }
}

@keyframes firework-spark {
  0% { opacity: 1; transform: translate(-50%, -50%) translate3d(0, 0, 0) scale(1); }
  70% { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -50%) translate3d(var(--fx), var(--fy), 0) scale(0.45); }
}

@keyframes celebration-fall {
  0% {
    opacity: 0;
    transform: translate3d(0, 0, 0) rotate(0deg) scale(0.86);
  }
  8% { opacity: 1; }
  100% {
    opacity: 0.86;
    transform: translate3d(var(--drift), calc(118 * var(--dvh)), 0) rotate(var(--rotate)) scale(1);
  }
}

@keyframes board-win-pop {
  0% { transform: translateX(-50%) scale(1); filter: brightness(1); }
  28% { transform: translateX(-50%) scale(1.018); filter: brightness(1.18) saturate(1.1); }
  58% { transform: translateX(-50%) scale(0.995); filter: brightness(1.06); }
  100% { transform: translateX(-50%) scale(1); filter: brightness(1); }
}

@keyframes board-lose-settle {
  0% { filter: saturate(1) brightness(1); }
  45% { filter: saturate(0.8) brightness(0.88); }
  100% { filter: saturate(1) brightness(1); }
}

@keyframes dice-impact-ring {
  0% { opacity: 0.9; transform: translate(-50%, -50%) scale(0.42); }
  72% { opacity: 0.34; }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.55); }
}

@keyframes dice-shadow-pulse {
  0% { opacity: 0.18; transform: translate(-50%, -50%) rotateX(58deg) scale(0.56); }
  34% { opacity: 0.08; transform: translate(-50%, -50%) rotateX(58deg) scale(0.42); }
  64% { opacity: 0.48; transform: translate(-50%, -50%) rotateX(58deg) scale(1.18); }
  78% { opacity: 0.24; transform: translate(-50%, -50%) rotateX(58deg) scale(0.72); }
  100% { opacity: 0.42; transform: translate(-50%, -50%) rotateX(58deg) scale(0.96); }
}

@keyframes token-pop {
  0% { opacity: 0.9; transform: translateY(10px) scale(0.86); }
  70% { opacity: 1; transform: translateY(-5px) scale(1.08); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}

/* ============================================================================
   RTL (Arabic / Urdu). The document flows right-to-left so menus, dialogs,
   settings, chat and text alignment flip naturally (direction + logical
   properties). The GAME BOARD is force-LTR so the board grid, token paths,
   dice, directional arrows and the #ludoBoard canvas geometry are IDENTICAL in
   LTR and RTL — gameplay NEVER mirrors. Player names already use
   unicode-bidi:plaintext; room codes / #tags are LTR-isolated so mixed
   Arabic/Latin/number runs keep their correct visual order.
   ============================================================================ */
html[dir="rtl"] { direction: rtl; }

/* Trailing-corner pins flip to the start side in RTL. (These used inset-inline-end, which is
   Chromium 87+ and silently UNPINNED the badges/close buttons on older WebViews — physical
   right + this explicit flip behaves identically everywhere.) */
html[dir="rtl"] .token-style-lock,
html[dir="rtl"] .dice-style-lock { right: auto; left: 4px; }
html[dir="rtl"] .acct-close,
html[dir="rtl"] .fr-close { right: auto; left: 8px; }

/* R2-033: Home-only drawer flip. The home ☰ trigger sits in the .home-topbar grid (justify-self:end =
   inline-end = physical LEFT under direction:rtl), so the home drawer must follow it to the left. The
   IN-GAME #menuButton stays physical right (no RTL override), so the in-game drawer (same #drawer) must
   NOT flip — hence this is scoped to .is-home only. Mirrors the base .drawer right clamp. */
html[dir="rtl"] .phone-stage.is-home .drawer { right: auto; left: clamp(14px, 3.1vw, 32px); }

/* The gameplay region must NOT mirror — force it LTR (board uses physical left/top + a geometric canvas;
   this also isolates it from any flex/inline/logical flipping). */
html[dir="rtl"] .board-frame,
html[dir="rtl"] .seat-pods,
html[dir="rtl"] .seat-pod,
html[dir="rtl"] .token-hit-layer,
html[dir="rtl"] .spark-layer,
html[dir="rtl"] .dice-flight-layer,
html[dir="rtl"] #ludoBoard,
html[dir="rtl"] #wallpaperCanvas,
html[dir="rtl"] #immersiveBgCanvas,
html[dir="rtl"] #sceneBackdrop { direction: ltr; }
/* The wordmark is a BRAND NAME and must never mirror. It is built from individual <i> letters inside
   inline-flex .title-word spans, so an RTL document reversed the flex line and the logo rendered
   "oduLiAlaeR" — verified in the browser at dir="rtl", not assumed. Forcing ltr on both the h1 (word order)
   and each .title-word (letter order) restores it. The boot splash is included as a guard only: it paints
   before app.js sets dir, so it is not reversed today, but it renders the same letter-span structure and
   would break the moment the direction were applied any earlier. */
html[dir="rtl"] .home-hero h1,
html[dir="rtl"] .home-hero h1 .title-word,
html[dir="rtl"] #appBoot .boot-name { direction: ltr; }

/* ...but a seat chat bubble's TEXT follows the player's selected language (RTL for ar/ur); its centered
   position stays geometric. */
html[dir="rtl"] .seat-chat-bubble { direction: rtl; unicode-bidi: isolate; }

/* Physical left/right that must follow the reading direction in menus / dialogs / leaderboard. */
html[dir="rtl"] .setup-row-result .setup-label,
html[dir="rtl"] .policy-body { text-align: right; }
html[dir="rtl"] .lb-me-rank { margin-left: 0; margin-right: auto; text-align: left; }
html[dir="rtl"] .lb-report { margin-left: 0; margin-right: 8px; }
html[dir="rtl"] .pro-benefits li { padding-left: 0; padding-right: 22px; }
html[dir="rtl"] .pro-benefits li::before { left: auto; right: 0; }

/* Keep ASCII room codes / #tags / digits in left-to-right order inside RTL text (CSS equivalent of <bdi>). */
html[dir="rtl"] .online-code,
html[dir="rtl"] .lb-tag { direction: ltr; unicode-bidi: isolate; }

/* ===== Provably-fair BADGE + PANEL (surfaces the HMAC commit-reveal; DO Online + Friends only) =====
   Chromium-80 floor: physical inset longhands (no bare inset), no aspect-ratio, no is/where selector lists, no clipped overflow;
   the always-visible badge uses margin (not flex gap); grid gap (66) + flex gap in .fair-card (84, mirrors
   the shipped .mode-config-card) degrade to tighter spacing on Chromium 80-83, never a break. */
.fair-badge { display: none; }
.phone-stage.is-home .fair-badge { display: none; }
.phone-stage.is-do-fair .fair-badge {
  display: inline-flex;
  align-items: center;
  position: absolute;
  top: 3px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  height: clamp(22px, 6vw, 28px);
  padding-left: clamp(7px, 2.4vw, 11px);
  padding-right: clamp(7px, 2.4vw, 11px);
  border-radius: 999px;
  border: 1px solid rgba(255, 222, 110, 0.5);
  background: linear-gradient(135deg, rgba(23, 99, 78, 0.96), rgba(7, 42, 33, 0.96));
  color: #ffe9a8;
  font: 800 clamp(10px, 2.7vw, 12px)/1 var(--font-ui);
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.fair-badge-icon { margin-right: 4px; font: 400 clamp(11px, 3vw, 13px)/1 var(--font-emoji); }
[dir="rtl"] .fair-badge-icon { margin-right: 0; margin-left: 4px; }
.fair-badge::before { content: ""; position: absolute; top: -11px; right: -11px; bottom: -11px; left: -11px; border-radius: 999px; }
.fair-badge[data-state="ok"] { border-color: #46d17f; color: #9af0bd; }
.fair-badge[data-state="fail"] { border-color: #ff8a8a; color: #ffc7c7; }
@media (prefers-reduced-motion: no-preference) {
  .phone-stage.is-do-fair .fair-badge[data-state="sealed"] { animation: fair-pulse 2.4s ease-in-out infinite; }
}
@keyframes fair-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.74; } }

.fair-panel { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 16; display: grid; place-items: stretch center; background: rgba(2, 14, 10, 0.62); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); }
.fair-panel[hidden] { display: none; }
.fair-card { position: relative; width: 100%; max-width: 520px; height: 100%; overflow-y: auto; overscroll-behavior: contain; display: flex; flex-direction: column; gap: clamp(10px, 1.8vh, 16px); padding: clamp(16px, 3vh, 24px) clamp(16px, 4.6vw, 26px); background: radial-gradient(circle at 86% -8%, rgba(255, 219, 108, 0.16), transparent 42%), linear-gradient(168deg, rgba(16, 92, 70, 0.98), rgba(6, 34, 26, 0.99)); color: #f4fff8; text-align: center; animation: home-rise 320ms cubic-bezier(0.18, 0.9, 0.26, 1.06) both; }
.fair-close { position: absolute; top: 10px; right: 12px; width: 34px; height: 34px; border-radius: 50%; background: var(--surface-2); border: 1px solid var(--hairline); color: var(--ink-2); font: 700 22px/1 var(--font-ui); }
[dir="rtl"] .fair-close { right: auto; left: 12px; }
.fair-hero { font-size: clamp(38px, 12vw, 58px); line-height: 1; }
.fair-lead { font: 700 clamp(15px, 4vw, 18px)/1.4 var(--font-ui); margin: 0; }
.fair-steps { list-style: none; margin: 0; padding: 0; display: grid; gap: clamp(8px, 1.6vh, 12px); text-align: start; }
.fair-steps li { display: grid; grid-template-columns: 26px minmax(0, 1fr); gap: 10px; align-items: center; font: 600 clamp(13px, 3.6vw, 15px)/1.35 var(--font-ui); }
.fair-step-ic { font: 400 20px/1 var(--font-emoji); text-align: center; }
.fair-status { margin: 0; font: 800 clamp(14px, 3.8vw, 16px)/1.4 var(--font-ui); padding: clamp(8px, 1.4vh, 12px); border-radius: 12px; border: 1px solid var(--hairline); }
.fair-status[data-state="ok"] { color: #9af0bd; border-color: #46d17f; }
.fair-status[data-state="fail"] { color: #ffc7c7; border-color: #ff8a8a; }
.fair-offline-note { margin: 0; font: 600 clamp(12px, 3.2vw, 14px)/1.4 var(--font-ui); opacity: 0.85; }
.fair-tech { text-align: start; }
.fair-tech summary { cursor: pointer; font: 700 clamp(13px, 3.6vw, 15px)/1.3 var(--font-ui); padding: 8px 0; }
.fair-hashrow { display: grid; gap: 3px; margin-bottom: 10px; }
.fair-hashrow > span:first-child { font: 700 12px/1.3 var(--font-ui); opacity: 0.8; }
.fair-hash { display: block; max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; white-space: nowrap; font: 600 clamp(11px, 3vw, 13px)/1.4 monospace; padding: 6px 8px; border-radius: 8px; background: rgba(0, 0, 0, 0.28); }
.fair-tech-hint { font: 600 clamp(12px, 3.2vw, 13px)/1.45 var(--font-ui); opacity: 0.85; }
.fair-copy-btn { align-self: flex-start; margin-top: 4px; padding: 8px 16px; border-radius: 999px; border: 1px solid var(--hairline); background: var(--surface-2); color: var(--ink); font: 800 13px/1 var(--font-ui); }
.gameplay-toast.is-tappable { cursor: pointer; }

/* App version footer at the bottom of the menu drawer — small + muted, unobtrusive. */
.drawer .drawer-version { margin: 6px 0 2px; text-align: center; font: 600 11px/1.3 var(--font-ui); color: var(--ink-3); letter-spacing: 0.02em; }
.drawer .drawer-version { font-size: min(calc(11px * var(--ui-scale)), 14px); }
.drawer .drawer-version:empty { display: none; }


/* =========================================================================
   RESPONSIVE — LANDSCAPE + LARGE-SCREEN (tablets, foldables, split-screen)
   -------------------------------------------------------------------------
   REGRESSION INVARIANT: every rule below is gated by a media query a
   portrait PHONE can never match — `orientation: landscape`, or
   `orientation: portrait AND min-width >= 600px`. A phone in portrait
   (<=~430px wide) matches NONE of them, so the signed portrait-phone
   baseline is untouched BY CONSTRUCTION. Placed at end-of-file so these
   coherent overrides win the cascade over the per-mode board-area blocks.
   State preservation on rotate/fold/resize is handled natively (manifest
   configChanges -> no Activity recreation; the WebView persists and only
   the CSS reflows), so no JS is required here.
   ========================================================================= */

/* ---- LANDSCAPE ---------------------------------------------------------
   The stage is a portrait column, so a height-bound board collapses in
   landscape (e.g. 844x390 -> a 67px, unplayable board). Fix: reclaim the
   FULL vertical budget for the board and move the four seat pods from
   above/below the board into its LEFT/RIGHT margins (abundant free
   horizontal space in landscape). The board becomes large + centred. ---- */
@media (orientation: landscape) {
  .phone-stage[data-game-mode]:not(.call-docked):not(.is-home) {
    /* In landscape the SHORT dimension is height, so size pods to vh (>=44px
       keeps the dice a comfortable touch target). */
    --corner-dice-size: clamp(44px, 11vh, 60px);
    --seat-avatar-size: clamp(38px, 10vh, 56px);
    --seat-pod-gap: clamp(6px, 1.4vh, 12px);
    /* Horizontal room reserved OUTSIDE each vertical board edge for a side
       pod (avatar hugging the board + its dice at the outer edge). */
    --ls-side-reserve: calc(var(--seat-avatar-size) + var(--corner-dice-size) + clamp(16px, 4vw, 34px));
    /* Top band = notch/banner + the top control row (Exit | Get Pro | Menu
       all live here). The `+ clamp(10..3vh..20)` gap is >= the top row's own
       gap, so the board always clears the row. Bottom band is just a small
       safe margin: in-game the HUD is display:none, the pods are on the sides
       and Exit is at the top, so nothing sits below the board. Cutout-safe
       via env() + --top-ad-y. */
    --ls-top-band: calc(max(var(--top-ad-y), env(safe-area-inset-top, 0px)) + var(--top-ad-h) + var(--top-ctrl) + clamp(10px, 3vh, 20px));
    --ls-bottom-band: calc(env(safe-area-inset-bottom, 0px) + clamp(8px, 2.5vh, 16px));
    /* Board = min(full vertical budget, stage width minus the two side
       columns). Square + centred; never distorted. */
    --board-area: calc(calc(100 * var(--dvh)) - var(--ls-top-band) - var(--ls-bottom-band));
    --board-size: min(var(--board-area), calc(var(--stage-width) - 2 * var(--ls-side-reserve)));
    --board-top-space: calc(var(--ls-top-band) + (var(--board-area) - var(--board-size)) / 2);
    --game-board-shift: 0px;
  }

  /* Top control row (Exit | Get Pro | Menu) hugs the very top so it clears
     the taller centred board (the default 242/2048 ratio would push the row
     down onto the board). The row keeps its normal Exit-left / GetPro-centre
     / Menu-right placement — all three sit in the top band above the board. */
  .phone-stage[data-game-mode]:not(.call-docked):not(.is-home) {
    --top-y: calc(max(var(--top-ad-y), env(safe-area-inset-top, 0px)) + var(--top-ad-h) + clamp(6px, 1.6vh, 10px));
  }

  /* Seat pods -> board SIDES. Left column: red (upper), blue (lower);
     right column: green (upper), yellow (lower). row-reverse on the left
     keeps the avatar hugging the board with the dice at the outer edge.
     Same mapping for the network view-seat variants. */
  .phone-stage[data-game-mode]:not(.call-docked) .seat-pod[data-seat="red"],
  .phone-stage[data-game-mode]:not(.call-docked) .seat-pod[data-view-seat="red"] {
    top: 4%; bottom: auto; left: auto; right: calc(100% + var(--seat-pod-gap)); flex-direction: row-reverse;
  }
  .phone-stage[data-game-mode]:not(.call-docked) .seat-pod[data-seat="blue"],
  .phone-stage[data-game-mode]:not(.call-docked) .seat-pod[data-view-seat="blue"] {
    top: auto; bottom: 4%; left: auto; right: calc(100% + var(--seat-pod-gap)); flex-direction: row-reverse;
  }
  .phone-stage[data-game-mode]:not(.call-docked) .seat-pod[data-seat="green"],
  .phone-stage[data-game-mode]:not(.call-docked) .seat-pod[data-view-seat="green"] {
    top: 4%; bottom: auto; right: auto; left: calc(100% + var(--seat-pod-gap)); flex-direction: row;
  }
  .phone-stage[data-game-mode]:not(.call-docked) .seat-pod[data-seat="yellow"],
  .phone-stage[data-game-mode]:not(.call-docked) .seat-pod[data-view-seat="yellow"] {
    top: auto; bottom: 4%; right: auto; left: calc(100% + var(--seat-pod-gap)); flex-direction: row;
  }

  /* Online roll die (a board-frame child, not inside a pod) -> parked at the
     active side's vertical centre, clear of that side's upper/lower pods. */
  .phone-stage[data-game-mode][data-turn-seat="blue"]:not(.call-docked) .board-frame > .dice-button,
  .phone-stage[data-game-mode][data-turn-seat="red"]:not(.call-docked) .board-frame > .dice-button {
    left: auto; right: calc(100% + var(--seat-pod-gap) + var(--board-frame-border, 0px)); top: calc(50% - var(--corner-dice-size) / 2); bottom: auto;
  }
  .phone-stage[data-game-mode][data-turn-seat="green"]:not(.call-docked) .board-frame > .dice-button,
  .phone-stage[data-game-mode][data-turn-seat="yellow"]:not(.call-docked) .board-frame > .dice-button {
    right: auto; left: calc(100% + var(--seat-pod-gap) + var(--board-frame-border, 0px)); top: calc(50% - var(--corner-dice-size) / 2); bottom: auto;
  }

  /* The preset chat dock is bottom-CENTRE (portrait: below the board). In landscape the board is
     full-height + centred with nothing meant to sit below it, so a bottom-centre toggle overlaps the
     board. Move it into the free bottom-RIGHT margin (right of the centred board + its side pods) and
     right-align its tray so the tray opens upward-left without covering the board centre. Sized for the
     short landscape height. Safe-area aware. */
  .chat-dock {
    left: auto;
    right: max(8px, env(safe-area-inset-right, 0px));
    bottom: max(8px, env(safe-area-inset-bottom, 0px));
    transform: none;
    align-items: flex-end;
  }
  .chat-dock .chat-tray { max-height: 80vh; }
}

/* ---- LANDSCAPE HOME (living-board mode selector) ----------------------
   The home is a 4-row column (topbar / hero / daily-challenge / living
   board). In landscape the square board is width-sized and balloons past
   the viewport height, pushing the mode-selector corners off-screen. Reflow
   to TWO columns: a height-bound square board on the left, the three chrome
   rows stacked on the right (using the free horizontal space). Everything
   stays on-screen and tappable; the board column is capped by BOTH height
   (56vh) and width (50vw) so it never distorts or crowds the right column. */
@media (orientation: landscape) {
  .phone-stage.is-home .home-screen {
    /* The board column is height-bound (56vh) on a short landscape phone (e.g. 640x360 -> 202px), which
       shrinks the 19% centre "AI" mode button to ~38px (< 44dp). Floor the column at 236px so AI stays
       >= ~44.8px and the corner modes >= ~64px, while the 50vw cap still protects the right-hand chrome
       column from being crowded at narrow landscape widths. On common landscape phones (>= 640 wide) the
       50vw cap (>= 320px) never binds, so the board becomes the 236px floor. min()/max() are Chromium-80
       safe; there is ample vertical room (236 < any real landscape height) so nothing overflows. */
    grid-template-columns: min(50vw, max(236px, 56vh)) minmax(0, 1fr);
    grid-template-rows: auto auto 1fr;
    grid-template-areas:
      "board topbar"
      "board hero"
      "board challenge";
    align-content: center;
    column-gap: clamp(10px, 3vw, 28px);
    row-gap: clamp(6px, 1.4vh, 12px);
  }
  .phone-stage.is-home .home-topbar { grid-area: topbar; align-self: start; }
  .phone-stage.is-home .home-hero { grid-area: hero; align-self: center; }
  .phone-stage.is-home .daily-challenge { grid-area: challenge; align-self: end; }
  .phone-stage.is-home .home-board {
    grid-area: board;
    align-self: center;
    justify-self: center;
    width: 100%;        /* fills the height+width capped left column */
    height: auto;
    aspect-ratio: 1 / 1;
    margin: 0;
  }
}

/* ---- LARGE PORTRAIT (near-square foldables / tablets) ------------------
   Tall tablets (e.g. 800x1280) are already width-bound (board ~97%); only
   the NEAR-SQUARE unfolded-foldable band (aspect >= 3/4, height-bound) has
   dead vertical space. Trim the tablet breathing there so the board grows
   a little and the layout looks less sparse. Width-bound tablets are
   unaffected (board stays at the width cap). ---- */
@media (orientation: portrait) and (min-width: 600px) and (min-aspect-ratio: 3/4) {
  .phone-stage[data-game-mode]:not(.call-docked):not(.is-home) {
    --board-breathing: clamp(6px, 1.4vh, 18px);
  }
}

/* Home one-screen gate is WIDER than the in-game 3/4 band: aspect >= 2/3 also
   catches tall splits like 600x900, where the width-sized board column overflows
   by a few px. Still excludes 800x1280-class tall tablets (0.625 — the classic
   centered column fits there and stays untouched) and every portrait phone. */
@media (orientation: portrait) and (min-width: 600px) and (min-aspect-ratio: 2/3) {
  /* ---- NEAR-SQUARE PORTRAIT HOME (fully-open Fold): ONE-SCREEN, no scroll.
     The default home stacks topbar/hero/challenge above a WIDTH-sized square
     living-board (.home-screen > * forces width:100%), so on a near-square
     viewport the column exceeds 100dvh and the board bottom + its two lower
     mode buttons scroll off-screen (P1, seen on a physical Fold). Fix: the
     board row becomes the REMAINDER (minmax(0,1fr)) and the board is the
     largest square that fits BOTH the remaining row height and the available
     width — height drives, aspect-ratio derives width, so it can never
     distort. Nothing is hidden, cropped, transformed or zoomed; hero and
     challenge keep their intrinsic (auto) heights, so long-language labels
     and larger font scales simply shrink the board instead of overflowing.
     The 280px floor keeps every mode button comfortably tappable (corner
     27% => >=75px, centre AI 19% => >=53px, all >=44dp). The floor lives on
     the TRACK (minmax(280px, 1fr)), NOT on the board's height: when the
     chrome rows leave less than 280px (extreme ~200% font scale / tiny
     windows) the GRID grows past the viewport block-END, which
     .home-screen's existing overflow-y:auto reaches by scrolling — while the
     board itself never exceeds its row (height <= 100%), so it can never
     paint UPWARD over the daily-challenge row (an item-level floor +
     align-self:center did exactly that: block-start overflow of a scroll
     container is unreachable — caught in adversarial review). Content is
     never cropped and every control stays reachable. ---- */
  .phone-stage.is-home .home-screen {
    grid-template-rows: auto auto auto minmax(280px, 1fr);
    align-content: stretch;
  }
  .phone-stage.is-home .home-board {
    /* width:auto overrides `.home-screen > * { width:100% }`; with an explicit
       height + aspect-ratio 1/1 the width FOLLOWS the height => strict square.
       calc(...) = the grid column width (stage minus the home-screen x padding),
       so the min() is exactly "remaining row height vs available width". The
       board never exceeds its track (<=100%), so align-self:center can never
       push it over the rows above. */
    width: auto;
    height: min(100%, calc(min(100vw, 945px) - 2 * clamp(8px, 2.8vw, 16px)));
    aspect-ratio: 1 / 1;
    margin: 0;
    justify-self: center;
    align-self: center;
  }
}

/* ===== Phase G: Account + Friends panels ===== */
/* Full-screen overlay dialogs. NOTE (css-hidden gotcha): each display rule needs a matching [hidden] rule. */
.acct-panel, .fr-panel {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0; /* inset longhands (Chromium<87 WebView support) */
  z-index: 80;
  display: grid;
  place-items: center;
  padding: 12px;
  padding-top: max(12px, env(safe-area-inset-top));
  padding-bottom: max(12px, env(safe-area-inset-bottom));
  background: rgba(2, 14, 10, 0.66);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.acct-panel[hidden], .fr-panel[hidden] { display: none; }

.acct-card, .fr-card {
  position: relative;
  width: 100%;
  max-width: 460px;
  max-height: 100%;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  box-sizing: border-box;
  padding: 20px 18px calc(18px + env(safe-area-inset-bottom));
  border-radius: 18px;
  background: #10352a;
  color: #eafaf1;
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.45);
}
.acct-card h2, .fr-card h2 { margin: 0 0 12px; font-size: 20px; }

.acct-close, .fr-close {
  position: absolute;
  top: 8px;
  right: 8px; /* physical + RTL flip below (inset-inline-end is Chromium 87+) */
  min-width: 44px;
  min-height: 44px;
  border: 0;
  background: transparent;
  color: #cfeee0;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  border-radius: 12px;
}
.acct-close:focus-visible, .fr-close:focus-visible,
.fr-tab:focus-visible, .fr-chip:focus-visible,
.acct-primary:focus-visible, .acct-secondary:focus-visible,
.fr-list button:focus-visible {
  outline: 3px solid #7fe3b0;
  outline-offset: 2px;
}

/* Account */
.acct-h { font-weight: 700; margin: 6px 0 4px; }
.acct-b, .acct-benefit { margin: 4px 0; font-size: 14px; color: #bfe6d3; line-height: 1.4; }
.acct-benefit { font-size: 13px; color: #8fd3ae; }
.acct-warn { margin: 8px 0; font-size: 13px; color: #ffd28a; line-height: 1.4; }
.acct-msg { margin: 10px 0 0; font-size: 14px; color: #ffd28a; }
.acct-msg[hidden] { display: none; }
.acct-state[hidden], .acct-collision[hidden] { display: none; }
.acct-primary, .acct-secondary, .acct-cancel {
  display: block;
  width: 100%;
  min-height: 48px;
  margin-top: 10px;
  border-radius: 12px;
  border: 0;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}
/* The [hidden] GUARD. `.acct-primary/.acct-secondary` set `display: block`, and an author rule beats the UA
   stylesheet's `[hidden] { display: none }` — so a button marked `hidden` in the markup renders anyway. That
   is how the Apple sign-in controls, which are hidden by default and revealed only by the isIosShell() gate,
   would have shipped VISIBLE on Android, where the appleLink op hits `default: break` and does nothing.
   This file already carries 57 such per-class guards; these two were the ones missing. */
.acct-primary[hidden], .acct-secondary[hidden], .acct-cancel[hidden] { display: none; }
.acct-primary { background: #2ec77e; color: #06251a; }
/* Sign in with Apple. Apple's Human Interface Guidelines allow black, white, or white-with-outline only, and
   the button must not be restyled into the app's own palette — this panel is dark, so black-on-dark would
   disappear and white is the correct one of the three. It sits ABOVE the green Google button, so the two do
   not read as a single control. Deliberately not the panel's accent colour: a recognisable Apple button is
   part of what Guideline 4.8 asks for. */
.acct-apple { background: #ffffff; color: #000000; }
.acct-secondary { background: rgba(255, 255, 255, 0.1); color: #eafaf1; }
.acct-cancel { background: transparent; color: #bfe6d3; min-height: 44px; font-weight: 500; }
.acct-collision { margin-top: 12px; border-top: 1px solid rgba(255, 255, 255, 0.12); padding-top: 12px; }

/* Friends */
.fr-tabs { display: flex; gap: 6px; margin: 4px 0 12px; }
.fr-tab {
  flex: 1 1 0;
  min-height: 44px;
  border: 0;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.08);
  color: #cfeee0;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.fr-tab.is-active { background: #2ec77e; color: #06251a; }
.fr-tabpane[hidden] { display: none; }
.fr-msg { margin: 0 0 10px; font-size: 14px; color: #ffd28a; }
.fr-msg[hidden] { display: none; }
.fr-sub { margin: 12px 0 6px; font-size: 13px; color: #8fd3ae; font-weight: 600; }
.fr-list { list-style: none; margin: 0; padding: 0; }
.fr-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 4px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  min-height: 48px;
}
.fr-list .fr-name { flex: 1 1 auto; font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fr-list button {
  min-height: 44px;
  min-width: 44px;
  padding: 0 10px;
  border: 0;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.12);
  color: #eafaf1;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.fr-list button.fr-accept { background: #2ec77e; color: #06251a; }
.fr-list button.fr-danger { background: rgba(255, 120, 120, 0.2); color: #ffc9c9; }
.fr-empty[hidden] { display: none; }
.fr-empty { margin: 16px 4px; font-size: 14px; color: #9fc9b5; text-align: center; }

.fr-mycode { padding: 10px; border-radius: 12px; background: rgba(255, 255, 255, 0.06); margin-bottom: 14px; }
.fr-mycode-label { display: block; font-size: 12px; color: #8fd3ae; }
.fr-mycode-val { display: block; font-size: 26px; font-weight: 800; letter-spacing: 3px; margin: 4px 0 10px; unicode-bidi: isolate; }
.fr-mycode-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.fr-chip {
  min-height: 44px;
  min-width: 44px; /* a11y: comfortable touch target on every chip */
  flex-shrink: 0;  /* never shrink below the min in the flex rows */
  padding: 0 12px;
  border: 0;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.12);
  color: #eafaf1;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.fr-qr { display: block; margin: 12px auto 0; background: #fff; border-radius: 10px; padding: 8px; }
.fr-qr[hidden] { display: none; }
.fr-add-label { display: block; font-size: 13px; color: #8fd3ae; margin-bottom: 6px; }
.fr-add-row { display: flex; gap: 6px; }
.fr-add-row input {
  flex: 1 1 auto;
  min-height: 48px;
  box-sizing: border-box;
  padding: 0 12px;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: rgba(0, 0, 0, 0.2);
  color: #eafaf1;
  font-size: 16px; /* >=16px avoids iOS/Android zoom-on-focus */
  letter-spacing: 2px;
  text-transform: uppercase;
}
.fr-find { min-width: 64px; }
.fr-add-result { margin-top: 10px; font-size: 14px; }
.fr-add-result .fr-name { font-weight: 700; }

/* RTL: text flips (html[dir="rtl"]); the codes/inputs stay LTR via dir="ltr" in markup. */
html[dir="rtl"] .acct-card, html[dir="rtl"] .fr-card { text-align: right; }
html[dir="rtl"] .fr-list li, html[dir="rtl"] .fr-add-row, html[dir="rtl"] .fr-mycode-actions { flex-direction: row-reverse; }
/* Short screens (320x480 / split-screen): the card is overflow-y:auto so it scrolls within the viewport — no
   portrait-affecting @media here (the responsive-guard keeps the portrait baseline untouched). */
