:root {
  /* Surfaces */
  --bg:        #0f1014;             /* page background outside the felt */
  --felt:      #14543b;             /* primary felt */
  --felt-dark: #0e3d2a;             /* felt vignette edge */
  --felt-rim:  #2a1a0e;             /* rim around the felt — like dark walnut */
  --paper:     #faf6ec;             /* modal cream */
  --paper-2:   #f3edd9;             /* subtle modal accent */
  --paper-border: #e6dcbf;

  /* Ink */
  --ink:       #0f1419;
  --ink-soft:  #29333a;
  --muted:     #6b7785;

  /* Brand */
  --accent:        #d4533f;         /* warm coral — more refined than bright red */
  --accent-2:      #f17a55;         /* lift for gradients/hover */
  --gold:          #d4a92a;
  --success:       #1a7f37;
  --danger:        #b3261e;

  /* Team colours — used to tell "your side" from "their side" on the meld
     and summary screens. Deliberately NOT red or green: red collides with
     the card suits and the "set" result, green collides with the "made"
     result. Blue vs violet are distinct from both and from each other, so a
     player never mistakes a team tint for a suit or a win/loss cue.
     Spectators don't get these at all — they see neutral greys (below). */
  --team-us:        #2563eb;        /* blue  — your team */
  --team-us-dark:   #1d4ed8;
  --team-them:      #7c3aed;        /* violet — opponents */
  --team-them-dark: #6d28d9;

  /* Card stock */
  --card-bg:    #fffefa;
  --card-red:   #c8102e;
  --card-black: #15202b;

  /* Depth */
  --shadow-sm: 0 1px 2px rgba(15,20,25,0.08), 0 1px 1px rgba(15,20,25,0.04);
  --shadow:    0 6px 16px rgba(15,20,25,0.18), 0 2px 4px rgba(15,20,25,0.10);
  --shadow-lg: 0 20px 50px rgba(15,20,25,0.45), 0 6px 16px rgba(15,20,25,0.25);

  /* Type */
  --font-ui:      'Inter', ui-sans-serif, -apple-system, "Segoe UI", system-ui, sans-serif;
  --font-display: 'Playfair Display', Georgia, serif;

  /* Responsive sizing (overridden at smaller breakpoints). All card
     dimensions are roughly 25% bigger than the previous defaults — they
     read more clearly across card styles, especially for older
     players. Trick-slot gap is also wider so a played trick has room
     to breathe instead of feeling cramped against the centerpoint. */
  /* Global readability scale ("Grandpa mode"). 1 = normal. The body classes
     ui-scale-large / ui-scale-grandpa raise it (see applyUiScale in app.js);
     scaled font-sizes across the site read it via calc(). At 1× nothing
     changes, so it's safe everywhere. */
  --ui-scale: 1;
  --card-w: 98px;
  --card-h: 138px;
  --hand-overlap: -62px;     /* ~36px visible per card */
  --hand-suit-gap: 14px;
  --mini-card-w: 55px;
  --mini-card-h: 78px;
  --status-fs: 13px;
  --tag-fs: 13px;
  --trick-slot-w: 105px;
  --trick-slot-h: 148px;
  --card-back-w: 40px;
  --card-back-h: 58px;
  --trick-cross-gap: 12px;   /* gap each side of the center cross */
  --trick-card-scale: 1.35;  /* user "oversized playing-area cards" slider (1–2×, default 1.35) */
  --trick-spread: 0px;       /* extra px the E/W played cards push outward (0–320) */
  --trick-spread-ns: 0px;    /* extra px the N/S played cards push outward (0–240) */
  --radius-sm: 6px;
  --radius:    10px;
  --radius-lg: 16px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  /* min-height (not height) so the lobby/auth views can grow with
     content and scroll naturally. The table view explicitly sets its
     own 100vh height and overflow: hidden — it doesn't depend on the
     body being clamped. */
  min-height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
/* Only the live table view forbids scrolling — everything is sized to
   fit the screen on purpose. Everywhere else, the page may scroll. */
#view-table { overflow: hidden; }

/* ---------- Buttons ---------- */
button {
  background: linear-gradient(180deg, var(--accent-2) 0%, var(--accent) 100%);
  color: white;
  border: 1px solid rgba(0,0,0,0.10);
  padding: 11px 18px;
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.1px;
  line-height: 1;
  border-radius: 999px;          /* pill */
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255,255,255,0.18);
  transition: transform 100ms ease, box-shadow 120ms ease, filter 120ms ease, background 120ms ease;
}
button:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: var(--shadow), inset 0 1px 0 rgba(255,255,255,0.22);
}
button:active:not(:disabled) {
  transform: translateY(0);
  box-shadow: var(--shadow-sm) inset, 0 1px 2px rgba(0,0,0,0.08);
}
button:disabled {
  background: #cbd5e1;
  color: #6b7785;
  border-color: rgba(0,0,0,0.05);
  box-shadow: none;
  cursor: not-allowed;
  filter: none;
  transform: none;
}
button.ghost {
  background: transparent;
  color: white;
  border: 1px solid rgba(255,255,255,0.24);
  box-shadow: none;
}
button.ghost:hover:not(:disabled) {
  background: rgba(255,255,255,0.08);
  border-color: rgba(255,255,255,0.4);
  filter: none;
  transform: translateY(-1px);
}
button.big {
  padding: 14px 22px;
  font-size: 15px;
  letter-spacing: 0.2px;
}

/* On light surfaces (modals), ghost buttons need dark ink. */
.modal button.ghost,
.panel button.ghost {
  color: var(--ink-soft);
  border-color: rgba(15,20,25,0.18);
}
.modal button.ghost:hover:not(:disabled),
.panel button.ghost:hover:not(:disabled) {
  background: rgba(15,20,25,0.05);
  border-color: rgba(15,20,25,0.3);
}

/* ---------- Inputs ---------- */
/* iOS Safari auto-zooms in on focus whenever an input's computed
   font-size is < 16px. That zoom is jarring (the whole site
   re-flows mid-tap) and there's no JS-only way to opt out — the
   font-size threshold is the only reliable lever. So 16px is the
   global floor for every form control. Individual inputs that want
   to look smaller can size down via padding / width — never via
   font-size below 16px. */
input, select, textarea {
  font-family: var(--font-ui);
  padding: 9px 12px;
  font-size: 16px;
  border: 1px solid var(--paper-border);
  border-radius: var(--radius-sm);
  background: white;
  color: var(--ink);
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(212, 83, 63, 0.18);
}

/* ---------- Type ---------- */
/* Headings use the UI sans-serif. The Playfair Display "Just Pinochle"
   wordmark is the one place we still want a serif feel, and it sets its
   own font-family on .wordmark, so we don't lose it here. */
h1 { font-family: var(--font-ui); font-weight: 700; letter-spacing: -0.2px; }
h2 { font-family: var(--font-ui); font-weight: 700; letter-spacing: -0.1px; }
h3, h4 { font-family: var(--font-ui); font-weight: 600; }
/* ===========================================================
   "Just Pinochle" wordmark — typographic logo.

   Two-tone, italic→roman contrast pair (a classic editorial move): the
   "Just" sits in Playfair Display italic at a lighter weight; "Pinochle"
   takes over in heavier roman with a slight letterspaced confidence. A
   thin hairline rule between them establishes a nameplate feel without
   resorting to ornaments.

   `--wm-just-color` and `--wm-pin-color` are themed by the parent (the
   topbar / auth screens override them on a dark background).
   =========================================================== */
.wordmark {
  font-family: var(--font-ui);
  display: inline-flex;
  align-items: baseline;
  /* Was gap: 0.2em which left a visible space between "Just" and
     "Pinochle". The brand reads as ONE word — set the gap to 0 so
     the two halves sit flush. */
  gap: 0;
  letter-spacing: -0.4px;
  line-height: 1;
  font-feature-settings: "kern" 1, "liga" 1;
}
/* "Just" and "Pinochle" sit flush — same size, same weight, no divider.
   The two halves still carry their own colors so the brand keeps its
   gold/white contrast on dark surfaces. */
.wordmark .wm-just {
  font-weight: 800;
  letter-spacing: -0.2px;
  color: var(--wm-just-color, #b67e1d);
}
.wordmark .wm-pin {
  font-weight: 800;
  letter-spacing: -0.2px;
  color: var(--wm-pin-color, var(--ink));
}
/* Tiny suit pair tucked next to the wordmark — a black queen-of-spades
   glyph and a red jack-of-diamonds glyph. Slightly smaller than the
   wordmark, vertically centered. */
.wordmark .wm-suits {
  display: inline-flex;
  gap: 0.05em;
  font-size: 0.7em;
  margin-left: 0.25em;
  font-weight: 700;
  line-height: 1;
}
.wordmark .wm-suit-spade { color: #1a1a1a; }
.wordmark .wm-suit-diamond { color: #c11414; }

/* ---- Auth ---- */
.auth-wrapper {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(ellipse at top, rgba(212,83,63,0.10), transparent 55%),
    radial-gradient(ellipse at bottom, rgba(20,84,59,0.35), transparent 60%),
    var(--bg);
  color: white;
  gap: 60px;
  padding: 48px 24px;
  flex-wrap: wrap;
}
/* Desktop: two columns. .auth-landing on the left (the landing copy
   shipped in 0.12.1 to make the unauth experience feel like more
   than a bare sign-in box), .panel on the right. Vertical centering
   is still flex-aligned but min-height now lets the page grow if
   the landing column is taller than the viewport. */
.auth-wrapper .auth-landing {
  flex: 0 1 420px;
  max-width: 480px;
}
.auth-wrapper > .wordmark,
.auth-landing .wordmark {
  font-size: 56px;
  margin: 0 0 18px;
  --wm-just-color: #e9d6a8;
  --wm-pin-color: #ffffff;
}
.landing-lede {
  font-size: 19px;
  line-height: 1.45;
  margin: 0 0 22px;
  color: #f0e6c8;
  font-weight: 500;
}
.landing-features {
  list-style: none;
  margin: 0 0 22px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  font-size: 15px;
  line-height: 1.45;
}

/* v0.20.2 — minimal "trio" row replaces the 4-bullet feature list.
   Three quiet lines, one-icon-each, no paragraph copy. The aim is
   to communicate the product shape in one glance instead of asking
   the visitor to read a marketing pitch. Mobile-first: stacks
   vertically; widens to a tighter row at desktop. */
.landing-trio {
  list-style: none;
  margin: 0 0 20px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  font-size: 14.5px;
  line-height: 1.4;
  color: #f4ecd8;
}
.landing-trio li {
  display: flex;
  gap: 12px;
  align-items: center;
}
.landing-trio .trio-icon {
  font-size: 22px;
  width: 30px;
  text-align: center;
  flex-shrink: 0;
  /* Faint accent rather than a saturated emoji so the row reads as
     monochrome text first, decoration second. */
  filter: saturate(0.7);
  opacity: 0.95;
}

/* Below-fold SEO + LLMO section. Renders under the auth panel on
   ALL viewports. The content is the same plain language statement
   of what this site is — used by crawlers and by humans who landed
   on a search and want to know what they're signing up for before
   they register. Visual treatment is intentionally quiet: dark
   background, narrow column, readable line-length. */
.landing-seo {
  padding: 32px 20px 24px;
  background: rgba(0,0,0,0.18);
  color: #f4ecd8;
  border-top: 1px solid rgba(255,255,255,0.06);
}
.landing-seo-inner {
  max-width: 720px;
  margin: 0 auto;
  font-size: 15px;
  line-height: 1.55;
}
.landing-seo h2 {
  font-family: var(--font-display, 'Playfair Display', Georgia, serif);
  font-size: 22px;
  font-weight: 700;
  margin: 0 0 12px;
  color: #fff;
}
.landing-seo h3 {
  font-size: 16px;
  font-weight: 700;
  margin: 22px 0 8px;
  color: #f0e6c8;
  letter-spacing: 0.2px;
}
.landing-seo p { margin: 0 0 12px; color: #e0d7be; }
.landing-seo-list {
  list-style: none;
  padding: 0;
  margin: 0 0 4px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.landing-seo-list li {
  padding-left: 16px;
  position: relative;
  color: #e0d7be;
}
.landing-seo-list li::before {
  content: '·';
  position: absolute;
  left: 4px;
  top: -2px;
  color: var(--accent);
  font-weight: 900;
  font-size: 18px;
}
.landing-seo-list strong { color: #fff; font-weight: 700; }
.landing-faq {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.landing-faq dt {
  font-weight: 700;
  color: #fff;
  margin-top: 10px;
}
.landing-faq dd {
  margin: 4px 0 0;
  color: #d7ceb2;
}
.landing-faq a { color: var(--accent); }

@media (min-width: 720px) {
  .landing-seo { padding: 48px 32px 40px; }
}
.landing-features li {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  color: #f4ecd8;
}
.landing-features .lf-emoji {
  font-size: 24px;
  line-height: 1.2;
  flex: 0 0 auto;
  width: 32px;
  text-align: center;
}
.landing-features strong { color: #fff; font-weight: 700; }
.landing-fine {
  font-size: 13px;
  color: rgba(255,255,255,0.55);
  margin: 0 0 18px;
}
/* Homepage "Buy us a hand" callout — quiet, single-line, no
   competing colour with the sign-in CTA. Tinted gold like the
   wordmark accent so it sits in the same visual family. */
.landing-support {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 18px;
  background: rgba(212,169,42,0.10);
  border: 1px solid rgba(212,169,42,0.35);
  border-radius: 14px;
  color: #f4ecd8;
  text-decoration: none;
  transition: background 140ms ease, transform 140ms ease;
}
.landing-support:hover {
  background: rgba(212,169,42,0.18);
  transform: translateY(-1px);
}
.landing-support-icon {
  font-size: 24px;
  color: var(--gold);
  flex: 0 0 auto;
}
.landing-support-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  line-height: 1.35;
}
.landing-support-body strong { font-size: 15px; font-weight: 700; color: #fff; }
.landing-support-body small { font-size: 12px; color: rgba(255,255,255,0.6); }
.auth-wrapper .panel {
  width: 360px;
  max-width: 100%;     /* shrink on narrow phones so it doesn't overflow */
  flex: 0 0 auto;
}

/* Mobile-only floating CTA at the top of the auth view. Jumps to
   #auth-panel so a visitor arriving on a phone can sign in
   immediately without scrolling through the landing copy. Sticky
   so it follows scroll until you reach the panel. */
/* Two-button sticky CTA bar. "Sign in" (primary orange) + "Register"
   (ghost on the same bar). Each button jumps to the auth panel AND
   switches the panel's tab to match — wired in app.js. */
.auth-mobile-cta {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  gap: 10px;
  padding: 12px 12px;
  background: linear-gradient(180deg, #1d2630, #131921);
  box-shadow: 0 3px 14px rgba(15,20,25,0.35);
}
.auth-cta-btn {
  flex: 1 1 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 12px 14px;
  border-radius: 10px;
  font-weight: 800;
  font-size: 15px;
  letter-spacing: 0.2px;
  text-decoration: none;
  border: 1.5px solid transparent;
  text-align: center;
  min-height: 44px;
}
.auth-cta-btn.primary {
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
  color: white;
  box-shadow: 0 2px 8px rgba(212,83,63,0.35);
}
.auth-cta-btn.ghost {
  background: rgba(255,255,255,0.06);
  border-color: rgba(255,255,255,0.35);
  color: white;
}
.auth-cta-btn.primary:active { transform: translateY(1px); }
.auth-cta-btn.ghost:hover { background: rgba(255,255,255,0.12); }

@media (max-width: 899px) {
  .auth-wrapper {
    gap: 28px;
    padding: 28px 16px 32px;
    flex-direction: column;
    align-items: stretch;
    min-height: unset;            /* let the page scroll naturally */
  }
  /* Landing copy now stays VISIBLE on mobile (stacked above the
     panel) so a phone visitor isn't dropped onto a bare form. */
  .auth-wrapper .auth-landing {
    flex: 1 1 auto;
    max-width: 100%;
  }
  .auth-landing .wordmark { font-size: 52px; }
  .landing-lede { font-size: 16px; margin-bottom: 16px; }
  .landing-features { font-size: 14px; gap: 10px; margin-bottom: 16px; }
  .landing-features li { gap: 10px; }
  .landing-features .lf-emoji { font-size: 20px; width: 26px; }
  .landing-support { padding: 12px 14px; }
  .auth-wrapper .panel { width: 100%; max-width: 420px; margin: 0 auto; }
}
/* Desktop hides the mobile CTA — flow is two columns side by side. */
@media (min-width: 900px) {
  .auth-mobile-cta { display: none; }
}

.panel {
  background: var(--paper);
  color: var(--ink);
  padding: 24px;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  border: 1px solid var(--paper-border);
}
.panel h2 {
  margin-bottom: 12px;
  font-size: 18px;
  color: var(--ink);
}
.panel h2:not(:first-child) { margin-top: 20px; }
.panel form { display: flex; flex-direction: column; gap: 10px; margin-bottom: 6px; }
.err { color: var(--accent); min-height: 1.2em; margin-top: 8px; font-size: 13px; }
.ok  { color: #1f9d6c; min-height: 1.2em; margin-top: 4px; font-size: 13px; font-weight: 600; }

/* Quiet "link" button — looks like an anchor, not a CTA. */
.link-btn {
  background: transparent;
  border: 0;
  padding: 8px 4px;
  color: var(--muted);
  font-size: 13px;
  text-decoration: underline;
  cursor: pointer;
  font-family: var(--font-ui);
  box-shadow: none;
  text-align: center;
  width: 100%;
  font-weight: 500;
}
.link-btn:hover:not(:disabled) {
  color: var(--accent);
  filter: none;
}

/* Auth tabs */
.auth-tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 16px;
  border-bottom: 1px solid rgba(0,0,0,0.08);
  padding-bottom: 0;
}
.auth-tab {
  background: none;
  border: 0;
  /* v0.20.1: bumped horizontal padding from 4px to 14px so the
     "Sign in" / "Register" labels aren't hugging the underline.
     Vertical 12px gives a slightly bigger tap target too. */
  padding: 12px 14px;
  margin-bottom: -1px;
  font-weight: 600;
  font-size: 14px;
  color: var(--muted);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  box-shadow: none;
}
.auth-tab:hover:not(.active) { color: var(--ink); filter: none; }
.auth-tab.active {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

.auth-fine {
  font-size: 12px;
  color: var(--muted);
  margin-top: 2px;
  line-height: 1.45;
}

/* "Check your email" pending state */
#signup-pending h2 { margin-top: 0; }
#signup-pending p { margin: 8px 0; font-size: 14px; }
#signup-pending button { margin-top: 10px; width: 100%; }
#signup-pending button.ghost { margin-top: 6px; }
#login-unverified {
  margin-top: 10px;
  padding: 10px;
  background: rgba(212, 169, 42, 0.12);
  border-radius: 8px;
  font-size: 13px;
}
#login-unverified button { margin-top: 8px; }

/* Honeypot: visually hidden but still focusable-by-bot. */
.hp-field {
  position: absolute !important;
  left: -10000px !important;
  width: 1px !important;
  height: 1px !important;
  opacity: 0 !important;
  pointer-events: none !important;
}

/* ---- Topbar (lobby) ---- */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 24px;
  background: linear-gradient(180deg, rgba(0,0,0,0.55), rgba(0,0,0,0.35));
  color: white;
  border-bottom: 1px solid rgba(255,255,255,0.06);
}
.topbar h1 {
  font-size: 22px;
  margin: 0;
  /* Same two-tone as the auth screen, scaled for the topbar. */
  --wm-just-color: #e9d6a8;
  --wm-pin-color: #ffffff;
}
#user-bar { font-size: 14px; display: flex; align-items: center; gap: 10px; }

/* ---- Lobby ---- */
.lobby-wrapper {
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 900px;
  margin: 0 auto;
}
.lobby-actions {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
}

/* Rejoin banner — shown when the user left a mid-game table. */
.rejoin-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 18px;
  background: linear-gradient(180deg, #fff4d6, #fcedb5);
  border: 1px solid var(--gold);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  margin-bottom: 4px;
}
.rejoin-banner .rejoin-msg { display: flex; flex-direction: column; gap: 2px; font-size: 14px; }
.rejoin-banner #rejoin-countdown {
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  color: var(--accent);
}
.rejoin-banner button { flex: 0 0 auto; }

/* Admin-reply notification banner. Sits below the start-game button.
   Styled blueish (info, not warning) so it doesn't compete with the
   gold rejoin-banner above it. Whole banner is a button — entire
   surface is clickable. */
.admin-msg-banner {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 12px 16px;
  background: linear-gradient(180deg, #e6f1ff, #d4e4fb);
  border: 1px solid #6f9bdc;
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  margin-bottom: 4px;
  text-align: left;
  cursor: pointer;
  color: inherit;
  font: inherit;
  transition: transform 80ms ease, box-shadow 80ms ease;
}
.admin-msg-banner:hover {
  transform: translateY(-1px);
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.admin-msg-banner .amb-icon {
  font-size: 28px;
  flex: 0 0 auto;
  line-height: 1;
}
.admin-msg-banner .amb-text {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.admin-msg-banner .amb-text strong { font-size: 14px; }
.admin-msg-banner .amb-sub { font-size: 12px; color: #496684; }
.admin-msg-banner .amb-cta {
  flex: 0 0 auto;
  font-weight: 700;
  color: #1f4d99;
}


/* Global admin announcement (#1) — a fixed amber strip across the very top of
   every view; sits above modals/overlays so a "server reboot soon" notice is
   never missed. Auto-dismisses; × closes it. */
.global-announcement {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  padding-top: max(10px, env(safe-area-inset-top));
  background: linear-gradient(180deg, #fde9b0, #f7d774);
  color: #4a3a08;
  border-bottom: 1px solid #d8b94e;
  box-shadow: 0 4px 14px rgba(0,0,0,0.28);
  font-weight: 600;
  font-size: 14px;
  animation: ga-drop 220ms ease-out;
}
.global-announcement .ga-icon { flex: 0 0 auto; font-size: 18px; line-height: 1; }
.global-announcement .ga-text { flex: 1 1 auto; line-height: 1.35; overflow-wrap: anywhere; }
.global-announcement .ga-refresh {
  flex: 0 0 auto;
  background: #4a3a08;
  color: #fde9b0;
  border: none;
  border-radius: 999px;
  padding: 6px 14px;
  font-size: 13px; font-weight: 700; cursor: pointer; line-height: 1;
  box-shadow: none;
}
.global-announcement .ga-refresh:hover { filter: brightness(1.15); }
.global-announcement .ga-close {
  flex: 0 0 auto;
  background: rgba(0,0,0,0.10);
  border: none; color: #4a3a08;
  border-radius: 999px; width: 28px; height: 28px;
  font-size: 14px; font-weight: 700; cursor: pointer; line-height: 1; padding: 0;
  box-shadow: none;
}
.global-announcement .ga-close:hover { background: rgba(0,0,0,0.18); }
@keyframes ga-drop { from { transform: translateY(-100%); opacity: 0; } to { transform: none; opacity: 1; } }

/* Leave-countdown overlay (for the players still at the table). */
.leave-countdown-modal {
  text-align: center;
  max-width: 460px;
}
.leave-countdown-modal h2 {
  margin: 0 0 10px;
  font-size: 22px;
}
.leave-countdown-msg {
  font-size: 15px;
  margin: 8px 0;
}
.leave-secs {
  color: var(--accent);
  font-size: 28px;
  font-family: var(--font-ui);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  margin: 0 4px;
}
.leave-actions {
  margin-top: 12px;
  display: flex;
  justify-content: center;
}
.lobby-actions button.primary.big,
.lobby-actions .lobby-howto {
  padding: 18px 26px;   /* a bit taller for clarity (#3) */
  font-size: 16px;
  font-weight: 700;
}
/* How-to-play CTA: a neutral GREY secondary pill, deliberately NOT the coral
   primary (and no longer green) so it clearly defers to "Start a game" (#3).
   It's an <a>, so it doesn't inherit the base button styles — fully styled here. */
.lobby-howto {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: linear-gradient(180deg, #7c8694 0%, #586273 100%);
  color: #fff;
  border: 1px solid rgba(0,0,0,0.10);
  border-radius: 999px;
  letter-spacing: 0.2px;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255,255,255,0.18);
  transition: transform 100ms ease, filter 120ms ease;
}
.lobby-howto:hover { filter: brightness(1.06); }
.lobby-howto:active { transform: translateY(1px); }
.table-count-badge {
  display: inline-block;
  background: var(--accent);
  color: white;
  font-size: 11px;
  font-weight: 700;
  padding: 1px 7px;
  border-radius: 999px;
  margin-left: 4px;
  vertical-align: middle;
  line-height: 1.5;
}
.panel.collapsible { animation: slideDown 180ms ease; }
@keyframes slideDown {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Open-tables panel — header w/ sort dropdown, list, pagination. */
.tables-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}
.tables-header h2 { margin: 0; }
.tables-controls { display: flex; gap: 8px; align-items: center; }
.tables-sort {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--muted);
}
.tables-sort select {
  /* Keep ≥ 16px so mobile Safari doesn't auto-zoom on focus. */
  font-size: 16px;
  padding: 6px 10px;
  border-radius: 8px;
  border: 1px solid var(--paper-border);
  background: white;
  color: var(--ink);
}
.tables-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-top: 12px;
  font-size: 13px;
  color: var(--muted);
}
.tables-pager button { font-size: 12px; padding: 6px 12px; }
.tables-pager button:disabled { opacity: 0.4; }
@media (max-width: 599px) {
  /* Keep Sort by inline with the title even on phones — stacking it
     was just pushing the rest of the page down. The dropdown stays
     compact enough to fit in the header row. */
  .tables-header { gap: 8px; }
  .tables-sort > span { display: none; }   /* drop the "Sort by" label on phones */
  /* font-size MUST stay ≥ 16px or iOS auto-zooms on focus. Use
     padding + max-width to keep the visual footprint compact. */
  .tables-sort select { font-size: 16px; padding: 4px 8px; max-width: 160px; }
}
#table-list { list-style: none; }
#table-list li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 14px;
  margin-bottom: 8px;
  background: white;
  border: 1px solid var(--paper-border);
  border-radius: var(--radius);
  transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;
}
#table-list li:hover { border-color: var(--accent); box-shadow: var(--shadow-sm); transform: translateY(-1px); }
#table-list li.empty { background: transparent; border: 0; color: var(--muted); padding: 8px 0; }
#table-list li.is-mine {
  border-color: var(--gold);
  background: #fffcf0;
  box-shadow: 0 0 0 1px rgba(212, 169, 42, 0.3);
}
#table-list .table-info {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  min-width: 0;
}
#table-list .table-name { font-weight: 700; }
#table-list .table-meta { color: var(--muted); }
#table-list .table-actions { display: flex; gap: 6px; flex: 0 0 auto; }
#table-list .host-tag {
  background: var(--gold);
  color: #1f1408;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}
#table-list .tier-private {
  background: #f1ecdb;
  color: #6b5a2a;
  font-family: var(--font-ui);
  letter-spacing: 0.3px;
}
#table-list .close-table-btn {
  background: transparent;
  color: var(--accent);
  border: 1px solid rgba(212, 83, 63, 0.4);
  padding: 4px 10px;
  font-weight: 700;
  font-size: 14px;
  line-height: 1;
  border-radius: 6px;
  box-shadow: none;
}
#table-list .close-table-btn:hover:not(:disabled) {
  background: var(--accent);
  color: white;
  filter: none;
}

/* Invite-code reveal panel in the create-game modal. */
#sg-invite-success h3 { margin: 0 0 8px; font-size: 18px; }
#sg-invite-success p { font-size: 14px; margin: 6px 0; }
.invite-reveal {
  display: flex;
  gap: 10px;
  align-items: center;
  background: #f1ecdb;
  padding: 12px 16px;
  border-radius: 10px;
  margin: 12px 0;
}
.invite-reveal code {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 22px;
  font-weight: 800;
  letter-spacing: 4px;
  color: var(--ink);
  flex: 1 1 auto;
}
#sg-invite-link {
  word-break: break-all;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  background: #faf6ec;
  padding: 6px 10px;
  border-radius: 6px;
  font-size: 12px;
}
#sg-continue { margin-top: 6px; }

/* Players-online grid — square tiles, more per row. */
.online-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 10px;
}
.online-grid .empty { color: var(--muted); font-size: 14px; }
.player-chip {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 14px 8px;
  background: white;
  border: 1px solid var(--paper-border);
  border-radius: var(--radius);
  text-align: center;
  box-shadow: none;
  cursor: pointer;
  transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;
  font-family: var(--font-ui);
  color: var(--ink);
  font-weight: 500;
  position: relative;
  /* Content-height tiles. The previous `aspect-ratio: 1` ran into a
     mobile-Safari quirk where the grid track grew to fill its parent
     and the chip stretched vertically — leaving a 600px empty panel
     under a single tile. min-height keeps the chip roughly square
     without forcing exact 1:1. Bumped to 132 in 0.12.2 to give the
     bigger 56px emoji vertical room without crowding the name. */
  min-height: 132px;
}
.player-chip:hover:not(:disabled) {
  border-color: var(--accent);
  box-shadow: var(--shadow-sm);
  transform: translateY(-1px);
  filter: none;
}
.player-chip .player-emoji {
  /* The player's avatar should be the dominant element of the chip
     — the lobby's job is 'who's here.' Bumped from 34 → 56 so the
     emoji reads as a face, not a footnote. min-height on the chip
     already accommodates the larger glyph. */
  font-size: 56px;
  line-height: 1;
  flex: 0 0 auto;
}
.player-chip .player-name {
  font-weight: 700;
  font-size: 13px;
  width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: flex;
  gap: 4px;
  align-items: center;
  justify-content: center;
}
.player-chip .player-rating {
  color: var(--accent);
  font-weight: 800;
  font-size: 15px;
  font-variant-numeric: tabular-nums;
}
/* Subtle tier-tinted border so skill level is still discoverable
   without text — borrows the tier-badge palette. */
.player-chip.tier-pro      { border-color: rgba(180, 130, 16, 0.7); }
.player-chip.tier-advanced { border-color: rgba(110, 70, 180, 0.55); }
.player-chip.tier-good     { border-color: rgba(30, 90, 200, 0.45); }
.player-chip.tier-starting { border-color: var(--paper-border); }
.player-chip .you-tag {
  font-size: 10px;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--gold);
  color: #1f1408;
  letter-spacing: 0.5px;
}
.player-chip .blocked-tag {
  font-size: 10px;
  padding: 2px 6px;
  border-radius: 999px;
  background: rgba(212, 83, 63, 0.12);
  color: var(--accent);
  font-weight: 700;
  white-space: nowrap;
}
.player-chip.is-blocked {
  opacity: 0.6;
  background: #faf3df;
}
.player-chip.is-you {
  border-color: var(--gold);
  background: #fffcf0;
}

/* Push opt-in + install prompts (above the rankings panel). Compact panels;
   hidden by default until app.js decides they apply on this device. */
.push-activate-panel, .install-prompt-panel { padding: 12px 14px; }
.push-activate-row {
  display: flex;
  align-items: center;
  gap: 12px;
  justify-content: space-between;
}
.push-activate-copy { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.push-activate-copy strong { font-size: 14px; }
.push-activate-copy span { font-size: 12px; color: var(--muted); }
.push-toggle {
  flex: 0 0 auto;
  padding: 8px 16px;
  border-radius: 999px;
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
  border: 1px solid rgba(0,0,0,0.1);
  white-space: nowrap;
}
.push-toggle.is-off { background: linear-gradient(180deg, var(--accent-2), var(--accent)); color: #fff; }
.push-toggle.is-on  { background: var(--paper-2, #f3edd9); color: var(--ink); border-color: var(--paper-border, #e6dcbf); }
.push-toggle:hover { filter: brightness(1.05); }
.push-toggle[disabled] { opacity: 0.6; cursor: default; }
.install-row { display: flex; align-items: center; gap: 12px; justify-content: space-between; }
.install-row span { font-size: 12px; color: var(--muted); }
.install-row.install-ios span { line-height: 1.5; }
.install-row.install-ios strong { color: var(--ink); }

/* Rankings panel — smaller and secondary now that players grid is the star. */
.rankings-panel { font-size: 13px; }
.rankings-panel h2 { font-size: 14px; }
.rankings-tabs {
  display: flex;
  gap: 4px;
  margin: 0 0 10px;
  border-bottom: 1px solid var(--paper-border);
}
.rankings-tab {
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  /* Reset the global button pill + shadow so these read as flat underline
     tabs, exactly like the Sign in / Register and modal tabs (#5). */
  border-radius: 0;
  box-shadow: none;
  padding: 8px 12px;
  margin-bottom: -1px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}
.rankings-tab:hover:not(.active) { background: transparent; color: var(--accent); transform: none; filter: none; box-shadow: none; }
.rankings-tab small { font-weight: 400; opacity: 0.7; margin-left: 2px; }
.rankings-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
/* Rankings render as a single descending list — one player per row.
   The previous columns:2 split read like two parallel leaderboards
   and made it harder to compare rank-to-rank ("am I 8th or 15th?").
   Padding-left keeps the ol's numerals visible. */
.rankings-list { padding-left: 20px; }
.rankings-list li { padding: 2px 0; }
.rankings-list li small { color: var(--muted); }
#rankings, #rankings-bot { padding-left: 20px; }
#rankings li, #rankings-bot li { padding: 2px 0; }
@media (max-width: 600px) {
  .lobby-wrapper { padding: 14px; }
}

/* Modal-create-game extras */
.form-inline-block { display: flex; flex-direction: column; gap: 4px; }
.form-inline-block .label-text { font-size: 12px; color: var(--muted); font-weight: 600; }
/* font-size must stay ≥ 16px to avoid mobile Safari auto-zoom. */
.form-inline-block select { padding: 10px 12px; border-radius: 6px; border: 1px solid rgba(0,0,0,0.15); font-size: 16px; }

/* Start-a-game modal: segmented pill controls (replaces dropdowns) */
.sg-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 14px;
}
.sg-field:first-of-type { margin-top: 8px; }
.sg-label {
  font-size: 12px;
  font-weight: 700;
  color: var(--muted);
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.sg-label .sg-sub {
  font-weight: 500;
  color: #aaa39a;
  text-transform: none;
  letter-spacing: normal;
  margin-left: 6px;
  font-size: 11px;
}
/* Range-slider control (e.g. oversized playing-area cards). */
.sg-slider-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.sg-slider-row .sg-slider-end {
  font-size: 11px;
  color: var(--muted);
  font-weight: 600;
  flex: 0 0 auto;
}
.sg-slider {
  flex: 1 1 auto;
  min-width: 0;
  height: 6px;
  -webkit-appearance: none;
  appearance: none;
  background: linear-gradient(90deg, var(--accent-2), var(--accent));
  border-radius: 999px;
  outline: none;
  cursor: pointer;
  padding: 0;
  box-shadow: none;
}
.sg-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--accent);
  box-shadow: 0 1px 3px rgba(0,0,0,0.3);
  cursor: pointer;
}
.sg-slider::-moz-range-thumb {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--accent);
  box-shadow: 0 1px 3px rgba(0,0,0,0.3);
  cursor: pointer;
}
.sg-slider-val {
  flex: 0 0 auto;
  min-width: 52px;
  text-align: right;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}
/* Device fit-limit (#8): on the spread sliders the track is green up to the
   amount that fits this screen and red beyond it, so a spread set wider than
   the viewport can hold reads as "won't fit here". --limit-pct is set in JS. */
.sg-slider.has-limit {
  background: linear-gradient(90deg,
    #3f9d6b 0 calc(var(--limit-pct, 100) * 1%),
    #e0533d calc(var(--limit-pct, 100) * 1%) 100%);
}
.sg-slider-val.over-limit { color: #d63b25; }
.sg-slider-warn {
  display: block;
  margin-top: 4px;
  color: #d63b25;
  font-size: 11px;
  font-weight: 600;
}

.sg-pills {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.sg-pills-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
}
.sg-pill {
  flex: 1 1 0;
  min-width: 0;
  padding: 10px 12px;
  background: white;
  border: 1px solid var(--paper-border);
  border-radius: 10px;
  cursor: pointer;
  text-align: center;
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--ink);
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: center;
  justify-content: center;
  transition: border-color 120ms ease, background 120ms ease, transform 120ms ease, box-shadow 120ms ease;
  box-shadow: none;
}
.sg-pill:hover { filter: none; background: #faf6ec; }
.sg-pill strong { font-weight: 700; font-size: 14px; }
.sg-pill small { font-size: 11px; color: var(--muted); line-height: 1.25; }
.sg-pill.active {
  background: #fffcf0;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(212, 83, 63, 0.18);
  color: var(--ink);
}
.sg-pill.active strong { color: var(--accent); }
.sg-pill.active small { color: var(--ink); }

/* Tier pills get a live "(N online)" count appended at build time.
   When N is 0, .sg-pill-empty greys the pill out so the host can see
   the tier exists but has nobody at it. Still clickable — useful for
   private invite-only tables — but visually flagged. */
.sg-pill .sg-pill-count { font-size: 11px; color: var(--muted); margin-left: 4px; font-weight: 600; }
.sg-pill-empty { opacity: 0.45; filter: grayscale(0.4); }
.sg-pill-empty.active { opacity: 1; filter: none; }  /* if the host picks it anyway, restore full color */
.sg-pill-empty .sg-pill-count { color: #b00020; }
.sg-tier-empty-hint {
  background: rgba(212, 83, 63, 0.08);
  border: 1px solid rgba(212, 83, 63, 0.25);
  border-radius: 8px;
  padding: 8px 10px;
  margin-top: 6px;
  color: #6b3a30;
  font-size: 12.5px;
}

.sg-hint { margin-top: 4px; }

#form-sg-create input[type="text"],
#sg-create input {
  margin-bottom: 0;
}
/* Submit buttons in the Start-a-game form get breathing room above so they
   don't sit flush against the last pill group. */
#sg-create > button[type="submit"],
#sg-join > button[type="submit"] {
  margin-top: 20px;
}

@media (max-width: 599px) {
  .sg-pill { padding: 8px 10px; font-size: 12px; }
  .sg-pill strong { font-size: 13px; }
  .sg-pill small { font-size: 10px; }
  .sg-pills-grid { grid-template-columns: repeat(auto-fit, minmax(70px, 1fr)); }
}

/* ==========================================================
   Your Settings modal — pill groups w/ live previews
   ========================================================== */
.settings-modal .settings-field {
  margin-top: 18px;
}
.settings-modal .settings-field:first-of-type { margin-top: 6px; }
.settings-modal .sg-label { letter-spacing: 0.5px; }

.settings-pills {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px;
}
.settings-pill {
  /* Inherits the segmented-pill styling from .sg-pill but adds an optional
     visual preview block at the top. */
  padding: 12px 10px;
  gap: 6px;
  position: relative;   /* anchor for the "NEW" badge */
}
/* "NEW" badge on a freshly-added option (e.g. the Blocks card style).
   Sits in the top-right corner of the pill. */
.settings-pill-badge {
  position: absolute;
  top: -6px;
  right: -4px;
  background: var(--accent);
  color: #fff;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.5px;
  padding: 2px 6px;
  border-radius: 999px;
  z-index: 3;
  box-shadow: 0 1px 3px rgba(15,20,25,0.3);
}
.settings-pill-preview {
  /* Show the top portion of the card so it fits inside the pill.
     50px gets the TL rank+suit chip AND most of the centered
     suit/face glyph (which sits at the vertical middle of a 56px
     card) in view, while still saving the bottom corner. */
  display: flex;
  align-items: flex-start;
  justify-content: center;
  height: 50px;
  margin-bottom: 4px;
  width: 100%;
  padding-top: 12px;
  overflow: hidden;
}
/* Position the READY badge to sit visibly inside the preview pill
   (the default -9px above the card would be clipped by the pill's
   overflow:hidden). Same blue pill, just nudged onto the top of the
   card so the user sees the same affordance they'll see in-game. */
.settings-pill-preview .preview-card.ready::before {
  /* Keep the base rule's centered positioning (left:50% + translateX(-50%));
     only nudge it onto the card top and shrink it. An earlier `right:-4px`
     here fought the inherited `left:50%`, stretching the badge and pushing it
     off-position. */
  top: 1px;
  font-size: 7px;
  padding: 1px 4px;
  letter-spacing: 0.3px;
}
/* Title + subtitle wrapper. On desktop the pill stays flex-column so this
   just appears as two stacked text lines below the preview. On mobile we
   flip the pill to flex-row and let this column sit beside the preview. */
.settings-pill-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: center;
  text-align: center;
}

/* Preview cards live inside settings pills — smaller than table cards, no
   click affordance, no shadow tricks. */
.preview-card {
  width: 40px;
  height: 56px;
  position: relative;
  border-radius: 6px;
  background: white;
  border: 1px solid #ddd6c4;
  box-shadow: 0 1px 2px rgba(0,0,0,0.08);
  display: block;
  font-family: var(--font-ui);
  overflow: hidden;
  pointer-events: none;
}
.preview-card .corner { position: absolute; padding: 3px 4px; line-height: 1; }
.preview-card .corner.tl { top: 0; left: 0; }
.preview-card .corner.br { bottom: 0; right: 0; transform: rotate(180deg); }
.preview-card .corner .rank { font-size: 10px; font-weight: 700; }
.preview-card .corner .suit { font-size: 11px; }
.preview-card .center {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
}
.preview-card.red .corner .rank,
.preview-card.red .corner .suit,
.preview-card.red .center { color: var(--card-red); }
.preview-card.black .corner .rank,
.preview-card.black .corner .suit,
.preview-card.black .center { color: var(--card-black); }
/* Big-suit preview overrides */
.preview-card.big-suit .corner, .preview-card.big-suit .center { display: none; }
.preview-card.big-suit .bs-rank {
  position: absolute;
  top: 3px;
  left: 5px;
  font-size: 13px;
  font-weight: 800;
  line-height: 1;
}
.preview-card.big-suit .bs-suit {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 36px;
  line-height: 1;
}
/* Vector preview — uses SVG already; just constrain size */
.preview-card.vector { padding: 0; }
.preview-card.vector svg { display: block; width: 100%; height: 100%; }

/* Traditional preview — italic-serif rank stacked over suit pip,
   scaled to the 40x56 preview footprint. */
.preview-card.traditional .center { display: none; }
.preview-card.traditional .trad-stack {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  line-height: 0.9;
}
.preview-card.traditional .trad-rank {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 700;
  font-size: 22px;
  letter-spacing: -0.5px;
}
.preview-card.traditional .trad-suit { font-size: 14px; margin-top: 1px; }

/* "Ready" preview: subtle lift + ring + READY badge for clarity. */
.preview-card.ready {
  transform: translateY(-6px);
  box-shadow: 0 0 0 2px #3b82f6, 0 8px 10px rgba(15,20,25,0.18);
  position: relative;
  overflow: visible;
}
.preview-card.ready::before {
  content: 'READY';
  position: absolute;
  top: -9px;
  left: 50%;
  transform: translateX(-50%);
  background: #3b82f6;
  color: white;
  font-size: 9px;
  font-weight: 800;
  padding: 2px 6px;
  border-radius: 999px;
  letter-spacing: 0.5px;
  white-space: nowrap;
}

/* Active settings pill — slightly brighter background to make the choice
   read at a glance among multi-line cards. */
.settings-pill.active .preview-card {
  border-color: var(--accent);
}

@media (max-width: 599px) {
  /* On phones each option becomes a list-style row: a small preview on
     the left (where present) and title + subtitle stacked left-aligned
     on the right. The previous full-width vertical layout left huge
     empty stripes either side of the centered card preview and pushed
     the modal much taller than it needed to be. */
  /* Two pills per row on phones — almost every settings group has only
     two options (On/Off, Manual/Auto, …), and pairing them in a row
     is faster to read and tap than a stacked list. Groups with three
     options (Card style) wrap to 2+1. Each pill stays a compact
     vertical column inside its half-width box. */
  .settings-pills { grid-template-columns: 1fr 1fr; gap: 8px; }
  .settings-pill {
    padding: 10px 8px;
    gap: 4px;
  }
  .settings-pill-preview {
    height: 60px;
    padding-top: 12px;
    margin-bottom: 2px;
  }
  .settings-pill-body {
    align-items: center;
    text-align: center;
  }
  .settings-pill strong { font-size: 13px; }
  .settings-pill small { font-size: 11px; }
  .sg-label {
    font-size: 13px;
    font-weight: 600;
  }
  .sg-sub { font-size: 11px; opacity: 0.7; }
  .sg-field { margin-top: 14px; }
}

/* Lobby — online-now and clickable player links */
.online-count { font-size: 13px; color: var(--muted); font-weight: 400; margin-left: 6px; }
.online-list { list-style: none; padding: 0; margin-bottom: 16px; }
.online-list li { padding: 4px 0; font-size: 14px; }
.profile-link {
  background: transparent;
  color: inherit;
  border: none;
  padding: 0;
  font: inherit;
  font-weight: inherit;
  cursor: pointer;
  text-decoration: underline dotted transparent;
  transition: text-decoration-color 80ms;
}
.profile-link:hover { background: transparent; filter: none; color: var(--accent); text-decoration-color: var(--accent); }
.user-bar-label { color: white; font-size: 14px; }
.user-bar-label strong { color: white; }

/* Profile modal */
.profile-modal { max-width: 540px; }
.profile-top {
  display: flex;
  gap: 24px;
  flex-wrap: wrap;
  background: white;
  border: 1px solid #d6cfb4;
  border-radius: 8px;
  padding: 10px 14px;
  margin-bottom: 14px;
  font-size: 14px;
}
.profile-top > div { display: flex; align-items: baseline; gap: 6px; }
.profile-top .k { color: var(--muted); text-transform: uppercase; font-size: 11px; letter-spacing: 0.5px; margin-right: 4px; }
.profile-top .tier-emoji-inline { font-size: 16px; line-height: 1; }
.profile-top small { color: var(--muted); margin-left: 4px; }

/* Compact dual tier badge (used in the header, status bar, player
   chips, invite list). Shows the human-rating tier emoji and the
   bot-rating tier emoji side by side with a thin separator. Hover
   reveals the exact numbers via the title attribute. The whole badge
   is small so it fits anywhere a single number used to. */
.tier-dual {
  display: inline-flex;
  align-items: center;
  gap: 1px;
  font-size: 14px;
  line-height: 1;
  vertical-align: middle;
  cursor: help;
}
.tier-dual .tier-emoji-h,
.tier-dual .tier-emoji-b {
  display: inline-block;
  line-height: 1;
}
.tier-dual .tier-dual-sep {
  color: var(--muted);
  font-size: 11px;
  opacity: 0.6;
  margin: 0 1px;
}
.profile-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.profile-table td {
  padding: 4px 8px;
  border-bottom: 1px solid #f0e8cf;
}
.profile-table td:first-child { color: var(--ink); }
.profile-table td:last-child  { text-align: right; font-weight: 700; }
.profile-loading { font-size: 12px; color: var(--muted); font-weight: 400; margin-left: 6px; }

/* ==========================================================
   TABLE VIEW
   ========================================================== */

#view-table {
  /* Use the dynamic viewport height on mobile (iOS Safari shrinks the
     visible area when the URL bar appears). Falls back to 100vh elsewhere. */
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  background: #1c2833;
}

/* Status bar — single non-wrapping row. The phase text shrinks/ellipsises
   first, scores stay nailed to the right, action icons compact down. */
.status-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 14px;
  background: linear-gradient(180deg, rgba(0,0,0,0.65), rgba(0,0,0,0.45));
  color: white;
  font-size: 13px;
  flex-shrink: 0;
  flex-wrap: nowrap;
  gap: 10px;
  border-bottom: 1px solid rgba(255,255,255,0.06);
  white-space: nowrap;
  min-width: 0;
}
.status-bar #status-phase {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1 1 auto;
  min-width: 0;
}
.status-bar #status-trump { font-weight: 600; opacity: 0.95; flex: 0 0 auto; }
.status-info, .status-scores {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: nowrap;
  min-width: 0;
}
.status-info { flex: 1 1 auto; min-width: 0; }
.status-scores { flex: 0 0 auto; }
/* #5: all the in-game action icons moved into the ☰ menu, so the top bar
   stays clean (menu + live status + score + chat) on every screen size. We
   hide the relocated buttons here rather than removing them — their handlers
   are reused by the menu, which .click()s / mirrors them. ID selectors + the
   !important keep them hidden regardless of the `hidden` attribute that
   renderTable still toggles for availability. */
