/* DeNiro Card — vibrant card-room theme.

   The governing rule, and the reason this reads as "poppy" without becoming a
   toy: vibrancy costs nothing, LIGHTNESS costs everything. Held at luminance
   ≤0.178 a ground can carry near-maximum chroma and still keep white text above
   4.5:1. So the pop comes from saturated hues on capped-luminance grounds,
   bright play pieces, and near-black / pure-white ink — never from pastels on
   large areas, which is what makes an app look cheap AND hurts to read.

   Every legacy name below is kept as an alias: there are 41 live var(--gold),
   18 var(--muted) and 14 var(--line) references, and renaming them would be a
   large diff with no user-visible benefit. The --dn-* tokens are the source of
   truth; the short names point at them. */
:root{
  color-scheme: dark;

  /* ---- chrome ---- */
  --dn-ink-900:#0b0c12;  --dn-ink-800:#12141d;  --dn-ink-700:#191d29;
  --dn-ink-600:#2b3244;  --dn-fg:#f7f8fc;       --dn-fg-dim:#b6c0d4;
  --dn-scrim:rgba(6,10,16,.62);
  --dn-plate:#fffdf6;

  /* ---- accent ---- */
  --dn-gold:#f2c14e;  --dn-gold-hi:#ffdc7a;  --dn-gold-deep:#c28f22;
  --dn-gold-ink:#2a1c02;
  /* The gold as raw channels, so the 211 hand-written rgba() literals across 16
     files share one definition instead of each holding its own copy. */
  --dn-gold-rgb:242,193,78;

  /* ---- card: not a sold cosmetic, so it is free to be the most vibrant
          surface in the app — and it is the one an 8-year-old actually reads ---- */
  --dn-card-face-hi:#ffffff; --dn-card-face-mid:#fbfcff; --dn-card-face-lo:#eef2fa;
  --dn-card-ink:#0d1018;  --dn-card-ink-red:#d90429;
  --dn-card-edge:#a9b4c7; --dn-card-frame:rgba(13,16,24,.12);

  /* ---- state ---- */
  --dn-good:#24e08c;  --dn-warn:#ffb020;  --dn-bad:#ff3355;
  --dn-bad-soft:#ff8fa3;  --dn-info:#4aa8ff;

  /* ---- on-felt text ---- */
  --dn-on-felt:#ffffff;
  --dn-on-felt-accent:var(--dn-gold-hi);

  /* ---- focus ---- */
  --dn-focus:#ffffff;  --dn-focus-halo:rgba(36,63,111,.9);

  /* ---- play pieces ---- */
  --dn-sq-light:#ffe6a8;  --dn-sq-dark:#147c56;  --dn-board-frame:#062a1e;
  --dn-p1:#ff4d5e;  --dn-p1-deep:#c8102e;
  --dn-p2:#5aa8ff;  --dn-p2-deep:#12448f;
  --dn-piece-rim:#0d1018;
  --dn-tile-hi:#fffdf5; --dn-tile-lo:#f0efe0; --dn-tile-half:#fbfaf0;
  --dn-tile-edge:#c8caba; --dn-pip:#0d1018;

  /* ---- legacy aliases: consumed throughout, do not delete ---- */
  --bg:var(--dn-ink-900);
  --bg2:var(--dn-ink-800);
  --panel:var(--dn-ink-700);
  --line:var(--dn-ink-600);
  --text:var(--dn-fg);
  --muted:var(--dn-fg-dim);
  --gold:var(--dn-gold);
  --gold-deep:var(--dn-gold-deep);
  --felt:#16513a;
  --red:var(--dn-bad);
  --radius:14px;
  --dn-card-deal-duration:520ms;
  --dn-card-flip-duration:460ms;
  /* Capacitor injects --safe-area-inset-* on Android. Keep env() as the web/iOS
     fallback, but consume the injected values everywhere through one alias. */
  --dn-safe-top:var(--safe-area-inset-top, env(safe-area-inset-top));
  --dn-safe-right:var(--safe-area-inset-right, env(safe-area-inset-right));
  --dn-safe-bottom:var(--safe-area-inset-bottom, env(safe-area-inset-bottom));
  --dn-safe-left:var(--safe-area-inset-left, env(safe-area-inset-left));
}
*{box-sizing:border-box}
html,body{margin:0;touch-action:manipulation;overscroll-behavior:none} /* manipulation: double-tap-zoom killed; overscroll none: no Android stretch-bounce / pull-to-refresh on the felt (native feel) */
/* Deep-purple "casino room" backdrop (the reference tables) — a faint damask-ish
   dot weave over a dark violet fall-off. Pure gradients: no images, no repaints. */