.status-bar #btn-stepaway,
.status-bar #btn-last-trick,
.status-bar #btn-tracker,
.status-bar #btn-meld-hint,
.status-bar #btn-history,
.status-bar #btn-settings,
.status-bar #btn-settings-compact,
.status-bar #btn-leave-table { display: none !important; }
/* Action icons in the right side of the status bar (chat, history,
   settings gear, etc) — bump glyph size so they read as buttons
   rather than tiny text. Was too small on desktop, especially the
   gear which is now the primary settings entry. */
/* Desktop status-bar action icons — gear, history, chat, etc.
   These were rendering small because the base .ghost button rules
   shrunk them. Bumped to a comfortable 44x44 tap target with a
   26px glyph + subtle tint background so they read as buttons.

   IMPORTANT: scoped to min-width:600px (desktop) so the rule does
   NOT override `.wide-only { display: none !important; }` or the
   `hidden` attribute on mobile. Earlier the unscoped `display:
   inline-flex !important` was forcing all five wide-only icons to
   show on phones — that's how the duplicate gear and "tracker
   showing despite being off" bug crept in. */
@media (min-width: 600px) {
  .status-bar .status-scores button.ghost {
    font-size: 26px;
    padding: 0;
    line-height: 1;
    width: 44px;
    height: 44px;
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 12px;
    background: rgba(255,255,255,0.07);
    color: white;
  }
  .status-bar .status-scores button.ghost:hover {
    background: rgba(255,255,255,0.14);
  }
  /* The `hidden` attribute must still win, even with our extra
     selector specificity — keep it explicit. */
  .status-bar .status-scores button.ghost[hidden] { display: none; }
}
.status-scores #status-game { font-variant-numeric: tabular-nums; }
.status-scores #status-game strong { color: var(--gold); }
.status-scores #status-game .dash { opacity: 0.5; padding: 0 2px; }
.status-scores #status-game .target { opacity: 0.55; font-size: 11px; margin-left: 4px; }
.status-bar .muted { opacity: 0.7; font-weight: 500; }
#status-trump.suit-red { color: #ff7b8c; }
#status-trump.suit-black { color: white; }

/* Compact "bid progress" pill during play. */
.bid-pill {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 2px 10px;
  background: rgba(255,255,255,0.08);
  border-radius: 999px;
  font-weight: 600;
}
.bid-pill .suit-red  { color: #ff7b8c; font-size: 16px; }
.bid-pill .suit-black { color: white;  font-size: 16px; }
.bid-pill strong { color: var(--gold); }
.bid-pill strong.ok { color: #6ee7a7; }
/* "SET" indicator — bidder team is mathematically eliminated. Bright
   red + slight pulse so a player notices the moment the lock is in. */
.bid-pill strong.set {
  color: #fff;
  background: var(--danger);
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11px;
  letter-spacing: 1px;
  animation: pulse 1.4s ease-in-out infinite;
}

/* Private-table invite chip in the status bar. */
.invite-chip {
  background: rgba(212, 169, 42, 0.18);
  color: var(--gold);
  border: 1px solid rgba(212, 169, 42, 0.4);
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 12px;
  cursor: pointer;
  font-family: var(--font-ui);
  letter-spacing: 0.5px;
}
.invite-chip:hover:not(:disabled) {
  background: rgba(212, 169, 42, 0.28);
  filter: none;
}
.invite-chip strong { color: #fde9a6; font-weight: 700; }

/* House-rule chip in the status bar (e.g. "Double pinochle = 300"). */
.rule-chip {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 12px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.15);
  color: #e9d6a8;
}
.rule-chip strong { color: #fff; font-weight: 700; }

/* Lobby: create-table extras + join-by-code. */
#form-create-table .form-row {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
}
#form-create-table .form-inline {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--ink);
  white-space: nowrap;
}
#form-create-table select {
  padding: 8px 10px;
  border-radius: 6px;
  border: 1px solid rgba(0,0,0,0.15);
  background: white;
  font-size: 13px;
  flex: 1 1 auto;
  min-width: 0;
}
.join-code-form {
  display: flex !important;
  flex-direction: row !important;
  gap: 8px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px dashed rgba(0,0,0,0.08);
}
.join-code-form input { flex: 1 1 auto; text-transform: uppercase; letter-spacing: 2px; }
.join-code-form button { flex: 0 0 auto; }