body{background:
    radial-gradient(1200px 600px at 50% -10%,rgba(124,82,186,.20),transparent 60%),
    repeating-radial-gradient(circle at 24% 18%,rgba(255,255,255,.022) 0 2px,transparent 2px 26px),
    repeating-radial-gradient(circle at 78% 64%,rgba(255,255,255,.015) 0 2px,transparent 2px 34px),
    linear-gradient(180deg,#231533,#130d1f);
  color:var(--text);
  font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;min-height:100vh;
  min-height:100dvh;width:100%;overflow-x:clip;-webkit-text-size-adjust:100%}
a{color:inherit;text-decoration:none}
.wrap{max-width:1440px;margin:0 auto;padding:0 22px}

/* WRAP, never clip. The header row is brand + [Sign in] + Coin Shop + Online, and the
   sign-in pill is INJECTED at runtime by account-modal.js, so the row is wider than any
   static reading of this file suggests. Measured on the live page: at 678px (an iPad 50/50
   Split View) the row needed 743px and `body{overflow-x:clip}` silently cut the Online
   button off the screen entirely; at 430px it cut 44px. Between 560px (where the compact
   header block below starts) and 760px NO compaction rule matches at all, which is why that
   band was the worst. Wrapping costs one row of height only when the row cannot fit, and
   changes no size the owner tuned. */
header{display:flex;flex-wrap:wrap;row-gap:8px;align-items:center;justify-content:space-between;padding:calc(8px + var(--dn-safe-top)) 22px 6px;max-width:1440px;margin:0 auto}
/* keeps the buttons right-aligned on the second row, where space-between would left-align a
   lone wrapped item */
header .header-cta{margin-left:auto}
.brand{display:flex;align-items:center;gap:12px;font-weight:800;font-size:20px;letter-spacing:.01em}
/* Brand mark = the current gold DC-rose (transparent) — the old blue app-icon
   tile is retired everywhere, including every game screen's top-left. The brand
   is always an <a href="index.html">, so the logo doubles as the way home. */
.brand .rose{width:48px;height:48px;border-radius:0;flex:0 0 auto;
  background:center/contain no-repeat url(icons/dc-rose-gold.png);
  box-shadow:none;
  font-size:0;line-height:0;overflow:hidden}
.brand b{color:var(--gold)}
/* Home header wordmark: bigger "DeNiro Card" with a tighter space between the two
   words, and the current gold DC-rose mark (transparent) instead of the old blue tile.
   The DC mark is deliberately LARGER than the wordmark, sitting to its left.
   Scoped to the page <header> so game-page .topbar brands are untouched. */
header .brand{font-size:30px;gap:14px;word-spacing:-.16em}
header .brand .rose{width:64px;height:64px;border-radius:0;background:center/contain no-repeat url(icons/dc-rose-gold.png);box-shadow:none;filter:drop-shadow(0 3px 10px rgba(0,0,0,.45))}
.header-cta{display:flex;gap:10px}
/* TOP-RIGHT ACTION ROW (owner audit 2026-07-23): [Sign in] [Coin Shop] [Online]
   — all one size/shape, each its own colour. Online is the biggest and gently
   pulses to draw the eye. The old "Live" button is gone. */
.header-cta{display:flex;gap:10px;align-items:center}
.header-cta .btn,.dna-signin-pill{font-size:19px;padding:15px 28px;border-radius:13px;
  min-height:56px;font-weight:800;border:0;display:inline-flex;align-items:center;justify-content:center;
  white-space:nowrap;cursor:pointer;line-height:1}
/* Sign in / Sign out — violet */
.dna-signin-pill{margin-left:0;color:#fff;
  background:linear-gradient(180deg,#8d5cf0,#5f34c2);
  box-shadow:0 0 18px rgba(141,92,240,.42),0 2px 12px rgba(95,52,194,.5)}
body.dn-home .header-cta .dna-signin-pill{max-width:220px;overflow:hidden;text-overflow:ellipsis}
.dna-signin-pill:hover{background:linear-gradient(180deg,#9d70f5,#6b3ed4)}
/* the display rule above must never defeat the hidden attribute (Sign out) */
.dna-signin-pill[hidden]{display:none !important}
/* Coin Shop — emerald */
.header-cta .btn-shop{color:#04231a;
  background:linear-gradient(180deg,#59e0a8,#22a877);
  box-shadow:0 0 18px rgba(89,224,168,.42),0 2px 12px rgba(34,168,119,.45)}
/* COIN BALANCE IN THE LOBBY HEADER. game-shell.js turns the Coin Shop button
   into two stacked lines — the balance above its own label — rather than
   adding a sixth control to the rail. The rail's width is set by the wider
   line ("🪙 Coin Shop"), which is what it already was, so this costs zero
   horizontal pixels and neither the wrap threshold nor the clearance over the
   hero mark moves. Vertically the two lines fit the existing 56px min-height
   with the tighter padding and line-heights below. */
/* Scoped to the BUTTON, not to .header-cta. The home lobby's shop control
   lives in .header-cta; the Online lobby's (#lobbyTopShop) lives in a .topbar
   .score cluster instead, and a .header-cta-only rule left that one as a
   fixed-height inline-flex row with the balance and the label overlapping. */
.btn-shop.dn-has-balance{flex-direction:column;gap:1px;
  padding:8px 20px;line-height:1.05;white-space:nowrap}
/* color:inherit is load-bearing. A <b> inside the Online lobby's rail picks up
   a gold rule from that page, which put gold text on the gold shop button —
   measured at rgb(242,193,78) on the same gradient. Inheriting the button's own
   foreground keeps the balance readable on either lobby, and the white
   text-shadow that suits the label washes out a large numeral. */
.btn-shop.dn-has-balance .dn-lobbybal{font-size:20px;font-weight:900;
  letter-spacing:.01em;line-height:1.1;color:inherit;text-shadow:none}
.btn-shop.dn-has-balance .dn-lobbybal--signedout{font-size:15px;font-weight:800}
.btn-shop.dn-has-balance .dn-lobbybal-label{font-size:13px;font-weight:800;
  opacity:.92;line-height:1.1}
/* The Online lobby's rail is shorter and sits over the felt; it has no room for
   the home header's type sizes. */
.topbar .btn-shop.dn-has-balance{padding:6px 14px}
.topbar .btn-shop.dn-has-balance .dn-lobbybal{font-size:16px}
.topbar .btn-shop.dn-has-balance .dn-lobbybal-label{font-size:11px}
/* Narrow phones already run the rail at its limit; keep both lines legible
   rather than letting the balance dominate. */
@media (max-width:520px){
  .btn-shop.dn-has-balance{padding:6px 12px}
  .btn-shop.dn-has-balance .dn-lobbybal{font-size:17px}
  .btn-shop.dn-has-balance .dn-lobbybal-label{font-size:12px}
}
/* Online — gold, biggest, gentle pulse */
.header-cta .btn-gold{font-size:21px;padding:17px 38px;color:#241c07;
  background:linear-gradient(180deg,#f2d078,#c9a347);
  box-shadow:0 0 20px rgba(240,205,116,.45),0 2px 12px rgba(var(--dn-gold-rgb),.42);
  animation:onlinePulse 2.6s ease-in-out infinite}
@keyframes onlinePulse{
  0%,100%{box-shadow:0 0 18px rgba(240,205,116,.38),0 2px 12px rgba(var(--dn-gold-rgb),.4);filter:brightness(1)}
  50%{box-shadow:0 0 34px rgba(255,222,140,.85),0 2px 16px rgba(var(--dn-gold-rgb),.6);filter:brightness(1.1)}}
@media (prefers-reduced-motion:reduce){.header-cta .btn-gold{animation:none}}
.btn{border:0;cursor:pointer;font-weight:700;border-radius:10px;padding:10px 16px;font-size:14px;transition:filter .15s,transform .1s;-webkit-tap-highlight-color:transparent}
.btn-gold{background:linear-gradient(180deg,var(--gold),var(--gold-deep));color:#241c07;box-shadow:0 2px 10px rgba(var(--dn-gold-rgb),.28)}
.btn-line{background:transparent;border:1px solid var(--line);color:var(--text)}
.btn-ghost{background:var(--bg2);color:var(--muted);border:1px solid var(--line)}
@media (hover:hover) and (pointer:fine){ .btn:hover{filter:brightness(1.06)} }
.btn:active{transform:translateY(1px)}
/* A disabled control must LOOK disabled and must not react — several games gray
   out Undo/Hint/Deal mid-turn and taps on them were still flashing/lifting. */
.btn:disabled,.btn[disabled]{opacity:.45;cursor:default;pointer-events:none;transform:none;filter:none}
/* Hints are removed product-wide (owner decision 2026-07-23). Shell.hints is
   already hard-false, but several pages gated only the highlight, not the
   button — this stylesheet choke point guarantees no Hint button ever renders
   (solitaire/blackjack/rummy/dominoes had live ones). The "?" rules buttons
   are separate and stay. */
#hintBtn{display:none !important}
/* The account modal's own .dna-* class rules (display:block/inline-flex) are
   injected after the [hidden] UA rule and outrank it, so fields that
   niro-password.js hides would still paint. Make hidden authoritative. */
.dna-card [hidden],.dna-overlay [hidden]{display:none !important}
/* PHONE HEADERS: the enlarged home header (64px gold mark + 30px wordmark +
   56px action buttons) is sized for desktop. On a phone that same header on the
   legal/support pages pushed "← All games" into the wordmark and off the edge —
   the glitched top-right button. Scale the whole header down under 560px. */
@media (max-width:560px){
  header{padding-left:12px;padding-right:12px;gap:8px}
  header .brand{font-size:19px;gap:8px}
  header .brand .rose{width:40px;height:40px}
  .header-cta{gap:6px;flex-wrap:nowrap;min-width:0}
  .header-cta .btn,.header-cta .dna-signin-pill{font-size:14px;padding:11px 14px;min-height:46px;border-radius:11px}
  .header-cta .btn-gold{font-size:15px;padding:12px 16px}
  body.dn-home .header-cta .dna-signin-pill{max-width:170px;overflow:hidden;text-overflow:ellipsis}
  body.dn-home #deniro-account-signout{margin-left:0 !important}
}
@media (max-width:400px){
  header{padding-left:10px;padding-right:10px}
  header .brand{font-size:0;flex:0 0 36px;gap:0}
  header .brand b{display:none}
  header .brand .rose{width:36px;height:36px}
  .header-cta{flex:1;justify-content:flex-end;gap:6px}
  .header-cta .btn,.header-cta .dna-signin-pill{font-size:13px;padding:10px 12px;min-height:44px}
  .wrap{padding-left:10px;padding-right:10px}
  .grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
  .hero{padding-left:12px;padding-right:12px}
  .hero p{white-space:normal;overflow:visible;text-overflow:clip;line-height:1.35}
}
/* A settled web account adds two controls to the home header: the email entry
   and an explicit Sign out button.  The source-only guest row has one, which
   let the four-control signed-in row escape left/right on 390-480px phones
   while body overflow clipping hid the defect.  At narrow tablet and phone
   widths, give the action rail its own row and let its visible children form
   two equal columns.
   The account email stays available as the button's accessible name while its
   visual label ellipsizes inside the bounded cell. */
@media (max-width:760px){
  body.dn-home header .header-cta{
    flex:0 0 100%;width:100%;flex-wrap:wrap;justify-content:center;gap:6px
  }
  body.dn-home header .header-cta > *{
    flex:1 1 calc(50% - 3px);min-width:0
  }
  body.dn-home header .header-cta .dna-signin-pill{
    max-width:none;overflow:hidden;text-overflow:ellipsis
  }
}
/* Guest coin warning in the shop — visible, not alarming. Buying signed out is
   allowed, so this is the only place the "cannot be kept" consequence is stated
   before a price is tapped; it stays large enough to read at arm's length.
   The plain shape below is for the two purely INFORMATIONAL states ("Checking your
   account…"). The real consequence gets .dn-shop-guestwarn-alert, so the loud
   version only ever means "read this before you pay". */
.dn-shop-guestwarn{margin:0 0 10px;padding:11px 13px;border-radius:10px;font-size:14px;line-height:1.5;
  color:#f6e3b0;background:rgba(var(--dn-gold-rgb),.10);border:1px solid rgba(var(--dn-gold-rgb),.42)}
.dn-shop-guestwarn-alert{display:grid;grid-template-columns:auto minmax(0,1fr);
  column-gap:11px;align-items:start;padding:14px 16px;border-radius:12px;
  font-size:15px;color:#ffe9c2;background:rgba(190,112,40,.22);
  border-color:rgba(242,172,84,.78);box-shadow:0 0 0 1px rgba(242,172,84,.26)}
.dn-shop-guestwarn-mark{grid-row:1 / 3;font-size:26px;line-height:1.05}
.dn-shop-guestwarn-head{font-size:17px;font-weight:900;color:#ffd79a;letter-spacing:-.01em}
.dn-shop-guestwarn-alert .dn-shop-guestwarn-copy{margin-top:3px}

/* ONE-PAGE lobby (owner audit): the hero is compressed hard so every game tile
   is visible with NO scrolling on a normal desktop window. */
.hero{text-align:center;padding:2px 22px 2px}
/* Centered brand mark (current gold DC-rose) pulled UP tight under the header. */
.hero-logo{display:block;width:min(124px,28vw);height:auto;margin:0 auto 4px;filter:drop-shadow(0 6px 22px rgba(0,0,0,.45))}
/* 14px READING FLOOR on the lobby. Compressing the hero for the one-page desktop
   layout had shrunk the two lines that actually tell a new player what this app is
   (the eyebrow at 11px, the blurb at 13px) below the size an 80-year-old can read at
   arm's length. The hero is still compact — these gain 3px and 1.5px, which the
   stretch grid below absorbs — but nothing on the lobby now sits under ~14px. */
.hero .eyebrow{color:var(--gold);font-weight:700;letter-spacing:.16em;text-transform:uppercase;font-size:14px}
.hero h1{font-size:clamp(20px,2.4vw,27px);margin:.1em 0 .06em;font-weight:800;letter-spacing:-.02em}
.hero p{color:var(--muted);max-width:960px;margin:0 auto;font-size:14.5px;line-height:1.4;
  white-space:normal;overflow:visible;text-overflow:clip}

.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(172px,1fr));gap:12px;padding:6px 0 12px}
/* FILL THE PAGE (owner audit): the catalog stretches DOWN to eat all bottom
   dead space — tiles grow as tall as the viewport allows, while short windows
   still fit one page with no scrolling. Web home only (native has its own pane). */
html:not(.is-native) body.dn-home{min-height:100vh;min-height:100dvh;display:flex;flex-direction:column}
html:not(.is-native) body.dn-home header{width:100%}
html:not(.is-native) body.dn-home .wrap{flex:1;display:flex;flex-direction:column;min-height:0;width:100%}
html:not(.is-native) body.dn-home .grid{flex:1;grid-auto-rows:1fr;align-content:stretch}
html:not(.is-native) body.dn-home .game{height:100%}
.game{background:linear-gradient(180deg,var(--panel),#12141b);border:1px solid var(--line);border-radius:16px;padding:10px;position:relative;transition:.15s;display:flex;flex-direction:column;gap:6px;min-height:120px;
  /* SQUARE launcher tiles (owner audit): big centered icon + name; the content
     scales WITH the tile via container-query units, so bigger squares mean a
     proportionally bigger emoji and game name — no small text in big boxes. */
  align-items:center;justify-content:center;text-align:center;container-type:inline-size}
@media (hover:hover) and (pointer:fine){ .game:hover{border-color:var(--gold-deep);transform:translateY(-2px)} }
.game:active{border-color:var(--gold-deep)}
.game[role="button"]:focus-visible{outline:3px solid var(--gold);outline-offset:3px}
.game .suit{font-size:clamp(34px,30cqw,92px);line-height:1.05;filter:drop-shadow(0 2px 6px rgba(0,0,0,.4));display:flex;align-items:center;justify-content:center}
/* Custom per-game vector art — one design system, scales with the tile. */
.game .suit .game-art{width:clamp(38px,34cqw,104px);height:auto;display:block}
/* The native launcher used to pin this to a flat 56px, which is why the shipped
   iOS/Android iPad lobby measured a 56.0px icon at 507pt AND at 1366pt — zero
   response to screen size, by construction. It now joins the same container-query
   system as the web tile, with the old phone value as the FLOOR so no phone
   shrinks: 56px until the tile is wide enough for 34cqw to exceed it. */
html.is-native .game .suit .game-art{width:clamp(56px,34cqw,104px)}
.game h3{margin:0;font-size:clamp(15px,10cqw,30px);line-height:1.15;max-width:96cqw;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* marketing copy retires — the square is icon + name, like the native launcher */
.game p{display:none}
.game .play{display:none}
.game .badge.live{display:none}
.game.soon{opacity:.72}
/* 14px floor again: "Soon" / "🔒" is the only thing telling a player a tile will not
   open, so it may not be the smallest text on the page. */
.game .badge{position:absolute;top:12px;right:12px;font-size:14px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;padding:3px 8px;border-radius:99px}
.badge.live{background:rgba(var(--dn-gold-rgb),.16);color:var(--gold)}
.badge.soon{background:#20242e;color:var(--muted)}

/* Support / Privacy / Terms live ONLY in this footer, so 12px made the legally
   required links the hardest thing on the page to hit and read. 14px floor. */
footer{border-top:1px solid var(--line);color:var(--muted);text-align:center;padding:7px 14px;font-size:14px}
/* TAP TARGETS in the footer. Measured in the 2026-08-23 QA pass at 17px tall on
   every viewport including a 320px phone — below even WCAG 2.5.8's 24px floor,
   let alone the 44px this product should hold itself to for an elderly
   audience. The links are the only route to Support, Privacy and Terms, so they
   are navigation, not incidental inline text, and the inline exception does not
   cover them. Padding rather than font-size: the row must not get taller than
   one line of text on a phone. */
footer a{display:inline-flex;align-items:center;justify-content:center;
  min-height:44px;padding:0 12px}
/* The rose mark in the header of the legal and support pages is the only way
   back to the games from them, so it is navigation, and it measured 36x36 on a
   phone. */
header .brand,
.topbar a.brand{display:inline-flex;align-items:center;min-height:44px}
/* The injected Analytics settings button matches the small footer links —
   not a big boxed button (owner audit). Specificity beats the runtime CSS.
   "Matching" now means matching their TARGET as well as their look: the links
   above grew to a 44px hit area in the 2026-08-23 QA pass, and this one has to
   grow with them or it is the one 17px control left in the row. Still no
   border, no background, no box — just a bigger place to press. */
footer button.niro-analytics-ui.niro-analytics-settings{background:none;border:0;box-shadow:none;
  margin:0 2px;padding:0 10px;min-height:44px;font:inherit;font-size:14px;color:var(--muted);
  cursor:pointer;display:inline-flex;align-items:center;justify-content:center;
  text-decoration:none}
footer button.niro-analytics-ui.niro-analytics-settings::before{content:"· ";color:var(--muted)}
footer button.niro-analytics-ui.niro-analytics-settings:hover{color:var(--gold)}

/* ================= TABLET TIER — the whole tablet strategy, one place =================
   THE BUG: `.grid` above is repeat(auto-fill,minmax(172px,1fr)). auto-fill spends every
   extra point on MORE COLUMNS and never on a bigger tile, so the measured tile width was
   trapped in 178-225px at EVERY iPad size, and `.game`'s container-query clamps
   (30cqw/34cqw/10cqw) could never reach their 92/104/30px ceilings because their container
   never grew. Measured before this block: a 430pt iPhone rendered a 187.0px tile / 56.1px
   icon / 16.50px label; a 1366pt iPad Pro rendered 178.6px / 53.2px / 15.66px — on a screen
   3.2x wider, everything was ~5% SMALLER.

   THE STRATEGY: an EXPLICIT column count. That is the one change auto-fill cannot express
   and the only one that makes the tile itself grow; because every tile-internal size is
   already in cqw, the icon, the name and the corner badge then scale for free. We
   deliberately did NOT raise the cqw ceilings (dead code against a trapped container) and
   did NOT add per-element tablet font sizes for the tile — a second set of numbers is what
   froze the native lobby in the first place.
   COST: the column count is now three steps we own, so a new screen width lands in the
   nearest step instead of self-tuning; and the tablet web lobby scrolls where a
   7-column one fit on one page. Bigger tiles and 18 games cannot both be free.

   THE GATE IS DELIBERATELY THREE-PART — "wide" is NOT "tablet":
   - min-height:700px keeps a LANDSCAPE PHONE out (844x390, 956x440). Tablet-sized tiles on
     a 390px-tall screen would push the catalog off the bottom, and the phone geometry
     regression tests live at exactly those sizes.
   - min-width:700px keeps iPad SPLIT VIEW and SLIDE OVER out. Those panes are 320-507pt
     wide on a 1366pt-TALL screen, so a height-only or device-based check would hand a
     phone-width column a tablet layout. They fall through to the phone rules, which
     already measure a 203-300px tile in those panes — correct without special-casing.
   - pointer:coarse keeps a DESKTOP window out. The one-page 7-column desktop lobby is an
     owner decision and is untouched. */
@media (min-width:700px) and (min-height:700px) and (pointer:coarse){
  /* SINGLE SOURCE OF TRUTH for "this is a tablet". native.js and game-shell.js read this
     custom property instead of re-typing the media query in JS, so the layout tier and the
     orientation policy below can never drift apart. */
  :root{--dn-tablet:1}
  .grid{--dn-lobby-cols:3;grid-template-columns:repeat(var(--dn-lobby-cols),minmax(0,1fr));gap:14px}
  /* Steps, not a formula: a vw-driven minmax() holds the column count CONSTANT (tried it —
     3 columns and 431px tiles on a 13-inch iPad), so the count has to be chosen. These
     three keep the measured tile between 224px (iPad mini portrait) and 277px (iPad Pro 11
     landscape) across every current iPad size, in both orientations. */
  @media (min-width:1024px){ .grid{--dn-lobby-cols:4} }
  @media (min-width:1280px){ .grid{--dn-lobby-cols:5} }
  /* The hero is compressed for a one-page DESKTOP lobby; a tablet has the height to spare
     and holds the screen further from the face than a phone. */
  .hero{padding:6px 26px 4px}
  .hero-logo{width:min(200px,20vw)}
  .hero .eyebrow{font-size:16px}
  .hero h1{font-size:clamp(28px,3.4vw,40px)}
  .hero p{font-size:17px;line-height:1.45;max-width:min(94%,1080px)}
  .game .badge{font-size:15px;top:14px;right:14px;padding:4px 10px}
  footer{font-size:15px;padding:10px 16px}
  footer button.niro-analytics-ui.niro-analytics-settings{font-size:15px}
}

/* ============ table (game screens) ============ */
.table{min-height:100vh;min-height:100dvh;width:100%;display:flex;flex-direction:column}
.topbar{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:calc(11px + var(--dn-safe-top)) 18px 11px;
  border-bottom:1px solid var(--line);background:rgba(14,15,19,.72);backdrop-filter:blur(8px);
  position:sticky;top:0;z-index:20}
.topbar .brand{font-size:17px}
.topbar .score{color:var(--muted);font-size:13px;font-weight:700}
.topbar .score b{color:var(--gold)}
/* Native game chrome uses a compact nav bar. The explicit Games exit itself is
   shared by web, iOS and Android so every platform has the same safe way out. */
html.is-native-game .topbar{justify-content:flex-start;gap:8px;min-height:52px}
/* Leaving a game is the single most-used control on a game page, so it reads as
   a real button: a bordered pill with a 44px tap target, not a muted text link
   that players had to hunt for against the felt. */
/* Same plain look as the Coin Shop control, for one reason: they are the same
   control and used to be deliberately matched. The pill originally existed
   because a muted text link vanished against the felt — that problem is real,
   so the fix is a text-shadow rather than a bubble. Legibility kept, chip gone.
   The 44px target is unchanged.

   This is deliberately PAINT-ONLY. The border becomes transparent rather than
   0, and the padding, gap and font sizes are untouched, so the control occupies
   exactly the box it did before. An earlier version of this rule did shrink the
   box, and two pixels of reclaimed width was enough to overflow the euchre
   status strip's clip box at 667x375 — every game page lays out around this
   control, so its metrics are not ours to reclaim for a cosmetic change. */
.table .topbar .brand.dn-back{display:inline-flex;align-items:center;gap:4px;flex:0 0 auto;
  min-height:44px;padding:0 13px 0 9px;
  background:transparent;border:1px solid transparent;
  text-shadow:0 1px 3px rgba(0,0,0,.55);
  transition:opacity .12s ease}
.table .topbar .brand.dn-back:active{opacity:.55}
.table .topbar .brand.dn-back .dn-back-chev{font-size:26px;line-height:1;color:var(--gold);font-weight:600}
.table .topbar .brand.dn-back .dn-back-label{font-size:16px;color:var(--gold);font-weight:700;letter-spacing:.01em}
html.is-native-game .topbar .dn-gtitle{margin-right:auto;font-weight:700;font-size:17px;
  min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.shell-actions{display:flex;gap:8px;align-items:center;flex:0 0 auto}
.shell-game-meta{color:var(--gold);font-size:12px;font-weight:800;letter-spacing:.04em;
  white-space:nowrap;padding:5px 8px;border-left:1px solid rgba(var(--dn-gold-rgb),.24)}
/* ---- SLIM top bar for the non-felt (board / solo / 1v1) games ----
   html.dn-flat is set by orientForTable for every game NOT in the landscape-table
   set. These games keep a normal (non-floating) bar, but slimmed: the oversized
   42px brand tile shrinks, padding tightens, and the felt's content centres
   vertically (safe = never clips tall boards) so there's no wasted full-width bar
   or dead green at the bottom. */
html.dn-flat .topbar{padding-top:calc(6px + var(--dn-safe-top));padding-bottom:6px;gap:9px}
html.dn-flat .topbar .brand{font-size:15px}
html.dn-flat .topbar .brand .rose{width:36px;height:36px;border-radius:0}
html.is-native-game.dn-flat .topbar{min-height:42px}
html.dn-flat .topbar .brand.dn-back{min-height:44px;padding:0 11px 0 8px}
html.dn-flat .topbar .brand.dn-back .dn-back-chev{font-size:23px}
html.dn-flat .topbar .brand.dn-back .dn-back-label{font-size:15px}
html.is-native-game.dn-flat .topbar .dn-gtitle{font-size:16px}
html.dn-flat .felt{margin-top:6px;justify-content:safe center !important}
.shell-new-icon{display:none}
/* Portrait phones use one compact native navigation rail. Bot difficulty remains
   in Settings and table items in the Shop, while the old second row no longer
   wastes card-table space or makes controls jump between games. */
@media (max-width:520px){
  html.is-native-game .topbar{flex-wrap:nowrap;gap:5px;
    padding:calc(5px + var(--dn-safe-top)) 10px 5px;min-height:44px}
  html.is-native-game .topbar .brand.dn-back{min-width:64px}
  html.is-native-game .topbar .dn-gtitle{font-size:15px;flex:1 1 0;margin-right:0}
  html.is-native-game .shell-actions{gap:4px}
  html.is-native-game .shell-game-meta{font-size:12px;padding:4px 5px}
  html.is-native-game .shell-actions .btn{width:34px;height:34px;min-height:34px;padding:0;
    display:grid;place-items:center;font-size:16px;white-space:nowrap}
  /* "New" is much clearer than a lone circular arrow: the old icon resembled a
     page refresh and hid the fact that it deals a new match. */
  html.is-native-game .shell-actions .shell-new-game{width:auto;min-width:44px;padding:0 7px}
  html.is-native-game .shell-new-game .shell-new-icon{display:none}
  html.is-native-game .shell-new-game .shell-new-copy{display:inline;font-size:0}
  html.is-native-game .shell-new-game .shell-new-copy::before{content:"New";font-size:12px}
  html.is-native-game .dn-coinhud{margin-left:0;gap:0}
  html.is-native-game .dn-coin{min-height:34px;padding:4px 7px 4px 9px;font-size:12px}
  html.is-native-game .dn-coin .dn-coin-plus{width:14px;height:14px;font-size:11px}
  /* The web game header now carries the same coin pill as native (autoCoinHud
     mounts on any .table topbar), so it needs narrow-phone compaction too —
     otherwise the pill lands beside Back + title + ? + gear + New and wraps the
     row on a 375px browser.
     HORIZONTAL ONLY. The phone release contract further down this file keeps
     every one of these controls at a real 44px box so adjacent Help/Settings
     cannot activate each other; these selectors are more specific than it, so
     any width/height here would silently defeat that floor. Tighten gaps,
     padding and type — never the box. */
  html:not(.is-native-game) .table .topbar{gap:5px;flex-wrap:nowrap}
  /* The pill must be able to GIVE, not just be small. "Sign in" is the narrowest
     thing it ever says — a funded account reads "10,000,000", which is far wider
     and would clip on any bar that only just fit the signed-out width. So the
     balance truncates and the 🪙/+ affordance always survives. */
  html:not(.is-native-game) .table .topbar .dn-coinhud{
    margin-left:auto;gap:0;min-width:0;flex:0 1 auto}
  html:not(.is-native-game) .table .topbar .dn-coin{
    padding:4px 6px 4px 8px;font-size:12px;min-width:0;max-width:100%}
  html:not(.is-native-game) .table .topbar .dn-coin b{
    min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* A phone game bar has room for ONE short string in the pill, not two. The
     balance and the free-coin countdown were both clipping to a few unreadable
     pixels ("🪙 23:0…"), so they take turns instead of competing: while coins
     are counting down that is the live information, and the balance comes back
     the moment they are ready. Same contract as above — the 🪙 and the tap
     target survive either way, and the full balance is one tap away in the
     shop. Wider bars (>520px) show both. */
  html:not(.is-native-game) .table .topbar
    .dn-coin:has(.dn-free-timer:not([hidden])) b{display:none}
  html:not(.is-native-game) .table .topbar .dn-coin .dn-coin-plus{
    width:14px;height:14px;font-size:11px}
  html:not(.is-native-game) .table .topbar .shell-actions{gap:5px}
  /* A landscape table game carries a round/score chip the flat games do not
     (Spades: "Round 1"). Back + ? + gear + chip + New + coin needed 404px in a
     375px bar, which clipped the coin pill off the right edge. Compact the chip
     the way native already does, and let it be the element that gives — it is
     the only one here that degrades gracefully, since the controls are pinned to
     their 44px targets and the coin pill is the whole point of the rail. */
  html:not(.is-native-game) .table .topbar .shell-game-meta{
    font-size:12px;padding:4px 5px;min-width:0;flex:0 1 auto;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  /* "New" beats a lone circular arrow, which reads as a page refresh rather than
     dealing a fresh match — the same call native already makes. */
  html:not(.is-native-game) .shell-new-game .shell-new-icon{display:none}
  html:not(.is-native-game) .shell-new-game .shell-new-copy{display:inline;font-size:0}
  html:not(.is-native-game) .shell-new-game .shell-new-copy::before{
    content:"New";font-size:12px}
}
/* On a narrow mobile browser, the DC tile already identifies the app. Drop the
   repeated DeNiro wordmark on GAME pages so the actual game title gets first
   claim on the rail. The lobby keeps its full brand. */
@media (max-width:420px){
  html:not(.is-native-game) .table .topbar .brand{
    flex:0 0 44px;min-width:44px;min-height:44px;justify-content:center;
    gap:0;font-size:0 !important}
  html:not(.is-native-game) .table .topbar .brand .rose{
    width:32px;height:32px;border-radius:8px}
  html:not(.is-native-game) .topbar .brand b{display:none}
  .table .topbar .dn-gtitle{
    flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;
    text-overflow:clip;font-size:15px}
  .table .topbar .dn-gtitle.dn-phone-title-priority{
    flex:0 0 auto;overflow:visible}
  .table .topbar .dn-gtitle.dn-phone-title-pitch{font-size:0}
  .table .topbar .dn-gtitle.dn-phone-title-pitch::before{
    content:"Pitch";font-size:15px}
}
/* On the narrowest native phones, the back chevron still has its explicit
   “Back to games” accessibility label.  Hiding only the repeated visual word
   gives long game names their full, readable title instead of “Gin Rum…”. */
@media (max-width:430px){
  /* Without the word the chevron alone carries the meaning, so the control
     keeps a full 44x44 hit box rather than collapsing to glyph width. The box
     is invisible now that the pill is gone — which is the point: the target is
     the thumb's, not the eye's. */
  html.is-native-game .table .topbar .brand.dn-back,
  .table .topbar .brand.dn-back{min-width:44px;width:44px;height:44px;
    padding:0;justify-content:center}
  .table .topbar .brand.dn-back .dn-back-chev{font-size:28px;
    position:relative;left:-1px}
  .table .topbar .brand.dn-back .dn-back-label{display:none}
}
/* A 360px portrait game bar has Back + title + Help + Settings + New + wallet.
   Keeping all six visible left only 21px for “Five Dice” on the Play phone
   capture. Settings is already available from the native More tab; on only this
   narrow flat-game rail, remove the duplicate gear so the actual game name stays
   fully readable. Help, New game, wallet/sign-in and Back remain 44px targets. */
@media (max-width:380px) and (orientation:portrait){
  html.is-native-game.dn-flat .topbar .shell-gear{display:none}
}
/* ---- Landscape: slim the top bar so the table reclaims the height ----------
   The portrait compaction above is gated on max-width:520px, which NEVER matches a
   wide landscape viewport — so landscape games ran the full ~65px bar and squished
   the felt. In landscape (dn-land, or native force-rotate dn-rot) collapse it to
   ~40px: tight padding, compact icon buttons, a "New" chip, drop the redundant web
   wordmark (native already shows a back button). One rule → every game. */
/* LANDSCAPE: no full-width bar at all. The top bar becomes a transparent, pointer-
   through OVERLAY and its controls float into the top CORNERS over the felt — brand+
   title top-left, ?/⚙/Round/New/coins top-right — so the freed centre-top is live
   felt and the table extends to the very top. Cards then grow (see the hand rule).
   One rule → all 18 games. Portrait keeps its normal bar. */
html.dn-land .topbar, html.dn-rot .topbar{
  position:absolute;top:0;left:0;right:0;z-index:30;background:transparent;
  border-bottom:none;backdrop-filter:none;box-shadow:none;min-height:0;
  justify-content:flex-start;align-items:flex-start;gap:6px;
  padding:calc(5px + var(--dn-safe-top))
    max(10px,var(--dn-safe-right)) 0
    max(10px,var(--dn-safe-left));pointer-events:none;
  text-shadow:0 1px 3px rgba(0,0,0,.75)}
html.dn-land .topbar > *, html.dn-rot .topbar > *{pointer-events:auto}
html.dn-land.is-native-game .topbar, html.dn-rot.is-native-game .topbar{min-height:0}
/* right cluster: push actions + coins to the top-right, kept together */
html.dn-land .shell-actions, html.dn-rot .shell-actions{margin-left:auto;gap:3px;
  background:rgba(9,12,17,.88);border-radius:999px;padding:2px 5px;backdrop-filter:blur(6px);
  text-shadow:none}
html.dn-land .dn-coinhud, html.dn-rot .dn-coinhud{margin-left:5px}
/* web: keep only the 🌹 home mark, drop the "DeNiro Card" wordmark (inline style → !important) */
html.dn-land:not(.is-native-game) .topbar .brand, html.dn-rot:not(.is-native-game) .topbar .brand{font-size:0 !important;gap:0;padding:2px}
/* Keep the rose EMOJI hidden in landscape too — the gold DC mark is the logo;
   the 🌹 text glyph next to it read as a stray sticker (owner audit). */
html.dn-land:not(.is-native-game) .topbar .brand .rose, html.dn-rot:not(.is-native-game) .topbar .brand .rose{font-size:0 !important}
html.dn-land .topbar .dn-gtitle, html.dn-rot .topbar .dn-gtitle{font-size:14px;margin-right:0;font-weight:800}
html.dn-land .topbar .brand.dn-back .dn-back-chev, html.dn-rot .topbar .brand.dn-back .dn-back-chev{font-size:22px}
html.dn-land .topbar .brand.dn-back .dn-back-label, html.dn-rot .topbar .brand.dn-back .dn-back-label{font-size:13px}
html.dn-land .shell-game-meta, html.dn-rot .shell-game-meta{font-size:14px;padding:2px 5px;border-left:none}
html.dn-land .shell-actions .btn, html.dn-rot .shell-actions .btn{
  width:27px;height:27px;min-height:27px;padding:0;display:grid;place-items:center;font-size:14px;white-space:nowrap}
html.dn-land .shell-actions .shell-new-game, html.dn-rot .shell-actions .shell-new-game{width:auto;min-width:32px;padding:0 6px}
html.dn-land .shell-new-game .shell-new-copy, html.dn-rot .shell-new-game .shell-new-copy{font-size:0}
html.dn-land .shell-new-game .shell-new-copy::before, html.dn-rot .shell-new-game .shell-new-copy::before{content:"New";font-size:12.5px}
html.dn-land .dn-coin, html.dn-rot .dn-coin{min-height:27px;padding:2px 6px;font-size:12.5px}
html.dn-land .dn-coin .dn-coin-plus, html.dn-rot .dn-coin .dn-coin-plus{width:14px;height:14px;font-size:11px}
/* Seat chips open the full scoreboard, so they are controls, and the QA pass
   measured them at 38px on a tablet in portrait — where there is no shortage of
   room. Give them the 44px floor only when the viewport is tall enough that the
   felt does not need the pixels back. Phone landscape keeps its compaction. */
/* Tablet and up, where the felt has room. Deliberately NOT phones: a 390x844
   portrait phone is tall but narrow, and tests/tablet-table.test.mjs pins its
   seat avatar at exactly 28px because the felt needs those pixels for cards. */
@media (min-width:768px) and (min-height:700px){
  .table .seats .seat,
  .felt .dn-seat{min-height:44px}
  /* .seat-av is the element that actually carries role="button" and tabindex —
     the round avatar, 38px. The seat around it can be tall and still leave the
     only thing you can press too small. */
  .table .seats .seat .seat-av,
  .felt .dn-seat .seat-av{min-width:44px;min-height:44px}
}
/* The 27px rail above is a real constraint on a phone held sideways: the bar
   floats over the felt and there is no room. It is NOT a constraint on a laptop
   or a tablet, where dn-land is also set simply because the window is wider
   than it is tall — and there the QA pass measured the coin pill and every
   topbar control at 27px, which is too small for this audience. Give the height
   back only where there is height to give, so the phone rail is untouched. */
@media (min-height:700px){
  html.dn-land .table .topbar .shell-actions .btn,
  html.dn-rot .table .topbar .shell-actions .btn{
    width:auto;min-width:44px;height:44px;min-height:44px;padding:0 12px;font-size:15px}
  html.dn-land .table .topbar .dn-coin,
  html.dn-rot .table .topbar .dn-coin{min-height:44px;padding:6px 12px;font-size:15px}
  html.dn-land .table .topbar .dn-coin .dn-coin-plus,
  html.dn-rot .table .topbar .dn-coin .dn-coin-plus{width:18px;height:18px;font-size:13px}
}
/* felt now fills to the very top since the bar floats */
html.dn-land .felt, html.dn-rot .felt{margin-top:2px}

/* Portrait-only web games use this in-flow note on short landscape phones.
   game-shell.js only creates it when that recommendation is relevant; keeping
   it in normal flow means it never covers cards or controls. */
.dn-portrait-rotate{
  position:static;display:flex;align-items:center;justify-content:center;gap:8px;
  flex:0 0 auto;min-height:38px;
  margin:6px max(10px,var(--dn-safe-right)) 0 max(10px,var(--dn-safe-left));
  padding:7px 12px;border:1px solid rgba(var(--dn-gold-rgb),.42);border-radius:12px;
  background:rgba(14,15,19,.9);color:#f4e7bd;font-size:13px;font-weight:750;
  line-height:1.3;text-align:center}
.dn-portrait-rotate-icon{font-size:18px;line-height:1}

/* Coin HUD — 🪙 balance, top-right of every native game bar. */
.dn-coinhud{margin-left:auto;display:flex;align-items:center;gap:8px;flex:0 0 auto}
.dn-coin{display:inline-flex;align-items:center;gap:5px;font-weight:800;font-size:14px;color:var(--gold);
  position:relative;overflow:visible;
  background:rgba(var(--dn-gold-rgb),.14);border:1px solid rgba(var(--dn-gold-rgb),.42);border-radius:999px;padding:5px 9px 5px 11px;cursor:pointer}
/* ---- compact platform availability row (WEB ONLY) -------------------------
   Verified public listings are links; unpublished platforms are status cards,
   never dead or invented store targets. Phones use a two-by-two grid so every
   card stays fully on-screen without tiny text or a hidden horizontal scroller. */
.dn-getapp{display:none}
html:not(.is-native) .dn-getapp{
  display:block;text-align:center;margin:6px auto 0;padding:12px 16px 4px;
  width:100%;max-width:820px;min-width:0}
.dn-getapp h2{margin:0 0 8px;font-size:15px;font-weight:800;letter-spacing:.02em;
  color:var(--muted)}
.dn-platform-row{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;
  width:100%;max-width:760px;min-width:0;margin:0 auto;padding:2px 2px 6px;
}
.dn-platform{
  min-width:0;min-height:58px;padding:7px 10px;border:1px solid rgba(var(--dn-gold-rgb),.3);
  border-radius:11px;display:flex;align-items:center;justify-content:center;gap:7px;
  color:var(--ink);background:#171920;text-decoration:none;line-height:1.15
}
.dn-platform strong{font-size:14px;white-space:nowrap}
.dn-platform small{font-size:11px;font-weight:800;letter-spacing:.04em;
  text-transform:uppercase;color:var(--muted);white-space:nowrap}
.dn-platform-android{border-color:rgba(118,211,151,.46);background:#13231b}
.dn-platform-android img{width:auto;height:40px;display:block}
.dn-platform-soon{cursor:default;background:#15171d;color:#d9dbe1}
.dn-platform:focus-visible{outline:3px solid var(--gold);outline-offset:2px}
@media (max-width:620px){
  .dn-platform-row{grid-template-columns:repeat(2,minmax(0,1fr))}
  .dn-platform{flex-direction:column;gap:4px;min-height:76px;padding:6px 8px}
  .dn-platform-android img{max-width:100%;height:auto;max-height:40px}
}
@media (min-width:900px){
  .dn-getapp{padding:14px 16px 6px}
  .dn-getapp h2{font-size:16px}
}
.dn-coin b{color:var(--gold);font-weight:800}
/* TRUNCATION SAFETY NET — both shells, every width.
   updateCoinHud compacts the balance with coinShort ("10M"), so in normal
   operation this never engages. It exists because the failure mode when it is
   absent is silent and total: an over-long balance pushes the pill clean off the
   right edge of the game rail, where it cannot be tapped at all. Web got this
   when the rail was fixed; native had no equivalent, which left the shipping
   App Store build one formatting change away from the same bug. The 🪙 and the
   + must always survive, so only the number may give. */
.topbar .dn-coinhud{min-width:0;flex-shrink:1}
.topbar .dn-coin{min-width:0;max-width:100%}
.topbar .dn-coin b{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dn-coin .dn-coin-plus{display:inline-grid;place-items:center;width:16px;height:16px;border-radius:50%;
  background:var(--gold);color:#241c07;font-weight:900;font-size:13px;line-height:1;margin-left:3px}
.dn-coin:active{transform:scale(.96)}
.dn-coin.dn-coin-gain{animation:dnCoinGain .54s cubic-bezier(.2,.8,.25,1)}
.dn-coin.dn-coin-spend{animation:dnCoinSpend .44s cubic-bezier(.25,.65,.3,1)}
.dn-hud-flycoin{position:absolute;left:50%;top:50%;z-index:4;pointer-events:none;
  font-size:13px;line-height:1;filter:drop-shadow(0 4px 4px rgba(0,0,0,.4));
  animation:dnHudCoinIn .62s cubic-bezier(.16,.72,.24,1) both}
.dn-hud-flycoin.spend{animation-name:dnHudCoinOut;animation-duration:.52s}
@keyframes dnCoinGain{0%,100%{transform:none}48%{transform:translateY(-2px) scale(1.08);
  box-shadow:0 0 18px rgba(var(--dn-gold-rgb),.48)}}
@keyframes dnCoinSpend{0%,100%{transform:none}45%{transform:translateY(1px) scale(.95);
  filter:saturate(.75)}}
@keyframes dnHudCoinIn{
  0%{opacity:0;transform:translate(calc(-50% + var(--dx,0px)),28px) scale(.6)}
  58%{opacity:1;transform:translate(calc(-50% + var(--dx,0px)),4px) scale(1.08)}
  100%{opacity:0;transform:translate(-50%,-4px) scale(.74)}
}
@keyframes dnHudCoinOut{
  0%{opacity:1;transform:translate(-50%,-3px) scale(.8)}
  100%{opacity:0;transform:translate(calc(-50% + var(--dx,0px)),24px) scale(.55)}
}
.dn-practice-pill{font-weight:700;font-size:12.5px;color:var(--muted);background:var(--bg2);
  border:1px solid var(--line);border-radius:999px;padding:5px 12px;cursor:pointer;white-space:nowrap}
.dn-practice-pill.on{color:#12241b;background:linear-gradient(180deg,#9fe6c4,#4bbf8b);border-color:transparent}
.dn-practice-pill:active{transform:scale(.96)}

/* The whole play surface is tap-driven: long-press text selection / the iOS
   callout sheet on a seat name or status line is the loudest "this is a web
   page" tell. Lobby/marketing copy (outside .felt/.table) keeps selection. */
.felt,.felt *{-webkit-user-select:none;user-select:none;-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent}
/* Felt colors as vars so the Table color setting swaps ONE place.
   Classic Green is included; every other optional felt is a paid class. */
:root{--felt-hi:#319475;--felt-mid:#1b6853;--felt-lo:#0d4034;--felt-line:#082b23;
  --felt-etch:rgba(255,224,157,.14)}
html.dn-felt-red{--felt-hi:#d61831;--felt-mid:#961122;--felt-lo:#560a14;--felt-line:#38060d}
html.dn-felt-blue{--felt-hi:#1967dd;--felt-mid:#11489b;--felt-lo:#0a2959;--felt-line:#061b3a}
/* premium felts (coin-shop cosmetics) */
html.dn-felt-royal{--felt-hi:#943ee0;--felt-mid:#691cac;--felt-lo:#3c1062;--felt-line:#270a40}
html.dn-felt-noir{--felt-hi:#474d59;--felt-mid:#32363e;--felt-lo:#1d1f24;--felt-line:#131417;--felt-etch:rgba(255,255,255,.08)}
html.dn-felt-emerald{--felt-hi:#088159;--felt-mid:#065a3e;--felt-lo:#033423;--felt-line:#022217;--felt-etch:rgba(var(--dn-gold-rgb),.28)}
html.dn-felt-crimson{--felt-hi:#df0e46;--felt-mid:#9c0a31;--felt-lo:#59061c;--felt-line:#3a0412;--felt-etch:rgba(var(--dn-gold-rgb),.35)}
html.dn-felt-sovereign{--felt-hi:#8f6b09;--felt-mid:#644b06;--felt-lo:#392b04;--felt-line:#251c02;--felt-etch:rgba(255,238,171,.36)}
/* Premium Midnight Gold — a deep navy-black cloth with a brighter champagne
   rail. It is purely visual and is applied only while the server VIP is live. */
html.dn-felt-premium-midnight-gold{--felt-hi:#243660;--felt-mid:#111b35;--felt-lo:#070b17;
  --felt-line:#03050b;--felt-etch:rgba(244,211,125,.5)}
/* rooms (body backdrops) */
html.dn-room-midnight body{background:
  radial-gradient(900px 500px at 50% -10%,rgba(63,81,181,.18),transparent 60%),
  repeating-radial-gradient(circle at 30% 20%,rgba(255,255,255,.03) 0 1.5px,transparent 1.5px 42px),
  repeating-radial-gradient(circle at 72% 55%,rgba(255,255,255,.02) 0 1px,transparent 1px 34px),
  linear-gradient(180deg,#0e1220,#070910)}
html.dn-room-ocean body{background:
  radial-gradient(1100px 600px at 50% -10%,rgba(46,160,180,.22),transparent 62%),
  radial-gradient(700px 300px at 20% 108%,rgba(30,120,140,.25),transparent 70%),
  linear-gradient(180deg,#0c2e38,#071820)}
html.dn-room-velvet body{background:
  radial-gradient(1100px 600px at 50% -10%,rgba(170,60,80,.16),transparent 60%),
  repeating-radial-gradient(circle at 26% 22%,rgba(255,255,255,.02) 0 2px,transparent 2px 28px),
  repeating-radial-gradient(circle at 76% 66%,rgba(255,255,255,.016) 0 2px,transparent 2px 36px),
  linear-gradient(180deg,#2c1119,#170810)}
.felt{flex:1;margin:0;max-width:none;width:100%;
  background:
    /* etched center ring — the reference felt's medallion, as a background layer
       (NOT a positioned element: it must never re-anchor a game's absolute UI) */
    radial-gradient(ellipse 44% 27% at 50% 45%,transparent 61%,var(--felt-etch) 62.5%,var(--felt-etch) 64%,transparent 65.5%),
    radial-gradient(120% 90% at 50% 0%,rgba(255,255,255,.07),transparent 60%),
    radial-gradient(900px 560px at 50% 44%,var(--felt-hi),var(--felt-mid) 66%,var(--felt-lo));
  border:0;border-radius:0;
  /* Keep the felt's depth, but not a heavy black rail around the cards.  The
     table should read as one open playing surface rather than a tray holding a
     pile of cards. */
  box-shadow:
    inset 0 0 0 1px rgba(0,0,0,.22),
    inset 0 0 0 4px rgba(255,255,255,.025),
    inset 0 0 90px rgba(0,0,0,.30);
  padding:22px max(16px,var(--dn-safe-right)) max(22px,var(--dn-safe-bottom)) max(16px,var(--dn-safe-left));
  display:flex;flex-direction:column;gap:14px}

/* SHORT PORTRAIT PHONE, every flat-layout game.
   html.dn-flat is the shared hook the shell already sets for the non-four-seat
   tables (gin-rummy, rummy, tonk, solitaire, go-fish, old-maid, blackjack), so
   one rule reaches all of them. On a 568px-tall phone these stacks ran a few
   pixels past the bottom; the space comes out of felt padding and row gap, not
   out of cards or type, which the readability pass needs to grow. */
@media (max-height: 640px) and (orientation: portrait){
  html.dn-flat .felt{
    padding-top:4px;
    padding-bottom:max(4px, var(--dn-safe-bottom));
    gap:6px;
  }
}


.seats{display:flex;align-items:flex-start;justify-content:space-around;gap:8px;flex-wrap:wrap}
.seat{display:flex;flex-direction:column;align-items:center;gap:5px;min-width:88px;
  padding:9px 12px;border-radius:16px;transition:.18s;
  background:transparent;border:0}
/* The avatar's existing halo is enough to show a turn.  Keeping the whole seat
   transparent stops player labels from becoming dark rectangles on the felt. */
.seat.turn{background:transparent;box-shadow:none}
.seat .name{font-weight:800;color:#eafff3;font-size:14px;display:flex;align-items:center;gap:5px}
.seat .name::before{display:none}   /* legacy dot — replaced by the real .seat-av avatar */
/* dealer marker: a small gold "D" chip beside the dealer's name, all hand long */
.dn-dealer-badge{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;
  width:17px;height:17px;border-radius:50%;background:linear-gradient(180deg,#f0d488,#c99a3e);
  color:#2a1c05;font-weight:900;font-size:11px;line-height:1;letter-spacing:0;
  box-shadow:0 1px 3px rgba(0,0,0,.45),inset 0 1px 0 rgba(255,255,255,.4);vertical-align:middle}
/* real circular avatar with the player's initial (injected by Shell.decorateSeats) */
/* position:relative anchors the trick-count badge (.dn-ss-badge). Safe here and ONLY
   here: making the .seat relative collapses the landscape layout and re-anchors the
   pinned hand (see the focus-mode note below) — the avatar has no such role. */
.seat-av{width:44px;height:44px;border-radius:50%;display:grid;place-items:center;position:relative;
  font-weight:900;font-size:18px;color:#fff;flex:0 0 auto;
  background:radial-gradient(circle at 34% 28%,var(--av1,#3a4560),var(--av2,#232a3d));
  border:2px solid rgba(255,255,255,.22);
  box-shadow:0 3px 8px rgba(0,0,0,.4),inset 0 1px 2px rgba(255,255,255,.28);
  text-shadow:0 1px 2px rgba(0,0,0,.45);transition:.18s}
/* ACTIVE SEAT — a ring around the avatar, matching the reference build.
   The old treatment was a flat green border plus glow: it said "your turn" but
   carried no sense of time. The reference draws a ring that DEPLETES as the
   turn runs down, which is the same pixel budget doing two jobs.

   The depletion is driven by --dn-turn-ms, set by whichever layer actually
   knows the deadline (the authoritative protocol sends turnDeadline). Solo
   games are NOT timed, so when nothing sets it the ring simply sits full and
   reads as a steady highlight — a game with no clock must not show a frozen
   timer, which would look broken rather than calm.

   conic-gradient in a masked ring rather than an SVG circle: no new element,
   no per-frame JS, and it inherits the avatar's size at every breakpoint. */
.seat.turn .seat-av{border-color:#4be3a5;
  box-shadow:0 0 0 3px rgba(75,227,165,.45),0 0 16px rgba(75,227,165,.5)}
.seat.turn .seat-av::after{
  content:"";position:absolute;inset:-6px;border-radius:50%;
  padding:3px;pointer-events:none;
  background:conic-gradient(#4be3a5 var(--dn-turn-sweep,360deg),
    rgba(75,227,165,.16) 0);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  mask-composite:exclude;
}
/* Only animate when a real deadline was supplied. */
.seat.turn .seat-av[style*="--dn-turn-ms"]::after{
  animation:dn-turn-deplete var(--dn-turn-ms,0ms) linear forwards;
}
@property --dn-turn-sweep{syntax:"<angle>";inherits:false;initial-value:360deg}
@keyframes dn-turn-deplete{from{--dn-turn-sweep:360deg}to{--dn-turn-sweep:0deg}}
/* Reduced motion and the in-app animations-off setting keep the RING — it is
   state, not decoration — and drop only the sweep. */
html.dn-anim-off .seat.turn .seat-av::after{animation:none}
@media (prefers-reduced-motion:reduce){
  .seat.turn .seat-av::after{animation:none}
}
/* meta = score/bid line under the name — CLEAN text on the felt (no pill; the
   dark bubbles read as clutter — Jesse 2026-07-16), shadowed for legibility.
   The second shadow layer used to be a CENTRED 10px blur, which spreads underneath the
   glyph and greys its own strokes — that is what made this line read grainy at 11.5px.
   A single tight OFFSET shadow separates the text from the felt without touching the
   strokes, and the size floor below does the rest. */
.seat .meta{color:#eafff3;font-size:14px;font-weight:700;text-align:center;line-height:1.35;
  padding:2px 4px;text-shadow:0 1px 2px rgba(0,0,0,.95)}
.seat .meta:empty{display:none;padding:0}
/* the bottom "you" seat holds the hand — keep it transparent, avatar inline with name */
.seat.seat-you{background:transparent;border:0;padding:6px 0}
.seat.seat-you .name{flex-direction:row;gap:8px}
.seat.seat-you .seat-av{width:34px;height:34px;font-size:15px;color:#2a2410;text-shadow:none}

.hand{display:flex;flex-wrap:wrap;align-items:flex-start;gap:7px;justify-content:center;min-height:calc(clamp(46px,13vw,66px) * 1.4)}
.hand.mine{margin-top:2px}
.center-row{display:flex;align-items:center;justify-content:center;gap:clamp(10px,4vw,26px);flex:1;flex-wrap:wrap;min-height:96px}
.pile{display:flex;flex-direction:column;align-items:center;gap:6px;color:#dff5ea;font-size:14px;font-weight:600}
/* who played which card is state, not decoration — it does not get to be 12px */
.pile .lbl{color:#bfe9d5;text-transform:uppercase;letter-spacing:.04em;font-size:14px}
.msg{text-align:center;color:#eafff3;min-height:22px;font-weight:600;font-size:15px;padding:2px 8px;
  text-shadow:0 1px 2px rgba(0,0,0,.95)}
.msg.win{color:var(--gold)}

/* ============ cards ============ */
.card{
  position:relative;
  /* Let type scale from the actual card width.  Landscape tables deliberately
     shrink cards; viewport-based type made a 10's index bigger than its card. */
  container-type:inline-size;
  width:clamp(58px,16.2vw,86px);
  /* a card NEVER flex-squeezes: in a nowrap hand row, overlap (fanHand's negative
     margins) is how many cards fit — flex-shrink was crushing 12-card hands to
     ~22px slivers on pages whose first render beats the fan pass */
  flex:0 0 auto;
  aspect-ratio:5 / 7;
  border-radius:8px;
  /* The fronts need to read as one intentional physical deck at both hand and
     hero sizes.  The old flat grey ramp made number cards look like raw HTML
     tiles on a bright table.  Keep the paper clean, but give it a cool ivory
     stock, a crisp edge and a very subtle inner press line. */
  background:linear-gradient(158deg,#ffffff 0%,#f8f9fc 54%,#e6eaf1 100%);
  color:#1a1c22;
  border:1px solid #b9c1ce;
  box-shadow:0 2px 6px rgba(0,0,0,.30),inset 0 0 0 1px rgba(255,255,255,.82),inset 0 0 0 3px rgba(26,31,43,.025);
  user-select:none;cursor:default;
  font-weight:800;line-height:1;
  transition:transform .12s ease,box-shadow .12s ease,filter .12s ease;
}
.dn-vip-badge{font-size:9px!important;line-height:1!important;letter-spacing:.08em;
  color:#241c07!important;background:linear-gradient(180deg,#f0d48a,#b88a2b);
  border-radius:999px;padding:3px 5px;box-shadow:0 1px 5px rgba(var(--dn-gold-rgb),.35)}
.dn-vip-badge.dn-premium-emoji-badge{display:inline-grid;place-items:center;min-width:25px;min-height:25px;
  padding:2px!important;font-size:17px!important;letter-spacing:0;background:linear-gradient(180deg,#18213a,#090d1d);
  border:1px solid rgba(240,212,138,.78);box-shadow:0 1px 7px rgba(0,0,0,.45),0 0 8px rgba(var(--dn-gold-rgb),.2)}
.card.red{color:#d8283f}
/* A hairline printed frame gives every front a polished deck identity without
   competing with pips or court art. Backs own their own pseudo-elements. */
.card:not(.back)::after{content:"";position:absolute;inset:3.5%;border:1px solid rgba(26,31,43,.10);
  border-radius:calc(8px - 2px);pointer-events:none;box-shadow:inset 0 0 0 1px rgba(255,255,255,.45)}
.card.red:not(.back)::after{border-color:rgba(216,40,63,.14)}
/* Your own hand is the one you actually have to READ — give it the biggest cards.
   The shell's fanHand() overlaps them to keep a 13-card hand on one row. */
.hand.mine .card{width:clamp(64px,17.2vw,96px);
  /* the fan arc: fanHand() sets --fanr/--fany per card; every lifted state below
     re-composes these vars so a tilted card lifts along its own angle */
  transform:rotate(var(--fanr,0deg)) translateY(var(--fany,0px))}
/* MINI cards: the sizing rule lives further down (.card.mini width + corner/pip
   fonts); the landscape table's .dn-seat .card !important rule overrides it there.
   A duplicate width here was dead in every context and got removed — don't re-add. */
.card .corner{position:absolute;display:flex;flex-direction:column;align-items:center;line-height:.82;z-index:2;
  min-width:1.45em;font-variant-numeric:tabular-nums}
/* The rank is the first thing a player should see in a fan. Big index + one
   oversized centre suit is deliberately clearer than a miniature pip diagram. */
.card .corner .r{font-size:clamp(17px,6.4vw,40px);font-weight:900;letter-spacing:-.09em}
.card .corner .s{font-size:clamp(12px,4.8vw,27px);margin-top:1px;font-family:Georgia,"Times New Roman",serif}
/* Tap-twice guard (Shell setting "confirmTaps"): the first tap LIFTS and rings the
   card, the second actually plays it — so a stray tap can't throw the wrong card. */
.hand.mine .card.armed{
  /* Selection must never make the rest of the hand look unavailable.  The
     chosen card gets one restrained physical cue: an 8px lift, a 4% grow and
     a crisp ring.  No sibling opacity/filter change is involved. */
  transform:rotate(var(--fanr,0deg)) translateY(calc(var(--fany,0px) - 8px)) scale(1.04);
  box-shadow:0 12px 24px rgba(0,0,0,.5),0 0 0 3px var(--gold),inset 0 0 0 1px rgba(255,255,255,.7);
  z-index:60 !important;
}
.card .corner.tl{top:6%;left:8%}
.card .corner.br{bottom:6%;right:8%;transform:rotate(180deg)}
.card .pip{position:absolute;inset:0;display:grid;place-items:center;
  font-size:clamp(38px,12.5vw,64px);opacity:.97;z-index:1;font-family:Georgia,"Times New Roman",serif}
.card .pip .ace{font-size:clamp(44px,14vw,72px)}
/* A hero card can carry a dramatic centre suit. In a 10–13 card fan that same
   glyph would overlap the next card's index and make the hand look like a pile.
   Keep the ranks big (they do the scanning), then use a still-obvious but calmer
   centre suit just for cards the player is holding. */
.hand.mine .card .pip:not(.face){font-size:clamp(31px,9vw,50px)}
.hand.mine .card .pip .ace{font-size:clamp(37px,10.5vw,58px)}
/* Face cards render a real court illustration (inline SVG from Cards.faceArt).
   Let that printed artwork use more of the card while the oversized tl/br indices
   remain above it: the former narrow inset made K/Q/J faces look undersized. */
.card .pip.face{inset:5% 4% 3%;display:block;font-size:0}
.card .pip.face svg{width:100%;height:100%;display:block}
/* Crisp VECTOR suit marks (Cards.suitSvg) replace the old font glyph on the corner
   pip AND a number card's centre suit — clubs/spades no longer render heavy/blobby.
   Sized in em off the existing font-size clamps so they inherit the responsive +
   held-hand fan shrink. Small clean corner pips, one clearly dominant centre suit;
   all three symbols kept, per Jesse. */
.card .corner .s{line-height:0}
.card .corner .s .suit-mark{width:.82em;height:.82em;display:block}
.card .pip:not(.face) .suit-mark{width:.92em;height:.92em;display:block}
/* COURT corner index — the ONLY thing visible on a fanned court (the next card covers
   the rest), so it must read as instantly as a number card's. The figure fills the card,
   so the dark corner suit used to land on the dark robe and vanish. Fix: give the index
   its own soft WHITE clearing (a radial fade, so no sticker edge) so the rank+suit always
   sit on WHITE, never on the figure/robe. Sized a touch bigger than a number pip (a court
   has no big centre suit to fall back on) but kept clean, not clunky. Scales via cqw below;
   this is the vw fallback. */
.card.court .corner{border-radius:9px;padding:0 4px 2px;
  background:radial-gradient(116% 104% at 50% 46%, #fff 58%, rgba(255,255,255,.72) 78%, rgba(255,255,255,0) 100%)}
.card.court .corner .r{font-size:clamp(17px,6.6vw,42px)}
.card.court .corner .s{font-size:clamp(14px,5.8vw,34px);margin-top:1px}
.card.court .corner .s .suit-mark{width:1.06em;height:1.06em}
.card.court.mini .corner{background:none;padding:0}   /* opponent mini stays plain + small */
.card.court.mini .corner .s{font-size:clamp(8px,2.6vw,12px)}
/* Legality is communicated by the gold playable ring, cursor, accessible name
   and the game's refusal feedback.  Do not wash out a player's card face: rank,
   suit and court art remain equally readable while another card is selected. */
.card.dim{opacity:1;filter:none}

/* Fallback above keeps older WebViews usable. Current native shells support
   container units, which keep a small landscape card's indexes and centre suit
   in separate visual lanes instead of letting the 8/10 collide with the suit. */
@supports (width:1cqw){
  /* THE CORNER INDEX IS WHAT CARRIES READABILITY. In a fanned hand the next
     card covers everything except the top-left corner, so the index — not the
     centre art — is what a player actually reads. The reference build Jesse
     supplied sets the rank at roughly 40-45% of card width; DeNiro already had
     the right UNIT (38cqw scales with the card) but a 25px ceiling threw it
     away: on a 127px tablet card 38cqw wants 48px, clamped to 25px, so the
     index collapsed to ~20% of the card exactly where there was most room for
     it. Raising the ceiling lets the card width govern, which is what cqw was
     chosen for. The floor rises too so a small landscape card stays legible. */
  /* The RANK carries the readability; the suit under it only has to be
     identifiable. Sizing them near each other made the corner ~0.75x the card
     width tall, and a card is only ~1.43x tall — so the top-left and
     bottom-right corners grew into each other across the middle of the card.
     Keeping the corner under ~0.57x the card width is what leaves the two in
     separate lanes at every size the fan produces. */
  .card .corner .r{font-size:clamp(17px,40cqw,46px)}
  .card .corner .s{font-size:clamp(12px,22cqw,28px)}
  .card .pip:not(.face){inset:25% 17% 20%;font-size:clamp(25px,66cqw,53px);line-height:.84}
  .hand.mine .card .pip:not(.face){inset:26% 18% 21%;font-size:clamp(24px,62cqw,49px)}
  /* court corner suit scales with the card (proportional at every fanned size) */
  /* A court card is the case where the corner matters most: the centre is an
     illustration, so the index is the only thing identifying the card. */
  /* Court corners carry a white clearing so the index stays readable over the
     robe. That clearing is exactly why the overlap mattered here more than
     anywhere: two merged clearings do not just crowd the index, they erase the
     illustration between them. The clearing is what makes the suit legible on
     dark art, so the suit no longer needs to be oversized to fight it. */
  .card.court .corner .r{font-size:clamp(17px,42cqw,48px)}
  .card.court .corner .s{font-size:clamp(11px,22cqw,29px)}
}

/* Dense portrait hands become orderly rows before their corner indexes could
   overlap.  This is intentionally scoped to a class applied by Shell.fanHand;
   normal short hands still get the elegant one-row fan. */
/* The .dn-hand twin is belt and braces, added when a PORTRAIT TABLET started using these
   rows too: fanHand treats a hand as the player's own if it carries EITHER class, so a hand
   that reached the wrap path with only .dn-hand would have got the wrap inline style and
   none of the row geometry. The 6px column gap is deliberately the same number fanHand
   subtracts per column when it sizes the cards — change one and the last column overflows. */
.hand.mine.dn-hand-grid,.dn-hand.dn-hand-grid{flex-wrap:wrap;align-content:flex-end;column-gap:6px;row-gap:6px;padding:0 4px}
.hand.mine.dn-hand-grid .card,.dn-hand.dn-hand-grid .card{margin-left:0!important;transform:none!important}

/* premium card back: gold diamond-lattice over deep indigo + a filigree frame and
   centered emblem. Pure CSS (no images), scales down cleanly for mini/opponent backs. */
.card.back{
  background:
    repeating-linear-gradient(45deg, transparent 0 8px, rgba(var(--dn-gold-rgb),.09) 8px 9px),
    repeating-linear-gradient(-45deg, transparent 0 8px, rgba(var(--dn-gold-rgb),.09) 8px 9px),
    radial-gradient(125% 95% at 50% 0%, #2b3668, #1a2247 68%, #131a37);
  border-color:#0e1330;color:transparent;
  box-shadow:0 2px 6px rgba(0,0,0,.4),
    inset 0 0 0 2px rgba(var(--dn-gold-rgb),.34),
    inset 0 0 0 3px rgba(0,0,0,.4),
    inset 0 0 0 4px rgba(var(--dn-gold-rgb),.13)}
.card.back::after{content:"";position:absolute;inset:15% 21%;border-radius:6px;
  border:1px solid rgba(var(--dn-gold-rgb),.42);
  background:radial-gradient(circle at 50% 50%, rgba(var(--dn-gold-rgb),.16), transparent 62%)}
.card.back::before{content:"";position:absolute;top:50%;left:50%;width:24%;height:24%;
  transform:translate(-50%,-50%) rotate(45deg);border-radius:3px;
  background:linear-gradient(135deg, rgba(var(--dn-gold-rgb),.6), rgba(var(--dn-gold-rgb),.22));
  border:1px solid rgba(var(--dn-gold-rgb),.55);box-shadow:0 0 8px rgba(var(--dn-gold-rgb),.3)}

/* ---- purchasable card-back deck themes (coinShop → Decks) ----
   Equipped globally via html.dn-back-<id> (applyCosmetics). Each theme also has a
   .dn-bk-<id> card-level twin so the shop can render a live mini swatch of ANY
   theme regardless of what's equipped. 'classic' is the free default above.
   Pure CSS + inline SVG data-URIs — no image files, scales to .card.mini.back. */
/* Rose Noir — near-black lacquer, faint gold weave, DC-rose monogram in gold */
html.dn-back-rose-noir .card.back,:root .card.back.dn-bk-rose-noir{
  background:
    repeating-radial-gradient(circle at 50% 50%,rgba(var(--dn-gold-rgb),.05) 0 1px,transparent 1px 9px),
    radial-gradient(125% 95% at 50% 0%,#26272f,#131419 62%,#0a0b0e);
  border-color:#000;
  box-shadow:0 2px 6px rgba(0,0,0,.5),
    inset 0 0 0 2px rgba(var(--dn-gold-rgb),.5),
    inset 0 0 0 3px rgba(0,0,0,.55),
    inset 0 0 0 4px rgba(var(--dn-gold-rgb),.16)}
html.dn-back-rose-noir .card.back::after,:root .card.back.dn-bk-rose-noir::after{
  inset:11% 15%;border-color:rgba(var(--dn-gold-rgb),.55);background:none;border-radius:7px}
html.dn-back-rose-noir .card.back::before,:root .card.back.dn-bk-rose-noir::before{
  width:62%;height:56%;border:0;border-radius:0;box-shadow:none;
  transform:translate(-50%,-50%);
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 84'%3E%3Cg fill='none' stroke='%23d8b45a' stroke-width='2.6' stroke-linecap='round'%3E%3Cpath d='M40 30c3-4-1-8-5-6-5 3-4 10 2 12 8 3 16-3 14-11-3-9-14-12-21-6-8 7-6 19 3 24 10 5 21 1 24-8'/%3E%3Cpath d='M24 44c-7 2-11 7-12 13 7-1 12-5 15-10'/%3E%3Cpath d='M56 44c7 2 11 7 12 13-7-1-12-5-15-10'/%3E%3Cpath d='M40 46v14'/%3E%3C/g%3E%3Ctext x='40' y='78' text-anchor='middle' font-family='Georgia,serif' font-style='italic' font-weight='700' font-size='17' fill='%23d8b45a'%3EDC%3C/text%3E%3C/svg%3E") center/contain no-repeat}
/* Gold Royale — gold-dominant lattice with the rose etched in deep bronze */
html.dn-back-gold-royale .card.back,:root .card.back.dn-bk-gold-royale{
  background:
    repeating-linear-gradient(45deg,rgba(120,86,22,.14) 0 7px,transparent 7px 14px),
    repeating-linear-gradient(-45deg,rgba(120,86,22,.10) 0 7px,transparent 7px 14px),
    radial-gradient(125% 95% at 50% 0%,#f0d48a,#d8b45a 58%,#a87f2c);
  border-color:#7c5f1e;
  box-shadow:0 2px 6px rgba(0,0,0,.4),
    inset 0 0 0 2px rgba(74,54,10,.6),
    inset 0 0 0 4px rgba(255,246,216,.55)}
html.dn-back-gold-royale .card.back::after,:root .card.back.dn-bk-gold-royale::after{
  inset:12% 16%;border-color:rgba(74,54,10,.55);
  background:radial-gradient(circle at 50% 50%,rgba(255,246,216,.25),transparent 62%)}
html.dn-back-gold-royale .card.back::before,:root .card.back.dn-bk-gold-royale::before{
  width:60%;height:56%;border:0;border-radius:0;box-shadow:none;
  transform:translate(-50%,-50%);
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 80'%3E%3Cg fill='none' stroke='%23503c0c' stroke-width='3' stroke-linecap='round'%3E%3Cpath d='M40 32c3-4-1-8-5-6-5 3-4 10 2 12 8 3 16-3 14-11-3-9-14-12-21-6-8 7-6 19 3 24 10 5 21 1 24-8'/%3E%3Cpath d='M24 46c-7 2-11 7-12 13 7-1 12-5 15-10'/%3E%3Cpath d='M56 46c7 2 11 7 12 13-7-1-12-5-15-10'/%3E%3Cpath d='M40 48v16'/%3E%3C/g%3E%3C/svg%3E") center/contain no-repeat}
/* Crimson Velvet — quilted deep-red velvet with a gold tufted button center */
html.dn-back-crimson-velvet .card.back,:root .card.back.dn-bk-crimson-velvet{
  background:
    repeating-linear-gradient(45deg,transparent 0 9px,rgba(0,0,0,.28) 9px 10px),
    repeating-linear-gradient(-45deg,transparent 0 9px,rgba(0,0,0,.28) 9px 10px),
    radial-gradient(125% 95% at 50% 0%,#b02237,#7d1522 62%,#4a0c13);
  border-color:#320710;
  box-shadow:0 2px 6px rgba(0,0,0,.45),
    inset 0 0 0 2px rgba(var(--dn-gold-rgb),.5),
    inset 0 0 0 3px rgba(0,0,0,.4),
    inset 0 0 0 4px rgba(var(--dn-gold-rgb),.14)}
html.dn-back-crimson-velvet .card.back::after,:root .card.back.dn-bk-crimson-velvet::after{
  inset:14% 19%;border-color:rgba(var(--dn-gold-rgb),.5);
  background:radial-gradient(circle at 50% 42%,rgba(255,220,150,.14),transparent 60%)}
html.dn-back-crimson-velvet .card.back::before,:root .card.back.dn-bk-crimson-velvet::before{
  width:20%;height:20%;box-shadow:0 0 10px rgba(var(--dn-gold-rgb),.45)}
/* Emerald Lux — deep-green art-deco: gold scallop arcs rising from the foot */
html.dn-back-emerald-lux .card.back,:root .card.back.dn-bk-emerald-lux{
  background:
    repeating-radial-gradient(circle at 50% 130%,rgba(var(--dn-gold-rgb),.18) 0 2px,transparent 2px 13px),
    repeating-linear-gradient(90deg,transparent 0 10px,rgba(255,255,255,.045) 10px 11px),
    radial-gradient(125% 95% at 50% 0%,#1e9161,#146b46 60%,#062c1c);
  border-color:#042315;
  box-shadow:0 2px 6px rgba(0,0,0,.45),
    inset 0 0 0 2px rgba(var(--dn-gold-rgb),.5),
    inset 0 0 0 3px rgba(0,0,0,.4),
    inset 0 0 0 4px rgba(var(--dn-gold-rgb),.14)}
html.dn-back-emerald-lux .card.back::after,:root .card.back.dn-bk-emerald-lux::after{
  inset:13% 17%;border-color:rgba(var(--dn-gold-rgb),.5);
  background:radial-gradient(circle at 50% 50%,rgba(var(--dn-gold-rgb),.12),transparent 62%)}
html.dn-back-emerald-lux .card.back::before,:root .card.back.dn-bk-emerald-lux::before{
  width:22%;height:22%;
  background:linear-gradient(135deg,#eede9d,#c99b2e);border-color:rgba(255,246,216,.7);
  box-shadow:0 0 10px rgba(var(--dn-gold-rgb),.5),inset 0 0 0 2px rgba(6,44,28,.35)}

.card.playable{cursor:pointer;outline:2px solid var(--gold);outline-offset:0;
  box-shadow:0 0 0 2px rgba(var(--dn-gold-rgb),.35),0 6px 16px rgba(var(--dn-gold-rgb),.4)}
.card[role="button"]:focus-visible{outline:3px solid #fff;outline-offset:3px;z-index:110 !important;
  box-shadow:0 0 0 6px rgba(var(--dn-gold-rgb),.82),0 8px 20px rgba(0,0,0,.48)}
.card.playable:active{transform:translateY(-7px)}
.hand.mine .card.playable:active{transform:rotate(var(--fanr,0deg)) translateY(calc(var(--fany,0px) - 8px))}
/* Hover lift ONLY where a real pointer hovers: on iOS/Android WebViews the
   emulated :hover STICKS after a tap, leaving the played-from spot's neighbour
   floating 14px up until the next tap. Touch keeps :active feedback instead. */
@media (hover:hover) and (pointer:fine){
  .card.playable:hover{transform:translateY(-14px)}
  .hand.mine .card.playable:hover{transform:rotate(var(--fanr,0deg)) translateY(calc(var(--fany,0px) - 14px))}
  .hand .card:hover{z-index:100 !important}
  .hand .card.playable:hover{box-shadow:0 0 0 2px rgba(var(--dn-gold-rgb),.5),0 14px 26px rgba(0,0,0,.5)}
}
/* in an overlapping fan, the touched card must jump above its neighbours.
   margin-left is transitioned too so a re-fan (draw/play) slides the horizontal
   spacing in lockstep with the rotation/lift transform instead of snapping the
   gaps instantly while the transform eases (the subtle re-fan desync). */
.hand .card{transition:transform .14s ease,margin-left .14s ease,box-shadow .14s ease,filter .14s ease}
.hand .card:active{z-index:100 !important}

.card.mini{width:clamp(30px,8vw,42px);border-radius:6px}
.card.mini .corner .r{font-size:clamp(8px,2.4vw,11px)}
.card.mini .corner .s{font-size:clamp(7px,1.9vw,9px)}
.card.mini .pip{font-size:clamp(13px,4vw,18px)}

/* compact tables on small screens: keep the opponent chips in ONE row */
@media (max-width:520px){
  /* Web tables use the same compact rail as the native build: no game page may
     create a sideways scroll just because Back/title/New game share one line. */
  .topbar{padding:8px 10px;gap:6px;overflow:hidden}
  .topbar .brand{font-size:15px;white-space:nowrap}
  .topbar > .brand + div{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .topbar .shell-actions{margin-left:auto;gap:4px}
  .topbar .shell-actions .btn{padding:7px 8px;font-size:13px;white-space:nowrap}
  .topbar .shell-new-game .shell-new-copy{font-size:0}
  .topbar .shell-new-game .shell-new-copy::before{content:"New";font-size:13px}
  .hand{gap:4px}
  .felt{padding:14px max(8px,var(--dn-safe-right)) max(14px,var(--dn-safe-bottom)) max(8px,var(--dn-safe-left));border-radius:0}
  .seats{flex-wrap:nowrap;gap:6px}
  .seat{min-width:0;padding:7px 7px;gap:3px;border-radius:12px}
  .seat-av{width:34px;height:34px;font-size:14px;border-width:2px}
  .seat .name{font-size:12px;gap:3px}
  .seat .meta{font-size:12px;padding:2px 6px;line-height:1.25}
  .seat.seat-you .seat-av{width:30px;height:30px}
}

.controls{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;padding:4px 0}
.controls .btn{padding:11px 18px}
.suit-pick{display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
.suit-pick button{width:52px;height:52px;font-size:24px;border-radius:12px;border:1px solid var(--line);
  background:linear-gradient(180deg,#fff,#eef0f5);cursor:pointer;transition:transform .1s,box-shadow .15s}
@media (hover:hover) and (pointer:fine){
  .suit-pick button:hover{transform:translateY(-2px);box-shadow:0 6px 14px rgba(0,0,0,.3)}
}
.suit-pick button:active{transform:translateY(1px)}
.suit-pick button.red{color:#d8283f}

.bid-pick{display:flex;gap:8px;justify-content:center;flex-wrap:wrap}
.bid-pick button{min-width:44px;height:44px;border-radius:11px;border:1px solid var(--line);
  background:var(--bg2);color:var(--text);font-weight:800;cursor:pointer;transition:.12s}
@media (hover:hover) and (pointer:fine){
  .bid-pick button:hover{border-color:var(--gold);color:var(--gold)}
}
.bid-pick button:active{border-color:var(--gold);color:var(--gold)}

/* ---- Combined bid pop-up (Shell.bidPanel) — one panel matching the reference
   Bid Whist bid pop-up: number row [+ direction row] + BID/PASS + countdown ring.
   Options at/below the current high bid are greyed and unselectable. Shared by
   every bidding game so the bid UX is identical on web / iOS / Android. ---- */
.dn-bidpanel{display:flex;flex-direction:column;align-items:center;gap:9px;
  background:linear-gradient(180deg,rgba(26,22,36,.975),rgba(13,11,19,.985));
  border:1px solid rgba(226,196,120,.38);border-radius:18px;padding:15px 18px 17px;
  box-shadow:0 16px 44px rgba(0,0,0,.6);position:relative;max-width:min(94vw,600px);margin:0 auto}
.dn-bidpanel .dn-bp-prompt{color:#eafff3;font-weight:700;font-size:14px;text-align:center;opacity:.95;line-height:1.3;margin-bottom:1px}
.dn-bidpanel .dn-bp-row{display:flex;gap:7px;justify-content:center;flex-wrap:wrap}
.dn-bidpanel .dn-bp-opt{min-width:46px;height:46px;padding:0 13px;border-radius:11px;
  border:1px solid rgba(255,255,255,.2);background:rgba(255,255,255,.07);color:#eef;
  font-weight:800;font-size:16px;cursor:pointer;transition:.12s;letter-spacing:.3px;line-height:1}
.dn-bidpanel .dn-bp-dir{font-size:13px;letter-spacing:.6px;min-width:92px}
.dn-bidpanel .dn-bp-opt.sel{background:#2f6df6;border-color:#7ea3ff;color:#fff;box-shadow:0 0 0 2px rgba(47,109,246,.45)}
.dn-bidpanel .dn-bp-opt.disabled{opacity:.3;filter:grayscale(.65);cursor:not-allowed;pointer-events:none}
/* Bid glossary — plain-language meanings for the words on this panel's own
   buttons, behind a one-tap disclosure. Deliberately quiet: it sits under the
   controls, never above them, so it can never push the bid buttons off a
   short landscape phone. See the o.legend note in game-shell.js. */
.dn-bidpanel .dn-bp-legend{width:100%;max-width:340px;margin-top:1px}
.dn-bidpanel .dn-bp-legend-btn{
  display:flex;align-items:center;justify-content:center;width:100%;min-height:44px;background:none;border:0;padding:3px 2px;
  color:rgba(234,255,243,.72);font-size:12px;font-weight:700;
  text-align:center;cursor:pointer;letter-spacing:.2px}
@media (hover:hover) and (pointer:fine){
  .dn-bidpanel .dn-bp-legend-btn:hover{color:var(--gold)} }
.dn-bidpanel .dn-bp-legend-btn:focus-visible{outline:2px solid var(--gold);outline-offset:2px;border-radius:8px}
.dn-bidpanel .dn-bp-legend-body{
  margin-top:5px;padding:9px 11px;border-radius:11px;
  background:rgba(6,10,14,.42);border:1px solid rgba(255,255,255,.09);
  max-height:31vh;overflow-y:auto;-webkit-overflow-scrolling:touch}
.dn-bidpanel .dn-bp-legend-row{
  display:flex;gap:8px;align-items:baseline;font-size:12.5px;line-height:1.4;
  padding:2.5px 0;text-align:left}
.dn-bidpanel .dn-bp-legend-row b{
  flex:0 0 auto;min-width:78px;color:var(--gold);font-weight:800;
  font-size:11.5px;letter-spacing:.05em;text-transform:uppercase}
.dn-bidpanel .dn-bp-legend-row span{flex:1 1 auto;color:rgba(234,255,243,.86)}
@media (max-height:430px){
  /* Landscape phones: the glossary must never cost the buttons their room. */
  .dn-bidpanel .dn-bp-legend-body{max-height:26vh;padding:7px 9px}
  .dn-bidpanel .dn-bp-legend-row{font-size:11.5px;padding:1.5px 0}
}
@media (hover:hover) and (pointer:fine){ .dn-bidpanel .dn-bp-opt:not(.disabled):not(.sel):hover{border-color:var(--gold);color:var(--gold)} }
.dn-bidpanel .dn-bp-toggle{border:1px dashed rgba(226,196,120,.55);background:transparent;color:#e2c478;
  border-radius:10px;height:44px;min-height:44px;padding:0 13px;font-weight:700;font-size:13px;cursor:pointer}
.dn-bidpanel .dn-bp-actions{display:flex;gap:11px;margin-top:3px}
.dn-bidpanel .dn-bp-bid,.dn-bidpanel .dn-bp-pass{height:46px;min-width:104px;border-radius:12px;
  font-weight:800;font-size:15px;cursor:pointer;border:none;letter-spacing:.6px}
.dn-bidpanel .dn-bp-bid{background:#2fae5e;color:#052e15}
.dn-bidpanel .dn-bp-bid.disabled{background:#39473f;color:#8aa295;cursor:not-allowed;pointer-events:none;opacity:.75}
.dn-bidpanel .dn-bp-pass{background:#c0392b;color:#fff}
.dn-bidpanel .dn-bp-ring{position:absolute;top:-15px;right:-13px;width:38px;height:38px;pointer-events:none}
.dn-bidpanel .dn-bp-ring svg{width:38px;height:38px;transform:rotate(-90deg)}
.dn-bidpanel .dn-bp-ring .dn-bp-seconds{position:absolute;inset:0;display:grid;place-items:center;
  color:#fff2bf;font-size:11px;font-weight:900;line-height:1;font-variant-numeric:tabular-nums;
  text-shadow:0 1px 2px rgba(0,0,0,.9)}
.dn-bidpanel .dn-bp-ring circle{fill:none;stroke-width:3.4}
.dn-bidpanel .dn-bp-ring circle.bg{stroke:rgba(255,255,255,.16)}
.dn-bidpanel .dn-bp-ring circle.fg{stroke:#e2c478;stroke-linecap:round;stroke-dasharray:100.53;
  animation:dn-bp-count linear forwards}
@keyframes dn-bp-count{from{stroke-dashoffset:0}to{stroke-dashoffset:100.53}}
/* the panel carries its own surface — neutralise the container's landscape pill */
#bidbar:has(.dn-bidpanel),.bid-pick:has(.dn-bidpanel){background:transparent!important;border:none!important;
  box-shadow:none!important;backdrop-filter:none!important;padding:0!important}
/* keep the pop-up's OWN button sizing in landscape (win over the compact
   .bid-pick/#bidbar button rules). Margin is 0 because the host below owns the
   placement; the width cap keeps a 14-number Spades row from spilling over the
   West/East seat chips (and their bid bubbles). */
html.dn-land .felt.dn-table .dn-bidpanel{margin:0;padding:10px 14px 12px;gap:6px;max-width:min(74%,600px)}
html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-opt{min-width:42px;height:40px;font-size:15px;padding:0 11px}
html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-dir{min-width:86px;font-size:12.5px}
html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-bid,
html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-pass{height:40px;min-width:96px;font-size:14px}
html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-prompt{font-size:12.5px}

.hidden{display:none}

/* entrance for dealt hands — a card slides up from the deck and settles INTO its
   fan position. Landing on the fan (rotate/translateY vars) instead of `none` matters
   because animation-fill-mode:both holds the last keyframe: a flat `transform:none`
   here was overriding fanHand's arc, leaving 13-card hands packed flat until a
   re-render. Non-fanned hands (vars unset → 0deg/0px) still settle upright. */
@keyframes dealIn{
  0%{opacity:0;transform:perspective(640px) translateY(34px) scale(.78) rotate(-5deg) rotateY(76deg);
    filter:drop-shadow(0 2px 2px rgba(0,0,0,.18))}
  56%{opacity:1;transform:perspective(640px) translateY(-8px) scale(.98) rotate(2deg) rotateY(18deg);
    filter:drop-shadow(0 14px 12px rgba(0,0,0,.42))}
  88%{opacity:1;transform:perspective(640px) translateY(3px) scale(1.02) rotateY(0);
    filter:drop-shadow(0 7px 6px rgba(0,0,0,.32))}
  100%{opacity:1;transform:rotate(var(--fanr,0deg)) translateY(var(--fany,0px));
    filter:drop-shadow(0 2px 2px rgba(0,0,0,.16))}
}
.hand .card{animation:dealIn var(--dn-card-deal-duration) cubic-bezier(.18,.66,.2,1) both}
/* cards kept across a re-render (Shell.syncHand) never replay the entrance;
   genuinely new cards get their stagger via inline animation-delay */
.hand .card.dealt{animation:none}
/* Shell.dealNewCards flies the physical card one at a time, while this real
   back layer turns away near landing to reveal the face beneath it. Keeping the
   flip on a pseudo-element avoids mirroring or exposing the face during travel. */
.card.dn-card-deal-flip,.ce-live-card.dn-card-deal-flip{transform-style:preserve-3d}
.card.dn-card-deal-flip::before,.ce-live-card.dn-card-deal-flip::before{
  content:"";position:absolute;z-index:12;inset:-1px;border-radius:inherit;
  pointer-events:none;transform-origin:50% 50%;backface-visibility:hidden;
  border:1px solid rgba(240,212,138,.7);
  background:
    linear-gradient(45deg,rgba(240,212,138,.16) 25%,transparent 25% 75%,rgba(240,212,138,.16) 75%),
    linear-gradient(-45deg,rgba(240,212,138,.11) 25%,transparent 25% 75%,rgba(240,212,138,.11) 75%),
    radial-gradient(circle at 50% 50%,#314886 0 16%,#162a63 17% 48%,#0c1838 49% 100%);
  background-size:14px 14px,14px 14px,100% 100%;
  animation:dn-card-deal-faceback var(--dn-card-deal-duration,390ms)
    cubic-bezier(.2,.68,.22,1) var(--dn-card-deal-delay,0ms) both}
@keyframes dn-card-deal-faceback{
  0%,54%{opacity:1;transform:perspective(640px) rotateY(0deg)}
  72%{opacity:1;transform:perspective(640px) rotateY(88deg)}
  73%,100%{opacity:0;transform:perspective(640px) rotateY(90deg)}
}
/* reveal flip — applied by Shell.syncHand when a back card turns face up,
   or by games flipping a card in place */
@keyframes cardFlip{
  0%{transform:perspective(640px) rotateY(88deg) scale(.94);opacity:.35;
    filter:drop-shadow(0 2px 2px rgba(0,0,0,.18))}
  62%{transform:perspective(640px) rotateY(-7deg) scale(1.025);opacity:1;
    filter:drop-shadow(0 13px 11px rgba(0,0,0,.4))}
  100%{transform:rotate(var(--fanr,0deg)) translateY(var(--fany,0px));opacity:1;
    filter:drop-shadow(0 2px 2px rgba(0,0,0,.16))}
}
.card.card-flip,.hand .card.card-flip{animation:cardFlip var(--dn-card-flip-duration) cubic-bezier(.2,.7,.2,1) both}
/* pass-selection highlight (hearts) — class-based so syncHand carries it over */
.card.pass-sel{outline:3px solid #e11d48;outline-offset:0}
.hand.mine .card.pass-sel{
  transform:rotate(var(--fanr,0deg)) translateY(calc(var(--fany,0px) - 8px)) scale(1.04);
  box-shadow:0 9px 18px rgba(225,29,72,.42);z-index:90!important}

/* ============ shared shell (game-shell.js) ============ */
.shell-gear{font-size:16px;padding:9px 12px;line-height:1}

/* toast */
.shell-toast{position:fixed;left:50%;bottom:26px;transform:translateX(-50%) translateY(20px);
  background:rgba(14,16,22,.62);color:#fff;border:1px solid rgba(255,255,255,.08);border-radius:999px;
  -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  padding:9px 16px;font-weight:600;font-size:13.5px;z-index:120;opacity:0;pointer-events:none;
  transition:opacity .2s,transform .2s;max-width:86vw;text-align:center;box-shadow:0 6px 20px rgba(0,0,0,.35)}
.shell-toast.show{opacity:1;transform:translateX(-50%) translateY(0)}

/* modal / settings drawer */
.shell-modal-back{position:fixed;inset:0;background:#11131a;
  display:block;z-index:130;padding:0;animation:fadeIn .15s ease;overflow:hidden}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
.shell-modal{background:
    radial-gradient(1000px 520px at 50% -15%,rgba(124,82,186,.18),transparent 64%),
    linear-gradient(180deg,var(--panel),#11131a);
  border:0;border-radius:0;
  padding:calc(20px + var(--dn-safe-top)) calc(clamp(18px,4vw,54px) + var(--dn-safe-right))
    calc(20px + var(--dn-safe-bottom)) calc(clamp(18px,4vw,54px) + var(--dn-safe-left));
  width:100%;height:100%;max-width:none!important;max-height:none!important;
  overflow-y:auto;-webkit-overflow-scrolling:touch;box-shadow:none;
  animation:sheetIn .22s cubic-bezier(.2,.72,.24,1)}
@keyframes sheetIn{from{opacity:.55;transform:translateY(12px)}to{opacity:1;transform:none}}
.shell-modal h3{margin:0 0 14px;font-size:20px}
/* The 18+ acknowledgement is a short, decision-shaped prompt, not a settings
   drawer, so it does not use the full-bleed sheet the other shell modals share.
   Centring it puts the question and its three answers in one glance instead of
   stranding them above an empty screen. It stays scrollable so a long
   translation, a large accessibility font, or a 390px-tall landscape phone can
   never push the buttons out of reach. */
.shell-modal-back.deniro-age-notice{display:flex;align-items:center;justify-content:center;
  overflow-y:auto;background:rgba(9,10,15,.9);
  padding:calc(18px + var(--dn-safe-top)) calc(18px + var(--dn-safe-right))
    calc(18px + var(--dn-safe-bottom)) calc(18px + var(--dn-safe-left))}
.deniro-age-notice .shell-modal{margin:auto;
  width:min(100%,430px);height:auto;
  max-width:min(100%,430px)!important;max-height:none!important;
  padding:26px 24px;border-radius:20px;
  border:1px solid rgba(var(--dn-gold-rgb),.30);
  box-shadow:0 26px 64px rgba(0,0,0,.62),0 0 0 1px rgba(0,0,0,.35)}
.deniro-age-notice .shell-modal h3{font-size:23px}
/* The 18+ notice is the FIRST thing every new customer touches, and its two
   buttons measured 135x36 and 99x38 in the QA pass. Bring them to a comfortable
   target and give them room from each other, so "I am 18 or older" cannot be
   mis-tapped as "I'm not 18" on a phone. */
.deniro-age-notice .shell-modal .row,
.deniro-age-notice .shell-modal .controls{gap:12px;flex-wrap:wrap}
.deniro-age-notice .shell-modal button,
.deniro-age-notice .shell-modal .btn{min-height:52px;padding:12px 22px;font-size:17px}
/* showAgeNotice() focuses the box so screen readers announce the dialog. That
   is a programmatic target, not a user-navigated control, and now that the box
   is a centred card the UA focus ring is a bright rectangle around the whole
   dialog. Drop the ring on the container only; every button keeps its own. */
.deniro-age-notice .shell-modal:focus,
.deniro-age-notice .shell-modal:focus-visible{outline:none}
html.deniro-age-failclosed{overflow:hidden}
html.deniro-age-failclosed body > :not(#deniroAgeNotice){
  pointer-events:none!important;user-select:none!important}
.deniro-age-failure .shell-modal{border-color:rgba(224,86,86,.72)}
.deniro-age-failure .shell-modal h3{color:#ff9c9c}
@media (max-height:430px){
  /* Landscape phones: tighten the box so all three answers stay on screen. */
  .deniro-age-notice .shell-modal{padding:18px 20px;border-radius:16px}
  .deniro-age-notice .shell-modal h3{font-size:19px;margin-bottom:9px}
  .deniro-age-copy{font-size:14px;line-height:1.45}
  .deniro-age-actions{margin-top:13px}
}
.deniro-age-copy{margin:0;color:var(--muted);font-size:15px;line-height:1.55}
.deniro-age-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:18px}
.deniro-age-actions .btn{text-decoration:none}
.deniro-age-actions .deniro-age-exit{color:#fff;border-color:rgba(255,255,255,.42)}
/* Compact score rails, seat avatars, and book/balance chips all open the same
   full table snapshot. Their visual footprint stays unchanged; keyboard focus
   and a pointer cursor are the only added affordances. */
.dn-a11y-only{
  position:absolute!important;width:1px!important;height:1px!important;
  padding:0!important;margin:-1px!important;overflow:hidden!important;
  clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}
[data-dn-scoreboard-trigger="true"]{
  cursor:pointer;pointer-events:auto!important;-webkit-tap-highlight-color:transparent}
[data-dn-scoreboard-trigger="true"]:focus-visible{
  outline:3px solid rgba(241,205,112,.9);outline-offset:3px}
.shell-modal-back.dn-scoreboard-back{
  display:flex;align-items:center;justify-content:center;overflow-y:auto;
  background:rgba(5,7,11,.88);backdrop-filter:blur(10px);
  padding:calc(16px + var(--dn-safe-top)) calc(16px + var(--dn-safe-right))
    calc(16px + var(--dn-safe-bottom)) calc(16px + var(--dn-safe-left))}
.dn-scoreboard{
  margin:auto;width:min(100%,640px);height:auto;max-width:640px!important;
  max-height:min(84dvh,720px)!important;padding:0!important;overflow:hidden!important;
  border:1px solid rgba(var(--dn-gold-rgb),.36)!important;border-radius:22px!important;
  background:linear-gradient(160deg,#1b1e27,#101219 72%)!important;
  box-shadow:0 28px 76px rgba(0,0,0,.7),inset 0 1px rgba(255,255,255,.05)!important}
.dn-scoreboard:focus,.dn-scoreboard:focus-visible{outline:none}
.dn-scoreboard-head{
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:20px 20px 16px;border-bottom:1px solid rgba(255,255,255,.08);
  background:linear-gradient(180deg,rgba(var(--dn-gold-rgb),.09),transparent)}
.dn-scoreboard-head h3{margin:2px 0 0;font-size:clamp(20px,4.5vw,27px);line-height:1.12}
.dn-scoreboard-eyebrow{
  display:block;color:var(--gold);font-size:11px;font-weight:900;
  letter-spacing:.16em;text-transform:uppercase}
.dn-scoreboard-close{
  flex:0 0 auto;width:44px;height:44px;padding:0!important;border-radius:14px!important;
  font-size:28px!important;line-height:1!important}
.dn-scoreboard-body{
  min-height:0;max-height:calc(min(84dvh,720px) - 82px);overflow-y:auto;
  -webkit-overflow-scrolling:touch;padding:18px 20px 22px}
.dn-scoreboard-summary{
  padding:15px 16px;margin:0 0 14px;border:1px solid rgba(var(--dn-gold-rgb),.22);
  border-radius:16px;background:rgba(var(--dn-gold-rgb),.07)}
.dn-scoreboard-summary span{
  color:var(--gold);font-size:11px;font-weight:900;letter-spacing:.09em;text-transform:uppercase}
.dn-scoreboard-summary p{margin:5px 0 0;color:#f6f1e7;font-size:15px;font-weight:800;line-height:1.45}
.dn-scoreboard-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
.dn-scoreboard-row{
  display:grid;grid-template-columns:46px minmax(0,1fr);align-items:center;gap:11px;
  min-width:0;padding:12px;border:1px solid rgba(255,255,255,.09);
  border-radius:15px;background:rgba(255,255,255,.035)}
.dn-scoreboard-avatar{
  display:grid;place-items:center;width:46px;height:46px;border-radius:50%;
  border:1px solid rgba(var(--dn-gold-rgb),.36);background:radial-gradient(circle at 35% 28%,#3d4351,#171a22 72%);
  color:#f1cd70;font-size:11px;font-weight:950;letter-spacing:.04em}
.dn-scoreboard-row strong{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dn-scoreboard-row p,.dn-scoreboard-note,.dn-scoreboard-empty{
  margin:4px 0 0;color:var(--muted);font-size:12.5px;line-height:1.42}
.dn-scoreboard-note{
  margin-top:14px;padding:11px 12px;border-radius:12px;background:rgba(255,255,255,.04)}
.dn-scoreboard-empty{padding:24px 8px;text-align:center;font-size:14px}
@media (max-width:560px){
  .dn-scoreboard-back{align-items:flex-end!important;padding:0!important}
  .dn-scoreboard{
    width:100%;max-width:none!important;max-height:min(88dvh,760px)!important;
    margin:auto 0 0;border-radius:22px 22px 0 0!important;
    border-left:0!important;border-right:0!important;border-bottom:0!important}
  .dn-scoreboard-head{
    padding:17px calc(16px + var(--dn-safe-right)) 14px calc(16px + var(--dn-safe-left))}
  .dn-scoreboard-body{
    max-height:calc(min(88dvh,760px) - 76px);
    padding:15px calc(15px + var(--dn-safe-right))
      calc(18px + var(--dn-safe-bottom)) calc(15px + var(--dn-safe-left))}
  .dn-scoreboard-list{grid-template-columns:1fr}
}
@media (max-height:430px) and (orientation:landscape){
  .dn-scoreboard{max-height:calc(100dvh - 12px)!important}
  .dn-scoreboard-head{padding-top:12px;padding-bottom:10px}
  .dn-scoreboard-head h3{font-size:18px}
  .dn-scoreboard-body{max-height:calc(100dvh - 72px);padding-top:10px}
  .dn-scoreboard-list{grid-template-columns:repeat(2,minmax(0,1fr))}
  .dn-scoreboard-row{padding:8px;grid-template-columns:38px minmax(0,1fr)}
  .dn-scoreboard-avatar{width:38px;height:38px}
}
/* Shared yes/no dialog (quit confirmation). Same centred-card treatment as the
   age notice: a decision needs to sit in front of the player, not above them. */
.shell-modal-back.dn-confirm-back{display:flex;align-items:center;justify-content:center;
  overflow-y:auto;background:rgba(9,10,15,.9);
  padding:calc(18px + var(--dn-safe-top)) calc(18px + var(--dn-safe-right))
    calc(18px + var(--dn-safe-bottom)) calc(18px + var(--dn-safe-left))}
.dn-confirm-back .shell-modal{margin:auto;width:min(100%,410px);height:auto;
  max-width:min(100%,410px)!important;max-height:none!important;
  padding:24px 22px;border-radius:20px;border:1px solid rgba(var(--dn-gold-rgb),.30);
  box-shadow:0 26px 64px rgba(0,0,0,.62)}
.dn-confirm-back .shell-modal:focus,.dn-confirm-back .shell-modal:focus-visible{outline:none}
.dn-confirm-back .shell-modal h3{font-size:21px;margin:0 0 10px}
.dn-confirm-body{margin:0;color:var(--muted);font-size:15px;line-height:1.5}
.dn-confirm-actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:18px}
.dn-confirm-actions .btn{flex:1 1 auto;min-height:46px;font-size:15px}
/* The destructive answer stays legible but never competes with "keep playing". */
.dn-confirm-actions .dn-confirm-danger{color:#ffb9b0;border-color:rgba(255,120,104,.5)}
.dn-confirm-actions .dn-confirm-danger:active{background:rgba(255,120,104,.14)}
@media (max-height:430px){
  .dn-confirm-back .shell-modal{padding:16px 18px;border-radius:16px}
  .dn-confirm-back .shell-modal h3{font-size:18px}
  .dn-confirm-body{font-size:14px;line-height:1.4}
  .dn-confirm-actions{margin-top:12px}
  .dn-confirm-actions .btn{min-height:42px}
}
/* ---- Coin-amount slider (solo custom stake + online custom table) ----------
   A bare range input renders a ~4px hairline track with a ~16px thumb. The
   element was already 44px tall, so a tap anywhere on it DID seek — the
   platform does that for free — but nothing on screen said so, and players
   aimed at the hairline and missed. So the fix is to make the target look like
   the size it already is: a thick rounded track and a thumb big enough to
   catch with a thumb-tip. Vendor pseudo-elements are the only way to size a
   native thumb, and each browser ignores the others' rules, so both are
   listed rather than combined.

   Native seek-on-tap is kept rather than replaced with a pointer handler: it
   already moves the handle to the finger, and it brings keyboard (arrows,
   Home/End) and screen-reader support that a custom handler would have to
   reimplement. Only the styling changes. */
.dn-coin-slider{
  -webkit-appearance:none;appearance:none;
  width:100%;min-height:56px;                    /* generous strike area */
  background:transparent;cursor:pointer;
  /* accent-color still paints the filled portion on browsers that honour it */
  accent-color:var(--gold);
  touch-action:pan-y;                            /* let a vertical scroll still work */
}
.dn-coin-slider:focus{outline:none}
.dn-coin-slider:focus-visible{outline:3px solid #fff;outline-offset:4px;border-radius:12px}
/* --- WebKit / Blink (iOS WKWebView + Android WebView) --- */
.dn-coin-slider::-webkit-slider-runnable-track{
  height:16px;border-radius:999px;
  background:linear-gradient(180deg,#0a0e16,#141a26);
  border:1px solid rgba(var(--dn-gold-rgb),.42);
  box-shadow:inset 0 2px 5px rgba(0,0,0,.6)}
.dn-coin-slider::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;
  width:36px;height:36px;border-radius:50%;
  /* centre a 36px thumb on a 16px track: (16 - 36) / 2 */
  margin-top:-10px;
  background:radial-gradient(circle at 38% 32%,#fff3cf,#e5b84c 58%,#b98b26);
  border:2px solid #fffaf0;
  box-shadow:0 3px 10px rgba(0,0,0,.55),0 0 0 1px rgba(0,0,0,.35)}
.dn-coin-slider:active::-webkit-slider-thumb{
  background:radial-gradient(circle at 38% 32%,#fffdf5,#f0cd6e 58%,#c99a2f);
  box-shadow:0 4px 14px rgba(0,0,0,.6),0 0 0 6px rgba(var(--dn-gold-rgb),.26)}
/* --- Gecko (the web build in Firefox) --- */
.dn-coin-slider::-moz-range-track{
  height:16px;border-radius:999px;
  background:linear-gradient(180deg,#0a0e16,#141a26);
  border:1px solid rgba(var(--dn-gold-rgb),.42)}
.dn-coin-slider::-moz-range-progress{
  height:16px;border-radius:999px;background:linear-gradient(180deg,#f0d48a,#c9a347)}
.dn-coin-slider::-moz-range-thumb{
  width:34px;height:34px;border-radius:50%;
  background:radial-gradient(circle at 38% 32%,#fff3cf,#e5b84c 58%,#b98b26);
  border:2px solid #fffaf0;
  box-shadow:0 3px 10px rgba(0,0,0,.55)}
html.dn-large-ui .dn-coin-slider{min-height:64px}
html.dn-large-ui .dn-coin-slider::-webkit-slider-thumb{width:42px;height:42px;margin-top:-13px}
html.dn-large-ui .dn-coin-slider::-moz-range-thumb{width:40px;height:40px}
@media (prefers-reduced-motion:reduce){.dn-coin-slider{transition:none}}
.shell-rows{display:flex;flex-direction:column;gap:4px}
/* Settings used to be one undifferentiated list of a dozen switches, so finding
   the one row you wanted meant reading all of them. Each heading names the
   handful of rows under it; the gold rule gives the eye somewhere to stop. */
.shell-group{margin:16px 2px 2px;padding-bottom:5px;font-size:12.5px;font-weight:900;
  letter-spacing:.09em;text-transform:uppercase;color:var(--gold);
  border-bottom:1px solid rgba(var(--dn-gold-rgb),.32)}
.shell-group:first-child{margin-top:4px}
.shell-row{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:11px 4px;border-bottom:1px solid rgba(255,255,255,.05);font-size:15px;font-weight:600}
.shell-switch{width:46px;height:27px;border-radius:99px;border:1px solid var(--line);background:#20242e;
  position:relative;cursor:pointer;transition:.16s;flex:0 0 auto}
.shell-switch::after{content:"";position:absolute;top:2px;left:2px;width:21px;height:21px;border-radius:50%;
  background:#8b93a3;transition:.16s}
.shell-switch.on{background:rgba(var(--dn-gold-rgb),.3);border-color:var(--gold-deep)}
.shell-switch.on::after{left:21px;background:var(--gold)}
.shell-select{background:#20242e;color:var(--text);border:1px solid var(--line);border-radius:9px;
  padding:8px 10px;font-weight:600;font-size:14px;cursor:pointer}

/* result overlay */
.shell-result{text-align:center}
.shell-result-emoji{font-size:52px;margin-bottom:4px;display:inline-block}
/* the trophy pops + glows on a win */
.shell-result.win .shell-result-emoji{animation:trophyPop .6s cubic-bezier(.2,.9,.3,1.5) both;
  filter:drop-shadow(0 0 16px rgba(240,212,138,.75))}
@keyframes trophyPop{0%{transform:scale(0) rotate(-22deg)}55%{transform:scale(1.28) rotate(7deg)}100%{transform:scale(1) rotate(0)}}
.shell-result h3{font-size:26px;margin:2px 0}
.shell-result.win h3{background:linear-gradient(90deg,#f0d48a,#fff6d8,#e0b34e);-webkit-background-clip:text;
  background-clip:text;-webkit-text-fill-color:transparent}
.shell-result.win{animation:dnWinFlourish 1.15s cubic-bezier(.2,.7,.22,1) both}
@keyframes dnWinFlourish{
  0%{filter:brightness(.92)}
  48%{filter:brightness(1.16) drop-shadow(0 0 24px rgba(var(--dn-gold-rgb),.28))}
  100%{filter:brightness(1)}
}
.shell-result p{color:var(--muted);margin:6px 0 16px;font-size:15px;white-space:pre-line}

/* The result is a card, not a drawer.
   .shell-modal is a full-bleed sheet (width/height 100%) because it was built
   for Settings. A win inherited that geometry, so a two-line result sat at the
   top-left of a full-screen opaque panel with a large empty area under it and
   the table hidden completely — the biggest moment in the game presented as a
   navigation event. Same reasoning, and the same shape, as the 18+ notice and
   the scoreboard above: centre it, bound it, and keep the felt visible behind
   so the win happens ON the table you were just playing. */
.shell-modal-back.dn-result-back{
  display:flex;align-items:center;justify-content:center;overflow-y:auto;
  background:rgba(7,9,14,.78);-webkit-backdrop-filter:blur(9px);backdrop-filter:blur(9px);
  padding:calc(18px + var(--dn-safe-top)) calc(18px + var(--dn-safe-right))
    calc(18px + var(--dn-safe-bottom)) calc(18px + var(--dn-safe-left))}
.dn-result-back .shell-modal{
  margin:auto;width:min(100%,440px);height:auto;
  max-width:min(100%,440px)!important;max-height:none!important;
  padding:30px 26px 26px;border-radius:22px;
  border:1px solid rgba(var(--dn-gold-rgb),.30);
  box-shadow:0 28px 72px rgba(0,0,0,.66),inset 0 1px rgba(255,255,255,.05)}
.dn-result-back .shell-result.win{
  border-color:rgba(var(--dn-gold-rgb),.52);
  box-shadow:0 28px 72px rgba(0,0,0,.66),0 0 46px rgba(var(--dn-gold-rgb),.16),
    inset 0 1px rgba(255,255,255,.06)}
.dn-result-back .shell-modal:focus,
.dn-result-back .shell-modal:focus-visible{outline:none}
/* Buttons were inline-with-margin-left in a left-aligned drawer. In a centred
   card they need to be a real row, or a long "Play again" + Share + Lobby wraps
   into a ragged stack. */
.dn-result-back .shell-result .btn{margin:0!important}
.dn-result-back .shell-result{
  display:flex;flex-direction:column;align-items:center}
.dn-result-back .shell-result > .btn{width:auto}
.dn-result-back .shell-result h3,
.dn-result-back .shell-result p{width:100%}
@media (max-height:430px){
  /* Landscape phones: the card must not push its own buttons off-screen. */
  .dn-result-back .shell-modal{padding:18px 20px 16px;border-radius:16px}
  .dn-result-back .shell-result-emoji{font-size:38px}
  .dn-result-back .shell-result h3{font-size:21px}
  .dn-result-back .shell-result p{margin:4px 0 10px}
}

/* A named feat (Bid Whist Boston): a gold ribbon above the trophy. Rare by
   design — see the opts.big note in game-shell.js. */
.shell-bigwin{
  align-self:center;display:inline-block;margin:0 0 10px;padding:5px 16px;
  border-radius:999px;font-size:12px;font-weight:900;letter-spacing:.20em;
  color:#2a1e04;background:linear-gradient(100deg,#e8c268,#fff4d2 46%,#dfae46);
  box-shadow:0 6px 20px rgba(var(--dn-gold-rgb),.42);
  animation:dnBigWinIn .7s cubic-bezier(.2,.9,.28,1.4) both}
@keyframes dnBigWinIn{
  0%{transform:scale(.4) translateY(10px);opacity:0}
  60%{transform:scale(1.14) translateY(0);opacity:1}
  100%{transform:scale(1)}}
.shell-result.big .shell-result-emoji{font-size:62px}

/* confetti + coin rain (win celebration) */
.shell-confetti{position:fixed;inset:0;pointer-events:none;z-index:140;overflow:hidden}
.shell-confetti i{position:absolute;top:-16px;width:9px;height:14px;border-radius:2px;opacity:.95;
  will-change:transform,opacity;animation:confFall 2.8s linear forwards}
@keyframes confFall{to{transform:translateY(108vh) rotate(680deg);opacity:.7}}
/* Coins: single soft shadow instead of a triple inset+outer stack — box-shadow
   on 16 moving GPU layers was the dominant per-frame raster cost on Android
   WebView. will-change pre-promotes the layer so all coins don't hitch on frame 0. */
.shell-confetti .coin{position:absolute;top:-30px;border-radius:50%;opacity:0;
  background:radial-gradient(circle at 34% 30%,#fff2c4,#e8bd57 58%,#b8862a);
  box-shadow:inset 0 -2px 4px rgba(120,80,20,.45);
  will-change:transform,opacity;animation:coinFall 2.4s cubic-bezier(.3,.1,.5,1) forwards}
@keyframes coinFall{0%{transform:translateY(0) rotateY(0);opacity:0}
  9%{opacity:1}90%{opacity:1}100%{transform:translateY(114vh) rotateY(900deg);opacity:.6}}

/* turn indicator + scoreboard bits games can use */
.turn-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--gold);
  margin-left:6px;box-shadow:0 0 8px var(--gold);animation:pulseDot 1s infinite}
@keyframes pulseDot{0%,100%{opacity:.4}50%{opacity:1}}
.card.hint{outline:2px dashed var(--gold);outline-offset:2px;animation:hintPulse 1s ease infinite}
@keyframes hintPulse{0%,100%{outline-color:rgba(var(--dn-gold-rgb),.4)}50%{outline-color:rgba(var(--dn-gold-rgb),1)}}

/* System reduced-motion is a release-wide contract, not a per-game courtesy.
   Suppress entrances, flips, modal/trophy motion, confetti, and continuous
   status pulses while leaving the final informational state fully visible. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    scroll-behavior:auto!important;
    transition-duration:.01ms!important;
    transition-delay:0s!important;
    animation-duration:.01ms!important;
    animation-delay:0s!important;
    animation-iteration-count:1!important}
  .shell-confetti{display:none!important}
  .hand .card,.card.card-flip,.hand .card.card-flip,
  .card.dn-card-deal-flip::before,.ce-live-card.dn-card-deal-flip::before,
  .shell-modal-back,.shell-modal,.shell-result.win .shell-result-emoji,
  .shell-bigwin,
  .turn-dot,.card.hint,.mp-timer.crit{
    animation:none!important}
  .hand .card{opacity:1}
}
/* Turning Animations OFF in Settings has to mean the same thing for the deal as
   the system preference above does. dealNewCards already skips the flight and
   the flip, but the generic dealIn entrance is pure CSS and kept fading a
   whole hand in over half a second after the player asked for no motion. */
html.dn-anim-off .hand .card,
html.dn-anim-off .card.card-flip,
html.dn-anim-off .card.dn-card-deal-flip::before,
html.dn-anim-off .ce-live-card.dn-card-deal-flip::before{animation:none!important}
html.dn-anim-off .hand .card{opacity:1}
.bagcount{display:inline-flex;align-items:center;gap:4px;font-size:13px;color:#bfe9d5}
.scorebar{display:flex;gap:14px;justify-content:center;flex-wrap:wrap;font-size:14px;font-weight:700;color:#dff5ea}
.scorebar .me{color:var(--gold)}
.breakdown{background:rgba(5,17,12,.9);border:1px solid rgba(255,255,255,.08);border-radius:12px;
  padding:12px 14px;margin:8px auto;max-width:460px;font-size:14px;line-height:1.7;color:#eafff3}
.breakdown b{color:var(--gold)}

/* Joker cards use the same detailed printed treatment as the courts. Big Joker
   is crimson/gold; Little Joker is navy/silver. The J + star indices stay clear
   in a hand while the name prints along the portrait edge. */
.card.joker{background:linear-gradient(158deg,#fffdf5,#f2ead2);border-color:#d9c48a}
.card.joker.big .corner{color:#c6253d}
.card.joker:not(.big) .corner{color:#354c83}
.card.joker .pip.joker-pip{opacity:1}
.card.joker.big{box-shadow:0 2px 6px rgba(0,0,0,.32),inset 0 0 0 2px rgba(216,40,63,.5)}
.card.joker:not(.big){box-shadow:0 2px 6px rgba(0,0,0,.32),inset 0 0 0 2px rgba(150,150,160,.5)}

/* Engagement: win-streak badge + warm-up note (added by the engagement layer) */
.shell-streak{ display:inline-block; margin:6px auto 2px; padding:5px 12px; border-radius:999px;
  background:linear-gradient(90deg,#e0565622,#d8b45a33); color:#f0d48a; font-weight:800;
  letter-spacing:.4px; font-size:13px; }
.shell-note{ margin:8px 2px 0; font-size:12px; line-height:1.45; color:var(--muted,#9aa2b1); opacity:.85; }


/* ============================================================================
   Native landscape "REAL TABLE" — partner across the top, opponents left &
   right, YOU at the bottom, trick in the middle of an oval felt (matches the
   reference tables). Guarded by is-native-game (native app only) + landscape +
   the .dn-table tag game-shell.js adds ONLY to the 4-seat trick games.
   Portrait and the public web are completely unaffected.
   ============================================================================ */
/* Force-landscape: when a table game opens in portrait on the device, the webview
   won't honor screen.orientation.lock — so game-shell.js adds html.dn-rot and we
   rotate the WHOLE view 90°. Rotating <body> (not just .table) makes position:fixed
   modals — the age gate, settings drawer, toasts — relative to the rotated body, so
   they stay centered in the landscape frame instead of drifting off. */
html.dn-rot, html.dn-rot body { overflow: hidden; margin: 0; }
html.dn-rot body {
  position: fixed; top: 0; left: 100vw;
  /* body normally has min-height:100dvh. In a portrait fallback that floor is
     the unrotated long edge, producing a square surface whose left side is
     cropped after rotation. The rotated frame's explicit 100vw height owns it. */
  width: 100vh; height: 100vw; min-height: 0;
  transform-origin: 0 0; -webkit-transform-origin: 0 0;
  transform: rotate(90deg); -webkit-transform: rotate(90deg);
}
html.dn-rot .table { width:100%;height:100%;min-height:0; }

html.dn-land .table { height: 100dvh; overflow: hidden; }
html.dn-rot.dn-land .table { width:100%;height:100%;min-height:0; }

/* Narrow UPRIGHT portrait: bound the table to the viewport and let the felt
   scroll inside it.
   ----------------------------------------------------------------------
   .table only ever had min-height:100dvh (line ~323). A bounded height was
   given to landscape (above) and to the rotated phone frame, but never to a
   plain upright portrait window — so there it is free to grow past the
   viewport, and it does. Measured at 390x844 mid-bid in Spades: the header
   takes 101px, leaving 743, and the felt's children want 835 (seats 83,
   message 74, bid panel 268, hand 196, score 104, the You button 44, plus
   30 of gaps and 20 of padding). The page ends up 936 tall and the whole
   document scrolls, which is what mobile-geometry has been failing on.

   Shrinking the children is the wrong lever: every one of them is either a
   44px touch target or readable text, and this app is for people who need
   both. So the fix is to stop the TABLE growing and give the overflow to the
   felt as an internal scroll. Nothing is clipped and nothing shrinks — a
   long bid panel just scrolls within the table, the way it already does in
   landscape.

   Scoped to upright portrait only: .dn-rot and .dn-land own their own sizing
   above and must not be touched. The 480px ceiling keeps this away from
   tablets, which pass today. */
@media (max-width: 480px) and (orientation: portrait) {
  html:not(.dn-rot):not(.dn-land) .table {
    height: 100dvh;
    min-height: 0;
    overflow: hidden;
  }
  html:not(.dn-rot):not(.dn-land) .table > .felt {
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}
html.dn-land .felt.dn-table {
  position: relative; flex: 1; min-height: 0;
  /* Give the usable table back to the game.  The prior 90px/104px rails made a
     short landscape phone look like a giant empty oval with a tiny game inside. */
  max-width: none; width: 100%;
  margin: 0; padding: 72px 16px 88px;
  border-radius: 0;
  /* overflow VISIBLE on purpose: the felt's oval look comes from its own rounded
     background + inset shadows (clipped to border-radius automatically), but the
     pinned hand must be able to sit over the bottom rail WITHOUT being clipped to
     the oval curve — overflow:hidden here cut the bottoms off the cards. */
  overflow: visible;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  /* viewport-fit=cover (all pages, 2026-07-15) lets the table extend under the
     notch in real landscape — keep the side seats out from under it. max() keeps
     the old 18px on notchless devices; dn-rot (CSS-rotated portrait) unaffected. */
  padding-left: max(18px, var(--dn-safe-left));
  padding-right: max(18px, var(--dn-safe-right));
}
/* the West/North/East wrapper dissolves so its seats float on the oval rail */
html.dn-land .felt.dn-table .dn-seats-wrap { display: contents; }
html.dn-land .felt.dn-table .dn-seat {
  position: absolute; z-index: 6; margin: 0; min-width: 0;
  /* hard cap so an opponent's growing card-back fan (crazy-eights/go-fish/old-maid/
     tonk/gin) can't balloon the chip across the centre pile — fanHand then overlaps
     the backs to fit this width. Trick-game text seats are far narrower, unaffected. */
  max-width: 22vw; max-height: 30vh; overflow: hidden;
  /* NO chip box on the landscape table (Jesse 2026-07-16: clean felt) — avatars,
     names and fans float directly on the table; the size caps above still apply. */
  background: transparent; border: 0; border-radius: 13px; padding: 4px 8px; gap: 1px;
}
/* an opponent's card-backs in a seat chip are just a "how many cards" indicator — render
   them MINI so the chip stays a compact tile and never drops its full-height fan down
   into the centre play area (the stock/discard the human must read). */
html.dn-land .felt.dn-table .dn-seat .hand { align-items: center; }
html.dn-land .felt.dn-table .dn-seat .card {
  /* 16px backs read as dark slivers on a real phone, especially in Color Match.
     These are still a compact count indicator, but are now visibly a branded deck
     rather than visual noise around the table. */
  width: clamp(22px, 3.4vw, 32px) !important; height: auto !important;
  /* the flex hand is align-items:stretch by default, which was stretching these
     narrow backs to the hand's full height (18x92 skinny slivers). Pin the card to its
     own aspect-ratio height and don't let it stretch. */
  align-self: center !important; aspect-ratio: 5 / 7;
  border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,.4);
}
html.dn-land .felt.dn-table .dn-seat .name { font-size: 14px; }
html.dn-land .felt.dn-table .dn-seat .meta { font-size: 14px; padding: 0 5px; margin-top: 0; }
html.dn-land .felt.dn-table .dn-seat .seat-av { width: 28px; height: 28px; font-size: 13px; }
html.dn-land .felt.dn-table .dn-dealer-badge {
  width:18px;height:18px;font-size:12.5px}
/* The top seat now carries a stat row UNDER its avatar, and at top:22px that row
   landed behind the topbar's control pill — North's table stack was half-hidden by the
   settings gear. Drop the seat far enough that the row clears the bar instead of
   shortening the row, which would cost the same information at every other seat. */
html.dn-land .felt.dn-table .dn-seat-top   { top: 56px; left: 50%; transform: translateX(-50%); }
html.dn-land.dn-flat .felt.dn-table .dn-seat-top { top: 46px; }
html.dn-land .felt.dn-table .dn-seat-left  { left: max(6px,var(--dn-safe-left));  top: 34%; transform: translateY(-50%); }
html.dn-land .felt.dn-table .dn-seat-right { right: max(6px,var(--dn-safe-right)); top: 34%; transform: translateY(-50%); }
/* central play column (trick / message / bid picker) sits BETWEEN the side seats */
html.dn-land .felt.dn-table .dn-center,
html.dn-land .felt.dn-table .msg,
html.dn-land .felt.dn-table .bid-pick,
html.dn-land .felt.dn-table .breakdown,
html.dn-land .felt.dn-table .lasttrick { max-width: 60%; margin-left: auto; margin-right: auto; }
/* the trick area must COLLAPSE when empty (e.g. during bidding) — its per-game inline
   min-height:112px otherwise reserves a big empty band that flex then steals from the
   message + hand, crushing them. !important beats the inline min-height; the trick still
   grows to fit real cards when they're played. */
html.dn-land .felt.dn-table .dn-center { margin-top: 1.6vh; z-index: 3; min-height: 0 !important; }
/* A live trick is a four-seat conversation, not a horizontal pile.  The cards
   occupy the side they came from so West/North/East stay legible even while the
   cards overlap toward the middle.  During bidding the container remains calm
   and compact because this only turns on once a directional card is present. */
html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) {
  position:relative;display:block;flex:0 0 clamp(132px,34vh,230px);
  width:min(60vw,600px);margin:0 auto;min-height:clamp(132px,34vh,230px)!important;
}
html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-0,
html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-1,
html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-2,
html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-3 {
  position:absolute;z-index:4;margin:0;gap:2px;pointer-events:none;
}
/* Overlapping center cluster: every played card sits near the MIDDLE, nudged
   toward the seat that played it, so position alone tells you who played what
   ("further left = the player on my left"). Cards LAYER by play order — the first
   card played sits on the bottom, each later play stacks on top (DOM order = play
   order gives that for free, since all four share the same z-index). A small tilt
   makes it read like a real tossed pile rather than a neat grid. */
html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-0 {left:50%;top:50%;bottom:auto;transform:translate(-50%,calc(-50% + 28px)) rotate(var(--dn-tilt,0deg))}
html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-1 {left:50%;right:auto;top:50%;transform:translate(calc(-50% - 26px),-50%) rotate(var(--dn-tilt,0deg))}
html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-2 {left:50%;top:50%;transform:translate(-50%,calc(-50% - 28px)) rotate(var(--dn-tilt,0deg))}
html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-3 {left:50%;right:auto;top:50%;transform:translate(calc(-50% + 26px),-50%) rotate(var(--dn-tilt,0deg))}
html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-0 .card,
html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-1 .card,
html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-2 .card,
html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-3 .card {width:clamp(62px,10vw,124px)!important}
/* drop the per-card name labels in the overlapping pile — the seat-offset is the label */
html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .pile>div:last-child{display:none}
/* never let the message or the picker be shrunk to a sliver by flex — keep full text. */
html.dn-land .felt.dn-table .msg { font-size: 14px; min-height: 0; z-index: 5; flex: 0 0 auto; }
html.dn-land .felt.dn-table .pile,
html.dn-land .felt.dn-table .pile .lbl,
html.dn-land .felt.dn-table .bagcount { font-size: 14px; }
/* Legibility on the felt used to come from a two-layer text shadow, the outer layer a
   14px black blur. At 12–13px that halo bleeds into the glyph strokes and the text reads
   as GRAINY rather than sharp (Jesse 2026-07-28: "in-game words are grainy and small").
   Supersedes the 2026-07-16 "no black bubbles" pass: the fix is a flat, opaque plate —
   one solid colour behind the text, no blur touching a glyph, so hinting stays crisp on
   every felt colour. The plate is a quiet chip, not the heavy bubble that pass removed. */
html.dn-land .felt.dn-table .msg:not(:empty),
html.dn-land .felt.dn-table #score:not(:empty),
html.dn-land .felt.dn-table .scorebar:not(:empty) {
  background: rgba(5,17,12,.86); border: 1px solid rgba(255,255,255,.07);
  /* 8px, not the plate's natural 10-12px: the plate lives INSIDE the measured lane, so every
     pixel of padding is a pixel the strip's own words cannot have. */
  border-radius: 12px; padding: 3px 8px; box-shadow: 0 2px 10px rgba(0,0,0,.34);
  text-shadow: none;
}
html.dn-land .felt.dn-table #score:not(:empty),
html.dn-land .felt.dn-table .scorebar:not(:empty) {
  font-size: 14px; z-index: 7;
  /* the base 14px gap costs more lane width than the separator it stands in for; 10px still
     reads as distinct groups and buys back ~12px of room for the words themselves, which is
     one more group kept before the shell has to shed anything. */
  gap: 10px;
}
/* pin the team score as a compact strip at the very top of the felt (like the
   reference's top score chip), OUT of the flow — so the vertically-centred play area
   (trick / message / picker) sits in the middle of the oval without ever colliding
   with it or the bottom action buttons. */
html.dn-land .felt.dn-table #score,
html.dn-land .felt.dn-table .scorebar {
  /* top:3px assumed the felt begins exactly where the topbar ends. It does not once
     the safe-area insets land: measured on a 1396x730 tablet the felt starts at 71px
     while the topbar (and the action pill inside it) still ends at 77px, so a 3px
     offset put the rail's row at 74px -- 3px INTO the pill. game-shell.js measures
     both boxes on every re-fit and publishes the real clearance, so the rail always
     starts below the bar it has to clear. 3px stays as the floor for any page that
     never publishes one. */
  position: absolute; top: var(--dn-score-rail-top, 3px);
  /* An explicit lane left+width, published by game-shell's layoutLandscapeScoreRail from
     the live gap between the title and the action pill (safe-area inset included). The strip
     used to be pinned by its CENTRE and allowed to overhang that lane, so `overflow:hidden`
     cut BOTH ends and the leading team label disappeared off the left screen edge entirely.
     Filling the lane means the box can never start outside it.
     Before the lane is published (the first frame, or a page with no shared topbar) the
     fallbacks put the strip back to centred-and-content-sized, which cannot reach under the
     action pill the way a fixed percentage guess could. --dn-score-rail-tx is written in the
     same call as the lane, so the two modes can never half-apply. */
  left: var(--dn-score-rail-left,50%); width: var(--dn-score-rail-width,max-content);
  transform: translateX(var(--dn-score-rail-tx,-50%));
  margin: 0; max-width: var(--dn-score-rail-width,calc(100% - 20px)); z-index: 9;
  white-space: nowrap; overflow: hidden;
  /* Belt and braces for the frame before the lane is measured, or if it ever is not:
     flex-start clips only the TRAILING end, so the first label survives. Browsers that
     understand overflow alignment centre the row and still refuse to clip its start;
     older WebViews drop the second declaration and keep the flex-start fallback. */
  justify-content: flex-start;
  justify-content: safe center;
  /* keep it a single row — the base .scorebar is flex-wrap:wrap, so as scores/bags/
     the won-bid tracker grow it would wrap to 2-3 lines and grow DOWN onto the North
     partner chip. */
  flex-wrap: nowrap;
}
/* pitch's persistent bid/trump line (#status) is reference info, not a centred status
   message — pin it as a compact chip in the top-LEFT corner (like the reference app's
   points chip) so it never sits behind the partner or in the play area. */
html.dn-land .felt.dn-table #status:not(:empty) {
  position: absolute; top: 42px; left: max(8px,var(--dn-safe-left)); transform: none;
  margin: 0; max-width: 42%; z-index: 8; text-align: left;
  /* same flat plate as the score strip — the trump/bid line is the one thing a Pitch
     player re-reads every trick, and it was the worst offender for halo grain. */
  background: rgba(5,17,12,.86); border: 1px solid rgba(255,255,255,.07);
  border-radius: 12px; padding: 3px 9px; text-shadow: none;
  font-size: 14px; line-height: 1.25; white-space: normal;
}
/* The PRIMARY bid / choice / action bar (.bid-pick in spades; #bidbar in
   bid-whist/euchre/pitch) FLOWS with the centred message — it is NEVER absolutely
   positioned over the text. One row across the wide-but-short landscape, in a solid
   rounded panel so nothing bleeds through it. Empty → no panel. */
html.dn-land .felt.dn-table .bid-pick:not(.hidden),
html.dn-land .felt.dn-table #bidbar:not(:empty):not(.hidden),
html.dn-land .felt.dn-table #targets:not(:empty):not(.hidden) {
  position: static; z-index: 9; margin: 6px auto 0; flex: 0 0 auto;
  display: flex; flex-wrap: nowrap; justify-content: center; align-items: center; gap: 4px;
  max-width: 94%;
  background: rgba(6,17,13,.9); border: 1px solid rgba(255,255,255,.06);
  border-radius: 16px; padding: 8px 10px; box-shadow: 0 6px 22px rgba(0,0,0,.55);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
}
html.dn-land .felt.dn-table .bid-pick button,
html.dn-land .felt.dn-table #bidbar .btn,
html.dn-land .felt.dn-table #targets .btn { padding: 6px 8px; font-size: 12.5px; min-width: 0; }
/* go-fish's ask-history log would push its ask bar down into the hand/controls — hide
   it in landscape (the running log isn't essential; the seat books show the state). */
html.dn-land .felt.dn-table #askLogWrap { display: none; }
/* keep the end-of-hand breakdown compact + scrollable in landscape so it stays in the
   centre band and never collides with the bottom Hint/Next controls on short screens. */
html.dn-land .felt.dn-table .breakdown:not(:empty) {
  font-size: 14px; line-height: 1.3; max-height: 38vh; overflow-y: auto; z-index: 6;
}
/* a per-bid advice line (pitch #hint) flows too — BELOW the buttons, never behind them. */
html.dn-land .felt.dn-table #hint:not(:empty) {
  order: 0; margin: 5px auto 0; max-width: 78%; text-align: center;
  font-size: 14px; line-height: 1.3; color: var(--muted, #a9d8c2);
}
/* The ONLY absolute control row is the SECONDARY Hint / Undo (#controls): pin it just
   above the hand so it never lands over the message or the flowing bid bar. */
/* z-index 5 and 9 both lost to `.hand .card:hover{z-index:100}` (desktop only —
   it lives in a (hover:hover) and (pointer:fine) block), so on a mouse the card
   under the pointer painted OVER these rows. Both pins sit inside the hand's own
   vertical band, so the collision was every hover, not an edge case. The rows
   now outrank a hovered (100) and a focus-visible (110) card. */
html.dn-land .felt.dn-table #controls:not(:empty) {
  position: absolute; bottom: 84px; left: 50%; right: auto; transform:translateX(-50%); margin: 0; z-index: 120;
  display: flex; justify-content: center; gap: 8px;
}
/* A classless PRIMARY .controls row (crazy-eights: Sort/Hint/Draw) gets the same
   flowing panel treatment as the primary bid bar — never absolute (an absolute pin
   collided with the message/stock lines), never under the hand. */
html.dn-land .felt.dn-table .controls:not(:empty):not(.hidden) {
  position: static; z-index: 120; flex: 0 0 auto; margin: 6px auto 0;
  display: flex; flex-wrap: nowrap; justify-content: center; align-items: center; gap: 6px;
}
/* the dark panel ONLY when something is actually visible inside — a row of
   hidden buttons still fails :empty and was rendering as a bare black band
   (hearts' pass phase). :has() is supported by our WebView floor; without it
   the row simply has no backdrop, which is the safe fallback. */
/* :not(.hidden) on the CONTAINER too — go-fish's #pondWrap is a .controls row
   whose static children are never .hidden, so the child check alone painted a
   phantom black panel (and its height shoved the status line up into North's
   label) while the row itself was hidden-by-class. */
html.dn-land .felt.dn-table .controls:not(:empty):not(.hidden):has(> :not(.hidden)) {
  background: rgba(6,17,13,.9); border: 1px solid rgba(255,255,255,.06);
  border-radius: 16px; padding: 6px 8px;
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
}
/* #controls is the compact Hint/Undo row.  It deliberately floats on the
   felt; giving its full-width absolute wrapper a dark panel creates the black
   tray/bar users read as a border around the cards. */
html.dn-land .felt.dn-table #controls.controls:not(:empty):not(.hidden):has(> :not(.hidden)) {
  background:transparent; border:0; border-radius:0; padding:0;
  -webkit-backdrop-filter:none; backdrop-filter:none;
}
html.dn-land .felt.dn-table .controls .btn { padding: 7px 10px; font-size: 12.5px; }
/* inside the pinned bottom strip: the action panel is pinned CLEAR of the hand
   (the hand is independently bottom-pinned, so flex order can't separate them),
   and the redundant "You" avatar/name hide — it's your hand, identity is obvious */
html.dn-land .felt.dn-table .dn-seat-you .controls:not(:empty) {
  position: absolute; left: 50%; bottom: 104px; transform: translateX(-50%); margin: 0; z-index: 120;
}
html.dn-land .felt.dn-table .dn-seat-you .controls .name { display: none; }
html.dn-land .felt.dn-table .dn-seat-you > .seat-av,
html.dn-land .felt.dn-table .dn-seat-you > .name { display: none; }
/* the center stock/discard row must never flex-stretch the column apart; the
   bottom margin lifts it clear of the pinned action panel below */
html.dn-land .felt.dn-table .center-row { flex: 0 0 auto; min-height: 0; margin-bottom: 34px; }
/* YOU pinned across the bottom. Do NOT override display: a seat-wrapper keeps its
   column layout (hand over hidden name); a bare hand keeps its centered flex ROW
   (the fan) — forcing a direction here would stack the fan vertically. */
/* YOUR hand: pinned across the very bottom of the felt, whatever seat markup a
   game uses (some wrap it in a .seat, some leave it bare; some tag it .hand.mine,
   some just .hand). tagTable() adds .dn-hand to the real hand and .mine for shared
   styling, so this one rule places every game's hand identically. Absolute vs the
   felt so a nested seat can't shove it up into the middle of the table. */
/* .hand.mine included as a belt-and-braces twin: one sweep run caught a hand
   without its dn-hand tag (unpinned, giant cards mid-felt) — the pin must never
   depend on a single tagging pass. */
html.dn-land .felt.dn-table .dn-hand,
html.dn-land .felt.dn-table .hand.mine {
  /* Rotated outer fan cards extend a few pixels past their flex boxes. Keep a
     small optical gutter inside the safe area so those corners never clip. */
  position: absolute;
  left: calc(var(--dn-safe-left) + 8px);
  right: calc(var(--dn-safe-right) + 8px);
  bottom: calc(16px + var(--dn-safe-bottom)); width: auto; margin: 0;
  min-height: 0; z-index: 8;
  display: flex; flex-wrap: nowrap; justify-content: center; align-items: flex-end;
}
/* Cards grow into the height reclaimed by the floating (no-bar) top overlay —
   "longer, bigger, easier to see" (fanHand's overlap keeps 13 on one row). */
/* Bigger, clearer hand cards — but the width is the SMALLER of a vw-based size and a
   height-derived cap (card height = width*7/5, so capping width at 32vh*5/7 pins the
   card to ~32vh tall). That lets the card grow into a roomy 16:9 table yet never clip
   the bottom of a tall-narrow (~2.16) phone in landscape. */
html.dn-land .felt.dn-table .dn-hand .card,
html.dn-land .felt.dn-table .hand.mine .card { width: min(clamp(66px, 12.5vw, 124px), calc(32vh * 5 / 7)); }
/* A mouse/trackpad web table has room for a larger, clearer hand.  Phones retain
   their compact cap so a full 13-card fan never becomes a clipped row. */
@media (min-width:800px) and (pointer:fine) {
  html.dn-land .felt.dn-table .dn-hand .card,
  html.dn-land .felt.dn-table .hand.mine .card { width: min(clamp(66px, 11vw, 124px), calc(32vh * 5 / 7)); }
}
/* Landscape is where full trick-taking hands hit their largest card count. Cap
   the centre suit below the width of its card so every top-left rank stays clear. */
html.dn-land .felt.dn-table .dn-hand .card .pip:not(.face),
html.dn-land .felt.dn-table .hand.mine .card .pip:not(.face) { font-size:clamp(28px,3.8vw,46px); }
html.dn-land .felt.dn-table .dn-hand .card .pip .ace,
html.dn-land .felt.dn-table .hand.mine .card .pip .ace { font-size:clamp(33px,4.5vw,52px); }
/* the human seat's own label/meta are redundant next to the pinned hand */
html.dn-land .felt.dn-table .dn-seat-you {
  background: transparent; border: 0; padding: 0;
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
html.dn-land .felt.dn-table .dn-seat-you > .name,
html.dn-land .felt.dn-table .dn-seat-you > .meta { display: none; }
/* Some games (bid-whist, euchre, pitch) leave the human's own status line as a bare
   .meta directly on the felt — it's redundant next to your visible hand and it lands
   behind the bid buttons. Opponent metas live inside .dn-seat chips, so hiding only
   the felt's DIRECT-child meta clears the clutter without touching them. */
html.dn-land .felt.dn-table > .meta { display: none; }

/* ---------- storefront (coinShop) ---------- */
.dn-shop{max-width:none;padding-bottom:max(18px,var(--dn-safe-bottom));
  background:linear-gradient(180deg,#1a2450,#101736 62%,#0b1026);
  border:0;box-shadow:none}
/* One familiar left-side Back action; the equal-width empty third grid column
   keeps the title visually centred without adding a second close control. */
.dn-shop-head{display:grid;grid-template-columns:minmax(82px,auto) 1fr minmax(82px,auto);
  align-items:center;gap:12px;margin:0 0 4px}
.dn-shop-head::after{content:"";width:82px}
.dn-shop-head h3{margin:0;font-size:22px;color:var(--gold);text-align:center}
/* Same SHAPE as the exit on every game page (.topbar .brand.dn-back): gold-tinted
   pill, "‹" chevron, one word. It used to be a dark rectangle with "← Back", which
   made the way out look like a different control on the one screen where the player
   is spending money. Only the word differs — this closes a sheet, not a game. */
/* Plain "‹ Back", no pill. The bubble read as a stray chip on a sheet that
   already has its own solid background, where iOS/Android both use a bare
   tinted chevron + word. The 44px target is unchanged: it now comes from
   min-height + padding instead of from the pill's own box, so removing the
   bubble did not shrink anything the thumb has to hit. Chevron and label share
   one tint, which is what makes it read as native rather than as a link. */
.dn-shop-back-btn{min-width:82px;min-height:46px;display:inline-flex;align-items:center;
  justify-content:flex-start;gap:2px;cursor:pointer;
  border:0;background:transparent;
  padding:0 12px 0 4px;line-height:1;white-space:nowrap;
  transition:opacity .12s ease}
.dn-shop-back-btn .dn-back-chev{font-size:28px;line-height:1;color:var(--gold);font-weight:400}
.dn-shop-back-btn .dn-back-label{font-size:17px;color:var(--gold);font-weight:600;letter-spacing:0}
.dn-shop-back-btn:hover{opacity:.78}
.dn-shop-back-btn:active{opacity:.55}
/* The focus ring is the one box that must stay: it is the only affordance a
   keyboard or switch-control user gets now that the border is gone. */
.dn-shop-back-btn:focus-visible{outline:3px solid #fff;outline-offset:2px;border-radius:10px}
@media(max-width:380px){
  .dn-shop-head{grid-template-columns:76px 1fr 76px;gap:6px}
  .dn-shop-head::after{width:76px}
  .dn-shop-back-btn{min-width:76px;padding:0 10px 0 4px}
  .dn-shop-back-btn .dn-back-label{font-size:16px}
  .dn-shop-head h3{font-size:20px}
}
/* COIN PACKS: a card GRID, not a column of bars.
   auto-fit + a 240px floor is the whole tablet story for this list. Six one-per-row
   bars measured a 382px-wide column on a portrait iPad and a 1038px-wide bar with a
   73px price button marooned at the far right on a landscape iPad mini — only two
   of the six packs on screen. `minmax(min(100%,240px),1fr)` keeps ONE column on a
   phone (and in an iPad Split View pane), gives two or three on every iPad, and lets
   the cards absorb the leftover width instead of adding a fourth thin column. */
.dn-pack-grid{display:grid;gap:14px;margin:0 0 12px;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr))}
/* One card shape at every width: [ribbon] / [🪙 | amount·tier·value·×2] / [Buy price].
   The price button spans the card and is 54px tall, so the thing you press is the
   widest thing on the card — the old 73px pill was the smallest. */
.dn-pack{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;
  column-gap:12px;row-gap:10px;padding:14px;margin:0;border-radius:16px;
  border:1px solid rgba(var(--dn-gold-rgb),.34);background:linear-gradient(180deg,rgba(255,255,255,.06),rgba(0,0,0,.25));
  container-type:inline-size}
.dn-pack.best{border-color:var(--gold);box-shadow:0 0 0 1px rgba(var(--dn-gold-rgb),.45),0 6px 20px rgba(var(--dn-gold-rgb),.18)}
.dn-pack-ico{grid-column:1;width:54px;height:54px;border-radius:13px;display:grid;place-items:center;font-size:27px;
  background:radial-gradient(circle at 40% 35%,#f3d488,#b8912f);box-shadow:inset 0 -2px 6px rgba(0,0,0,.35)}
.dn-pack-main{grid-column:2;min-width:0}
/* Sized against the CARD, not the viewport, because the same card is 350px wide in
   a phone's single column and 240px in a tablet's third column. The divisor is the
   width of the longest amount this ladder can print ("10,000,000 coins") in ems, so
   the largest tier is the one that decides the size and NOTHING wraps; 66px is the
   coin badge plus its gap, which is not available to the text in this mode. */
.dn-pack-coins{font-weight:900;font-size:clamp(19px,calc((100cqi - 66px) / 9.6),24px);
  color:#fff;line-height:1.12}
/* Below a ~260px content box the badge leaves too little beside it for the amount to
   stay on one line, so the badge keeps the top row to itself and the number takes
   the full width (and, having it, gets bigger rather than smaller). Above that the
   badge stays anchored to the number: spending a whole row on it is worse, because
   card HEIGHT is what decides how many packs fit on a LANDSCAPE tablet, where the
   catalog pane is only ~580px tall. */
@container (max-width:259px){
  .dn-pack-main{grid-column:1 / -1}
  .dn-pack-coins{font-size:clamp(19px,calc(100cqi / 9.6),24px)}
}
.dn-pack-label{font-size:14px;color:#c3cbe4;font-weight:700}
/* "Most Popular" / "Best Value" were an 11px pill UNDER the fine print, which is
   where a merchandising claim goes to die. Now it is a solid gold ribbon on its own
   row at the top of the card, in the same gold as the price button. It cannot be a
   negative-offset ribbon: the tablet catalog pane scrolls, and the first row's
   ribbon would be clipped by that scroll box. */
.dn-pack-tag{grid-column:1 / -1;justify-self:start;padding:4px 12px;border-radius:99px;
  font-size:13px;font-weight:900;letter-spacing:.02em;
  background:linear-gradient(180deg,#f2d078,#c9a347);color:#241c07;
  box-shadow:0 2px 8px rgba(0,0,0,.32)}
/* Coins per unit of the store's OWN localized price — the one number that answers
   "which of these is the better deal?", which six amounts beside six prices did not.
   The best rate in the live catalog gets the gold. */
.dn-pack-value{margin-top:3px;font-size:13.5px;font-weight:800;color:#c3cbe4}
.dn-pack-value-best{color:#f4df9f}
.dn-pack-buy{grid-column:1 / -1;width:100%;min-height:54px;padding:0 16px;border-radius:13px;
  border:0;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;
  font-weight:900;font-size:16px;color:#241c07;
  background:linear-gradient(180deg,#f2d078,#c9a347);white-space:nowrap}
.dn-pack-buy-lead{font-size:15px;font-weight:800;opacity:.8}
.dn-pack-buy-price{font-size:21px;font-weight:900;letter-spacing:-.01em}
/* A disabled gold button still looks buyable. "Loading store details…" also has to
   be allowed to wrap — at 240px it does not fit on one line. */
.dn-pack-buy:disabled{background:rgba(255,255,255,.09);color:#aeb7d2;cursor:default;
  font-size:14px;white-space:normal;line-height:1.3;padding:8px 14px}
.dn-pack-buy:focus-visible{outline:3px solid #fff;outline-offset:2px}
.dn-pay{margin:6px 0 10px;padding:12px;border-radius:13px;border:1px dashed rgba(var(--dn-gold-rgb),.45);
  background:rgba(0,0,0,.22);font-size:13px;line-height:1.5;color:#d7dcf0}
.dn-pay b{color:var(--gold)}
.dn-pay-btn{width:100%;min-height:50px;margin-top:10px;border-radius:12px;border:0;cursor:pointer;
  font-weight:900;font-size:15.5px;color:#241c07;background:linear-gradient(180deg,#f2d078,#c9a347)}
.dn-shop-bal{color:var(--muted);margin:0 0 10px;font-weight:700}
.dn-shop-account-bal{margin-top:-6px;color:#f1d98e}
.dn-shop-live-preview{position:sticky;top:-1px;z-index:4;margin:0 0 10px;
  padding:8px 10px;border-radius:14px;background:rgba(10,13,22,.96);
  border:1px solid rgba(var(--dn-gold-rgb),.52);box-shadow:0 8px 22px rgba(0,0,0,.42)}
.dn-shop-live-preview[hidden]{display:none}
.dn-shop-live-preview-name{display:block;margin-bottom:6px;color:#f5dfa0;
  font-size:14px;text-align:center}
.dn-shop-live-preview-stage{height:118px;border-radius:12px;padding:9px;
  display:grid;place-items:center;overflow:hidden}
.dn-shop-live-preview-felt{width:min(88%,360px);height:92px;border-radius:50%;
  display:grid;place-items:center;border:3px solid rgba(var(--dn-gold-rgb),.72);
  background:radial-gradient(ellipse at 50% 42%,var(--felt-hi),var(--felt-mid) 66%,var(--felt-lo));
  box-shadow:inset 0 0 0 2px rgba(0,0,0,.34),0 5px 14px rgba(0,0,0,.38)}
.dn-shop-live-preview .card.mini{width:54px;height:76px;position:relative;transform:rotate(-5deg);
  box-shadow:0 5px 12px rgba(0,0,0,.46)}
.dn-shop-tabs{display:flex;gap:6px;margin-bottom:10px;overflow-x:auto;scrollbar-width:none}
.dn-shop-tabs::-webkit-scrollbar{display:none}
.dn-shop-tab{flex:1;min-height:44px;padding:10px 4px;border-radius:11px;font-weight:800;border:1px solid rgba(var(--dn-gold-rgb),.3);
  background:var(--bg2);color:var(--muted);font-weight:800;font-size:13px;cursor:pointer}
.dn-shop-tab.on{background:var(--gold);color:#241c07;border-color:var(--gold)}
.dn-shop-body{max-height:none;overflow-y:visible;padding-right:4px;padding-bottom:4px}
.dn-shop-legal{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:0 0 10px}
.dn-shop-legal .btn{display:inline-flex;align-items:center;justify-content:center;min-height:44px;text-align:center}
.dn-reward-button{width:100%;margin-top:7px}
/* Keep one-time grants visible as an audit trail without letting two already-
   claimed rows crowd the daily wheel below the fold. */
.dn-reward-claimed{padding:5px 10px;margin-top:3px;font-size:12.5px;opacity:.68}
.dn-reward-claimed + .dn-reward-claimed{margin-top:2px}
.dn-shop-row{display:flex;align-items:center;gap:10px;padding:8px 2px;border-bottom:1px solid rgba(255,255,255,.05)}
.dn-shop-icon{font-size:22px;flex:0 0 52px;text-align:center;font-style:normal;display:grid;place-items:center}
.dn-shop-main{flex:1;min-width:0;display:flex;flex-direction:column;line-height:1.25}
.dn-shop-main small{color:var(--muted);font-size:11.5px;text-transform:capitalize}
.dn-shop-actions{display:flex;align-items:center;justify-content:flex-end;gap:6px;flex:0 0 auto}
.dn-shop-mini{padding:7px 10px;font-size:12px;white-space:nowrap}
/* 13px, not 12px: these carry the section labels and the coin disclosure, and the
   lobby already has a 14px reading floor for exactly this reason. */
.dn-shop-note{color:var(--muted);font-size:13px;margin:10px 0 6px;line-height:1.45}
.dn-shop-packs-head{font-size:14.5px;font-weight:800;color:#e6ecff}
.dn-shop-place{width:100%;margin:6px 0 2px;font-size:12.5px}
.dn-shop-summary{min-width:0}
.dn-shop-assurance{display:none}
/* A landscape phone has width to spare and almost no height. Keep account copy
   and sign-in side by side so merchandise remains visible without scrolling. */
@media (max-height:560px) and (min-width:700px){
  .dn-shop-summary{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(0,.9fr);
    column-gap:12px;align-items:start}
  .dn-shop-bal,.dn-shop-account-bal{grid-column:1 / -1;margin:0 0 6px}
  .dn-shop-guestwarn{grid-column:1;grid-row:3 / 5;margin:0;align-self:start}
  .dn-shop-summary > .btn{grid-column:2;grid-row:3;margin:0!important;min-height:46px}
}
.dn-shop-body:has(.dn-shop-preview){display:flex;flex-direction:column}
.dn-shop-preview{
  flex:1 0 auto;display:flex;flex-direction:column;margin:14px 0 10px;padding:18px;
  border:1px solid rgba(var(--dn-gold-rgb),.28);border-radius:18px;
  background:
    radial-gradient(620px 260px at 50% -8%,rgba(var(--dn-gold-rgb),.14),transparent 65%),
    linear-gradient(155deg,rgba(255,255,255,.055),rgba(2,5,17,.34));
  box-shadow:inset 0 1px 0 rgba(255,255,255,.045)
}
.dn-shop-preview-eyebrow{
  margin:0 0 5px;color:var(--gold);font-size:11px;font-weight:900;
  letter-spacing:.12em;text-transform:uppercase;text-align:center
}
.dn-shop-preview h4{margin:0;color:#fff;font-size:clamp(21px,3vw,28px);text-align:center}
.dn-shop-preview-copy{
  max-width:680px;margin:7px auto 16px;color:#cbd2e8;font-size:13px;line-height:1.45;text-align:center
}
.dn-shop-preview-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px}
.dn-shop-preview-pack{
  position:relative;min-width:0;min-height:92px;display:grid;
  grid-template-columns:38px minmax(0,1fr);grid-template-rows:auto auto;
  align-content:center;column-gap:9px;padding:12px;border-radius:14px;
  border:1px solid rgba(var(--dn-gold-rgb),.26);background:rgba(5,8,20,.64)
}
/* The USD website price under each pack. Only rendered in a browser, where
   the provider never returns a localized string and the customer was
   otherwise shown six coin amounts and no price at all. */
.dn-shop-preview-webprice{display:block;margin-top:2px;font-size:14px;
  font-weight:800;color:#ffe6a3;letter-spacing:.01em}

.dn-shop-preview-pack.best{
  border-color:rgba(var(--dn-gold-rgb),.78);
  box-shadow:0 0 0 1px rgba(var(--dn-gold-rgb),.18),0 8px 20px rgba(0,0,0,.24)
}
.dn-shop-preview-coin{
  grid-row:1 / 3;width:38px;height:38px;display:grid;place-items:center;
  border-radius:50%;font-size:20px;background:radial-gradient(circle at 36% 30%,#f3d488,#a47b21);
  filter:drop-shadow(0 4px 7px rgba(0,0,0,.34))
}
.dn-shop-preview-pack b{
  align-self:end;min-width:0;color:#fff;font-size:clamp(16px,2.2vw,21px);
  line-height:1.05;white-space:nowrap
}
.dn-shop-preview-pack > span:not(.dn-shop-preview-coin){
  align-self:start;color:#bfc7df;font-size:12px;font-weight:750
}
.dn-shop-preview-pack em{
  position:absolute;right:7px;top:6px;padding:2px 7px;border-radius:99px;
  background:rgba(var(--dn-gold-rgb),.18);color:#f3d98d;font-size:9px;font-style:normal;
  font-weight:900;letter-spacing:.03em;text-transform:uppercase
}
.dn-shop-preview-benefits{display:grid;grid-template-columns:1fr;gap:9px;margin-top:12px}
.dn-shop-preview-benefits.dn-shop-preview-benefits-primary{
  flex:0 0 auto;min-height:0;grid-auto-rows:auto;grid-template-columns:1fr;
  margin:8px 0 16px
}
.dn-shop-preview-benefits.dn-shop-preview-benefits-secondary{
  grid-template-columns:1fr
}
.dn-shop-preview-benefits article{
  padding:13px 14px;border-radius:14px;background:rgba(10,16,42,.72);
  border:1px solid rgba(255,255,255,.085)
}
.dn-shop-preview-benefits h5{margin:0 0 5px;color:#f4df9f;font-size:15px}
.dn-shop-preview-benefits p{margin:0;color:#cbd2e8;font-size:12px;line-height:1.48}
.dn-shop-preview-safety{
  margin:auto 0 0;padding:13px 3px 0;color:#aeb7d2;font-size:11.5px;
  line-height:1.5;text-align:center
}
@media (min-width:620px){
  .dn-shop-preview-grid{grid-template-columns:repeat(3,minmax(0,1fr))}
  .dn-shop-preview-benefits{grid-template-columns:repeat(2,minmax(0,1fr))}
}
/* The complement of the two-pane tablet block below: wherever the shop is one
   scrolling column, the preview grows to fill it instead of sitting in a short box
   above dead space. Kept in lockstep with that block's breakpoints. */
@media (max-width:899px),(max-height:699px){
  .dn-shop:has(.dn-shop-preview){display:flex;flex-direction:column}
  .dn-shop:has(.dn-shop-preview) .dn-shop-body{flex:1 0 auto}
}
@media(max-width:520px){
  .dn-shop-row{display:grid;grid-template-columns:44px minmax(0,1fr);column-gap:8px;row-gap:6px}
  .dn-shop-icon{grid-column:1;grid-row:1;min-width:0;width:44px}
  .dn-shop-main{grid-column:2;grid-row:1}
  .dn-shop-actions{grid-column:2;grid-row:2;flex-wrap:wrap}
  .dn-shop-mini{flex:1 1 auto;min-width:68px}
}
/* 700-899px WIDE TABLET (iPad mini / 10.9" / 11" in PORTRAIT). Too narrow to split
   into a rail plus a catalog that can still hold two pack columns — 744pt leaves a
   382px pane, which is a phone column with an iPad around it. So these keep one
   document column and give the whole width to the pack grid (two or three cards),
   and only the wallet block is laid out two-up so it costs one band instead of four
   full-width stripes with a 690px-wide "Sign in" button in the middle of them. */
@media (min-width:700px) and (max-width:899px) and (min-height:700px){
  .dn-shop-summary{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);
    column-gap:16px;align-items:start}
  .dn-shop-bal,.dn-shop-account-bal{grid-column:1 / -1;margin:0 0 8px}
  .dn-shop-guestwarn{grid-column:1;grid-row:3 / 5;margin:0;align-self:start}
  .dn-shop-summary > .btn{grid-column:2;grid-row:3;margin:0!important;min-height:52px}
  .dn-shop-head h3{font-size:25px}
  .dn-shop-tab{font-size:15px;min-height:50px}
  .dn-shop-note{font-size:13.5px}
}
/* A tablet is not a stretched phone. Keep the shop full-bleed, but turn the
   available canvas into a wallet/terms rail and a real catalog pane. Short
   phone screens retain the single scroll column above.
   WIDTH 900, not 720: below that the rail's 280px minimum eats the catalog down to
   a single 382px column, so a portrait iPad mini is handled by the full-width block
   above instead. HEIGHT 700, not 800: it is the same floor the app's one tablet
   definition uses (:root{--dn-tablet:1}), and 800 silently excluded a LANDSCAPE
   iPad mini (1133x744) — a 1133pt-wide screen was rendering 1038px-wide phone bars.
   A landscape phone (844x390, 956x440) is still nowhere near 700 tall. */
@media (min-width:900px) and (min-height:700px){
  .dn-shop{
    display:grid;
    grid-template-columns:minmax(280px,.72fr) minmax(0,1.28fr);
    grid-template-rows:auto auto minmax(0,1fr);
    column-gap:22px;row-gap:14px;
    overflow:hidden;
  }
  .dn-shop-head{grid-column:1 / -1;grid-row:1;margin-bottom:0}
  /* align-self:start, NOT stretch. Stretched to a 1200px portrait rail this card
     became one enormous bordered box with 750px of nothing between the offer and
     the terms. It now ends where its content ends and scrolls only if the rail is
     shorter than the content (landscape). */
  .dn-shop-summary{
    grid-column:1;grid-row:2 / 4;min-height:0;max-height:100%;
    align-self:start;overflow-y:auto;
    display:flex;flex-direction:column;
    padding:20px;border-radius:20px;
    background:linear-gradient(160deg,rgba(255,255,255,.07),rgba(0,0,0,.18));
    border:1px solid rgba(var(--dn-gold-rgb),.24);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.05);
  }
  .dn-shop-tabs{grid-column:2;grid-row:2;margin:0}
  .dn-shop-body{
    grid-column:2;grid-row:3;min-height:0;overflow-y:auto;
    padding:18px 20px;border-radius:20px;
    background:rgba(5,9,24,.30);border:1px solid rgba(255,255,255,.08);
    scrollbar-gutter:stable;
  }
  /* When purchases are intentionally unavailable, centre the concise state
     instead of leaving a few disabled rows stranded above a blank tablet. */
  .dn-shop-body:has(.dn-shop-sales-unavailable):not(:has(.dn-shop-preview)){
    display:flex;flex-direction:column;justify-content:center}
  /* A live pack list IS the catalog, so it gets the pane: the grid's cards are the
     only thing allowed to absorb leftover height, and whatever they leave goes
     under the closing coin disclosure so it sits on the floor of the pane instead
     of hovering above a hole. */
  .dn-shop-body:has(.dn-pack-grid){display:flex;flex-direction:column}
  .dn-shop-body:has(.dn-pack-grid) > *{flex:0 0 auto}
  /* NOT flex:0 1 auto — a shrinkable grid in this scrolling column compresses the
     cards and clips the price button instead of letting the pane scroll. */
  .dn-shop-body:has(.dn-pack-grid) > .dn-pack-grid{flex:0 0 auto;gap:16px}
  .dn-shop-body:has(.dn-pack-grid) > .dn-shop-note:last-child{margin-top:auto}
  /* Bigger everything in the pack cards — the pane is 600-850px wide here, not a
     phone column, and this is the screen where money is spent. */
  .dn-pack{padding:16px}
  .dn-pack-buy{min-height:58px}
  .dn-pack-buy-price{font-size:23px}
  .dn-shop-head h3{font-size:27px}
  .dn-shop-tab{font-size:15px;min-height:52px}
  .dn-shop-note{font-size:13.5px}
  .dn-shop-packs-head{font-size:15.5px}
  .dn-shop-preview-benefits{flex:1 1 auto;min-height:250px;grid-auto-rows:1fr}
  .dn-shop-preview-benefits article{
    position:relative;display:flex;flex-direction:column;justify-content:center;
    overflow:hidden;padding:22px
  }
  .dn-shop-preview-benefits article::after{
    position:absolute;right:14px;bottom:4px;font-size:88px;line-height:1;
    opacity:.055;filter:grayscale(1);pointer-events:none
  }
  .dn-shop-preview-benefits article:first-child::after{content:"🌹"}
  .dn-shop-preview-benefits article:last-child::after{content:"🎁"}
  .dn-shop-preview-benefits.dn-shop-preview-benefits-primary article::after{content:"🌹"}
  .dn-shop-preview-benefits.dn-shop-preview-benefits-secondary article::after{content:"🎁"}
  /* Sized to its content and sitting directly under the wallet block, so the rail
     is one short column of real information rather than a full-height box with a
     coin floating in the middle of it. */
  .dn-shop-assurance{
    display:flex;flex:0 0 auto;margin:16px 0 0;padding:20px 18px;
    flex-direction:column;align-items:center;justify-content:center;text-align:center;
    border-radius:18px;background:
      radial-gradient(circle at 50% 20%,rgba(var(--dn-gold-rgb),.18),transparent 48%),
      rgba(8,13,34,.55);border:1px solid rgba(var(--dn-gold-rgb),.22)}
  .dn-shop-assurance-mark{font-size:54px;filter:drop-shadow(0 8px 16px rgba(0,0,0,.45))}
  .dn-shop-assurance h4{margin:10px 0 12px;color:#f4df9f;font-size:20px}
  .dn-shop-assurance ul{margin:0;padding-left:20px;color:#c9d0e7;text-align:left;
    font-size:14px;line-height:1.65}
}

/* Every dialog surface is a real screen, not a bordered card floating inside
   the device. These modules inject their own CSS at runtime, so !important is
   intentional: one full-bleed safe-area contract covers account, analytics,
   rules and the small Tonk confirmation on web, iOS and Android. */
.dna-overlay,.niro-analytics-overlay,.nr-back,.tonk-confirm-back{
  inset:0!important;padding:0!important;background:#11131a!important;
  place-items:stretch!important;align-items:stretch!important;justify-content:stretch!important;
}
.dna-card,.niro-analytics-panel,.nr-box,.tonk-confirm{
  width:100%!important;height:100%!important;max-width:none!important;max-height:none!important;
  margin:0!important;border:0!important;border-radius:0!important;box-shadow:none!important;
  overflow:auto!important;
  padding:calc(20px + var(--dn-safe-top)) calc(clamp(18px,4vw,54px) + var(--dn-safe-right))
    calc(20px + var(--dn-safe-bottom)) calc(clamp(18px,4vw,54px) + var(--dn-safe-left))!important;
}
/* felt swatches — explicit colors so previews don't depend on what's equipped */
.dn-swatch{width:34px;height:22px;border-radius:50%/46%;border:2px solid #17181d;box-shadow:0 1px 4px rgba(0,0,0,.5)}
.dn-swatch-red{background:radial-gradient(circle at 50% 35%,#b3222d,#5f1017)}
.dn-swatch-green{background:radial-gradient(circle at 50% 35%,#227a55,#0d3123)}
.dn-swatch-blue{background:radial-gradient(circle at 50% 35%,#1f4f8a,#0d2342)}
.dn-swatch-royal{background:radial-gradient(circle at 50% 35%,#6a3fa0,#37205a)}
.dn-swatch-noir{background:radial-gradient(circle at 50% 35%,#3a3d46,#131418)}
.dn-swatch-emerald{background:radial-gradient(circle at 50% 35%,#1d8f5f,#0a3d28)}
.dn-swatch-crimson{background:radial-gradient(circle at 50% 35%,#a51d2d,#520d16);box-shadow:0 1px 4px rgba(0,0,0,.5),inset 0 0 0 1px rgba(var(--dn-gold-rgb),.5)}
.dn-swatch-premium-midnight-gold{background:
  radial-gradient(circle at 50% 34%,#2b416f,#080c19 72%);
  border-color:#d9b957;box-shadow:0 1px 5px rgba(0,0,0,.55),inset 0 0 0 2px rgba(240,212,138,.35)}
/* deck swatches (Decks tab) — a LIVE mini card back carrying its .dn-bk-<id>
   theme class, so the preview is the real rendering, not an approximation */
.dn-shop-icon .card.mini.back{width:46px;border-radius:7px;pointer-events:none}
.dn-room-swatch-classic{background:linear-gradient(160deg,#231533,#130d1f)}
.dn-room-swatch-midnight{background:linear-gradient(160deg,#0e1220,#070910)}
.dn-room-swatch-ocean{background:linear-gradient(160deg,#0c2e38,#071820)}
.dn-room-swatch-velvet{background:linear-gradient(160deg,#2c1119,#170810)}

/* ---------- profile flair + table items ---------- */
.dn-flair{margin-left:5px;font-style:normal;font-weight:400}
.dn-premium-badge-grid{display:grid;grid-template-columns:repeat(5,minmax(44px,1fr));gap:7px;margin:7px 0 3px}
.dn-premium-badge-choice{min-width:44px;min-height:46px;border-radius:12px;border:1px solid rgba(240,212,138,.35);
  background:#111727;color:#fff;font-size:24px;line-height:1;cursor:pointer;box-shadow:inset 0 0 0 1px rgba(255,255,255,.025)}
.dn-premium-badge-choice.is-selected{border-color:#f0d48a;background:linear-gradient(180deg,#26355c,#121a31);
  box-shadow:0 0 0 2px rgba(240,212,138,.24),0 4px 12px rgba(0,0,0,.35)}
.dn-premium-badge-choice:focus-visible{outline:3px solid #fff;outline-offset:2px}
.dn-premium-badge-note{margin-top:5px}
.dn-tray{padding:6px 9px}
.dn-tray-pop{position:fixed;z-index:140;display:flex;gap:6px;padding:8px;border-radius:14px;
  background:var(--panel);border:1px solid var(--line);box-shadow:0 12px 30px rgba(0,0,0,.5)}
.dn-tray-pop button{border:1px solid var(--line);background:var(--bg2);border-radius:10px;
  padding:8px 10px;font-size:20px;cursor:pointer;color:var(--text)}
.dn-tray-pop button small{font-size:10px;color:var(--muted)}
.dn-table-item{position:absolute;z-index:45;font-size:clamp(22px,4.5vw,34px);font-style:normal;pointer-events:none;
  filter:drop-shadow(0 4px 6px rgba(0,0,0,.45));
  animation:dnItemPop .55s cubic-bezier(.34,1.56,.64,1);transition:opacity 2s ease}
.dn-table-item.dn-item-fade{opacity:0}
.dn-rarity-rare{filter:drop-shadow(0 4px 6px rgba(0,0,0,.45)) drop-shadow(0 0 8px rgba(var(--dn-gold-rgb),.55))}
.dn-rarity-epic{filter:drop-shadow(0 4px 6px rgba(0,0,0,.45)) drop-shadow(0 0 14px rgba(var(--dn-gold-rgb),.85))}
@keyframes dnItemPop{0%{transform:translateY(-42px) scale(.3) rotate(-14deg);opacity:0}
  62%{transform:translateY(4px) scale(1.14) rotate(3deg)}100%{transform:none;opacity:1}}

/* ---------- focus mode: clean table once play is underway ---------- */
/* Labels retire; avatars, badges, and the turn glow stay. Tap an avatar to
   peek that player's name/meta as a floating chip for a moment. */
html.dn-focus .seat .name,html.dn-focus .seat .meta{display:none}
/* While a trick is on the table, leave the names visible.  The player needs to
   connect the directional card and its source seat at a glance; quiet focus can
   resume as soon as the cards sweep away. */
html.dn-focus .felt:has(#trick .dn-trick-seat-0) .seat .name,
html.dn-focus .felt:has(#trick .dn-trick-seat-0) .seat .meta{display:block}
/* During the AUCTION, each seat's live bid must be readable next to that seat —
   focus mode otherwise hides seat labels until cards hit the table. The game sets
   html.dn-bidding while any bid/name-trump phase is live. */
html.dn-focus.dn-bidding .seat .name,
html.dn-focus.dn-bidding .seat .meta{display:block}
/* Go Fish / Old Maid: the opponent card/book counts (and the player's pool count)
   are live info the game is built around — keep the .meta counts on the table in
   focus mode. Names still retire to the avatars for a clean look. */
html.dn-focus.dn-seatmeta .seat .meta{display:block}
/* !important on purpose: the landscape .dn-seat chip rule is more specific and
   used to keep its dark box behind the avatar — Jesse's bar is a CLEAN felt,
   so in focus the avatar floats directly on the table in every orientation. */
html.dn-focus .seat,html.dn-focus .dn-seat{min-width:0;padding:6px;
  background:transparent !important;border-color:transparent !important;box-shadow:none !important}
/* peek chips anchor to their seat. Positioning discipline learned the hard way:
   (a) the YOU-seat must NEVER become positioned — it re-anchors the absolutely-
   pinned hand off the felt and floats it mid-table; (b) in LANDSCAPE the seats
   are already position:absolute — a later `relative` OVERRIDES it (same
   specificity, later in file) and collapses them into the flow as clipped
   slivers. So: relative only in PORTRAIT, only for opponent seats. */
html.dn-focus:not(.dn-land) .seat:not(.seat-you):not(.dn-seat-you){position:relative}
html.dn-focus .seat .seat-av{cursor:pointer}
html.dn-focus .seat.dn-peek .name,html.dn-focus .seat.dn-peek .meta{
  display:block;position:absolute;left:50%;transform:translateX(-50%);z-index:82;white-space:nowrap;
  background:rgba(10,11,14,.94);border:1px solid var(--line);border-radius:10px;padding:5px 10px;
  box-shadow:0 8px 20px rgba(0,0,0,.45);animation:dnPeek .16s ease}
html.dn-focus .seat.dn-peek .name{top:calc(100% + 4px)}
html.dn-focus .seat.dn-peek .meta{top:calc(100% + 38px)}
/* bottom seat (you): peek upward so it never collides with your hand */
html.dn-focus .seat.seat-you.dn-peek .name,html.dn-focus .dn-seat-you.dn-peek .name{top:auto;bottom:calc(100% + 4px)}
html.dn-focus .seat.seat-you.dn-peek .meta,html.dn-focus .dn-seat-you.dn-peek .meta{top:auto;bottom:calc(100% + 38px)}
@keyframes dnPeek{from{opacity:0;transform:translateX(-50%) translateY(-3px)}to{opacity:1;transform:translateX(-50%)}}
.shell-session{color:var(--muted);font-size:12.5px;margin:2px 0 0}

/* ---- live-table turn clock (play.html) — appended, scoped to .mp-timer ---- */
.mp-timer{align-self:center;height:28px;display:inline-flex;align-items:center;gap:6px;padding:0 14px;
  border-radius:999px;background:rgba(0,0,0,.35);border:1px solid var(--line);color:var(--text);
  font-weight:800;font-size:13px;letter-spacing:.04em;box-shadow:0 2px 8px rgba(0,0,0,.25)}
.mp-timer.amber{color:#ffb347;border-color:rgba(255,179,71,.45)}
.mp-timer.crit{color:#ff5d5d;border-color:rgba(255,93,93,.55);animation:mp-timer-pulse .8s ease-in-out infinite}
.mp-timer.hidden{display:none}
@keyframes mp-timer-pulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.08);opacity:.7}}

/* ============================================================
   D23 polish niceties (appended 2026-07-16) — additive, dn- scoped
   ============================================================ */
/* Seat speech bubble (Shell.say): brief table talk by the acting seat for
   moments the games already announce (bids, "Pass", "Go fish!"). CLEAN-LABEL
   standard — NO box; legibility comes from the standard text shadow. Fixed
   position (appended to <body>) so it never re-anchors a pinned hand and
   never participates in table layout. */
.dn-bubble{position:fixed;z-index:96;pointer-events:none;transform:translateX(-50%);
  font-weight:800;font-size:14px;letter-spacing:.02em;color:#f7dd96;white-space:nowrap;
  padding:2px 8px;border-radius:10px;background:rgba(5,17,12,.86);
  text-shadow:none;
  animation:dnBubbleIn 1.6s ease both}
@keyframes dnBubbleIn{0%{opacity:0;transform:translateX(-50%) translateY(5px) scale(.9)}
  9%,74%{opacity:1;transform:translateX(-50%) translateY(0) scale(1)}
  100%{opacity:0;transform:translateX(-50%) translateY(-7px) scale(1)}}
@media (prefers-reduced-motion:reduce){.dn-bubble{animation:none}}

/* Go Fish draw pond: appears only when YOU must "Go fish" — the pile pulses
   gold until tapped (static glow under reduced motion so the affordance stays). */
.dn-pond{display:flex;align-items:center;gap:10px;background:none;border:0;cursor:pointer;
  padding:4px 10px;-webkit-tap-highlight-color:transparent}
.dn-pond .card{display:block;pointer-events:none}
.dn-pond-lbl{font-size:14px;font-weight:800;color:#f7dd96;
  padding:2px 8px;border-radius:10px;background:rgba(5,17,12,.86);text-shadow:none}
.dn-pond .card.back{outline:2px solid rgba(var(--dn-gold-rgb),.7);outline-offset:2px;
  animation:dnPondPulse 1.1s ease-in-out infinite}
@keyframes dnPondPulse{0%,100%{outline-color:rgba(var(--dn-gold-rgb),.35);filter:drop-shadow(0 0 4px rgba(var(--dn-gold-rgb),.2))}
  50%{outline-color:rgba(var(--dn-gold-rgb),1);filter:drop-shadow(0 0 13px rgba(var(--dn-gold-rgb),.8))}}
@media (prefers-reduced-motion:reduce){.dn-pond .card.back{animation:none;
  outline-color:var(--gold);filter:drop-shadow(0 0 10px rgba(var(--dn-gold-rgb),.6))}}

/* ============================================================
   Cosmetics + retention batch (appended 2026-07-17) — dn- scoped
   ============================================================ */

/* ---- per-player card backs (Jesse ruling 2026-07-17) ----
   The equipped deck theme (html.dn-back-<id>) skins only the backs the BUYER
   owns visually: the deck/stock piles they draw from and their own face-down
   cards. game-shell.js tags every back that belongs to an OPPONENT (their hand
   minis, war's opponent pile, old-maid's neighbor hand) with .dn-back-theirs,
   and this block pins those to the free Classic Gold back — the :root prefix
   out-specifies every html.dn-back-<id> theme rule above. Cards carrying an
   explicit .dn-bk-<id> (shop swatches, the MP play-reveal) are never tagged. */
:root .card.back.dn-back-theirs,:root .card.back.dn-bk-classic{
  background:
    repeating-linear-gradient(45deg, transparent 0 8px, rgba(var(--dn-gold-rgb),.09) 8px 9px),
    repeating-linear-gradient(-45deg, transparent 0 8px, rgba(var(--dn-gold-rgb),.09) 8px 9px),
    radial-gradient(125% 95% at 50% 0%, #2b3668, #1a2247 68%, #131a37);
  border-color:#0e1330;
  box-shadow:0 2px 6px rgba(0,0,0,.4),
    inset 0 0 0 2px rgba(var(--dn-gold-rgb),.34),
    inset 0 0 0 3px rgba(0,0,0,.4),
    inset 0 0 0 4px rgba(var(--dn-gold-rgb),.13)}
:root .card.back.dn-back-theirs::after,:root .card.back.dn-bk-classic::after{inset:15% 21%;border-radius:6px;
  border:1px solid rgba(var(--dn-gold-rgb),.42);
  background:radial-gradient(circle at 50% 50%, rgba(var(--dn-gold-rgb),.16), transparent 62%)}
:root .card.back.dn-back-theirs::before,:root .card.back.dn-bk-classic::before{width:24%;height:24%;
  transform:translate(-50%,-50%) rotate(45deg);border-radius:3px;
  background:linear-gradient(135deg, rgba(var(--dn-gold-rgb),.6), rgba(var(--dn-gold-rgb),.22));
  border:1px solid rgba(var(--dn-gold-rgb),.55);box-shadow:0 0 8px rgba(var(--dn-gold-rgb),.3)}

/* ---- five NEW purchasable card-back themes (same twin-selector pattern:
   html.dn-back-<id> equips it, .dn-bk-<id> is the card-level shop swatch).
   Each is deliberately tell-apart at 16-24px minis: navy-with-pinstripes,
   the only LIGHT back, saturated blue, graphite weave, purple damask. */
/* Midnight Pinstripe — deep navy banker's cloth, fine silver pinstripes */
html.dn-back-midnight-pinstripe .card.back,:root .card.back.dn-bk-midnight-pinstripe{
  background:
    repeating-linear-gradient(90deg,transparent 0 6px,rgba(190,205,235,.22) 6px 7px),
    radial-gradient(125% 95% at 50% 0%,#1d2a4e,#131c38 62%,#0a0f22);
  border-color:#060a18;
  box-shadow:0 2px 6px rgba(0,0,0,.5),
    inset 0 0 0 2px rgba(190,205,235,.4),
    inset 0 0 0 3px rgba(0,0,0,.5),
    inset 0 0 0 4px rgba(190,205,235,.14)}
html.dn-back-midnight-pinstripe .card.back::after,:root .card.back.dn-bk-midnight-pinstripe::after{
  inset:12% 16%;border-color:rgba(190,205,235,.45);background:none;border-radius:7px}
html.dn-back-midnight-pinstripe .card.back::before,:root .card.back.dn-bk-midnight-pinstripe::before{
  width:26%;height:26%;transform:translate(-50%,-50%) rotate(45deg);border-radius:3px;
  background:linear-gradient(135deg,rgba(190,205,235,.55),rgba(190,205,235,.16));
  border:1px solid rgba(190,205,235,.6);box-shadow:0 0 8px rgba(190,205,235,.3)}
/* Ivory Deco — the one LIGHT back: warm ivory field, gold art-deco sunburst */
html.dn-back-ivory-deco .card.back,:root .card.back.dn-bk-ivory-deco{
  background:
    repeating-conic-gradient(from 240deg at 50% 108%,rgba(201,155,46,.16) 0 5deg,transparent 5deg 14deg),
    radial-gradient(125% 95% at 50% 0%,#f7efdc,#efe2c2 62%,#ddc794);
  border-color:#8a6d2e;
  box-shadow:0 2px 6px rgba(0,0,0,.35),
    inset 0 0 0 2px rgba(138,109,46,.6),
    inset 0 0 0 3px rgba(247,239,220,.8),
    inset 0 0 0 4px rgba(138,109,46,.25)}
html.dn-back-ivory-deco .card.back::after,:root .card.back.dn-bk-ivory-deco::after{
  inset:12% 16%;border-color:rgba(138,109,46,.55);
  background:radial-gradient(circle at 50% 42%,rgba(255,255,255,.5),transparent 60%);border-radius:7px}
html.dn-back-ivory-deco .card.back::before,:root .card.back.dn-bk-ivory-deco::before{
  width:30%;height:22%;transform:translate(-50%,-50%);border:0;border-radius:0;box-shadow:none;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 44'%3E%3Cg fill='none' stroke='%238a6d2e' stroke-width='3'%3E%3Cpath d='M30 42V20'/%3E%3Cpath d='M30 42L12 8'/%3E%3Cpath d='M30 42L48 8'/%3E%3Cpath d='M30 42L2 26'/%3E%3Cpath d='M30 42L58 26'/%3E%3C/g%3E%3Ccircle cx='30' cy='42' r='6' fill='%23c99b2e'/%3E%3C/svg%3E") center/contain no-repeat}
/* Sapphire Filigree — saturated royal blue, pale-gold filigree scrollwork */
html.dn-back-sapphire-filigree .card.back,:root .card.back.dn-bk-sapphire-filigree{
  background:
    repeating-radial-gradient(circle at 50% 50%,rgba(240,212,138,.06) 0 1px,transparent 1px 8px),
    radial-gradient(125% 95% at 50% 0%,#1e56b4,#153f8a 62%,#0a2050);
  border-color:#071636;
  box-shadow:0 2px 6px rgba(0,0,0,.45),
    inset 0 0 0 2px rgba(240,212,138,.5),
    inset 0 0 0 3px rgba(0,0,0,.45),
    inset 0 0 0 4px rgba(240,212,138,.15)}
html.dn-back-sapphire-filigree .card.back::after,:root .card.back.dn-bk-sapphire-filigree::after{
  inset:11% 15%;border-color:rgba(240,212,138,.5);background:none;border-radius:8px}
html.dn-back-sapphire-filigree .card.back::before,:root .card.back.dn-bk-sapphire-filigree::before{
  width:58%;height:54%;border:0;border-radius:0;box-shadow:none;transform:translate(-50%,-50%);
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 80'%3E%3Cg fill='none' stroke='%23f0d48a' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M40 12c10 0 16 7 16 14s-6 12-16 12-16-5-16-12 6-14 16-14z'/%3E%3Cpath d='M40 38v20'/%3E%3Cpath d='M40 48c-8 0-14 4-16 10 8 0 13-3 16-7'/%3E%3Cpath d='M40 48c8 0 14 4 16 10-8 0-13-3-16-7'/%3E%3Cpath d='M28 20c-6-2-10-1-13 2 4 3 8 3 13 1'/%3E%3Cpath d='M52 20c6-2 10-1 13 2-4 3-8 3-13 1'/%3E%3C/g%3E%3Ccircle cx='40' cy='25' r='4' fill='%23f0d48a'/%3E%3C/svg%3E") center/contain no-repeat}
/* Onyx Carbon — graphite carbon weave, gunmetal frame, steel stud center */
html.dn-back-onyx-carbon .card.back,:root .card.back.dn-bk-onyx-carbon{
  background:
    repeating-linear-gradient(45deg,rgba(255,255,255,.05) 0 3px,transparent 3px 6px),
    repeating-linear-gradient(-45deg,rgba(0,0,0,.4) 0 3px,transparent 3px 6px),
    radial-gradient(125% 95% at 50% 0%,#2e3138,#1b1d22 62%,#0b0c0f);
  border-color:#000;
  box-shadow:0 2px 6px rgba(0,0,0,.55),
    inset 0 0 0 2px rgba(154,161,173,.45),
    inset 0 0 0 3px rgba(0,0,0,.6),
    inset 0 0 0 4px rgba(154,161,173,.14)}
html.dn-back-onyx-carbon .card.back::after,:root .card.back.dn-bk-onyx-carbon::after{
  inset:13% 17%;border-color:rgba(154,161,173,.45);
  background:radial-gradient(circle at 50% 50%,rgba(154,161,173,.1),transparent 62%);border-radius:6px}
html.dn-back-onyx-carbon .card.back::before,:root .card.back.dn-bk-onyx-carbon::before{
  width:24%;height:24%;transform:translate(-50%,-50%) rotate(45deg);border-radius:2px;
  background:linear-gradient(135deg,#c6ccd6,#5c616d);
  border:1px solid rgba(220,226,236,.7);box-shadow:0 0 9px rgba(154,161,173,.5)}
/* Royal Damask — regal purple damask, gold fleur ornament */
html.dn-back-royal-damask .card.back,:root .card.back.dn-bk-royal-damask{
  background:
    repeating-radial-gradient(circle at 25% 20%,rgba(240,212,138,.07) 0 2px,transparent 2px 11px),
    repeating-radial-gradient(circle at 75% 80%,rgba(240,212,138,.07) 0 2px,transparent 2px 11px),
    radial-gradient(125% 95% at 50% 0%,#6b3fa8,#4c2b7c 62%,#271343);
  border-color:#1a0c30;
  box-shadow:0 2px 6px rgba(0,0,0,.5),
    inset 0 0 0 2px rgba(240,212,138,.5),
    inset 0 0 0 3px rgba(0,0,0,.45),
    inset 0 0 0 4px rgba(240,212,138,.15)}
html.dn-back-royal-damask .card.back::after,:root .card.back.dn-bk-royal-damask::after{
  inset:12% 16%;border-color:rgba(240,212,138,.5);
  background:radial-gradient(circle at 50% 45%,rgba(240,212,138,.12),transparent 60%);border-radius:8px}
html.dn-back-royal-damask .card.back::before,:root .card.back.dn-bk-royal-damask::before{
  width:52%;height:52%;border:0;border-radius:0;box-shadow:none;transform:translate(-50%,-50%);
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cg fill='%23f0d48a'%3E%3Cpath d='M32 6c4 8 4 14 0 20-4-6-4-12 0-20z'/%3E%3Cpath d='M18 18c8 1 12 5 14 11-7-1-12-5-14-11z'/%3E%3Cpath d='M46 18c-8 1-12 5-14 11 7-1 12-5 14-11z'/%3E%3Cpath d='M29 30h6l-1 14h-4z'/%3E%3Cpath d='M24 46h16l-2 6H26z'/%3E%3C/g%3E%3C/svg%3E") center/contain no-repeat}
/* Premium Black Diamond — champagne-gold facets over black lacquer. The shared
   DC rose mark below remains the center signature at every card size. */
html.dn-back-premium-black-diamond .card.back,:root .card.back.dn-bk-premium-black-diamond{
  background:
    linear-gradient(135deg,transparent 0 46%,rgba(244,211,125,.2) 47% 50%,transparent 51%),
    linear-gradient(45deg,transparent 0 46%,rgba(244,211,125,.12) 47% 50%,transparent 51%),
    repeating-linear-gradient(90deg,rgba(255,255,255,.025) 0 1px,transparent 1px 9px),
    radial-gradient(125% 95% at 50% 0%,#26304a,#111522 58%,#050609);
  background-size:28px 28px,28px 28px,auto,auto;
  border-color:#d8b65a;
  box-shadow:0 2px 7px rgba(0,0,0,.58),inset 0 0 0 2px rgba(240,212,138,.62),
    inset 0 0 0 3px rgba(0,0,0,.62),inset 0 0 0 5px rgba(240,212,138,.13)}

/* ---- play-reveal (.dn-play-reveal, class contract shared with the MP client) ----
   Apply ONCE to a card landing in the trick/center together with its .dn-bk-<id>;
   remove both classes after ≤750ms (game-shell.js does this for solo; mp-room
   does it for live tables). A short edge-flash in the back's signature color —
   never persistent, runs a single iteration. Reduced motion / Animations off
   get a static tinted edge for the same window instead. */
.card.dn-bk-rose-noir{--dn-bk-glow:rgba(var(--dn-gold-rgb),.95)}
.card.dn-bk-midnight-pinstripe{--dn-bk-glow:rgba(154,180,235,.95)}
.card.dn-bk-ivory-deco{--dn-bk-glow:rgba(240,226,190,.98)}
.card.dn-bk-gold-royale{--dn-bk-glow:rgba(240,212,138,.98)}
.card.dn-bk-sapphire-filigree{--dn-bk-glow:rgba(80,140,235,.95)}
.card.dn-bk-crimson-velvet{--dn-bk-glow:rgba(224,86,99,.95)}
.card.dn-bk-emerald-lux{--dn-bk-glow:rgba(47,191,135,.95)}
.card.dn-bk-onyx-carbon{--dn-bk-glow:rgba(198,204,214,.9)}
.card.dn-bk-royal-damask{--dn-bk-glow:rgba(169,112,255,.95)}
.card.dn-play-reveal{animation:dnPlayReveal .62s ease-out 1}
@keyframes dnPlayReveal{
  0%{box-shadow:0 0 0 0 var(--dn-bk-glow,rgba(var(--dn-gold-rgb),.9))}
  24%{box-shadow:0 0 0 3px var(--dn-bk-glow,rgba(var(--dn-gold-rgb),.9)),0 0 20px 4px var(--dn-bk-glow,rgba(var(--dn-gold-rgb),.55))}
  100%{box-shadow:0 0 0 0 rgba(0,0,0,0)}}
@media (prefers-reduced-motion:reduce){
  .card.dn-play-reveal{animation:none;box-shadow:0 0 0 2px var(--dn-bk-glow,rgba(var(--dn-gold-rgb),.75))}}
html.dn-anim-off .card.dn-play-reveal{animation:none;box-shadow:0 0 0 2px var(--dn-bk-glow,rgba(var(--dn-gold-rgb),.75))}

/* ---- flagship prestige table (large-coin-cost goal per Jesse) ---- */
/* Sovereign Gold felt — black-gold baize with a bright gold etch ring */
html.dn-felt-sovereign{--felt-hi:#7a6420;--felt-mid:#554414;--felt-lo:#2b2208;--felt-line:#1a1404;
  --felt-etch:rgba(240,212,138,.42)}
.dn-swatch-sovereign{background:radial-gradient(circle at 50% 35%,#7a6420,#2b2208);
  box-shadow:0 1px 4px rgba(0,0,0,.5),inset 0 0 0 1px rgba(240,212,138,.75)}
/* Penthouse Skyline room — night glass, warm city glow low on the horizon */
html.dn-room-penthouse body{background:
  radial-gradient(1100px 620px at 50% -12%,rgba(120,150,255,.14),transparent 60%),
  radial-gradient(900px 320px at 50% 106%,rgba(240,180,80,.22),transparent 68%),
  repeating-linear-gradient(90deg,transparent 0 46px,rgba(255,214,140,.05) 46px 49px),
  repeating-radial-gradient(circle at 78% 18%,rgba(255,255,255,.035) 0 1px,transparent 1px 46px),
  linear-gradient(180deg,#0b0e1c,#050308)}
.dn-room-swatch-penthouse{background:linear-gradient(180deg,#0b0e1c 40%,#241503)}

/* ---- free-coin timer: quiet HUD nudge when a claim is ready ---- */
.dn-coin.dn-free-ready .dn-coin-plus{color:#4bd0a0;text-shadow:0 0 8px rgba(75,208,160,.8)}
/* Free-coin countdown, inside the existing pill. It replaces the "+" rather
   than sitting beside it, so the control is one thing in two states: counting
   down, then ready. Tabular figures stop the pill twitching every second. */
.dn-coin .dn-free-timer{margin-left:6px;font-size:.82em;font-weight:800;
  font-variant-numeric:tabular-nums;letter-spacing:-.02em;opacity:.86;
  /* The balance above is the pill's designated shrink victim (min-width:0 plus
     an ellipsis), so anything added beside it takes its width first. On a 390px
     phone that ate the number entirely and left "🪙 23:0…" — a coin pill with no
     coins in it. The timer is the secondary reading, so it yields instead: it
     clips itself, and the balance keeps a floor wide enough for a compacted
     figure like "12.3M". */
  flex:0 1 auto;min-width:0;overflow:hidden;white-space:nowrap}
.dn-coin.dn-free-ready .dn-free-timer{display:none}
/* SEAT PHOTO. Replaces the initial without changing the box: the turn ring,
   the books badge and the scoreboard tap target all hang off this same node, so
   the photo is a background rather than a new element. */
.seat-av.dn-seat-photo{background-size:cover;background-position:center;
  color:transparent;text-shadow:none}
/* The books badge from seatStats sits on top of a photo, so it keeps its own
   plate rather than relying on the avatar colour behind it. */
.seat-av.dn-seat-photo .dn-ss-badge{box-shadow:0 0 0 2px var(--dn-ink-900)}

/* GIFTS beside the name. Each chip carries its own remaining life, so the row
   is whatever is currently live for that seat and nothing else. */
.dn-seat-gifts{display:inline-flex;align-items:center;gap:3px;margin-left:5px;
  vertical-align:middle}
.dn-seat-gifts[hidden]{display:none}
.dn-seat-gift{display:inline-grid;place-items:center;min-width:19px;height:19px;
  padding:0 2px;border-radius:7px;font-size:12px;line-height:1;
  background:rgba(var(--dn-gold-rgb),.16);
  box-shadow:inset 0 0 0 1px rgba(var(--dn-gold-rgb),.42)}
/* A gift lasting beyond this game reads differently from a one-game one, so a
   player can tell at a glance which of theirs will still be there next hand. */
.dn-seat-gift[data-dn-games]:not([data-dn-games="1"]){
  background:rgba(var(--dn-gold-rgb),.3);
  box-shadow:inset 0 0 0 1px var(--dn-gold)}

/* DAILY SPIN DISC. All v3 wedges are visibly equal. The large center, the
   button below, and a deliberate swipe all claim the same server event. */
.dn-spin{display:flex;flex-direction:column;align-items:center;gap:6px;
  margin:5px 0 2px;position:relative;width:100%}
.dn-spin-disc{width:min(96vw,460px);max-width:100%;aspect-ratio:1;border-radius:50%;
  position:relative;display:grid;place-items:center;
  border:5px solid var(--dn-gold);
  box-shadow:0 9px 24px rgba(0,0,0,.46),inset 0 0 0 3px rgba(0,0,0,.28);
  transform:rotate(0deg);touch-action:pan-y}
.dn-spin-prize{position:absolute;z-index:1;transform:translate(-50%,-50%);
  pointer-events:none}
.dn-spin-prize-text{display:grid;place-items:center;padding:0;background:transparent;
  color:#fffdf4;border:0;border-radius:0;box-shadow:none;
  font-size:clamp(29px,9.2vw,42px);font-weight:950;letter-spacing:.015em;
  line-height:1;font-variant-numeric:tabular-nums;white-space:nowrap;
  text-shadow:0 2px 3px rgba(0,0,0,.96),0 0 7px rgba(0,0,0,.58);
  transform:rotate(var(--dn-prize-angle,0deg)) scaleX(1.12);transform-origin:center}
.dn-spin-prize-text b{font:inherit}
.dn-spin-rare .dn-spin-prize-text{color:#fff1ad}
.dn-spin-ultra .dn-spin-prize-text{color:#fff1ad;
  text-shadow:0 2px 3px rgba(0,0,0,.98),0 0 9px rgba(255,218,90,.34)}
.dn-spin-face{width:34%;aspect-ratio:1;border-radius:50%;
  background:var(--dn-ink-800);color:var(--dn-gold);
  display:grid;place-items:center;font-weight:900;
  font-size:clamp(15px,4.5vw,21px);font-variant-numeric:tabular-nums;
  text-align:center;line-height:1.05;padding:4px;
  box-shadow:inset 0 0 0 3px var(--dn-gold-deep),0 3px 9px rgba(0,0,0,.4);
  border:0;cursor:pointer;z-index:2}
.dn-spin-face:focus-visible{outline:4px solid #fff;outline-offset:4px}
.dn-spin-face:disabled{cursor:default;opacity:1}
.dn-spin-action{width:min(78%,280px);min-height:48px;margin-top:3px;
  padding:9px 18px;font-size:18px;font-weight:950;letter-spacing:.025em}
.dn-daily-spin-modal .btn.dn-spin-action{min-height:48px}
.dn-spin-action:disabled{opacity:.62;cursor:default}
.dn-spin-pointer{width:0;height:0;border-left:14px solid transparent;
  border-right:14px solid transparent;border-top:20px solid var(--dn-gold);
  filter:drop-shadow(0 2px 3px rgba(0,0,0,.5));margin-bottom:-7px;z-index:3}
.dn-spin-hint{margin:4px 0 0;color:#d7dcf0;font-size:14px;font-weight:800;
  text-align:center}
.shell-modal-back.dn-daily-spin-back{display:flex;align-items:center;justify-content:center;
  overflow-y:auto;background:rgba(7,9,15,.94);
  padding:calc(12px + var(--dn-safe-top)) calc(4px + var(--dn-safe-right))
    calc(12px + var(--dn-safe-bottom)) calc(4px + var(--dn-safe-left))}
.dn-daily-spin-modal{width:min(100%,540px);height:auto;max-width:540px!important;
  max-height:none!important;margin:auto;padding:14px 4px 12px;border-radius:24px;
  overflow:visible;text-align:center;border:1px solid rgba(var(--dn-gold-rgb),.48);
  box-shadow:0 28px 72px rgba(0,0,0,.68)}
.dn-daily-spin-modal h3{font-size:clamp(24px,6vw,31px);margin:0 0 2px;color:#fff2bf}
.dn-daily-spin-copy{margin:0 0 7px;color:#cdd4e8;font-size:14px;font-weight:750}
.dn-daily-spin-skip{margin-top:7px}
.dn-daily-spin-modal > .btn.dn-daily-spin-skip{
  width:min(calc(100% - 22px),448px);min-height:64px;
  font-size:21px;font-weight:900;justify-self:center}
.dn-paid-spin[hidden]{display:none}
.dn-paid-spin{width:min(100%,470px);margin:4px auto 0;padding:10px 10px 9px;
  border-radius:16px;background:rgba(9,21,42,.94);
  border:1px solid rgba(var(--dn-gold-rgb),.54);text-align:center}
.dn-paid-spin h4{margin:0;color:#fff2bf;font-size:20px;line-height:1.15}
.dn-paid-spin-buy{width:100%;min-height:48px;padding:9px 12px;font-size:16px;
  line-height:1.15;font-weight:900;white-space:normal}
.dn-daily-spin-modal .btn.dn-paid-spin-buy{min-height:64px;font-size:21px}
/* Honest wheel presentation (OFF by default — see HONEST_WHEEL_PRESENTATION in
   premium.js). Nothing carries these classes while the flag is off, so these
   rules are inert in the shipped build. Restored unchanged from the live
   compliant v1 paid spin so both disclosures look the same. */
.dn-paid-spin-odds-label{margin:8px 0 4px;color:#fff2bf;font-size:14px;font-weight:900}
.dn-paid-spin-odds{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:4px;font-variant-numeric:tabular-nums}
.dn-paid-spin-odds span{padding:4px 2px;border-radius:7px;background:rgba(255,255,255,.08);
  color:#f5f7ff;font-size:13px;font-weight:850;line-height:1.15;white-space:nowrap}
/* The legend under the wheel. Only rendered when the wedges are drawn to scale,
   because a proportional picture without its numbers is still a claim. */
.dn-spin-legend{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:4px;
  width:min(96vw,460px);margin:6px auto 0;font-variant-numeric:tabular-nums}
.dn-spin-legend-item{padding:4px 2px;border-radius:7px;background:rgba(255,255,255,.08);
  color:#f5f7ff;font-size:13px;font-weight:850;line-height:1.15;white-space:nowrap;
  text-align:center}
/* A wedge drawn at its true rarity is too narrow to seat a full-size figure.
   Shrink it and let it sit further out toward the rim; the exact probability
   is published in the odds list above, which is the disclosure that counts. */
.dn-spin-sliver .dn-spin-prize-text{font-size:clamp(13px,3.6vw,19px);
  text-shadow:0 1px 3px rgba(0,0,0,.85)}
.dn-paid-spin-status{min-height:16px;margin:6px 0 0;color:#d8deef;
  font-size:13px;font-weight:700;line-height:1.2}
@media (max-height:620px) and (orientation:landscape){
  .dn-daily-spin-modal{width:min(100%,760px);max-width:760px!important;padding:8px 12px;display:grid;
    grid-template-columns:minmax(275px,430px) minmax(190px,1fr);align-items:center;
    column-gap:12px;text-align:left}
  .dn-daily-spin-modal h3,.dn-daily-spin-copy,.dn-paid-spin,.dn-daily-spin-skip{grid-column:2}
  .dn-daily-spin-modal h3{grid-row:1;margin-top:auto}
  .dn-daily-spin-copy{grid-row:2}
  .dn-daily-spin-modal .dn-spin{grid-column:1;grid-row:1 / span 5}
  .dn-daily-spin-modal .dn-spin-disc{width:min(64vh,430px)}
  .dn-paid-spin{grid-row:3;margin:2px 0;padding:7px}
  .dn-paid-spin h4{font-size:18px}
  .dn-daily-spin-modal .btn.dn-paid-spin-buy{min-height:50px;font-size:16px;padding:6px}
  .dn-spin-legend{grid-template-columns:repeat(4,minmax(0,1fr));gap:2px;margin-top:4px}
  .dn-spin-legend-item{font-size:11.5px;padding:2px}
  .dn-paid-spin-odds-label{margin:5px 0 3px;font-size:13px}
  .dn-paid-spin-odds{grid-template-columns:repeat(2,minmax(0,1fr));gap:2px}
  .dn-paid-spin-odds span{font-size:12.5px;padding:2px}
  .dn-paid-spin-status{font-size:12.5px;margin-top:4px}
  .dn-daily-spin-modal > .btn.dn-daily-spin-skip{
    grid-row:4;width:calc(100% - 16px);min-height:50px;margin:5px auto auto}
}
/* Reduced motion keeps the result, drops the spin. */
html.dn-anim-off .dn-spin-disc,
html.dn-anim-off .dn-spin-face,
html.dn-anim-off .dn-spin-prize-text{transition:none!important}
@media (prefers-reduced-motion:reduce){
  .dn-spin-disc,.dn-spin-face,.dn-spin-prize-text{transition:none!important}
}
.dn-coin:has(.dn-free-timer:not([hidden])) .dn-coin-plus{display:none}

/* The rules "?" is THE way to re-read rules (coaches auto-show only on a
   game's first-ever open) — make it read as a real control, not a ghost:
   gold-ringed circle, always visible in every game topbar. */
.shell-help{width:34px;height:34px;padding:0 !important;border-radius:50% !important;
  flex:0 0 auto;font-size:16px;font-weight:800;line-height:1;
  color:var(--gold);border:1.5px solid rgba(var(--dn-gold-rgb),.65);background:rgba(var(--dn-gold-rgb),.08)}
.shell-help:active{transform:translateY(1px)}

/* ---- shared DeNiro Card deck mark -------------------------------------------------
   The free Classic back is the actual live DeNiro card-back artwork. Every paid
   material keeps its own surface, but carries the same authentic gold DC-with-rose
   logo from the live brand rather than an abstract placeholder glyph. */
.card.back{--dn-back-frame:rgba(240,212,138,.66);--dn-back-glow:rgba(240,212,138,.14)}
html.dn-back-rose-noir,:root .card.back.dn-bk-rose-noir{--dn-back-frame:rgba(241,204,115,.72);--dn-back-glow:rgba(241,204,115,.16)}
html.dn-back-gold-royale,:root .card.back.dn-bk-gold-royale{--dn-back-frame:rgba(74,54,9,.7);--dn-back-glow:rgba(74,54,9,.13)}
html.dn-back-crimson-velvet,:root .card.back.dn-bk-crimson-velvet{--dn-back-frame:rgba(244,212,127,.72);--dn-back-glow:rgba(244,212,127,.16)}
html.dn-back-emerald-lux,:root .card.back.dn-bk-emerald-lux{--dn-back-frame:rgba(243,212,110,.72);--dn-back-glow:rgba(243,212,110,.15)}
html.dn-back-midnight-pinstripe,:root .card.back.dn-bk-midnight-pinstripe{--dn-back-frame:rgba(184,210,255,.7);--dn-back-glow:rgba(184,210,255,.15)}
html.dn-back-ivory-deco,:root .card.back.dn-bk-ivory-deco{--dn-back-frame:rgba(111,73,20,.62);--dn-back-glow:rgba(111,73,20,.13)}
html.dn-back-sapphire-filigree,:root .card.back.dn-bk-sapphire-filigree{--dn-back-frame:rgba(232,241,255,.72);--dn-back-glow:rgba(232,241,255,.15)}
html.dn-back-onyx-carbon,:root .card.back.dn-bk-onyx-carbon{--dn-back-frame:rgba(228,233,242,.66);--dn-back-glow:rgba(228,233,242,.14)}
html.dn-back-royal-damask,:root .card.back.dn-bk-royal-damask{--dn-back-frame:rgba(255,224,138,.72);--dn-back-glow:rgba(255,224,138,.16)}
html.dn-back-premium-black-diamond,:root .card.back.dn-bk-premium-black-diamond{
  --dn-back-frame:rgba(255,226,143,.9);--dn-back-glow:rgba(240,212,138,.27)}

/* Match the specificity of every existing pack override without changing a pack ID,
   its owned state, pricing, or the buyer-only / opponent-classic policy. */
:root .card.back::after,
:root .card.back.dn-back-theirs::after,
:root .card.back[class*="dn-bk-"]::after,
html[class*="dn-back-"] .card.back::after{
  content:"";inset:9% 13%;border-radius:8px;border:1px solid var(--dn-back-frame);
  background:radial-gradient(circle at 50% 44%,var(--dn-back-glow),transparent 62%);
  box-shadow:inset 0 0 0 2px rgba(0,0,0,.16),0 0 0 1px rgba(255,255,255,.05)
}
:root .card.back::before,
:root .card.back.dn-back-theirs::before,
:root .card.back[class*="dn-bk-"]::before,
html[class*="dn-back-"] .card.back::before{
  content:"";top:50%;left:50%;width:63%;height:58%;border:0;border-radius:0;
  transform:translate(-50%,-50%);background:url("icons/dc-rose-gold.png") center/contain no-repeat;
  box-shadow:none;filter:drop-shadow(0 1px 4px var(--dn-back-glow));
  -webkit-mask:none;mask:none
}
/* On tiny opponent cards and shop swatches the real mark stays readable. */
.card.mini.back::before{width:66%;height:60%}

/* Classic is a faithful local copy of the live back — no external dependency.
   Opponents always use it, and the free shop swatch stays truthful to the deck. */
html:not([class*="dn-back-"]) .card.back:not([class*="dn-bk-"]),
:root .card.back.dn-back-theirs,
:root .card.back.dn-bk-classic{
  background:#090d1f url("icons/dc-rose-card-back.png") center/100% 100% no-repeat;
  border-color:#e7c52b;
  box-shadow:0 2px 6px rgba(0,0,0,.42),inset 0 0 0 1px rgba(255,225,110,.25)
}
html:not([class*="dn-back-"]) .card.back:not([class*="dn-bk-"])::after,
html:not([class*="dn-back-"]) .card.back:not([class*="dn-bk-"])::before,
:root .card.back.dn-back-theirs::after,
:root .card.back.dn-back-theirs::before,
:root .card.back.dn-bk-classic::after,
:root .card.back.dn-bk-classic::before{display:none}

/* ---- Comfortable text & controls -------------------------------------------------
   Native WebView zoom stays off while the public web remains pinch-zoomable. This
   optional in-app preference gives players a reliable, saved way to enlarge the
   things that matter. Portrait gets the full
   boost.  Landscape keeps its dense table geometry, but still increases labels
   and targets enough to be noticeably easier to read without covering the hand. */
html.dn-large-ui .shell-modal .shell-group{font-size:14px}
html.dn-large-ui .shell-modal .shell-row{min-height:48px;font-size:16px}
html.dn-large-ui .shell-modal .shell-select{min-height:42px;font-size:16px}
html.dn-large-ui:not(.dn-land) .felt .seat .name{font-size:16px}
html.dn-large-ui:not(.dn-land) .felt .seat .meta{font-size:16px;line-height:1.35}
html.dn-large-ui:not(.dn-land) .felt .msg{font-size:17px;line-height:1.34}
html.dn-large-ui:not(.dn-land) .felt .pile{font-size:16px}
html.dn-large-ui:not(.dn-land) .felt .scorebar{font-size:15px}
html.dn-large-ui:not(.dn-land) .felt .controls .btn{min-height:46px;font-size:16px}
html.dn-large-ui:not(.dn-land) .felt .hand.mine .card{width:clamp(58px,16vw,86px)}
/* These landscape sizes were 13px/12.5px, chosen when the landscape base was 12.5px.
   The base is now a 14px floor, so leaving them alone made "Larger text" SHRINK the
   table — the exact opposite of the setting. Each one now steps up from 14px. */
html.dn-large-ui.dn-land .felt.dn-table .dn-seat .name{font-size:15px}
html.dn-large-ui.dn-land .felt.dn-table .dn-seat .meta{font-size:15px}
html.dn-large-ui.dn-land .felt.dn-table .msg{font-size:15px}
html.dn-large-ui.dn-land .felt.dn-table #score:not(:empty),
html.dn-large-ui.dn-land .felt.dn-table .scorebar:not(:empty){font-size:15px}
html.dn-large-ui.dn-land .felt.dn-table #score[data-dn-compact][data-dn-fit]::after,
html.dn-large-ui.dn-land .felt.dn-table .scorebar[data-dn-compact][data-dn-fit]::after{
  font-size:15px}
html.dn-large-ui.dn-land .felt.dn-table .controls .btn{min-height:44px;font-size:14px}
html.dn-large-ui .felt .lasttrick,
html.dn-large-ui .felt #status,
html.dn-large-ui .felt #hint,
html.dn-large-ui .felt .bagcount,
html.dn-large-ui .felt .breakdown{font-size:14px;line-height:1.35}
html.dn-large-ui .felt .dn-bidpanel .dn-bp-prompt{font-size:14px}
html.dn-large-ui .felt .dn-bidpanel .dn-bp-opt,
html.dn-large-ui .felt .dn-bidpanel .dn-bp-bid,
html.dn-large-ui .felt .dn-bidpanel .dn-bp-pass{font-size:15px}
html.dn-large-ui .dn-seg-btn{min-height:48px;font-size:15px}
html.dn-large-ui .dn-tab{min-height:52px;font-size:14px}
html.dn-large-ui .dn-section-label,
html.dn-large-ui .dn-stat .lbl{font-size:13px}

/* Phone release contract: controls may look compact on a short table, but their
   ACTUAL, non-overlapping boxes stay at the 44px mobile target floor. Do not
   reintroduce transparent pseudo hit-slop: adjacent Help/Settings controls can
   otherwise activate each other. Board cells and playing cards are spatial game
   pieces, so they keep their board/fan geometry. */
@media (pointer:coarse),(max-width:900px){
  header .header-cta .btn{
    min-height:44px;display:inline-flex;align-items:center;justify-content:center}
  html .topbar .brand{min-width:44px;min-height:44px;justify-content:center}
  html .topbar .shell-actions .btn,html .topbar > .shell-help{
    min-width:44px;min-height:44px;height:44px;padding:0;
    display:grid;place-items:center}
  html .topbar .shell-actions .shell-new-game{width:auto;min-width:44px;padding:0 8px}
  html .topbar .dn-coin{min-height:44px}
  .controls .btn,.ctrlrow .btn,.shell-modal .btn,.bid-pick button{
    min-height:44px}
  .shell-modal .btn{min-width:44px}
  .shell-select{min-height:44px}
  .shell-switch{height:44px;min-height:44px}
  .shell-switch::after{top:10px}
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-opt,
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-bid,
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-pass{
    min-height:44px;height:44px}
}

/* ---- Bidding happens in the CENTRE of the table, never on top of the hand ----
   Reported by Jesse: the bid pop-up covered the cards you are bidding on. The old
   fix only ran inside `@media (orientation:landscape) and (max-height:430px)`, so
   it missed BOTH real cases — any landscape table taller than 430px (measured
   overlap: 23,275px² on a 740x460 split view, 19,163px² on euchre, ~11,000px² on a
   1280x800 tablet) and the CSS-rotated portrait fallback (html.dn-rot), whose
   VIEWPORT is portrait and therefore never matched an orientation:landscape query
   at all — that is where the panel sat squarely over the fan.
   --dn-hand-lane is the strip the pinned hand owns: its bottom offset plus a card
   height derived from the SAME expression that sizes the cards (below) so the two
   cannot drift, floored at fanHand's 86px short-hand hero card (86*1.4) which only
   the script knows about. This is the BOOTSTRAP value — publishHandLane() overwrites
   it with the measured strip on every fan. The host then spans the felt from under
   the score strip down to the top of that lane, and the panel is anchored to the
   BOTTOM of that band and grows upward — never downward. That direction is the whole
   guarantee: `align-items:center` was measured spilling a three-row panel
   symmetrically (12px back onto the cards on a 568x320 table), because a panel taller
   than its band overflows both ends. The band's floor is then lifted to 40% of the
   FELT's height (a percentage, not vh — html.dn-rot's vh is the phone's long edge and
   would be nonsense) so on a roomy tablet the panel rises into the middle of the table
   instead of hugging the fan, while max() keeps the 12px hand clearance absolute. */
html.dn-land .felt.dn-table{
  --dn-hand-lane:calc(16px + var(--dn-safe-bottom) + max(121px,min(calc(clamp(66px,12.5vw,124px) * 1.4),32vh)));
}
@media (min-width:800px) and (pointer:fine){
  html.dn-land .felt.dn-table{
    --dn-hand-lane:calc(16px + var(--dn-safe-bottom) + max(121px,min(calc(clamp(66px,11vw,124px) * 1.4),32vh)));
  }
}
html.dn-land .felt.dn-table:has(.dn-bidpanel) > #msg{display:none!important}
html.dn-land .felt.dn-table #bidbar:has(.dn-bidpanel),
html.dn-land .felt.dn-table .bid-pick:has(.dn-bidpanel){
  position:absolute!important;
  left:max(10px,var(--dn-safe-left))!important;
  right:max(10px,var(--dn-safe-right))!important;
  top:24px!important;
  /* Six measured pixels above the published hand lane is enough separation.
     Keeping the roomy-table lift at 35% gives the compact auction another
     9–16px on short phones without ever entering the cards. */
  bottom:max(calc(var(--dn-hand-lane) + 6px),35%)!important;
  width:auto!important;max-width:none!important;margin:0!important;padding:0!important;
  transform:none!important;z-index:12!important;pointer-events:none;
  /* !important on align-items too: the flowing-bar rules above are a whole class
     more specific (#bidbar:not(:empty):not(.hidden)) and their align-items:center
     silently re-centred the panel — which is what put it back on the cards. */
  display:flex!important;justify-content:center!important;align-items:flex-end!important}
html.dn-land .felt.dn-table .dn-bidpanel{pointer-events:auto}

/* Bid Whist has two independent choices (number + direction). Stacking those
   above two full-width action buttons made the chooser 159–194px tall, which
   necessarily covered North on a short landscape phone. Keep every 44px target,
   but use the otherwise-empty action column. The resulting 344px-wide chooser
   clears both side-seat rails even at 568px. */
html.dn-land .felt.dn-table .dn-bidpanel:has(.dn-bp-dirs){
  display:grid;
  grid-template-columns:auto 88px;
  grid-template-areas:
    "prompt prompt"
    "nums actions"
    "dirs actions";
  gap:4px 6px;
  width:max-content;
  max-width:calc(100vw - 190px);
  padding:7px 10px 9px;
}
html.dn-land .felt.dn-table .dn-bidpanel:has(.dn-bp-dirs) .dn-bp-prompt{grid-area:prompt}
html.dn-land .felt.dn-table .dn-bidpanel:has(.dn-bp-dirs) .dn-bp-nums{grid-area:nums}
html.dn-land .felt.dn-table .dn-bidpanel:has(.dn-bp-dirs) .dn-bp-dirs{grid-area:dirs}
html.dn-land .felt.dn-table .dn-bidpanel:has(.dn-bp-dirs) .dn-bp-actions{
  grid-area:actions;display:flex;flex-direction:column;gap:4px;margin:0}
html.dn-land .felt.dn-table .dn-bidpanel:has(.dn-bp-dirs) .dn-bp-opt{
  width:44px;min-width:44px;height:44px;padding:0}
html.dn-land .felt.dn-table .dn-bidpanel:has(.dn-bp-dirs) .dn-bp-dir{
  width:72px;min-width:72px;font-size:10.5px;letter-spacing:.1px}
html.dn-land .felt.dn-table .dn-bidpanel:has(.dn-bp-dirs) .dn-bp-bid,
html.dn-land .felt.dn-table .dn-bidpanel:has(.dn-bp-dirs) .dn-bp-pass{
  width:88px;min-width:88px;height:44px}

/* Euchre's option, Go Alone, and Pass controls used to form four vertical rows.
   The option stays full-width; the two independent footer actions share one row.
   Round-two's three suit choices still fit between the side seats. */
html.dn-land .felt.dn-table .dn-bidpanel.dn-bpm-options{
  display:grid;
  grid-template-columns:auto auto;
  grid-template-areas:
    "prompt prompt"
    "options options"
    "toggle actions";
  gap:5px 8px;
  width:max-content;
  max-width:calc(100vw - 190px);
  padding:7px 10px 9px;
}
html.dn-land .felt.dn-table .dn-bidpanel.dn-bpm-options .dn-bp-prompt{
  grid-area:prompt}
html.dn-land .felt.dn-table .dn-bidpanel.dn-bpm-options .dn-bp-options{
  grid-area:options}
html.dn-land .felt.dn-table .dn-bidpanel.dn-bpm-options .dn-bp-toggle{
  grid-area:toggle;margin:0}
html.dn-land .felt.dn-table .dn-bidpanel.dn-bpm-options .dn-bp-actions{
  grid-area:actions;margin:0}
html.dn-land .felt.dn-table .dn-bidpanel.dn-bpm-options:not(:has(.dn-bp-toggle)) .dn-bp-actions{
  grid-column:1 / -1;justify-self:center}
/* On a 320px-tall table (iPhone SE 1st gen in landscape) Euchre's four-row panel is
   taller than the whole band, so growing upward walked its first row off the felt.
   The option buttons already say what the choice is — trade the prompt line and some
   padding rather than the hand. dn-rot's felt height is the viewport WIDTH, hence the
   second query. */
@media (max-height:340px),(max-width:340px){
  html.dn-land .felt.dn-table .dn-bidpanel{padding:6px 12px 8px;gap:4px}
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-prompt{display:none}
  /* At 568x320 there is no fourth 44px lane between North and the hand.
     The persistent 44px ? button remains the on-demand rules path. */
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-legend{display:none}
}
@media (max-height:430px){
  /* The decision buttons already say Bid, Pass, or the bid value. Retiring the
     duplicate prompt on a short phone preserves the full 44px glossary target
     while keeping both the North seat and the player's hand unobstructed. This
     sits after the landscape panel rules so it is the final phone geometry. */
  html.dn-land .felt.dn-table .dn-bidpanel{padding:4px 10px;gap:3px}
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-prompt{display:none}
}

/* ---- Per-seat bid bubble (Shell.seatBid) ----
   With the chooser off in the middle of the table, the auction is only readable if
   each seat says what IT did — the reference shows a small gold "BID 5" / "PASS"
   bubble beside every avatar. Landscape-table only: the stacked portrait layout has
   room for the seat's full status sentence and already prints the bid there. */
.dn-bidbubble{display:none}
html.dn-land .felt.dn-table .dn-bidbubble{
  display:inline-flex;align-items:center;justify-content:center;max-width:100%;
  margin-top:2px;padding:1px 9px;border-radius:999px;
  background:linear-gradient(180deg,#f0d488,#c99a3e);color:#2a1c05;
  font-weight:900;font-size:12px;letter-spacing:.03em;line-height:1.55;
  text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  box-shadow:0 2px 6px rgba(0,0,0,.45),inset 0 1px 0 rgba(255,255,255,.4)}
/* The seat's own bid sentence is redundant once the bubble carries it, and the seats
   that have NOT bid yet were printing a placeholder tail ("bid — · won 0") wide
   enough to run under the centred pop-up. Retire the whole meta line on the landscape
   table for the length of the auction; it comes back for the trick counts at play.
   Portrait keeps it — that layout has room for the sentence and shows no bubble. */
html.dn-land.dn-bidding .felt.dn-table .dn-seat .meta,
html.dn-land .felt.dn-table .dn-seat:has(.dn-bidbubble) .meta{display:none}

/* Auction seats are status rails, not tall player cards. The avatar letters,
   coin chip, and live bid bubble retain all useful information; redundant
   names/meta retire so the chooser can own the middle without overlap. North
   uses a single compact row above the chooser. */
html.dn-land.dn-bidding .felt.dn-table .dn-seat{overflow:visible}
html.dn-land.dn-bidding .felt.dn-table .dn-seat .name,
html.dn-land.dn-bidding .felt.dn-table .dn-seat .meta{display:none!important}
html.dn-land.dn-bidding .felt.dn-table .dn-seat-top{
  top:58px;
  display:grid;
  grid-template-columns:28px auto auto;
  grid-template-rows:28px;
  align-items:center;
  justify-items:center;
  column-gap:5px;
  width:max-content;
  max-width:52vw;
  padding:0 6px;
}
html.dn-land.dn-bidding .felt.dn-table .dn-seat-top>.seat-av{grid-column:1;grid-row:1}
html.dn-land.dn-bidding .felt.dn-table .dn-seat-top>.dn-seatstats{
  grid-column:2;grid-row:1;margin:0}
html.dn-land.dn-bidding .felt.dn-table .dn-seat-top>.dn-bidbubble{
  grid-column:3;grid-row:1;margin:0}
html.dn-land.dn-bidding .felt.dn-table .dn-seat .dn-bidbubble{
  max-width:86px;padding-left:6px;padding-right:6px;font-size:11px}
/* YOUR bubble has no seat chip to sit in — float it centred just above the fan, in
   the lane the bid pop-up vacates the moment you have bid. */
html.dn-land .felt.dn-table > .dn-bidbubble.dn-bb-you{
  position:absolute;left:50%;transform:translateX(-50%);
  bottom:calc(var(--dn-hand-lane) + 6px);z-index:9;max-width:60%}

/* ---- Per-seat stats (Shell.seatStats): books won + truthful table stack ----
   Both numbers have to outlive focus mode — they are exactly what a player checks
   mid-hand — so neither may live in .name/.meta (html.dn-focus hides those).

   The books count is a badge ON THE AVATAR, like the reference table. That also
   keeps it free: a second chip in the row measured wide enough to push the
   landscape seat into the centred message band (spades 568x320: 170px² of overlap).
   It hangs 3px into the seat's own padding, which is inside the padding box that
   .dn-seat{overflow:hidden} clips to, so the size cap on opponent fans still holds. */
.dn-ss-badge{position:absolute;top:-3px;right:-3px;min-width:21px;height:21px;
  padding:0 4px;border-radius:999px;display:flex;align-items:center;justify-content:center;
  background:linear-gradient(180deg,#8ff0c6,#2fae7e);color:#03251a;
  font-weight:900;font-size:13px;line-height:1;letter-spacing:0;text-shadow:none;
  border:1px solid rgba(3,20,14,.4);
  box-shadow:0 1px 3px rgba(0,0,0,.5),inset 0 1px 0 rgba(255,255,255,.45)}
/* 11px in a 17px circle was the smallest live number on the table; the trick count is how
   a player knows who is winning, so it grows with the rest of the floor. */
html.dn-land .felt.dn-table .dn-ss-badge{min-width:20px;height:20px;font-size:13px}
html.dn-large-ui .dn-ss-badge{min-width:23px;height:23px;font-size:14px}
/* The table-stack row. `span:empty` is the hide, so its gap cannot open around a
   chip with nothing to say (YOUR row carries a books chip too — see below). */
.dn-seatstats{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;
  gap:5px;margin:1px 0 0;max-width:100%;line-height:1}
.dn-seatstats span:empty{display:none}
.dn-seatstats span{display:inline-flex;align-items:center;gap:3px;
  padding:2px 7px;border-radius:999px;white-space:nowrap;
  font-weight:900;font-size:12px;letter-spacing:.04em;
  /* opaque enough to stay crisp when it lands on a white trick card — the top seat's
     chip sits where the seat-2 pile fans up (translate -28px) toward it */
  background:rgba(6,15,11,.95);border:1px solid rgba(255,255,255,.16);
  text-shadow:none}
/* mint = live hand state (same family as the turn glow and the badge); gold = money */
.dn-seatstats .dn-ss-tricks{color:#8ff0c6}
.dn-seatstats .dn-ss-coins{color:#f0d48a;border-color:rgba(226,196,120,.32)}
/* SIM is already the provenance label; removing display tracking keeps the added
   truth marker inside the measured North/message lanes on short phones. */
.dn-seatstats .dn-ss-coins.dn-ss-simulated{letter-spacing:0}
html.dn-land .felt.dn-table .dn-seatstats{gap:4px;margin-top:0}
html.dn-land .felt.dn-table .dn-seatstats span{font-size:14px;padding:1px 7px}
/* YOUR row has no seat to live in: in landscape the human seat is collapsed (its own
   name and meta are hidden) and bid-whist has no seat wrapper at all, so an in-flow
   row would either vanish or push the felt's centred column apart. The felt's top-left
   is NOT free — the back pill sits there (measured 1,114px² of overlap) — so pin it
   to the bottom-left, in the strip the hand publishes, hard against the safe edge.
   The max-width keeps it inside the 20% margin that the centred msg/breakdown band
   (max-width:60%) leaves free, wrapping to a second line on a narrow table. */
html.dn-land .felt.dn-table .dn-seatstats.dn-ss-you{
  position:absolute;left:max(6px,var(--dn-safe-left));
  bottom:calc(var(--dn-hand-lane) + 6px);
  z-index:9;margin:0;justify-content:flex-start;max-width:calc(20% - 10px)}
/* The auction owns that strip: your own bid bubble is pinned to it and the pop-up
   above is anchored to its top edge. Stand the chips down for the length of the
   auction — the same window this file already clears the seat metas for. */
html.dn-land.dn-bidding .felt.dn-table .dn-seatstats.dn-ss-you{display:none}
html.dn-large-ui .dn-seatstats span{font-size:16px;padding:2px 9px}
html.dn-large-ui.dn-land .felt.dn-table .dn-seatstats span{font-size:15px}

/* ---- Live trick geometry on short landscape tables ----
   A transient instruction used to sit on top of the human fan, while North's
   stack sat directly over North's played card. Collapse North into a compact
   avatar + live-stack row, place the instruction in the open rail to its
   left, and keep the tossed-card cluster inside a measured 31vh centre lane.
   No information is removed: N/W/E remain on the avatars, live books/coins
   remain visible, and the instruction wraps instead of being clipped. */
html.dn-land .felt.dn-table:has(#trick .dn-trick-seat-0,#trick .dn-trick-seat-1,#trick .dn-trick-seat-2,#trick .dn-trick-seat-3) .dn-seat .name,
html.dn-land .felt.dn-table:has(#trick .dn-trick-seat-0,#trick .dn-trick-seat-1,#trick .dn-trick-seat-2,#trick .dn-trick-seat-3) .dn-seat .meta{
  display:none!important}
html.dn-land .felt.dn-table:has(#trick .dn-trick-seat-0,#trick .dn-trick-seat-1,#trick .dn-trick-seat-2,#trick .dn-trick-seat-3) .dn-seat-top{
  top:58px;
  display:grid;
  grid-template-columns:28px auto;
  grid-template-rows:28px;
  align-items:center;
  column-gap:5px;
  width:max-content;
  padding:0 6px;
}
html.dn-land .felt.dn-table:has(#trick .dn-trick-seat-0,#trick .dn-trick-seat-1,#trick .dn-trick-seat-2,#trick .dn-trick-seat-3) .dn-seat-top>.seat-av{
  grid-column:1;grid-row:1}
html.dn-land .felt.dn-table:has(#trick .dn-trick-seat-0,#trick .dn-trick-seat-1,#trick .dn-trick-seat-2,#trick .dn-trick-seat-3) .dn-seat-top>.dn-seatstats{
  grid-column:2;grid-row:1;margin:0}
html.dn-land .felt.dn-table:has(#trick .dn-trick-seat-0,#trick .dn-trick-seat-1,#trick .dn-trick-seat-2,#trick .dn-trick-seat-3)>#msg:not(:empty){
  position:absolute;
  top:58px;
  /* This was a hand-tuned 60px, which happened to clear the North chip only while its stat
     text was 11.5px; at the 14px floor the chip grew and swallowed the rail's last 6px.
     Derive the offset instead: the seat is centred on this same 50% and .dn-seat caps it at
     max-width:22vw, so half of it can never exceed 11vw. Clearance is now structural — the
     chip can grow to its cap without ever reaching the rail. */
  right:calc(50% + 11vw + 8px);
  width:clamp(84px,18vw,150px);
  min-height:28px;
  margin:0;
  padding:1px 4px;
  display:flex;
  align-items:center;
  justify-content:flex-end;
  text-align:right;
  line-height:1.12;
  white-space:normal;
  z-index:7;
}
@media (max-height:430px),(max-width:430px){
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3){
    flex-basis:clamp(112px,31vh,128px);
    min-height:clamp(112px,31vh,128px)!important;
  }
  /* Bid Whist's page-local discard-tray floor is intentionally taller. A live
     trick is not that tray, so this more-specific state override owns play. */
  html.dn-land .felt.dn-table #trick.bw-trick:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3){
    min-height:clamp(112px,31vh,128px)!important;
  }
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .card{
    width:clamp(56px,8.8vw,74px)!important}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-0{
    transform:translate(-50%,calc(-50% + 8px)) rotate(var(--dn-tilt,0deg))}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-1{
    transform:translate(calc(-50% - 22px),-50%) rotate(var(--dn-tilt,0deg))}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-2{
    transform:translate(-50%,calc(-50% - 8px)) rotate(var(--dn-tilt,0deg))}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-3{
    transform:translate(calc(-50% + 22px),-50%) rotate(var(--dn-tilt,0deg))}
}

/* ---- Narrow portrait fallback for web/tablet split views -----------------
   Real touch phones rotate these six physical-table games into dn-land. A
   portrait browser window remains stacked by design, and previously retained
   an empty 112px trick lane, empty last-trick/control rows, 14px between every
   child, and desktop felt padding. On a 390×844 viewport that pushed the hand,
   wallet chip and score below the first screen. Collapse only empty lanes and
   tighten the stack; live cards and every meaningful label keep their size. */
@media (max-width:520px) and (orientation:portrait){
  html:not(.dn-land) .felt.dn-table{
    min-height:0;padding:10px 8px max(10px,var(--dn-safe-bottom));
    gap:6px}
  html:not(.dn-land) .felt.dn-table .dn-center:empty{
    display:none!important;min-height:0!important}
  html:not(.dn-land) .felt.dn-table .lasttrick:empty,
  html:not(.dn-land) .felt.dn-table .breakdown.hidden,
  html:not(.dn-land) .felt.dn-table .controls:not(:has(> :not(.hidden))){
    display:none!important}
  html:not(.dn-land) .felt.dn-table .msg{
    min-height:0;font-size:14px;line-height:1.28;padding:1px 5px}
  html:not(.dn-land) .felt.dn-table .seat{padding-top:5px;padding-bottom:5px;gap:3px}
  html:not(.dn-land) .felt.dn-table .scorebar{gap:8px;font-size:14px}
}

/* A short landscape phone leaves only a narrow lane between the Back/title group and
   the 44px action controls; the full strip does not always fit it. Game renderers
   publish a concise, equally truthful version of the SAME state, and game-shell sets
   data-dn-fit="compact" only after measuring that the full row overflows the lane.
   This replaced a max-width:700px/max-height:430px media query, which a 844×390 phone
   missed — that phone got the full prose in a lane too small for it and had the leading
   team label clipped away. The full text stays the element's accessible name. */
html.dn-land .felt.dn-table #score[data-dn-compact][data-dn-fit],
html.dn-land .felt.dn-table .scorebar[data-dn-compact][data-dn-fit]{
  display:block;font-size:0!important;line-height:1.3;text-align:center;text-overflow:clip}
html.dn-land .felt.dn-table #score[data-dn-compact][data-dn-fit] > *,
html.dn-land .felt.dn-table .scorebar[data-dn-compact][data-dn-fit] > *{
  display:none!important}
/* The ::after deliberately does NOT clip itself. The strip's own overflow:hidden is the
   clip, which leaves the container's scrollWidth reporting the text's NATURAL width — the
   only way the shell can measure whether a candidate string fits before committing to it. */
html.dn-land .felt.dn-table #score[data-dn-compact][data-dn-fit]::after,
html.dn-land .felt.dn-table .scorebar[data-dn-compact][data-dn-fit]::after{
  content:attr(data-dn-compact);display:block;
  color:#dff5ea;font-size:14px;font-weight:800;white-space:nowrap}
/* "trim" = the compact row did not fit either, so the shell dropped whole trailing groups
   from it and published what survived. Both teams' scores are never dropped.
   "wrap" = not even the two bare scores fit one line (a ~105px lane on a 568x320 phone),
   so they stack. Two 14px lines end around y=47, still clear of the North seat at 56px. */
/* "none" = there is no lane at all between the title group and the action pill, so
   there is no honest place to paint this. It is set only where the shell previously
   returned early and left the full strip painting across the whole bar. Suppressing
   the ::after is what makes that safe: the element already hides its children and
   drops to font-size:0 for every fitted tier, so with no generated content it paints
   nothing while keeping its text for the scoreboard summary. Do NOT display:none it
   -- that would take the running score out of the accessibility tree entirely. */
html.dn-land .felt.dn-table #score[data-dn-fit="none"]::after,
html.dn-land .felt.dn-table .scorebar[data-dn-fit="none"]::after{content:none!important}
/* Suppressing the ::after empties the tier but does NOT empty its box, and the box
   is what collides. This element is sized from --dn-score-rail-left/-width, which
   the shell only rewrites on a pass that finds a lane; when the lane collapses the
   shell now clears them, and the element falls back to left:50% + translateX(-50%)
   -- landing it dead centre, on top of the action pill. Measured on crazy-eights at
   844x390 once the safe-area insets arrive: an empty 18px box at x=413 while the
   pill starts at x=362.
   So pin the empty tier to a 1px box at the far left instead of letting it fall back.
   Same reasoning as the comment above: it stays in the accessibility tree, keeps its
   aria-label and its text for the scoreboard summary, and simply stops occupying a
   lane it was told it does not have. */
html.dn-land .felt.dn-table #score[data-dn-fit="none"],
html.dn-land .felt.dn-table .scorebar[data-dn-fit="none"]{
  left:0!important; right:auto!important;
  width:1px!important; max-width:1px!important;
  transform:none!important; overflow:hidden!important;
}
html.dn-land .felt.dn-table #score[data-dn-fit="trim"][data-dn-fit-text]::after,
html.dn-land .felt.dn-table .scorebar[data-dn-fit="trim"][data-dn-fit-text]::after,
html.dn-land .felt.dn-table #score[data-dn-fit="wrap"][data-dn-fit-text]::after,
html.dn-land .felt.dn-table .scorebar[data-dn-fit="wrap"][data-dn-fit-text]::after{
  content:attr(data-dn-fit-text)}
/* [data-dn-compact] is repeated here on purpose: without it this selector is one attribute
   LESS specific than the nowrap rule above and silently loses to it. */
html.dn-land .felt.dn-table #score[data-dn-compact][data-dn-fit="wrap"]::after,
html.dn-land .felt.dn-table .scorebar[data-dn-compact][data-dn-fit="wrap"]::after{
  white-space:normal;line-height:1.25}

/* ================= TABLET TIER — THE GAME TABLE ====================================
   Same three-part gate as the lobby tier further up, for the same reasons (the long note
   there is the canonical one): min-height:700px keeps a LANDSCAPE PHONE out (844x390,
   568x320 — where the geometry regression tests live), min-width:700px keeps an iPad SPLIT
   VIEW / SLIDE OVER pane out (320-507pt on a 1366pt-tall screen; those keep the phone rules
   and dn-rot), pointer:coarse keeps a desktop window out. It is a SECOND block with the
   same gate rather than one merged block because it must come LAST in the file: nearly
   everything it corrects is a flat pixel written FURTHER DOWN than the lobby block (the
   44px control contract, the dn-large-ui overrides, the live-trick geometry), and at equal
   specificity the later rule wins. `:root{--dn-tablet:1}` stays declared once, up there.

   MEASURED BEFORE, spades in a live trick (hand card / trick card / seat avatar / on-felt
   text), all with a full 13-card hand:
     844x390   phone landscape   53px / 76px  / 28px / 14px
     1133x744  iPad mini land.   75px / 102px / 28px / 14px
     744x1133  iPad mini portr.  46px / 67px  / 28px / 14px
     834x1194  iPad Air portr.   52px / 75px  / 28px / 14px
     1366x1024 iPad Pro land.   124px / 112px / 28px / 14px
   Two separate failures, not one:
   - EVERY avatar, badge, label and control was the same size on a 13-inch iPad as on a
     4.7-inch phone, because they are flat pixels: 28px avatar, 20px books badge, 18px
     dealer chip, 14px for all on-felt text, 44px controls. No screen size can move them.
   - The PORTRAIT tablet was inverted, as the lobby was: a 744pt iPad mini rendered a 46px
     hand card where an 844x390 iPhone renders 53px — 2.6x the screen area, 13% LESS card.
     A 13-card hand has to fit the table's width in one row, and in portrait that width is
     the SHORT edge, so no clamp can win the arithmetic. See fanHand in game-shell.js: the
     room on a portrait tablet is VERTICAL, so the hand wraps into rows there, which is
     what a portrait phone has always done.

   ONE UNIT. --dn-tab-u is the tablet's table unit and every value below is that number or
   a fixed multiple of it, so there is no second set of hand-tuned tablet numbers to drift
   out of step — that second set is exactly what froze the native lobby before. The phone
   values stay literal where they already are: DELETE THIS BLOCK AND THE PHONE RENDERING IS
   BYTE-IDENTICAL. Every multiple is also chosen so the tier's own floor (vmin 700 ->
   --dn-tab-u 16px) already exceeds the phone value it replaces: nothing can get smaller or
   harder to hit by entering this tier.
   COST: ~30 selectors that each have to be listed, because the table's furniture is NOT in
   container units the way a lobby tile is — there is no single container whose growth they
   all follow. One unit keeps that a list of multiples rather than a list of decisions. */
@media (min-width:700px) and (min-height:700px) and (pointer:coarse){
  /* vmin, not vw or vh: the same physical iPad must produce the same sized furniture in
     both orientations, and vmin is the only unit that does. (A tablet is never dn-rot —
     the previous pass made the CSS rotation phone-only — so vmin cannot be reading a
     rotated frame's long edge here.) */
  :root{--dn-tab-u:clamp(16px,calc(1.2vmin + 7.2px),21px)}

  /* ---- on-felt text: the 14px reading floor becomes the tablet unit ---- */
  html.dn-land .felt.dn-table .msg,
  html.dn-land .felt.dn-table .dn-seat .name,
  html.dn-land .felt.dn-table .dn-seat .meta,
  html.dn-land .felt.dn-table .pile,
  html.dn-land .felt.dn-table .pile .lbl,
  html.dn-land .felt.dn-table .bagcount,
  html.dn-land .felt.dn-table .dn-seatstats span,
  html.dn-land .felt.dn-table #status:not(:empty),
  html.dn-land .felt.dn-table #hint:not(:empty),
  html.dn-land .felt.dn-table .breakdown:not(:empty),
  html.dn-land .felt.dn-table #score:not(:empty),
  html.dn-land .felt.dn-table .scorebar:not(:empty),
  html.dn-land .felt.dn-table #score[data-dn-compact][data-dn-fit]::after,
  html.dn-land .felt.dn-table .scorebar[data-dn-compact][data-dn-fit]::after{
    font-size:var(--dn-tab-u)}
  /* "Larger text" must never make the table SMALLER than the tier it is switched on in —
     the same trap the 12.5px landscape set fell into. These selectors carry an extra class
     (dn-large-ui) so they outrank the group above and have to be restated. */
  html.dn-large-ui.dn-land .felt.dn-table .dn-seat .name,
  html.dn-large-ui.dn-land .felt.dn-table .dn-seat .meta,
  html.dn-large-ui.dn-land .felt.dn-table .msg,
  html.dn-large-ui.dn-land .felt.dn-table .dn-seatstats span,
  html.dn-large-ui.dn-land .felt.dn-table #score:not(:empty),
  html.dn-large-ui.dn-land .felt.dn-table .scorebar:not(:empty),
  html.dn-large-ui.dn-land .felt.dn-table #score[data-dn-compact][data-dn-fit]::after,
  html.dn-large-ui.dn-land .felt.dn-table .scorebar[data-dn-compact][data-dn-fit]::after{
    font-size:calc(var(--dn-tab-u) + 2px)}
  html.dn-large-ui.dn-land .felt.dn-table .controls .btn{
    min-height:calc(var(--dn-tab-u) * 3);font-size:calc(var(--dn-tab-u) + 1px)}

  /* ---- opponents' seat chips ---- */
  html.dn-land .felt.dn-table .dn-seat .seat-av{
    width:calc(var(--dn-tab-u) * 2.3);height:calc(var(--dn-tab-u) * 2.3);
    font-size:calc(var(--dn-tab-u) * 1.05)}
  html.dn-land .felt.dn-table .dn-dealer-badge{
    width:calc(var(--dn-tab-u) * 1.3);height:calc(var(--dn-tab-u) * 1.3);
    font-size:calc(var(--dn-tab-u) * .9)}
  /* the books count is how a player knows who is winning — it grows with the avatar it
     hangs on, or it turns back into the smallest live number on the table */
  html.dn-land .felt.dn-table .dn-ss-badge{
    min-width:calc(var(--dn-tab-u) * 1.55);height:calc(var(--dn-tab-u) * 1.55);
    font-size:calc(var(--dn-tab-u) * .95)}
  html.dn-large-ui.dn-land .felt.dn-table .dn-ss-badge{
    min-width:calc(var(--dn-tab-u) * 1.7);height:calc(var(--dn-tab-u) * 1.7);
    font-size:calc(var(--dn-tab-u) * 1.05)}
  html.dn-land .felt.dn-table .dn-seatstats span{padding:2px calc(var(--dn-tab-u) * .55)}
  html.dn-land .felt.dn-table .dn-bidbubble,
  html.dn-land.dn-bidding .felt.dn-table .dn-seat .dn-bidbubble{
    font-size:calc(var(--dn-tab-u) - 1px);padding:2px calc(var(--dn-tab-u) * .6);
    max-width:calc(var(--dn-tab-u) * 7)}
  /* An opponent's card backs are a "how many cards" indicator, but 32px of dark sliver on a
     13-inch screen is not one. The base rule's !important has to be answered in kind. */
  html.dn-land .felt.dn-table .dn-seat .card{width:calc(var(--dn-tab-u) * 2.4) !important}
  /* The seat chips clear the FLOATING top bar by a hand-tuned 56/58px, and that bar is one
     of the things this tier makes taller — so the drop has to track the same unit. All
     three states are restated because the bidding and live-trick blocks each re-pin `top`
     at a higher specificity (the live-trick one through an ID inside :has()). */
  html.dn-land .felt.dn-table .dn-seat-top,
  html.dn-land.dn-bidding .felt.dn-table .dn-seat-top,
  html.dn-land .felt.dn-table:has(#trick .dn-trick-seat-0,#trick .dn-trick-seat-1,#trick .dn-trick-seat-2,#trick .dn-trick-seat-3) .dn-seat-top{
    top:calc(var(--dn-tab-u) * 3.7)}
  /* those two collapsed seat rows lay their avatar out on an explicit 28px grid track */
  html.dn-land.dn-bidding .felt.dn-table .dn-seat-top{
    grid-template-columns:calc(var(--dn-tab-u) * 2.3) auto auto;
    grid-template-rows:calc(var(--dn-tab-u) * 2.3)}
  html.dn-land .felt.dn-table:has(#trick .dn-trick-seat-0,#trick .dn-trick-seat-1,#trick .dn-trick-seat-2,#trick .dn-trick-seat-3) .dn-seat-top{
    grid-template-columns:calc(var(--dn-tab-u) * 2.3) auto;
    grid-template-rows:calc(var(--dn-tab-u) * 2.3)}
  /* pitch's trump/bid chip sits under the bar at a flat 42px once the bar is unit-sized */
  html.dn-land .felt.dn-table #status:not(:empty){top:calc(var(--dn-tab-u) * 3.4)}
  /* the live-trick instruction rail: same drop, and a min-height that matches the avatar */
  html.dn-land .felt.dn-table:has(#trick .dn-trick-seat-0,#trick .dn-trick-seat-1,#trick .dn-trick-seat-2,#trick .dn-trick-seat-3)>#msg:not(:empty){
    top:calc(var(--dn-tab-u) * 3.7);min-height:calc(var(--dn-tab-u) * 2.3);
    width:clamp(84px,18vw,calc(var(--dn-tab-u) * 11))}

  /* ---- the trick / centre area ----
     FIRST, THE TRICK WAS NOT IN THE MIDDLE OF THE TABLE, whatever the layout note above
     claims. spades.html:46 and play.html:516 pin their you-seat with an inline
     style="margin-top:auto", and in a column flex container an auto top margin eats ALL the
     free space — so the play column is packed against the felt's TOP padding, under the
     partner's chip, and the felt's own justify-content:center never gets a say. Measured at
     1024x1366 before this rule: band top 72px, partner chip bottom 117px, 7,655px² of the
     played pile covered by the chip; growing the cards below only made that worse. An inline
     style can only be answered with !important.
     PHONES KEEP THE PIN, deliberately: on a 390pt-tall table the play column is most of the
     content box (measured 143px of 230px at 844x390), so packing it against the top is the
     safer of the two there and the phone geometry tests are tuned around it. On a tablet the
     same column is under a third of the box.
     THEN the four tossed cards, which are pinned to the centre and nudged toward the seat
     that played them by flat pixels, so the whole cluster stayed phone-sized inside a band
     whose own 34vh was capped at 230px. Card, nudge and band now come off one unit together
     — if the card grew alone the pile would read as one stack instead of four plays. */
  html.dn-land .felt.dn-table .dn-seat-you{margin-top:0 !important}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3){
    flex-basis:calc(var(--dn-tab-u) * 15);min-height:calc(var(--dn-tab-u) * 15) !important}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-0 .card,
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-1 .card,
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-2 .card,
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-3 .card{
    width:calc(var(--dn-tab-u) * 7.8) !important}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-0{
    transform:translate(-50%,calc(-50% + var(--dn-tab-u) * 1.85)) rotate(var(--dn-tilt,0deg))}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-1{
    transform:translate(calc(-50% - var(--dn-tab-u) * 1.72),-50%) rotate(var(--dn-tilt,0deg))}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-2{
    transform:translate(-50%,calc(-50% - var(--dn-tab-u) * 1.85)) rotate(var(--dn-tilt,0deg))}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-3{
    transform:translate(calc(-50% + var(--dn-tab-u) * 1.72),-50%) rotate(var(--dn-tilt,0deg))}

  /* ---- the player's hand ----
     LANDSCAPE ONLY, and the ceiling is the ONLY thing that changes. WHY not portrait too:
     a portrait tablet's 13 cards no longer live in one row at all (fanHand wraps them), and
     there the row width is already the binding term — 12.5vw never reaches 124px below
     992pt, so raising the ceiling would buy an iPad Pro 4px and every other iPad nothing.
     WHY the ceiling and not the 12.5vw coefficient: the coefficient is what a WIDE table
     spends its extra points on, and it was already being thrown away by the 124px cap
     (measured 124px at 1180, 1194, 1366 and 1396 alike). The 32vh height cap is kept
     exactly as it was — it is what stops a tall card clipping on a short landscape table.
     Splitting on `orientation` is safe here for the same reason vmin is: a tablet is never
     dn-rot, so the viewport's orientation and the table's are the same thing. */
  @media (orientation:landscape){
    html.dn-land .felt.dn-table .dn-hand .card,
    html.dn-land .felt.dn-table .hand.mine .card{
      width:min(clamp(66px,12.5vw,calc(var(--dn-tab-u) * 9)),calc(32vh * 5 / 7))}
  }
  /* The Hint/Undo rows were pinned a flat 84px/104px above the felt's bottom, a number that
     assumed a ~121px-tall card. At the sizes above they sat ON the hand. --dn-hand-lane is
     the strip the hand actually measured and published, so "just above the hand" becomes
     true again instead of being re-guessed. Phones keep the tuned pixels. */
  html.dn-land .felt.dn-table #controls:not(:empty),
  html.dn-land .felt.dn-table .dn-seat-you .controls:not(:empty){
    bottom:calc(var(--dn-hand-lane) + 8px)}
  /* A card is its own container (container-type:inline-size), so its ink IS proportional —
     but every ceiling was reached at a ~66px card, which is where a PHONE lands. On a
     124-153px tablet card the rank was still 25px: 17% of the card, where a real deck's
     index is nearer 28%. Only the ceilings move; the cqw coefficients that decide the
     proportion are untouched, and the vw fallbacks below @supports are left for the old
     WebViews they exist for. */
  @supports (width:1cqw){
    html.dn-land .felt.dn-table .card .corner .r{font-size:clamp(15px,38cqw,40px)}
    html.dn-land .felt.dn-table .card .corner .s{font-size:clamp(10px,25cqw,27px)}
    html.dn-land .felt.dn-table .card .pip:not(.face){font-size:clamp(25px,66cqw,84px)}
    html.dn-land .felt.dn-table .card.court .corner .r{font-size:clamp(15px,40cqw,42px)}
    html.dn-land .felt.dn-table .card.court .corner .s{font-size:clamp(13px,34cqw,36px)}
    /* the held hand keeps its calmer centre suit (a hero glyph would run into the next
       card's index in a fan) — only its ceiling lifts */
    html.dn-land .felt.dn-table .dn-hand .card .pip:not(.face),
    html.dn-land .felt.dn-table .hand.mine .card .pip:not(.face){
      font-size:clamp(24px,62cqw,66px)}
    html.dn-land .felt.dn-table .dn-hand .card .pip .ace,
    html.dn-land .felt.dn-table .hand.mine .card .pip .ace{font-size:clamp(28px,70cqw,76px)}
  }

  /* ---- the bid pop-up ---- */
  html.dn-land .felt.dn-table .dn-bidpanel{gap:calc(var(--dn-tab-u) * .45);
    padding:calc(var(--dn-tab-u) * .6) calc(var(--dn-tab-u) * .9) calc(var(--dn-tab-u) * .75)}
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-prompt{font-size:var(--dn-tab-u)}
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-opt{
    min-width:calc(var(--dn-tab-u) * 3);height:calc(var(--dn-tab-u) * 3);
    font-size:calc(var(--dn-tab-u) * 1.05);padding:0 calc(var(--dn-tab-u) * .7)}
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-dir{
    min-width:calc(var(--dn-tab-u) * 5.4);font-size:calc(var(--dn-tab-u) * .85)}
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-bid,
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-pass{
    height:calc(var(--dn-tab-u) * 3);min-width:calc(var(--dn-tab-u) * 6.4);
    font-size:calc(var(--dn-tab-u) * .95)}
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-toggle{
    height:calc(var(--dn-tab-u) * 3);min-height:calc(var(--dn-tab-u) * 3);
    font-size:calc(var(--dn-tab-u) * .85)}
  /* Bid Whist's two-choice chooser reserves an explicit action column and square number
     buttons; both were phone pixels and have to move with the buttons in them. */
  html.dn-land .felt.dn-table .dn-bidpanel:has(.dn-bp-dirs){
    grid-template-columns:auto calc(var(--dn-tab-u) * 6.4)}
  html.dn-land .felt.dn-table .dn-bidpanel:has(.dn-bp-dirs) .dn-bp-opt{
    width:calc(var(--dn-tab-u) * 3);min-width:calc(var(--dn-tab-u) * 3);
    height:calc(var(--dn-tab-u) * 3)}
  html.dn-land .felt.dn-table .dn-bidpanel:has(.dn-bp-dirs) .dn-bp-dir{
    width:calc(var(--dn-tab-u) * 5.4);min-width:calc(var(--dn-tab-u) * 5.4)}
  html.dn-land .felt.dn-table .dn-bidpanel:has(.dn-bp-dirs) .dn-bp-bid,
  html.dn-land .felt.dn-table .dn-bidpanel:has(.dn-bp-dirs) .dn-bp-pass{
    width:calc(var(--dn-tab-u) * 6.4);min-width:calc(var(--dn-tab-u) * 6.4);
    height:calc(var(--dn-tab-u) * 3)}
  /* the in-flow primary bar (spades .bid-pick, #bidbar, crazy-8s .controls) */
  html.dn-land .felt.dn-table .bid-pick button,
  html.dn-land .felt.dn-table #bidbar .btn,
  html.dn-land .felt.dn-table #targets .btn,
  html.dn-land .felt.dn-table .controls .btn{
    min-height:calc(var(--dn-tab-u) * 3);padding:0 calc(var(--dn-tab-u) * .8);
    font-size:calc(var(--dn-tab-u) * .95)}

  /* ---- the top bar, on EVERY game page ----
     Not scoped to .dn-table: solitaire, war, blackjack and the board games share this bar,
     and a 44px control on a 13-inch screen is a 44px control there too. The 44px floor
     further up this file is a FLOOR — these are all larger, so it still holds. */
  html .table .topbar .shell-actions{gap:calc(var(--dn-tab-u) * .35)}
  html .table .topbar .shell-actions .btn{
    width:calc(var(--dn-tab-u) * 3);height:calc(var(--dn-tab-u) * 3);
    min-width:calc(var(--dn-tab-u) * 3);min-height:calc(var(--dn-tab-u) * 3);
    font-size:calc(var(--dn-tab-u) * 1.05)}
  html .table .topbar .shell-actions .shell-new-game{
    width:auto;min-width:calc(var(--dn-tab-u) * 4);padding:0 calc(var(--dn-tab-u) * .55)}
  html .table .topbar .shell-new-game .shell-new-copy::before{font-size:calc(var(--dn-tab-u) * .9)}
  html .table .topbar .dn-coin{min-height:calc(var(--dn-tab-u) * 3);
    padding:0 calc(var(--dn-tab-u) * .55) 0 calc(var(--dn-tab-u) * .7);
    font-size:calc(var(--dn-tab-u) * .95)}
  html .table .topbar .dn-coin .dn-coin-plus{
    width:calc(var(--dn-tab-u) * 1.15);height:calc(var(--dn-tab-u) * 1.15);
    font-size:calc(var(--dn-tab-u) * .9)}
  html .table .topbar .brand.dn-back{min-height:calc(var(--dn-tab-u) * 3);
    padding:0 calc(var(--dn-tab-u) * .9) 0 calc(var(--dn-tab-u) * .6)}
  html .table .topbar .brand.dn-back .dn-back-chev{font-size:calc(var(--dn-tab-u) * 1.65)}
  html .table .topbar .brand.dn-back .dn-back-label{font-size:calc(var(--dn-tab-u) * 1.02)}
  html .table .topbar .dn-gtitle{font-size:calc(var(--dn-tab-u) * 1.12)}
  html .table .topbar .shell-game-meta{font-size:var(--dn-tab-u)}
}

/* --- Interactive edge-swipe back ------------------------------------------
   The sheet follows the finger, so while a drag is live it must not animate
   (that would lag behind the thumb) and must not scroll (the axis is already
   locked to horizontal by then). .dn-edge-settling is the opposite: the finger
   is gone and the sheet either flies out or springs home.

   Only the overlay's own transform and shadow move, both driven by a custom
   property the drag writes — paint-only, no layout, no reflow, so it stays
   smooth on a mid-range Android. */
.dn-edge-dragging{
  transition:none!important;
  touch-action:none;
  will-change:transform;
}
.dn-edge-settling{
  transition:transform .22s cubic-bezier(.32,.72,0,1)!important;
  will-change:transform;
}
/* Most of these overlays are OPAQUE full-screen sheets (.shell-modal-back
   defaults to #11131a), so dragging one aside simply reveals the screen beneath
   — the effect iOS is actually producing, and no backdrop to dim.

   Three are NOT: .dn-scoreboard-back (88% + backdrop-filter), .dn-confirm-back
   (90%) and .dn-daily-spin-back (94%) paint a scrim. Dragging a scrim would
   slide a hard-edged wall of dim across the live table, and would detach the
   scoreboard's blur from what it is blurring. For those, edgeBackMover() moves
   the inner .shell-modal panel and leaves the dim where it is — so this rule is
   written to apply to whichever element actually moves.

   Either way the moving surface needs the leading-edge shadow that sells the
   two layers as stacked rather than as one picture sliding. */
.dn-edge-dragging,
.dn-edge-settling{
  box-shadow:-12px 0 28px rgba(0,0,0,calc(.55 * var(--dn-edge-back-progress, 0)));
}
/* Reduced-motion users still get the gesture — it is a navigation control, not
   decoration — but the spring-home and fly-out are cut to a near-instant fade
   so nothing slides across the screen. */
@media (prefers-reduced-motion: reduce){
  .dn-edge-settling{transition-duration:.01ms!important}
}

/* ================= 2026-08 VISUAL PARITY FREEZE ==========================
   The shared table chrome now names the four controls players repeatedly asked
   for. Icons remain on narrow phones, where the full words cannot share one row;
   desktop/tablet render the words and use an independent, full-width score rail. */
.shell-action-icon{display:inline-flex;align-items:center;justify-content:center;line-height:1}
.shell-action-copy{display:none;white-space:nowrap}
.table .topbar .brand.dn-back,
.table .topbar .shell-actions .btn{
  color:#fff8e2;background:rgba(13,17,27,.96);
  border:1px solid rgba(var(--dn-gold-rgb),.7);
  box-shadow:0 2px 8px rgba(0,0,0,.38),inset 0 1px 0 rgba(255,255,255,.08);
  text-shadow:none}
.table .topbar .brand.dn-back:hover,
.table .topbar .shell-actions .btn:hover{filter:brightness(1.12);border-color:#f0d48a}
.table .topbar .brand.dn-back:active,
.table .topbar .shell-actions .btn:active{opacity:1;filter:brightness(.94)}
.table .topbar .brand.dn-back:focus-visible,
.table .topbar .shell-actions .btn:focus-visible{
  outline:3px solid #fff;outline-offset:2px;
  box-shadow:0 0 0 6px rgba(var(--dn-gold-rgb),.72),0 6px 16px rgba(0,0,0,.5)}
.table .topbar .shell-sound[aria-pressed="false"]{
  color:#fff;border-color:rgba(159,190,255,.82);background:rgba(22,36,65,.98)}

/* Landscape phone chrome is still phone chrome: the older compact-table rule
   below visually shrinks the glyphs, but these are the real interactive boxes.
   Keep every control at the same 44px touch floor used in portrait while the
   floating overlay preserves all of the table height. */
html:is(.dn-land,.dn-rot) .table .topbar .brand.dn-back{
  min-width:44px!important;width:auto;height:44px;min-height:44px}
html:is(.dn-land,.dn-rot) .table .topbar .shell-actions .btn{
  min-width:44px!important;width:44px;height:44px;min-height:44px!important}
html:is(.dn-land,.dn-rot) .table .topbar .shell-actions .shell-new-game{
  width:auto;min-width:52px!important}

/* A real human anchor above the fan. Games disagree about whether the hand has
   a seat wrapper; Shell inserts this one direct felt child so YOU is centered in
   every four-seat game, never offset by a bid/books chip. */
.felt.dn-table>.dn-you-anchor{
  width:max-content;min-width:116px;min-height:44px;margin:6px auto 10px;
  display:flex;align-items:center;justify-content:center;gap:8px;padding:4px 12px;
  color:#fff9e8;background:rgba(5,30,22,.94);border:1px solid rgba(var(--dn-gold-rgb),.7);
  border-radius:999px;font-size:15px;letter-spacing:.05em;text-shadow:none}
html.dn-land .felt.dn-table>.dn-you-anchor{
  position:absolute;left:50%;bottom:calc(var(--dn-hand-lane) + 7px);
  transform:translateX(-50%);z-index:10;display:flex;align-items:center;gap:7px;
  /* 44px, not 36px. The base rule above already uses 44 - the accessibility
     floor for a tap target - and this rule was quietly undercutting it by 8px
     wherever it applied. That turns out to be every narrow phone: the table
     sets .dn-land from its own rotation logic, not from the viewport, so a
     320x812 phone held UPRIGHT still gets this rule. Measured 36px there.
     Found by CI 2026-08-17 across spades, hearts, bid-whist, euchre and pitch.
     Growing it costs no layout: the element is absolutely positioned from the
     bottom, so the extra height goes upward into empty felt. */
  min-height:44px;padding:3px 10px;border-radius:999px;
  color:#fff9e8;background:rgba(5,17,12,.92);border:1px solid rgba(var(--dn-gold-rgb),.6);
  font-size:14px;letter-spacing:.05em;text-shadow:none;white-space:nowrap}
.dn-you-avatar{width:28px;height:28px;border-radius:50%;display:grid;place-items:center;
  background:linear-gradient(180deg,#f2d98c,#c89b36);color:#241b05;
  font-size:13px;font-weight:950;box-shadow:0 2px 6px rgba(0,0,0,.42)}

/* A portrait phone has room for a clean second control row, not seven clipped
   neighbours. The first row keeps Back/title/wallet; every game action remains a
   distinct 44px button on row two. Landscape phones keep the existing one-row rail. */
@media (max-width:520px) and (orientation:portrait){
  html:not(.is-native-game) .table>.topbar{
    display:grid;grid-template-columns:44px minmax(0,1fr) auto;
    grid-template-rows:44px 44px;height:auto;min-height:96px;align-items:center;
    overflow:visible;padding:4px 8px;gap:4px 6px}
  html:not(.is-native-game) .table>.topbar>.brand{grid-column:1;grid-row:1}
  html:not(.is-native-game) .table>.topbar>.dn-gtitle{grid-column:2;grid-row:1}
  html:not(.is-native-game) .table>.topbar>.dn-coinhud{grid-column:3;grid-row:1;margin:0}
  html:not(.is-native-game) .table>.topbar>.shell-actions{
    grid-column:1 / -1;grid-row:2;margin:0;justify-content:center;width:100%;gap:6px}
}

/* Desktop and full-size tablet furniture must scale beyond phone pixels. The
   score has its own lower rail, so explicit control labels never compete with it. */
@media (min-width:900px) and (min-height:600px){
  .table .topbar .shell-action-copy{display:inline}
  html .table .topbar .shell-actions{gap:6px}
  html .table .topbar .shell-actions .btn{
    width:auto!important;height:44px!important;min-width:44px!important;min-height:44px!important;
    display:inline-flex!important;align-items:center;justify-content:center;gap:6px;
    padding:0 10px!important;font-size:15px!important}
  html .table .topbar .shell-actions .shell-new-game{min-width:88px!important}
  html .table .topbar .shell-new-game .shell-new-copy{font-size:15px!important}
  html .table .topbar .shell-new-game .shell-new-copy::before{content:none!important}
  html .table .topbar .brand.dn-back{min-width:78px;min-height:44px}

  html.dn-land .felt.dn-table #score,
  html.dn-land .felt.dn-table .scorebar{
    top:max(58px,var(--dn-score-rail-top,58px))!important;left:50%!important;width:min(86vw,1000px)!important;
    max-width:min(86vw,1000px)!important;transform:translateX(-50%)!important;
    min-height:44px;align-items:center;justify-content:center!important;
    overflow:visible;font-size:clamp(16px,1.05vw,20px)!important;
    padding:7px 14px;gap:clamp(12px,1.5vw,24px)}
  html.dn-land .felt.dn-table #score[data-dn-compact][data-dn-fit]::after,
  html.dn-land .felt.dn-table .scorebar[data-dn-compact][data-dn-fit]::after{
    font-size:clamp(16px,1.05vw,20px)}
  html.dn-land .felt.dn-table .dn-seat-top,
  html.dn-land.dn-bidding .felt.dn-table .dn-seat-top,
  html.dn-land .felt.dn-table:has(#trick .dn-trick-seat-0,#trick .dn-trick-seat-1,#trick .dn-trick-seat-2,#trick .dn-trick-seat-3) .dn-seat-top{
    top:clamp(120px,13.5vh,154px)}
  html.dn-land .felt.dn-table .dn-seat-left{left:max(6px,var(--dn-safe-left))}
  html.dn-land .felt.dn-table .dn-seat-right{right:max(6px,var(--dn-safe-right))}
  html.dn-land .felt.dn-table .dn-seat .seat-av{
    width:clamp(48px,3.5vw,62px);height:clamp(48px,3.5vw,62px);
    font-size:clamp(19px,1.35vw,24px)}
  html.dn-land .felt.dn-table .dn-seat .name,
  html.dn-land .felt.dn-table .dn-seat .meta,
  html.dn-land .felt.dn-table .dn-seatstats span{font-size:clamp(16px,1.05vw,19px)}
  html.dn-land .felt.dn-table .dn-ss-badge{
    min-width:30px;height:30px;font-size:17px;top:-5px;right:-5px}
  html.dn-land .felt.dn-table>.dn-you-anchor{
    bottom:calc(var(--dn-hand-lane) + 10px);min-height:44px;padding:4px 14px;
    font-size:17px;gap:9px}
  html.dn-land .felt.dn-table>.dn-you-anchor .dn-you-avatar{
    width:34px;height:34px;font-size:16px}
  html.dn-land .felt.dn-table .dn-seatstats.dn-ss-you{
    left:50%;bottom:calc(var(--dn-hand-lane) + 60px);transform:translateX(-50%);
    max-width:min(70%,640px);justify-content:center}

  /* Larger center heroes with restrained, seat-authored offsets. They remain a
     thrown cluster, but each card exposes substantially more of its face. */
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3){
    width:min(66vw,760px);flex-basis:clamp(250px,40vh,330px);
    min-height:clamp(250px,40vh,330px)!important}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-0 .card,
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-1 .card,
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-2 .card,
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-3 .card{
    width:clamp(126px,10.5vw,152px)!important}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-0{
    transform:translate(-50%,calc(-50% + 48px)) rotate(var(--dn-tilt,0deg))}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-1{
    transform:translate(calc(-50% - 58px),-50%) rotate(var(--dn-tilt,0deg))}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-2{
    transform:translate(-50%,calc(-50% - 48px)) rotate(var(--dn-tilt,0deg))}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-3{
    transform:translate(calc(-50% + 58px),-50%) rotate(var(--dn-tilt,0deg))}

  /* Flat two-player tables were still rendering 34/44px avatars and 14px labels
     at 2560px. Grow the anchors without changing card or rules geometry. */
  html.dn-flat .felt .seat .seat-av{
    width:52px;height:52px;font-size:21px}
  html.dn-flat .felt .seat.seat-you .seat-av{
    width:44px;height:44px;font-size:18px}
  html.dn-flat .felt .seat .name,
  html.dn-flat .felt .seat .meta{font-size:16px}
}

/* Shop rows carry purchase/equip state, so they use the same reading and tap
   floors as game controls. Dice styles are distinct even in a still preview. */
.dn-shop-main{font-size:15px}
.dn-shop-main small{font-size:13px;line-height:1.35;margin-top:2px}
.dn-shop-mini{min-height:44px;padding:8px 11px;font-size:14px}
.dn-dice-swatch{width:38px;height:38px;border-radius:9px;display:grid;place-items:center;
  font-size:27px;line-height:1;background:#f4f2e8;color:#17191f;border:2px solid #aeb2aa;
  box-shadow:0 3px 8px rgba(0,0,0,.42)}
.dn-dice-swatch-tumble{transform:rotate(-12deg);color:#fff4c5;
  background:linear-gradient(145deg,#6d42ac,#26163f);border-color:#f0d48a;
  box-shadow:0 3px 8px rgba(0,0,0,.45),0 0 10px rgba(var(--dn-gold-rgb),.38)}

/* The default room is intentionally warm and open. Earlier builds put a dark
   etched oval on saturated green; at large sizes it read as an unrelated black
   border around a mostly empty table. Soft amber room light now defines the
   play zone without drawing a hard shape behind cards or boards. Paid felt
   colors still flow through the same variables and remain visibly distinct. */
.felt{
  background:
    radial-gradient(ellipse 54% 42% at 50% 43%,rgba(255,246,215,.14),transparent 68%),
    radial-gradient(760px 480px at 8% 4%,rgba(255,207,122,.13),transparent 58%),
    radial-gradient(760px 480px at 92% 6%,rgba(255,207,122,.10),transparent 58%),
    repeating-linear-gradient(118deg,rgba(255,255,255,.016) 0 1px,transparent 1px 7px),
    radial-gradient(1100px 680px at 50% 42%,var(--felt-hi),var(--felt-mid) 66%,var(--felt-lo));
  box-shadow:inset 0 0 0 1px rgba(255,229,169,.12),inset 0 0 100px rgba(0,0,0,.24)}

/* Penthouse is a catalog room, so its room light and preview cannot alias the
   free Purple Room. It stays decorative and never touches the table geometry. */
html.dn-room-penthouse body{background:
  radial-gradient(1100px 520px at 50% -8%,rgba(255,205,117,.24),transparent 62%),
  linear-gradient(100deg,transparent 0 14%,rgba(91,135,177,.20) 14% 15%,transparent 15% 30%,rgba(91,135,177,.14) 30% 31%,transparent 31% 100%),
  linear-gradient(180deg,#17253a,#0c1321 62%,#080d16)}
.dn-room-swatch-penthouse{background:
  radial-gradient(circle at 64% 20%,#f1c77a 0 5%,transparent 6%),
  linear-gradient(150deg,#274669,#0b1322 72%)}

@media (min-width:900px) and (min-height:600px){
  /* A calm material frame replaces the old full-bleed green sheet. The slim
     walnut/champagne rail consumes under 2% of either axis, but makes the play
     surface read as one welcoming table rather than a blank page. */
  html body .table{
    background:
      radial-gradient(900px 260px at 50% 0,rgba(255,214,147,.16),transparent 68%),
      linear-gradient(135deg,#5c3629,#322129 48%,#43291f)}
  html body .table>.felt{
    width:calc(100% - 20px);margin:6px 10px 10px!important;
    border:6px solid #70442e;border-radius:30px;
    background:
      radial-gradient(ellipse 54% 42% at 50% 43%,rgba(255,247,218,.15),transparent 68%),
      radial-gradient(760px 480px at 8% 4%,rgba(255,207,122,.13),transparent 58%),
      radial-gradient(760px 480px at 92% 6%,rgba(255,207,122,.10),transparent 58%),
      repeating-linear-gradient(118deg,rgba(255,255,255,.016) 0 1px,transparent 1px 7px),
      radial-gradient(1100px 680px at 50% 42%,var(--felt-hi),var(--felt-mid) 66%,var(--felt-lo))!important;
    box-shadow:
      inset 0 0 0 2px rgba(255,226,161,.28),
      inset 0 0 100px rgba(0,0,0,.22),
      0 8px 22px rgba(0,0,0,.34)}
  /* Round/card families get one restrained inset table line. It is a background
     layer — never a positioned overlay — so it cannot cover or re-anchor a card,
     label, seat, or action. Purpose-built boards (Solitaire, Checkers, Dice,
     Dominoes, Tic-Tac-Toe) keep their own surfaces, and phones stay full-felt. */
  html:is(
    [data-dn-game="bid-whist"],[data-dn-game="blackjack"],
    [data-dn-game="crazy-eights"],[data-dn-game="euchre"],
    [data-dn-game="gin-rummy"],[data-dn-game="go-fish"],
    [data-dn-game="hearts"],[data-dn-game="old-maid"],
    [data-dn-game="pitch"],[data-dn-game="rummy"],
    [data-dn-game="spades"],[data-dn-game="tonk"],
    [data-dn-game="war"]
  ) body .table>.felt{
    background:
      radial-gradient(ellipse 92% 84% at 50% 49%,
        transparent 0 94%,rgba(255,229,169,.20) 94.5% 96%,
        rgba(35,19,17,.30) 96.5% 98%,transparent 98.5%),
      radial-gradient(ellipse 54% 42% at 50% 43%,rgba(255,247,218,.15),transparent 68%),
      radial-gradient(760px 480px at 8% 4%,rgba(255,207,122,.13),transparent 58%),
      radial-gradient(760px 480px at 92% 6%,rgba(255,207,122,.10),transparent 58%),
      repeating-linear-gradient(118deg,rgba(255,255,255,.016) 0 1px,transparent 1px 7px),
      radial-gradient(1100px 680px at 50% 42%,var(--felt-hi),var(--felt-mid) 66%,var(--felt-lo))!important}
  html.dn-land .topbar .shell-actions,html.dn-rot .topbar .shell-actions{
    background:rgba(48,31,36,.94);border:1px solid rgba(255,221,153,.26);
    box-shadow:0 6px 18px rgba(0,0,0,.28)}
  .table .topbar .brand.dn-back,
  .table .topbar .shell-actions .btn{
    background:linear-gradient(180deg,rgba(69,47,53,.98),rgba(37,27,34,.98));
    border-color:rgba(255,220,149,.56)}

  /* Large windows should feel like a close seat at a real card table. Keep the
     furniture inside a broad 65–85% safe zone and let every reading size grow
     continuously instead of freezing at the old phone-oriented 14px/62px caps. */
  html .table .topbar .brand.dn-back .dn-back-label,
  html .table .topbar .shell-actions .btn{font-size:clamp(16px,.78vw,20px)!important}
  html .table .topbar .dn-gtitle{font-size:clamp(18px,1vw,25px)!important}
  html .table .topbar .shell-actions .btn{
    height:48px!important;min-height:48px!important;min-width:48px!important}
  html .table .topbar .brand.dn-back{min-height:48px;min-width:82px}

  html.dn-land .felt.dn-table #score,
  html.dn-land .felt.dn-table .scorebar{
    top:max(62px,var(--dn-score-rail-top,62px))!important;width:min(82vw,2000px)!important;max-width:min(82vw,2000px)!important;
    min-height:48px;font-size:clamp(18px,1.18vw,28px)!important;
    padding:8px 18px;gap:clamp(14px,1.7vw,34px);
    background:rgba(10,46,35,.94);border-color:rgba(255,229,169,.30)}
  html.dn-land .felt.dn-table #score[data-dn-compact][data-dn-fit]::after,
  html.dn-land .felt.dn-table .scorebar[data-dn-compact][data-dn-fit]::after{
    font-size:clamp(18px,1.18vw,28px)}

  html.dn-land .felt.dn-table .dn-seat-left{left:max(clamp(12px,1.45vw,32px),var(--dn-safe-left))}
  html.dn-land .felt.dn-table .dn-seat-right{right:max(clamp(12px,1.45vw,32px),var(--dn-safe-right))}
  html.dn-land .felt.dn-table .dn-seat{
    max-width:min(20vw,280px);overflow:visible;padding:8px 11px;gap:4px;
    background:rgba(7,29,22,.78)!important;border:1px solid rgba(255,231,177,.22)!important;
    border-radius:18px;box-shadow:0 7px 20px rgba(0,0,0,.24)!important}
  html.dn-land .felt.dn-table .dn-seat .seat-av{
    width:clamp(52px,3.35vw,72px);height:clamp(52px,3.35vw,72px);
    font-size:clamp(20px,1.35vw,30px)}
  html.dn-land .felt.dn-table .dn-seat .name{
    display:flex!important;justify-content:center;max-width:240px;
    font-size:clamp(17px,1.08vw,26px)!important;line-height:1.18;text-align:center;
    padding:2px 8px;border-radius:999px;color:#fffbed;background:rgba(3,17,13,.72)}
  html.dn-land .felt.dn-table .dn-seat .meta,
  html.dn-land .felt.dn-table .dn-seatstats span,
  html.dn-land .felt.dn-table .dn-bidbubble{
    font-size:clamp(16px,.92vw,22px)!important;line-height:1.35}
  html.dn-land .felt.dn-table .dn-bidbubble{
    min-height:32px;max-width:180px!important;padding:3px 11px!important}
  html.dn-land.dn-bidding .felt.dn-table .dn-seat-top{
    display:flex!important;flex-direction:column;align-items:center;justify-content:center;
    width:auto;max-width:280px;padding:8px 11px;gap:4px}
  html.dn-land.dn-bidding .felt.dn-table .dn-seat-top>*{grid-column:auto!important;grid-row:auto!important}
  html.dn-land.dn-bidding .felt.dn-table .dn-seat .name{display:flex!important}

  /* Live play previously collapsed North into a 28px grid and hid all three
     names. On a desktop that produced anonymous floating initials and let the
     enlarged cards cover North. Keep the top player a compact horizontal rail,
     preserve the direction/name, and lower the thrown cards into their own lane. */
  html.dn-land .felt.dn-table:has(#trick .dn-trick-seat-0,#trick .dn-trick-seat-1,#trick .dn-trick-seat-2,#trick .dn-trick-seat-3) .dn-seat .name{
    display:flex!important}
  html.dn-land .felt.dn-table:has(#trick .dn-trick-seat-0,#trick .dn-trick-seat-1,#trick .dn-trick-seat-2,#trick .dn-trick-seat-3) .dn-seat-top{
    top:120px;display:flex!important;flex-direction:row;align-items:center;
    width:auto;max-width:360px;height:auto;min-height:78px;padding:7px 11px;gap:8px}
  html.dn-land .felt.dn-table:has(#trick .dn-trick-seat-0,#trick .dn-trick-seat-1,#trick .dn-trick-seat-2,#trick .dn-trick-seat-3) .dn-seat-top>*{
    grid-column:auto!important;grid-row:auto!important;margin:0}
  html.dn-land .felt.dn-table:has(#trick .dn-trick-seat-0,#trick .dn-trick-seat-1,#trick .dn-trick-seat-2,#trick .dn-trick-seat-3)>#msg:not(:empty){
    top:124px;left:30%;right:auto;transform:translateX(-50%);max-width:min(25vw,320px);
    font-size:clamp(16px,.9vw,21px);padding:6px 11px;z-index:12}

  html.dn-land .felt.dn-table>.dn-you-anchor{
    min-height:48px;font-size:clamp(18px,1.05vw,25px);padding:5px 16px;
    background:rgba(7,29,22,.94);border-color:rgba(255,229,169,.45)}
  html.dn-land .felt.dn-table>.dn-you-anchor .dn-you-avatar{
    width:clamp(36px,2.25vw,54px);height:clamp(36px,2.25vw,54px);
    font-size:clamp(17px,1vw,24px)}
  html.dn-land .felt.dn-table .dn-seatstats.dn-ss-you{
    bottom:calc(var(--dn-hand-lane) + 66px)}

  html.dn-flat .felt .seat .seat-av{
    width:clamp(52px,3.35vw,72px);height:clamp(52px,3.35vw,72px);
    font-size:clamp(20px,1.35vw,30px)}
  html.dn-flat .felt .seat.seat-you .seat-av{
    width:clamp(48px,2.8vw,62px);height:clamp(48px,2.8vw,62px);
    font-size:clamp(18px,1.15vw,25px)}
  html.dn-flat .felt .seat .name,
  html.dn-flat .felt .seat .meta{font-size:clamp(17px,1vw,23px)}
  html.dn-flat .felt .scorebar,
  html.dn-flat .felt>#score{
    min-height:44px;font-size:clamp(18px,1.08vw,24px);padding:7px 13px;
    border-radius:14px;background:rgba(10,46,35,.91);border:1px solid rgba(255,229,169,.25)}
  html.dn-flat .felt>.msg,
  html.dn-flat .felt .board>.msg{font-size:clamp(17px,1vw,22px);line-height:1.4}
  html.dn-flat .felt button:not(.card):not(.sq){min-height:48px}

  html.dn-land .felt.dn-table .dn-hand .card,
  html.dn-land .felt.dn-table .hand.mine .card{
    width:min(clamp(90px,8vw,176px),calc(38vh * 5 / 7))}
  html.dn-land .felt.dn-table .dn-hand .card .corner .r,
  html.dn-land .felt.dn-table .hand.mine .card .corner .r{
    font-size:clamp(22px,2vw,42px)}

  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3){
    width:min(72vw,1160px);flex-basis:clamp(280px,46vh,430px);
    min-height:clamp(280px,46vh,430px)!important}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-0 .card,
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-1 .card,
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-2 .card,
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-3 .card{
    width:clamp(136px,9.1vw,196px)!important}
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-0,
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-1,
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-2,
  html.dn-land .felt.dn-table .dn-center:has(.dn-trick-seat-0,.dn-trick-seat-1,.dn-trick-seat-2,.dn-trick-seat-3) .dn-trick-seat-3{
    top:calc(50% + 70px)}

  /* Auction/choice panels are the current action, not a tiny island inside a
     large empty oval. Each option is independently readable and tappable. */
  html.dn-land .felt.dn-table .dn-bidpanel{
    position:relative;top:min(0px,calc((100vh - 900px)*.35));
    width:min(78vw,940px);max-width:min(78vw,940px);padding:18px 24px 20px;gap:10px;
    border-color:rgba(255,224,157,.34);border-radius:20px}
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-prompt{
    font-size:clamp(18px,1vw,24px);line-height:1.35}
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-opt{
    min-width:52px;height:52px;font-size:clamp(18px,1vw,24px);padding:0 14px}
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-dir{
    min-width:118px;font-size:clamp(16px,.9vw,21px)}
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-bid,
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-pass,
  html.dn-land .felt.dn-table .dn-bidpanel .dn-bp-toggle{
    min-width:126px;height:52px;min-height:52px;font-size:clamp(17px,.95vw,22px)}
}

/* Names are primary identity on a family table. Focus mode may simplify a
   narrow phone after play begins, but a tablet/desktop has ample room to keep
   each opponent's name grouped with the portrait and live score. */
@media (min-width:700px) and (min-height:600px){
  html.dn-focus .felt .seat:not(.seat-you):not(.dn-seat-you)>.name{
    display:flex!important;justify-content:center}
}

/* Disabled means unavailable, not illegible. Preserve a clear state change
   without washing the label down to the old 30–45% contrast. */
html .btn:disabled,html .btn[disabled]{opacity:.70!important;filter:none!important}
html .dn-bidpanel .dn-bp-opt.disabled{opacity:.68;filter:none}

/* ── House promo tiles ────────────────────────────────────────────────────
   Eighteen games in a seven-wide auto-fill grid leave the last row three
   short. These three fill it with the company's other products — without
   saying so on the tile: the label reads "Also try", not "Also from NIRO",
   because Jesse does not want a visitor connecting the properties.

   The whole point of the styling is that they must NOT read as a nineteenth,
   twentieth and twenty-first game. Someone scanning a board of identical
   squares — which on this product is often an older player — should be able
   to tell at a glance that these go somewhere else. So: no gradient panel, a
   dashed border instead of a solid one, a label across the top, and the
   destination host spelled out at the bottom where a game says "Play now →".

   Geometry deliberately matches .game (same min-height, same container-query
   sizing, same stretch-to-row-height rule) so the grid stays a clean lattice.
   Change .game's box and change this one with it. */
.promo{
  background:rgba(255,255,255,.014);
  border:1px dashed rgba(201,162,39,.36);
  border-radius:16px;padding:10px;position:relative;transition:.15s;
  display:flex;flex-direction:column;gap:5px;min-height:120px;
  align-items:center;justify-content:center;text-align:center;
  container-type:inline-size;text-decoration:none;color:inherit}
/* .game gets height:100% from the fill-the-page rule; without the same line
   these three sit short and the last row looks broken. */
html:not(.is-native) body.dn-home .promo{height:100%}
@media (hover:hover) and (pointer:fine){
  .promo:hover{border-color:var(--gold);border-style:solid;
    background:rgba(201,162,39,.055);transform:translateY(-2px)}
}
.promo:active{border-color:var(--gold);border-style:solid}
.promo:focus-visible{outline:3px solid var(--gold);outline-offset:3px}
.promo .promo-tag{
  font-size:clamp(9px,4.4cqw,12px);letter-spacing:.10em;text-transform:uppercase;
  font-weight:700;color:rgba(201,162,39,.82);line-height:1}
.promo .promo-mark{display:flex;align-items:center;justify-content:center;
  margin:2px 0 1px}
.promo .promo-mark svg{width:clamp(28px,24cqw,68px);height:auto;display:block}
.promo h3{margin:0;font-size:clamp(14px,9cqw,26px);line-height:1.15;
  max-width:96cqw;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* The description is the first thing to go when a tile gets small: it would
   wrap to four lines and shove the host label out of the box. The name and
   the destination are what actually matter.

   The query measures the CONTAINER's content box, not the tile, so 150px here
   fires at a 172px tile — 10px padding and 1px border on each side. 172px is
   also the grid's minmax floor, which means the narrowest column the layout
   can ever produce is exactly the one that drops the description. That lines
   up on purpose; if the grid floor moves, move this with it.

   Measured across all three tiles at 172/200/240/300/380px on 2026-08-16:
   nothing clipped, nothing scrolled. */
.promo p{margin:0;font-size:clamp(10px,4.6cqw,14px);line-height:1.3;
  color:var(--muted);max-width:94cqw}
@container (max-width:150px){ .promo p{display:none} }
.promo .promo-go{
  font-size:clamp(10px,4.6cqw,14px);font-weight:600;color:var(--gold);
  word-break:break-all;line-height:1.2}

/* Native builds show eighteen games and nothing else. Sending a player from
   inside a reviewed app out to a website earns extra scrutiny from both
   stores and buys nothing — the people already have the app. */
html.is-native .promo{display:none}

/* ── Windows High Contrast / forced-colors ─────────────────────────────────
 * Added 2026-08-17, after rendering the board under forced-colors and looking
 * at it rather than reasoning about it.
 *
 * Nearly all of this app is FINE in forced-colors mode: the browser's own
 * handling turns it into legible white-on-black with yellow links, and no
 * author CSS was needed for that. (An earlier note here claimed the web app had
 * "no high contrast support" because the stylesheet had no forced-colors rules.
 * That was wrong — the user agent supplies it.)
 *
 * The playing cards are the exception, and they break badly. Forcing the system
 * palette makes every card face black and every suit the same colour, and suit
 * COLOUR is not decoration in these games — it is the rule:
 *   · Solitaire: "columns come down in alternating colours"
 *   · Hearts:    the entire scoring system is the red suits
 *   · Blackjack, Spades, Euchre, Pitch: red/black reading at a glance
 * A player using High Contrast could not follow any of them. This is the case
 * forced-color-adjust exists for — the same exemption a colour picker or a
 * photo gets.
 *
 * Scope is deliberately tight: only the cards opt out. Every control, heading,
 * link and surface around them still obeys the user's chosen colours. */
@media (forced-colors: active) {
  .card,
  .card *,
  .pip,
  .pip * {
    forced-color-adjust: none;
  }

  /* The table behind the cards IS forced, so it becomes Canvas. Without an
   * explicit edge a white card on a white Canvas (light High Contrast themes)
   * would vanish, and stacked cards would merge into one shape. */
  .card:not(.back) {
    border: 1px solid #1a1f2b;
  }
  .card.back {
    border: 1px solid #f0d48a;
  }

  /* Empty slots are not cards and stay forced, but they need to remain visible
   * as drop targets against the forced background. */
  .slot {
    border: 1px dashed CanvasText;
  }
}

/* ── macOS / iOS "Increase Contrast" ───────────────────────────────────────
 * Added 2026-08-17. This is a DIFFERENT setting from Windows High Contrast
 * above: it asks for more contrast within our palette rather than replacing it
 * with the system's, and it is what an iPhone or iPad player turns on.
 *
 * Deliberately small, because measuring first showed the base palette is
 * already good: every foreground/background pair in the theme clears WCAG AA
 * except one, and most clear it by a wide margin (body text sits at 18.4:1).
 * There was no rescue needed here, so this does not attempt a redesign — it
 * removes the deliberate softening instead.
 *
 *   · dim secondary text stops being dim; it becomes the full foreground.
 *     That flattens some visual hierarchy, which is the trade the player asked
 *     for by turning the setting on.
 *   · the deep gold is replaced by the bright gold. Deep gold on the raised
 *     surface was the single failing pair, at 4.41 against a 4.5 requirement.
 *   · borders stop being translucent hairlines, so panel edges are real edges.
 *
 * tests/increase-contrast.test.mjs recomputes every pair from these values and
 * fails if any drops below 4.5. */
@media (prefers-contrast: more) {
  :root {
    --dn-fg-dim: #f7f8fc;
    --dn-gold-deep: #f2c14e;
    --dn-scrim: rgba(6, 10, 16, .86);
  }

  /* Hairline borders are a softening device; at this setting they should read
   * as structure. */
  .panel, .card-surface, .sheet, .modal, .tile, .btn, button, input, select {
    border-color: var(--dn-fg);
  }

  /* Focus must be unmistakable for the same reason. */
  :focus-visible {
    outline: 3px solid var(--dn-gold-hi);
    outline-offset: 2px;
  }
}


/* ── Tablet in landscape is not a phone in landscape ───────────────────────
 * Found by CI on 2026-08-17: on a 1396x730 landscape tablet every top-bar
 * control measured 44px — the iOS phone MINIMUM — identical to a 390px phone.
 *
 * The cause is a rule further up whose own comment says "Landscape phone
 * chrome is still phone chrome", pinning these controls to a 44px floor with
 * `height:44px` and `!important`. Its reasoning is right for a phone. But
 * `.dn-land` is set for ANY landscape orientation, so it also catches a tablet
 * — and a tablet held sideways is how most people actually play cards on one.
 * The tablet sizing that should have applied (min-height: 3 tablet units) sits
 * right beside it and loses on specificity.
 *
 * So this re-applies the tablet scale for the landscape+tablet case only.
 * Phones in landscape are untouched and keep their 44px floor.
 *
 * Values use --dn-tab-u, the tablet unit the rest of the tablet layer is built
 * from, rather than a number invented here: 3u is 48-63px depending on screen.
 *
 * Two things this fix had to get past, both worth knowing:
 *   · specificity — the landscape rule is html:is(.dn-land,.dn-rot) + 4
 *     classes, so an override needs to match it. A tidier-looking rule with
 *     fewer selectors silently does nothing, which is what my first attempt
 *     did: the computed min-height stayed 44px and nothing complained.
 *   · !important — the originals use it on min-width/min-height, so these must
 *     too. Not decoration; without it the width stays clamped. */
@media (min-width:700px) and (min-height:700px) and (pointer:coarse) {
  html:is(.dn-land,.dn-rot) .table .topbar .brand.dn-back {
    height: auto;
    min-height: calc(var(--dn-tab-u) * 3);
    min-width: auto !important;
    width: auto;
  }
  html:is(.dn-land,.dn-rot) .table .topbar .shell-actions .btn {
    width: calc(var(--dn-tab-u) * 3);
    height: calc(var(--dn-tab-u) * 3);
    min-width: calc(var(--dn-tab-u) * 3) !important;
    min-height: calc(var(--dn-tab-u) * 3) !important;
  }
  html:is(.dn-land,.dn-rot) .table .topbar .dn-coin {
    min-height: calc(var(--dn-tab-u) * 3);
  }
}

/* ── The felt was 20px wider than the space it sits in ─────────────────────
 * Found by CI on 2026-08-17, immediately behind the tablet touch-target bug —
 * that assertion failed first, so this one had never been reached.
 *
 * On a 1396x730 landscape tablet the felt measured 10..1406 in a 1396 viewport:
 * 10px of it off the right edge of the screen.
 *
 * Two rules disagreeing. A larger-screen rule pairs `margin: 6px 10px 10px`
 * with `width: calc(100% - 20px)` — self-consistent, correct. A landscape rule
 * then resets `width: 100%` while those 10px margins stay applied, so the
 * element asks for the full width of its parent PLUS 20px of margin.
 *
 * `width: auto` is the fix rather than another calc(): a block-level flex child
 * with auto width fills the line box minus its own margins, whatever they are.
 * A second hard-coded 20px would just be a third number to keep in sync with
 * the first two, and this bug is what happens when those drift.
 *
 * Verified by measuring, before and after, at the exact viewport the test uses. */
@media (min-width: 900px) and (min-height: 600px) {
  html.dn-land body .table > .felt,
  html.dn-rot body .table > .felt {
    width: auto;
    align-self: stretch;
  }
}

/* ── The narrowest phones were 19px short of fitting the table ─────────────
 * Found by CI 2026-08-17: Blackjack's bet chips sat at y 761..815 in an 812px
 * viewport, cut off at the BOTTOM edge (the report reads "34,761", which is
 * x,y — I first read it as a horizontal clip and went looking in the wrong
 * place).
 *
 * It is not the chips, and it is not density. The arithmetic closes exactly:
 *   topbar 101 + margin 6 + felt 724 = 831, in an 812 viewport
 *   felt content = 612 (children) + 84 (6 gaps x 14px) + 28 (padding) = 724
 *   available to felt = 812 - 101 - 6 = 705
 * So the felt needs 724 and has 705. Everything below the fold by 19px.
 *
 * A rendered screenshot at 320x812 settles what to do about it: the table has a
 * large empty band through its middle, so this is not a page that needs less in
 * it — it needs slightly tighter seams on the smallest screens. Trimming the
 * gap and padding by 4px each frees 32px, which clears the 19 with room for the
 * other games that share this layout.
 *
 * Two things I got wrong on the way, both caught by testing rather than
 * reasoning: `min-height: 0` on the felt does nothing here (the container is
 * sized by min-height, so it simply grows — there is no overflow for the flex
 * algorithm to resolve), and the children alone sum to 612, which had me
 * looking for a phantom 82px until I remembered to measure the gaps. */
@media (max-width: 400px) {
  .table > .felt {
    gap: 10px;
    padding-top: 10px;
    padding-bottom: 10px;
  }
}

/* The same seam, for the BIGGER phones — found 2026-08-17 by the CI gate, and
 * the direction of it is the surprise: Blackjack's bet chips are clipped at
 * 440px wide but NOT at 430px, and it gets worse as the screen gets WIDER.
 * Measured, idle, at 812 tall:
 *
 *   430 ok   440 +1   450 +3   500 +12   560 +1   600 +1   650 +1
 *   700 +16  760 +19  820 ok   900 ok
 *
 * Widening a screen makes something taller because the dealer's hand reserves
 * `clamp(46px,13vw,66px) * 1.4` of empty space for cards that have not been
 * dealt yet, and that reservation keeps growing with the viewport until 13vw
 * hits its 66px ceiling at ~508px. The cards themselves are clamped, so past
 * about 430px the app is holding open a gap wider than anything that will ever
 * land in it, and the chip rack is what gets pushed off the bottom.
 *
 * I did NOT shrink that reservation, which was my first instinct. Measured with
 * cards actually dealt, the hand is 89px against a reservation of 87 at 480px —
 * so the reservation is very nearly right, and cutting it would just move the
 * problem into a visible jump when the first card lands.
 *
 * Worth recording that this overflows ONLY on the betting screen: dealt a hand,
 * the chip rack is gone and every width above measures 0 over. That is the
 * opposite of Tonk, where the idle measurement was the flattering one and play
 * was 18px worse. Here idle IS the worst case, so the gate is measuring the
 * right moment — and it is the first screen anyone sees in Blackjack.
 *
 * Height-scoped on purpose: a tall window has the room, and there is no reason
 * to tighten it there. Kept as a separate block rather than widening the rule
 * above so the 320px case that CI has already proved green is left untouched. */
@media (min-width: 401px) and (max-width: 820px) and (max-height: 900px) {
  .table > .felt {
    gap: 10px;
    padding-top: 10px;
    padding-bottom: 10px;
  }
}

/* ===================== QUIET CHROME PASS — 2026-08-22 (PROPOSAL) =====================
   Owner ask (2026-08-21): "get rid of a lot of those bubbles that are around the words."
   History that constrains this block:
     - 2026-07-16 "clean felt / no chip box" pass (owner) removed the seat boxes.
     - 2026-07-28 owner: "in-game words are grainy and small" — the cause was 12–14px text
       under a 14px BLURRED shadow; the fix then was an opaque plate behind every label,
       which over time grew back into the bubbles being removed here.
   So this pass removes the plates WITHOUT going back to blurred halos. Legibility on the
   felt comes from a CRISP 1px outline (eight hard 0-blur shadows — no blur ever touches a
   glyph, so hinting stays sharp) plus one small, offset lift shadow. That is also how the
   reference app (BidWhistPlus, ~/Downloads/refframes) paints its centre text: stroked
   type straight on the felt, no box. Sizes are NOT reduced anywhere; several go up.
   Every rule here is scoped to the table chrome and overrides only paint (background,
   border, box-shadow, text-shadow). Geometry (padding, min-height, 44px targets, lanes)
   is untouched, so the measured mobile-geometry / header-fit contracts still hold.
   KEPT ON PURPOSE (they carry state or are tap targets): the per-seat BID/PASS bubble,
   the coin / Sign-in pill, the New game outline, bid-chooser and chip buttons, the
   dealer "D" badge, avatars, and the tic-tac-toe active-side highlight.
   Each [Pn] block is independently removable. */
:root{
  --dn-crisp-ink:rgba(0,0,0,.92);
  --dn-crisp:
    1px 0 0 var(--dn-crisp-ink),-1px 0 0 var(--dn-crisp-ink),
    0 1px 0 var(--dn-crisp-ink),0 -1px 0 var(--dn-crisp-ink),
    1px 1px 0 var(--dn-crisp-ink),-1px -1px 0 var(--dn-crisp-ink),
    1px -1px 0 var(--dn-crisp-ink),-1px 1px 0 var(--dn-crisp-ink),
    0 2px 3px rgba(0,0,0,.55);
}

/* [P1] Table top bar: one quiet rail, not six boxes. The four named controls and
   the Back chevron keep their 44/48px hit boxes and labels; only the box paint goes.
   "New game" keeps a single gold outline because it is the one action that ends the
   hand — it should still look like a button. Sound-off keeps its blue tint as TEXT. */
.table .topbar .brand.dn-back,
.table .topbar .shell-actions .btn{
  background:transparent;border-color:transparent;box-shadow:none;color:#fff3d6}
.table .topbar .shell-actions .btn.shell-new-game{
  border-color:rgba(var(--dn-gold-rgb),.72)}
.table .topbar .shell-sound[aria-pressed="false"]{
  background:transparent;border-color:transparent;color:#bcd6ff}
.table .topbar .brand.dn-back:hover,
.table .topbar .shell-actions .btn:hover{
  filter:none;background:rgba(255,255,255,.09);border-color:transparent}
.table .topbar .shell-actions .btn.shell-new-game:hover{border-color:#f0d48a}
.table .topbar .brand.dn-back:focus-visible,
.table .topbar .shell-actions .btn:focus-visible{
  outline:3px solid #fff;outline-offset:2px;box-shadow:0 0 0 6px rgba(var(--dn-gold-rgb),.72)}
/* Wide windows paint the bar as a solid dark band, so the floating capsule behind the
   controls is paint on paint — drop it there. Phone landscape keeps its ONE capsule:
   the bar floats over live felt and cards there, and that capsule is the only thing
   separating five glyphs from the table behind them. One bubble instead of six. */
@media (min-width:900px) and (min-height:600px){
  html.dn-land .table .topbar .shell-actions,
  html.dn-rot .table .topbar .shell-actions{
    background:transparent;border-color:transparent;box-shadow:none;backdrop-filter:none}
}

/* [P2] Team score rail: words on the felt, not a 900px slab across the top of the
   table. Same lane, same position, same measured width — only the plate goes. */
html.dn-land .felt.dn-table #score:not(:empty),
html.dn-land .felt.dn-table .scorebar:not(:empty){
  background:transparent;border-color:transparent;box-shadow:none;
  color:#f8fbf9;text-shadow:var(--dn-crisp)}
html.dn-land .felt.dn-table #score .me,
html.dn-land .felt.dn-table .scorebar .me{color:#ffd166}

/* [P3] Opponent seats on wide windows: back to the owner's 2026-07-16 clean felt.
   Avatar, name, books and the BID/PASS bubble all stay; the dark card behind them and
   the pill behind the name go. (The base landscape rule already has the box off; the
   ≥900×600 block re-added it with !important, hence the !important here.) */
html.dn-land .felt.dn-table .dn-seat{
  background:transparent!important;border-color:transparent!important;box-shadow:none!important}
html.dn-land .felt.dn-table .dn-seat .name{
  background:transparent;color:#fffbed;text-shadow:var(--dn-crisp)}
html.dn-land .felt.dn-table .dn-seat .meta{text-shadow:var(--dn-crisp)}
/* books / coins under an avatar: coloured text, no chip */
.dn-seatstats span{
  background:transparent;border-color:transparent;text-shadow:var(--dn-crisp)}
.dn-seatstats .dn-ss-coins{border-color:transparent}

/* [P4] The human anchor above the fan: avatar + YOU, no pill. It is still the
   scoreboard button (44px tall, aria-label intact); a keyboard focus ring shows it. */
.felt.dn-table>.dn-you-anchor,
html.dn-land .felt.dn-table>.dn-you-anchor{
  background:transparent;border-color:transparent;box-shadow:none;
  color:#fff9e8;text-shadow:var(--dn-crisp)}
.felt.dn-table>.dn-you-anchor:focus-visible{
  outline:3px solid #fff;outline-offset:2px;border-radius:999px}

/* [P5] Status line ("Your turn — …", "Pass 3 cards left …"): stroked text on the felt,
   like the reference's "Your team made 3 books". Position and size unchanged. */
html.dn-land .felt.dn-table .msg:not(:empty){
  background:transparent;border-color:transparent;box-shadow:none;
  color:#f8fbf9;text-shadow:var(--dn-crisp)}
.felt .msg{text-shadow:var(--dn-crisp)}

/* [P6] Small labels that had their own plate: pond count (Go Fish), stock/discard
   labels, dice-tray owner, checkers turn tag, blackjack shoe count. Turn indicators keep
   their colour AND their pip/glyph so state never depends on the box. */
.felt .dn-pond-lbl{background:transparent;text-shadow:var(--dn-crisp)}
.felt .pile .lbl{text-shadow:var(--dn-crisp)}
html .felt .tray-who{background:transparent;border-color:transparent;text-shadow:var(--dn-crisp)}
html .cscore .turn{background:transparent;border-color:transparent;text-shadow:var(--dn-crisp)}
html .cscore .turn.waiting{background:transparent;border-color:transparent}
html .felt .bj-shoen{background:transparent;border-color:transparent;text-shadow:var(--dn-crisp)}
html .felt .gf-you-summary{background:transparent;border-color:transparent;text-shadow:var(--dn-crisp)}
html .felt .gf-you-summary:focus-visible{outline:3px solid #fff;outline-offset:2px}

/* [P7] Crazy Eights / Color Match opponent tile and the hearts pass prompt: the
   cards and the name are the information; the dark card behind them is not. */
html.dn-land .felt.ce-action-felt.dn-table .ce-opponent.seat{background:transparent}
html.dn-land .felt.ce-action-felt.dn-table .ce-opponent .name,
html.dn-land .felt.ce-action-felt.dn-table .ce-opponent .meta{text-shadow:var(--dn-crisp)}
html .felt #bidbar.bid-pick{background:transparent;border-color:transparent;box-shadow:none}
html .felt #bidbar .pass-prompt{text-shadow:var(--dn-crisp)}

/* [P8] Tic-tac-toe side rows: the WAITING side loses its box; the ACTIVE side keeps
   its tinted outline because that outline is the turn indicator. */
html .ttt-side{background:transparent;border-color:transparent}
html .ttt-side .ttt-who{text-shadow:var(--dn-crisp)}
html .ttt-side.on{background:rgba(86,184,255,.16);border-color:#56b8ff}
html .ttt-side.ai.on{background:rgba(255,150,63,.16);border-color:#ff963f}

/* [P9] Flat-layout (two-seat / solo) score bars and status lines: Gin Rummy, Rummy,
   Tonk, Solitaire, Tic-Tac-Toe, Blackjack, War. Same lane, plate off, crisp text. */
html .felt .scorebar:not(:empty),
html .felt>#score:not(:empty),
html.dn-flat .felt .scorebar,
html.dn-flat .felt>#score{
  background:transparent;border-color:transparent;box-shadow:none;text-shadow:var(--dn-crisp)}
html .felt #msg.msg,
html .felt #msg.war-pill{
  background:transparent;border-color:transparent;box-shadow:none;text-shadow:var(--dn-crisp)}
/* Pitch's persistent bid/trump line in the top-left corner. */
html.dn-land .felt.dn-table #status:not(:empty){
  background:transparent;border-color:transparent;text-shadow:var(--dn-crisp)}
/* The flowing choice bar (hearts pass prompt, bid-whist/euchre/pitch #bidbar): the
   buttons inside keep their own faces; the panel behind them goes. */
html.dn-land .felt.dn-table .bid-pick:not(.hidden),
html.dn-land .felt.dn-table #bidbar:not(:empty):not(.hidden),
html.dn-land .felt.dn-table #targets:not(:empty):not(.hidden){
  background:transparent;border-color:transparent;box-shadow:none;
  -webkit-backdrop-filter:none;backdrop-filter:none}
html.dn-land .felt.dn-table #bidbar .pass-prompt{text-shadow:var(--dn-crisp)}
/* Crazy Eights human control strip ("You · Draw 1") and the Go Fish pond count. */
html.dn-land .felt.ce-action-felt.dn-table .ce-player .controls.controls{
  background:transparent;border-color:transparent;box-shadow:none}
html .felt .gf-pondrow .dn-pond-lbl{background:transparent;text-shadow:var(--dn-crisp)}
/* ===================== /QUIET CHROME PASS ===================== */