/* Tier badges on table list. */
.tier-badge {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.4px;
  margin-left: 6px;
  vertical-align: middle;
}
.tier-starting { background: #e5e7eb; color: #374151; }
.tier-good     { background: #dbeafe; color: #1e3a8a; }
.tier-advanced { background: #fef3c7; color: #92400e; }
.tier-pro      { background: linear-gradient(135deg, #d4a92a, #c08810); color: #1f1408; }
.tier-pirrotta { background: linear-gradient(135deg, #6b3aa0, #4a2570); color: #fff; }
/* Custom template badge — gold-ish to set it apart from the built-in
   presets. Tooltip carries the rule summary so other players can hover
   to see what they're agreeing to. */
.tier-custom   { background: linear-gradient(135deg, #c08810, #8e5b0c); color: #fff; cursor: help; }
/* Multi-tier badge: shows a string of tier emojis (e.g. "🃏🎩")
   so the lobby reads as "this table allows Good or Advanced players."
   Hover for the spelled-out list. */
.tier-multi {
  background: rgba(0,0,0,0.06);
  color: var(--ink);
  font-size: 13px;
  letter-spacing: 0;
  padding: 1px 6px;
}

/* Leave-choice modal — surfaced when a seated player clicks Leave mid-game */
.leave-choice-modal { max-width: 460px; }
.leave-choice-intro { color: var(--muted); font-size: 13px; margin: 0 0 14px; }
.leave-choice-options { display: flex; flex-direction: column; gap: 10px; }
.leave-choice-option {
  display: block;
  width: 100%;
  text-align: left;
  background: white;
  border: 1px solid var(--paper-border);
  border-radius: 12px;
  padding: 14px 16px;
  cursor: pointer;
  color: var(--ink);
  font-family: var(--font-ui);
  transition: border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease;
  box-shadow: none;
}
.leave-choice-option:hover:not(:disabled) {
  border-color: var(--accent);
  box-shadow: var(--shadow-sm);
  filter: none;
}
.leave-choice-option .lc-title {
  font-weight: 700;
  font-size: 15px;
  margin-bottom: 4px;
  color: var(--ink);
}
.leave-choice-option .lc-body {
  font-size: 13px;
  color: var(--muted);
  line-height: 1.5;
  font-weight: 400;
}

/* End-of-game play-again vote */
.play-again-prompt {
  font-weight: 700;
  font-size: 15px;
  margin-right: 6px;
}
.play-again-tally {
  display: inline-flex;
  gap: 6px;
  margin-left: 12px;
  flex-wrap: wrap;
}
.vote-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
}
.vote-chip.yes { background: rgba(110, 231, 167, 0.18); border-color: rgba(110, 231, 167, 0.4); color: #b9f1d2; }
.vote-chip.no  { background: rgba(212, 83, 63, 0.22); border-color: rgba(212, 83, 63, 0.45); color: #ffc3b8; }
.vote-chip.pending { opacity: 0.7; }
.vote-chip.bot { opacity: 0.55; }
/* The chip colors above are tuned for the dark action-bar background. When
   chips render inside a tan modal (.overlay .modal), pale green/pink text
   becomes nearly invisible — bump to high-contrast solid colors instead. */
.overlay .modal .vote-chip { background: #f3eed8; border-color: #d6cfb4; color: var(--ink); }
.overlay .modal .vote-chip.yes { background: #d8f5d2; border-color: #5fbf6c; color: #0f4d22; }
.overlay .modal .vote-chip.no  { background: #fadcd6; border-color: #d4533f; color: #6b1320; }
.overlay .modal .vote-chip.bot { background: #efe7cc; color: #6c5b1d; }

/* The playing surface */
.table-surface {
  flex: 1 1 0;
  min-height: 0;   /* let the grid shrink so action-bar stays on-screen */
  position: relative;
  /* Derive the played-trick card size from the responsive --card-w/h times
     the user's "oversized cards" slider. Computed HERE (on the parent) so
     .trick-area can override its own --card-w/h from it without a CSS
     variable cycle. */
  --trick-card-w: calc(var(--card-w) * var(--trick-card-scale, 1));
  --trick-card-h: calc(var(--card-h) * var(--trick-card-scale, 1));
  display: grid;
  grid-template-columns: minmax(140px, 1fr) minmax(360px, 2.5fr) minmax(140px, 1fr);
  grid-template-rows: minmax(90px, auto) 1fr minmax(80px, auto) minmax(40px, auto);
  gap: 8px;
  padding: 20px;
  background:
    radial-gradient(ellipse at center, var(--felt) 0%, var(--felt-dark) 80%),
    repeating-linear-gradient(45deg, rgba(0,0,0,0.03) 0 2px, transparent 2px 4px);
  border: 8px solid;
  border-image: linear-gradient(180deg, #4d3320, var(--felt-rim) 50%, #2a1a0e) 1;
  border-radius: 18px;
  margin: 12px;
  box-shadow:
    inset 0 0 80px rgba(0,0,0,0.45),
    inset 0 2px 6px rgba(255,255,255,0.04),
    0 12px 32px rgba(0,0,0,0.55);
  overflow: hidden;
}

/* Subtle pinochle watermark — Q♠ + J♦ pair printed faintly at table
   centre. Pointer-events:none so it never blocks clicks; positioned
   under the play grid via z-index:0. Opacity tuned to be perceptible
   on the felt-green background (0.08 was invisible) while staying
   well in the "decorative" range so it never competes with cards. */
.table-surface::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: url('/pinochle-watermark.svg');
  background-repeat: no-repeat;
  background-position: center 50%;
  background-size: min(60%, 460px) auto;
  opacity: 0.22;
  color: rgba(255,255,255,0.95); /* drives the SVG's currentColor stroke */
  pointer-events: none;
  z-index: 0;
}
/* Ensure all real grid children render ON TOP of the watermark. */
.table-surface > * { position: relative; z-index: 1; }

/* Seat positions on the grid — hand above tag for the local player. */
.seat-top    { grid-column: 2; grid-row: 1; }
.seat-left   { grid-column: 1; grid-row: 2; }
.seat-right  { grid-column: 3; grid-row: 2; }
.trick-area  { grid-column: 2; grid-row: 2; }
.your-hand   { grid-column: 1 / -1; grid-row: 3; }
/* The bottom (your-self) seat spans the full row so the badges have room. */
.seat-bottom { grid-column: 1 / -1; grid-row: 4; }

/* Seat area: opponent card-backs above, name/badges tag below. The order
   keeps the tag out of the central playing surface. */
.seat-area {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 6px;
  position: relative;
  min-width: 0;
}
.seat-top    { justify-content: flex-start; }
.seat-left   { justify-content: center; align-items: flex-start; }
.seat-right  { justify-content: center; align-items: flex-end; }
.seat-bottom { justify-content: flex-start; align-items: center; }

.seat-tag {
  background: rgba(15,20,25,0.55);
  border: 1px solid rgba(255,255,255,0.06);
  color: white;
  border-radius: 999px;
  padding: 6px 12px;
  font-size: 13px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  row-gap: 4px;
  max-width: 100%;
  box-shadow: var(--shadow-sm);
  position: relative;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  transition: box-shadow 140ms ease, background 140ms ease;
}
.seat-tag .seat-emoji { font-size: 18px; line-height: 1; }
.seat-tag .seat-mute { font-size: 13px; }
.seat-tag .name { white-space: nowrap; }
.seat-tag.to-act {
  box-shadow: 0 0 0 2px var(--gold), 0 0 28px rgba(212,169,42,0.55);
  background: rgba(20, 30, 40, 0.85);
}
.seat-tag.empty {
  /* A plain box, not the occupied seat's full pill — square-ish corners
     read as "an empty slot here". The inner fill is a touch darker than
     the felt so the vacancy is visible against the table (#6). */
  background: rgba(0,0,0,0.22);
  border: 1px dashed rgba(255,255,255,0.3);
  border-radius: 8px;
  box-shadow: inset 0 1px 4px rgba(0,0,0,0.25);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 8px 10px;
}
.seat-tag.empty .empty-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.15;
  gap: 1px;
}
.seat-tag.empty .empty-stack .seat-pos {
  font-weight: 700;
  letter-spacing: 0.2px;
  font-size: 14px;
  white-space: nowrap;       /* "West Seat" stays one line everywhere */
}
.seat-tag.empty .empty-stack .seat-state {
  font-size: 11px;
  opacity: 0.65;
  text-transform: lowercase;
}
.seat-tag.empty .empty-actions {
  flex-direction: row;       /* icons sit side-by-side */
  gap: 6px;
  margin-left: 0;
}
/* Text-labeled action button (Sit / Invite / Bot). Plain words instead
   of emoji pucks — clearer affordance, especially in the narrow side-
   seat columns on mobile where the puck's clickable area got clipped
   off-screen. */
.seat-tag.empty .seat-action-btn {
  padding: 5px 12px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.2px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(180deg, var(--accent-2) 0%, var(--accent) 100%);
  border: 1px solid rgba(0,0,0,0.1);
  color: white;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  min-height: 26px;
  white-space: nowrap;
  /* Treat these as tap targets, not gesture surfaces: kills the ~300ms
     double-tap-zoom wait and the tap-vs-scroll ambiguity that makes a tap
     "not register" on iOS. The side (left/right) seats stack two small
     buttons in a cramped 72px column — exactly where that ambiguity bites
     hardest — which is why those, not the roomy top row, felt unclickable (#2). */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  /* Sit above the tag's inset box-shadow / dashed-border layer so nothing
     in the same stacking context can shadow the hit region. */
  position: relative;
  z-index: 2;
}
.seat-tag.empty .seat-action-btn:hover {
  filter: brightness(1.05);
  transform: translateY(-1px);
}
.seat-tag.you {
  background: linear-gradient(180deg, rgba(20,84,59,0.85), rgba(14,61,42,0.85));
  border: 1px solid rgba(212,169,42,0.55);
}
.seat-tag.has-passed { opacity: 0.45; filter: grayscale(40%); }
.seat-tag.clickable-tag { cursor: pointer; }
.seat-tag.clickable-tag:hover { box-shadow: 0 0 0 2px rgba(255,255,255,0.35), var(--shadow); }
.seat-tag .name   { font-weight: 700; letter-spacing: 0.3px; }
.seat-tag .badge {
  display: inline-block;
  font-size: 11px;
  padding: 1px 6px;
  border-radius: 10px;
  background: rgba(255,255,255,0.15);
}
.seat-tag .badge.bidder { background: var(--gold); color: #15202b; }
.seat-tag .badge.dealer { background: rgba(255,255,255,0.25); }
.seat-tag .badge.bot    { background: #475569; }
.seat-tag .badge.passed { background: rgba(0,0,0,0.5); color: #ccc; }
.seat-tag .badge.disconnected { background: #b00020; color: white; }
.seat-tag .badge.timeouts { background: #5e3b00; color: #ffd966; }
.seat-tag .badge.away-badge { background: var(--gold); color: #2a1a05; font-weight: 700; }
/* A stepped-away seat gets a soft gold ring so it reads at a glance. */
.seat-tag.away { box-shadow: 0 0 0 2px rgba(212,169,42,0.55); }
/* Step-away toggle button: unmistakably "on" when active. */
#btn-stepaway.active {
  background: var(--gold);
  color: #2a1a05;
  box-shadow: 0 0 0 2px rgba(212,169,42,0.6), 0 0 14px rgba(212,169,42,0.45);
  animation: pulse 1.6s ease-in-out infinite;
}
.seat-tag .badge.timer { background: #1f2937; color: #ffd966; font-variant-numeric: tabular-nums; }
.seat-tag .badge.timer.urgent { background: var(--accent); color: white; animation: pulse 1s ease-in-out infinite; }
.seat-tag .badge.kick-vote { background: var(--accent); color: white; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.65; } }
.seat-tag .empty-actions {
  display: flex;
  gap: 6px;
  margin-left: 6px;
  position: relative;
  /* Establish a new stacking context (isolation) so nothing inside the
     seat-tag can be reordered against siblings outside it. Z-index high
     enough to clear all in-table overlays (chat-toast is 110, watermark is
     0, trick cross uses 1). */
  isolation: isolate;
  z-index: 200;
}
.seat-tag .empty-actions button { padding: 4px 8px; font-size: 11px; }
/* Lift the entire empty seat-tag too — if a sibling row (e.g. the trump
   medallion row, .bidder-trump-flag, or the bidding-current-big pill in
   .trick-center) extends out of its own grid cell visually, the tag's
   stacking context must still win the hit-test. */
.seat-area .seat-tag.empty {
  position: relative;
  z-index: 200;
  pointer-events: auto;
}
/* Bulletproof empty-seat action buttons — never let an overlay, sibling, or
   parent CSS regression eat the click again. position+z-index lifts above any
   overlapping element; pointer-events:auto undoes any inherited "none";
   touch-action:manipulation kills the 300ms tap delay on iOS Safari.
   Z-index pushed to 9999 (above chat-toasts at 110) since the wide-landscape
   regression suggests some in-table element was layering above 10. */
.seat-tag .seat-action-btn {
  position: relative;
  z-index: 9999;
  pointer-events: auto !important;
  cursor: pointer;
  touch-action: manipulation;
}

/* Mini card-backs (opponents' hands) */
.cards-row, .cards-col {
  display: flex;
  pointer-events: none;
}
.cards-row { flex-direction: row; }
.cards-col { flex-direction: column; }
.cards-row .card-back  { margin-left: -16px; }
.cards-row .card-back:first-child { margin-left: 0; }

/* Opponent avatar (default opponent-display mode). */
.opp-avatar {
  font-size: 44px;
  line-height: 1;
  filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 50px;
}
.seat-area .opp-avatar { margin-bottom: 4px; }

/* Row that holds the bidder trump medallion next to its seat tag. */
.seat-tag-row {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
}

/* Big trump medallion shown next to the player who took the bid. */
.bidder-trump-flag {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 14px;
  background: white;
  border-radius: 999px;
  font-weight: 800;
  font-size: 14px;
  box-shadow: 0 0 0 2px var(--gold), var(--shadow);
  color: var(--ink);
}
.bidder-trump-flag .trump-flag-bid {
  font-family: var(--font-ui);
  font-size: 18px;
  font-weight: 800;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.bidder-trump-flag .trump-flag-suit {
  font-size: 32px;
  line-height: 1;
}
.bidder-trump-flag.red  .trump-flag-suit { color: var(--card-red); }
.bidder-trump-flag.black .trump-flag-suit { color: var(--card-black); }
@media (max-width: 599px) {
  .bidder-trump-flag { padding: 3px 10px; gap: 6px; }
  .bidder-trump-flag .trump-flag-bid { font-size: 15px; }
  .bidder-trump-flag .trump-flag-suit { font-size: 26px; }
}

.card-back {
  width: var(--card-back-w);
  height: var(--card-back-h);
  background:
    repeating-linear-gradient(45deg, #7a2230 0 5px, #5a1620 5px 10px),
    radial-gradient(circle at 30% 30%, rgba(255,255,255,0.05), transparent 60%);
  border: 1px solid #2b070b;
  border-radius: 4px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.05);
  flex-shrink: 0;
}

/* Trick area: the 4-card cross is absolutely positioned around the geometric
   centre of the trick zone, with a tight 4px gap between adjacent cards.
   This keeps the bottom card off your hand and gives every played card a
   clear viewing zone. trick-center holds non-play content (bid display,
   meld totals, sweep banner); during play those four slot cards cover the
   centre so it's effectively invisible. */
.trick-area {
  position: relative;
  /* Floor for the cross, sized from the UNSCALED card height. We deliberately
     do NOT scale this with --trick-card-scale: a big "oversized cards" setting
     would otherwise reserve a huge minimum here and squeeze the hand off the
     bottom of the felt. The row is 1fr, so it still expands to fit big trick
     cards when there's vertical room; the floor just protects the cross on
     normal screens without stealing the hand's space. */
  min-height: calc(var(--card-h) * 2 + var(--trick-cross-gap) * 2 + 12px);
}
.trick-slot {
  position: absolute;
  /* Adopt the (possibly scaled) trick-card size here — the slot geometry
     (.slot-top/left/right/bottom positions, below) and the .card elements
     both read --card-w/h, so the whole played cross grows together. Scoped
     to the slot (not .trick-area) so the area's min-height above stays
     unscaled. --trick-card-w/h come from .table-surface → no var cycle. */
  --card-w: var(--trick-card-w);
  --card-h: var(--trick-card-h);
  width: var(--card-w);
  height: var(--card-h);
  display: flex;
  align-items: center;
  justify-content: center;
}
/* During the 16-card passing hand (two-row layout) there's no played cross —
   just the "Passing 4 cards" note — so drop the tall floor and hand the
   vertical space to the hand, whose bottom row was getting clipped by the
   felt's overflow:hidden. */
.table-surface:has(.your-hand.hand-two-rows) .trick-area {
  min-height: calc(var(--card-h) + var(--trick-cross-gap));
}
.slot-top {
  /* --trick-spread-ns pushes the N/S cards out toward the top/bottom edges (#8). */
  top: calc(50% - var(--card-h) - var(--trick-cross-gap) - var(--trick-spread-ns, 0px));
  left: 50%;
  transform: translateX(-50%);
}
.slot-bottom {
  top: calc(50% + var(--trick-cross-gap) + var(--trick-spread-ns, 0px));
  left: 50%;
  transform: translateX(-50%);
}
.slot-left {
  top: calc(50% - var(--card-h) / 2);
  /* --trick-spread pushes the side cards out toward the table edges (#2). */
  left: calc(50% - var(--card-w) - var(--trick-cross-gap) - var(--trick-spread, 0px));
}
.slot-right {
  top: calc(50% - var(--card-h) / 2);
  left: calc(50% + var(--trick-cross-gap) + var(--trick-spread, 0px));
}
.trick-center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: white;
  text-align: center;
  max-width: 280px;
}

/* ===== Classic playing-card design =====
   Stacked rank+suit in top-left and (rotated) bottom-right corners; large
   center glyph — the suit pip for A/10/9, or the rank letter for J/Q/K. */
.card {
  width: var(--card-w);
  height: var(--card-h);
  background:
    radial-gradient(110% 90% at 20% 0%, #ffffff 0%, var(--card-bg) 55%, #f4ecd5 100%);
  border-radius: 8px;
  position: relative;
  border: 1px solid rgba(15,20,25,0.18);
  box-shadow: 0 2px 5px rgba(15,20,25,0.22), inset 0 1px 0 rgba(255,255,255,0.6);
  user-select: none;
  font-family: 'Inter', 'Helvetica Neue', Arial, sans-serif;
  flex-shrink: 0;
  transition: transform 140ms cubic-bezier(.2,.7,.3,1), box-shadow 140ms ease;
  line-height: 1;
}
.card.trump { box-shadow: 0 0 0 2px var(--gold), 0 2px 5px rgba(15,20,25,0.25); }
.card .corner {
  position: absolute;
  width: 26px;
  text-align: center;
  line-height: 1;
}
.card .corner.tl { top: 6px; left: 5px; }
.card .corner.br { bottom: 6px; right: 5px; transform: rotate(180deg); }
.card .corner .rank { font-size: 22px; font-weight: 800; }
.card .corner .suit { font-size: 24px; margin-top: 2px; line-height: 1; }
.card .center {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 56px;
  font-weight: 800;
  letter-spacing: -1px;
}
.card.red   { color: var(--card-red); }
.card.black { color: var(--card-black); }
.card.selected { transform: translateY(-14px); box-shadow: 0 0 0 2px var(--gold), 0 16px 22px rgba(15,20,25,0.4); }
/* Pass-phase selection: distinct teal/cyan ring so it doesn't read as
   the gold trump highlight. A pronounced lift + z-index so all four
   picked cards clearly "pull out of the hand."
   HISTORY: an earlier version dropped z-index because a single gold
   play-select that drew over its right neighbour read as "overtaking
   everything." But for PASSING the opposite was the real bug — with
   only a 14px lift and no z-index, a selected card with un-selected
   neighbours to its right got buried under the fan, so users saw
   "3 cards up, 1 down" even though all four were selected (the heavy
   overlap swallowed the small lift). Pulling four cards halfway out of
   the hand is exactly the mental model we want here.
   NO z-index: an earlier version raised picked cards above their
   neighbours with z-index:6, but that made a selected card paint OVER
   the (normally on-top) card to its right — players read it as the pick
   "covering the cards behind it." The lift alone (-30px here, more on the
   back row below) is enough to show which cards are picked while leaving
   the natural fan layering intact, so a selected card never covers a
   neighbour. */
.card.passing {
  transform: translateY(-30px);
  box-shadow: 0 0 0 2px #2e90c4, 0 16px 22px rgba(15,20,25,0.4);
}
/* Pre-selected ("ready") card: lifted, blue ring, animated pulse so the user
   sees the card waiting to fire when their turn arrives. The preview-card
   variant carries its own bigger "READY" pill (see .preview-card.ready
   above) so we deliberately exclude it here — otherwise both badges
   render and clobber each other in the settings preview. */
.card.ready:not(.preview-card) {
  /* Lifts more than the old -10px so the "ready" badge clears the card and
     reads cleanly, but deliberately less than the -30px pass lift so the two
     states stay visually distinct. EXCLUDES .preview-card: the tiny settings
     preview card also carries .card + .ready, and this -18px would override
     its intended -6px lift (same specificity, this rule is later) and shove
     its READY badge out of the pill's overflow:hidden box. */
  transform: translateY(-18px);
  box-shadow: 0 0 0 2px #3b82f6, 0 12px 18px rgba(15,20,25,0.35);
  animation: card-ready-pulse 1.4s ease-in-out infinite;
}
.card.ready:not(.preview-card)::after {
  content: 'ready';
  position: absolute;
  top: -10px;
  right: -6px;
  background: #3b82f6;
  color: white;
  font-size: 9px;
  font-weight: 800;
  padding: 2px 6px;
  border-radius: 999px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  z-index: 5;
}
@keyframes card-ready-pulse {
  0%, 100% { box-shadow: 0 0 0 2px #3b82f6, 0 12px 18px rgba(15,20,25,0.35); }
  50%      { box-shadow: 0 0 0 3px #60a5fa, 0 14px 22px rgba(15,20,25,0.4); }
}
/* "Sure?" — a card that WAS readied but whose trick situation flipped before
   our turn (e.g. partner surprise-trumped). We don't auto-play it; instead we
   flag it amber so the player notices and re-confirms. Same lift as ready but a
   distinct gold colour + "sure?" badge. */
.card.needs-confirm:not(.preview-card) {
  transform: translateY(-18px);
  box-shadow: 0 0 0 2px #d4a92a, 0 12px 18px rgba(15,20,25,0.4);
  animation: card-confirm-pulse 0.9s ease-in-out infinite;
}
.card.needs-confirm:not(.preview-card)::after {
  content: 'sure?';
  position: absolute;
  top: -10px;
  right: -6px;
  background: #d4a92a;
  color: #1a1208;
  font-size: 9px;
  font-weight: 800;
  padding: 2px 6px;
  border-radius: 999px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  z-index: 5;
}
@keyframes card-confirm-pulse {
  0%, 100% { box-shadow: 0 0 0 2px #d4a92a, 0 12px 18px rgba(15,20,25,0.4); }
  50%      { box-shadow: 0 0 0 4px #f1c34b, 0 14px 22px rgba(15,20,25,0.45); }
}
.card.illegal { filter: grayscale(60%) brightness(0.7); cursor: not-allowed !important; }
.card.played-card { width: var(--card-w); height: var(--card-h); }
.card.faded { opacity: 0.45; filter: grayscale(20%); }

/* ===== Card play animations (opt-in setting; transform/opacity only so
   they're GPU-cheap on older phones). A played card flies in from its
   player's side of the table; when the trick resolves, all four sweep off
   toward the winner. Distances are intentionally modest so they read fast. */
.card.played-card.fly-from-top    { animation: pp-fly-top    220ms cubic-bezier(.2,.7,.3,1) both; }
.card.played-card.fly-from-bottom { animation: pp-fly-bottom 220ms cubic-bezier(.2,.7,.3,1) both; }
.card.played-card.fly-from-left   { animation: pp-fly-left   220ms cubic-bezier(.2,.7,.3,1) both; }
.card.played-card.fly-from-right  { animation: pp-fly-right  220ms cubic-bezier(.2,.7,.3,1) both; }
@keyframes pp-fly-top    { from { opacity: 0; transform: translateY(-64px) scale(0.9); } to { opacity: 1; transform: none; } }
@keyframes pp-fly-bottom { from { opacity: 0; transform: translateY(64px)  scale(0.9); } to { opacity: 1; transform: none; } }
@keyframes pp-fly-left   { from { opacity: 0; transform: translateX(-72px) scale(0.9); } to { opacity: 1; transform: none; } }
@keyframes pp-fly-right  { from { opacity: 0; transform: translateX(72px)  scale(0.9); } to { opacity: 1; transform: none; } }

/* The trick-resolve sweep (cards fly onto the winner's seat icon) is now
   JS-driven (sweepTrickToWinner) so it can target the actual seat position —
   see app.js. The old fixed-direction CSS sweep was replaced. */

/* Card-backs gliding from a passer to their partner during the passing phase
   (#3). Transient fixed-position elements created in JS. */
.card-back.pass-fly {
  position: fixed;
  z-index: 90;
  pointer-events: none;
  opacity: 0.95;
  transition: transform 600ms cubic-bezier(.3,.1,.3,1), opacity 600ms ease-in;
}

/* Hard belt-and-suspenders: never run the fly-in animation for reduce-motion
   users, even if a stale setting says on (the JS gate already covers this, but
   CSS makes it bulletproof). */
@media (prefers-reduced-motion: reduce) {
  .card.played-card { animation: none !important; }
}

/* Mini cards (meld breakdown, trick history). The classic look at full
   size shows top-left + bottom-right rotated corners + a centered suit
   glyph. At 44x62 (mini) that's three overlapping glyphs and the card
   reads as visual noise — the bottom-right rotated corner especially
   fights with the centered suit. Drop the bottom-right corner on minis:
   the top-left corner + center suit is plenty to identify each card. */
.card.mini {
  width: var(--mini-card-w);
  height: var(--mini-card-h);
  border-radius: 4px;
}
.card.mini .corner { width: 13px; }
.card.mini .corner.tl { top: 3px; left: 3px; }
.card.mini .corner.br { display: none; }
.card.mini .corner .rank { font-size: 12px; font-weight: 800; }
.card.mini .corner .suit { font-size: 10px; }
.card.mini .center { font-size: 22px; }

/* TILE — compact rank-over-suit badge for dense displays (meld
   review, trick review, hand-history). Replaces the "mini card with
   tiny corner glyphs" pattern which was unreadable at the
   meld/trick-card sizes. A tile is the same height/width as a mini
   card but uses ONLY the rank + suit glyph centred on the face —
   no face-card art, no centre pip, no corner stack. Two stacked
   lines so both rank and suit pop at small sizes.
   Width/height inherit from --mini-card-w/h so layouts using
   .card.mini styling neighbours don't need to change. */
.card.tile {
  width: var(--mini-card-w);
  height: var(--mini-card-h);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 0;
  background: white;
  border-radius: 6px;
  border: 1px solid rgba(0,0,0,0.18);
  box-shadow: 0 1px 2px rgba(0,0,0,0.08);
  overflow: hidden;
}
.card.tile.red   { color: var(--card-red); }
.card.tile.black { color: var(--card-black); }
.card.tile .tile-rank {
  font-size: 22px;
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.card.tile .tile-suit {
  font-size: 22px;
  line-height: 1;
}
/* Trump tile gets a thin gold band along the top — still readable
   at small sizes, doesn't obscure the rank/suit. */
.card.tile.trump {
  border-color: var(--gold);
  box-shadow: 0 1px 2px rgba(0,0,0,0.08), inset 0 2px 0 var(--gold);
}
/* Suit-break gap matches mini-card so adjacent meld rows align. */
.meld-simple-cards .card.tile { margin-left: -8px; }
.meld-simple-cards .card.tile:first-child { margin-left: 0; }
.meld-simple-cards .card.tile.suit-break { margin-left: 8px; }
.meld-item-cards   .card.tile { margin-left: -8px; }
.meld-item-cards   .card.tile:first-child { margin-left: 0; }
.meld-item-cards   .card.tile.suit-break { margin-left: 8px; }

/* Mobile — slightly smaller tile so a 20-card double-deck meld fan
   wraps cleanly inside the modal. */
@media (max-width: 599px) {
  .card.tile { width: 32px; height: 40px; border-radius: 5px; }
  .card.tile .tile-rank { font-size: 16px; }
  .card.tile .tile-suit { font-size: 16px; }
  .meld-simple-cards .card.tile { margin-left: -10px; }
  .meld-item-cards   .card.tile { margin-left: -10px; }
}

/* Mobile mini cards inside summary / meld / history modals — bigger,
   more legible. The default tablet/mobile mini sizes were dropping
   the rank+centre below readability. These overrides only apply
   inside an .overlay (modal context), so the in-game cards keep
   their existing sizing. */
@media (max-width: 599px) {
  .overlay .card.mini {
    width: 38px;
    height: 54px;
    border-radius: 5px;
  }
  .overlay .card.mini .corner { width: 16px; }
  .overlay .card.mini .corner.tl { top: 3px; left: 4px; }
  .overlay .card.mini .corner .rank { font-size: 13px; font-weight: 800; }
  .overlay .card.mini .corner .suit { font-size: 12px; }
  .overlay .card.mini .center { font-size: 22px; }
}

/* Vector cards render an inline <svg><use href="#cards.svg#..."/></svg>.
   Hide the CSS corner/center pieces, let the SVG fill the card. */
.card.vector { background: transparent; padding: 0; overflow: hidden; }
.card.vector .corner, .card.vector .center { display: none; }
.card.vector svg { display: block; width: 100%; height: 100%; }

/* Big-suit card style — for low-vision players. A small rank chip top-left
   plus a HUGE centered suit symbol filling the rest of the card. Hides the
   classic corner/center markup. */
.card.big-suit .corner, .card.big-suit .center { display: none; }
.card.big-suit {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.card.big-suit .bs-rank {
  position: absolute;
  top: 4px;
  left: 6px;
  font-size: calc(var(--card-w) * 0.32);
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.card.big-suit .bs-suit {
  font-size: calc(var(--card-w) * 0.95);
  line-height: 1;
  /* Slight downward optical correction for ♠/♣/♥/♦ glyphs (they're not
     vertically centered in their em box). */
  transform: translateY(2%);
}
.card.big-suit.mini .bs-rank { font-size: calc(var(--mini-card-w) * 0.34); top: 2px; left: 3px; }
.card.big-suit.mini .bs-suit { font-size: calc(var(--mini-card-w) * 1.0); }

/* Traditional card style — editorial vintage look. The classic
   centre glyph is replaced by a vertical stack: a big italic serif
   rank above a medium suit pip. Reads as a "real" playing card
   without needing any custom art. The TL/BR corner chips stay
   visible so partly-hidden back-row cards are still identifiable. */
.card.traditional .center { display: none; }
.card.traditional {
  position: relative;
}
.card.traditional .trad-stack {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  line-height: 0.95;
  pointer-events: none;
}
.card.traditional .trad-rank {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 700;
  font-size: calc(var(--card-w) * 0.55);
  letter-spacing: -1px;
}
.card.traditional .trad-suit {
  font-size: calc(var(--card-w) * 0.42);
  margin-top: 2px;
}
/* The TL/BR corners get a slight upgrade so they read as "Bicycle"
   chips (bold rank, suit underneath) instead of the tiny classic
   corner. Bumping them keeps the back-row tops legible. */
.card.traditional .corner .rank { font-size: calc(var(--card-w) * 0.26); font-weight: 800; }
.card.traditional .corner .suit { font-size: calc(var(--card-w) * 0.22); }
.card.traditional.mini .trad-rank { font-size: calc(var(--mini-card-w) * 0.55); }
.card.traditional.mini .trad-suit { font-size: calc(var(--mini-card-w) * 0.42); }
/* Trump highlight ring still works since it's on the outer .card */

/* ==========================================================
   "Blocks" card style (v0.20.5) — a deliberately non-card look.
   Each card is a flat four-colour tile: a solid suit-coloured band
   down the left edge carrying the rank (stacked over its suit
   glyph), sized to stay readable inside the ~33px strip that shows
   when a hand is fanned, plus a big faded suit watermark filling
   the body when the card is unobscured.

   Four-colour suits — spades slate, hearts crimson, diamonds blue,
   clubs green — let a player read their whole hand by colour alone,
   which is also a colour-blind-friendlier scheme than red/black
   (the four hues stay distinct under the common deuteranopia /
   protanopia palettes, and the glyph is always present as a
   non-colour backstop).
   ========================================================== */
.card.blocks {
  /* Override the skeuomorphic gradient + radius from the base .card.
     NOTE: no `overflow: hidden` — that clipped the "ready" badge
     (which sits at top:-10px/right:-6px outside the card). The
     watermark + band are kept inside the rounded corners by rounding
     the children themselves instead. */
  background: var(--blk-tint, #eceef1);
  border-radius: 4px;
  border: 1px solid rgba(15,20,25,0.22);
  box-shadow: 0 1px 3px rgba(15,20,25,0.22);
  color: var(--blk-color, #1f2937);
}
.card.blocks .corner,
.card.blocks .center { display: none; }
.card.blocks .blk-band {
  position: absolute;
  top: 0; left: 0; bottom: 0;
  /* ~34px keeps the band inside the visible fan strip on desktop;
     scales down with the card on smaller breakpoints. */
  width: calc(var(--card-w) * 0.34);
  background: var(--blk-color, #1f2937);
  color: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding-top: calc(var(--card-h) * 0.06);
  gap: 2px;
  /* Match the card's left corners now that the card no longer clips. */
  border-radius: 3px 0 0 3px;
}
.card.blocks .blk-rank {
  font-size: calc(var(--card-w) * 0.26);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.5px;
}
.card.blocks .blk-suit {
  font-size: calc(var(--card-w) * 0.2);
  line-height: 1;
}
.card.blocks .blk-fill {
  position: absolute;
  top: 0; bottom: 0; right: 0;
  left: calc(var(--card-w) * 0.34);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(var(--card-w) * 0.62);
  color: var(--blk-color, #1f2937);
  opacity: 0.18;
  /* Keep the watermark off the rounded right corners (card no longer
     clips). overflow:hidden here is safe — the badge/ring live on the
     parent .card, not inside .blk-fill. */
  overflow: hidden;
  border-radius: 0 3px 3px 0;
}

/* Four-colour suit mapping — only in blocks mode. --blk-color is the
   band + watermark colour; --blk-tint is the body wash behind it.
   v1.2.21 (#5): clubs moved from forest green (#15803d, nearly identical
   to the "made" green) to TEAL, and hearts from a brick red close to the
   coral "set" colour to a clearer ROSE-crimson. Now the suit tints can't
   be mistaken for the win/loss result colours on the meld + summary
   screens, while still reading as the same four distinct hues. */
.card.blocks.suit-S { --blk-color: #28323f; --blk-tint: #eceef1; }
.card.blocks.suit-H { --blk-color: #c81d4a; --blk-tint: #fce8ee; }
.card.blocks.suit-D { --blk-color: #2563eb; --blk-tint: #e9effd; }
.card.blocks.suit-C { --blk-color: #0d9488; --blk-tint: #e0f2f0; }

/* Colour-blind Blocks (#6) — same tiles, an Okabe-Ito palette chosen to
   stay distinguishable under deuteranopia / protanopia / tritanopia. The
   four hues (near-black / vermillion / blue / bluish-green) avoid the
   classic red-green trap and also ramp in lightness, and the rank+suit
   glyph is always present as a non-colour backstop. These .card.blocks-cb
   rules sit AFTER the .card.blocks ones so they win the (0,3,0) tie. */
.card.blocks-cb.suit-S { --blk-color: #1a1a1a; --blk-tint: #ededed; }
.card.blocks-cb.suit-H { --blk-color: #d55e00; --blk-tint: #fbe9dd; }
.card.blocks-cb.suit-D { --blk-color: #0072b2; --blk-tint: #e1edf5; }
.card.blocks-cb.suit-C { --blk-color: #009e73; --blk-tint: #e0f3ec; }

/* v1.2.0: extend the four-colour scheme to the SUIT INDICATORS in the
   chrome — the status-bar trump, the bid pill's trump glyph, the
   bidder medallion, and the in-game menu trump chip — so they match
   the cards instead of falling back to red/black. Only applies while
   THIS player is using Blocks (it's a per-account display setting, so
   it never affects other players at the table).
   Two tiers: *-light for white/cream surfaces, *-dark lifted for the
   dark status bar / felt where a slate spade would otherwise vanish. */
body.card-style-blocks {
  --suit-S-light: #28323f; --suit-H-light: #c81d4a; --suit-D-light: #2563eb; --suit-C-light: #0d9488;
  --suit-S-dark:  #cdd5df; --suit-H-dark:  #ff8fae; --suit-D-dark:  #7fb0ff; --suit-C-dark:  #5fd5c6;
}
/* Colour-blind Blocks overrides the suit-colour variables with the
   Okabe-Ito palette. Because the chrome + meld/summary tile rules all read
   these variables, this one block re-themes every suit indicator at once.
   body.card-style-blocks-cb ALSO carries card-style-blocks (added in
   applyCardStyleClass), so it inherits all those rules; this block just
   wins the variable values via source order. */
body.card-style-blocks-cb {
  --suit-S-light: #1a1a1a; --suit-H-light: #d55e00; --suit-D-light: #0072b2; --suit-C-light: #009e73;
  --suit-S-dark:  #d4d4d4; --suit-H-dark:  #ffa766; --suit-D-dark:  #6cb6e6; --suit-C-dark:  #4fd3aa;
}
/* Status bar (dark): trump chip + bid-pill glyph use the lifted tones. */
body.card-style-blocks #status-trump.suit-S,
body.card-style-blocks .bid-pill .suit-S { color: var(--suit-S-dark); }
body.card-style-blocks #status-trump.suit-H,
body.card-style-blocks .bid-pill .suit-H { color: var(--suit-H-dark); }
body.card-style-blocks #status-trump.suit-D,
body.card-style-blocks .bid-pill .suit-D { color: var(--suit-D-dark); }
body.card-style-blocks #status-trump.suit-C,
body.card-style-blocks .bid-pill .suit-C { color: var(--suit-C-dark); }
/* Bidder medallion (white bg) — recolour just the suit glyph. */
body.card-style-blocks .bidder-trump-flag.suit-S .trump-flag-suit { color: var(--suit-S-light); }
body.card-style-blocks .bidder-trump-flag.suit-H .trump-flag-suit { color: var(--suit-H-light); }
body.card-style-blocks .bidder-trump-flag.suit-D .trump-flag-suit { color: var(--suit-D-light); }
body.card-style-blocks .bidder-trump-flag.suit-C .trump-flag-suit { color: var(--suit-C-light); }
/* Menu trump chip (light chip on the cream sheet). */
body.card-style-blocks .menu-trump.suit-S { color: var(--suit-S-light); }
body.card-style-blocks .menu-trump.suit-H { color: var(--suit-H-light); }
body.card-style-blocks .menu-trump.suit-D { color: var(--suit-D-light); }
body.card-style-blocks .menu-trump.suit-C { color: var(--suit-C-light); }

/* v1.2.20: carry the Blocks four-colour scheme into the between-hands
   MELD review and the HAND SUMMARY so those screens match the player's
   cards instead of falling back to red/black. Only the Blocks user sees
   this (a per-account display preference, like the rest of the chrome
   above) — other seats keep the classic look.
     • .card.tile      — the compact meld/summary mini-cards: tinted body
                         + suit-coloured rank/suit, echoing the hand tiles.
     • .meld-bid-trump — the big trump glyph in the meld-modal header.
     • .badge-suit     — the inline trump glyph in the summary bid line.
   Specificity: these are (0,4,0), beating .card.tile.red/.black (0,3,0). */
body.card-style-blocks .card.tile.suit-S { color: var(--suit-S-light); background: #eceef1; border-color: rgba(40,50,63,0.28); }
body.card-style-blocks .card.tile.suit-H { color: var(--suit-H-light); background: #fce8ee; border-color: rgba(200,29,74,0.28); }
body.card-style-blocks .card.tile.suit-D { color: var(--suit-D-light); background: #e9effd; border-color: rgba(37,99,235,0.28); }
body.card-style-blocks .card.tile.suit-C { color: var(--suit-C-light); background: #e0f2f0; border-color: rgba(13,148,136,0.28); }
/* Colour-blind Blocks tile backgrounds (text colour already follows the
   overridden --suit-*-light vars). Source order beats the rules above. */
body.card-style-blocks-cb .card.tile.suit-S { background: #ededed; border-color: rgba(26,26,26,0.30); }
body.card-style-blocks-cb .card.tile.suit-H { background: #fbe9dd; border-color: rgba(213,94,0,0.30); }
body.card-style-blocks-cb .card.tile.suit-D { background: #e1edf5; border-color: rgba(0,114,178,0.30); }
body.card-style-blocks-cb .card.tile.suit-C { background: #e0f3ec; border-color: rgba(0,158,115,0.30); }
/* Trump tile keeps its gold cue — placed AFTER the suit rules so the
   gold border wins the (0,4,0) tie; the inset-gold box-shadow from the
   base .card.tile.trump rule is untouched. */
body.card-style-blocks .card.tile.trump { border-color: var(--gold); }

body.card-style-blocks .meld-bid-trump.suit-S { color: var(--suit-S-light); }
body.card-style-blocks .meld-bid-trump.suit-H { color: var(--suit-H-light); }
body.card-style-blocks .meld-bid-trump.suit-D { color: var(--suit-D-light); }
body.card-style-blocks .meld-bid-trump.suit-C { color: var(--suit-C-light); }

body.card-style-blocks .badge-suit.suit-S { color: var(--suit-S-light); }
body.card-style-blocks .badge-suit.suit-H { color: var(--suit-H-light); }
body.card-style-blocks .badge-suit.suit-D { color: var(--suit-D-light); }
body.card-style-blocks .badge-suit.suit-C { color: var(--suit-C-light); }

/* Trump on blocks. The base .card.trump outer gold ring gets covered
   by the next card when the hand is fanned, so trump was invisible
   mid-hand. Add a gold ring INSIDE the always-visible band so trump
   reads at a glance no matter how tightly the hand is packed. */
.card.blocks.trump .blk-band {
  box-shadow: inset 0 0 0 2.5px var(--gold);
}

/* Fan overlap for blocks, driven by a single --blk-overlap var so
   suit-break cards use the SAME overlap (no widening gap). Blocks
   already separate suits by colour, so the --hand-suit-gap that the
   skeuomorphic styles insert at suit boundaries is redundant here —
   and on a dense double-deck row it was the thing pushing the row
   past the container width (centred, so it clipped on BOTH ends).
     • normal hands: reveal ~50% of each tile ("squares in a row")
     • big double-deck hands (.hand-large): ~58% overlap so 10-12
       tiles per row still fit a phone without the suit-gap. */
body.card-style-blocks .your-hand { --blk-overlap: calc(var(--card-w) * -0.5); }
body.card-style-blocks .your-hand.hand-large { --blk-overlap: calc(var(--card-w) * -0.58); }
body.card-style-blocks .your-hand .card.blocks,
body.card-style-blocks .your-hand .hand-row .card.blocks,
body.card-style-blocks .your-hand .card.blocks.suit-break,
body.card-style-blocks .your-hand .hand-row .card.blocks.suit-break {
  margin-left: var(--blk-overlap);
}
body.card-style-blocks .your-hand .card.blocks:first-child,
body.card-style-blocks .your-hand .hand-row .card.blocks:first-child {
  margin-left: 0;
}

/* Mini (meld modal / trick review) — shrink the band + watermark
   proportionally to --mini-card-w so dense displays stay legible. */
.card.blocks.mini .blk-band { width: calc(var(--mini-card-w) * 0.36); }
.card.blocks.mini .blk-rank { font-size: calc(var(--mini-card-w) * 0.42); }
.card.blocks.mini .blk-suit { font-size: calc(var(--mini-card-w) * 0.34); }
.card.blocks.mini .blk-fill { left: calc(var(--mini-card-w) * 0.36); font-size: calc(var(--mini-card-w) * 0.7); }

/* Settings modal */
.settings-row { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.settings-option {
  display: flex;
  gap: 10px;
  align-items: center;
  padding: 10px 12px;
  background: white;
  border: 2px solid #d6cfb4;
  border-radius: 8px;
  cursor: pointer;
  font-size: 13px;
}
.settings-option:hover { border-color: var(--gold); }
.settings-option.active { border-color: var(--accent); background: #fff6e9; }
.settings-option input[type="radio"] { margin: 0; }

/* Emoji picker — used on signup form and in the Settings overlay. */
.emoji-pick-label { font-size: 12px; color: var(--muted); margin: 4px 0 2px; }
.emoji-picker {
  display: grid;
  grid-template-columns: repeat(11, 1fr);
  gap: 4px;
  margin-bottom: 8px;
}
.emoji-cell {
  background: white;
  border: 1px solid #d6cfb4;
  border-radius: 6px;
  padding: 4px 0;
  font-size: 20px;
  cursor: pointer;
  line-height: 1;
  text-align: center;
}
.emoji-cell:hover { background: #faf3df; }
.emoji-cell.selected { background: var(--accent); border-color: var(--accent); }
.current-emoji { font-size: 20px; margin-left: 6px; }
@media (max-width: 599px) {
  .emoji-picker { grid-template-columns: repeat(8, 1fr); }
  .emoji-cell { font-size: 18px; padding: 3px 0; }
}

/* Minimal current-bid display in the center during bidding. Full bid log is
   in the History modal (📋 button in the status bar). */
.bidding-current-big {
  color: white;
  text-align: center;
  background: linear-gradient(180deg, rgba(0,0,0,0.40), rgba(0,0,0,0.25));
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: var(--radius);
  padding: 14px 20px;
  min-width: 140px;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  box-shadow: var(--shadow-sm);
}
.bidding-cur-num {
  /* Sans-serif for numeric clarity at a glance — the wordmark uses Playfair
     but bid amounts read way better in a clean grotesk. */
  font-family: var(--font-ui);
  font-size: 44px;
  font-weight: 800;
  color: var(--gold);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
.bidding-cur-label {
  font-size: 11px;
  opacity: 0.8;
  margin-top: 2px;
}
.bidding-leader { font-size: 12px; opacity: 0.85; margin-top: 4px; }
/* Jump-bid indicator (#7) — a gold "⚡ Jump bid" pill that appears on the
   centre bidding display when the standing bid leapt past the wild-jump
   threshold. The brief pulse draws the eye to a bold escalation. */
.bidding-jump-badge {
  margin-top: 6px;
  display: inline-block;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  /* Electric violet with white text (#1) — the yellow ⚡ pops on it and it
     reads clearly against the gold bid number; the old gold-on-gold made the
     bolt nearly invisible. */
  color: #fff;
  background: linear-gradient(180deg, #8a6dff, #6a47e6);
  border: 1px solid rgba(255,255,255,0.30);
  padding: 4px 12px;
  border-radius: 999px;
  box-shadow: 0 2px 10px rgba(106,71,230,0.5);
  animation: jump-bid-pulse 1.1s ease-in-out 3;
}
/* Wild jumps (beyond the rules' threshold) — a louder coral pill so the
   "this is a big, penalty-eligible leap" reads at a glance. */
.bidding-jump-badge.wild {
  background: linear-gradient(180deg, #f17a55, var(--accent));
  border-color: rgba(255,255,255,0.35);
  box-shadow: 0 2px 10px rgba(212,83,63,0.5);
}
@keyframes jump-bid-pulse {
  0%, 100% { transform: scale(1);    box-shadow: 0 2px 8px rgba(106,71,230,0.45); }
  50%      { transform: scale(1.1);  box-shadow: 0 3px 14px rgba(106,71,230,0.75); }
}
/* Now an actual button — opens the hand-history modal on the Bidding
   tab. Styled as a small pill so it reads as tappable but doesn't shout. */
button.bidding-history-hint {
  font-size: 11px;
  opacity: 0.7;
  margin-top: 8px;
  background: rgba(255,255,255,0.08);
  color: inherit;
  border: 1px solid rgba(255,255,255,0.18);
  padding: 4px 10px;
  border-radius: 999px;
  box-shadow: none;
  cursor: pointer;
  font-weight: 500;
  letter-spacing: 0.2px;
}
button.bidding-history-hint:hover:not(:disabled) {
  opacity: 1;
  background: rgba(255,255,255,0.14);
  border-color: rgba(255,255,255,0.3);
  transform: translateY(-1px);
}

/* Suit glyph on bidder badge */
.seat-tag .badge-suit {
  display: inline-block;
  font-weight: 700;
  margin-left: 2px;
}
.seat-tag .badge-suit.red   { color: #ff7b8c; }
.seat-tag .badge-suit.black { color: #15202b; }

/* Hand history modal — tabbed pane (Bidding / Tricks). */
.tabs {
  display: flex;
  gap: 0;
  border-bottom: 2px solid #d6cfb4;
  margin-bottom: 12px;
}
.tabs .tab {
  background: transparent;
  color: var(--ink);
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
  /* So an <a class="tab"> (e.g. the Admin tab → /admin.html) matches the
     <button> tabs: no underline, pointer cursor, same box. */
  text-decoration: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  /* v0.20.1: roomier — was 8/16, now 12/20 so tabs feel like real
     buttons not a thin strip of underlined text. Bonus: bigger tap
     target on phones. */
  padding: 12px 20px;
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.3px;
  border-radius: 0;
}
.tabs .tab:hover:not(.active) { background: #faf3df; filter: none; color: var(--accent); }
.tabs .tab.active { color: var(--accent); border-bottom-color: var(--accent); background: transparent; }
.tab-pane { min-height: 200px; }

/* Hand history modal — stable layout. The modal is a flex column with a
   fixed footprint; only the .tab-pane scrolls. Means the modal doesn't
   jump in size as the user pages through hands with different content.
   The base .overlay .modal sets `overflow: auto`, which would otherwise
   make the whole modal scroll and defeat the inner pane scroll. Force
   it back to hidden so the flex layout works. */
.overlay .modal.history-modal {
  display: flex;
  flex-direction: column;
  width: min(640px, calc(100% - 32px));
  height: min(640px, calc(100vh - 60px));
  height: min(640px, calc(100dvh - 60px));
  overflow: hidden;
  padding-bottom: 16px;
}
.overlay .modal.history-modal .modal-header,
.overlay .modal.history-modal .hand-selector,
.overlay .modal.history-modal .hand-history-result,
.overlay .modal.history-modal .tabs {
  flex: 0 0 auto;        /* fixed; don't shrink with content */
}
.overlay .modal.history-modal .tab-pane {
  flex: 1 1 auto;
  min-height: 0;          /* allow shrink so overflow kicks in */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 4px 2px 0;
  margin-top: 4px;
}
@media (max-width: 599px) {
  .overlay .modal.history-modal {
    height: min(560px, calc(100vh - 40px));
    height: min(560px, calc(100dvh - 40px));
  }
}

/* Meld pane inside the history modal — two stacked team blocks, each with
   their two players' meld breakdowns. */
.history-meld-team {
  background: white;
  border: 1px solid var(--paper-border);
  border-radius: 10px;
  padding: 8px 12px;
  margin-bottom: 10px;
}
.history-meld-team-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--paper-border);
  margin-bottom: 6px;
}
.history-meld-team-label { font-weight: 700; font-size: 13px; }
.history-meld-team-total { font-weight: 700; font-size: 15px; color: var(--accent); }
.history-meld-seat { padding: 4px 0; }
.history-meld-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 13px;
}
.history-meld-emoji { font-size: 16px; }
.history-meld-name { font-weight: 600; flex: 1 1 auto; }
.history-meld-total { font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 700; }
.history-meld-items {
  list-style: none;
  padding: 0 0 0 24px;
  margin: 4px 0 0;
  font-size: 12px;
}
.history-meld-items li {
  display: flex;
  justify-content: space-between;
  padding: 2px 0;
  color: var(--ink);
}
.history-meld-items .hm-pts { color: var(--accent); font-weight: 600; }
.history-meld-none { padding: 2px 0 4px 24px; font-size: 12px; color: var(--muted); }

.history-list { padding-left: 18px; font-size: 13px; line-height: 1.6; }
.history-list li { margin-bottom: 4px; }
/* Jump-bid marker in the bidding history (#1) — matches the live ⚡ pill.
   Gold for an ordinary jump, coral for a wild one. */
.history-jump {
  display: inline-block;
  margin-left: 4px;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  color: #fff;
  background: linear-gradient(180deg, #8a6dff, #6a47e6);  /* match the live indicator (#1) */
  vertical-align: 1px;
}
.history-jump.wild { color: #fff; background: linear-gradient(180deg, #f17a55, var(--accent)); }
.history-summary { margin-top: 8px; font-size: 13px; color: #6c5b1d; }
.history-trick-head { font-size: 12px; margin-bottom: 3px; }
.history-trick-cards { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.history-play { display: inline-flex; flex-direction: column; align-items: center; gap: 2px; font-size: 10px; }
.history-seat {
  font-weight: 700;
  opacity: 0.75;
  max-width: 56px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: center;
}

/* Hand selector — arrows + label centered above the tabs. Lets the user
   page through every hand played in the current game. */
.hand-selector {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin: 4px 0 10px;
  font-size: 14px;
}
.hand-selector-solo { color: var(--ink); opacity: 0.85; }
.hand-selector .hs-label { color: var(--ink); }
.hand-selector .hs-label strong { font-weight: 700; }
.hand-selector .hs-arrow {
  background: var(--paper-2, #efe7cc);
  border: 1px solid #cbbf99;
  color: var(--ink);
  width: 32px;
  height: 32px;
  border-radius: 50%;
  font-size: 18px;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.hand-selector .hs-arrow:disabled { opacity: 0.35; cursor: default; }
.hand-selector .hs-arrow:not(:disabled):hover { filter: brightness(1.05); }
.hand-live-tag {
  display: inline-block;
  margin-left: 4px;
  padding: 1px 6px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  border-radius: 8px;
  background: var(--accent, #c66a3f);
  color: white;
  vertical-align: middle;
}

/* Per-hand result strip — meld + trick + delta for each team, shown above
   the tabs so the outcome reads at a glance. */
.hand-history-result {
  background: white;
  border: 1px solid #d6cfb4;
  border-radius: 8px;
  padding: 8px 12px;
  margin-bottom: 12px;
  font-size: 13px;
}
.hand-history-result .hhr-row {
  display: grid;
  grid-template-columns: 70px 1fr 1fr 60px;
  gap: 6px;
  align-items: baseline;
  padding: 2px 0;
}
.hand-history-result .hhr-team { font-weight: 700; }
.hand-history-result .hhr-meld,
.hand-history-result .hhr-trick { color: #6c5b1d; font-size: 12px; }
.hand-history-result .hhr-total { font-weight: 700; text-align: right; }
.hand-history-result .hhr-total.made { color: #1a7f37; }
.hand-history-result .hhr-total.set  { color: var(--accent, #c66a3f); }
.hand-history-result .hhr-note { font-size: 11px; color: #6c5b1d; text-align: right; min-height: 0; }
.hand-history-result .hhr-note:empty { display: none; }

/* Summary bid prominence */
.summary-bid {
  background: white;
  border: 1px solid #d6cfb4;
  border-radius: 8px;
  padding: 12px 16px;
  margin-bottom: 12px;
}
.summary-bid-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 4px 0;
}
.summary-bid-row + .summary-bid-row { border-top: 1px solid #f0e8cf; }
.summary-label {
  display: inline-block;
  width: 70px;
  color: var(--muted);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.summary-bid-amount {
  font-size: 26px;
  font-weight: 800;
  letter-spacing: 0.5px;
}
.summary-bid-amount.made { color: #1a7f37; } /* green when bidder made it */
.summary-bid-amount.set  { color: var(--accent); } /* red when bidder went set */
.summary-team-tag {
  display: inline-block;
  background: var(--ink);
  color: white;
  font-size: 11px;
  padding: 1px 7px;
  border-radius: 10px;
  margin-left: 6px;
}
.summary-result {
  font-weight: 700;
  font-size: 15px;
}
.summary-result.made { color: #1a7f37; }
.summary-result.set { color: var(--accent); }

/* Your hand (fanned across the bottom, with suit clusters separated) */
.your-hand {
  display: flex;
  justify-content: center;
  align-items: flex-end;
  padding: 0 16px 6px;
  min-height: 102px;
  flex-wrap: nowrap;
}

/* Two-row hand layout — used when count > 12 (the bidder right after the
   partner pass). Top row tucks under the bottom row so the hand still reads
   as one piece, not two separate strips.
   IMPORTANT: no `transform: scale()` on the row — that breaks the cards'
   own `transform: translateY()` for the selected/ready states (they end up
   lifting less than bottom-row cards). Use opacity + a negative margin
   instead so the row appears "behind" without consuming the transform slot. */
.your-hand.hand-two-rows {
  flex-direction: column;
  /* Center the rows themselves on the horizontal cross-axis. Without this,
     the parent's `align-items: flex-end` (set for the single-row case) is
     inherited and shoves both rows to one edge instead of centering them. */
  align-items: center;
  gap: 0;
  /* Reserve room above for the back-row lift. This used to be
     (card-h * 0.55 + 16px) ≈ 92px on a 138px desktop card, sized for the
     OLD -64.6px back-row lift. Since v1.2.6 the back-row lift is a FIXED
     -30px (and ready is -18px + ~8px badge ≈ -26px), so a fixed ~38px
     reserve clears it. The old, oversized reserve + a 2.1*card-h min-height
     made the two-row pass hand ~306px tall on desktop, overflowing its
     grid cell, spilling over the seat name and below the (overflow:hidden)
     table surface so the cards got clipped and couldn't be tapped. The
     lift is a fixed px amount, so the reserve is fixed too (doesn't scale
     with card size). min-height is now just a small floor — the two rows'
     own height drives the box. */
  padding-top: 38px;
  min-height: calc(var(--card-h) + 12px);
}
.your-hand.hand-two-rows .hand-row {
  display: flex;
  justify-content: center;
  align-items: flex-end;
  position: relative;
}
.your-hand.hand-two-rows .hand-row:first-child {
  /* How far the front row tucks up over the back row. This was -0.55 (front
     covered 55% of the back row), which left only the back card's top ~45%
     exposed — and since taps hit the topmost element, tapping a back-row card
     a hair too low landed on the front-row card covering it. That's the
     "I tap a card and the wrong one raises / nothing raises" bug: the
     selection logic was correct, the tap was just landing on the wrong card.
     Measured tap-targeting across the back row: wrong-card hits stayed at 7
     for -0.55/-0.45/-0.40 and dropped to 0 at -0.35 (back card ~65% exposed)
     for only ~16px more hand height. Geometric, so it helps every card style. */
  margin-bottom: calc(var(--card-h) * -0.35);
}
/* NOTE: the first row used to carry `opacity: 0.95` + `z-index: 1`
   and the second row `z-index: 2`. Both rules created separate
   stacking contexts, which trapped any z-index we set on individual
   lifted cards inside their row — so a lifted back-row card could
   never draw above a front-row card, and a lifted front-row card
   could be visually clipped by adjacent rows. Removing the row-level
   stacking lets per-card `z-index` actually work. DOM order still
   puts the back row first and front row second, so the natural
   visual stacking (back drawn first, front drawn on top) is
   preserved without explicit z-index. */

/* 2-row hand selection.
   Front row (second row): the standard `.card.selected/.passing/.ready`
   14px lift works as-is — the row already sits visually on top.
   Back row (first row): the default 14px lift is hidden behind the
   front row's 55% vertical overlap, so a user passing the Q♥ in the
   back row saw it stay flat at the original line. We now lift the
   back row's selected cards UP by (card-h * 0.55 + 14px), which is
   exactly enough to land its bottom edge 14px above the front row's
   top edge — the same line a front-row selected card occupies. The
   lift sits ENTIRELY above the front row, so no z-index is needed
   to clear it (we'd cover unselected front-row neighbours
   horizontally otherwise — that was the historical complaint). */
.your-hand.hand-two-rows .hand-row:first-child .card.selected,
.your-hand.hand-two-rows .hand-row:first-child .card.passing {
  /* Match the front row's lift exactly (-30px for .passing). The back row
     used to lift the WHOLE card clear of the front row (~-65px) so its
     bottom edge showed, but that read as "too far up." Players don't need
     the back card's bottom to be visible — the same separation as the
     front row reads as "picked" while the lower half stays tucked behind
     the front row.
     NOTE: .ready is intentionally NOT in this group — pre-select uses the
     smaller -18px lift everywhere (the back row's top is already above the
     front row, so the badge still clears) to stay distinct from a pass. */
  transform: translateY(-30px);
}
.your-hand.hand-two-rows .hand-row .card {
  margin-left: var(--hand-overlap);
}

/* Big hand mode — double-deck dealing 20 per seat means two rows of
   ~10 cards each. We want about 35% of each card visible so the
   user can read rank+suit without scrolling.

   v0.18.2: scale overlap to --card-w directly. Previous attempts
   pinned an absolute pixel value (-65/-60/-52), but --card-w shifts
   under at least FIVE different media queries (default 98 → tablet
   82 → short-height laptop 75 → mobile 65 → display-size-big 72 →
   display-size-sunday 100). A fixed -65px overlap on a 75px card
   leaves only 10px visible — what we kept shipping as "broken
   spacing" on laptops. Multiplying by -0.65 always lands at ~35%
   visible regardless of which query is in force. */
.your-hand.hand-large {
  --hand-overlap: calc(var(--card-w) * -0.65);
}
/* Big-hand (double-deck, 20 cards) two rows lift the same fixed -30px, so
   the same fixed 38px reserve as the regular two-row layout (was the old
   oversized 0.55*card-h reserve — see note above). */
.your-hand.hand-large.hand-two-rows {
  padding-top: 38px;
}
.your-hand.hand-two-rows .hand-row .card:first-child { margin-left: 0; }
.your-hand.hand-two-rows .hand-row .card.suit-break {
  margin-left: var(--hand-suit-gap);
}

.your-hand .card {
  margin-left: var(--hand-overlap);
  cursor: default;
}
.your-hand .card:first-child { margin-left: 0; }
.your-hand .card.suit-break { margin-left: var(--hand-suit-gap); }
.your-hand .card.clickable {
  cursor: pointer;
  /* Tell the browser these are tap targets, not gesture surfaces: removes the
     ~300ms double-tap-zoom wait and the tap-vs-gesture ambiguity that can make
     a tap feel like it "didn't register" on iOS. */
  touch-action: manipulation;
}
/* Hover lift for idle cards only, and ONLY on devices with a real pointer.
   Two reasons for the (hover: hover) gate:
   1. On touch there is no hover — a tap leaves a "sticky" :hover on whatever
      element is under the finger. After you play a card the hand shifts and a
      DIFFERENT card slides under your finger, inheriting :hover and lifting on
      its own ("a random card jumps up"). Gating by pointer capability removes
      the rule entirely on touch, so it can't fire there no matter what.
   2. The :not(...) chain raises this rule's specificity above the old mobile
      `transform: none` guard, so that guard alone could no longer suppress it.
   Excludes the active states so they keep their own (larger) lift on hover. */
@media (hover: hover) and (pointer: fine) {
  .your-hand .card.clickable:not(.passing):not(.selected):not(.ready):hover {
    transform: translateY(-10px);
  }
}

/* Action bar */
.action-bar {
  flex-shrink: 0;
  background: linear-gradient(180deg, rgba(0,0,0,0.55), rgba(0,0,0,0.65));
  color: white;
  padding: 12px 18px;
  min-height: 60px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  font-size: 14px;
  border-top: 1px solid rgba(255,255,255,0.06);
}
.action-bar .label { color: #d6dce5; }
.action-bar input { width: 92px; }
.action-bar button.suit {
  font-size: 16px;
  padding: 8px 14px;
  background: white;
  color: var(--ink);
  border: 1px solid rgba(0,0,0,0.12);
  box-shadow: var(--shadow-sm);
}
.action-bar button.suit:hover:not(:disabled) {
  background: var(--paper);
  filter: none;
}
.action-bar button.suit.suit-red   { color: var(--card-red); }
.action-bar button.suit.suit-black { color: var(--card-black); }
/* Icon-only trump picker: round, large glyph, no label */
.action-bar button.suit-pick {
  width: 52px;
  height: 52px;
  padding: 0;
  font-size: 30px;
  border-radius: 50%;
  line-height: 1;
  border: 1px solid rgba(0,0,0,0.15);
  box-shadow: var(--shadow);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.action-bar button.suit-pick:hover:not(:disabled) {
  transform: translateY(-2px) scale(1.04);
  box-shadow: var(--shadow), 0 0 0 3px rgba(212,169,42,0.35);
}
/* Blocks card style: the trump picker becomes four filled chips in the
   same four-colour palette as the cards (white glyph on the suit
   colour), so choosing trump matches what the player has been looking
   at instead of flipping back to red/black. Filled chips also read
   cleanly on the dark action bar, where a slate-coloured spade glyph
   would otherwise vanish. */
body.card-style-blocks .action-bar button.suit-pick {
  color: #fff;
  border-color: transparent;
}
body.card-style-blocks .action-bar button.suit-pick.suit-S { background: #28323f; }
body.card-style-blocks .action-bar button.suit-pick.suit-H { background: #c0392b; }
body.card-style-blocks .action-bar button.suit-pick.suit-D { background: #2563eb; }
body.card-style-blocks .action-bar button.suit-pick.suit-C { background: #15803d; }
body.card-style-blocks .action-bar button.suit-pick:hover:not(:disabled) {
  filter: brightness(1.08);
}
.action-bar.idle { color: #94a3b8; font-style: italic; }
.quick-jumps { display: inline-flex; gap: 4px; margin-left: 2px; }

/* Bid stepper — used at every screen size. −/+ buttons flank the current
   amount; the user nudges by bidIncrement per click then commits with Bid. */
.bid-stepper {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 999px;
  padding: 3px;
}
.bid-step-btn {
  min-width: 40px;
  min-height: 40px;
  padding: 0;
  font-size: 20px;
  font-weight: 700;
  line-height: 1;
  border-radius: 999px;
}
.bid-step-amount {
  min-width: 60px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 20px;
  color: white;
  padding: 0 6px;
}
.bid-stepper.disabled {
  opacity: 0.55;
}
.bid-stepper.disabled .bid-step-amount {
  color: #9c9991;
}
@media (max-width: 599px) {
  .bid-step-btn { min-width: 38px; min-height: 38px; font-size: 18px; }
  .bid-step-amount { min-width: 50px; font-size: 18px; }
}
.action-bar .quick-jump {
  padding: 6px 10px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0;
  color: white;
  border-color: rgba(255,255,255,0.24);
  background: rgba(255,255,255,0.04);
  box-shadow: none;
}
.action-bar .quick-jump:hover:not(:disabled) {
  background: rgba(255,255,255,0.10);
  border-color: rgba(255,255,255,0.5);
}
.action-bar .quick-jump.wild {
  color: #ffb5a8;
  border-color: rgba(212,83,63,0.55);
  background: rgba(212,83,63,0.10);
}
.action-bar .quick-jump.wild:hover:not(:disabled) {
  background: rgba(212,83,63,0.20);
  border-color: rgba(212,83,63,0.85);
  color: #ffd9d0;
}
.action-bar .surrender-btn {
  font-size: 12px;
  padding: 6px 12px;
  color: #ffb5a8;
  border-color: rgba(212,83,63,0.45);
  background: rgba(0,0,0,0.18);
}
.action-bar .surrender-btn:hover:not(:disabled) {
  background: rgba(212,83,63,0.18);
  border-color: rgba(212,83,63,0.8);
  color: white;
}
.action-bar .countdown {
  display: inline-flex;
  align-items: center;
  background: #1f2937;
  color: var(--gold);
  padding: 4px 10px;
  border-radius: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.action-bar .countdown.urgent {
  background: var(--accent);
  color: white;
  animation: pulse 1s ease-in-out infinite;
}

/* Meld-hint modal — opt-in breakdown of the meld currently in the user's
   hand, gated by the showMeldHint setting. */
.overlay .modal.meld-hint-modal {
  width: min(420px, calc(100% - 32px));
}
.meld-hint-modal .meld-hint-foot {
  font-size: 13px;
  color: var(--muted);
  margin: 0 0 12px;
}
.meld-hint-modal .meld-hint-items {
  list-style: none;
  padding: 0;
  margin: 0;
  background: white;
  border: 1px solid var(--paper-border);
  border-radius: 8px;
}
.meld-hint-modal .meld-hint-items li {
  display: flex;
  justify-content: space-between;
  padding: 8px 14px;
  border-bottom: 1px solid var(--paper-border);
  font-size: 14px;
}
.meld-hint-modal .meld-hint-items li:last-child { border-bottom: 0; }
.meld-hint-modal .mh-name { font-weight: 500; }
.meld-hint-modal .mh-pts { font-weight: 700; color: var(--accent); }
.meld-hint-modal .meld-hint-total {
  margin-top: 12px;
  font-size: 16px;
  text-align: right;
  padding-right: 6px;
}
.meld-hint-modal .meld-hint-total strong { color: var(--accent); font-size: 22px; margin-left: 6px; }

/* Last-trick viewer (v1.2.2) — a compact replay of a completed trick
   in the gameplay cross formation (you bottom, partner top, opponents
   left/right) with prev/next nav. */
.overlay .modal.last-trick-modal { width: min(420px, calc(100% - 32px)); }
.last-trick-cross {
  /* Local card sizing so the cross stays compact in the sheet,
     independent of the table's responsive --card-w/h. */
  --card-w: 60px;
  --card-h: 84px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 8px 0 4px;
}
.last-trick-cross .lt-mid {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
}
.last-trick-cross .lt-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-width: var(--card-w);
}
.last-trick-cross .lt-card-holder .card {
  /* Winner gets a gold ring (applied on the slot below). */
  cursor: default;
}
.last-trick-cross .lt-slot.winner .lt-card-holder .card {
  box-shadow: 0 0 0 3px var(--gold), 0 4px 10px rgba(15,20,25,0.35);
}
.last-trick-cross .lt-name {
  font-size: 11px;
  color: var(--ink-soft);
  text-align: center;
  max-width: 88px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.last-trick-cross .lt-tag {
  display: inline-block;
  font-size: 9px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  padding: 1px 5px;
  border-radius: 999px;
  margin-left: 3px;
}
.last-trick-cross .lt-tag.lead { background: rgba(0,0,0,0.08); color: var(--ink-soft); }
.last-trick-cross .lt-tag.won  { background: var(--gold); color: #3a2e07; }
.last-trick-cross .lt-center {
  text-align: center;
  min-width: 92px;
}
.last-trick-cross .lt-center-lbl { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
.last-trick-cross .lt-center-winner { font-size: 15px; font-weight: 800; color: var(--ink); line-height: 1.2; }
.last-trick-cross .lt-pts { font-size: 12px; color: var(--accent); font-weight: 700; }
.last-trick-modal .lt-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 14px;
}
.last-trick-modal .lt-nav .lt-counter { font-size: 13px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.last-trick-modal .lt-nav button { padding: 10px 14px; }
.last-trick-modal .lt-nav button:disabled { opacity: 0.4; cursor: default; }

/* Invite-a-player modal (sender side). A scrollable list of online
   players with a per-row Invite button that flips through pending /
   declined / accepted states. */
.invite-modal { width: min(520px, calc(100vw - 32px)); }
.invite-modal .invite-hint {
  font-size: 13px;
  color: var(--muted);
  margin: 6px 0 12px;
}
.invite-modal .invite-list {
  list-style: none;
  padding: 0;
  margin: 0;
  max-height: 52vh;
  overflow-y: auto;
  border: 1px solid var(--paper-border);
  border-radius: 10px;
  background: white;
}
.invite-modal .invite-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--paper-border);
}
.invite-modal .invite-row:last-child { border-bottom: 0; }
.invite-modal .invite-emoji { font-size: 22px; flex: 0 0 auto; }
.invite-modal .invite-info { flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.invite-modal .invite-name { font-weight: 600; color: var(--ink); }
.invite-modal .invite-meta { display: flex; gap: 6px; align-items: center; font-size: 12px; color: var(--muted); }
.invite-modal .invite-rating { font-variant-numeric: tabular-nums; }
.invite-modal .invite-send,
.invite-modal .invite-cancel { font-size: 13px; padding: 8px 14px; flex: 0 0 auto; }
.invite-modal .invite-status {
  font-size: 13px;
  font-weight: 600;
  padding: 4px 10px;
  border-radius: 999px;
}
.invite-modal .invite-status.accepted { background: #d8f5d2; color: #0f4d22; border: 1px solid #5fbf6c; }
.invite-modal .invite-status.declined { background: #fadcd6; color: #6b1320; border: 1px solid #d4533f; }
.invite-modal .invite-empty { text-align: center; color: var(--muted); padding: 24px 12px; }
/* Shareable code/link block at the bottom of the Invite modal. */
.invite-modal .invite-share { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--paper-border); }
.invite-modal .invite-share-label { font-size: 13px; color: var(--muted); margin: 0 0 2px; }
.invite-modal .invite-share .invite-reveal { flex-wrap: wrap; }
.invite-modal .invite-share-link { word-break: break-all; font-family: ui-monospace, Menlo, monospace; font-size: 12px; color: var(--muted); margin: 0; }

/* Incoming invite modal — small, friendly, with a visible countdown. */
.invite-incoming {
  width: min(440px, calc(100vw - 32px));
  text-align: center;
}
.invite-incoming-header { display: flex; flex-direction: column; align-items: center; gap: 6px; margin-bottom: 8px; }
.invite-incoming-emoji { font-size: 48px; line-height: 1; }
.invite-incoming h2 { margin: 0; }
.invite-incoming p { color: var(--ink); margin: 6px 0; }
.invite-incoming-timer { color: var(--muted); font-size: 13px; }
.invite-incoming-timer #inv-secs { font-weight: 700; color: var(--accent); }
.invite-incoming-actions { display: flex; flex-direction: column; gap: 8px; margin-top: 14px; align-items: stretch; }
.invite-incoming-actions button { width: 100%; }

/* In-app modal confirm/alert — replaces window.alert() / confirm() for
   anything that should look like part of the app instead of a browser
   chrome dialog. */
.overlay .modal.modal-confirm {
  width: min(520px, calc(100% - 32px));
  text-align: left;
  /* Cap height so long-bodied alerts (like the Anti-Spam Credits
     explainer) don't push the modal off-screen on a short device. */
  max-height: 85vh;
  display: flex;
  flex-direction: column;
}
.modal-confirm-body {
  margin: 0 0 14px;
  overflow-y: auto;
  flex: 1 1 auto;
  -webkit-overflow-scrolling: touch;
}
/* Plain-text variant uses whitespace-pre-wrap so old-style modalAlert
   calls (single paragraph) still render newlines correctly. */
p.modal-confirm-body { white-space: pre-wrap; }
/* Rich-content variant (bodyHtml) needs reasonable defaults for the
   <p> / <ul> / <li> / <strong> tags we render inside. Without these
   the bullet list jumps to the left edge and lines crowd together. */
.modal-confirm-body p { margin: 0 0 10px; line-height: 1.55; font-size: 14px; }
.modal-confirm-body p:last-child { margin-bottom: 0; }
.modal-confirm-body ul {
  margin: 0 0 10px;
  padding-left: 20px;
  line-height: 1.5;
  font-size: 14px;
}
.modal-confirm-body li { margin: 2px 0; }
.modal-confirm-body strong { color: var(--ink); }
.modal-confirm-body em { font-style: italic; color: var(--accent); }

/* Start/join rules preview ("here's what you're getting into"). */
.rules-preview { text-align: left; }
.rules-preview-head {
  font-size: 17px;
  font-weight: 800;
  margin-bottom: 8px;
}
.rules-preview-list {
  margin: 0 0 12px;
  padding-left: 20px;
  line-height: 1.55;
  font-size: 14px;
}
.rules-preview-list li { margin: 3px 0; }
.rules-preview-foot {
  margin: 0;
  padding-top: 10px;
  border-top: 1px solid var(--paper-border);
  font-size: 12.5px;
  color: var(--muted);
  line-height: 1.5;
}
/* "★ your usual" tag on the last-played preset card in the start modal. */
.sg-usual-tag {
  display: inline-block;
  margin-top: 4px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  color: var(--gold);
}
.modal-confirm-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  flex-wrap: wrap;
  flex: 0 0 auto;
  padding-top: 8px;
  border-top: 1px solid var(--paper-border);
}
.modal-confirm-actions button { flex: 0 0 auto; min-width: 100px; }

/* Bot difficulty picker — three stacked cards, one per tier. The
   active card gets a gold ring and a soft tint so the host can
   tell at a glance which difficulty their click is queued to add. */
.bot-difficulty-modal { max-width: 460px; }
.bot-difficulty-pills {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 8px 0 14px;
}
.bot-diff-pill {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 12px 14px;
  border-radius: 10px;
  background: white;
  border: 1.5px solid var(--paper-border);
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
}
.bot-diff-pill:hover { border-color: rgba(212, 83, 63, 0.45); }
.bot-diff-pill.active {
  border-color: var(--gold);
  background: rgba(240, 193, 75, 0.10);
  box-shadow: 0 0 0 2px rgba(240, 193, 75, 0.25);
}
.bot-diff-pill strong { font-size: 15px; color: var(--ink); }
.bot-diff-pill small { font-size: 12px; color: var(--muted); }
/* Inline "default" badge on the Hard pill in the picker. */
.bot-diff-default {
  display: inline-block;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  background: var(--gold);
  color: #1f1408;
  padding: 1px 6px;
  border-radius: 999px;
  margin-left: 6px;
  vertical-align: middle;
}

@media (max-width: 599px) {
  .overlay .modal.modal-confirm {
    width: calc(100% - 16px);
    max-height: 90vh;
    padding: 18px;
  }
  .modal-confirm-body p,
  .modal-confirm-body ul { font-size: 14px; }
  .modal-confirm-actions { flex-direction: column-reverse; align-items: stretch; }
  .modal-confirm-actions button { width: 100%; }
}

/* Account-management section inside the Settings modal: Messages,
   Change email, Delete account. Buttons sit on their own card so the
   destructive action has visual weight. */
.settings-account .account-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 6px 0 10px;
}
/* All ghost-style children of the account-actions row get the same
   pill look — buttons (Messages / Change email / Run wizard / Delete),
   anchor 'admin-link' and the Support anchor. */
.settings-account .account-actions button,
.settings-account .account-actions a.ghost,
.settings-account .account-actions .admin-link {
  font-size: 13px;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.settings-account .account-actions .danger {
  color: var(--accent);
  border-color: rgba(212, 83, 63, 0.45);
}
.settings-account .account-actions .danger:hover {
  background: rgba(212, 83, 63, 0.08);
  border-color: var(--accent);
}
.settings-account .inbox-badge {
  display: inline-block;
  background: var(--accent);
  color: white;
  font-size: 10px;
  font-weight: 800;
  border-radius: 999px;
  padding: 1px 6px;
  margin-left: 4px;
}

/* In-app inbox modal — the user's thread with the admin. */
.contact-modal.inbox-modal { width: min(520px, calc(100% - 32px)); }
.contact-modal .inbox-thread {
  max-height: 320px;
  overflow-y: auto;
  background: #fff;
  border: 1px solid var(--paper-border);
  border-radius: 8px;
  padding: 10px;
}
.contact-modal .inbox-thread .msg {
  margin: 6px 0;
  padding: 8px 12px;
  border-radius: 10px;
  max-width: 80%;
  font-size: 14px;
  line-height: 1.35;
}
.contact-modal .inbox-thread .msg-body { white-space: pre-wrap; word-wrap: break-word; }
.contact-modal .inbox-thread .msg-ts { font-size: 11px; color: var(--muted); margin-top: 2px; }
.contact-modal .inbox-thread .msg.mine {
  background: rgba(212, 83, 63, 0.12);
  margin-left: auto;
}
.contact-modal .inbox-thread .msg.admin {
  background: #f3eed8;
}

/* Profile modal: footer-style "Report this player" link. */
.profile-foot {
  margin-top: 18px;
  padding-top: 12px;
  border-top: 1px solid var(--paper-border);
  display: flex;
  justify-content: flex-end;
}
.danger-link {
  color: var(--accent) !important;
  border-color: rgba(212, 83, 63, 0.45) !important;
  font-size: 13px;
}
.danger-link:hover {
  background: rgba(212, 83, 63, 0.08) !important;
  border-color: var(--accent) !important;
}

/* Soft notice that drops into the top of the lobby — used for non-fatal
   transition messages like "the table you were in just closed". Replaces
   the old blocking alert() that left the user trapped on iOS. */
.lobby-notice {
  background: linear-gradient(135deg, #fff3d6, #ffe3a8);
  color: #4a3517;
  border: 1px solid #e6c270;
  border-radius: 10px;
  padding: 12px 16px;
  margin: 12px auto 0;
  max-width: 720px;
  width: calc(100% - 32px);
  text-align: center;
  font-size: 14px;
  font-weight: 500;
  box-shadow: 0 4px 16px rgba(0,0,0,0.18);
  cursor: pointer;
  animation: lobby-notice-in 220ms ease-out;
}
.lobby-notice.leaving { animation: lobby-notice-out 280ms ease-in forwards; }
@keyframes lobby-notice-in {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes lobby-notice-out {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(-6px); }
}

/* Chat toasts — ephemeral message popovers that drift across the bottom-
   right of the table, then fade away. They sit above the table surface
   (z-index: 60) but below overlays (z-index: 100) so any modal still
   covers them. Multiple stack vertically, newest on top. */
/* Full-bleed, click-through layer; individual bubbles are positioned by JS
   (positionSeatBubble) next to the sender's seat. Above .overlay (z-index
   100) so a message still pops while the chat modal is open. */
.chat-toasts {
  position: fixed;
  inset: 0;
  z-index: 110;
  pointer-events: none;
}
.chat-toast {
  position: absolute;
  /* Cream bubble to match the rest of the UI — warmer + more readable over
     the green felt than the old dark-navy (#2). */
  background: var(--paper);
  color: var(--ink);
  padding: 8px 12px;
  border-radius: 14px;
  box-shadow: 0 8px 22px rgba(0,0,0,0.42);
  font-size: 14px;
  line-height: 1.35;
  pointer-events: auto;
  cursor: pointer;
  max-width: 240px;
  animation: chat-toast-in 200ms ease-out;
  border: 1px solid var(--paper-border);
}
.chat-toast.mine {
  background: linear-gradient(180deg, var(--accent-2), var(--accent)); /* own messages stand apart */
  color: #fff;
  border-color: rgba(255,255,255,0.18);
}
.chat-toast.leaving { animation: chat-toast-out 240ms ease-in forwards; }
/* Emoji-only attribution (#2): avatar + text on one line, no name. */
.chat-toast .ct-body { display: flex; flex-direction: row; align-items: center; gap: 7px; min-width: 0; }
.chat-toast .ct-emoji { font-size: 18px; flex: 0 0 auto; line-height: 1; }
/* Truncate long messages — the full text is in the chat overlay. */
.chat-toast .ct-text {
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Opacity-only so the fade doesn't fight the JS positioning transform. */
@keyframes chat-toast-in  { from { opacity: 0; } to { opacity: 1; } }
@keyframes chat-toast-out { from { opacity: 1; } to { opacity: 0; } }
@media (max-width: 599px) {
  .chat-toast { font-size: 13px; padding: 7px 10px; max-width: min(200px, 60vw); }
  .chat-toast .ct-text { font-size: 13px; }
}

/* Quick-phrase pills in the chat overlay. */
.chat-quick {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  max-height: 96px;
  overflow-y: auto;
  padding: 8px 0;
  border-top: 1px solid var(--paper-border);
}
.chat-quick-btn {
  background: var(--paper-2);
  color: var(--ink);
  border: 1px solid var(--paper-border);
  border-radius: 999px;
  padding: 5px 11px;
  font-size: 13px;
  font-weight: 600;
  box-shadow: none;
  cursor: pointer;
}
.chat-quick-btn:hover:not(:disabled) { background: #ece3c8; transform: none; filter: none; }
.chat-quick-btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* Overlays (meld results, hand summary, game over) */
[hidden] { display: none !important; }
.overlay {
  position: fixed;
  inset: 0;
  background: rgba(15,20,25,0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  animation: overlay-in 140ms ease-out;
}
@keyframes overlay-in {
  from { opacity: 0; backdrop-filter: blur(0); }
  to   { opacity: 1; backdrop-filter: blur(6px); }
}
.overlay .modal {
  background: var(--paper);
  color: var(--ink);
  padding: 26px 26px 22px;
  border-radius: var(--radius-lg);
  max-width: 760px;
  width: calc(100% - 40px);
  max-height: calc(100vh - 60px);
  max-height: calc(100dvh - 60px);
  overflow: auto;
  border: 1px solid var(--paper-border);
  box-shadow: var(--shadow-lg);
  animation: modal-in 200ms cubic-bezier(.2,.7,.3,1);
}
@keyframes modal-in {
  from { opacity: 0; transform: translateY(8px) scale(0.985); }
  to   { opacity: 1; transform: translateY(0)   scale(1); }
}
.overlay .modal h2 {
  margin-bottom: 12px;
  font-family: var(--font-ui);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.2px;
  color: var(--ink);
}
.overlay .modal h3 {
  margin: 18px 0 8px;
  font-size: 14px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--muted);
  font-weight: 600;
}
.overlay .actions { margin-top: 16px; display: flex; gap: 10px; justify-content: flex-end; }
.modal-header {
  display: flex;
  align-items: center;
  margin-bottom: 12px;
  gap: 12px;
}
/* Visual order is fixed: title left, then view-toggle, then actions on the
   right — regardless of the HTML source order. Lets every modal share one
   set of CSS rules without rewriting markup. */
.modal-header h2 { order: 0; flex: 1 1 auto; min-width: 0; margin: 0; }
.modal-header .view-toggle { order: 1; flex: 0 0 auto; margin: 0; }
.modal-header .actions.actions-top,
.modal-header .actions-top { order: 2; flex: 0 0 auto; margin: 0; }

/* Meld grid inside overlay */
.meld-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.meld-card {
  background: white;
  border: 1px solid #d6cfb4;
  border-radius: 8px;
  padding: 8px 10px;
}
.meld-card h4 {
  font-size: 13px;
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 4px;
}
.meld-card .meld-player {
  font-weight: 700;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.meld-card .meld-seat {
  font-size: 11px;
  color: var(--muted);
  font-weight: 600;
  background: rgba(0,0,0,0.06);
  padding: 1px 6px;
  border-radius: 4px;
  letter-spacing: 0.5px;
}
.meld-card ul { margin: 0; padding-left: 16px; font-size: 12px; }
.meld-card .pts { color: var(--accent); font-weight: 700; font-size: 14px; }
.meld-totals { display: flex; gap: 24px; margin: 6px 0 14px; font-size: 15px; }

/* Team-grouped meld view — partners are adjacent under a labelled header
   that calls out the TEAM total. Helps users immediately read "we made 7,
   not just 1" when one partner had thin meld. */
.meld-team-group { margin: 6px 0 10px; }
.meld-team-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 4px 4px 6px;
  border-bottom: 1px solid var(--paper-border);
  margin-bottom: 6px;
}
/* Junk/spam folder heads-up on the signup + verify-pending screens.
   Background tinted just enough to draw the eye without screaming. */
.auth-fine.auth-spam-warning {
  background: rgba(240, 193, 75, 0.14);
  border: 1px solid rgba(240, 193, 75, 0.35);
  border-radius: 8px;
  padding: 8px 10px;
  color: #6c5320;
  font-size: 12.5px;
  line-height: 1.4;
  margin-top: 8px;
}
.auth-fine.auth-spam-warning strong { color: #5a3f0e; }

/* Forfeit button below the meld breakdown — bidder-only, low-emphasis. */
.meld-forfeit-row {
  display: flex;
  justify-content: center;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--paper-border);
}
.meld-forfeit-row button.ghost {
  font-size: 13px;
  color: var(--accent);
  border-color: rgba(212, 83, 63, 0.45);
}
.meld-forfeit-row button.ghost:hover:not(:disabled) {
  background: rgba(212, 83, 63, 0.08);
  border-color: var(--accent);
}

.meld-team-name {
  font-family: var(--font-ui);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.1px;
  letter-spacing: 0.3px;
  color: var(--ink);
}
.meld-team-points {
  font-size: 22px;
  font-weight: 800;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.meld-team-points small {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);
  font-weight: 700;
  margin-left: 6px;
}
.meld-team-us .meld-team-name { color: var(--team-us); }
.meld-team-us .meld-team-header { border-bottom-color: rgba(37,99,235,0.25); }
.meld-team-them .meld-team-name { color: var(--team-them); }
.meld-team-them .meld-team-points { color: var(--team-them); }

/* ============================================================
   Clean meld view (default when Settings → Meld display = Simple).
   Four stacked rows, one per seat. Each row carries a team-coloured
   name pill on the left (your team = green, opponents = red), the
   meld-card fan in the middle, and the seat's point total inside the
   pill. Team grouping is communicated by colour alone — no separate
   group headers. Mirrors the at-a-glance scoreboard layout the user
   requested from a competitor app.
   ============================================================ */
.meld-rows {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 6px 0 10px;
}
.meld-row {
  position: relative;
  display: grid;
  grid-template-columns: 152px 1fr;
  gap: 12px;
  align-items: center;
  padding: 8px 10px;
  border-radius: 10px;
  background: rgba(0,0,0,0.02);
  border: 1px solid var(--paper-border);
  min-height: 64px;
}
.meld-row-name {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 8px;
  font-weight: 700;
  color: white;
  line-height: 1.1;
  min-height: 44px;
}
.meld-row-us .meld-row-name {
  background: linear-gradient(180deg, var(--team-us), var(--team-us-dark));
  box-shadow: 0 2px 6px rgba(37,99,235,0.18);
}
.meld-row-them .meld-row-name {
  background: linear-gradient(180deg, var(--team-them), var(--team-them-dark));
  box-shadow: 0 2px 6px rgba(124,58,237,0.18);
}
.meld-row-emoji { font-size: 18px; flex: 0 0 auto; }
/* Crown marking the bid winner, sized to sit beside the emoji without
   stealing room from the name. */
.meld-row-bidcrown { font-size: 14px; flex: 0 0 auto; line-height: 1; }
.meld-row-username {
  flex: 1 1 auto;
  /* Guarantee at least a couple of characters are visible before the
     ellipsis kicks in — names were collapsing to a single letter in the
     narrow name pill (#1). */
  min-width: 2.4ch;
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.meld-row-points {
  flex: 0 0 auto;
  font-size: 20px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  background: rgba(255,255,255,0.18);
  padding: 0 7px;
  border-radius: 6px;
  min-width: 28px;
  text-align: center;
}
/* "(You)" marker on your own row — subtle gold ring so the user can
   pick their own line at a glance without breaking the team colour. */
.meld-row-you .meld-row-name {
  outline: 2px solid var(--gold);
  outline-offset: 1px;
}
/* Bid winner (#1): ring the whole row in gold + a soft gold wash so it
   reads instantly as "this is who took the bid", distinct from the
   name-pill outline that marks "you". */
.meld-row-bidder {
  border-color: var(--gold);
  box-shadow: 0 0 0 2px rgba(212,169,42,0.55);
  background: rgba(212,169,42,0.08);
}
/* Small "Took the bid" ribbon in the row's top-right corner. */
.meld-bidder-tag {
  position: absolute;
  top: 4px;
  right: 6px;
  z-index: 2;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #2a1a05;
  background: var(--gold);
  padding: 1px 6px;
  border-radius: 999px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
  pointer-events: none;
}
/* Detailed (opt-in) view: ring the bidder's card too. */
.meld-card-bidder {
  border-color: var(--gold);
  box-shadow: 0 0 0 2px rgba(212,169,42,0.5);
}

/* Spectator view (#8): a watcher has no side, so the blue/violet team
   tints would be arbitrary. Show the two pairs as two shades of grey
   (keyed on the literal team, since us/them collapses for spectators)
   and ring the BIDDING pair in gold so it's obvious who's chasing the
   bid. Specificity (0,3,0) beats the .meld-row-us/them (0,2,0) defaults. */
body.is-spectator .meld-row-ns .meld-row-name {
  background: linear-gradient(180deg, #64748b, #475569);
  box-shadow: 0 2px 6px rgba(71,85,105,0.18);
}
body.is-spectator .meld-row-ew .meld-row-name {
  background: linear-gradient(180deg, #aab2c0, #8b93a3);
  box-shadow: 0 2px 6px rgba(139,147,163,0.18);
}
body.is-spectator .meld-row-bidteam .meld-row-name {
  outline: 2px solid var(--gold);
  outline-offset: 1px;
}
body.is-spectator .meld-team-totals-us  { color: #475569; }
body.is-spectator .meld-team-totals-them { color: #8b93a3; }
body.is-spectator .simple-summary-table thead th.team-us   { color: #475569; }
body.is-spectator .simple-summary-table thead th.team-them { color: #8b93a3; }
body.is-spectator .meld-team-us .meld-team-name  { color: #475569; }
body.is-spectator .meld-team-them .meld-team-name { color: #8b93a3; }
.meld-row-cards {
  min-width: 0; /* let the cards row shrink within the grid column */
}
.meld-row-cards .meld-simple-cards {
  padding: 0;
  min-height: 48px;
  margin: 0;
}
.meld-row-cards .meld-empty {
  font-size: 12px;
  opacity: 0.55;
  padding: 0;
}
/* Compact team-total strip below the four rows — gives the
   bidder a quick "we have 7 / they have 12" read without
   reintroducing the heavy team headers. */
.meld-team-totals {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  margin: 4px 0 8px;
  border-radius: 8px;
  background: rgba(0,0,0,0.04);
  font-size: 13px;
  font-weight: 600;
}
.meld-team-totals-us { color: var(--team-us); }
.meld-team-totals-them { color: var(--team-them); }
.meld-team-totals strong {
  font-size: 18px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  margin-left: 4px;
}

/* Condensed bid header on the meld modal — single horizontal row:
   coloured trump pill on the left, who-bid-what + "need N trick
   pts" callout on the right. Half the vertical footprint of the
   old layout, with the new "need" indicator the user actually
   wanted (so the bidder team can see at a glance how close their
   meld already got them to the bid). */
.meld-bid-header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  margin: 0 0 10px;
  background: linear-gradient(180deg, #fffcf0 0%, #fbf3da 100%);
  border: 1px solid #e8dcb1;
  border-radius: 12px;
}
.meld-bid-trump {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 10px 18px;
  background: white;
  border-radius: 14px;
  border: 1px solid #e8dcb1;
  flex: 0 0 auto;
  min-width: 84px;
}
.meld-bid-trump.red  { color: var(--card-red); }
.meld-bid-trump.black { color: var(--card-black); }
/* Big trump glyph so the suit is immediately readable from a glance.
   On the meld review screen the suit is what's actually being played
   for — make it the visual focus, not a footnote. */
.meld-bid-trump-glyph { font-size: 44px; line-height: 1; font-weight: 700; }
.meld-bid-trump-name {
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.meld-bid-info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.meld-bid-line {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.meld-bid-line strong { color: var(--accent); font-weight: 800; }
.meld-bid-need {
  font-size: 13px;
  color: var(--muted);
  font-weight: 600;
}
.meld-bid-need strong { color: var(--accent); font-size: 15px; font-weight: 800; }
.meld-bid-need.made { color: var(--success); }
@media (max-width: 599px) {
  .meld-bid-header { padding: 8px 10px; gap: 10px; }
  .meld-bid-trump { padding: 8px 14px; min-width: 72px; }
  .meld-bid-trump-glyph { font-size: 36px; }
  .meld-bid-trump-name { font-size: 11px; }
  .meld-bid-line { font-size: 13px; }
  .meld-bid-need { font-size: 12px; }
  .meld-bid-need strong { font-size: 14px; }
}

.view-toggle { display: inline-flex; border: 1px solid #cbb988; border-radius: 6px; overflow: hidden; }
.toggle-btn {
  background: white;
  color: var(--ink);
  border: none;
  padding: 5px 12px;
  font-size: 12px;
  font-weight: 600;
  border-radius: 0;
}
.toggle-btn + .toggle-btn { border-left: 1px solid #cbb988; }
.toggle-btn.active { background: var(--ink); color: white; }
.toggle-btn:hover:not(.active) { background: #faf3df; filter: none; }

.meld-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
  border-bottom: 1px solid #eee5cb;
}
.meld-item:last-child { border-bottom: 0; }
.meld-item-cards { display: flex; align-items: center; flex: 0 0 auto; }
/* Lighter overlap than the hand fan (-22 -> -8) so each meld card stays
   readable across card styles. With the previous tight stack the Big-Suit
   centered glyph and the Vector SVG bled into the next card's left edge.
   Now ~36px of each card is visible (rank corner + half the suit body) —
   enough for any card style. */
.meld-item-cards .card.mini { margin-left: -8px; }
.meld-item-cards .card.mini:first-child { margin-left: 0; }
.meld-item-cards .card.mini.suit-break { margin-left: 8px; } /* visible suit gap */
.meld-item-label { flex: 1; font-size: 12px; line-height: 1.3; }
.meld-item-pts { color: var(--accent); font-weight: 700; font-size: 13px; }

.meld-empty { font-size: 12px; opacity: 0.6; padding: 6px 0; }

/* Simple meld mode: one fanned row of just the meld cards, no labels.
   Same loose-overlap rationale as .meld-item-cards. Wrap on long runs
   so cards stay readable; vertical space is cheap inside the modal. */
.meld-simple-cards {
  display: flex;
  flex-wrap: wrap;
  row-gap: 6px;
  align-items: center;
  padding: 8px 0 2px;
  min-height: 56px;
}
.meld-simple-cards .card.mini { margin-left: -8px; }
.meld-simple-cards .card.mini:first-child { margin-left: 0; }
.meld-simple-cards .card.mini.suit-break { margin-left: 8px; }

/* Suit tracker: simple count of how many of each suit have been played. */
.tracker-modal { max-width: 520px; }
.tracker-note { font-size: 12px; color: var(--muted); margin: 4px 0 14px; }
.tracker-grid {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.tracker-suit-row {
  display: grid;
  grid-template-columns: 32px 1fr 110px auto auto;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: white;
  border: 1px solid var(--paper-border);
  border-radius: var(--radius);
  font-size: 14px;
}
.tracker-suit-row.red   { color: var(--card-red); }
.tracker-suit-row.black { color: var(--card-black); }
.tracker-suit-row.trump { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold); }
.tracker-suit-icon { font-size: 24px; line-height: 1; font-weight: 700; text-align: center; }
.tracker-suit-name { font-weight: 600; color: var(--ink); }
.tracker-suit-row.red .tracker-suit-name,
.tracker-suit-row.black .tracker-suit-name { color: var(--ink); }
.tracker-suit-progress {
  position: relative;
  height: 8px;
  background: #ece4cc;
  border-radius: 999px;
  overflow: hidden;
}
.tracker-suit-bar {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--accent);
  border-radius: 999px;
}
.tracker-suit-row.trump .tracker-suit-bar { background: var(--gold); }
.tracker-suit-count  { font-variant-numeric: tabular-nums; color: var(--ink); }
.tracker-suit-remain { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
@media (max-width: 599px) {
  .tracker-suit-row { grid-template-columns: 28px 1fr 70px auto; }
  .tracker-suit-remain { display: none; }
}

/* Chat */
.chat-unread {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: white;
  border-radius: 9px;
  min-width: 18px;
  height: 18px;
  font-size: 11px;
  font-weight: 700;
  padding: 0 5px;
  margin-left: 4px;
}
/* Flex column so, when the viewport shrinks (phone keyboard, see #2), the
   message list gives up height first — the header (with the close button)
   and the input stay on-screen. max-height:100% bounds the sheet to the
   visualViewport-pinned overlay. */
.chat-modal { max-width: 560px; display: flex; flex-direction: column; max-height: 100%; }
/* Beat the mobile bottom-sheet `.overlay .modal { max-height: 92dvh }` (specificity
   0,2,0 vs `.chat-modal`'s 0,1,0). The chat overlay is pinned to
   window.visualViewport (pinChatOverlayToViewport), so 100% binds the sheet to the
   *visible* height above the keyboard rather than 92% of the full layout viewport —
   otherwise the bottom-anchored sheet overflows upward and the header scrolls off (#1). */
.overlay .modal.chat-modal { max-height: 100%; }
.chat-scroll {
  background: white;
  border: 1px solid #d6cfb4;
  border-radius: 8px;
  padding: 8px 10px;
  flex: 1 1 auto;
  height: 320px;       /* basis on desktop; shrinks via flex when space is tight */
  min-height: 64px;
  overflow-y: auto;
  font-size: 14px;
  margin-bottom: 10px;
}
.chat-modal .chat-quick, .chat-modal .chat-form { flex: 0 0 auto; }
.chat-line { padding: 4px 0; border-bottom: 1px dashed #f0e8cf; }
.chat-line:last-child { border-bottom: 0; }
.chat-line.mine .chat-from { color: var(--accent); font-weight: 700; }
.chat-from { font-weight: 600; margin-right: 6px; }
.chat-text { color: var(--ink); }
.chat-text.flagged { color: #6b1320; }
.chat-empty { color: var(--muted); }
.chat-form { display: flex; gap: 8px; }
.chat-form input { flex: 1; }

/* Mute indicator on the opp-avatar (top-right corner) */
.opp-avatar { position: relative; }
.opp-avatar-emoji { display: inline-block; }
.opp-mute {
  position: absolute;
  top: -2px;
  right: -10px;
  font-size: 14px;
  background: rgba(0,0,0,0.6);
  border-radius: 50%;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.auto-set-banner {
  background: #fff3cd;
  border: 1px solid #d4a017;
  border-radius: 6px;
  padding: 10px 14px;
  margin: 10px 0;
  font-size: 13px;
  line-height: 1.4;
}
.auto-set-tag {
  font-size: 12px;
  color: var(--accent);
  background: #fff3cd;
  padding: 2px 6px;
  border-radius: 4px;
  margin-left: 6px;
  font-weight: 600;
}

/* Stake-earned toast — floats in from the top of the viewport when a
   game ends, fades out after ~3s. Confirms to the player that bot
   games are rewarded just like ranked ones (server grants the bonus
   regardless of ranked status). pointer-events:none so it never
   blocks the summary modal underneath. */
.stake-toast {
  position: fixed;
  top: 24px;
  left: 50%;
  transform: translate(-50%, -16px);
  background: rgba(20, 84, 59, 0.96);
  color: white;
  /* v0.20.1: roomier on the sides — was 10/18 which crowded the
     "credits earned" text against the pill edge. Also has a close
     button on the right now so we need room for it. */
  padding: 12px 16px 12px 22px;
  border-radius: 999px;
  font-size: 14px;
  box-shadow: 0 6px 22px rgba(15,20,25,0.35);
  z-index: 9998;
  opacity: 0;
  /* Pointer events allowed now — the close button needs to be
     clickable. The toast itself doesn't block underneath since
     it's narrow + centered at the top. */
  pointer-events: auto;
  transition: opacity 280ms ease, transform 320ms cubic-bezier(.2,.7,.3,1);
  display: flex;
  align-items: center;
  gap: 8px;
  /* Don't let a long string push past the viewport edge on phones. */
  max-width: calc(100% - 24px);
}
.stake-toast.show {
  opacity: 1;
  transform: translate(-50%, 0);
}
.stake-toast strong { color: var(--gold); margin: 0 4px; font-weight: 700; }
.stake-toast .stake-toast-msg { flex: 1; }
.stake-toast .stake-toast-close {
  background: rgba(255,255,255,0.12);
  color: white;
  border: 0;
  border-radius: 999px;
  width: 24px;
  height: 24px;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
}
.stake-toast .stake-toast-close:hover { background: rgba(255,255,255,0.22); }

/* Confetti celebration — fires once when the user wins a game.
   Pieces are absolutely-positioned characters that fall from above
   the viewport with a horizontal drift + rotation. Pointer-events
   none so the layer never blocks clicks on the summary modal. */
.confetti-layer {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 9999;
}
.confetti-piece {
  position: absolute;
  top: -8vh;
  font-weight: 800;
  line-height: 1;
  animation: confetti-fall 2500ms cubic-bezier(.4,.2,.6,1) forwards;
  will-change: transform, opacity;
}
@keyframes confetti-fall {
  0%   { transform: translate(0, -10vh) rotate(0deg); opacity: 1; }
  10%  { opacity: 1; }
  100% { transform: translate(var(--dx, 0), 110vh) rotate(var(--rot, 360deg)); opacity: 0.85; }
}

/* Settings modal tabs — Game vs Account. Tucks the noisier
   account / Stake / email stuff out of the way so the player
   landing in Settings to swap a card style isn't drowned. */
.settings-tabs { margin-bottom: 14px; }
.settings-tab-pane { min-height: 200px; }

/* Swipeable horizontal pill row — used for the Card-Style picker
   on mobile so all four styles fit in one row and the user swipes
   left/right rather than scrolling vertically through a 2x2 grid.
   On desktop the row still wraps naturally to a multi-row layout
   if widths require, since the screen has room. */
.sg-pills-swipe {
  display: flex;
  flex-wrap: nowrap;
  gap: 8px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  /* Top room for the "NEW" corner badge on a tile (it sits ~6px above the
     tile). overflow-x:auto makes this row clip vertically too, so without
     the padding the badge's top got cut off. */
  padding-top: 10px;
  padding-bottom: 6px;
  /* Hide the scrollbar but keep the gesture. */
  scrollbar-width: thin;
}

/* ==========================================================
   Scroll affordance (v0.20.6) — directional edge fade.
   The .scroll-hint class + scroll-can-left / scroll-can-right
   (toggled by the scroll-hint system in ga.js) fade the content
   at whichever edge has more to scroll toward. mask-image is
   anchored to the visible box, so the fade stays at the edge as
   content swipes underneath. 32px fade ramp reads as "more this
   way" without hiding much. The chevron pseudo reinforces it for
   anyone who reads the fade as just a vignette.
   ========================================================== */
.scroll-hint { position: relative; }
.scroll-hint.scroll-can-right:not(.scroll-can-left) {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent 100%);
          mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent 100%);
}
.scroll-hint.scroll-can-left:not(.scroll-can-right) {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 32px);
          mask-image: linear-gradient(to right, transparent 0, #000 32px);
}
.scroll-hint.scroll-can-left.scroll-can-right {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 32px, #000 calc(100% - 32px), transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 32px, #000 calc(100% - 32px), transparent 100%);
}
.sg-pills-swipe .settings-pill {
  flex: 0 0 auto;
  scroll-snap-align: start;
  min-width: 140px;
}

/* Advanced toggle — collapses the long list of game options behind
   a single click for users who came in to just swap card style. */
.sg-advanced-toggle {
  background: transparent;
  border: 1px dashed #c3b78c;
  color: var(--ink-soft);
  padding: 10px 14px;
  border-radius: 8px;
  width: 100%;
  text-align: center;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  margin: 4px 0 10px;
}
.sg-advanced-toggle:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.sg-advanced-block {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* Create-Game quick-start tier — three big stacked buttons. Users
   bounce off the old multi-pill form; this lets them pick a preset
   and hit the table in one click. Advanced is one tap away if they
   need it. */
#sg-quick { display: flex; flex-direction: column; gap: 12px; padding: 8px 0; }

/* Game-preset swipe rail (v0.20.0). The three built-ins go side-by-
   side. On mobile / narrow screens the row scrolls horizontally with
   momentum (overflow-x:auto + scroll-snap), so a thumb swipe moves
   one card per gesture. On wide screens the row just looks like a
   normal flex row with all three visible at once. */
.sg-quick-rail {
  display: flex;
  flex-direction: row;
  gap: 10px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* Pull padding back so snap aligns to the visible edge instead of
     stopping mid-card. */
  padding: 4px 2px 10px;
  margin: 0 -4px;  /* let the rail bleed slightly past the modal padding */
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.sg-quick-rail::-webkit-scrollbar { display: none; }
/* v0.20.9: square-ish preset tiles instead of wide horizontal bars.
   aspect-ratio keeps them square at any width; content centres
   vertically. ~3 across on desktop, ~2.3 across (with a peek) on
   phones so the swipe affordance still reads. */
.sg-quick-rail .sg-rail-card {
  flex: 0 0 auto;
  width: calc(33.333% - 7px);       /* 3 across on wide screens */
  min-width: 124px;
  aspect-ratio: 1 / 1;              /* square */
  justify-content: center;          /* centre the icon/title/subtitle stack */
  scroll-snap-align: start;
  padding: 12px 10px;
  gap: 6px;
}
.sg-quick-rail .sg-rail-card strong { font-size: 15px; }
.sg-quick-rail .sg-rail-card small { font-size: 11.5px; line-height: 1.3; }
@media (max-width: 599px) {
  /* Phones: ~2.3 tiles visible so two fit comfortably and the third
     peeks to telegraph the swipe. min-width drops so the square can
     shrink with the screen. */
  .sg-quick-rail .sg-rail-card {
    width: 43%;
    min-width: 0;
  }
}

/* Custom + Advanced share a single row so the two secondary actions
   read as a pair instead of stacking. On narrow phones they stack
   again — two ghost buttons side-by-side feel too cramped under
   60-65px each. */
.sg-quick-secondary-row {
  display: flex;
  gap: 10px;
}
.sg-quick-secondary-row .sg-quick-btn { flex: 1; }
@media (max-width: 420px) {
  .sg-quick-secondary-row { flex-direction: column; }
}

.sg-quick-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 18px 16px;
  border-radius: 12px;
  border: 1px solid transparent;
  font: inherit;
  cursor: pointer;
  text-align: center;
  line-height: 1.3;
}
.sg-quick-btn strong { font-size: 17px; font-weight: 700; }
.sg-quick-btn small { font-size: 12px; opacity: 0.85; }
.sg-quick-btn.primary {
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
  color: white;
}
.sg-quick-btn.primary:hover { filter: brightness(1.05); }
.sg-quick-btn.ghost {
  background: transparent;
  color: var(--ink);
  border: 1px solid #c3b78c;
}
.sg-quick-btn.ghost strong { font-size: 15px; }
/* Engagement-gated pill: greyed but still clickable so the
   explanation modal can fire. */
.sg-quick-btn.sg-quick-locked { opacity: 0.55; filter: grayscale(0.6); }
.sg-quick-btn.sg-quick-locked strong::after {
  content: ' 🔒';
  font-size: 12px;
}

/* Custom-rules template cards — each is a primary pill with edit /
   delete affordances on a second row. Tucked between the built-in
   presets and the "🎨 Custom rules…" + Advanced ghost buttons. */
#sg-quick-templates { display: flex; flex-direction: column; gap: 8px; margin: 8px 0; }
.sg-template-card { display: flex; flex-direction: column; gap: 0; }
.sg-template-card .sg-template-launch { border-radius: 12px 12px 0 0; margin-bottom: 0; }
.sg-template-card .sg-template-actions {
  display: flex; gap: 6px;
  background: rgba(212, 83, 63, 0.08);
  border: 1px solid rgba(212, 83, 63, 0.25);
  border-top: 0; border-radius: 0 0 12px 12px;
  padding: 4px 8px;
}
.sg-template-card .sg-template-actions .ghost {
  font-size: 11px; padding: 4px 8px; min-height: 28px;
  background: transparent; color: var(--accent);
  border: 1px solid transparent;
}
.sg-template-card .sg-template-actions .ghost:hover {
  background: rgba(212, 83, 63, 0.12);
  border-color: rgba(212, 83, 63, 0.35);
}
.sg-template-delete { margin-left: auto; }

/* Wizard modal — separate overlay, opens on top of the start-game
   modal. Compact form with grouped fields. */
.sg-wizard-overlay { z-index: 1100; }
.sg-wizard { max-width: 560px; max-height: 90vh; overflow-y: auto; }
.sg-wizard .sg-2col {
  display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
}
.sg-wizard .sg-counters {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px;
}
.sg-wizard .sg-counter {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
}
.sg-wizard .sg-counter-rank {
  font-size: 13px; font-weight: 800; color: var(--accent);
}
.sg-wizard .sg-counter input {
  width: 100%; text-align: center; padding: 6px 4px;
}
.sg-wizard input[type="number"] { width: 100%; }
@media (max-width: 599px) {
  .sg-wizard .sg-2col { grid-template-columns: 1fr; }
  .sg-wizard .sg-counters { grid-template-columns: repeat(3, 1fr); }
}
.sg-back-quick {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 13px;
  cursor: pointer;
  padding: 4px 8px;
  text-decoration: underline;
}
.sg-advanced-hint { margin: 0 0 10px; }

/* Pinochle Stake — per-account "trust budget" indicator shown in
   settings. Refills over time + on completed games; gates a handful
   of expensive endpoints (contact form, email change, reports) so
   throwaway accounts can't spam. Engaged players see a full bar and
   never bump the wall. */
/* Stake panel gets its own visual card so it doesn't blur into the
   adjacent emoji picker / account-actions blocks. */
.sg-field.settings-stake {
  background: linear-gradient(180deg, #fff 0%, #faf3df 100%);
  border: 1px solid var(--paper-border);
  border-radius: 12px;
  padding: 14px 16px;
  margin-bottom: 14px;
}
.settings-stake .stake-bar {
  width: 100%;
  height: 10px;
  background: #ece3c6;
  border-radius: 999px;
  overflow: hidden;
  margin-top: 4px;
}
.settings-stake .stake-bar-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--success), #4ec76a);
  transition: width 240ms ease;
}
.settings-stake .stake-bar-fill.low {
  background: linear-gradient(90deg, #b3261e, var(--accent));
}
.settings-stake .stake-meta {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-top: 6px;
  font-size: 12px;
  color: var(--muted);
}
.settings-stake .stake-meta strong { color: var(--ink); font-size: 13px; }
.settings-stake .stake-hint { text-align: right; max-width: 70%; }
.settings-stake .stake-learn-more {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  padding: 0 4px;
  margin-left: 8px;
  text-decoration: underline;
}
.settings-stake .stake-learn-more:hover { color: var(--accent-2); }

/* Stake audit table — opened from the 'See audit' button on the
   Anti-Spam Credits panel. Sits inside the modal-confirm scroll area. */
.stake-audit-wrap { max-height: 50vh; overflow-y: auto; border: 1px solid var(--paper-border); border-radius: 8px; }
.stake-audit { width: 100%; border-collapse: collapse; font-size: 13px; }
.stake-audit th, .stake-audit td { padding: 6px 10px; border-bottom: 1px solid #f0e8cf; text-align: left; vertical-align: top; }
.stake-audit th { background: #f3eed8; color: var(--ink-soft); font-weight: 700; font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px; }
.stake-audit tr:last-child td { border-bottom: 0; }
.stake-audit .audit-ts { color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.stake-audit .audit-delta { font-variant-numeric: tabular-nums; font-weight: 800; text-align: right; }
.stake-audit .audit-credit .audit-delta { color: var(--success); }
.stake-audit .audit-debit  .audit-delta { color: var(--accent); }
.stake-audit .audit-noop   .audit-delta { color: var(--muted); }
.stake-audit .audit-noop   .audit-reason { color: var(--muted); font-style: italic; }
.stake-audit .audit-reason { color: var(--ink); }

/* First-run setup wizard — TypeForm-style stepped modal. Sits on
   top of the welcome flow once a user finishes signup confirmation.
   Re-uses the same .settings-pill component as Your Settings so the
   visual choices are identical. */
.setup-modal {
  max-width: 540px;
  width: 100%;
  padding: 28px 28px 22px;
}
.setup-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 18px;
}
.setup-dots {
  display: inline-flex;
  gap: 6px;
}
.setup-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #d6cfb4;
}
.setup-dot.done   { background: var(--success); opacity: 0.7; }
.setup-dot.active { background: var(--accent); transform: scale(1.3); }
.setup-skip {
  font-size: 13px;
  color: var(--muted);
}
.setup-title {
  font-family: var(--font-display);
  font-size: 22px;
  margin: 0 0 14px;
  text-align: center;
}
.setup-body { margin-bottom: 22px; }
.setup-body .sg-label { display: none; } /* the step title says it */
.setup-lede {
  font-size: 15px;
  line-height: 1.5;
  margin: 0 0 12px;
  text-align: center;
}
.setup-fine {
  font-size: 12px;
  color: var(--muted);
  text-align: center;
  margin: 0;
}
.setup-footer {
  display: flex;
  justify-content: space-between;
  gap: 12px;
}
.setup-footer .invisible { visibility: hidden; }
.setup-footer button { padding: 10px 18px; font-size: 14px; min-width: 110px; }
.setup-footer button.primary { background: var(--accent); color: white; border: 0; }
@media (max-width: 599px) {
  .setup-modal { padding: 20px 18px 16px; }
  .setup-title { font-size: 18px; }
}

/* Readiness footer (#2): the per-seat ✓/⏳ chips moved from the top of the
   modal down to the bottom (near the Start-round button) and are centered. */
.meld-readiness {
  margin: 12px 0 2px;
  padding-top: 10px;
  border-top: 1px solid var(--paper-border);
  text-align: center;
}
.meld-readiness-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 6px;
}
.meld-ack-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  margin: 0;
}
.meld-ack-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  background: rgba(0,0,0,0.05);
  color: var(--muted);
}
.meld-ack-chip.on {
  background: #d8f1de;
  color: #14532d;
}
.meld-ack-chip.pending {
  background: #fdecc8;
  color: #6b4a00;
}
.meld-timer { color: var(--ink); margin: 0 0 8px; font-size: 12px; }
.meld-countdown {
  display: inline-block;
  min-width: 32px;
  text-align: right;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}

/* Modal buttons need primary contrast on the tan background. */
.overlay .actions button {
  background: var(--accent);
  color: white;
}
.overlay .actions button:hover { filter: brightness(1.1); }

/* ============================================================
   Simple hand summary view (default when Settings → Hand summary
   = Simple). One compact score table:
     Bid          [bid]
     Meld         us | them
     Tricks       us | them
     Hand Score   +Δ | -Δ
     Last Score   prev | prev
     Score        cur  | cur
   Mirrors the at-a-glance layout the user requested from a
   competitor app. The detailed tabbed view (meld review / trick
   review / cards left) is still available via the "Show advanced ›"
   button below the table.
   ============================================================ */
.simple-summary { padding: 4px 0 8px; }
.simple-summary-table {
  border-collapse: separate;
  border-spacing: 0;
  width: 100%;
  margin: 0 0 12px;
  background: #fbf6e3;
  border-radius: 10px;
  overflow: hidden;
  font-size: 18px;
}
.simple-summary-table th,
.simple-summary-table td {
  padding: 8px 10px;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  border-bottom: 1px solid rgba(0,0,0,0.08);
}
.simple-summary-table tr:last-child th,
.simple-summary-table tr:last-child td { border-bottom: 0; }
.simple-summary-table thead th {
  font-size: 14px;
  color: var(--muted);
  background: rgba(0,0,0,0.04);
  text-align: right;
}
.simple-summary-table thead th.team-us   { color: var(--team-us); }
.simple-summary-table thead th.team-them { color: var(--team-them); }
.simple-summary-table tbody th {
  text-align: left;
  font-weight: 800;
  color: var(--ink);
  width: 42%;
}
.simple-summary-table td.bid-row {
  text-align: center;
  font-size: 22px;
}
.simple-summary-table td.bid-row small {
  display: inline-block;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  margin-left: 6px;
}
.simple-summary-table tr.divider th,
.simple-summary-table tr.divider td {
  border-top: 2px solid rgba(0,0,0,0.12);
}
.simple-summary-table td.pos  { color: #1a7f37; }
.simple-summary-table td.neg  { color: var(--accent); }
.simple-summary-table td.zero { color: var(--muted); }
.simple-summary-table tr.totals th,
.simple-summary-table tr.totals td {
  background: rgba(0,0,0,0.05);
  font-size: 22px;
}
.simple-summary-advanced {
  display: flex;
  justify-content: center;
  margin-top: 6px;
}
.simple-summary-advanced .ghost { font-size: 13px; }

/* Summary table */
.summary-table, .rating-table {
  border-collapse: collapse;
  width: 100%;
  margin-top: 8px;
}
/* Fixed layout + explicit 50/50 widths on the You/Them columns so a
   long footnote ("0 (9 lost — no tricks)") in one cell doesn't push
   the column wider than its mirror. The label column stays auto so
   it shrinks to its content. */
.summary-table { table-layout: fixed; }
.summary-table col.label { width: auto; }
.summary-table col.team { width: 42%; }
.summary-table td, .summary-table th { word-wrap: break-word; }
.summary-table td small {
  display: block;
  font-size: 11px;
  color: var(--muted);
  font-weight: 400;
  margin-top: 2px;
}
.summary-table th, .summary-table td,
.rating-table th, .rating-table td {
  border: 1px solid #d6cfb4;
  padding: 6px 10px;
  text-align: right;
}
.summary-table th:first-child, .summary-table td:first-child,
.rating-table th:first-child, .rating-table td:first-child,
.rating-table th:nth-child(2), .rating-table td:nth-child(2) {
  text-align: left;
}
.summary-table tr.totals { background: #f9f3dd; font-weight: 700; }
.summary-table tr.subtotal td {
  color: var(--muted);
  font-size: 12px;
  border-top: 0;
  padding-top: 6px;
  padding-bottom: 4px;
}

.game-over-banner {
  margin-top: 16px;
  padding: 14px;
  background: linear-gradient(135deg, #fff2c9, #ffd966);
  border: 2px solid #b8860b;
  border-radius: 10px;
  text-align: center;
  font-size: 18px;
  font-weight: 700;
}
.game-over-banner.win {
  background: linear-gradient(135deg, #d8f5d2, #5fbf6c);
  border-color: #1a7f37;
  color: #0f4d22;
}
.game-over-banner.lose {
  background: linear-gradient(135deg, #f8d4d4, #d97070);
  border-color: var(--accent);
  color: #6b1320;
}

/* Bidding history details */
details.history {
  margin-top: 10px;
  font-size: 12px;
  color: #cbd5e1;
}
details.history summary { cursor: pointer; opacity: 0.8; }
details.history ol { padding-left: 18px; margin-top: 6px; }

/* ==========================================================
   Responsive tiers — desktop ≥900 / tablet 600-899 / mobile <600.
   Trick area + your hand are always visible; everything else
   progressively collapses into the ☰ menu drawer.
   ========================================================== */

/* Default: hide the compact-only menu button on wide screens. */
.compact-only { display: none !important; }

/* Short-viewport (typical laptop landscape ~660-768px tall): cards and
   trick area together exceeded the available vertical space, clipping
   the bottom of the hand. Shrink cards proportionally so the trick
   cross + hand + seats all fit. Width restriction prevents this from
   firing on tall portrait phones where the mobile breakpoints already
   take care of sizing. */
@media (max-height: 760px) and (min-width: 600px) {
  :root {
    --card-w: 75px;
    --card-h: 105px;
    --hand-overlap: -48px;
    --hand-suit-gap: 10px;
    --mini-card-w: 42px;
    --mini-card-h: 60px;
    --trick-slot-w: 80px;
    --trick-slot-h: 112px;
    --trick-cross-gap: 6px;   /* tighter on short screens */
  }
  .trick-area { min-height: calc(var(--card-h) * 2 + var(--trick-cross-gap) * 2 + 8px); }
  .your-hand { min-height: calc(var(--card-h) + 4px); padding-bottom: 4px; }
  .card .corner { width: 22px; }
  .card .corner .rank { font-size: 17px; }
  .card .corner .suit { font-size: 19px; }
  .card .center { font-size: 44px; }
}

/* Tablet — shrink cards & paddings a bit, keep all info readable. */
@media (max-width: 899px) {
  :root {
    --card-w: 82px;
    --card-h: 118px;
    --hand-overlap: -54px;
    --hand-suit-gap: 12px;
    --mini-card-w: 47px;
    --mini-card-h: 68px;
    --trick-slot-w: 90px;
    --trick-slot-h: 128px;
    --card-back-w: 32px;
    --card-back-h: 48px;
    --trick-cross-gap: 10px;
    --tag-fs: 12px;
    --status-fs: 12px;
  }
  .table-surface {
    grid-template-columns: minmax(100px, 1fr) minmax(280px, 2.6fr) minmax(100px, 1fr);
    padding: 12px;
    gap: 6px;
  }
  .status-bar { padding: 6px 12px; font-size: var(--status-fs); }
  .seat-tag { font-size: var(--tag-fs); padding: 5px 8px; }
  .seat-tag .badge { font-size: 10px; padding: 1px 5px; }
  .card .corner { width: 24px; }
  .card .corner .rank { font-size: 20px; }
  .card .corner .suit { font-size: 21px; }
  .card .center { font-size: 50px; }
  .card.mini .corner .rank { font-size: 14px; }
  .card.mini .corner .suit { font-size: 15px; }
  .card.mini .center { font-size: 26px; }
  .opp-avatar { font-size: 38px; min-height: 44px; }
  .your-hand { min-height: 100px; padding-bottom: 4px; }
  .cards-row .card-back { margin-left: -14px; }
}

/* ==========================================================
   MOBILE — sweeping pass for ≤599px.
   Goals: every essential interaction reachable with one thumb, no horizontal
   overflow, modals as bottom-sheets, 44pt minimum tap targets.

   Core layout strategy: keep the 3-column grid, but make the side columns
   ultra-thin (just enough for opponent emoji + 1-line label). Bottom seat
   tag goes ABOVE the hand so the action bar reads naturally next to it.
   ========================================================== */
@media (max-width: 599px) {
  :root {
    --card-w: 65px;
    --card-h: 92px;
    /* Looser overlap on phones (-34 instead of -42) so each visible card
       strip is ~31px wide — close enough to the 44px recommended tap
       target to feel reliable. The 2-row split fires earlier on tiny
       phones so the wider per-card visibility doesn't overflow. */
    --hand-overlap: -34px;
    --hand-suit-gap: 8px;
    /* Bumped mini-card from 35x50 to 48x68 — at 35px wide the meld
       modal's corner-rank text (14px) and centered glyph (26px from
       the tablet override) overwhelmed the card and the design read
       as broken. 48x68 leaves the same fonts proportional and gives
       cards a readable footprint; the meld modal scrolls if needed. */
    --mini-card-w: 48px;
    --mini-card-h: 68px;
    --trick-slot-w: 70px;
    --trick-slot-h: 100px;
    --trick-cross-gap: 6px;
    --tag-fs: 11px;
    --status-fs: 11px;
  }

  .wide-only { display: none !important; }
  .compact-only { display: inline-flex !important; }
  body, html { font-size: 13px; }

  /* ---- Status bar ---- */
  .status-bar {
    padding: 6px 10px;
    gap: 8px;
    min-height: 40px;
  }
  .status-info { gap: 6px; font-size: 12px; flex: 1 1 auto; min-width: 0; }
  .status-info #status-phase {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .status-scores { gap: 6px; flex: 0 0 auto; }
  .bid-pill { padding: 1px 8px; font-size: 12px; }
  .bid-pill .suit-red, .bid-pill .suit-black { font-size: 14px; }
  /* During play, drop the game score on phones so the contract pill (trump +
     "X to go") has room. The score still shows during bidding. (#2) */
  .status-bar.phase-play #status-game { display: none; }
  /* Mobile status-bar buttons — the gear and hamburger have to be
     visibly tap-target-sized (Apple HIG recommends 44pt; we're at
     44px here) and the glyph itself must read as a button, not as
     a faint text character. !important to overpower any inherited
     ghost-button shrinkage from other rules. */
  #btn-menu,
  #btn-settings-compact,
  /* Tracker + meld-hint now appear in the mobile status bar too
     (their wide-only gate was lifted so the toggle has a visible
     effect on phones). Use the same comfortable 44x44 tap target
     and glyph styling as the gear / hamburger so the row reads
     consistently. */
  #btn-tracker,
  #btn-meld-hint {
    width: 44px !important;
    height: 44px !important;
    padding: 0 !important;
    font-size: 26px !important;
    line-height: 1 !important;
    flex: 0 0 auto !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    border-radius: 12px !important;
    background: rgba(255,255,255,0.07) !important;
  }
  /* `hidden` must still win — `!important display:inline-flex` would
     otherwise force the icon visible even when the user has the
     setting off. */
  #btn-tracker[hidden],
  #btn-meld-hint[hidden] { display: none !important; }

  /* ---- Table surface: tight side columns, generous middle ----
     48px was too narrow once the empty-seat tag picked up two action
     pucks + the "East Seat / West Seat" label; the West tag clipped
     off the right edge of the screen. 72px holds the tag without
     clipping and still leaves ~230px for the center column on a
     375px phone — plenty for the trick cross. */
  .table-surface {
    grid-template-columns: 72px minmax(0, 1fr) 72px;
    grid-template-rows: minmax(60px, auto) minmax(0, 1fr) minmax(48px, auto) minmax(72px, auto);
    padding: 6px;
    gap: 4px;
    border-width: 4px;
    margin: 3px;
  }

  /* Hide opponents' card-back fans on mobile — too much horizontal space. */
  .seat-area .cards-row { display: none; }

  /* Compress side seats to a vertical chip: emoji on top, tiny name. */
  .seat-left, .seat-right {
    padding: 0;
  }
  .seat-area.seat-left .seat-tag,
  .seat-area.seat-right .seat-tag {
    flex-direction: column;
    padding: 3px 4px;
    gap: 2px;
    font-size: 10px;
    text-align: center;
    max-width: 100%;
  }
  .seat-area.seat-left .seat-tag .name,
  .seat-area.seat-right .seat-tag .name {
    max-width: 44px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: inline-block;
  }
  .seat-area.seat-left .seat-tag .badge,
  .seat-area.seat-right .seat-tag .badge {
    font-size: 9px;
    padding: 0 4px;
  }

  /* Top seat (partner) keeps horizontal layout but compressed. */
  .seat-tag {
    padding: 3px 8px;
    font-size: var(--tag-fs);
    gap: 4px;
    min-height: 28px;
  }
  .seat-tag .badge { font-size: 9px; padding: 0 5px; }
  .seat-tag .empty-actions button {
    font-size: 11px;
    padding: 7px 10px;
    min-height: 38px;   /* bigger touch target — these were fiddly to tap (#2) */
  }
  /* Side seats: text Invite/Bot buttons stack vertically inside the
     72px column so each label has the full column width to render
     without clipping. The empty tag is sized exactly to the column. */
  .seat-area.seat-left .seat-tag.empty,
  .seat-area.seat-right .seat-tag.empty {
    padding: 6px 4px;
    width: 100%;
    box-sizing: border-box;
    gap: 5px;
    overflow: hidden;
  }
  .seat-area.seat-left .seat-tag.empty .empty-actions,
  .seat-area.seat-right .seat-tag.empty .empty-actions {
    flex-direction: column;
    gap: 4px;
    margin-left: 0;
    margin-top: 2px;
    width: 100%;
    align-items: stretch;
  }
  .seat-area.seat-left .seat-tag.empty .seat-action-btn,
  .seat-area.seat-right .seat-tag.empty .seat-action-btn {
    padding: 7px 4px;
    font-size: 11px;
    min-height: 36px;   /* bigger touch target on the narrow side columns (#2) */
    width: 100%;
  }
  /* And the seat-name on side seats should be allowed to display
     full-width inside its column — undo the 44px max-width clamp for
     empty seats so "West Seat" doesn't wrap. */
  .seat-area.seat-left .seat-tag.empty .seat-pos,
  .seat-area.seat-right .seat-tag.empty .seat-pos {
    max-width: none;
    font-size: 11px;
  }

  /* Bidder trump medallion shrinks but stays inline with the seat tag row. */
  .bidder-trump-flag { padding: 2px 8px; gap: 4px; font-size: 11px; }
  .bidder-trump-flag .trump-flag-bid { font-size: 13px; }
  .bidder-trump-flag .trump-flag-suit { font-size: 18px; }
  .seat-tag-row { gap: 4px; flex-wrap: wrap; }

  /* Trick area cross — keep the four-card cross but a bit smaller. */
  .trick-area { padding: 4px; }

  /* Card sizing already set above via vars. Tighter corner styling. */
  .card .corner { width: 16px; }
  .card .corner .rank { font-size: 13px; line-height: 1; }
  .card .corner .suit { font-size: 14px; }
  .card .center { font-size: 30px; }
  .card.mini .corner .rank { font-size: 9px; }
  .card.mini .corner .suit { font-size: 10px; }
  .card.mini .center { font-size: 16px; }
  .opp-avatar { font-size: 28px; min-height: 32px; }

  /* Your hand — fanned, full-width, scrolls horizontally if absolutely needed.
     With 12 cards × (52 + -34) overlap = 250px hand, fits on any phone.
     Generous top padding so the "READY" badge above a pre-selected card
     never gets clipped. NOTE: overflow-x:auto makes the browser clip the
     OTHER axis too (CSS spec — overflow-y:visible is computed as auto when
     overflow-x isn't visible), so the badge above a lifted card is clipped
     at this padding edge. The ready lift is -18px and the badge sits ~8px
     above that (~26px total), so the top padding must clear it — bumped
     16px → 30px after the v1.2.8 ready-lift increase. */
  .your-hand {
    min-height: 88px;
    padding: 30px 6px 6px;
    justify-content: center;
    overflow-x: auto;
    overflow-y: visible;
    scrollbar-width: none;
  }
  .your-hand::-webkit-scrollbar { display: none; }
  /* Disable hover-lift on touch — it leaves cards "stuck" up on phones.
     Same guard as :active below: this rule is more specific than
     .card.passing/.ready/.selected and iOS keeps :hover stuck on the
     tapped card, so without :not(...) it forced a SELECTED card's lift to
     `none` (card showed its ring but stayed flat until the next tap). */
  .your-hand .card.clickable:not(.passing):not(.ready):not(.selected):hover {
    transform: none;
  }
  /* Press feedback ONLY for cards that aren't already in a lifted state.
     CRITICAL: this :active rule is more specific than .card.passing/.ready/
     .selected, and on iOS :active STICKS to the tapped element until the next
     tap. Without the :not(...) guard, tapping a card to select it left it at
     the -6px press lift instead of the -30px selected lift — and it only
     jumped to the real lift when the NEXT tap moved :active off it. That was
     the "the card only raises after I tap the next one" bug. Excluding the
     selected states lets .passing/.ready/.selected own the transform. */
  .your-hand .card.clickable:not(.passing):not(.ready):not(.selected):active {
    transform: translateY(-6px);
  }
  .card.ready:not(.preview-card) { transform: translateY(-18px); }
  /* Move the badge inside the bounds of the row so even with tight rows
     above (e.g. seat tags), it never gets clipped. */
  .your-hand .card.ready::after {
    top: -8px;
    font-size: 8px;
    padding: 2px 5px;
  }

  /* ---- Action bar — single row, big tap targets ---- */
  .action-bar {
    padding: 8px 10px;
    gap: 8px;
    flex-wrap: wrap;
    min-height: 56px;
    font-size: 13px;
    align-content: center;
  }
  /* On phones the label takes its own row (flex: 100%). Center it so
     prompts like "Select 4 cards to send to X (4/4 selected)" sit
     under the Send button cleanly instead of hanging on the left. */
  .action-bar .label { flex: 1 1 100%; font-size: 12px; text-align: center; }
  .action-bar button {
    min-height: 40px;
    padding: 10px 12px;
    font-size: 14px;
  }
  .action-bar button.big {
    min-height: 44px;
    padding: 12px 16px;
    font-size: 15px;
  }
  .action-bar input { width: 84px; min-height: 40px; font-size: 16px; /* 16px = no zoom on iOS */ }
  .action-bar .quick-jump { min-width: 40px; }
  .action-bar .countdown { flex: 0 0 auto; font-size: 12px; }
  .action-bar .surrender-btn { font-size: 12px; padding: 7px 10px; }
  .play-again-tally { flex: 1 1 100%; justify-content: center; }

  /* ---- Modals — bottom-sheet style ---- */
  .overlay {
    align-items: flex-end;
  }
  .overlay .modal {
    width: 100%;
    max-width: 100%;
    max-height: 92dvh;
    border-radius: 16px 16px 0 0;
    padding: 16px;
    margin: 0;
    animation: sheet-up 220ms cubic-bezier(.2,.7,.3,1);
  }
  @keyframes sheet-up {
    from { transform: translateY(20%); opacity: 0; }
    to   { transform: translateY(0);    opacity: 1; }
  }
  .overlay .modal h2 { font-size: 18px; margin-bottom: 8px; }
  .overlay .modal h3 { font-size: 12px; margin: 14px 0 6px; }
  .modal-header {
    position: sticky;
    top: -16px;
    margin: -16px -16px 8px;
    padding: 12px 16px 8px;
    background: var(--paper);
    border-bottom: 1px solid var(--paper-border);
    border-radius: 16px 16px 0 0;
    z-index: 1;
  }
  .modal-header h2 { margin: 0; font-size: 17px; }
  .modal-header .actions-top button {
    min-height: 36px;
    padding: 7px 12px;
  }
  /* When the button's only visible label is a single icon (.compact-only),
     drop it to a square tap target for cleaner geometry. */
  .modal-header .actions-top button .compact-only {
    font-size: 18px;
    font-weight: 700;
    line-height: 1;
  }

  /* Meld grid collapses, simpler scoring panel. Cards in each item spread
     out a little more on small screens — the default overlap (-22px) reads
     as a stack, not a fan. */
  .meld-grid { grid-template-columns: 1fr; gap: 10px; }
  .meld-card { padding: 10px 12px; }
  .meld-totals { gap: 16px; font-size: 14px; }
  .meld-item-cards .card.mini { margin-left: -14px; }
  .meld-item-cards .card.mini.suit-break { margin-left: 8px; }
  .meld-simple-cards .card.mini { margin-left: -14px; }
  .meld-simple-cards .card.mini.suit-break { margin-left: 8px; }
  /* Clean-meld rows: narrower name pill, allow the fan to wrap. */
  .meld-row { grid-template-columns: 126px 1fr; gap: 8px; padding: 6px 8px; min-height: 56px; }
  .meld-row-name { padding: 4px 8px; min-height: 40px; gap: 4px; }
  .meld-row-username { font-size: 12px; }
  .meld-row-emoji { font-size: 16px; }
  .meld-row-bidcrown { font-size: 12px; }
  .meld-row-points { font-size: 17px; min-width: 24px; padding: 0 5px; }
  .meld-team-totals { padding: 6px 10px; font-size: 12px; }
  .meld-team-totals strong { font-size: 16px; }
  /* Slightly bigger mini cards in the meld so suits read at a glance. */
  :root { --mini-card-w: 40px; --mini-card-h: 58px; }
  .card.mini .corner .rank { font-size: 11px; }
  .card.mini .corner .suit { font-size: 12px; }
  .card.mini .center { font-size: 20px; }
  .summary-bid-amount { font-size: 22px; }

  /* Auth + lobby tweaks. */
  .auth-wrapper { padding: 24px 12px; }
  .auth-wrapper .panel { width: 100%; max-width: 360px; }
  /* Compact topbar — small wordmark + 3 icon buttons. Profile becomes the
     user's emoji avatar, settings is a gear, logout is ⎋. */
  .topbar { padding: 10px 12px; gap: 8px; }
  .topbar h1 { font-size: 16px; }
  .topbar .wordmark .wm-just { font-size: 14px; padding-right: 0.3em; }
  .topbar .wordmark .wm-pin { font-size: 16px; letter-spacing: 0.04em; }
  #user-bar { gap: 6px; }
  #user-bar .user-bar-icon,
  #user-bar .user-bar-emoji {
    width: 36px;
    height: 36px;
    min-width: 36px;
    padding: 0;
    border-radius: 999px;
    font-size: 16px;
    line-height: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  #user-bar .user-bar-emoji { font-size: 20px; }
  .lobby-wrapper { padding: 12px 10px; gap: 12px; }
  .lobby-actions { gap: 8px; flex-wrap: nowrap; }
  /* Side by side, not stacked (#3). Start a game keeps more of the row so it
     still reads as the primary action; How to play takes the smaller share. */
  .lobby-actions button.primary.big,
  .lobby-actions .lobby-howto {
    padding: 15px 10px;
    font-size: 15px;
    min-width: 0;        /* let them shrink to share the row */
    white-space: nowrap;
  }
  .lobby-actions button#btn-start-game.primary.big { flex: 1.4 1 0; }
  .lobby-actions .lobby-howto { flex: 1 1 0; }
  .online-grid {
    grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
    gap: 6px;
  }
  .player-chip {
    padding: 10px 4px 8px;
    min-height: 108px;
    gap: 3px;
  }
  .player-chip .player-emoji { font-size: 40px; }
  .player-chip .player-name { font-size: 11px; }
  .player-chip .player-rating { font-size: 13px; }
  .player-chip .you-tag { font-size: 9px; padding: 0 4px; }

  /* Lobby table-list — single line per row, big Join button. */
  #table-list li {
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
    padding: 10px 12px;
  }
  #table-list .table-actions { display: flex; gap: 8px; }
  #table-list .table-actions button { flex: 1 1 auto; min-height: 38px; }
  #table-list .close-table-btn { flex: 0 0 auto; min-width: 38px; }

  /* Rejoin banner stacks vertically. */
  .rejoin-banner { flex-direction: column; align-items: stretch; padding: 10px 12px; gap: 8px; }
  .rejoin-banner button { width: 100%; min-height: 40px; }
  /* Admin-msg banner: keep the icon + text on one row but allow the
     CTA chevron to wrap below if the message is long on a narrow phone. */
  .admin-msg-banner { padding: 10px 12px; gap: 10px; }
  .admin-msg-banner .amb-icon { font-size: 24px; }
  .admin-msg-banner .amb-cta { font-size: 13px; }

  /* Vote-extend modal: stack tally chips and button. */
  .leave-actions button { width: 100%; }
}

/* ==========================================================
   Mobile — BIG display size (default on phones)

   Defaults bump seat tags, badges, opponent avatars, bot names, and bid
   amounts up to readable sizes for elderly / low-vision users. Users who
   prefer the previous tight layout flip the toggle to "Compact" in
   Your Settings → Display size on phones.
   ========================================================== */
@media (max-width: 599px) {
  body.display-size-big .seat-tag {
    font-size: 14px;
    padding: 6px 10px;
    gap: 6px;
    min-height: 36px;
  }
  body.display-size-big .seat-tag .name { font-size: 14px; }
  body.display-size-big .seat-tag .badge {
    font-size: 13px;
    padding: 2px 8px;
    font-weight: 700;
  }
  body.display-size-big .seat-tag .badge.bidder { font-size: 14px; padding: 2px 10px; }
  body.display-size-big .seat-tag .empty-actions button {
    font-size: 13px;
    padding: 8px 14px;
    min-height: 36px;
  }
  /* Side seats stay vertical but get bigger emoji + name */
  body.display-size-big .seat-area.seat-left .seat-tag,
  body.display-size-big .seat-area.seat-right .seat-tag {
    font-size: 12px;
    padding: 6px 6px;
    gap: 4px;
  }
  body.display-size-big .seat-area.seat-left .seat-tag .name,
  body.display-size-big .seat-area.seat-right .seat-tag .name {
    max-width: 56px;
    font-size: 12px;
  }
  body.display-size-big .seat-area.seat-left .seat-tag .badge,
  body.display-size-big .seat-area.seat-right .seat-tag .badge {
    font-size: 11px;
    padding: 1px 6px;
  }
  body.display-size-big .opp-avatar { font-size: 38px; min-height: 44px; }

  /* Empty-seat 'Seat S · empty' tag specifically — that was the smallest
     element in the screenshot. */
  body.display-size-big .seat-tag.empty { font-size: 14px; padding: 8px 12px; }

  /* Bid pill in the in-bidding bidder seat */
  body.display-size-big .bidder-trump-flag { padding: 4px 12px; gap: 6px; font-size: 14px; }
  body.display-size-big .bidder-trump-flag .trump-flag-bid { font-size: 17px; }
  body.display-size-big .bidder-trump-flag .trump-flag-suit { font-size: 26px; }

  /* Status bar — phase / bid pill / scores */
  body.display-size-big .status-bar { font-size: 13px; min-height: 44px; }
  body.display-size-big .bid-pill { font-size: 13px; padding: 3px 10px; }
  body.display-size-big .bid-pill .suit-red,
  body.display-size-big .bid-pill .suit-black { font-size: 16px; }

  /* Slightly bigger main hand cards too */
  body.display-size-big {
    --card-w: 72px;
    --card-h: 102px;
    --hand-overlap: -48px;
    --hand-suit-gap: 9px;
  }
  body.display-size-big .card .corner .rank { font-size: 18px; }
  body.display-size-big .card .corner .suit { font-size: 19px; }
  body.display-size-big .card .center { font-size: 40px; }
}

/* ==========================================================
   SUNDAY GAME mobile mode — milestone v0.10.0.
   Goal: a phone layout that reads like a card game at a real
   kitchen table, with cards big enough for older eyes. Targets
   phones ≤599px. Builds ON TOP of display-size-big (which is
   also set), so this section only contains the additional
   boosts. Side seats become big avatar tiles instead of skinny
   columns, hand cards balloon, trick cross is full size.
   ========================================================== */
@media (max-width: 599px) {
  /* Card stock is the marquee change: ~50% bigger than big-mode
     defaults. Tight overlap (-58) keeps the typical 6-card row
     inside a 393px iPhone (100 + 5*42 = 310 vs ~335px usable).
     Suit-gap is set equal to overlap so a suit change doesn't
     balloon the row off-screen — visual suit breaks are sacrificed
     to keep every card on screen. The bidder's 16-card 8/8 split
     still overflows slightly; that state is transient (just the
     pre-pass moment) so we accept the trim. */
  body.display-size-sunday {
    --card-w: 100px;
    --card-h: 142px;
    --hand-overlap: -58px;
    --hand-suit-gap: -58px;
    --mini-card-w: 60px;
    --mini-card-h: 86px;
    --trick-slot-w: 110px;
    --trick-slot-h: 156px;
    --trick-cross-gap: 8px;
  }
  body.display-size-sunday .card .corner .rank { font-size: 24px; }
  body.display-size-sunday .card .corner .suit { font-size: 24px; }
  body.display-size-sunday .card .center { font-size: 56px; }

  /* Side seats: drop the skinny 72px column and give them a real
     tile — 100px so the avatar (opp-avatar) reads as a face/photo
     and the name pill underneath has room. */
  body.display-size-sunday .table-surface {
    grid-template-columns: 100px minmax(0, 1fr) 100px;
    grid-template-rows: minmax(64px, auto) minmax(0, 1fr) minmax(64px, auto) minmax(64px, auto);
    gap: 6px;
  }
  /* Bigger opponent avatar so it reads as a face from across the
     room. The 1.05 line-height keeps the emoji centered visually. */
  body.display-size-sunday .opp-avatar {
    font-size: 64px;
    min-height: 72px;
    line-height: 1.05;
  }

  /* Seat tags: bigger pill, name reads as a banner. Side seats keep
     their stacked layout but get the wider 100px column. */
  body.display-size-sunday .seat-tag {
    font-size: 15px;
    padding: 8px 12px;
    min-height: 40px;
    gap: 6px;
  }
  body.display-size-sunday .seat-tag .name { font-size: 15px; }
  body.display-size-sunday .seat-area.seat-left .seat-tag,
  body.display-size-sunday .seat-area.seat-right .seat-tag {
    font-size: 13px;
    padding: 6px 8px;
  }
  body.display-size-sunday .seat-area.seat-left .seat-tag .name,
  body.display-size-sunday .seat-area.seat-right .seat-tag .name {
    max-width: 84px;
    font-size: 13px;
  }
  /* Empty-seat tile (side): keep the vertical stack of buttons but
     give each enough padding to feel like a real button. */
  body.display-size-sunday .seat-area.seat-left .seat-tag.empty .seat-pos,
  body.display-size-sunday .seat-area.seat-right .seat-tag.empty .seat-pos {
    font-size: 13px;
  }
  body.display-size-sunday .seat-area.seat-left .seat-tag.empty .seat-action-btn,
  body.display-size-sunday .seat-area.seat-right .seat-tag.empty .seat-action-btn {
    padding: 7px 6px;
    font-size: 12px;
    min-height: 32px;
  }

  /* Status bar — phase / scores at the top reads big enough that an
     older player can glance at it without leaning in. */
  body.display-size-sunday .status-bar {
    font-size: 15px;
    min-height: 52px;
    padding: 10px 14px;
  }
  body.display-size-sunday .bid-pill { font-size: 15px; padding: 5px 12px; }
  body.display-size-sunday .bid-pill .suit-red,
  body.display-size-sunday .bid-pill .suit-black { font-size: 18px; }

  /* Two-row hand: bigger min-height to leave room for the default
     small lift on selection without clipping into the trick area. */
  body.display-size-sunday .your-hand { min-height: calc(var(--card-h) + 8px); }
  body.display-size-sunday .your-hand.hand-two-rows {
    min-height: calc(var(--card-h) * 1.6);
  }

  /* Selection ring boost — with the default 14px lift being a
     smaller percentage of a 142px-tall card, the gold/blue ring
     does most of the "you picked this" work. Make the ring thicker
     and the glow brighter so it reads at a glance. */
  body.display-size-sunday .card.selected {
    box-shadow: 0 0 0 4px var(--gold), 0 18px 26px rgba(15,20,25,0.45);
  }
  body.display-size-sunday .card.passing {
    box-shadow: 0 0 0 4px #2e90c4, 0 18px 26px rgba(15,20,25,0.45);
  }
  body.display-size-sunday .card.ready {
    box-shadow: 0 0 0 4px #3b82f6, 0 14px 22px rgba(15,20,25,0.4);
  }

  /* Action bar lower controls — bigger pills/buttons so they're
     thumb-friendly without trial-and-error. */
  body.display-size-sunday .action-bar {
    font-size: 15px;
    min-height: 64px;
    padding: 12px 16px;
    gap: 14px;
  }
  body.display-size-sunday .action-bar button {
    min-height: 44px;
    padding: 10px 16px;
    font-size: 15px;
  }

  /* Traditional card style on Sunday-sized cards: bump the centred
     rank/suit stack proportionally so it reads "tall" instead of
     small-on-a-big-card. */
  body.display-size-sunday .card.traditional .trad-rank {
    font-size: calc(var(--card-w) * 0.6);
  }
  body.display-size-sunday .card.traditional .trad-suit {
    font-size: calc(var(--card-w) * 0.46);
  }
}

/* Menu drawer specifics — used on mobile. */
.menu-modal { max-width: 360px; }

/* Compact phase-aware scoreboard (v1.2.0). Replaces the old verbose
   You/Game/Trump/Phase label table — leads with the score, then a
   trump chip + phase-specific detail, then a "Your turn" highlight. */
.menu-status {
  background: white;
  border: 1px solid #d6cfb4;
  border-radius: 10px;
  padding: 12px 14px;
  margin-bottom: 14px;
  text-align: center;
}
.menu-status-phase { color: var(--muted); font-style: italic; font-size: 14px; }
.menu-score {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 4px 12px;
}
.menu-score .ms-side { display: inline-flex; align-items: baseline; gap: 6px; }
.menu-score .ms-lbl {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--muted);
}
.menu-score .ms-val {
  font-size: 26px; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; color: var(--ink);
}
.menu-score .ms-dash { font-size: 18px; color: var(--muted); }
.menu-score .ms-target { flex-basis: 100%; font-size: 11px; color: var(--muted); }
.menu-status-line {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: 8px; margin-top: 8px; font-size: 13px;
}
.menu-trump {
  display: inline-flex; align-items: center; gap: 4px;
  font-weight: 700; padding: 2px 10px; border-radius: 999px; background: rgba(0,0,0,0.05);
}
.menu-trump.red   { color: var(--card-red); }
.menu-trump.black { color: var(--card-black); }
.menu-trump.tbd   { color: var(--muted); font-weight: 600; font-style: italic; }
.menu-detail { color: var(--ink-soft); }
.menu-detail strong { color: var(--ink); }
.menu-turn-row { margin-top: 8px; }
.menu-turn { font-size: 12px; color: var(--muted); }
.menu-turn.you {
  color: #14543b; font-weight: 800; background: rgba(20,84,59,0.10);
  padding: 3px 12px; border-radius: 999px;
}

/* Contextual quick-tools row (My meld / Suit tracker). */
.menu-tools { display: flex; gap: 8px; margin-bottom: 12px; }
.menu-tool {
  flex: 1; padding: 12px 8px; font-size: 13px; font-weight: 600;
  background: rgba(20,84,59,0.08); color: var(--ink);
  border: 1px solid rgba(20,84,59,0.25); border-radius: 10px; cursor: pointer;
}
.menu-tool:hover { background: rgba(20,84,59,0.14); }

.menu-actions { display: flex; flex-direction: column; gap: 8px; }
.menu-actions button { width: 100%; padding: 13px 12px; font-size: 14px; }
/* #5: the menu now hosts every in-game action — lay them out as a tidy
   2-column grid of neutral (non-coral) buttons so the list stays compact and
   doesn't read as eight competing CTAs. */
.menu-actions.menu-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.menu-actions.menu-grid button {
  width: 100%; padding: 12px 10px; font-size: 13px; font-weight: 600;
  background: rgba(20,84,59,0.08); color: var(--ink);
  border: 1px solid rgba(20,84,59,0.22); border-radius: 10px;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  box-shadow: none;
}
.menu-actions.menu-grid button:hover { background: rgba(20,84,59,0.15); }
.menu-actions.menu-grid button[disabled] { opacity: 0.5; cursor: default; }

/* Demoted "My profile" — secondary text-style link, not a big pill. */
.menu-secondary {
  display: block; width: 100%; margin: 10px 0 4px;
  background: transparent; border: 0; color: var(--ink-soft);
  font: inherit; font-size: 13px; padding: 6px; cursor: pointer; text-decoration: underline;
}
.menu-secondary:hover { color: var(--accent); }

/* Danger zone — exit actions, visually separated from the rest. */
.menu-danger {
  display: flex; flex-direction: column; gap: 8px;
  margin-top: 10px; padding-top: 12px; border-top: 1px solid #e6dcc0;
}
.menu-danger button { width: 100%; padding: 13px 12px; font-size: 14px; }

.menu-version { text-align: center; margin: 12px 0 0; color: var(--muted); opacity: 0.7; }

/* ==========================================================
   App footer — pinned at the bottom of the auth and lobby views
   and reproduced inside the in-table Settings menu, so privacy /
   terms / contact links are always reachable. Visible on light
   panels and on the dark felt-table app shell, so colors are
   chosen to read on either.
   ========================================================== */
.app-footer {
  margin: 32px auto 16px;
  max-width: 760px;
  width: calc(100% - 32px);
  padding: 14px 18px;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.10);
  border-radius: 12px;
  font-size: 13px;
  color: rgba(255,255,255,0.8);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.app-footer .footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: center;
}
.app-footer a,
.app-footer button.footer-link {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: rgba(255,255,255,0.25);
  text-underline-offset: 3px;
  background: transparent;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
.app-footer a:hover,
.app-footer button.footer-link:hover {
  text-decoration-color: rgba(255,255,255,0.7);
  color: white;
  filter: none;
  transform: none;
  box-shadow: none;
}
/* "♥ Support" footer link — tinted gold so it stands a bit apart
   from Privacy / Terms / Contact without shouting. */
.app-footer .footer-link-support {
  color: var(--gold);
  text-decoration-color: rgba(212,169,42,0.45);
  font-weight: 600;
}
.app-footer .footer-link-support:hover {
  color: #ffd966;
  text-decoration-color: rgba(212,169,42,0.85);
}
/* Settings → Account "♥ Support" pill — same pill shape as the
   other ghost actions (Messages / Change email / Run wizard) but
   tinted red so it visually invites the click. Mirrors the .danger
   pill (Delete account) in color, with a soft red fill on hover. */
.account-actions .btn-support {
  color: var(--accent);
  border-color: rgba(212, 83, 63, 0.45);
}
.account-actions .btn-support:hover {
  background: rgba(212, 83, 63, 0.08);
  border-color: var(--accent);
}
/* Native app (Capacitor / iOS): hide every donation / external-payment link.
   Apple Guideline 3.1.1 forbids linking out to payment from inside the app.
   <html class="native-app"> is set by app.js when running in the wrapper. */
.native-app .footer-link-support,
.native-app .landing-support,
.native-app .btn-support,
.native-app .server-full-donate {
  display: none !important;
}
/* Native app: also hide the cookie-consent banner. ga.js already early-returns
   in native (no GA, no banner build), but this is a belt-and-suspenders guard
   so a stale-cache visitor or future regression never shows a tracking-consent
   UI inside the iOS wrapper (Apple 5.1.2(i)). */
.native-app .cookie-consent,
.native-app .cookie-bar,
.native-app #cookie-consent {
  display: none !important;
}
.app-footer .footer-tag {
  color: rgba(255,255,255,0.55);
  font-size: 12px;
}
/* Build version stamp in the footer (populated by /version.js). Quiet,
   monospaced-ish digits; collapses to nothing if the version didn't load. */
.footer-version { opacity: 0.85; font-variant-numeric: tabular-nums; white-space: nowrap; }
.footer-version:empty { display: none; }
/* Standalone legal pages get the version centered under the article. */
.legal-version {
  margin: 28px 0 8px;
  text-align: center;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.legal-version:empty { display: none; }

/* Same footer reused inside the Settings menu (light card on tan). */
.menu-modal .app-footer,
.modal .app-footer {
  background: var(--paper);
  border-color: var(--paper-border);
  color: var(--ink);
  margin: 14px 0 0;
  width: 100%;
}
.menu-modal .app-footer a,
.modal .app-footer a,
.menu-modal .app-footer button.footer-link,
.modal .app-footer button.footer-link {
  color: var(--ink);
  text-decoration-color: rgba(15,20,25,0.3);
}
.menu-modal .app-footer a:hover,
.modal .app-footer a:hover,
.menu-modal .app-footer button.footer-link:hover,
.modal .app-footer button.footer-link:hover {
  color: var(--accent);
  text-decoration-color: var(--accent);
}
.menu-modal .app-footer .footer-tag,
.modal .app-footer .footer-tag { color: var(--muted); }

@media (max-width: 599px) {
  /* Keep the row layout on phones; just tighten padding and gaps so it
     still fits. Stacking vertically (the previous behavior) made the
     footer feel like a separate section instead of a slim line at the
     bottom. */
  .app-footer { padding: 10px 12px; gap: 8px; font-size: 12px; }
  .app-footer .footer-tag { font-size: 11px; }
  .app-footer .footer-links { gap: 10px; }
}

/* ==========================================================
   Contact form modal — opened from any footer's "Contact" link.
   Submits to POST /api/contact which routes to webmaster@
   justpinochle.com via SES.
   ========================================================== */
.overlay .modal.contact-modal { width: min(460px, calc(100% - 32px)); }

/* Advanced "Tournament rules" disclosure (v0.20.3). Quietly tucked
   below the main Advanced fields — most hosts don't need to touch
   it, but the depth is one click away when they do. */
.sg-tournament {
  margin: 8px 0 4px;
  border: 1px solid rgba(0,0,0,0.08);
  border-radius: 10px;
  background: rgba(0,0,0,0.025);
}
.sg-tournament > summary {
  cursor: pointer;
  list-style: none;
  padding: 10px 14px;
  font-weight: 700;
  font-size: 14px;
  color: var(--ink);
  user-select: none;
}
.sg-tournament > summary::-webkit-details-marker { display: none; }
.sg-tournament > summary::before {
  content: '▸ ';
  display: inline-block;
  width: 14px;
  transition: transform 140ms ease;
}
.sg-tournament[open] > summary::before { transform: rotate(90deg); }
.sg-tournament > summary small {
  font-weight: 400;
  font-size: 12px;
  color: var(--muted);
  margin-left: 6px;
}
.sg-tournament-inner { padding: 0 14px 14px; }
.sg-tournament-inner .sg-field { margin-bottom: 10px; }
.sg-tournament-inner .sg-field:last-child { margin-bottom: 0; }

/* Country picker (v0.20.0) — opt-in field in Settings → Account.
   Lives directly under the account-actions row + email hint. */
.settings-country { margin-top: 10px; }
.settings-country select {
  font: inherit;
  font-size: 14px;
  padding: 6px 8px;
  border-radius: 6px;
  border: 1px solid #c3b78c;
  background: white;
  color: var(--ink);
  min-width: 200px;
}

/* Cookie consent banner (v0.20.4). Fixed to the bottom of the
   viewport, shown until the visitor accepts or declines GA cookies.
   Mobile-first: text + buttons stack on narrow screens, sit in a
   row on wider ones. Slides up via the .show class (added a frame
   after mount so the transition fires). z-index above everything
   except hard modals. */
.cookie-consent {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 10001;
  background: #11181f;
  color: #f4ecd8;
  border-top: 1px solid rgba(255,255,255,0.12);
  box-shadow: 0 -4px 18px rgba(0,0,0,0.4);
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  font-size: 13.5px;
  line-height: 1.5;
  transform: translateY(110%);
  transition: transform 300ms cubic-bezier(.2,.7,.3,1);
}
.cookie-consent.show { transform: translateY(0); }
.cookie-consent-text { color: #e0d7be; }
.cookie-consent-text strong { color: #fff; }
.cookie-consent-text a { color: var(--accent); }
.cookie-consent-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}
.cookie-btn {
  flex: 1 1 0;
  min-height: 44px;
  border-radius: 10px;
  font: inherit;
  font-weight: 700;
  font-size: 14px;
  cursor: pointer;
  border: 1.5px solid transparent;
  padding: 10px 18px;
}
.cookie-btn.cookie-accept {
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
  color: #fff;
}
.cookie-btn.cookie-accept:hover { filter: brightness(1.05); }
.cookie-btn.cookie-decline {
  background: rgba(255,255,255,0.06);
  border-color: rgba(255,255,255,0.35);
  color: #fff;
}
.cookie-btn.cookie-decline:hover { background: rgba(255,255,255,0.12); }

/* Wider screens: single row, text takes the slack, buttons hug right
   and shrink to content width. */
@media (min-width: 640px) {
  .cookie-consent {
    flex-direction: row;
    align-items: center;
    gap: 20px;
    padding: 14px 24px;
  }
  .cookie-consent-text { flex: 1; }
  .cookie-consent-actions { flex: 0 0 auto; }
  .cookie-btn { flex: 0 0 auto; min-width: 110px; }
}

/* Reconnect banner (v0.19.0) — pinned at the top of the viewport
   whenever socket.io is mid-reconnect. Non-modal so the user can
   keep reading the table; just informs them why their input might
   be queueing. .stuck = repeated reconnect_failed, so we switch to
   a more urgent red. */
.reconnect-banner {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 10000;
  padding: 8px 16px;
  text-align: center;
  font-size: 13px;
  font-weight: 600;
  background: #b58300;
  color: #fff;
  box-shadow: 0 2px 8px rgba(0,0,0,0.25);
  letter-spacing: 0.2px;
}
.reconnect-banner.stuck { background: #b00020; }
.reconnect-banner[hidden] { display: none; }

/* Stats modal (v0.20.0 redesign) — hero + tile grid + fun-fact badges
   + collapsed detail tables. Width bumped because the tile grid
   wants 3 columns at desktop, 2 on phones. */
.overlay .modal.stats-modal { width: min(560px, calc(100% - 32px)); }
.stats-modal .stats-body { padding: 4px 2px; }

/* Game-type selector for the per-preset bidding + meld tables. Segmented
   pills; the active one gets the accent. */
.stats-modal .stats-preset-tabs {
  display: flex;
  gap: 6px;
  margin: 4px 0 10px;
  flex-wrap: wrap;
}
.stats-modal .stats-preset-tab {
  flex: 1 1 0;
  min-width: 0;
  padding: 8px 10px;
  border: 1px solid var(--paper-border, #e6dcbf);
  background: var(--paper-2, #f3edd9);
  color: var(--ink, #2a241a);
  border-radius: 999px;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  white-space: nowrap;
}
.stats-modal .stats-preset-tab.active {
  background: linear-gradient(180deg, var(--accent-2, #f17a55), var(--accent, #d4533f));
  border-color: rgba(0,0,0,0.1);
  color: #fff;
}

/* ---- Hero ---- */
.stats-modal .stats-hero {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 14px;
  background: linear-gradient(135deg, rgba(212, 83, 63, 0.12), rgba(20, 84, 59, 0.12));
  border: 1px solid rgba(212, 83, 63, 0.25);
  border-radius: 14px;
  margin-bottom: 14px;
}
.stats-modal .stats-hero-emoji {
  font-size: 44px;
  line-height: 1;
  width: 64px;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(255,255,255,0.7);
  flex-shrink: 0;
}
.stats-modal .stats-hero-name {
  font-size: 20px;
  font-weight: 800;
  line-height: 1.1;
}
.stats-modal .stats-hero-meta {
  font-size: 12px;
  color: var(--ink-soft);
  margin-top: 4px;
}
.stats-modal .stats-rank {
  display: inline-block;
  margin-left: 6px;
  font-weight: 700;
  background: rgba(212, 83, 63, 0.18);
  color: var(--accent);
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11px;
}

/* "Learn more" link beneath the rankings area — appears in both the
   "Your stats" modal (under the Elo tiles) and the player profile
   modal (under the dual-rating row). Quiet, right-aligned. */
.stats-rankings-learn {
  margin: -6px 0 14px;
  text-align: right;
  font-size: 12px;
}
.stats-rankings-learn a {
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
}
.stats-rankings-learn a:hover { text-decoration: underline; }

/* ---- Headline tile grid ---- */
.stats-modal .stats-tile-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-bottom: 16px;
}
@media (max-width: 480px) {
  .stats-modal .stats-tile-grid { grid-template-columns: repeat(2, 1fr); }
}
.stats-modal .stats-tile {
  background: rgba(0,0,0,0.04);
  border: 1px solid rgba(0,0,0,0.08);
  border-radius: 10px;
  padding: 10px 12px;
  text-align: center;
}
.stats-modal .stats-tile-value {
  font-size: 22px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.stats-modal .stats-tile-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--ink-soft);
  margin-top: 4px;
}
.stats-modal .stats-tile-sub {
  font-size: 11px;
  color: var(--muted);
  margin-top: 2px;
}

/* ---- Fun-fact badges ---- */
.stats-modal .stats-badge-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}
.stats-modal .stats-badge {
  background: linear-gradient(180deg, #fef3d7, #fde2a3);
  border: 1px solid rgba(150, 100, 20, 0.25);
  border-radius: 10px;
  padding: 8px 10px;
  flex: 1 1 calc(50% - 4px);
  min-width: 140px;
  color: #4a3105;
}
.stats-modal .stats-badge-title {
  font-weight: 700;
  font-size: 13px;
  line-height: 1.2;
}
.stats-modal .stats-badge-blurb {
  font-size: 11px;
  opacity: 0.8;
  margin-top: 2px;
}
.stats-modal .stats-empty {
  background: rgba(0,0,0,0.04);
  border: 1px dashed rgba(0,0,0,0.15);
  border-radius: 10px;
  padding: 12px;
  text-align: center;
  font-size: 13px;
  color: var(--ink-soft);
  font-style: italic;
}

/* ---- Section headings + detail tables ---- */
.stats-modal .stats-h3 {
  margin: 14px 0 6px;
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--ink-soft);
}
.stats-modal .stats-h3:first-child { margin-top: 0; }
.stats-modal .stats-table {
  background: rgba(0,0,0,0.025);
  border: 1px solid rgba(0,0,0,0.06);
  border-radius: 10px;
  padding: 4px 12px;
}
.stats-modal .stats-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
  border-bottom: 1px solid rgba(0,0,0,0.06);
  font-size: 13px;
}
.stats-modal .stats-row:last-child { border-bottom: 0; }
.stats-modal .stats-label { color: var(--ink-soft); }
.stats-modal .stats-value { font-variant-numeric: tabular-nums; font-weight: 600; }
.stats-modal .stats-sub { color: var(--muted); font-weight: 400; font-size: 12px; }
.contact-modal label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-soft);
  letter-spacing: 0.3px;
  text-transform: uppercase;
  margin: 10px 0 4px;
}
.contact-modal input[type="text"],
.contact-modal input[type="email"],
.contact-modal textarea {
  width: 100%;
  font-family: var(--font-ui);
}
.contact-modal textarea {
  min-height: 120px;
  resize: vertical;
}
.contact-modal .contact-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  margin-top: 14px;
}
.contact-modal .contact-result { font-size: 13px; margin-top: 8px; }
.contact-modal .contact-result.err { color: var(--accent); }
.contact-modal .contact-result.ok  { color: #1a7f37; }

/* ==========================================================
   Standalone legal pages (privacy.html / terms.html).
   Sand-colored card-on-felt look that matches the auth view,
   readable serif/sans body, friendly back-link.
   ========================================================== */
/* Legal pages reuse the lobby's look — dark page bg with a single tan
   "panel" containing the legal text. Override the global html, body
   { height: 100% } rule so the page actually scrolls when the content
   runs long. (The legal HTML files also inline an html/body height
   reset for older browsers without :has().) */
html:has(body.legal-page),
body.legal-page {
  height: auto;
  min-height: 100vh;
  overflow-y: auto;
}
body.legal-page {
  background: var(--bg);
  margin: 0;
  font-family: var(--font-ui);
  color: var(--ink);
  /* Wordmark color vars need a dark-bg theme here (same as the auth
     wrapper). Without this the "Pinochle" half rendered in the default
     ink color, which is dark, and was invisible against --bg. */
  --wm-just-color: #e9d6a8;
  --wm-pin-color: #ffffff;
}
.legal-wrapper {
  max-width: 760px;
  margin: 0 auto;
  padding: 32px 16px 56px;
}
.legal-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 18px;
  gap: 12px;
  flex-wrap: wrap;
}
.legal-header .wordmark { font-size: 26px; }
.legal-back {
  color: rgba(255,255,255,0.85);
  text-decoration: none;
  font-size: 14px;
  background: rgba(255,255,255,0.08);
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid rgba(255,255,255,0.18);
}
.legal-back:hover { background: rgba(255,255,255,0.16); }
.legal-body {
  background: var(--paper);
  color: var(--ink);
  padding: 28px 32px;
  border-radius: 14px;
  border: 1px solid var(--paper-border);
  box-shadow: 0 8px 24px rgba(0,0,0,0.35);
  line-height: 1.55;
}
.legal-body h1 { font-size: 28px; margin: 0 0 4px; }
.legal-body h2 {
  font-size: 17px;
  margin: 22px 0 6px;
  letter-spacing: -0.1px;
}
.legal-body .legal-meta { color: var(--muted); font-size: 13px; margin: 0 0 14px; }
.legal-body ul,
.legal-body ol { padding-left: 24px; }
/* v0.20.2: ol was missing from the rule above so numbered lists in
   rankings.html were rendering with the numbers clipped off the left
   edge. Adding ol fixes that without touching the ul layout. */
.legal-body ol { list-style: decimal; }
.legal-body li { margin: 6px 0; padding-left: 4px; }
.legal-body code {
  background: #efe7cc;
  padding: 1px 6px;
  border-radius: 4px;
  font-size: 13px;
}
.legal-body .legal-foot {
  margin-top: 28px;
  padding-top: 16px;
  border-top: 1px solid var(--paper-border);
  color: var(--muted);
  font-size: 13px;
}
.legal-body a { color: var(--accent); }
.legal-cookie-link {
  background: none;
  border: 0;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  padding: 0;
  text-decoration: underline;
}
.legal-body h3 {
  font-size: 16px;
  margin: 18px 0 6px;
  color: var(--ink);
}
/* Data tables in the new enterprise privacy policy (v0.20.4). Wrapped
   in a horizontally-scrollable container on mobile so a 5-column
   table doesn't blow out the page width. */
.legal-table-wrap { overflow-x: auto; margin: 12px 0 16px; -webkit-overflow-scrolling: touch; }
.legal-body table {
  border-collapse: collapse;
  width: 100%;
  min-width: 460px;
  font-size: 13px;
}
.legal-body table th,
.legal-body table td {
  border: 1px solid var(--paper-border, #d6cfb4);
  padding: 8px 10px;
  text-align: left;
  vertical-align: top;
  line-height: 1.4;
}
.legal-body table th {
  background: #efe7cc;
  font-weight: 700;
}
.legal-body table tr:nth-child(even) td { background: rgba(0,0,0,0.02); }
@media (max-width: 599px) {
  .legal-wrapper { padding: 24px 12px 40px; }
  .legal-body { padding: 20px 18px; }
  .legal-body h1 { font-size: 24px; }
}

/* ============================================================================
   GLOBAL READABILITY SCALE — "Grandpa mode" (and a milder "Large").
   ----------------------------------------------------------------------------
   Everything here is gated behind body.ui-scale-large / .ui-scale-grandpa, so
   at the default scale NOTHING below applies. When a scale is active, these
   rules lift the font-size of the text surfaces players actually read — across
   the WHOLE site, including the landing page and the settings descriptions
   that were previously tiny on phones. Sizes use calc(base * var(--ui-scale))
   so the single 'large' (1.25×) and 'grandpa' (1.5×) tiers both flow from one
   knob. The :is() prefix also raises specificity above the per-device
   (display-size-*) overrides so the scale wins on phones too.
   ============================================================================ */

/* Base inherited text everywhere (landing paragraphs, lists, modal copy that
   isn't explicitly sized). */
:is(body.ui-scale-large, body.ui-scale-grandpa) { font-size: calc(14px * var(--ui-scale)); }

/* ---- Modals + settings (the "I can't read the descriptions" complaint) ---- */
:is(body.ui-scale-large, body.ui-scale-grandpa) .overlay .modal { font-size: calc(15px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .overlay .modal p,
:is(body.ui-scale-large, body.ui-scale-grandpa) .overlay .modal li { font-size: calc(15px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .overlay .modal h2 { font-size: calc(22px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .overlay .modal h3 { font-size: calc(15px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .sg-label { font-size: calc(14px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .sg-sub   { font-size: calc(12px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .sg-pill strong,
:is(body.ui-scale-large, body.ui-scale-grandpa) .settings-pill strong { font-size: calc(14px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .sg-pill small,
:is(body.ui-scale-large, body.ui-scale-grandpa) .settings-pill small { font-size: calc(12px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .sg-slider-val,
:is(body.ui-scale-large, body.ui-scale-grandpa) .sg-slider-end { font-size: calc(13px * var(--ui-scale)); }

/* ---- In-game information: trump/score line, seat names, menu status ---- */
:is(body.ui-scale-large, body.ui-scale-grandpa) .status-bar  { font-size: calc(14px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .status-info { font-size: calc(13px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .bid-pill    { font-size: calc(13px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .bid-pill .suit-red,
:is(body.ui-scale-large, body.ui-scale-grandpa) .bid-pill .suit-black { font-size: calc(16px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .seat-tag        { font-size: calc(13px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .seat-tag .name  { font-size: calc(14px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .seat-tag .badge { font-size: calc(11px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .menu-status,
:is(body.ui-scale-large, body.ui-scale-grandpa) .menu-status-phase,
:is(body.ui-scale-large, body.ui-scale-grandpa) .menu-turn,
:is(body.ui-scale-large, body.ui-scale-grandpa) .menu-detail { font-size: calc(14px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .action-bar .label { font-size: calc(14px * var(--ui-scale)); }

/* ---- Meld + hand-summary screens ---- */
:is(body.ui-scale-large, body.ui-scale-grandpa) .meld-totals { font-size: calc(15px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .meld-row-username { font-size: calc(13px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .meld-item-label,
:is(body.ui-scale-large, body.ui-scale-grandpa) .meld-item-pts { font-size: calc(13px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .meld-bid-line,
:is(body.ui-scale-large, body.ui-scale-grandpa) .meld-bid-need { font-size: calc(14px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .meld-row-points,
:is(body.ui-scale-large, body.ui-scale-grandpa) .meld-bidder-tag,
:is(body.ui-scale-large, body.ui-scale-grandpa) .meld-row-bidcrown { font-size: calc(13px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .meld-countdown,
:is(body.ui-scale-large, body.ui-scale-grandpa) .meld-hint-foot,
:is(body.ui-scale-large, body.ui-scale-grandpa) .meld-hint-total { font-size: calc(13px * var(--ui-scale)); }

/* ---- Central bidding medallion ("25 / current bid / by Tobor / 📋 history") ---- */
:is(body.ui-scale-large, body.ui-scale-grandpa) .bidding-cur-num    { font-size: calc(32px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .bidding-cur-label,
:is(body.ui-scale-large, body.ui-scale-grandpa) .bidding-leader,
:is(body.ui-scale-large, body.ui-scale-grandpa) .bidding-history-hint,
:is(body.ui-scale-large, body.ui-scale-grandpa) .bidding-jump-badge { font-size: calc(12px * var(--ui-scale)); }

/* ---- In-play contract pill ("29 ♠ You · 8 to go") + side trump flag ("29 ♠") ---- */
:is(body.ui-scale-large, body.ui-scale-grandpa) .bid-pill strong,
:is(body.ui-scale-large, body.ui-scale-grandpa) .bid-pill .muted,
:is(body.ui-scale-large, body.ui-scale-grandpa) .bid-pill .ok,
:is(body.ui-scale-large, body.ui-scale-grandpa) .bid-pill .set { font-size: calc(13px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .bidder-trump-flag,
:is(body.ui-scale-large, body.ui-scale-grandpa) .trump-flag-bid,
:is(body.ui-scale-large, body.ui-scale-grandpa) .trump-flag-suit { font-size: calc(14px * var(--ui-scale)); }

/* ---- Action bar (Bid / Pass + the +/- bid amount) ---- */
:is(body.ui-scale-large, body.ui-scale-grandpa) .action-bar button { font-size: calc(14px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .action-bar .bid-amount,
:is(body.ui-scale-large, body.ui-scale-grandpa) .action-bar input,
:is(body.ui-scale-large, body.ui-scale-grandpa) .action-bar .num { font-size: calc(16px * var(--ui-scale)); }

/* ---- Landing / homepage ---- */
:is(body.ui-scale-large, body.ui-scale-grandpa) .landing-lede { font-size: calc(18px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .landing-features,
:is(body.ui-scale-large, body.ui-scale-grandpa) .landing-trio li { font-size: calc(15px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .landing-seo p,
:is(body.ui-scale-large, body.ui-scale-grandpa) .landing-seo-list li { font-size: calc(15px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .landing-seo h2 { font-size: calc(26px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .landing-seo h3 { font-size: calc(18px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .landing-faq dt { font-size: calc(15px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .landing-faq dd { font-size: calc(14px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .landing-support-body strong { font-size: calc(15px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .landing-support-body small { font-size: calc(12px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .landing-fine { font-size: calc(13px * var(--ui-scale)); }

/* ---- Legal / how-to-play long-form pages ---- */
:is(body.ui-scale-large, body.ui-scale-grandpa) .legal-body { font-size: calc(15px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .legal-body p,
:is(body.ui-scale-large, body.ui-scale-grandpa) .legal-body li { font-size: calc(15px * var(--ui-scale)); }

/* ---- Lobby: Start a game / How to play + the rankings panel + tabs ---- */
:is(body.ui-scale-large, body.ui-scale-grandpa) .lobby-actions button.primary.big,
:is(body.ui-scale-large, body.ui-scale-grandpa) .lobby-actions .lobby-howto { font-size: calc(16px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .rankings-panel { font-size: calc(13px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .rankings-panel h2 { font-size: calc(15px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .rankings-tab { font-size: calc(13px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .rankings-list li,
:is(body.ui-scale-large, body.ui-scale-grandpa) .rankings-list li small,
:is(body.ui-scale-large, body.ui-scale-grandpa) #rankings li,
:is(body.ui-scale-large, body.ui-scale-grandpa) #rankings-bot li { font-size: calc(14px * var(--ui-scale)); }

/* ---- Tabs + the "Show advanced options" toggle in settings ---- */
:is(body.ui-scale-large, body.ui-scale-grandpa) .auth-tab { font-size: calc(14px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .sg-advanced-toggle { font-size: calc(14px * var(--ui-scale)); }

/* ---- Buttons inside modals (Start game / Back / Send / Continue / Close…)
        and the start-a-game wizard's preset cards. These were missed before —
        only their containers scaled, not the buttons. ---- */
:is(body.ui-scale-large, body.ui-scale-grandpa) .overlay .modal button,
:is(body.ui-scale-large, body.ui-scale-grandpa) .overlay .actions button { font-size: calc(15px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .sg-quick-btn { font-size: calc(14px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .sg-quick-btn strong { font-size: calc(16px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .sg-quick-btn small { font-size: calc(13px * var(--ui-scale)); }

/* ---- In-game hamburger menu actions (Settings / Step away / Hand history…) ---- */
:is(body.ui-scale-large, body.ui-scale-grandpa) .menu-actions button { font-size: calc(15px * var(--ui-scale)); }

/* ---- Empty-seat titles + Sit/Invite/Bot buttons (waiting room) ---- */
:is(body.ui-scale-large, body.ui-scale-grandpa) .seat-tag.empty .seat-pos { font-size: calc(14px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .seat-tag.empty .seat-state { font-size: calc(11px * var(--ui-scale)); }
:is(body.ui-scale-large, body.ui-scale-grandpa) .seat-action-btn { font-size: calc(12px * var(--ui-scale)); }

/* ============================================================================
   SMALL-SCREEN TRADE-OFFS at the biggest (Grandpa) scale.
   When text + cards are blown up on a phone, something has to give. Keep the
   essentials big (trump, contract, whose turn, your own cards) and shed the
   secondary chrome. See the discussion in app.js / with the product owner.
   ============================================================================ */
@media (max-width: 599px) {
  /* The dealt hand stays on-screen via the JS overlap-clamp (fitHandOverlap)
     plus the existing two-row split for narrow phones — so no wrap hacks here.
     Side seats: show just the avatar + name; the rating/secondary badges drop
     so the bigger name has room in the narrow side columns. */
  body.ui-scale-grandpa .seat-area.seat-left .seat-tag .badge:not(.timer):not(.away-badge),
  body.ui-scale-grandpa .seat-area.seat-right .seat-tag .badge:not(.timer):not(.away-badge) {
    display: none;
  }
}

/* "We're slammed" capacity notice (SERVER_FULL). Friendly full-screen wall
   with a donate CTA, shown instead of admitting an overflow player. */
.overlay .modal.server-full-modal { max-width: 460px; text-align: center; }
.server-full-modal h2 { margin-bottom: 10px; }
.server-full-modal p { margin: 8px 0; color: var(--ink); line-height: 1.5; }
.server-full-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-top: 18px; }
.server-full-donate {
  display: inline-flex; align-items: center; gap: 6px;
  background: linear-gradient(180deg, var(--accent-2), var(--accent)); color: #fff;
  border: 1px solid rgba(0,0,0,0.1); border-radius: 999px; padding: 10px 20px;
  font-weight: 700; text-decoration: none;
}
.server-full-donate:hover { filter: brightness(1.05); }
