/* =========================================================
   Tarneeb — Card Platform
   Rich dark charcoal & gold casino palette
   ========================================================= */

:root {
  /* =========================================================
     "Kufi Noir" — pure black ground, ONE saffron accent.
     Token NAMES are kept stable so existing rules re-skin in
     place; only the VALUES change from the old charcoal+gold.

     ---- 2026-08 theme unification ----
     The values below now mirror design-tokens.css, so the game table adopts
     the same palette as every rebuilt screen without a single geometry rule
     changing. That is the whole point of this seam: the file was written so
     names stay stable and only values move, which means the card proportions
     measured against Jawaker (21.5vw hand / 15.3vw table, aspect 1.4) cannot
     be affected by a re-skin.

     Where a name does NOT collide with a design token, this consumes the token
     via var() so the table follows the theme. Where it DOES collide (--ink,
     --red, --green exist in both files) the literal value is written instead —
     `--ink: var(--ink)` is a cycle and silently invalidates the property.
     ========================================================= */

  /* Surfaces */
  --navy:         var(--surf);        /* surface: panels, felt, tray */
  --navy-deep:    var(--bg);          /* the black ground */
  --navy-soft:    var(--surf2);       /* raised surface */

  --blue:         var(--surf);
  --blue-soft:    var(--line2);
  --blue-dark:    var(--surf2);

  /* Neutral "slate" family (was bronze) → greys + accent */
  --slate:        #8e867b;
  --slate-soft:   #a09689;
  --slate-dark:   #6b6459;
  --slate-deep:   var(--gold);   /* accent */

  /* Ink / paper — the card face, which is the one light surface on the table */
  --cream:        #efe9dc;
  --cream-soft:   var(--card);
  --cream-dark:   var(--card-line);

  /* Accent — the design-token gold is THE only accent colour */
  --orange:       var(--gold);
  --orange-soft:  var(--gold-hi);
  --orange-dark:  var(--gold-dim);

  /* Legacy aliases → gold / neutral (never bronze or lime) */
  --yellow:       var(--gold);
  --yellow-soft:  var(--gold-hi);
  --yellow-dark:  var(--gold-dim);
  --lime:         #8e867b;
  --lime-soft:    #a09689;
  --lime-dark:    #6b6459;
  --sage:         #8e867b;
  --sage-soft:    #a09689;
  --sage-dark:    #6b6459;
  --sage-deep:    var(--gold);
  --sage-accent:  var(--gold);

  /* Flat backgrounds (no gradient) */
  --bg-grad-1:    var(--bg);
  --bg-grad-2:    var(--bg);
  --bg-grad-3:    var(--surf);

  --panel:        var(--surf);
  --panel-2:      var(--surf2);
  --panel-3:      var(--bg);

  /* Solid hairline borders */
  --border:       var(--line2);
  --border-soft:  var(--line);
  --border-lit:   var(--gold-dim);

  /* Literal, not var(): these three names exist in design-tokens.css too, and
     a self-reference is a cycle that silently drops the declaration. Values
     mirror the tokens. */
  --ink:          #efe9dc;
  --ink-soft:     #a09689;
  /* Muted helper text kept at the AA-safe end of the token ramp — the
     design's lighter grey fails contrast on these dark surfaces. */
  --ink-mute:     #8e867b;
  --ink-inverse:  #0d0c0b;

  --accent:       var(--orange);
  --accent-dark:  var(--orange-dark);
  --highlight:    var(--orange);

  --red:          #E0472F;   /* danger + red suits */
  --red-soft:     #F0684F;
  --green:        #3FAE6E;

  /* Team identity — used ONLY as thin underlines/bars, never large fills */
  --team-blue:    #4A7DD6;
  --team-green:   #3FAE6E;

  --shadow-xl: 0 24px 60px rgba(0, 0, 0, 0.55);
  --shadow-lg: 0 14px 34px rgba(0, 0, 0, 0.45);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, 0.35);
  --shadow-sm: 0 2px 8px  rgba(0, 0, 0, 0.30);

  /* Kufi Noir is sharp — no radius (playing cards get their own 6px) */
  --radius-sm: 0px;
  --radius:    0px;
  --radius-lg: 0px;
  --radius-xl: 0px;

  /* Size of the circular seat avatars. Referenced by .seat .avatar and
     by .table-panel so the panel width can leave clearance from the
     W/E avatars on every breakpoint. Each responsive media-query rule
     (bottom of this file) updates this token; the avatar rules and
     panel width calc consume it. */
  --seat-avatar-size: 80px;

  /* Height reserved for the bottom .round-status pill so floating UI
     (toasts, etc.) can anchor above it instead of overlapping. */
  --status-bar-height: 48px;
  --z-toast: 1100;
}

* { box-sizing: border-box; }

/* =========================================================
   Kufi Noir — foundation add-ons (narrator, identity, seats).
   Placed early; overrides use !important to win the cascade.
   ========================================================= */

/* Persistent narrator banner over the felt (the #1 clarity element). */
.narrator {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  margin: 4px 0 6px; padding: 0 12px;
  background: var(--accent); color: #111111;
  font-weight: 800; letter-spacing: 1px; text-transform: uppercase;
  font-size: 12.5px; line-height: 1.15;
  /* FIXED single-line height so the banner never changes size and reflows the
     table below it. Full width (see .ga-narrator grid-column) keeps the status
     text on one line, so a short 34px bar is enough — no two-line wrapping. */
  height: 34px; min-height: 34px; max-height: 34px; overflow: hidden;
  box-shadow: 4px 4px 0 #1D1D1D;
}
.narrator-text { flex: 0 1 auto; min-width: 0; line-height: 1.1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.narrator[hidden] { display: none !important; }
.narrator-wait { background: var(--panel); color: var(--ink); box-shadow: 4px 4px 0 #0A0A0A; }
.narrator-timer {
  flex: 0 0 auto; min-width: 24px; height: 24px; padding: 0 5px;
  display: inline-flex; align-items: center; justify-content: center;
  background: #111111; color: var(--accent);
  font-size: 13px; font-weight: 800;
}
.narrator-text { flex: 1 1 auto; }

/* "YOU — TEAM BLUE" identity row above the hand. */
.you-identity {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 3px 0 5px;
}
.you-identity[hidden] { display: none !important; }
.you-identity .yi-you { font-weight: 800; letter-spacing: 2px; font-size: 11px; color: var(--accent); }
.you-identity .yi-team { font-weight: 800; letter-spacing: 2px; font-size: 11px; padding: 1px 6px; }
.you-identity.team-0 .yi-team { color: var(--team-blue); box-shadow: inset 0 -3px 0 var(--team-blue); }
.you-identity.team-1 .yi-team { color: var(--team-green); box-shadow: inset 0 -3px 0 var(--team-green); }

/* Saffron ring on the active seat; explicit PARTNER label on the partner. */
.seat.turn { box-shadow: 0 0 0 3px var(--accent) !important; }
.seat.partner { position: relative; }
.seat.partner::after {
  content: "PARTNER";
  position: absolute; top: -7px; left: 50%; transform: translateX(-50%);
  font-size: 8px; font-weight: 800; letter-spacing: 2px;
  padding: 1px 5px; background: var(--panel-2); white-space: nowrap; z-index: 4;
}
.seat.partner.team-0::after { color: var(--team-blue); box-shadow: inset 0 0 0 1px var(--team-blue); }
.seat.partner.team-1::after { color: var(--team-green); box-shadow: inset 0 0 0 1px var(--team-green); }

/* Kufi Noir: square avatar tiles (no circles). */
.seat .avatar, .seat .avatar img { border-radius: 0 !important; }

/* The felt is a BOUNDED, INSET SURFACE in Claude Design's dark green.
   Decision 3 on the gameplay prototype: "A = inset felt, C = full-bleed felt,
   B = no surface at all." Composition A is the reference logic — the seats
   straddle the surface edge and the trick sits tight in the middle.
   design-tokens.css has carried --felt (#123024) and --felt-gradient since the
   handoff landed; these rules used to bury both under a flat #1D1D1D with
   !important, so the tokens were never once referenced and the table read as an
   unbounded black void. Radius 0, per the Kufi Noir rule. */
:root {
  --felt-line: #2f5c45;
  --felt-shadow:
    inset 0 1px 0 rgba(232, 200, 116, 0.20),
    inset 0 0 0 4px rgba(0, 0, 0, 0.28),
    inset 0 -30px 50px rgba(0, 0, 0, 0.45),
    0 14px 34px rgba(0, 0, 0, 0.55);
}
:root[data-theme="light"] {
  --felt-line: #3f7d59;
  --felt-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.13),
    inset 0 0 0 4px rgba(0, 0, 0, 0.10),
    inset 0 -32px 52px rgba(0, 0, 0, 0.26),
    0 12px 30px rgba(46, 70, 52, 0.26);
}

/* C2 Play. The sub line under "Find me a table" is the promise the button was
   missing, and FULL is a STATE — a full table has no action, so it gets a tag
   rather than a button that could only fail. */
.play-sub {
  font: 400 12px/1.45 var(--font-ui);
  color: var(--ink-mute);
  text-align: center;
  margin: -2px 0 10px;
}
.qj-eta { font-weight: 400; opacity: 0.8; margin-inline-start: 8px; }
.room-full-tag {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--tap);
  padding: 0 14px;
  font: 800 11px/1 var(--font-ui);
  letter-spacing: 0.14em;
  color: var(--ink-mute);
  background: var(--surf2, #26262c);
  border: 1px solid var(--line2, #2a2a30);
}

/* Toasts. Decision 7: "Its own lane on the table's bottom edge. It can never
   cover the card you just played."
   Outside a game they stay top-centre, which is right for a page. INSIDE a game
   the lane moves to the bottom edge of the felt: the trick sits in the middle,
   so a centred overlay is exactly where the card you just played lands. It was
   also free-floating mid-right and clipping off the screen edge — hence the
   explicit left/right bounds and the ellipsis. */
/* `.toast-host` is GONE, along with the second toast system that owned it.
   It was top-anchored at 72px on every non-game page, which is the opposite of
   what the handoff specifies ("One anchor — bottom, above the tab bar"), and it
   painted its own hex palette and 10px corners outside the design system.
   showToast() now adapts onto T.toast, and the in-game lane offset that used to
   live here moved to `body.in-game #t-toasts` in components.css, next to the
   component it belongs to. */

/* tokens.json: "radius.all: 0 — no rounded corners anywhere; circles only for
   presence dots and frame artwork". The RADIUS TOKENS are already 0px
   (--radius, --radius-sm/lg/xl); the violations are hardcoded literals that
   bypass them. These are the ones visible during play — the trick zone was
   18px, the table panels 14px and the last-trick tray a 999px pill, sitting
   inside a felt this pass just squared off.
   NOTE: styles.css still carries ~90 more hardcoded non-zero radii elsewhere.
   A full sweep is its own task; blanket-forcing every element would also square
   the presence dots and avatar-frame artwork, which are the documented
   exceptions. */
#view-room .trick,
#view-room .table-panel,
#view-room .last-trick-tray,
#view-room .last-trick-mini,
#last-trick-mini,
#view-room .pass-btn,
#view-room .weak-suit-btn,
#view-room .cut-top,
#view-room .cut-bottom { border-radius: 0 !important; }

#view-room .felt-wrap { background: transparent; position: relative; }

/* Journey seat tiles (kn-seats, journey.html). The turn cue is the prototype's
   own breathing ring — knRing 1600ms, verbatim keyframes. While the journey
   tiles are mounted the legacy seat rectangles hide: two seat systems on one
   felt is the two-toast-systems trap (#36) wearing a new hat. */
@keyframes knRing{0%,100%{box-shadow:0 0 0 2px var(--gold),0 0 12px rgba(201,162,74,.30)}50%{box-shadow:0 0 0 3px var(--gold-hi),0 0 22px rgba(201,162,74,.55)}}
/* Waiting room + partner sheet, verbatim from the prototype. */
@keyframes knSheet{0%{opacity:0;transform:translateY(22px)}100%{opacity:1;transform:none}}
@keyframes knMeter{from{width:0}}
/* Round summary's draining bar (prototype line 32). It is not decoration: its
   length IS the remaining time, so the animation duration and the dismiss
   timeout are the same number or the bar lies about how long you have. */
@keyframes knDrain{0%{transform:scaleX(1)}100%{transform:scaleX(0)}}
@keyframes knDot{from{transform:scale(.3);opacity:0}}
/* The deck ceremony, verbatim. Riffle throws cards left and right; the cut
   lifts a band clear and sets it back down. */
@keyframes knRiffL{0%{transform:translate(0,0) rotate(0)}22%{transform:translate(-44px,-7px) rotate(-8deg)}52%{transform:translate(-15px,-2px) rotate(-3deg)}100%{transform:translate(0,0) rotate(0)}}
@keyframes knRiffR{0%{transform:translate(0,0) rotate(0)}22%{transform:translate(44px,7px) rotate(8deg)}52%{transform:translate(15px,2px) rotate(3deg)}100%{transform:translate(0,0) rotate(0)}}
@keyframes knCutLift{0%{transform:translate(0,0) rotate(0)}26%{transform:translate(66px,-32px) rotate(6deg)}60%{transform:translate(66px,16px) rotate(2deg)}100%{transform:translate(0,28px) rotate(0)}}
@keyframes knCutStay{0%{transform:translate(0,0)}30%{transform:translate(0,7px)}100%{transform:translate(0,-15px)}}
/* While the designer's waiting room owns the screen, the legacy in-flow game
   chrome behind it stays down — same one-system-per-surface rule as the seat
   tiles (trap 36). The overlay is inside #view-room, so it inherits nothing. */
body.kn-waiting-live .ga-narrator,
body.kn-waiting-live #seat-panel { display: none !important; }
body.kn-seats-live .seat-slot, body.kn-seats-live #seat-south { display: none !important; }
#view-room .felt {
  /* Inset from the felt cell on all four sides — the prototype's 11% / 14% /
     16%, expressed as margins so the seats can still overlay the cell and sit
     ON the edge rather than outside it. */
  background: var(--felt-gradient) !important;
  border: 1px solid var(--felt-line) !important;
  border-radius: 0 !important;
  box-shadow: var(--felt-shadow) !important;
  margin: 4% 6% 6%;
}
.sp-felt { background: var(--felt-gradient); border: 1px solid var(--felt-line); border-radius: 0; }

/* Kufi Noir: flat saffron primary buttons with an offset block shadow. */
.primary, button.primary, a.primary, .gold, button.gold {
  background: var(--accent) !important;
  background-image: none !important;
  color: #111111 !important;
  border: none !important;
  border-radius: 0 !important;
  font-weight: 800 !important;
  letter-spacing: 1px;
  text-transform: uppercase;
  box-shadow: 4px 4px 0 #1D1D1D !important;
  text-shadow: none !important;
}
.primary:hover, button.primary:hover, a.primary:hover, .gold:hover { filter: brightness(1.06); }
.primary:active:not(:disabled), button.primary:active:not(:disabled),
a.primary:active, .gold:active:not(:disabled) {
  transform: translate(2px, 2px) !important;
  box-shadow: 2px 2px 0 #1D1D1D !important;
}
.primary:disabled, button.primary:disabled, .gold:disabled {
  background: #161616 !important; color: #777777 !important; box-shadow: none !important;
}

/* Kufi Noir: playable hand cards get a saffron ring. NO resting lift — a
   translateY here made cards bob up when it became your turn and drop back
   when it didn't, so the whole hand row jumped every turn. The ring alone
   signals "playable"; the lift is reserved for hover/focus (see below). */
.hand .card.playable {
  /* No highlight ring on playable cards (per Ahmad) — legal cards look normal;
     the ILLEGAL cards dim (.invalid-play) to guide the eye instead. */
  box-shadow: inset 0 0 0 2px #F5F1E8, inset 0 0 0 3px #1A1A1A !important;
}

/* Kufi Noir: table-slam celebration (capot / sweep). */
#view-room .felt { position: relative; }
.slam-overlay { position:absolute; inset:0; z-index:60; pointer-events:none; overflow:hidden; display:flex; align-items:center; justify-content:center; }
.slam-flash { position:absolute; inset:0; background:radial-gradient(circle at 50% 55%, rgba(255,255,255,0.9), transparent 60%); opacity:0; animation:slamFlash 1s ease-out both; }
.slam-fist { position:absolute; top:50%; left:50%; transform:translate(-50%,-240px); font-size:84px; line-height:1; animation:slamHand 1.1s cubic-bezier(.2,.8,.2,1) both; }
.slam-cracks { position:absolute; top:50%; left:50%; width:72%; height:72%; transform:translate(-50%,-50%); opacity:0; animation:slamCrack 1.6s ease-out both; }
.slam-cracks polyline { fill:none; stroke:#888; stroke-width:2; vector-effect:non-scaling-stroke; }
.slam-word { position:relative; font-weight:800; letter-spacing:2px; font-size:34px; color:var(--accent); text-shadow:4px 4px 0 #111; opacity:0; white-space:nowrap; animation:slamWord 1.4s ease-out both; }
.slam-dust { position:absolute; top:50%; left:50%; width:10px; height:10px; border-radius:50%; background:rgba(210,210,210,.5); opacity:0; animation:slamDust .8s ease-out both; }
.slam-dust.d1{--dx:-70px;--dy:-40px}.slam-dust.d2{--dx:70px;--dy:-30px}.slam-dust.d3{--dx:-60px;--dy:50px}.slam-dust.d4{--dx:65px;--dy:55px}
.slam-shake { animation:slamShake .55s ease-in-out both; }
@keyframes slamHand { 0%{transform:translate(-50%,-240px) scale(2.4) rotate(-14deg);opacity:0} 12%{opacity:1} 26%{transform:translate(-50%,-4px) scale(1) rotate(0)} 33%{transform:translate(-50%,8px) scale(.94)} 44%{transform:translate(-50%,0) scale(1)} 72%{opacity:1} 100%{transform:translate(-50%,-60px) scale(1.1);opacity:0} }
@keyframes slamShake { 0%,100%{transform:translate(0,0)} 10%{transform:translate(-6px,4px) rotate(-.6deg)} 25%{transform:translate(6px,-5px) rotate(.5deg)} 40%{transform:translate(-5px,3px)} 55%{transform:translate(4px,-3px) rotate(-.3deg)} 70%{transform:translate(-3px,2px)} 85%{transform:translate(2px,-1px)} }
@keyframes slamFlash { 0%,25%{opacity:0} 30%{opacity:.9} 45%,100%{opacity:0} }
@keyframes slamCrack { 0%,27%{opacity:0;transform:translate(-50%,-50%) scale(.2)} 33%{opacity:1;transform:translate(-50%,-50%) scale(1.08)} 38%{transform:translate(-50%,-50%) scale(1)} 88%{opacity:1} 100%{opacity:0} }
@keyframes slamWord { 0%,26%{opacity:0;transform:scale(.4)} 34%{opacity:1;transform:scale(1.15)} 44%{transform:scale(1)} 82%{opacity:1} 100%{opacity:0;transform:scale(1.05) translateY(-10px)} }
@keyframes slamDust { 0%{opacity:0;transform:translate(-50%,-50%)} 30%{opacity:.8} 100%{opacity:0;transform:translate(calc(-50% + var(--dx)), calc(-50% + var(--dy)))} }

/* Kufi Noir: emotes + bilingual quick-chat tray. */
/* Overlays the felt cell, bottom-right, the same way the seats overlay it.
   NOT position:fixed — see the note in index.html: a fixed bottom:96px landed
   inside the 191px hand lane and stole taps from the rightmost card. */
.emote-btn {
  grid-area: felt; align-self: end; justify-self: end;
  margin: 0 4px 6px 0; z-index: 45;
  width: 40px; height: 40px; padding: 0; font-size: 20px; line-height: 1;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--border-lit); box-shadow: 3px 3px 0 #0A0A0A; cursor: pointer;
}
.emote-btn:active { transform: translate(2px,2px); box-shadow: 1px 1px 0 #0A0A0A; }
.emote-tray {
  position: fixed; left: 8px; right: 8px; bottom: 8px; z-index: 46;
  max-width: 560px; margin: 0 auto;
  background: var(--panel); border: 1px solid var(--border);
  box-shadow: 0 -6px 24px rgba(0,0,0,0.6); padding: 14px 12px 14px;
  display: flex; flex-direction: column; gap: 10px;
}
.emote-tray[hidden] { display: none !important; }
.emote-close { position: absolute; top: 5px; right: 8px; background: none; border: none; color: var(--ink-mute); font-size: 16px; cursor: pointer; }
.emote-row { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
.emote-tile { width: 44px; height: 44px; font-size: 22px; padding: 0; background: #161616; border: 1px solid var(--border); cursor: pointer; }
.emote-tile:hover, .emote-tile:focus { border-color: var(--accent); box-shadow: 3px 3px 0 #0A0A0A; outline: none; }
.qc-wrap { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; }
.qc-chip { padding: 6px 10px; font-size: 12px; font-weight: 700; background: #161616; color: var(--ink); border: 1px solid var(--border); cursor: pointer; white-space: nowrap; }
.qc-chip:hover, .qc-chip:focus { border-color: var(--accent); color: var(--accent); outline: none; }

/* Emote bubble anchored to the sender's seat. */
.emote-bubble {
  position: fixed; z-index: 70; transform: translate(-50%,-100%);
  background: var(--ink); color: #111; font-size: 26px; line-height: 1;
  padding: 6px 8px; box-shadow: 4px 4px 0 #0A0A0A; pointer-events: none;
  animation: emPop 2.1s ease-out both;
}
.emote-bubble.emote-phrase { font-size: 13px; font-weight: 800; max-width: 180px; white-space: normal; text-align: center; }
.emote-bubble.em-laugh { animation: emPop 2.1s ease-out both, emRock .35s ease-in-out 3; }
.emote-bubble.em-angry { animation: emPop 2.1s ease-out both, emShake .3s ease-in-out 4; box-shadow: 4px 4px 0 #0A0A0A, 0 0 0 3px rgba(224,71,47,.4); }
@keyframes emPop { 0%{transform:translate(-50%,-90%) scale(.2);opacity:0} 18%{transform:translate(-50%,-100%) scale(1.25);opacity:1} 30%{transform:translate(-50%,-100%) scale(1)} 80%{transform:translate(-50%,-100%) scale(1);opacity:1} 100%{transform:translate(-50%,-130%) scale(.6);opacity:0} }
@keyframes emRock { 0%,100%{rotate:-10deg} 50%{rotate:10deg} }
@keyframes emShake { 0%,100%{margin-left:0} 20%,60%{margin-left:-5px} 40%,80%{margin-left:5px} }

/* Kufi Noir: partner signals (knock / swap / tilt). Always-visible left-edge rail
   during trick play + a suit-chooser popover + the anchored per-type badge. */
.signal-tray {
  /* Bottom-left of the felt cell. This was position:fixed at bottom:178px — a
     magic number chosen to "lift clear of the hand row" (96px before that, which
     was worse). Both were guesses at a lane whose locked height is
     card-w * 1.4 + 62px, i.e. 135px at 768 and up to 180px at 412, so no single
     constant clears it everywhere; at 360-390 the rail sat on the leftmost
     cards. Overlaying the felt cell tracks the lane exactly. */
  grid-area: felt; align-self: end; justify-self: start;
  margin: 0 0 6px 4px;
  z-index: 45; display: flex; flex-direction: column; gap: 8px;
}
.signal-tray[hidden] { display: none !important; }
.signal-btn {
  width: 40px; height: 40px; padding: 0; font-size: 20px; line-height: 1;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--border-lit); box-shadow: 3px 3px 0 #0A0A0A; cursor: pointer;
}
.signal-btn:hover, .signal-btn:focus { border-color: var(--accent); color: var(--accent); outline: none; }
.signal-btn:active { transform: translate(2px,2px); box-shadow: 1px 1px 0 #0A0A0A; }

.signal-suit-chooser {
  position: fixed; z-index: 47; background: var(--panel);
  border: 1px solid var(--border); box-shadow: 4px 4px 0 #0A0A0A;
  padding: 6px; display: grid; grid-template-columns: repeat(2, 40px); gap: 6px;
}
.signal-suit-chooser[hidden] { display: none !important; }
.signal-suit-btn {
  width: 40px; height: 40px; padding: 0; font-size: 22px; line-height: 1;
  background: #161616; border: 1px solid var(--border); cursor: pointer;
}
.signal-suit-btn:hover, .signal-suit-btn:focus { border-color: var(--accent); box-shadow: 3px 3px 0 #0A0A0A; outline: none; }
.signal-suit-btn.suit-hearts, .signal-suit-btn.suit-diamonds { color: #E0472F; }
.signal-suit-btn.suit-spades, .signal-suit-btn.suit-clubs { color: var(--ink); }

/* Signal badge anchored to the sender's seat (mirrors .emote-bubble). */
.signal-badge {
  position: fixed; z-index: 70; transform: translate(-50%,-100%);
  display: flex; align-items: center; gap: 6px;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--accent); box-shadow: 4px 4px 0 #0A0A0A;
  padding: 5px 9px; pointer-events: none; white-space: nowrap;
  animation: sigPop 2.2s ease-out both;
}
.signal-badge-label { font-size: 11px; font-weight: 800; letter-spacing: 1.5px; color: var(--accent); text-transform: uppercase; }
.signal-badge-suit { font-size: 20px; line-height: 1; display: inline-block; }
.signal-badge-suit.suit-red { color: #E0472F; }
.signal-badge-suit.suit-black { color: var(--ink); }
@keyframes sigPop { 0%{transform:translate(-50%,-90%) scale(.2);opacity:0} 16%{transform:translate(-50%,-100%) scale(1.2);opacity:1} 28%{transform:translate(-50%,-100%) scale(1)} 80%{transform:translate(-50%,-100%) scale(1);opacity:1} 100%{transform:translate(-50%,-125%) scale(.6);opacity:0} }

/* knock → pulse + expanding ripple ring ("lead this!") */
.signal-badge.sig-knock { animation: sigPop 2.2s ease-out both, sigKnockPulse .5s ease-in-out 3; }
.signal-badge.sig-knock::after {
  content: ""; position: absolute; inset: -4px; border: 2px solid var(--accent);
  animation: sigRipple 1s ease-out 2; pointer-events: none;
}
@keyframes sigKnockPulse { 0%,100%{box-shadow:4px 4px 0 #0A0A0A} 50%{box-shadow:4px 4px 0 #0A0A0A, 0 0 0 4px rgba(245,176,39,.35)} }
@keyframes sigRipple { 0%{opacity:.7; transform:scale(1)} 100%{opacity:0; transform:scale(1.5)} }

/* swap → shuffle / cross wobble ("I'm void, swap suits") */
.signal-badge.sig-swap { animation: sigPop 2.2s ease-out both, sigSwapShuffle .4s ease-in-out 3; }
@keyframes sigSwapShuffle { 0%,100%{margin-left:0; rotate:0deg} 25%{margin-left:-6px; rotate:-4deg} 75%{margin-left:6px; rotate:4deg} }

/* tilt → the suit glyph tips 90° like a laid-down card ("I hold the Queen") */
.signal-badge.sig-tilt .signal-badge-suit { animation: sigTiltCard 1.4s ease-in-out both; transform-origin: 50% 80%; }
@keyframes sigTiltCard { 0%{rotate:0deg} 35%{rotate:90deg} 70%{rotate:90deg} 100%{rotate:0deg} }

/* Kufi Noir: felt watermark (Arabic wordmark) + classic double-border cards. */
.felt-logo { color: var(--accent) !important; opacity: 0.13 !important; font-size: 46px !important; letter-spacing: 3px !important; font-weight: 800 !important; }
.card, .trick .played-card { box-shadow: inset 0 0 0 2px #F5F1E8, inset 0 0 0 3px #1A1A1A; }
.card.red, .trick .played-card.red { box-shadow: inset 0 0 0 2px #F5F1E8, inset 0 0 0 3px #E0472F; }
.hand .card.playable { box-shadow: inset 0 0 0 2px #F5F1E8, inset 0 0 0 3px #1A1A1A !important; }
.hand .card.playable.red { box-shadow: inset 0 0 0 2px #F5F1E8, inset 0 0 0 3px #E0472F !important; }

/* Kufi Noir: home hub + bottom tab bar. */
.view-home { display: flex; flex-direction: column; min-height: calc(100dvh - 58px); }
.home-scroll { flex: 1 1 auto; overflow-y: auto; padding: 14px 16px 20px; max-width: 560px; margin: 0 auto; width: 100%; }
.home-section-label { font-size: 10px; font-weight: 800; letter-spacing: 3px; color: var(--ink-mute); text-transform: uppercase; margin: 18px 2px 8px; }
.game-hero { position: relative; background: var(--panel); border: 1px solid var(--border); padding: 22px 18px; overflow: hidden; box-shadow: 5px 5px 0 #0A0A0A; }
.game-hero-ghost { position: absolute; right: -6px; top: -34px; font-size: 150px; color: var(--accent); opacity: 0.09; line-height: 1; pointer-events: none; }
.game-hero-title { font-size: 28px; font-weight: 800; letter-spacing: 2px; color: var(--ink); position: relative; }
.game-hero-meta { font-size: 12px; color: var(--ink-mute); margin: 6px 0 16px; position: relative; }
.gh-friends { color: var(--team-green); }
.game-hero-actions { display: flex; gap: 10px; position: relative; }
.game-hero-actions .primary { padding: 12px 24px; }
.tertiary { background: transparent; border: 1px solid var(--border-lit); color: var(--ink-soft); font-weight: 800; letter-spacing: 1px; text-transform: uppercase; padding: 12px 18px; cursor: pointer; }
.tertiary:hover { border-color: var(--accent); color: var(--accent); }
.game-slot-soon { margin-top: 12px; border: 1px dashed var(--border-lit); color: var(--ink-mute); text-align: center; padding: 22px; font-size: 11px; font-weight: 800; letter-spacing: 2px; }
.home-friends { display: flex; gap: 10px; flex-wrap: wrap; }
.friend-invite { border: 1px dashed var(--border-lit); color: var(--ink-mute); padding: 14px 18px; font-size: 11px; font-weight: 800; letter-spacing: 1px; cursor: pointer; }
.tab-bar { display: flex; background: var(--panel); border-top: 1px solid var(--border); flex: 0 0 auto; }
.tab-item { flex: 1 1 0; background: none; border: none; color: var(--ink-mute); font-size: 9.5px; font-weight: 800; letter-spacing: 1px; padding: 8px 2px 10px; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 3px; }
.tab-item.active { color: var(--accent); }
.tab-ic { font-size: 17px; }

/* Fix default-purple inline links on the login/lobby views. */
#view-login a, #view-lobby a:not(.ghost):not(.primary):not(.user-chip) { color: var(--accent); }

/* =========================================================
   Native-app feel — kill browser tells when hosted in a
   MAUI WebView / Capacitor shell, and stay well-behaved in
   the browser too. Keep this block small and surgical.
   ========================================================= */
html, body {
  margin: 0;
  padding: 0;
  min-height: 100vh;
  min-height: 100dvh;                     /* no jitter from mobile address bar */
  background: #0C0C0F;                    /* match <meta theme-color> */
  overscroll-behavior: none;              /* no Chrome pull-to-refresh */
  /* Pin text scaling to the design size in a MOBILE BROWSER the same way the
     WebView shell pins native TextZoom=100. Without this, a phone with the
     system Font size set above 100% inflates all text and causes the overlap/
     clipping seen on web-but-not-app. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;            /* no long-press "Copy Image" menu */
  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;             /* no 300ms double-tap zoom */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}
/* Re-enable selection where it's useful (copying invite codes, typing). */
input, textarea, [contenteditable="true"],
.invite-code, #invite-code-display, #wr-invite-code, .wr-invite-url,
#invite-url-display, #log {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}
/* Safe-area padding for the notch / home indicator when the app runs
   fullscreen inside a WebView (apple-mobile-web-app-capable). Uses max()
   so browser-only layout is unchanged. */
.top-bar { padding-top: max(6px, env(safe-area-inset-top)); }
.ga-hand { padding-bottom: max(6px, env(safe-area-inset-bottom)); }
.waiting-room-content,
.action-modal-card { padding-bottom: max(14px, env(safe-area-inset-bottom)); }
.view-center { padding-top: max(8px, env(safe-area-inset-top)); padding-bottom: max(8px, env(safe-area-inset-bottom)); }

/* Pressed-state feedback — without :hover on touch, buttons feel dead. */
button, .quick-join-btn, .sp-seat, .hand .card, .room-list .room-row {
  transition: transform 80ms ease, filter 80ms ease, background-color 120ms ease;
  -webkit-touch-callout: none;
}
button:active:not(:disabled),
.quick-join-btn:active,
.sp-seat:active,
.room-list .room-row:active,
.hand .card.playable:active {
  transform: scale(0.97);
  filter: brightness(0.92);
}
/* Settle, don't spring.
   This was cubic-bezier(.2,.9,.25,1.2) on .card and .trick .played-card. The
   1.2 is the y2 control point: the curve travels 20% PAST the destination and
   comes back. On a modal or a sheet that reads as life; on a fanned hand of
   thirteen overlapping cards it is the "too much stretch and come back"
   Ahmad reported. Every card re-layout after a play — layoutHand() writes
   marginInlineStart on all remaining cards — overshot its new position and
   recoiled, thirteen cards at once, which is what made the hand hard to read.

   It applied to CARDS by accident of scope, too: a bare `.card` longhand beats
   the timing function inside `.hand .card`'s transition SHORTHAND depending on
   cascade order, so it was silently reassigning the easing of rules that had
   already chosen their own.

   Sheets and modals keep the spring. Cards get a decelerating curve that stops
   where it is going. */
.action-modal-card, .waiting-room-content {
  transition-timing-function: cubic-bezier(.2, .9, .25, 1.2);
}
.card, .trick .played-card {
  transition-timing-function: cubic-bezier(.22, 1, .36, 1);
}
/* Invisible scrollbars inside scrollable panels (WebView niceness). */
.rooms-list, #log, .go-history-scroll, .scores-details-panel,
.waiting-room-content, .action-modal-body {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.rooms-list::-webkit-scrollbar,
#log::-webkit-scrollbar,
.go-history-scroll::-webkit-scrollbar,
.scores-details-panel::-webkit-scrollbar,
.waiting-room-content::-webkit-scrollbar,
.action-modal-body::-webkit-scrollbar { width: 0; height: 0; display: none; }

body {
  font-family: var(--font-ui);
  font-weight: 600;
  letter-spacing: 0.2px;
  background:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='80' height='80' viewBox='0 0 80 80'><path d='M40 0L80 40L40 80L0 40Z' fill='none' stroke='%23C8963E' stroke-width='0.4' opacity='0.025'/></svg>"),
    radial-gradient(1200px 800px at 50% -10%, rgba(224,173,78,0.07) 0%, transparent 55%),
    radial-gradient(900px 600px at 90% 100%, rgba(200,150,62,0.05) 0%, transparent 55%),
    radial-gradient(600px 400px at 10% 80%, rgba(200,150,62,0.03) 0%, transparent 50%),
    linear-gradient(180deg, var(--bg-grad-1) 0%, var(--bg-grad-2) 55%, var(--bg-grad-3) 100%);
  background-size: 80px 80px, auto, auto, auto, auto;
  color: var(--ink);
  min-height: 100vh;
  min-height: 100dvh;                     /* dynamic vh — no jump as the mobile URL bar shows/hides */
  -webkit-font-smoothing: antialiased;
}

.hidden { display: none !important; }

/* ---------- Top bar ---------- */
/* Decision 1 on the gameplay prototype: the app bar does not exist during play.
   `show()` puts .in-game on <body> whenever #view-room is the active view. */
body.in-game .top-bar { display: none !important; }

.top-bar {
  padding: 10px 24px;
  background: linear-gradient(180deg, rgba(30,30,38,0.97), rgba(20,20,24,0.95));
  border-bottom: 1px solid rgba(224,173,78,0.18);
  backdrop-filter: blur(10px);
  position: sticky;
  top: 0;
  z-index: 20;
}
/* Bar stretches edge-to-edge; inner content matches the .game-shell cap
   so the logo + user chip align with the felt below on tablet/desktop. */
.top-bar-inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  max-width: 720px;
  margin-inline: auto;
  gap: 12px;
}
.brand {
  display: flex; align-items: center; gap: 12px;
  font-weight: 700;
  color: var(--ink);
}
.brand-mark {
  display: inline-flex; gap: 3px;
  font-size: 15px;
  padding: 8px 12px;
  background: var(--panel);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.brand-mark .suit.s { color: var(--ink); }
.brand-mark .suit.r { color: var(--red); }
.brand-name {
  font-size: 22px;
  letter-spacing: 1px;
  color: #E0AD4E;
  text-shadow: 0 1px 4px rgba(200,150,62,0.3);
}
.brand-sub {
  font-size: 12px;
  color: var(--ink-mute);
  text-transform: uppercase;
  letter-spacing: 2px;
  margin-left: 2px;
  padding-top: 4px;
  font-weight: 600;
}
.top-bar-right {
  display: flex; align-items: center; gap: 10px;
  /* A signed-in GUEST carries the most chips of anyone: History, Store, wallet,
     the legacy who + Logout pair, AND Log in / Register. Measured at 768px
     (iPad Mini) the row ran to 941px — the who chip and Logout hung off the
     right edge. Without wrap a flex row simply refuses to shrink past its
     content, so it overflows silently rather than reflowing. */
  flex-wrap: wrap;
  justify-content: flex-end;
  min-width: 0;
}
.user-chip {
  padding: 8px 16px;
  background: var(--panel);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-soft);
  box-shadow: var(--shadow-sm);
}
/* The guest who-chip (#who) is an empty <span> until a guest signs in —
   without content it still painted as a small blank pill next to Register
   for anonymous visitors. Hide any user-chip that has nothing to show. */
.user-chip:empty { display: none; }
/* Anchor chips in the top bar (History, Log in, Register). The .ghost /
   .primary button skins are declared on `button.…` only, so plain <a>
   chips used to render as underlined links. Mirror the small ghost-button
   look (like #btn-logout) for anchors, scoped to the top bar so links
   elsewhere keep their normal styling. */
.top-bar-right a.ghost,
.top-bar-right a.primary {
  display: inline-block;
  padding: 6px 12px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.3px;
  border-radius: var(--radius);
  text-decoration: none;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.top-bar-right a.ghost {
  color: var(--ink-soft);
  border: 1px solid var(--border);
  background: transparent;
}
.top-bar-right a.ghost:hover {
  color: var(--ink);
  border-color: var(--sage-dark);
  background: rgba(200, 150, 62, 0.18);
}
.top-bar-right a.primary {
  background: linear-gradient(180deg, #E0AD4E, #C8963E);
  border: 1px solid #8B6B2A;
  color: #111114;
}
.top-bar-right a.primary:hover {
  background: linear-gradient(180deg, #F0C76E, #E0AD4E);
  border-color: #C8963E;
}

/* ---------- Main ---------- */
main {
  padding: 14px 16px 18px;
  max-width: 1600px;
  margin: 0 auto;
}
/* In the game view, let the content fill the viewport so the grid has
   height to distribute — override the generic `main` padding. */
main:has(#view-room:not(.hidden)) {
  padding: 0;
  max-width: 100%;
}

.view-center {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(100vh - 140px);
  min-height: calc(100dvh - 140px);
}

/* ---------- Panels ---------- */
.card-panel {
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-xl);
  padding: 32px 36px;
}
/* Decorative corner accents on card panels */
.card-panel::after {
  content: "";
  position: absolute;
  inset: 6px;
  border: 1px solid rgba(200, 150, 62, 0.06);
  border-radius: calc(var(--radius-xl) - 4px);
  pointer-events: none;
  z-index: 0;
  background:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'><path d='M0 0L20 0L20 20' fill='none' stroke='%23C8963E' stroke-width='1.5' opacity='0.12'/></svg>") no-repeat top left / 20px 20px,
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'><path d='M20 0L0 0L0 20' fill='none' stroke='%23C8963E' stroke-width='1.5' opacity='0.12'/></svg>") no-repeat top right / 20px 20px,
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'><path d='M0 20L20 20L20 0' fill='none' stroke='%23C8963E' stroke-width='1.5' opacity='0.12'/></svg>") no-repeat bottom left / 20px 20px,
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'><path d='M20 20L0 20L0 0' fill='none' stroke='%23C8963E' stroke-width='1.5' opacity='0.12'/></svg>") no-repeat bottom right / 20px 20px;
}

.login-panel { width: 100%; max-width: 400px; }
.login-panel h2 { margin: 0 0 4px; font-size: 24px; color: var(--ink); }
.login-panel .muted { margin: 0 0 20px; color: var(--ink-mute); font-size: 15px; }
.login-panel label {
  display: block;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1.2px;
  color: var(--ink-mute);
  margin: 14px 0 6px;
}
.login-panel input { width: 100%; }

.divider {
  display: flex;
  align-items: center;
  text-align: center;
  color: var(--ink-mute);
  font-size: 12px;
  font-weight: 600;
  margin: 20px 0 4px;
  text-transform: uppercase;
  letter-spacing: 1.5px;
}
.divider::before, .divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border-soft);
}
.divider span { padding: 0 12px; }

.lobby-panel { width: 100%; max-width: 760px; }
.lobby-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}
.lobby-head h2 { margin: 0; }
/* ---------- Quick Join ---------- */
.quick-join-section {
  padding: 0 0 18px;
  border-bottom: 1px solid var(--border-soft);
  margin-bottom: 18px;
}
.quick-join-btn {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 16px 22px;
  border-radius: var(--radius);
  background: linear-gradient(135deg, rgba(224, 173, 78, 0.15), rgba(224, 173, 78, 0.06));
  border: 2px solid var(--yellow);
  color: var(--ink);
  cursor: pointer;
  transition: transform 0.1s, box-shadow 0.2s, background 0.2s;
  text-align: left;
}
.quick-join-btn:hover:not(:disabled) {
  background: linear-gradient(135deg, rgba(224, 173, 78, 0.25), rgba(224, 173, 78, 0.12));
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(224, 173, 78, 0.3);
}
.quick-join-btn:disabled { opacity: 0.5; cursor: wait; }
.quick-join-btn .qj-icon { font-size: 28px; line-height: 1; flex-shrink: 0; }
.quick-join-btn .qj-text { display: flex; flex-direction: column; gap: 2px; }
.quick-join-btn .qj-text strong { font-size: 17px; font-weight: 800; color: var(--yellow); letter-spacing: 0.5px; }
.quick-join-btn .qj-text small { font-size: 13px; color: var(--ink-mute); font-weight: 500; }

/* ---------- Create room ---------- */
.new-room {
  padding: 14px 0 20px;
  border-bottom: 1px solid var(--border-soft);
  margin-bottom: 18px;
}
.create-room-fields {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.create-room-fields input { flex: 1; min-width: 140px; }
.create-room-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.create-room-actions button {
  flex: 1;
  min-width: 170px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.create-room-actions .btn-icon { font-size: 16px; line-height: 1; }
.new-room h3,
.join-code-section h3,
.lobby-panel > h3 {
  margin: 0 0 10px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.new-room h3::before,
.join-code-section h3::before,
.lobby-panel > h3::before {
  content: "◆";
  font-size: 8px;
  color: var(--yellow);
  opacity: 0.5;
}

/* ---------- Typography ---------- */
/* Poiret One for decorative headings only */
.brand-name, h1, h2, .felt-logo, .action-modal-header h3 {
  font-family: var(--font-ui);
}
h1 { margin: 0; font-size: 28px; color: var(--ink); font-weight: 700; }
h2 { margin: 8px 0; font-size: 26px; color: var(--ink); font-weight: 700; }
h3 {
  margin: 8px 0 6px;
  font-size: 14px;
  color: #E0AD4E;
  text-transform: uppercase;
  letter-spacing: 2px;
  font-weight: 700;
}
.muted { color: var(--ink-mute); }

/* ---------- Inputs & buttons ---------- */
input, select {
  padding: 12px 16px;
  /* 16px minimum prevents iOS Safari / WKWebView from auto-zooming
     the whole page when a field gains focus. */
  font-size: 16px;
  font-family: inherit;
  font-weight: 600;
  border-radius: var(--radius);
  border: 1px solid var(--border-lit);
  background: rgba(255,255,255,0.06);
  color: var(--ink);
  transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
  outline: none;
  /* Force the native <option> dropdown to render with a dark color scheme so
     hovered/highlighted items aren't white-on-white regardless of OS theme. */
  color-scheme: dark;
}
/* Explicit option colors — some browsers (especially Windows Chrome in light
   OS mode) otherwise paint hovered options white-on-light-grey. */
select option {
  background-color: #1b1b1b;
  color: #f5f5f5;
}
select option:checked,
select option:hover {
  background-color: #E0AD4E;
  color: #111;
}
input::placeholder { color: var(--ink-mute); font-weight: 400; }
input:focus, select:focus {
  border-color: #E0AD4E;
  background: rgba(255,255,255,0.1);
  box-shadow: 0 0 0 3px rgba(224, 173, 78, 0.25);
}

button {
  padding: 12px 20px;
  font-size: 15px;
  font-family: inherit;
  font-weight: 700;
  letter-spacing: 0.5px;
  border-radius: var(--radius);
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 0.08s, background 0.15s, border-color 0.15s, box-shadow 0.15s;
  color: var(--ink);
  background: var(--panel-2);
  border-color: var(--border);
  box-shadow: var(--shadow-sm);
}
button:hover:not(:disabled) {
  background: var(--panel);
  border-color: var(--sage-dark);
  transform: translateY(-1px);
  box-shadow: var(--shadow-md);
}
button:active:not(:disabled) { transform: translateY(0); }
button:disabled { opacity: 0.45; cursor: not-allowed; box-shadow: none; }

/* Keyboard affordance: a clear gold ring on anything focusable via Tab.
   :focus-visible only — mouse/touch presses stay ring-free. Contrast note:
   .primary / .gold already use dark ink (#111114 / #1C1C22) on the gold
   gradients (≈7-9:1, clears WCAG AA); no text change needed there. */
button:focus-visible,
a:focus-visible,
[role="button"]:focus-visible {
  outline: 2px solid var(--yellow);
  outline-offset: 2px;
}

button.primary {
  background: linear-gradient(180deg, #E0AD4E, #C8963E);
  border-color: #8B6B2A;
  color: #111114;
  box-shadow: 0 6px 14px rgba(200, 150, 62, 0.35);
  text-shadow: 0 1px 0 rgba(255,255,255,0.15);
}
button.primary:hover:not(:disabled) {
  background: linear-gradient(180deg, #F0C76E, #E0AD4E);
  border-color: #C8963E;
  box-shadow: 0 8px 20px rgba(200, 150, 62, 0.45);
}
button.gold {
  background: linear-gradient(180deg, var(--yellow-soft), var(--yellow));
  border-color: var(--yellow-dark);
  color: #1C1C22;
}
button.gold:hover:not(:disabled) {
  background: linear-gradient(180deg, var(--yellow), var(--yellow-dark));
}
button.danger {
  background: linear-gradient(180deg, #e27060, var(--red));
  border-color: var(--red);
  color: #fff;
}
button.ghost {
  background: transparent;
  color: var(--ink-soft);
  border: 1px solid var(--border);
  box-shadow: none;
}
button.ghost:hover:not(:disabled) {
  color: var(--ink);
  border-color: var(--sage-dark);
  background: rgba(200, 150, 62, 0.18);
}
button.block { display: block; width: 100%; margin-top: 10px; }
button.flex  { flex: 1; }

.row {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}

/* ---------- Room list ---------- */
.room-list {
  list-style: none;
  padding: 0;
  margin: 8px 0 0;
  display: grid;
  gap: 10px;
}
.room-list li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  /* An open-table row carries a name, a phase line, a seat badge, an expiry
     pill and a Join button. Measured on a 360px Android the row ran to 412px
     and the Join button itself sat at 313-393 — its right edge past the screen,
     so the one action on the row was half untappable. A flex row will not
     shrink past its content's min-width, so it overflowed instead of
     reflowing; wrapping drops the controls onto their own line. Only shows up
     when the list has rows in it, which is why an empty-state sweep missed it. */
  flex-wrap: wrap;
  gap: 8px;
  padding: 14px 18px;
  background: var(--cream-soft);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  transition: border-color 0.15s, transform 0.08s, box-shadow 0.15s;
}
.room-list li:hover {
  border-color: var(--sage-dark);
  transform: translateY(-1px);
  box-shadow: var(--shadow-md);
}
.room-list .room-info strong { display: block; font-size: 16px; font-weight: 700; color: var(--ink); }
.room-list .room-info small  { color: var(--ink-mute); font-size: 13px; font-weight: 500; }
.room-list .empty {
  justify-content: center;
  color: var(--ink-mute);
  font-style: italic;
}
.room-list .room-info { min-width: 0; flex: 1 1 190px; }
.room-list .room-info strong,
.room-list .room-info small { overflow-wrap: anywhere; }
.room-list .room-right {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  /* Sit at the end when it shares the line, and fill the width when it wraps
     onto its own — so Join is always a full, reachable target. */
  justify-content: flex-end;
  flex: 1 1 auto;
  min-width: 0;
}
/* Player count badge */
.player-count-badge {
  display: inline-flex;
  align-items: center;
  gap: 1px;
  padding: 4px 10px;
  border-radius: var(--radius);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.3px;
  white-space: nowrap;
  border: 1px solid var(--border);
  background: var(--cream-soft);
  color: var(--ink-soft);
}
.player-count-badge .pcb-num { color: var(--ink); }
.player-count-badge.seats-open {
  background: rgba(76, 175, 80, 0.15);
  border-color: #4caf50;
  color: #4caf50;
}
.player-count-badge.seats-open .pcb-num { color: #4caf50; }
.player-count-badge.seats-mid {
  background: rgba(224, 173, 78, 0.15);
  border-color: var(--yellow);
  color: var(--yellow-dark);
}
.player-count-badge.seats-mid .pcb-num { color: var(--yellow); }
.player-count-badge.seats-low {
  background: rgba(198, 69, 44, 0.15);
  border-color: var(--red);
  color: var(--red);
}
.player-count-badge.seats-low .pcb-num { color: var(--red); }
.expiry-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border-radius: var(--radius);
  background: var(--cream-soft);
  border: 1px solid var(--border);
  color: var(--ink-soft);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.4px;
  white-space: nowrap;
}
.expiry-pill::before {
  content: "⏱";
  font-size: 12px;
  line-height: 1;
}
.expiry-pill.warn {
  background: var(--yellow-soft);
  border-color: var(--yellow-dark);
  color: #1C1C22;
}
.expiry-pill.danger {
  background: rgba(198, 69, 44, 0.2);
  border-color: var(--red);
  color: var(--red);
  animation: pulseRed 1.2s ease-in-out infinite;
}
.room-list li.expiring {
  opacity: 0.85;
}
.room-list li.expired {
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity 0.4s ease, transform 0.4s ease;
  pointer-events: none;
}
/* ---------- Waiting Room ---------- */
.waiting-room-overlay {
  position: fixed; inset: 0; z-index: 900;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.7);
  backdrop-filter: blur(6px);
}
.waiting-room-overlay.hidden { display: none; }
.waiting-room-content {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  width: 94%;
  max-width: 700px;
  max-height: 90vh;
  overflow-y: auto;
  padding: 28px 32px 24px;
}
.wr-header { text-align: center; margin-bottom: 20px; }
.wr-kicker {
  display: inline-block;
  padding: 4px 14px;
  border: 1px solid var(--yellow);
  border-radius: var(--radius);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--yellow);
  margin-bottom: 8px;
}
.wr-title {
  font-family: var(--font-ui);
  font-size: 24px; color: var(--ink); margin: 6px 0 4px;
  text-transform: none; letter-spacing: 0;
}
.wr-sub { color: var(--ink-mute); font-size: 14px; margin: 0; }

/* Seat picker — seats positioned around a large felt table */
.seat-picker {
  position: relative;
  margin: 0 auto 20px;
  max-width: 420px;
  width: 100%;
  aspect-ratio: 1.15;
}
.seat-picker .sp-table-felt {
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at center, #1a5a32, #0d3b20);
  border: 3px solid #2a7a48;
  border-radius: var(--radius);
  display: flex; align-items: center; justify-content: center;
  box-shadow: inset 0 2px 16px rgba(0, 0, 0, 0.5), 0 4px 20px rgba(0, 0, 0, 0.3);
}
.sp-felt-logo {
  font-size: 22px; letter-spacing: 8px;
  opacity: 0.2; color: #fff;
}

.sp-seat {
  position: absolute;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 8px 12px;
  background: rgba(0, 0, 0, 0.45);
  border: 2px dashed rgba(255, 255, 255, 0.25);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color 0.2s, background 0.2s, transform 0.15s;
  text-align: center;
  z-index: 2;
  backdrop-filter: blur(4px);
}
.sp-seat:hover {
  border-color: var(--yellow);
  background: rgba(224, 173, 78, 0.15);
  transform: scale(1.05);
}
.sp-seat.sp-occupied {
  border-style: solid;
  border-color: rgba(255, 255, 255, 0.35);
  cursor: default;
  background: rgba(0, 0, 0, 0.55);
}
.sp-seat.sp-occupied:hover { transform: none; }
.sp-seat.sp-you {
  border-color: var(--yellow);
  border-style: solid;
  background: rgba(224, 173, 78, 0.18);
}

/* Position each seat around the table */
.sp-seat.sp-north {
  top: 4%; left: 50%; transform: translateX(-50%);
}
.sp-seat.sp-north:hover { transform: translateX(-50%) scale(1.05); }
.sp-seat.sp-south {
  bottom: 4%; left: 50%; transform: translateX(-50%);
}
.sp-seat.sp-south:hover { transform: translateX(-50%) scale(1.05); }
.sp-seat.sp-west {
  left: 3%; top: 50%; transform: translateY(-50%);
}
.sp-seat.sp-west:hover { transform: translateY(-50%) scale(1.05); }
.sp-seat.sp-east {
  right: 3%; top: 50%; transform: translateY(-50%);
}
.sp-seat.sp-east:hover { transform: translateY(-50%) scale(1.05); }

.sp-seat .sp-avatar {
  width: 48px; height: 48px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
  border: 2px solid rgba(255, 255, 255, 0.3);
  display: flex; align-items: center; justify-content: center;
  font-size: 20px; color: rgba(255, 255, 255, 0.5);
  overflow: hidden;
}
.sp-seat .sp-avatar img {
  width: 100%; height: 100%; object-fit: cover; border-radius: 50%;
}
.sp-seat.sp-occupied .sp-avatar {
  border-color: var(--sage-dark);
  background: rgba(255, 255, 255, 0.15);
}
.sp-seat.sp-you .sp-avatar {
  border-color: var(--yellow);
  box-shadow: 0 0 0 3px rgba(224, 173, 78, 0.3);
}
.sp-seat .sp-label {
  font-size: 12px; font-weight: 700; color: rgba(255, 255, 255, 0.8);
  white-space: nowrap;
}
.sp-seat.sp-occupied .sp-label { color: #fff; }
.sp-seat.sp-you .sp-label { color: var(--yellow); }
.sp-seat .sp-team-tag {
  font-size: 9px; font-weight: 600;
  padding: 1px 7px; border-radius: var(--radius);
  text-transform: uppercase; letter-spacing: 1px;
}
.sp-team-0 { background: rgba(96, 165, 250, 0.25); color: #60a5fa; }
.sp-team-1 { background: rgba(74, 222, 128, 0.25); color: #4ade80; }

/* Invite panel */
.wr-invite {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  padding: 16px 20px;
  margin-bottom: 16px;
  text-align: center;
}
.wr-invite-code-row {
  display: flex; align-items: center; justify-content: center;
  gap: 12px; flex-wrap: wrap;
}
.wr-invite-label {
  font-size: 12px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 1.5px; color: var(--ink-mute);
}
.wr-invite-code {
  font-size: 24px; font-weight: 800;
  font-family: "Courier New", monospace;
  letter-spacing: 4px; color: var(--yellow);
  padding: 4px 16px;
  background: rgba(224, 173, 78, 0.1);
  border: 1px solid rgba(224, 173, 78, 0.3);
  border-radius: var(--radius);
}
.wr-invite-qr {
  margin: 12px auto 8px;
  display: flex; justify-content: center;
}
.wr-invite-qr canvas, .wr-invite-qr img {
  border-radius: var(--radius);
  border: 3px solid rgba(255, 255, 255, 0.1);
}
.wr-invite-url {
  font-size: 12px; color: var(--ink-mute);
  word-break: break-all;
  margin-top: 4px;
}

/* Actions */
.wr-actions {
  display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;
}
.wr-actions button { min-width: 160px; }
.wr-actions button.big {
  padding: 14px 28px; font-size: 16px;
}

@keyframes pulseRed {
  0%, 100% { box-shadow: 0 0 0 0 rgba(198, 69, 44, 0.3); }
  50%      { box-shadow: 0 0 0 4px rgba(198, 69, 44, 0.0); }
}

/* ---------- Centered game shell (tablet + desktop cap) ----------
   Phones (<700px) get the natural full width — no cap. Tablet (≥700px)
   caps the gameplay column at 560px; laptop (≥1024px) at 680px; large
   desktop (≥1440px) at 720px. Goal: desktop mirrors a phone-shaped
   table centered on the page rather than stretching the felt edge to
   edge. Applied via .game-shell on #view-room so every in-game
   element (.game-area grid, .round-status, .log-wrap) inherits the
   cap automatically. */
.game-shell {
  width: 100%;
  margin-inline: auto;
}
@media (min-width: 700px) {
  .game-shell { max-width: 560px; }
}
@media (min-width: 1024px) {
  .game-shell { max-width: 680px; }
}
@media (min-width: 1440px) {
  .game-shell { max-width: 720px; }
}
/* Override the .game-area's own 1700px max-width so it tracks the
   shell's cap on every viewport. */
.game-shell .game-area {
  max-width: 100%;
  margin-inline: auto;
}

/* Selector-scope fix for the 61adb99 regression: the .game-shell rule
   above caps #view-room itself, but on Laptop (1366x768) the in-game
   felt + hand row + score panels were still stretching to ~1140px wide
   because the grid parent (.game-area) and its descendants weren't
   tracking the cap reliably. Anchoring on #view-room bumps specificity
   so every structural element inside the room view honors the same cap. */
@media (min-width: 700px) and (max-width: 1023px) {
  #view-room.game-shell,
  #view-room .game-area,
  #view-room .felt-wrap,
  #view-room .felt,
  #view-room .hand-row,
  #view-room .ga-cell,
  #view-room .ga-hand,
  #view-room .round-status {
    max-width: 560px;
    width: min(100%, 560px);
    margin-left: auto;
    margin-right: auto;
  }
}
@media (min-width: 1024px) {
  #view-room.game-shell,
  #view-room .game-area,
  #view-room .felt-wrap,
  #view-room .felt,
  #view-room .hand-row,
  #view-room .ga-cell,
  #view-room .ga-hand,
  #view-room .round-status {
    max-width: 680px;
    width: min(100%, 680px);
    margin-left: auto;
    margin-right: auto;
  }
}
@media (min-width: 1440px) {
  #view-room.game-shell,
  #view-room .game-area,
  #view-room .felt-wrap,
  #view-room .felt,
  #view-room .hand-row,
  #view-room .ga-cell,
  #view-room .ga-hand,
  #view-room .round-status {
    max-width: 720px;
    width: min(100%, 720px);
    margin-left: auto;
    margin-right: auto;
  }
}

/* P1 (hand row cap): at >=700px the felt/scores/inline panels respect the
   in-game cap, but .hand-row's children (.card) keep their intrinsic desktop
   width (flex: 0 0 auto + negative-margin overlap) and overflow the capped
   column — leftmost/rightmost cards visibly clipped on Laptop 1366. Force
   the inner .hand to 100% of the capped row, and let each card take an equal
   share of that row with min-width: 0 (compress proportionally) and
   max-width: 56px (no oversized cards on Large Desktop). Phone (<700px)
   layout, which uses the existing fan/overlap math, is untouched. */
@media (min-width: 700px) {
  /* CRITICAL: cap --card-w from the cap, not from the viewport.
     Without this, .hand and .ga-hand still compute --card-w from
     `clamp(80px-90px, (100vw - 40px)/N, 120px-140px)`, which on Laptop
     1366 gives 120-140px. .ga-hand height tracks --card-w (height = card-w *
     1.65 + 62) so a 120px card-w bloats the lane to ~260px tall AND the
     corner ranks/pip font-sizes scale up too. The previous d7ef727 cap only
     constrained the card flex-basis/max-width via the flex trick, so width
     said 56 but height + typography still came from --card-w. Fix is to set
     --card-w to a desktop-appropriate value directly. */
  #view-room .ga-hand,
  #view-room .ga-hand .hand,
  #view-room .hand-row .hand {
    --card-w: 52px;
  }
  #view-room .ga-hand,
  #view-room .hand-row,
  #view-room .hand-row .hand {
    max-width: 100%;
    width: 100%;
    margin-inline: auto;
    overflow: hidden;
  }
  #view-room .ga-hand .hand .card,
  #view-room .hand-row .hand .card {
    flex: 1 1 0;
    min-width: 0;
    max-width: 56px;
    margin-left: 0;
  }
}

/* ---------- Game screen ---------- */
#view-room {
  display: flex;
  flex-direction: column;
  gap: 6px;
  height: calc(100vh - 62px); /* fill viewport under the top bar */
  height: calc(100dvh - 62px);
  min-height: 540px;
  padding: 6px 10px 6px;
}
/* IN GAME THE TABLE OWNS THE WHOLE SCREEN.
   Decision 1 of the handoff: "App bar during play — gone. The table owns the
   screen." The bar is already hidden by `body.in-game .top-bar`, but this rule
   was still reserving 62px for it, plus 12px of padding and a 6px gap — 80px of
   the 844 budget spent on chrome that is not on screen. The bands are computed
   against the full viewport, so every one of them was being squeezed. */
body.in-game #view-room {
  height: 100vh;
  height: 100dvh;
  min-height: 0;
  padding: 0;
  gap: 0;
}
#view-room > .game-area {
  flex: 1 1 auto;
  min-height: 0;
  /* Anchor for the journey overlays (kn-* sheets from journey.html): the
     design positions its sheets absolute over the game area with the sheet's
     bottom edge sitting above the hand. Grid layout is unaffected. */
  position: relative;
}
/* Legacy panels removed from HTML — suppress any stale rules. */
.room-head, .actions-panel, .hand-wrap { display: none !important; }
/* Room head is a slim single-row strip. */
.room-head {
  display: flex;
  gap: 10px;
  align-items: center;
  padding: 4px 12px;
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  flex-wrap: nowrap;
  min-height: 32px;
}
.room-head h2 { margin: 0; font-size: 13px; font-weight: 700; }
.room-head-spacer { flex: 1; }
.room-head button.ghost { padding: 3px 10px; font-size: 11px; }

.phase-badge {
  padding: 3px 10px;
  background: var(--yellow-soft);
  color: #1C1C22;
  border: 1px solid var(--yellow-dark);
  border-radius: var(--radius);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1px;
  font-weight: 700;
}
.phase-badge:empty { display: none; }

.score-panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
}
.score-box {
  background: var(--cream-soft);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  padding: 6px 8px;
  text-align: center;
  box-shadow: var(--shadow-sm);
  line-height: 1.1;
}
.score-box .score-label {
  font-size: 11px;
  font-weight: 700;
  color: var(--ink-mute);
  text-transform: uppercase;
  letter-spacing: 1px;
}
.score-box .score-value {
  font-size: 22px;
  font-weight: 800;
  color: var(--ink);
  margin-top: 2px;
}
.score-box .score-sub {
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-mute);
  margin-top: 1px;
  letter-spacing: 0.5px;
}
.score-box-team { }

/* Immersive layout — seats overlay the felt.
   Row 1:  [---------- slim top bar ----------]
   Row 2:  [--------  FELT (full width)  --------]
   Row 3:  [--- your hand spans full width ---]
   West/east/north/south are absolutely positioned over the felt. */
/* THE BANDS ARE THE DESIGN'S NUMBERS, NOT A CONSEQUENCE OF CONTENT.
 *
 * tokens.json > devices.phone gives every one of them: hud 54, hand 158,
 * rail 58, safe 26 — and 844 = 24 + 54 + 524 + 158 + 58 + 26 is exactly the
 * 2.8 / 6.4 / 62.1 / 18.7 / 6.9 / 3.1 vertical budget printed on the prototype.
 *
 * Ours drifted because the bands were derived instead of declared: the hand
 * sized itself off the card (`--card-w * 1.4 + 62px`, which measured 173.4px
 * against the design's 158) and there was no action rail at all, so the table
 * got 57.9% where the design allocates 62.1%. Declaring the bands makes the
 * budget hold by construction rather than by luck.
 *
 * There is no `narr` row any more. The prototype has no narrator band — whose
 * turn it is lives ON THE SEAT, and the centre of the felt carries the phase
 * card. The narrator now overlays the felt instead of stealing a band from it. */
.game-area {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: var(--hudH) minmax(0, 1fr) var(--handH) var(--railH);
  grid-template-areas:
    "top    top"
    "felt   felt"
    "hand   hand"
    "rail   rail";
  gap: 0;
  width: 100%;
  max-width: 1700px;
  height: 100%;
  margin: 0 auto;
  padding: 0;
  padding-bottom: max(var(--safeH), env(safe-area-inset-bottom, 0px));
  align-items: stretch;
  overflow: hidden;
  box-sizing: border-box;
}
.ga-cell { display: flex; min-width: 0; min-height: 0; }
.ga-top    { grid-area: top; }
/* Overlays the felt's top edge rather than occupying a row of its own. */
.ga-narrator { grid-area: felt; grid-column: 1 / -1; align-self: start; justify-self: stretch;
               z-index: 30; pointer-events: none; }
.ga-felt   { grid-area: felt;   align-items: stretch; justify-content: stretch; }
.ga-hand   { grid-area: hand;   flex-direction: column; align-items: stretch; }
.ga-rail   { grid-area: rail;   align-items: stretch; }

/* Seats overlay the felt — not in their own grid columns. */
.ga-north {
  grid-area: felt;
  align-self: start;
  justify-self: center;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  z-index: 2;
  pointer-events: none;
  padding: 4px 0 0;
  min-width: 0;
  width: auto;
}
.ga-north > .seat-slot { pointer-events: auto; width: auto; align-self: center; }

.ga-west, .ga-east {
  grid-area: felt;
  align-self: stretch;
  justify-self: stretch;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 0;
  z-index: 2;
  pointer-events: none;
  padding: 0 16px;
  min-width: 0;
}
.ga-west { justify-content: flex-start; }
.ga-east { justify-content: flex-end; }
/* East/west seats keep the vertical stack centered */
.ga-west > .seat-slot, .ga-east > .seat-slot {
  pointer-events: auto;
  width: auto;
  height: auto;
  align-self: center;
  flex: 0 0 auto;
}

.ga-south {
  display: flex;
  grid-area: felt;
  align-self: end;
  justify-self: center;
  justify-content: center;
  align-items: flex-end;
  z-index: 2;
  pointer-events: none;
  padding: 0 0 4px;
  min-width: 0;
  width: auto;
}
.ga-south > .seat-slot { pointer-events: auto; width: auto; align-self: center; }

/* ---------- ONE slim in-game top bar ----------
   Single grid row: [team scores + ℹ] [contract] [phase · leave].
   Pre-game, a second line (.gt-room-strip) carries the room name + invite
   banner; renderActions() hides it the moment the match starts so the bar
   stays one slim row in-game and the felt owns the vertical space. */
.ga-top {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) auto minmax(0, 1fr);
  grid-template-rows: auto;
  align-items: center;
  gap: 4px 10px;
  padding: 5px 10px;
  min-height: 46px;
  box-sizing: border-box;
  background: linear-gradient(180deg, rgba(30,30,38,0.95), rgba(20,20,24,0.95));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}

/* Left: both team scores — one compact "NAMES 12 · 3 tricks" line per team. */
.gt-scores {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.gt-score-rows {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.gt-score-rows .ms-row {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
  line-height: 1.25;
  border-radius: var(--radius);
  padding: 0 4px;
  margin: 0 -4px;
}
.gt-score-rows .ms-label {
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  max-width: 168px;
}
#ms-row-team0 .ms-label { color: #3b82f6; }
#ms-row-team1 .ms-label { color: #34d058; }
.gt-score-rows .ms-score {
  font-size: 15px;
  font-weight: 800;
  color: var(--ink);
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
}
.gt-score-rows .ms-tricks {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--ink-soft);
  white-space: nowrap;
  flex: 0 0 auto;
}
.gt-score-rows .ms-tricks::before { content: "· "; opacity: 0.6; }
/* Bidding team — subtle gold tint on its row. */
.gt-score-rows .ms-row.bidding { background: rgba(224, 173, 78, 0.12); }
.gt-score-rows .ms-row.bidding .ms-label,
.gt-score-rows .ms-row.bidding .ms-score { color: var(--sage-deep); }

/* Full names by default; short names swap in on phones (inline, "/"-joined). */
.ms-label-full { display: inline; }
.ms-label-short { display: none; }
.ms-label-short .ms-name + .ms-name::before {
  content: "/";
  opacity: 0.6;
  margin: 0 1px;
}

/* Center: the contract (bid + trump) — the most glanceable fact in play.
   Hidden (space reserved) until a bid exists so the bar never jumps. */
.gt-contract {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  justify-self: center;
  padding: 3px 12px;
  background: rgba(0, 0, 0, 0.30);
  border: 1px solid var(--border-lit);
  border-radius: var(--radius);
}
.gt-contract.empty { visibility: hidden; }
.gt-contract .gt-bid {
  font-size: 18px;
  font-weight: 900;
  color: var(--ink);
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}
.gt-contract .gt-trump {
  font-size: 20px;
  font-weight: 900;
  color: var(--sage-deep);
  line-height: 1;
}

/* Right: phase badge + Leave. */
.gt-right {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  min-width: 0;
}
.gt-right #room-phase {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 164px;
  min-width: 0;
}
.gt-right #btn-leave {
  flex: 0 0 auto;
  padding: 4px 10px;
  font-size: 12px;
  white-space: nowrap;
}

/* Pre-game strip: room name + invite code on a second bar line. */
.gt-room-strip {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 4px 10px;
  min-width: 0;
  border-top: 1px dashed var(--border-soft);
  padding-top: 4px;
}
.gt-room-strip h2 {
  margin: 0;
  font-family: inherit;
  font-size: 13px;
  font-weight: 800;
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.gt-room-strip .invite-banner {
  margin-top: 0;
  padding: 2px 10px;
  font-size: 12px;
  flex-wrap: wrap;
  justify-content: center;
  min-width: 0;
}
.gt-room-strip .invite-code { font-size: 13px; }

/* Round-history (ℹ) button sits inline beside the score rows. */
.scores-details-btn {
  flex: 0 0 auto;
  padding: 3px 7px;
  font-size: 13px;
  line-height: 1.3;
  cursor: pointer;
}
.scores-details-panel {
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 6px;
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  padding: 12px;
  max-width: 500px;
  min-width: 320px;
  z-index: 100;
}
.scores-details-panel.hidden {
  display: none;
}
.sdp-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  color: var(--ink-mute);
  letter-spacing: 1px;
  margin-bottom: 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border-soft);
}
.sdp-close {
  background: transparent;
  border: 1px solid var(--border-soft);
  color: var(--ink);
  width: 26px;
  height: 26px;
  border-radius: var(--radius);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition: background 0.15s, color 0.15s;
}
.sdp-close:hover {
  background: rgba(200, 60, 60, 0.18);
  color: #ff8a8a;
  border-color: rgba(200, 60, 60, 0.4);
}
.sdp-close:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.sdp-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}
.sdp-table thead {
  background: rgba(200,150,62,0.08);
}
.sdp-table th {
  padding: 6px 4px;
  text-align: left;
  font-weight: 700;
  color: var(--ink-mute);
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: 0.5px;
  border-bottom: 1px solid var(--border-soft);
}
.sdp-table td {
  padding: 6px 4px;
  text-align: left;
  color: var(--ink);
  border-bottom: 1px solid var(--border-soft);
}
.sdp-table tbody tr:hover {
  background: rgba(200,150,62,0.08);
}

/* ---------- Hand row spans the bottom ---------- */
.ga-hand {
  /* The lane is --handH, straight from tokens.json > devices, and the CARD is
     --cw x --ch (84x118 on a phone). Both are design numbers.
     This used to run the other way round: the card sized itself off the
     viewport and the lane was `card-w * 1.4 + 62px`, which measured 173.4px
     against the design's 158 and quietly ate 15px of the table zone. */
  --card-w: var(--cw);
  padding: 4px 8px 8px;
  background: none;
  border: 0;
  border-radius: var(--radius);
  box-shadow: none;
  height: var(--handH);
  min-height: var(--handH);
  max-height: var(--handH);
  box-sizing: border-box;
  overflow: hidden;
}
.ga-hand .hand-row {
  display: flex;
  justify-content: center;
}
.ga-hand .hand-hint-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  /* Lock to a fixed height so the hand row never jumps when the hint text,
     action deadline, or actions-row contents change between tricks. */
  height: 28px;
  min-height: 28px;
  max-height: 28px;
  margin-top: 2px;
  flex-wrap: nowrap;
  white-space: nowrap;
  overflow: hidden;
}
.ga-hand .hand-hint {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.ga-hand .actions-row {
  display: flex;
  gap: 6px;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: center;
  min-width: 0;
}
/* Keep the row reserved even when empty so the grid track doesn't shrink. */
.ga-hand .actions-row:empty { display: flex; }
.score-box-team.bidding {
  border-color: var(--yellow-dark);
  background: var(--yellow-soft);
  box-shadow: 0 0 0 3px rgba(224, 173, 78, 0.45);
}
.score-box-team.bidding .score-label { color: #5A5048; }
.score-box-team.bidding .score-value { color: #1C1C22; }
.score-box-team.bidding .score-sub   { color: #5A5048; }

/* ---------- Round status bar ----------
   Now a single centred progress pill ("Need X more · Y left") — the
   contract + bidder facts moved to the top bar / seat chips. */
.round-status {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
  padding: 3px 12px;
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  font-size: 11px;
  box-shadow: var(--shadow-sm);
  flex-wrap: nowrap;
  min-height: 28px;
}
.round-status.won  {
  border-color: var(--sage-deep);
  background: linear-gradient(180deg, var(--lime-soft), var(--sage-soft));
}
.round-status.lost {
  border-color: var(--red);
  background: linear-gradient(180deg, rgba(198, 69, 44, 0.2), rgba(178, 42, 34, 0.25));
}
.round-status-left {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.round-status .round-label {
  color: var(--ink-mute);
  text-transform: uppercase;
  letter-spacing: 1px;
  font-size: 12px;
  font-weight: 700;
}
.round-status strong { color: var(--ink); font-weight: 700; }
.round-status .round-sep { color: var(--ink-mute); opacity: 0.6; }
.round-status-right {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
}
.round-status-right .goal-pill {
  padding: 3px 10px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--cream-soft);
  color: var(--ink);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
}
.round-status-right .goal-pill.ok   { color: #1C1C22; border-color: var(--sage-dark); background: var(--lime-soft); }
.round-status-right .goal-pill.warn { color: #1C1C22; border-color: var(--orange); background: var(--yellow-soft); }
.round-status-right .goal-pill.fail { color: #fff; border-color: var(--red); background: var(--red); }
.round-status-right .goal-pill.done { color: #fff; border-color: var(--sage-deep); background: var(--sage-dark); }

/* Status text wraps cleanly rather than clipping at narrow widths.
   "Waiting for cutter's decision…" was rendering as "Waiting for cutte"
   on Fold Cover (344px). */
.round-status,
.round-status-left,
.round-status-right {
  min-width: 0;
  white-space: normal;
  overflow: visible;
  text-overflow: unset;
  text-wrap: balance;
}

/* ---------- Game table ----------
   Seats now live inside .side-rail on either side of the felt, so the felt
   gets the entire center column. The grid-area names are kept so the JS
   seat lookup (getElementById('seat-north'|'south'|'east'|'west')) keeps
   working unchanged; they're just purely logical now. */
.seat-slot { display: block; }

.seat {
  background: none;
  border: none;
  border-radius: var(--radius);
  padding: 0;
  min-width: 0;
  box-shadow: none;
  transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 4px;
}
/* Seat identity row: avatar, name block, bid/ready pills. */
.seat .seat-main {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-width: 0;
}
.seat .seat-identity {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 1px;
  min-width: 0;
  flex: 0 0 auto;
}
.seat .seat-pills {
  display: none;
}

/* Both horizontal and vertical seats use the same immersive overlay style:
   compact centered column with avatar, name, and minimal stats. */
.seat.seat-horizontal,
.seat.seat-vertical {
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 2px;
  padding: 0;
  width: auto;
  min-width: 0;
  background: none;
  border: none;
  box-shadow: none;
}
.seat.seat-horizontal .seat-main,
.seat.seat-vertical .seat-main {
  flex-direction: column;
  align-items: center;
  gap: 2px;
  max-width: none;
  flex: 0 0 auto;
}
.seat.seat-horizontal .seat-identity,
.seat.seat-vertical .seat-identity {
  align-items: center;
  text-align: center;
  flex: 0 0 auto;
}
.seat.seat-horizontal .seat-identity .name,
.seat.seat-vertical .seat-identity .name { white-space: nowrap; text-align: center; }
.seat.seat-horizontal .team-stats,
.seat.seat-vertical .team-stats {
  display: flex;
  background: none;
  border: none;
  padding: 0;
  margin: 2px 0 0;
  gap: 4px;
  flex-direction: row;
  align-items: center;
  justify-content: center;
}
.seat.seat-horizontal .team-stats .ts-chip,
.seat.seat-vertical .team-stats .ts-chip {
  background: rgba(0,0,0,0.55);
  border: 1px solid rgba(255,255,255,0.2);
  border-radius: var(--radius);
  padding: 2px 6px;
  font-size: 13px;
  font-weight: 700;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0,0,0,0.9);
  justify-content: center;
  display: inline-flex;
  align-items: center;
  gap: 3px;
}
.seat.seat-horizontal .team-stats .ts-chip .ts-label,
.seat.seat-vertical .team-stats .ts-chip .ts-label {
  font-size: 11px;
  font-weight: 600;
  opacity: 0.85;
  margin-right: 2px;
}
.seat.seat-horizontal .team-stats .ts-chip .ts-value,
.seat.seat-vertical .team-stats .ts-chip .ts-value {
  font-size: 14px;
  font-weight: 700;
  color: #fff;
}

/* ---- Per-seat bid chips, shown during the bidding phase so the whole auction
   reads at a glance (who bid what, the high bidder, and your own outbid bid). ---- */
.seat .ts-chip.ts-bid { font-size: 13px; padding: 2px 7px; white-space: nowrap; }
.seat .ts-chip.ts-bid-num {
  background: linear-gradient(180deg, #6b5a2e, #463b20);
  border-color: rgba(224, 173, 78, 0.55);
}
.seat .ts-chip.ts-bid-high {           /* current high bid — crowned */
  background: linear-gradient(180deg, #E0AD4E, #C8963E);
  border-color: #8B6B2A;
  color: #1a1408;
  text-shadow: none;
  box-shadow: 0 0 10px rgba(224, 173, 78, 0.7);
}
.seat .ts-chip.ts-bid-out {            /* your bid, now overtaken */
  background: linear-gradient(180deg, #7a2e2e, #561f1f);
  border-color: rgba(255, 120, 120, 0.55);
  color: #ffdada;
}
.seat .ts-chip.ts-bid-pass { background: rgba(0,0,0,0.45); color: rgba(255,255,255,0.62); }
.seat .ts-chip.ts-bid-wait { background: rgba(0,0,0,0.3);  color: rgba(255,255,255,0.4); letter-spacing: 1px; }
.seat.seat-horizontal .team-stats .ts-goal,
.seat.seat-vertical .team-stats .ts-goal { display: none; }
.seat.seat-horizontal .action-badge,
.seat.seat-vertical .action-badge { display: none; }
.seat.seat-horizontal .seat-action,
.seat.seat-vertical .seat-action { display: none; }

/* ts-wins chip — minimal trick count */
.ts-wins {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}
/* Stacked-cards glyph inside the tricks chip. Gold outline on the dark
   felt (matches the brand accent); stays crisp because it's vector. Sized
   to ride alongside the count without enlarging the chip footprint. */
.ts-trick-icon {
  width: 0.92em;
  height: 0.92em;
  flex: 0 0 auto;
  display: block;
}
.ts-trick-icon rect {
  fill: none;
  stroke: #E0AD4E;
  stroke-width: 1.4;
}
/* ts-tarneeb chip — trump icon + bid */
.ts-tarneeb {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}
/* Tarneeb chips wash out on the dark felt — give them a white background
   for consistent visibility. Suit colour stays authentic (red for hearts/
   diamonds, black for clubs/spades); label/value text is dark for contrast. */
.ts-chip.ts-tarneeb {
  background: #fff !important;
  border-color: rgba(0,0,0,0.15) !important;
  text-shadow: none !important;
}
.ts-chip.ts-tarneeb .ts-label,
.ts-chip.ts-tarneeb .ts-value { color: #111 !important; }
.ts-chip.ts-tarneeb.ts-suit-red { color: #E8192C !important; }
.ts-chip.ts-tarneeb.ts-suit-black { color: #111 !important; }
.seat.empty {
  opacity: 0.5;
}
.seat.you {
  /* Subtle glow — no background/border since overlay style. */
}
.seat.turn {
  /* Turn highlight expressed via avatar glow instead. */
}
/* Whose turn: "On the seat, never a banner. A = breathing gold ring."
   The handoff gives the ring its own tempo — "the ring itself breathes on a
   1.6s ease-in-out loop" — and a 140ms cross-fade when the turn moves between
   seats. A static ring reads as decoration; a breathing one reads as a clock. */
.seat.turn .avatar {
  box-shadow: 0 0 0 3px rgba(224, 173, 78, 0.85), 0 6px 18px rgba(224, 173, 78, 0.45) !important;
  transform: translateY(-2px);
  animation: turnRingBreathe 1600ms ease-in-out infinite;
}
.seat .avatar { transition: box-shadow 140ms ease, transform 140ms ease; }

@keyframes turnRingBreathe {
  0%, 100% { box-shadow: 0 0 0 2px rgba(224, 173, 78, 0.72), 0 0 12px rgba(224, 173, 78, 0.30) !important; }
  50%      { box-shadow: 0 0 0 3px rgba(224, 173, 78, 1.00), 0 0 22px rgba(224, 173, 78, 0.55) !important; }
}

@media (prefers-reduced-motion: reduce) {
  /* Keep the ring, drop the loop — it still says whose turn it is. */
  .seat.turn .avatar { animation: none; }
}
.seat.seat-disconnected {
  opacity: 0.4;
  filter: grayscale(0.5);
}
.seat .avatar {
  width: var(--seat-avatar-size); height: var(--seat-avatar-size); border-radius: 50%;
  background: linear-gradient(135deg, var(--sage), var(--sage-dark));
  color: #fff;
  /* Initials use the app's display face so the human's disc shares the
     bots' portrait language instead of reading as a flat UI placeholder. */
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: 24px;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid rgba(255,255,255,0.65);
  box-shadow: 0 3px 10px rgba(0,0,0,0.45);
  flex-shrink: 0;
}
/* Initial discs mirror the bots' frame: the same double ring (3px border +
   3px outer shadow ring) and a soft radial team tint lit from the top-left,
   so all four seats read as one matched set. */
.seat.team-0 .avatar {
  background: radial-gradient(circle at 32% 28%, #7BB4E8 0%, #4A90D9 45%, #2B6CB0 100%);
  border: 3px solid #5BA0E0;
  box-shadow: 0 0 0 3px #5BA0E0, var(--shadow-sm);
}
.seat.team-1 .avatar {
  background: radial-gradient(circle at 32% 28%, #F4D58A 0%, #E0AD4E 45%, #B07F2E 100%);
  color: #111114;
  text-shadow: 0 1px 1px rgba(255, 255, 255, 0.25);
  border: 3px solid #F0C76E;
  box-shadow: 0 0 0 3px #F0C76E, var(--shadow-sm);
}

/* Bot / user portrait image fills the circle cleanly. */
.seat .avatar.has-image {
  padding: 0;
  overflow: hidden;
  background: #fff;
}
.seat .avatar.has-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: 50%;
}
.seat.team-0 .avatar.has-image { box-shadow: 0 0 0 3px #5BA0E0, var(--shadow-sm); border: 3px solid #5BA0E0; }
.seat.team-1 .avatar.has-image { box-shadow: 0 0 0 3px #F0C76E, var(--shadow-sm); border: 3px solid #F0C76E; }

.seat .name {
  font-weight: 700;
  font-size: 15px;
  line-height: 1.15;
  color: #fff;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 120px;
  text-shadow: 0 1px 4px rgba(0,0,0,0.9), 0 0 8px rgba(0,0,0,0.5);
}
.seat .info {
  display: none;
  font-size: 15px;
  color: var(--ink-mute);
  text-transform: uppercase;
  letter-spacing: 0.9px;
}
.seat .bid {
  display: inline-block;
  padding: 5px 14px;
  font-size: 18px;
  background: var(--yellow-soft);
  border: 1px solid var(--yellow-dark);
  color: #1C1C22;
  border-radius: var(--radius);
  font-weight: 800;
  letter-spacing: 0.3px;
}
.seat .bid.bid-active {
  font-size: 20px;
  padding: 6px 16px;
  background: linear-gradient(180deg, #E0AD4E, #C8963E);
  border-color: #8B6B2A;
  color: #111114;
  box-shadow: 0 2px 8px rgba(224, 173, 78, 0.4);
}
.seat .bid.bid-pass {
  background: rgba(255,255,255,0.85);
  border-color: var(--border-soft);
  color: var(--ink-mute);
  font-size: 15px;
}
/* ---------- Per-seat thinking indicator + timer bar ---------- */
.seat .seat-thinking {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
  font-size: 13px;
  font-weight: 700;
  color: var(--sage-deep);
}
.seat .thinking-dots {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}
.seat .dots-anim {
  display: inline-block;
  width: 18px;
  overflow: hidden;
  animation: dotsAnim 1.4s steps(4, end) infinite;
}
@keyframes dotsAnim {
  0%   { width: 0; }
  25%  { width: 6px; }
  50%  { width: 12px; }
  75%  { width: 18px; }
  100% { width: 0; }
}
.seat .thinking-time {
  font-size: 15px;
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  color: var(--sage-deep);
  min-width: 28px;
  text-align: right;
}
.seat .thinking-time.thinking-danger {
  color: var(--red);
  animation: pulseRed 0.9s ease-in-out infinite;
}
.seat .seat-timer-bar {
  width: 100%;
  height: 4px;
  background: rgba(0,0,0,0.08);
  border-radius: var(--radius);
  overflow: hidden;
  margin-top: 3px;
}
.seat .seat-timer-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--yellow), var(--lime), var(--sage-deep));
  border-radius: var(--radius);
  transition: width 0.25s linear;
}
.seat .seat-timer-bar.timer-bar-danger .seat-timer-fill {
  background: linear-gradient(90deg, var(--red-soft), var(--red));
}
/* Highlight the seat whose turn it is to bid */
.seat.seat-bidding-turn {
  outline: 2px solid var(--sage-deep);
  outline-offset: -2px;
  box-shadow: 0 0 12px rgba(200,150,62,0.25);
}

/* ---------- Centre-table bid announcement ---------- */
.bid-announce {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.6);
  opacity: 0;
  z-index: 50;
  text-align: center;
  pointer-events: none;
  transition: opacity 0.25s ease-out, transform 0.25s ease-out;
}
.bid-announce.bid-announce-in {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}
.bid-announce.bid-announce-out {
  opacity: 0;
  transform: translate(-50%, -50%) scale(1.15);
  transition: opacity 0.35s ease-in, transform 0.35s ease-in;
}
.bid-announce-name {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: rgba(255,255,255,0.8);
  text-shadow: 0 1px 4px rgba(0,0,0,0.5);
  margin-bottom: 4px;
}
.bid-announce-value {
  font-size: 36px;
  font-weight: 900;
  letter-spacing: 1px;
  text-shadow: 0 2px 8px rgba(0,0,0,0.5);
  padding: 8px 28px;
  border-radius: var(--radius);
  white-space: nowrap;
}
.bid-announce-normal .bid-announce-value {
  color: #fff;
  background: rgba(200, 150, 62, 0.85);
  border: 2px solid rgba(255,255,255,0.3);
}
.bid-announce-high .bid-announce-value {
  color: #fff;
  background: rgba(220, 120, 40, 0.9);
  border: 2px solid rgba(255,255,255,0.3);
}
.bid-announce-pass .bid-announce-value {
  color: rgba(255,255,255,0.75);
  background: rgba(0,0,0,0.45);
  border: 2px solid rgba(255,255,255,0.15);
  font-size: 28px;
}
.bid-announce-spass .bid-announce-value {
  color: #F2E6D9;
  background: linear-gradient(180deg, rgba(200,150,62,0.9), rgba(139,94,60,0.9));
  border: 2px solid rgba(255,255,255,0.25);
  font-size: 28px;
}
/* Weak pass — same weight as the strong-pass flash so the declared suit reads
   at a glance, but drained of the gold so the two are never confused. */
.bid-announce-wpass .bid-announce-value {
  color: var(--ink-soft);
  background: linear-gradient(180deg, rgba(58,58,66,0.94), rgba(38,38,44,0.94));
  border: 2px solid rgba(255,255,255,0.16);
  font-size: 28px;
}

.seat .ready-pill {
  display: inline-block;
  padding: 3px 10px;
  font-size: 11px;
  background: var(--sage-soft);
  border: 1px solid var(--sage-dark);
  color: #1C1C22;
  border-radius: var(--radius);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.8px;
}

/* ---------- Per-seat team stats: HORIZONTAL strip beside the player ---------- */
.seat .team-stats {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  padding: 4px 6px;
  border-radius: var(--radius-sm);
  background: var(--cream-soft);
  border: 1px solid var(--border-soft);
}
.seat.team-0 .team-stats {
  background: linear-gradient(90deg, var(--lime-soft), var(--sage-soft));
  border-color: var(--sage-dark);
}
.seat.team-1 .team-stats {
  background: linear-gradient(90deg, var(--yellow-soft), #c4b5aa);
  border-color: var(--yellow-dark);
}
.seat .team-stats .ts-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 3px;
  padding: 1px 7px;
  border-radius: var(--radius);
  background: rgba(255,255,255,0.65);
  border: 1px solid rgba(0,0,0,0.06);
  white-space: nowrap;
}
.seat .team-stats .ts-label {
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-size: 10px;
  color: #5A5048;
  font-weight: 700;
}
.seat .team-stats .ts-value {
  font-weight: 800;
  color: #1C1C22;
  font-size: 14px;
}
.seat .team-stats .ts-goal {
  display: inline-block;
  padding: 3px 10px;
  border-radius: var(--radius);
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  background: var(--panel-2);
  color: var(--ink-soft);
  border: 1px solid var(--border-soft);
  white-space: nowrap;
}
.seat .team-stats .ts-goal.bidding  { background: var(--yellow-soft); color: #1C1C22; border-color: var(--yellow-dark); }
.seat .team-stats .ts-goal.done     { background: var(--sage-dark);   color: #fff;       border-color: var(--sage-deep); }
.seat .team-stats .ts-goal.warn     { background: var(--orange-soft); color: #1C1C22; border-color: var(--orange-dark); }
.seat .team-stats .ts-goal.fail     { background: var(--red);         color: #fff;       border-color: var(--red); }

.seat .team-stats .ts-tarneeb {
  background: linear-gradient(180deg, var(--yellow-soft), #c4b5aa);
  border-color: var(--yellow-dark);
  gap: 4px;
  padding: 2px 8px;
}
.seat .team-stats .ts-trump {
  font-size: 16px;
  font-weight: 700;
}
.seat .team-stats .ts-bid {
  font-weight: 800;
  color: var(--ink);
  font-size: 12px;
  background: rgba(0,0,0,0.08);
  padding: 0 4px;
  border-radius: var(--radius);
}

/* ---------- Felt ---------- */
.felt-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4px;
  width: 100%;
  height: 100%;
  min-height: 0;
}
.felt {
  position: relative;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  min-height: 380px;
  background: var(--felt-gradient);
  border: 1px solid var(--felt-line);
  border-radius: 0;
  box-shadow:
    inset 0 0 80px rgba(14, 34, 24, 0.5),
    inset 0 0 160px rgba(10, 25, 18, 0.3),
    0 0 0 1px rgba(200, 150, 62, 0.06),
    var(--shadow-md);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  /* Size container so the inner play-zone can derive a square that always fits
     inside the felt (cqi = felt width, cqb = felt height), regardless of whether
     the felt is portrait (phones) or landscape (desktop). This is what keeps the
     play-zone clear of the seats at every viewport. */
  container-type: size;
  container-name: felt;
}

/* Arabesque geometric overlay on felt */
.felt::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='60' height='60' viewBox='0 0 60 60'><path d='M30 0L60 30L30 60L0 30Z' fill='none' stroke='%23C8963E' stroke-width='0.5' opacity='0.08'/><circle cx='30' cy='30' r='8' fill='none' stroke='%23C8963E' stroke-width='0.3' opacity='0.06'/></svg>");
  background-size: 60px 60px;
  pointer-events: none;
  z-index: 0;
  opacity: 0.5;
  animation: borderGlow 6s ease-in-out infinite;
}

/* Subtle gold corner ornaments on felt */
.felt::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 0% 0%, rgba(200,150,62,0.08) 0%, transparent 20%),
    radial-gradient(circle at 100% 0%, rgba(200,150,62,0.08) 0%, transparent 20%),
    radial-gradient(circle at 0% 100%, rgba(200,150,62,0.08) 0%, transparent 20%),
    radial-gradient(circle at 100% 100%, rgba(200,150,62,0.08) 0%, transparent 20%),
    radial-gradient(circle at 50% 50%, rgba(200,150,62,0.03) 0%, transparent 40%);
  pointer-events: none;
  z-index: 0;
  animation: floatParticles 12s ease-in-out infinite;
}

.felt-logo {
  position: absolute;
  font-size: 64px;
  font-weight: 700;
  opacity: 0.08;
  letter-spacing: 18px;
  color: var(--sage-deep);
  user-select: none;
  pointer-events: none;
  z-index: 1;
  text-shadow: 0 0 40px rgba(200, 150, 62, 0.15);
}

.trick {
  position: relative;
  width: 100%;
  max-width: 420px;
  height: 340px;
  display: grid;
  grid-template-columns: 60px 1fr 60px;
  grid-template-rows: 80px 1fr 80px;
  grid-template-areas:
    ".     north .    "
    "west  .     east "
    ".     south .    ";
  place-items: center;
  gap: 6px;
  /* Jawaker-style bounded inner play-zone: a defined rounded rectangle inside
     the felt where ALL four played cards land. The subtle inset + gold hairline
     border visually separates the play-zone from the seats that sit around its
     OUTSIDE edges, keeping OUR dark-green + gold identity. Padding gives the
     directional cards a margin so they read as "inside" the zone. */
  box-sizing: border-box;
  padding: 10px;
  border-radius: var(--radius);
  background:
    radial-gradient(ellipse at center, rgba(8, 22, 15, 0.34) 0%, rgba(6, 18, 12, 0.16) 60%, rgba(6, 16, 11, 0) 100%);
  border: 1px solid rgba(200, 150, 62, 0.30);
  box-shadow:
    inset 0 0 0 1px rgba(0, 0, 0, 0.28),
    inset 0 0 26px rgba(0, 0, 0, 0.28),
    0 0 0 1px rgba(200, 150, 62, 0.08);
}
.trick .played-card { will-change: transform, opacity; }
.trick .played-card.north { grid-area: north; }
.trick .played-card.south { grid-area: south; }
.trick .played-card.east  { grid-area: east; }
.trick .played-card.west  { grid-area: west; }

/* Played cards on the felt — sized for the overlay layout. */
.trick .played-card {
  width: 90px;
  height: 130px;
  border-radius: var(--radius-card-lg);
}
.trick .played-card .corner.tl { top: 5px;    left: 7px; }
.trick .played-card .corner.br { bottom: 5px; right: 7px; }
.trick .played-card .corner .rank { font-size: 20px; letter-spacing: -0.5px; }
.trick .played-card .corner .suit { font-size: 16px; margin-top: 2px; }
.trick .played-card .pip { font-size: 52px; }

/* ============================================================================
   PLAY-ZONE GEOMETRY (Jawaker model) — single source of truth.
   High specificity (#view-room .ga-felt .trick …) so this one block overrides
   ALL the legacy per-breakpoint `.trick { width/height/grid }` rules (media
   queries add no specificity), giving one coherent, responsive layout instead
   of a dozen hand-tuned pixel grids.

   The zone is a CENTERED SQUARE sized from the felt's own box via container
   units: 64% of the felt's shorter axis width-wise, 62% height-wise, whichever
   is smaller. That guarantees the square always leaves a clear margin for the
   seat avatars that sit on the felt's OUTSIDE edges (W/E need horizontal room on
   narrow portrait felts; N/S need vertical room on short landscape felts).

   The four played cards are absolutely positioned INSIDE the zone, each pinned
   toward its player's edge (north→top, south→bottom, west→left, east→right) and
   nudged toward the centre so they overlap slightly but stay fully readable —
   so position alone tells you who played which card.
   ============================================================================ */
#view-room .ga-felt .trick {
  --zone: min(64cqi, 62cqb);
  /* Table card must stay SMALLER than the held card (Jawaker: table = 0.71x
     hand). The hand is 21.5vw on phones, so cap the played card at 15.3vw;
     the container-query term still governs on wide screens. */
  --pc-w: clamp(40px, 15.3vw, 92px);
  --pc-h: calc(var(--pc-w) * 1.4);
  /* inward nudge: how far each card slides from its edge toward the centre */
  --pc-nudge: calc(var(--pc-h) * 0.16);
  width: var(--zone);
  height: var(--zone);
  max-width: none;
  /* override grid placement — cards are absolutely positioned below */
  display: block;
  padding: 0;
  transform: none;
}
#view-room .ga-felt .trick .played-card {
  position: absolute;
  width: var(--pc-w);
  height: var(--pc-h);
  margin: 0;
  border-radius: var(--radius-card-lg);
}
/* Centring uses NEGATIVE MARGINS, not transform: the fly-in animation
   (animateCardFlyIn) drives `transform` and ends on translate(0,0), which
   would otherwise CLOBBER a translateX/Y(-50%) centring transform and shove
   every card off its anchor into an overlapping clump in the middle. Margins
   leave `transform` free for the animation and the winner-lift. */
/* North: top edge, centred horizontally, nudged DOWN toward middle. */
#view-room .ga-felt .trick .played-card.north {
  top: var(--pc-nudge); left: 50%;
  margin-left: calc(var(--pc-w) / -2);
}
/* South (you): bottom edge, centred, nudged UP toward middle. */
#view-room .ga-felt .trick .played-card.south {
  bottom: var(--pc-nudge); left: 50%;
  margin-left: calc(var(--pc-w) / -2);
}
/* West: left edge, centred vertically, nudged RIGHT toward middle. */
#view-room .ga-felt .trick .played-card.west {
  left: var(--pc-nudge); top: 50%;
  margin-top: calc(var(--pc-h) / -2);
}
/* East: right edge, centred vertically, nudged LEFT toward middle. */
#view-room .ga-felt .trick .played-card.east {
  right: var(--pc-nudge); top: 50%;
  margin-top: calc(var(--pc-h) / -2);
}
/* Card face type scales with the card so it stays readable at every size. */
#view-room .ga-felt .trick .played-card .pip { font-size: calc(var(--pc-w) * 0.62); }
#view-room .ga-felt .trick .played-card .corner .rank { font-size: calc(var(--pc-w) * 0.26); }
#view-room .ga-felt .trick .played-card .corner .suit { font-size: calc(var(--pc-w) * 0.21); }
#view-room .ga-felt .trick .played-card .corner.tl { top: 4px; left: 5px; }
#view-room .ga-felt .trick .played-card .corner.br { bottom: 4px; right: 5px; }

/* Sweep layer: body-level overlay where tricks-in-hold live while we
   animate them to the winner's seat and let them sit there for 4s. */
#sweep-layer { position: fixed; inset: 0; pointer-events: none; z-index: 900; }
#sweep-layer .played-card.sweeping {
  /* Held cards render with a subtle glow so they stand out over the seat. */
  box-shadow: 0 0 0 2px rgba(224, 173, 78, 0.9), 0 10px 24px rgba(0, 0, 0, 0.35);
}

/* ---------- Last-trick tray — neutral "Last trick" strip ----------
   Per ChatGPT UX review: anchoring the tray inside .ga-east semantically
   implies the east player owns the last trick. Move it to a neutral,
   centered location just above the bottom hand row, inside the capped
   game shell, so it reads as table-owned history. Horizontal strip:
   label + 4 mini cards in a single row, centered. Hidden on phones
   (<700px) — narrow viewports use the round-status text for the same
   information. Hidden when .empty (initial / between rounds). */
.last-trick-tray {
  display: none;  /* shown via media query at >=700px below */
  position: absolute;
  bottom: 6px;
  left: 50%;
  transform: translateX(-50%);
  padding: 4px 10px 5px;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  background: linear-gradient(180deg, rgba(20,20,28,0.86), rgba(12,12,16,0.92));
  box-shadow: 0 2px 10px rgba(0,0,0,0.5);
  flex-direction: row;
  align-items: center;
  gap: 8px;
  opacity: 0.96;
  pointer-events: none;
  z-index: 4;
  max-width: calc(100% - 32px);
  white-space: nowrap;
}
.last-trick-tray.empty { display: none !important; }
/* Single tray lives inside .felt (neutral, table-owned location).
   Shown at every viewport when populated. Phones place it ABOVE the
   south seat to avoid colliding with the avatar + AUTO chip; tablets
   and desktop pin it to the felt's bottom edge. */
.last-trick-tray:not(.empty) { display: flex; }
/* Phones (<700px): tiny strip above the south seat (~76px clearance).
   Mini cards shrunk to 13x18 so the whole strip is a barely-there
   pill (~85px wide, ~22px tall) that gives history at a glance
   without eating felt real estate. Label shortened to "LAST" only. */
@media (max-width: 699px) {
  .last-trick-tray {
    bottom: 76px;
    padding: 2px 5px 3px;
    gap: 4px;
    box-shadow: 0 1px 4px rgba(0,0,0,0.4);
    opacity: 0.92;
  }
  /* Hide the label on mobile — the gold-outlined winner card already
     tells the user what they're looking at, and the label was the
     widest element in the strip. */
  .last-trick-tray .ltt-label { display: none; }
  .last-trick-tray .ltt-cards { gap: 1px !important; min-height: 0 !important; }
  /* !important needed: a legacy block at line ~2241 sets width:46/height:66
     for the old in-felt tray and can win the cascade depending on parse
     order. Force the tiny-pill sizing on phones explicitly. */
  .last-trick-tray .ltt-cards .mini-card {
    width: 14px !important;
    height: 20px !important;
    border-radius: var(--radius-card-mini) !important;
    box-shadow: 0 1px 2px rgba(0,0,0,0.35) !important;
    min-height: 0 !important;
  }
  .last-trick-tray .ltt-cards .mini-card .rank {
    font-size: 7px !important;
    top: 0 !important;
    left: 1px !important;
    line-height: 1 !important;
  }
  .last-trick-tray .ltt-cards .mini-card .pip { font-size: 9px !important; }
  .last-trick-tray .ltt-cards .mini-card.winner {
    box-shadow: 0 0 0 1px var(--gold-accent, #E0AD4E), 0 1px 2px rgba(0,0,0,0.35) !important;
    transform: none !important;
  }
}
/* Strip-style mini cards: smaller still, packed tight in a row. */
.last-trick-tray .ltt-label {
  font-size: 9px;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--ink-mute);
  margin: 0;
}
.last-trick-tray .ltt-cards {
  display: flex;
  flex-direction: row;
  gap: 2px;
  min-height: 0;
  margin: 0;
}
.last-trick-tray .ltt-cards .mini-card {
  width: 22px;
  height: 30px;
  border-radius: var(--radius-card-mini);
  position: relative;
  background: #FDFAF6;
  color: #111;
  flex: 0 0 auto;
}
.last-trick-tray .ltt-cards .mini-card.red { color: #C02F2F; }
.last-trick-tray .ltt-cards .mini-card .rank {
  position: absolute;
  font-size: 9px;
  top: 1px;
  left: 2px;
  line-height: 1;
  font-weight: 700;
}
.last-trick-tray .ltt-cards .mini-card .pip {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  line-height: 1;
}
.last-trick-tray .ltt-cards .mini-card .who { display: none; }
.last-trick-tray .ltt-cards .mini-card.winner {
  box-shadow: 0 0 0 1.5px var(--gold-accent, #E0AD4E);
}
.last-trick-tray .ltt-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1.2px;
  color: var(--ink-mute);
  font-weight: 700;
  text-align: center;
}
.last-trick-tray .ltt-cards {
  display: flex;
  justify-content: center;
  align-items: flex-end;
  gap: 4px;
  flex: 1;
  min-height: 80px;
}
.last-trick-tray .ltt-cards .mini-card {
  width: 46px;
  height: 66px;
  border-radius: var(--radius-card-mini);
  background: #ffffff;
  color: var(--ink);
  position: relative;
  display: block;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
  font-weight: 800;
  overflow: hidden;
}
.last-trick-tray .ltt-cards .mini-card.red { color: #D42030; }
.last-trick-tray .ltt-cards .mini-card:not(.red) { color: #1A1A24; }
.last-trick-tray .ltt-cards .mini-card.winner {
  box-shadow: 0 0 0 2px var(--yellow), 0 4px 12px rgba(0, 0, 0, 0.4);
  transform: translateY(-4px);
}
.last-trick-tray .ltt-cards .mini-card .rank {
  position: absolute;
  top: 3px;
  left: 4px;
  font-size: 14px;
  line-height: 1;
}
.last-trick-tray .ltt-cards .mini-card .pip {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 26px;
  line-height: 1;
}
.last-trick-tray .ltt-cards .mini-card .who {
  position: absolute;
  bottom: 2px;
  left: 0; right: 0;
  text-align: center;
  font-size: 8px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--ink-mute);
}
.last-trick-tray.empty .ltt-cards::before {
  content: "—";
  color: var(--ink-mute);
  font-size: 18px;
  align-self: center;
  width: 100%;
  text-align: center;
}

/* ---------- Dark theme overrides ---------- */
body.theme-dark {
  background:
    radial-gradient(1200px 800px at 50% -10%, rgba(224,173,78,0.04) 0%, transparent 55%),
    radial-gradient(900px 600px at 90% 100%, rgba(200,150,62,0.03) 0%, transparent 55%),
    linear-gradient(180deg, #08080A 0%, #0C0C0F 55%, #141418 100%);
  color: #F0E8DC;
}
body.theme-dark {
  --cream:       #141418;
  --cream-soft:  #1A1A1E;
  --cream-dark:  #0C0C0F;
  --bg-grad-1:   #08080A;
  --bg-grad-2:   #0C0C0F;
  --bg-grad-3:   #141418;
  --panel:       #16161C;
  --panel-2:     #0E0E12;
  --panel-3:     #08080A;
  --border:      rgba(200, 150, 62, 0.15);
  --border-soft: rgba(200, 150, 62, 0.08);
  --border-lit:  rgba(224, 173, 78, 0.30);
  --ink:         #F0E8DC;
  --ink-soft:    #C8BCB0;
  /* theme-dark is applied on load, so this is the muted colour actually in
     use. Raised from #7A7268 to meet WCAG AA on the dark surfaces. */
  --ink-mute:    #968C82;
  --shadow-xl: 0 24px 60px rgba(0, 0, 0, 0.65);
  --shadow-lg: 0 14px 34px rgba(0, 0, 0, 0.55);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, 0.50);
  --shadow-sm: 0 2px 8px  rgba(0, 0, 0, 0.45);
}
body.theme-dark .seat .name { color: #fff; }
body.theme-dark .seat .info { color: var(--ink-mute); }
body.theme-dark .top-bar { background: linear-gradient(180deg, #141418, #0C0C0F); border-color: rgba(224,173,78,0.15); }
body.theme-dark .brand { color: var(--ink); }
body.theme-dark .card {
  background: #FDFAF6;
  color: #1C1C22;
}
body.theme-dark .card.red { color: #E05555; }
body.theme-dark .last-trick-tray .ltt-cards .mini-card { background: #FDFAF6; color: #1C1C22; }
body.theme-dark .last-trick-tray .ltt-cards .mini-card.red { color: #E05555; }
body.theme-dark .hand-row { background: linear-gradient(180deg, #0E0E12, #08080A); border-color: var(--border); }
body.theme-dark input, body.theme-dark select, body.theme-dark textarea {
  background: rgba(255,255,255,0.08);
  color: var(--ink);
  border-color: var(--border-lit);
}
body.theme-dark .btn, body.theme-dark button {
  color: var(--ink);
}

/* ---------- Cards (Lucky Draw–inspired: borderless minimal faces) ---------- */

/* Maze-like watermark used on card faces and the card back. Rendered as an
   SVG data URI so it scales crisply at any size. Uses currentColor for stroke
   so each card's color (red/ink) tints the pattern without extra CSS. */
:root {
  --maze-face: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23C8963E' stroke-width='1.2' stroke-linecap='square'><path d='M0 6h12M6 0v6M12 6v12M6 12h12M0 12v12M6 18h6M18 12v12M18 18h6'/></svg>");
  --maze-back: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 28 28' fill='none' stroke='%23E0AD4E' stroke-width='1.8' stroke-linecap='square'><path d='M0 7h14M7 0v7M14 7v14M7 14h14M0 14v14M7 21h7M21 14v14M21 21h7M14 0v3M24 3h4'/></svg>");
}

.card {
  width: 108px;
  height: 151px;
  border-radius: var(--radius-card);
  background: #ffffff;
  color: var(--ink);
  display: block; /* allow absolutely-positioned corners + centered pip */
  font-weight: 800;
  cursor: pointer;
  user-select: none;
  border: 0;
  box-shadow:
    0 1px 0 rgba(0,0,0,0.04),
    0 6px 14px rgba(0, 0, 0, 0.25);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

/* Single large centered suit glyph (default for table/played cards). */
.card .pip {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 96px;
  line-height: 1;
  color: currentColor;
  font-weight: 400;
  pointer-events: none;
  z-index: 1;
}

/* Corner indices: large bold rank stacked over suit glyph */
.card .corner {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1;
  font-weight: 800;
  z-index: 2;
  color: currentColor;
}
.card .corner.tl { top: 6px;    left: 8px;  }
.card .corner.br { bottom: 6px; right: 8px; transform: rotate(180deg); }
.card .corner .rank { font-size: 36px; letter-spacing: -0.5px; }
.card .corner .suit { font-size: 34px; margin-top: 1px; }

/* Force flat, monochrome suit glyphs everywhere — never colour emoji — so the
   card colour comes from CSS (red/black) and cards render pixel-identically on
   desktop and Android WebView. Pairs with the U+FE0E text selector in suitGlyph(). */
.card .pip, .card .corner .suit, .brand-mark .suit, .ts-trump, .trump-glyph,
.game-hero-ghost, .sp-felt-logo, .last-trick-tray .mini-card .pip, .trump-panel .trump-glyph {
  font-variant-emoji: text;
}

.card.red { color: #E8192C; }
.card:not(.back)::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--maze-face);
  background-size: 24px 24px;
  opacity: 0.025;
  pointer-events: none;
  z-index: 0;
}
.card:not(.back):not(.red) { color: #000000; }
.card:not(.back) { background: #ffffff; }

/* Card back — navy field with a subtle pattern and a centered Tarneeb
   monogram disc. */
.card.back {
  background:
    var(--maze-back),
    linear-gradient(180deg, var(--navy), var(--navy-deep));
  background-size: 28px 28px, auto;
  border: 0;
  box-shadow: 0 6px 14px rgba(0, 0, 0, 0.6);
}
.card.back::before { display: none; }
.card.back::after {
  content: "T";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 60px;
  height: 60px;
  margin: -30px 0 0 -30px;
  border-radius: 50%;
  background: var(--yellow);
  color: #1C1C22;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 900;
  font-size: 34px;
  border: 2px solid #1C1C22;
  box-shadow: 0 0 0 3px rgba(224, 173, 78, 0.35);
}

/* Disabled / illegal (must-follow-suit) cards stay fully opaque. We only
   desaturate slightly and nudge them down so the legal cards pop, instead
   of reducing opacity (which made them look transparent on the dark felt). */
.card.disabled { cursor: default; }
.card.preselected {
  transform: translateY(-14px);
  box-shadow: 0 10px 24px rgba(200, 150, 62, 0.4), 0 0 0 2px rgba(224, 173, 78, 0.6);
}
/* Illegal card on your turn (can't follow the led suit with it): dim + desaturate
   at rest so the LEGAL cards visibly stand out. Only applied on your turn — when
   it's not your turn cards keep full colour so you can plan ahead. */
/* Illegal on your turn. NOT dimmed — the handoff is explicit that dimming was
   the wrong answer ("Illegal cards are unreachable, not just dim"). Three things
   happen instead, and the first is the one that matters:
     1. renderHand's layoutHand() gives this card a layout weight of 1 against
        2.6 for a legal one, collapsing it toward an 11px index strip;
     2. it drops by 5% of card height so the legal row reads as the front rank;
     3. it goes GREY rather than transparent — full opacity, no colour.
   Pointer events are off because selection is done by nearestLegal() on the
   band, which never considers an illegal card at all. */
.card.invalid-play {
  cursor: default;
  opacity: 1;
  filter: grayscale(1) brightness(0.58);
  transform: translateY(5%);
  pointer-events: none;
  transition: filter 0.18s ease, transform 0.18s ease, margin-inline-start 0.18s cubic-bezier(.22,1,.36,1);
}
:root[data-theme="light"] .card.invalid-play { filter: grayscale(1) brightness(0.80); }
.card.invalid-play:hover { transform: translateY(5%); }

/* The spread itself is animated on the settle curve, so cards glide into their
   new share of the band when the led suit changes rather than jumping. */
.hand .card { transition: margin-inline-start 0.18s cubic-bezier(.22,1,.36,1); }

/* Press feedback, to the handoff's numbers: 90ms on cubic-bezier(.22,1,.36,1),
   lift 20% of card height, scale 1.06, gold rim. Local only — it fires on
   pointerdown and never waits on the server. */
/* Selector deliberately over-specified. `.hand .card.playable.red` also sets
   box-shadow with !important and is MORE specific than `.hand .card.previewing`,
   so the red suits kept their own red inset rim and the gold one never showed —
   on exactly the card you are about to play, which is the one moment the rim
   has a job. Matching .playable and both colour classes puts this above it. */
.hand .card.previewing,
.hand .card.playable.previewing,
.hand .card.playable.red.previewing,
.hand .card.previewing.red {
  transform: translateY(-20%) scale(1.06);
  z-index: 60;
  box-shadow: 0 0 0 2px var(--gold-hi, #E3C06A), 0 10px 24px rgba(0, 0, 0, 0.6) !important;
  transition: transform 90ms cubic-bezier(.22, 1, .36, 1),
              box-shadow 90ms cubic-bezier(.22, 1, .36, 1);
}
.hand .card.previewing:hover,
.hand .card.playable.red.previewing:hover { transform: translateY(-20%) scale(1.06); }

@media (prefers-reduced-motion: reduce) {
  /* The token table sets lift to 0 under reduced motion. Keep the rim — it is
     the thing telling you which card you are about to play — and drop the move. */
  .hand .card.previewing { transform: none; transition: box-shadow 90ms linear; }
  .hand .card.previewing:hover { transform: none; }
  .hand .card { transition: none; }
}

/* Legal playable card on your turn — subtle lift + glow on hover. (The clarity
   signal comes from dimming the ILLEGAL cards above, so legal cards just stay at
   full strength rather than adding noise when every card is legal on a lead.) */
.card.playable {
  cursor: pointer;
  transition: transform 0.14s ease, box-shadow 0.14s ease;
}

/* Keyboard focus on a hand card: gold ring + the same lift the mouse hover
   gives, so tabbing through the hand reads exactly like pointing at it. */
.hand .card:focus-visible {
  outline: 3px solid var(--yellow);
  outline-offset: 2px;
  transform: translateY(-10px);
  box-shadow: 0 10px 22px rgba(200, 150, 62, 0.35);
  z-index: 5;
}
.card.playable:hover {
  transform: translateY(-10px);
  box-shadow: 0 10px 22px rgba(200, 150, 62, 0.35);
  /* No z-index — card rises in place without overlaying neighbors */
}

/* Hand hint nudge when user clicks an illegal card. */
.hand-hint.nudge {
  color: #b63a2a;
  font-weight: 700;
  animation: handNudge 0.9s ease-out;
}
@keyframes handNudge {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-4px); }
  40% { transform: translateX(4px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(3px); }
}

/* Tarneeb selection helper header above the trump buttons. */
.tarneeb-hdr {
  flex: 1 0 100%;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-soft);
  margin-bottom: 6px;
  padding: 6px 10px;
  background: rgba(224, 173, 78, 0.28);
  border: 1px solid rgba(224, 173, 78, 0.06);
  border-radius: var(--radius);
  text-align: center;
}
.tarneeb-hdr strong { color: #1C1C22; }

/* Small chip showing how many of this suit you hold inside the Trump button. */
button.gold .suit-count {
  display: inline-block;
  min-width: 18px;
  margin-left: 6px;
  padding: 1px 6px;
  font-size: 11px;
  font-weight: 700;
  color: #1C1C22;
  background: rgba(255, 255, 255, 0.85);
  border-radius: var(--radius);
  vertical-align: middle;
}
button.gold.blocked {
  opacity: 0.5;
  filter: grayscale(0.7);
  cursor: not-allowed;
  position: relative;
}
button.gold.blocked::after {
  content: "";
  position: absolute;
  left: 10%;
  right: 10%;
  top: 50%;
  height: 2px;
  background: #b63a2a;
  transform: rotate(-10deg);
}
button.gold.blocked:hover { transform: none; }

/* ---------- Action deadline countdown ---------- */
.action-deadline {
  display: flex;
  justify-content: center;
}
.action-deadline.hidden { display: none; }
.deadline-pill {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 6px 14px;
  background: transparent;
  border: 0;
  border-radius: var(--radius);
  font-weight: 600;
  color: var(--ink);
  min-width: 260px;
  transition: background 0.2s, border-color 0.2s, color 0.2s;
}
.deadline-pill.me {
  color: var(--sage-deep);
}
.deadline-pill.danger {
  color: var(--red);
  animation: pulseRed 0.9s ease-in-out infinite;
}
.deadline-label {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  opacity: 0.85;
}
.deadline-time {
  font-size: 16px;
  font-weight: 800;
  min-width: 42px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.deadline-bar {
  flex: 1;
  height: 6px;
  background: rgba(0, 0, 0, 0.08);
  border-radius: var(--radius);
  overflow: hidden;
  min-width: 80px;
}
.deadline-bar-fill {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--yellow), var(--lime), var(--sage-deep));
  transition: width 0.25s linear;
}
.deadline-pill.danger .deadline-bar-fill {
  background: linear-gradient(90deg, var(--red-soft), var(--red));
}

/* ---------- Actions panel (slim horizontal strip) ----------
   The modal handles all interactive actions now, so this panel mostly just
   shows turn hints and the sync button. Keep it minimal so it doesn't steal
   vertical space from the table. */
.actions-panel {
  display: flex;
  flex-direction: row;
  gap: 8px;
  align-items: center;
  justify-content: center;
  padding: 3px 10px;
  background: transparent;
  border: 0;
  min-height: 0;
}
.actions-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
}
.actions-row:empty { display: none; }
.actions-panel em {
  color: var(--ink-soft);
  font-style: normal;
  font-size: 12px;
}
.actions-panel .turn-msg {
  color: var(--orange-dark);
  font-weight: 700;
  font-style: normal;
  animation: glow 2s ease-in-out infinite;
}
.actions-panel button {
  padding: 4px 10px;
  font-size: 11px;
}

/* ---------- Player hand ---------- */
.hand-wrap {
  padding: 10px 16px 14px;
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.hand-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 4px;
}
.hand-head h3 { margin: 0; font-size: 15px; }
.hand-hint { font-size: 13px; font-weight: 600; color: var(--ink-soft); }
.hand {
  display: flex;
  flex-wrap: nowrap;
  justify-content: center;
  align-items: flex-end;
  gap: 0;
  min-height: 180px;
  padding: 10px 16px 0;
}
.hand {
  --card-w: clamp(90px, calc((100vw - 40px) / 6), 140px);
  --card-count: 13;
  --card-overlap: calc(var(--card-w) * -0.45);
  --card-natural-gap: calc((100% - var(--card-count) * var(--card-w)) / (var(--card-count) - 1));
  --card-gap: clamp(var(--card-overlap), var(--card-natural-gap), 6px);
  min-height: calc(var(--card-w) * 1.4 + 18px);
  padding: 16px 4px 4px;
  overflow: visible;
  width: auto;
  max-width: 100%;
  justify-content: center;
}
.hand .card {
  width: var(--card-w);
  height: calc(var(--card-w) * 1.4);
  margin-left: var(--card-gap);
  border-radius: var(--radius-card);
  flex: 0 0 auto;
  /* THE authoritative transition for a hand card. There were two `.hand .card`
     rules declaring transitions at equal specificity: one above at ~3192 for
     `margin-inline-start 0.18s cubic-bezier(.22,1,.36,1)` — the fan re-layout —
     and this one. This is the later of the two, and a transition SHORTHAND
     resets every longhand it does not mention, so the re-layout curve was
     being discarded and the fan re-flowed on `margin 0.15s` with no easing of
     its own, inheriting whatever timing function won the cascade. That is the
     property layoutHand() actually animates, so the one motion the eye tracks
     most was the one running on nobody's curve.

     Named explicitly here, all in one place, on one curve. */
  transition:
    margin-inline-start 0.18s cubic-bezier(.22, 1, .36, 1),
    margin-left 0.18s cubic-bezier(.22, 1, .36, 1),
    transform 0.15s cubic-bezier(.22, 1, .36, 1),
    box-shadow 0.15s ease;
}
.hand .card:first-child { margin-left: 0; }
.hand .card .corner .rank { font-size: calc(var(--card-w) * 0.30); }
.hand .card .corner .suit { font-size: calc(var(--card-w) * 0.24); }
.hand .card .corner.tl { top: 4px; left: 6px; }
.hand .card .corner.br { display: none; }
.hand .card .pip {
  font-size: calc(var(--card-w) * 0.58);
  inset: auto; bottom: 4px; right: 6px;
  align-items: flex-end; justify-content: flex-end;
}
/* One hover lift, not three. This was -12px while `.card.playable:hover` and
   `.hand .card:focus-visible` both used -10px, so the same card rose by a
   different amount depending on which rule happened to win — pointer hover and
   keyboard focus disagreed on the same element. -10px everywhere. */
.hand .card:hover:not(.disabled) {
  transform: translateY(-10px);
  box-shadow: 0 16px 30px rgba(0, 0, 0, 0.28);
}

/* ---------- Event log ---------- */
/* Event log is hidden from the UI. Logs continue to flow to the browser
   console via log() in app.js. To re-enable the on-screen panel for
   debugging, toggle display: block here or remove this rule. */
.log-wrap { display: none !important; }
.log-wrap-legacy { margin-top: 2px; position: relative; }

/* Small top-of-screen banner for auto-reconnect status */
.connection-status {
  position: fixed;
  top: 8px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 500;
  padding: 6px 14px;
  border-radius: var(--radius);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.3px;
  border: 1px solid var(--border-soft);
  backdrop-filter: blur(6px);
  box-shadow: 0 4px 14px rgba(0,0,0,0.25);
  transition: opacity 0.2s ease;
}
.connection-status.hidden { display: none; }
.connection-status.cs-info { background: rgba(30,41,59,0.9); color: #e5e7eb; }
.connection-status.cs-warn { background: rgba(234,179,8,0.15); color: #fde68a; border-color: rgba(234,179,8,0.4); }
.connection-status.cs-ok   { background: rgba(34,197,94,0.15); color: #bbf7d0; border-color: rgba(34,197,94,0.4); }
.connection-status.cs-err  { background: rgba(239,68,68,0.18); color: #fecaca; border-color: rgba(239,68,68,0.5); }
.log-wrap summary {
  cursor: pointer;
  color: var(--ink-mute);
  font-size: 12px;
  font-weight: 700;
  padding: 4px 10px;
  text-transform: uppercase;
  letter-spacing: 1.5px;
}
.log-wrap[open] summary { margin-bottom: 0; }
.log-close-btn {
  position: absolute;
  top: 0;
  right: 6px;
  background: none;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  color: var(--ink-mute);
  font-size: 16px;
  line-height: 1;
  padding: 2px 8px;
  cursor: pointer;
  z-index: 2;
  transition: color 0.15s, border-color 0.15s;
  display: none;
}
.log-wrap[open] .log-close-btn { display: block; }
.log-close-btn:hover { color: var(--ink); border-color: var(--ink-soft); }
#log {
  max-height: 180px;
  overflow-y: auto;
  background: rgba(12, 12, 15, 0.85);
  color: var(--ink-soft);
  font-family: "SF Mono", "Consolas", monospace;
  font-size: 11px;
  line-height: 1.5;
  padding: 14px;
  border-radius: var(--radius);
  border: 1px solid var(--border-soft);
  margin-top: 6px;
  white-space: pre-wrap;
  word-break: break-word;
  backdrop-filter: blur(8px);
}

/* ---------- Action modal ---------- */
.action-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  animation: modalFadeIn 0.18s ease-out;
}
.action-modal.hidden { display: none; }
.action-modal-backdrop {
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at center, rgba(20, 20, 26, 0.6), rgba(8, 8, 10, 0.85));
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}
.action-modal-card {
  position: relative;
  min-width: 320px;
  max-width: 1100px;
  width: 100%;
  background: linear-gradient(180deg, var(--panel), var(--panel-2));
  border: 1px solid rgba(224, 173, 78, 0.08);
  border-radius: var(--radius-lg);
  box-shadow:
    0 24px 60px rgba(0, 0, 0, 0.5),
    0 0 0 1px rgba(224, 173, 78, 0.15);
  padding: 22px 26px 24px;
  animation: modalPop 0.28s cubic-bezier(.2, .9, .3, 1.25);
  overflow: hidden;
}
/* Subtle arabesque watermark in modal corner */
.action-modal-card::before {
  content: "";
  position: absolute;
  top: -20px;
  right: -20px;
  width: 120px;
  height: 120px;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><circle cx='50' cy='50' r='45' fill='none' stroke='%23C8963E' stroke-width='0.6' opacity='0.1'/><circle cx='50' cy='50' r='30' fill='none' stroke='%23C8963E' stroke-width='0.4' opacity='0.08'/><circle cx='50' cy='50' r='15' fill='none' stroke='%23C8963E' stroke-width='0.3' opacity='0.06'/><path d='M50 5L50 95M5 50L95 50M18 18L82 82M82 18L18 82' stroke='%23C8963E' stroke-width='0.3' opacity='0.05'/></svg>") no-repeat center / contain;
  pointer-events: none;
  z-index: 0;
  opacity: 0.8;
}
.action-modal-header {
  text-align: center;
  margin-bottom: 16px;
}
.action-modal-kicker {
  display: inline-block;
  padding: 4px 12px;
  margin-bottom: 8px;
  background: var(--yellow-soft);
  color: #1C1C22;
  border: 1px solid var(--yellow-dark);
  border-radius: var(--radius);
  font-size: 12px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1.6px;
}
.action-modal-header h3 {
  margin: 0;
  font-size: 24px;
  font-weight: 800;
  color: var(--ink);
  letter-spacing: -0.2px;
}
.action-modal-sub {
  margin: 6px 0 0;
  font-size: 15px;
  font-weight: 500;
  color: var(--ink-soft);
  line-height: 1.45;
}
.action-modal-sub strong { color: var(--sage-deep); }
.action-modal-sub.hidden { display: none; }

.action-modal-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: stretch;
}
.action-modal-body > button { width: 100%; }

/* Big chunky buttons for modal actions */
button.big {
  padding: 12px 18px;
  font-size: 16px;
  font-weight: 700;
  border-radius: var(--radius);
}
button.big.wide { width: 100%; }

/* Bid grid: 7 numeric buttons in a 4-column grid */
.bid-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}
.bid-grid button {
  padding: 14px 0;
  font-size: 18px;
  font-weight: 800;
}

/* ---------------------------------------------------------------
   .table-panel — shared inline popup that lives inside .felt
   (position: relative). Overlays the table surface without a
   backdrop scrim: seats, hand row, and scoreboard stay readable
   and (unless .table-panel--blocking is applied) interactive.

   Width is derived from the side-avatar size so the panel's
   horizontal extent always leaves clearance from the W/E avatars
   at every breakpoint, capped by a mobile/desktop ceiling.

   Variants add their own content-specific layout (e.g.
   .table-panel--bid keeps the tiered bid grid from the old
   .bid-panel). The base rules here never change between variants.
   --------------------------------------------------------------- */
.table-panel {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  /* Derive width from the side-avatar size so the panel keeps ≥16px
     clearance from the W/E avatar edges on every screen. The calc()
     leaves two avatar widths plus ~64px of gutter; the outer min()
     is the mobile cap so short viewports don't grow the panel. */
  width: min(calc(100vw - (var(--seat-avatar-size) * 2) - 64px), 340px);
  max-width: 340px;
  /* Keep the panel inside the felt — when a variant has a lot of
     content (e.g. the 6-bot list), the inner scroller kicks in and
     the header/actions stay pinned. The 24px floor leaves room for
     the N/S avatar chrome that sits at the felt edges. */
  max-height: calc(100% - 24px);
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: rgba(12, 12, 15, 0.92);
  border: 1px solid rgba(224, 173, 78, 0.35);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
  z-index: 60;
  overflow: hidden;
  animation: tablePanelFade 0.18s ease-out;
}
.table-panel.hidden { display: none; }
.table-panel button {
  min-height: 44px;
  touch-action: manipulation;
}
.table-panel button:focus-visible {
  outline: 2px solid rgba(224, 173, 78, 0.8);
  outline-offset: 2px;
}

/* Blocking modifier: the player must interact with this panel before
   they can touch anything else. Applied when a variant (e.g. game-over,
   waiting-for-players, trump-pick) genuinely gates progress. We do NOT
   re-introduce a full-screen scrim — the felt/seats/hand stay visually
   present, they just stop responding to input while the panel is open.
   The panel itself keeps its own pointer-events so its buttons work.

   Wiring: JS toggles a `table-panel-blocking` class on <body> while any
   blocking .table-panel is open; that class disables pointer-events on
   siblings outside the panel (seats live in sibling grid cells, not in
   .felt, so a pure-CSS selector from inside the felt can't reach them).
   Commit 2 will populate and activate this path as popups migrate off
   the legacy .action-modal — the bid variant here is non-blocking and
   the class ships empty for now. */
body.table-panel-blocking .ga-north .seat,
body.table-panel-blocking .ga-south .seat,
body.table-panel-blocking .ga-east  .seat,
body.table-panel-blocking .ga-west  .seat,
body.table-panel-blocking .hand,
body.table-panel-blocking .hand .card {
  pointer-events: none;
}
/* The panel's own buttons must still respond even when blocking is on. */
body.table-panel-blocking .table-panel,
body.table-panel-blocking .table-panel * { pointer-events: auto; }

/* While a centered modal panel is open, hide the south "you" seat overlay.
   It lives in the felt grid area with its own stacking context (z-index:2)
   that paints above the panel, so its chip would otherwise cover the
   panel's bottom controls (e.g. the cut-ceremony PASS button). */
body.tp-modal-open .ga-south { display: none; }

/* ------- Shared slots (used by non-bid variants) ------- */

.table-panel__header {
  text-align: center;
  padding: 2px 0 6px;
}
.table-panel__kicker {
  display: inline-block;
  padding: 2px 10px;
  margin-bottom: 6px;
  background: var(--yellow-soft);
  color: #1C1C22;
  border-radius: var(--radius);
  font-size: 10px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1.2px;
}
.table-panel__title {
  margin: 0;
  font-size: 18px;
  font-weight: 800;
  color: var(--ink);
  line-height: 1.2;
}
.table-panel__sub {
  margin: 4px 0 0;
  font-size: 12px;
  color: var(--ink-soft);
  line-height: 1.35;
}
.table-panel__actions {
  display: flex;
  gap: 8px;
  justify-content: stretch;
  flex-wrap: wrap;
}
.table-panel__actions > button { flex: 1 1 auto; min-width: 0; }

/* ------- Variants ------- */

/* .table-panel--bid — the tiered bid grid (13 / 10-11-12 / 7-8-9) plus
   the Strong Pass / Pass row. The seat chips remain the primary tell for
   what partner bid; the inline .bid-panel-status line just mirrors that
   so a bidder doesn't have to break eye contact with the panel. */
.table-panel--bid {
  /* Belt-and-suspenders cap — the panel is already absolutely positioned
     inside .felt so it can't push past the hand row, but on very short
     viewports an explicit vh cap protects against panel growth if the
     felt itself grows unexpectedly. */
  max-height: min(60vh, calc(100% - 24px));
}
.table-panel--bid .bid-grid-tiered { gap: 8px; margin: 0; }
.bid-panel-status {
  text-align: center;
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-soft);
  letter-spacing: 0.2px;
  padding: 2px 4px 4px;
}
.bid-panel-status strong {
  color: var(--yellow);
  font-weight: 800;
}
/* Second line under the high-bid status: spells out why the low bid
   buttons are locked ("must beat {leader}'s {bid}"). */
.bid-panel-status .bid-locked-note {
  display: block;
  margin-top: 1px;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--ink-mute);
  letter-spacing: 0.1px;
}

/* .table-panel--waiting — pre-game seat picker. The seating interaction
   happens on the REAL table seats (see renderSeats() empty-state), not
   inside this panel. The panel itself only hosts the title, the invite
   code strip, and the Start Game / Leave action row. */
.table-panel--waiting .table-panel__invite {
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: center;
  padding: 6px 10px;
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid rgba(224, 173, 78, 0.2);
  border-radius: var(--radius);
  flex-wrap: wrap;
}
.table-panel--waiting .tp-invite-label {
  color: var(--ink-soft);
  font-weight: 800;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  font-size: 10px;
}
.table-panel--waiting .tp-invite-code {
  color: var(--yellow);
  font-weight: 800;
  letter-spacing: 1.2px;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 13px;
  user-select: all;
}
.table-panel--waiting .tp-invite-code.copied::after {
  content: " ✓";
  color: var(--green);
}
.table-panel--waiting #tp-copy-link {
  padding: 4px 8px;
  font-size: 11px;
  min-height: 32px;
}

/* .table-panel--bot-setup — 6 bot personalities stacked, plus a
   Start Game primary button. Reuses the existing
   .bot-personality-accordion / .bot-card-accordion styling but
   shrinks the cards so the full list fits inside the panel, and
   lets the accordion be the scrolling region while the header and
   Start button stay pinned. */
.table-panel--bot-setup .bot-personality-accordion {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  margin: 0;
  padding: 2px;
  -webkit-overflow-scrolling: touch;
}
.table-panel--bot-setup .bot-card-accordion {
  border-radius: var(--radius);
  flex: 0 0 auto;          /* don't let the flex parent shrink the card */
}
.table-panel--bot-setup .bot-card-header {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  padding: 8px 12px;       /* override the base 12px 14px — tighter inside the panel */
  min-height: 40px;
}
.table-panel--bot-setup .bot-icon { font-size: 18px; }
.table-panel--bot-setup .bot-name { font-weight: 700; }

/* Bot names must stay readable. At 344px the rows rendered "THE S…",
   "THE C…", "TH…" — four partners you cannot tell apart, which defeats the
   whole point of a picker. Nothing overflowed the viewport, so the automated
   layout check passed; only the screenshot showed it.

   Measured in the browser rather than guessed: the header row is just 174px
   wide, split icon 30 / name 58 / meter 46 / gap, while "The Shield" needs 81.
   Two fixes, both required — the name has to be allowed to wrap AND has to be
   given the width back.

   The #bot-panel id is deliberate: .bot-card-header .bot-name later in this
   file sets nowrap + ellipsis at equal specificity and was winning. */
#bot-panel .bot-card-header .bot-name {
  flex: 1 1 auto;
  min-width: 0;
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  line-height: 1.2;
}
@media (max-width: 430px) {
  /* The aggression bar is the least load-bearing thing in the row — the
     selected card already spells the style out in words ("Balanced — smart and
     dependable"). Dropping it returns 46px, which is the difference between a
     readable name and an ellipsis. */
  #bot-panel .bot-aggr-mini { display: none; }
}
.table-panel--bot-setup .bot-checkmark {
  margin-left: auto;
  color: var(--yellow);
  font-weight: 800;
}
.table-panel--bot-setup .bot-card-body {
  font-size: 11px;
  color: var(--ink-soft);
  padding-top: 2px;
  line-height: 1.3;
}
/* Pin the header and actions so the scroller doesn't swallow them. */
.table-panel--bot-setup .table-panel__header,
.table-panel--bot-setup .table-panel__actions {
  flex: 0 0 auto;
}

/* .table-panel--shuffle — 4 shuffle levels in a 2x2 grid (mobile) or
   single row (desktop). The deck-stack visual is a cosmetic header
   and collapses to a small badge on short viewports. */
.table-panel--shuffle .ceremony-shuffle-wrap {
  margin: 0 0 4px;
  padding: 0;
}
.table-panel--shuffle .ceremony-deck-visual {
  padding: 6px 0 2px;
}
.table-panel--shuffle .deck-stack {
  width: 44px;
  height: 62px;
}
.table-panel--shuffle .ceremony-btn-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin: 0;
}
.table-panel--shuffle .ceremony-shuffle-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 6px;
  min-height: 56px;
}
.table-panel--shuffle .shuffle-level-label {
  font-weight: 800;
  font-size: 13px;
  line-height: 1.15;
}
.table-panel--shuffle .shuffle-level-sub {
  font-size: 10px;
  font-weight: 500;
  opacity: 0.85;
  line-height: 1.2;
}

/* .table-panel--cut — 3 cut positions in a single column, Pass below. */
.table-panel--cut .ceremony-btn-row.ceremony-cut-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
  margin: 0;
}
.table-panel--cut .ceremony-cut-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 8px 12px;
  font-weight: 800;
}
.table-panel--cut .ceremony-cut-btn .cut-icon { font-size: 16px; }
.table-panel--cut .ceremony-pass-btn {
  margin-top: 2px;
  min-height: 40px;
}

/* .table-panel--autoplay — six bot style buttons in a 2x3 grid
   (or 1x6 column on narrow screens). Pinned Cancel button stays
   at the bottom of the panel regardless of list height. */
.table-panel--autoplay .ap-picker-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px;
  margin: 0;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.table-panel--autoplay .ap-picker-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 2px;
  padding: 8px 6px;
  min-height: 68px;
  min-width: 0;
}
.table-panel--autoplay .ap-picker-icon {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #F5F1E8;
  border-bottom: 4px solid #888;   /* overridden inline per personality */
  font-size: 22px;
  margin-bottom: 3px;
}
.table-panel--autoplay .ap-picker-name {
  font-size: 12px;
  font-weight: 700;
  line-height: 1.15;
}
.table-panel--autoplay .ap-picker-desc {
  font-size: 10px;
  font-weight: 500;
  opacity: 0.85;
  line-height: 1.2;
}
.table-panel--autoplay .table-panel__header,
.table-panel--autoplay .table-panel__actions {
  flex: 0 0 auto;
}

/* .table-panel--round — round-end summary. Reuses .round-summary /
   .rs-* chrome but drops the outer padding/margins that the old
   full-screen modal used. */
.table-panel--round .round-summary {
  padding: 0;
  margin: 0;
  gap: 8px;
}
.table-panel--round .rs-verdict {
  font-size: 18px;
  padding: 6px 12px;
}
.table-panel--round .rs-line {
  font-size: 13px;
  line-height: 1.35;
}
.table-panel--round .rs-scores { gap: 10px; }
.table-panel--round .rs-team { padding: 8px 10px; }
.table-panel--round .rs-change { font-size: 22px; }
.table-panel--round .rs-total { font-size: 12px; }
.table-panel--round .rs-next {
  font-size: 11px;
  opacity: 0.8;
  text-align: center;
}

/* The handoff's dismissal affordance: "auto-dismisses in 1800ms with a
   draining 2px underline showing the remaining time". The underline IS the
   countdown — it drains left-to-right over exactly the summary's lifetime, so
   nobody is surprised when the panel leaves. scaleX rather than width so it
   animates on the compositor, and transform-origin left so it reads as time
   running out rather than the bar shrinking from both ends.
   Deliberately NOT gated behind prefers-reduced-motion: this is information
   (remaining time), not decoration, and the audit's rule is that reduced
   motion shortens the decorative, it does not delete the functional. */
.table-panel--round .rs-underline {
  height: 2px;
  margin-top: 8px;
  background: var(--gold, #c9a227);
  transform: scaleX(1);
  transform-origin: left center;
}
.table-panel--round .rs-underline.draining {
  transform: scaleX(0);
  transition: transform 1800ms linear;
}
[dir="rtl"] .table-panel--round .rs-underline {
  transform-origin: right center;
}

/* .table-panel--wait — two states driven by the caller:
   (1) options:  a 3-button column "Wait 1/3/5 min".
   (2) running:  a big mm:ss countdown + progress bar + Skip button. */
.table-panel--wait .wait-options {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.table-panel--wait .wait-timer-display {
  text-align: center;
  padding: 4px 0 8px;
}
.table-panel--wait .wtd-countdown {
  font-size: 38px;
  font-weight: 800;
  color: var(--yellow);
  letter-spacing: 1px;
  font-variant-numeric: tabular-nums;
}
.table-panel--wait .wtd-bar {
  height: 6px;
  background: rgba(0, 0, 0, 0.45);
  border-radius: var(--radius);
  overflow: hidden;
  margin: 10px 0 6px;
}
.table-panel--wait .wtd-fill {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--yellow), var(--orange));
  transition: width 200ms linear;
}
.table-panel--wait .wtd-hint {
  font-size: 11px;
  color: var(--ink-soft);
  line-height: 1.3;
}

/* .table-panel--trump — trump / Tarneeb picker. Four suit rows,
   sorted strongest → weakest. The existing .trump-grid-vertical
   layout carries the button internals (glyph, name, rank, count,
   upgrade note). The grid scrolls internally so the header +
   subtitle stay pinned; no in-panel hand preview (the hand row
   at the bottom of the screen is the source of truth). */
.table-panel--trump {
  /* Trump pick has fewer controls than the bid panel; a tighter vh cap
     keeps the hand row visible even on very short viewports. */
  max-height: min(50vh, calc(100% - 24px));
}
.table-panel--trump .trump-grid-vertical {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  gap: 8px;
  margin: 0;
}
.table-panel--trump .trump-grid-vertical button {
  min-height: 56px;
  padding: 6px 10px;
}
.table-panel--trump .trump-grid-vertical .trump-glyph {
  font-size: 26px;
}
.table-panel--trump .trump-grid-vertical .trump-name {
  font-size: 13px;
}
.table-panel--trump .trump-grid-vertical .trump-strength {
  font-size: 10px;
}
.table-panel--trump .trump-grid-vertical .trump-rank {
  font-size: 10px;
}
.table-panel--trump .trump-grid-vertical .suit-count {
  font-size: 18px;
}
.table-panel--trump .trump-grid-vertical .trump-upgrade {
  font-size: 10px;
}
.table-panel--trump .table-panel__header { flex: 0 0 auto; }

@media (min-width: 768px) {
  .table-panel {
    /* On wider screens let the panel grow a little, still capped so it
       doesn't stretch into the side-seat area of the felt. */
    width: min(calc(100vw - (var(--seat-avatar-size) * 2) - 64px), 400px);
    max-width: 400px;
    padding: 14px 16px;
  }
  .table-panel--bid .bid-btn,
  .table-panel--bid .pass-btn { padding: 14px 12px; }
}

@keyframes tablePanelFade {
  from { opacity: 0; transform: translate(-50%, -46%); }
  to   { opacity: 1; transform: translate(-50%, -50%); }
}
@media (prefers-reduced-motion: reduce) {
  .table-panel { animation: none; }
}

/* Dual-pass row: regular white Pass and Strong Pass with 💪 tell.
   Sits directly under the tiered bid grid. */
.pass-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 6px;
}
/* Weak pass sits on its own row under the two pass buttons. The four suits are
   the buttons — a third control in .pass-row would be ~110px wide at 344px,
   where "Strong Pass" already wraps to two lines. */
.weak-pass-row { margin-top: 8px; }
.weak-pass-label {
  font: 700 10px/1.3 var(--font-ui);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-mute);
  text-align: center;
  margin-bottom: 6px;
}
.weak-pass-suits {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
}
.weak-pass-suits .weak-suit-btn {
  min-width: 0;
  min-height: var(--tap);
  padding: 8px 0;
  font-size: 20px;
  line-height: 1;
  background: linear-gradient(180deg, #2A2A32, #1C1C22);
  border: 1px solid var(--border);
  box-shadow: 2px 2px 0 #0A0A0A;
}
.weak-pass-suits .weak-suit-btn:active { transform: translate(1px, 1px); box-shadow: 1px 1px 0 #0A0A0A; }
.weak-pass-suits .suit-hearts, .weak-pass-suits .suit-diamonds { color: var(--red, #d94141); }
.weak-pass-suits .suit-spades, .weak-pass-suits .suit-clubs { color: var(--ink); }

.pass-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-weight: 800;
  padding: 14px 0;
}
.pass-btn .pass-icon { font-size: 18px; line-height: 1; }
button.btn.pass-btn-regular,
.pass-row button.pass-btn-regular {
  background: #ffffff !important;
  color: #1C1C22 !important;
  border: 1px solid #8B5E3C !important;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
button.btn.pass-btn-regular:hover,
.pass-row button.pass-btn-regular:hover {
  background: #1e4260 !important;
  border-color: #8B5E3C !important;
}
.pass-btn-strong {
  background: linear-gradient(180deg, #2A2A32, #1C1C22);
  color: #F0E8DC;
  border: 1px solid rgba(224,173,78,0.3);
  box-shadow: 0 0 0 1px rgba(200, 150, 62, 0.45), 0 4px 14px rgba(0, 0, 0, 0.35);
}
.pass-btn-strong:hover {
  filter: brightness(1.12);
  box-shadow: 0 0 0 1px rgba(200, 150, 62, 0.65), 0 6px 18px rgba(0, 0, 0, 0.45);
}

/* Strong-pass "tell" badge shown on every seat that declared Strong Pass
   until bidding ends. Stays visible on mobile even when .seat-pills is
   hidden. */
.seat-strong-pass {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 4px;
  padding: 3px 8px;
  border-radius: var(--radius);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.3px;
  background: linear-gradient(180deg, #8B7355, #6B5840);
  color: #F0E8DC;
  border: 1px solid #1C1C22;
  box-shadow: 0 0 0 1px rgba(200, 150, 62, 0.35);
  white-space: nowrap;
}
.seat.has-strong-pass { outline: 2px solid rgba(200, 150, 62, 0.45); outline-offset: -2px; }

.seat .bid.bid-strong-pass {
  background: linear-gradient(180deg, #8B7355, #6B5840);
  color: #F0E8DC;
  border: 1px solid #1C1C22;
}
/* Weak pass reads as the muted counterpart of the strong-pass pill — same
   shape, drained of the warm tone, so a glance around the table tells you who
   declared strength and who declared nothing. */
.seat .bid.bid-weak-pass {
  background: linear-gradient(180deg, #3A3A42, #26262C);
  color: var(--ink-soft);
  border: 1px solid #1C1C22;
  letter-spacing: 0.5px;
}

/* Tiered bid grid: three risk rows (safe / fire / danger).
   Overrides the flat 4-col grid when .bid-grid-tiered is present. */
.bid-grid-tiered {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.bid-grid-tiered .bid-row {
  display: grid;
  gap: 8px;
}
.bid-grid-tiered .bid-row-safe,
.bid-grid-tiered .bid-row-fire   { grid-template-columns: repeat(3, 1fr); }
.bid-grid-tiered .bid-row-danger { grid-template-columns: 1fr; }

.bid-grid-tiered .bid-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.bid-grid-tiered .bid-btn .bid-amt  { font-size: 20px; font-weight: 800; }
.bid-grid-tiered .bid-btn .bid-icon { font-size: 18px; line-height: 1; }

/* Fire tier (10/11/12) — warm orange glow to signal aggression. */
.bid-grid-tiered .bid-btn-fire:not(.blocked) {
  background: linear-gradient(180deg, #f39a3b, #d7741a);
  border-color: #b85d10;
  box-shadow: 0 0 0 1px rgba(220, 120, 40, 0.35), 0 4px 14px rgba(220, 120, 40, 0.25);
}
.bid-grid-tiered .bid-btn-fire:not(.blocked):hover {
  filter: brightness(1.08);
  box-shadow: 0 0 0 1px rgba(220, 120, 40, 0.55), 0 6px 18px rgba(220, 120, 40, 0.35);
}

/* Danger tier (13) — red to signal all-or-nothing risk. White text here:
   the inherited dark ink only hits ~2.9:1 against the gradient's lower
   stop (#b22a22); white clears 4:1+ across the whole gradient (WCAG AA
   for this 20px-bold label). */
.bid-grid-tiered .bid-btn-danger:not(.blocked) {
  background: linear-gradient(180deg, #e1463d, #b22a22);
  border-color: #8c1a14;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  box-shadow: 0 0 0 1px rgba(200, 40, 30, 0.4), 0 4px 14px rgba(200, 40, 30, 0.28);
}
.bid-grid-tiered .bid-btn-danger:not(.blocked):hover {
  filter: brightness(1.08);
  box-shadow: 0 0 0 1px rgba(200, 40, 30, 0.6), 0 6px 18px rgba(200, 40, 30, 0.38);
}
.bid-grid-tiered .bid-btn-danger { padding: 14px 12px; }

/* Blocked bid buttons — visually greyed out and unclickable */
.bid-grid-tiered .bid-btn.blocked,
.bid-grid-tiered .bid-btn:disabled {
  opacity: 0.35;
  filter: grayscale(0.8);
  cursor: not-allowed;
  transform: none !important;
  box-shadow: none !important;
}
.bid-grid-tiered .bid-btn.blocked:hover,
.bid-grid-tiered .bid-btn:disabled:hover {
  filter: grayscale(0.8);
  transform: none;
}
/* Shake animation for clicking a blocked button */
@keyframes bidShake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-6px); }
  40% { transform: translateX(6px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(4px); }
}
.bid-btn.shake {
  animation: bidShake 0.35s ease-in-out;
  opacity: 0.55 !important;
}

/* Trump grid: 4 suit tiles in a 2×2 grid */
.trump-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.trump-grid button {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 16px 12px;
  min-height: 96px;
  position: relative;
}

/* Vertical variant used after winning a bid — suits are sorted strongest
   → weakest so the player can pick from top to bottom. Each row is a
   horizontal strip: rank badge · glyph · name · strength · big count. */
.trump-grid.trump-grid-vertical {
  grid-template-columns: 1fr;
  gap: 8px;
}
.trump-grid.trump-grid-vertical button {
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: 12px;
  padding: 12px 16px;
  min-height: 64px;
  text-align: left;
}
.trump-grid.trump-grid-vertical .trump-rank {
  flex: 0 0 auto;
  min-width: 44px;
  padding: 3px 8px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--ink-mute, #7a8e9e);
  background: rgba(0,0,0,0.06);
  border-radius: var(--radius);
  text-align: center;
}
.trump-grid.trump-grid-vertical .trump-rank.best {
  color: #fff;
  background: var(--sage-dark, #234C6A);
  box-shadow: 0 1px 3px rgba(0,0,0,0.25);
}
.trump-grid.trump-grid-vertical .trump-glyph {
  font-size: 32px;
  line-height: 1;
  flex: 0 0 auto;
}
.trump-grid.trump-grid-vertical .trump-name {
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 0.3px;
  flex: 0 0 auto;
}
.trump-grid.trump-grid-vertical .trump-strength {
  flex: 1 1 auto;
  font-size: 11px;
  font-weight: 600;
  color: var(--ink-mute, #7a8e9e);
  text-align: right;
  white-space: nowrap;
}
.trump-grid.trump-grid-vertical .suit-count {
  position: static;
  flex: 0 0 auto;
  min-width: 28px;
  padding: 3px 9px;
  font-size: 13px;
  font-weight: 900;
}
.trump-grid.trump-grid-vertical .trump-upgrade {
  position: static;
  flex: 0 0 auto;
  font-size: 9px;
  padding: 2px 6px;
}
.trump-grid.trump-grid-vertical button.blocked {
  opacity: 0.45;
  order: 1; /* additional safety net — CSS order puts blocked buttons last */
}
.trump-grid .trump-glyph { font-size: 36px; line-height: 1; }
.trump-grid .trump-name  { font-size: 13px; font-weight: 800; letter-spacing: 0.5px; }
.trump-grid .suit-count  {
  position: absolute;
  top: 6px;
  right: 8px;
  margin: 0;
  min-width: 22px;
  padding: 2px 7px;
  font-size: 11px;
  font-weight: 800;
  color: #1C1C22;
  background: rgba(255, 255, 255, 0.85);
  border-radius: var(--radius);
}
.trump-grid .trump-upgrade {
  position: absolute;
  bottom: 6px;
  right: 6px;
  padding: 2px 8px;
  font-size: 10px;
  font-weight: 800;
  color: #fff;
  background: var(--orange-dark, #1C1C22);
  border-radius: var(--radius);
  text-transform: uppercase;
  letter-spacing: 0.6px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
button.gold.upgrade {
  border-color: var(--orange-dark, #1C1C22);
  box-shadow: 0 0 0 2px rgba(214, 90, 44, 0.25);
}

/* ---------- Round summary modal (shown after each 13-trick round) ---------- */
.round-summary {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 4px 0 2px;
}
.round-summary .rs-verdict {
  display: inline-block;
  padding: 6px 18px;
  border-radius: var(--radius);
  font-size: 14px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: #fff;
}
.round-summary .rs-verdict.won  { background: var(--sage-dark, #234C6A); }
.round-summary .rs-verdict.lost { background: var(--red, #b33a3a); }
.round-summary .rs-line {
  font-size: 15px;
  line-height: 1.45;
  text-align: center;
  color: var(--ink, #1C1C22);
  max-width: 520px;
}
.round-summary .rs-scores {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  width: 100%;
  max-width: 480px;
  margin-top: 4px;
}
.round-summary .rs-team {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 14px 10px 12px;
  background: rgba(200, 150, 62, 0.07);
  border: 1px solid var(--border-soft, rgba(224,173,78,0.1));
  border-radius: var(--radius, 10px);
}
.round-summary .rs-team.plus  { border-color: rgba(200, 150, 62, 0.45); background: rgba(224, 173, 78, 0.18); }
.round-summary .rs-team.minus { border-color: rgba(179, 58, 58, 0.45); background: rgba(214, 120, 120, 0.15); }
.round-summary .rs-label {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--muted, #7a8e9e);
}
.round-summary .rs-change {
  font-size: 26px;
  font-weight: 800;
  line-height: 1;
}
.round-summary .rs-team.plus  .rs-change { color: var(--sage-deep, #456882); }
.round-summary .rs-team.minus .rs-change { color: var(--red, #b33a3a); }
.round-summary .rs-total {
  font-size: 13px;
  font-weight: 600;
  color: var(--muted, #7a8e9e);
}
.round-summary .rs-total::before { content: "Total "; opacity: 0.7; }
.round-summary .rs-next {
  font-size: 12px;
  font-style: italic;
  color: var(--muted, #7a8e9e);
  margin-top: 2px;
}

/* ---------- Game Over screen ---------- */
.game-over-screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding: 8px 0 4px;
}

/* ── End of match, the designer's composition (prototype 460-468) ──────────
   The old screen centred everything: a 40px VICTORY block, "TEAM BLUE WINS",
   and a three-column scoreboard — the same fact stated three times. The design
   is left-aligned and says it once. Values below are the prototype's own. */
.kn-go { align-items: stretch; text-align: start; gap: 0; }
.kn-go-kicker {
  font: 600 10px/1 var(--font-ui, inherit);
  letter-spacing: .18em;
  color: var(--gold-dim);
}
.kn-go-line {
  font: 700 40px/1.05 var(--font-ui, inherit);
  color: var(--gold-hi);
  margin: 14px 0 10px;
  text-wrap: pretty;
}
.kn-go-sub { font: 400 14px/1.5 var(--font-ui, inherit); color: var(--ink2); }
.kn-go-rule { height: 1px; background: var(--line2); margin: 22px 0; }

/* PLAY AGAIN — a gold block sized to its own text, sitting at the start edge,
   not a full-width button (prototype 467). */
.kn-go-again {
  height: 52px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 0 18px;
  border: 0;
  border-radius: 0;
  background: var(--gold);
  color: var(--on-gold);
  font: 700 13px/1 var(--font-ui, inherit);
  letter-spacing: .06em;
  cursor: pointer;
  align-self: flex-start;
}
/* Ready and waiting: the block stops being an action, so it stops looking like
   one, but it keeps its size so the layout does not jump on tap. */
.kn-go-again--waiting {
  background: transparent;
  border: 1px solid var(--line3);
  color: var(--ink3);
  cursor: default;
}
.kn-go .go-rematch-count { margin-top: 10px; font: 400 12px/1.5 var(--font-ui, inherit); color: var(--ink3); }
.kn-go .go-actions { align-items: flex-start; }
/* ── Kufi Noir victory / defeat ──────────────────────────────── */
.go-result {
  position: relative;
  text-align: center;
  padding: 16px 44px;
  border-radius: 0;
  font-family: var(--font-ui);
  font-weight: 800;
  font-size: 40px;
  line-height: 1;
  letter-spacing: 6px;
  text-indent: 6px; /* balance the trailing letter-space */
}
.go-result .go-result-word { display: inline-block; }
/* WIN — solid saffron block on black with an offset hard shadow */
.go-result.go-win {
  background: var(--accent);
  color: #111;
  box-shadow: 7px 7px 0 #000, 0 0 0 1px var(--accent);
  animation: goWinPop 0.5s cubic-bezier(.2,.9,.3,1.3) both;
}
/* LOSS — hollow, muted; no accent (accent is reserved for winning) */
.go-result.go-loss {
  background: transparent;
  color: var(--ink-mute);
  border: 2px solid #333;
  box-shadow: 5px 5px 0 #000;
}
@keyframes goWinPop {
  0%   { transform: scale(.6) rotate(-3deg); opacity: 0; box-shadow: 0 0 0 #000; }
  60%  { transform: scale(1.06) rotate(0deg); opacity: 1; }
  100% { transform: scale(1); box-shadow: 7px 7px 0 #000, 0 0 0 1px var(--accent); }
}
.go-winner {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--ink-mute);
  text-align: center;
}
.go-winner span {
  color: var(--ink);
  padding-bottom: 2px;
  border-bottom: 3px solid var(--ink-mute);
}
.go-winner span.team-blue  { color: var(--team-blue);  border-bottom-color: var(--team-blue); }
.go-winner span.team-green { color: var(--team-green); border-bottom-color: var(--team-green); }
.go-final-scores {
  display: flex;
  align-items: stretch;
  gap: 14px;
  justify-content: center;
}
.go-team {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 14px 26px;
  border-radius: 0;
  background: #1D1D1D;
  border: 2px solid #2A2A2A;
  min-width: 104px;
}
/* Champion tile carries the single saffron accent + block shadow */
.go-team.go-champ {
  border-color: var(--accent);
  background: #1D1D1D;
  box-shadow: 5px 5px 0 rgba(245, 176, 39, 0.28);
}
.go-team-label {
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  padding-bottom: 3px;
  border-bottom: 2px solid transparent;
  color: var(--ink-mute);
}
.go-team-label.team-blue  { color: var(--team-blue);  border-bottom-color: var(--team-blue); }
.go-team-label.team-green { color: var(--team-green); border-bottom-color: var(--team-green); }
.go-team-score {
  font-family: var(--font-ui);
  font-size: 40px;
  font-weight: 800;
  line-height: 1;
  color: var(--ink-mute); /* losing team — muted */
}
.go-team.go-champ .go-team-score {
  color: var(--accent); /* winning team — saffron */
}
.go-vs {
  align-self: center;
  font-size: 16px;
  color: var(--ink-mute);
  font-weight: 700;
}
@media (prefers-reduced-motion: reduce) {
  .go-result.go-win { animation: none; }
}
.go-history {
  width: 100%;
  max-width: 400px;
}
.go-history-title {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--muted, #7a8e9e);
  margin-bottom: 6px;
  text-align: center;
}
.go-history-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  color: var(--cream, #D2C1B6);
}
.go-history-table thead th {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--ink-soft, #C8BCB0);
  padding: 4px 6px;
  border-bottom: 1px solid rgba(255,255,255,0.15);
  text-align: center;
  background: rgba(0,0,0,0.2);
  position: sticky;
  top: 0;
  z-index: 1;
}
.go-history-table td {
  padding: 5px 6px;
  text-align: center;
  border-bottom: 1px solid rgba(255,255,255,0.05);
  color: var(--ink, #F0E8DC);
}
.go-history-table tbody tr:nth-child(odd) td {
  background: rgba(255,255,255,0.02);
}
/* Collapsible wrapper — keeps the action button as the priority and
   tucks the long round-by-round table behind a toggle. */
.go-history-details {
  width: 100%;
  max-width: 520px;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: var(--radius);
  overflow: hidden;
}
.go-history-summary {
  list-style: none;
  cursor: pointer;
  padding: 10px 14px;
  display: flex;
  align-items: center;
  gap: 10px;
  user-select: none;
  color: var(--ink, #F0E8DC);
  font-weight: 600;
  font-size: 14px;
  transition: background 0.15s ease;
}
.go-history-summary::-webkit-details-marker { display: none; }
.go-history-summary:hover { background: rgba(255,255,255,0.04); }
.go-history-summary-label { flex: 1; }
.go-history-summary-count {
  font-size: 12px;
  color: var(--ink-mute, #8A8078);
  font-weight: 500;
}
.go-history-summary-chev {
  font-size: 12px;
  color: var(--ink-mute, #8A8078);
  transition: transform 0.2s ease;
}
.go-history-details[open] .go-history-summary-chev { transform: rotate(180deg); }
.go-history-details[open] .go-history-summary {
  border-bottom: 1px solid rgba(255,255,255,0.08);
}
.go-history-scroll {
  max-height: 260px;
  overflow-y: auto;
  padding: 8px 10px;
}
.go-history-scroll::-webkit-scrollbar { width: 6px; }
.go-history-scroll::-webkit-scrollbar-track { background: transparent; }
.go-history-scroll::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,0.15);
  border-radius: var(--radius);
}
.go-history-details .go-history { max-width: 100%; }
.go-plus { color: var(--green); font-weight: 700; }
.go-minus { color: var(--red); font-weight: 700; }
.go-actions {
  width: 100%;
  margin-top: 8px;
}
.go-home-btn {
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 1px;
  text-transform: uppercase;
  padding: 15px 24px;
}

/* ── Rematch block (phase 8b) ───────────────────────────────────
   Sits above Back to Home. Rematch is the primary action here — the match
   just ended and the likeliest thing the table wants is another one — so
   Home was demoted to .secondary when this landed. */
/* D2's best-round + rating line — the only progression feedback at the end of
   a match, and it was absent entirely. */
.go-best {
  width: 100%;
  border: 1px solid var(--line2, #2a2a30);
  background: var(--surf2, #26262c);
  margin: 12px 0 6px;
}
.go-best-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 9px 12px;
}
.go-best-row + .go-best-row { border-top: 1px solid var(--line, #1f1f24); }
.go-best-label {
  font: 700 9px/1 var(--font-ui);
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink-mute); flex: none;
}
.go-best-val {
  font: 400 12px/1.35 var(--font-ui);
  color: var(--ink-soft); text-align: end; overflow-wrap: anywhere;
}
.go-best-val strong { color: var(--gold-hi, var(--orange)); font-weight: 900; }
.go-best-val.go-plus  { color: #4ac07a; font-weight: 800; }
.go-best-val.go-minus { color: #d9534f; font-weight: 800; }

.go-rematch { width: 100%; margin-bottom: 10px; }
.go-rematch-btn {
  width: 100%;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: 1px;
  text-transform: uppercase;
  padding: 15px 24px;
}
.go-rematch-secs {
  font-variant-numeric: tabular-nums;  /* the countdown must not jitter */
  opacity: 0.75;
  margin-inline-start: 6px;
}
.go-rematch-count {
  font: 700 12px/1.3 var(--font-ui);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  text-align: center;
  margin-bottom: 8px;
}
.go-rematch-count strong { color: var(--gold-hi, var(--orange)); font-weight: 900; }
.go-rematch-note {
  font: 400 13px/1.4 var(--font-ui);
  color: var(--ink-mute);
  text-align: center;
  padding: 12px 0;
}

/* ── Kufi Noir modal shell for the victory screen only ──────────
   Reskins the shared .action-modal-card without touching other modals. */
.action-modal-card.go-modal {
  background: #111;
  border: 1px solid var(--accent);
  border-radius: 0;
  box-shadow: 10px 10px 0 #000, 0 0 0 1px #2A2A2A;
}
.action-modal-card.go-modal::before { display: none; } /* drop the arabesque watermark */
.action-modal-card.go-modal .action-modal-header { display: none; } /* the VICTORY banner is the heading */
.action-modal-card.go-modal .game-over-screen { padding-top: 4px; }
.action-modal-card.go-modal .go-history-details {
  background: #1D1D1D;
  border: 1px solid #2A2A2A;
  border-radius: 0;
}
.action-modal-card.go-modal .go-history-table thead th {
  color: var(--accent);
  background: #111;
  border-bottom: 1px solid #2A2A2A;
}
.action-modal-card.go-modal .go-history-table td {
  border-bottom: 1px solid rgba(255,255,255,0.05);
  color: var(--ink);
}

/* Compact hand preview shown inside the bid / trump modal so the player
   can see their cards while deciding. Grouped by suit as text chips so it
   stays readable even on small screens. */
/* Bidding / trump modal hand preview — reuses the real .card rendering
   so the deciding view matches the playing view exactly (same sort, same
   face, same sizing). */
.modal-hand-preview {
  margin-top: 8px;
  padding: 10px 12px;
  background: rgba(200, 150, 62, 0.05);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.modal-hand-preview .hp-title {
  font-size: 10px;
  color: var(--ink-mute);
  text-transform: uppercase;
  letter-spacing: 1.2px;
  font-weight: 700;
  text-align: left;
}
.modal-hand-preview .hand.hand-preview {
  min-height: 0;
  padding: 4px 0 0;
  justify-content: center;
  flex-wrap: nowrap;
  overflow-x: auto;
}
.modal-hand-preview .hand.hand-preview .card {
  cursor: default;
  pointer-events: none;
  opacity: 1;
  filter: none;
  /* Extra-large preview so the player can clearly read their hand while bidding */
  width: 96px;
  height: 150px;
  margin-left: -22px;
  border-radius: var(--radius-card);
}
.modal-hand-preview .hand.hand-preview .card:first-child { margin-left: 0; }
.modal-hand-preview .hand.hand-preview .card .corner .rank { font-size: 34px; }
.modal-hand-preview .hand.hand-preview .card .corner .suit { font-size: 32px; }
.modal-hand-preview .hand.hand-preview .card .corner.tl { top: 5px; left: 7px; }
.modal-hand-preview .hand.hand-preview .card .corner.br { display: none; }
.modal-hand-preview .hand.hand-preview .card .pip {
  font-size: 76px; inset: auto; bottom: 4px; right: 6px;
  align-items: flex-end; justify-content: flex-end;
}
.modal-hand-preview .hand.hand-preview .card:hover { transform: none; }

@keyframes modalFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes modalPop {
  from { transform: translateY(8px) scale(0.94); opacity: 0; }
  to   { transform: translateY(0)   scale(1);    opacity: 1; }
}

@media (max-width: 560px) {
  .action-modal-card { min-width: 0; padding: 16px 10px 18px; }
  .bid-grid:not(.bid-grid-tiered) { grid-template-columns: repeat(4, 1fr); }
}

/* ---------- Animations ---------- */
@keyframes glow {
  0%, 100% { text-shadow: 0 0 6px rgba(200,150,62,0.35); }
  50%      { text-shadow: 0 0 14px rgba(200,150,62,0.85); }
}

/* Card fly-in & sweep animations are driven inline by app.js — see
   animateCardFlyIn() and animateTrickSweep() in app.js. */

/* ---------- Responsive ---------- */
/* Desktop (≥1024px) — avatars + names, and a COMPACT, LIFTED trick so the
   north/south played cards never cover the N/S seat avatars.

   The desktop felt is short (~385px tall on a 1366×768 laptop, ~420px on a
   1520×800 desktop) and viewport-constrained — we can't make it taller. The
   base trick (420×340, 90×130 cards) fills almost the whole felt, so its N/S
   cards land directly on the N avatar (pinned to the felt's top edge) and the
   S avatar (pinned to the bottom edge) — the laptop-1366 overlap bug.

   Fix: shrink the desktop avatars + played cards, and render the four cards as
   a SMALL pile lifted slightly above the felt centre. Because the bottom-
   anchored south seat creeps UP as the felt gets shorter, a fixed downward
   nudge on the avatar can't suit every height — so instead the cards are kept
   small and high, clearing the south avatar by lift alone at every desktop
   height. The N card always clears the name (felt top is constant); the W/E
   cards have ~100px of horizontal room. Verified at the true 1366×768 laptop,
   1100-wide capped desktop, and 1520×800 desktop. */
@media (min-width: 1024px) {
  :root { --seat-avatar-size: 56px; }
  .seat .avatar { font-size: 20px; }
  .seat .name { font-size: 15px; }
  /* Compact pile: N/S rows are one card tall, the centre row collapses to 0 so
     the four cards cluster tightly. The row template sums to the explicit
     height so the grid can't overflow downward (an earlier `1fr` middle row
     resolved to a full card height, bloating the grid to ~264px and dropping
     the S card onto the avatar). translateY lifts the whole pile into the clear
     band between the N name and the S avatar. */
  .trick {
    max-width: 290px;
    height: 150px;
    grid-template-columns: 66px 1fr 66px;
    grid-template-rows: 75px 0px 75px;
    gap: 0;
    transform: translateY(-28px);
  }
  .trick .played-card { width: 66px; height: 94px; border-radius: var(--radius-card-lg); }
  .trick .played-card .pip { font-size: 38px; }
  .trick .played-card .corner .rank { font-size: 15px; }
  .trick .played-card .corner .suit { font-size: 12px; }
  .trick .played-card .corner.tl { top: 4px; left: 5px; }
  .trick .played-card .corner.br { bottom: 4px; right: 5px; }
}

/* Tablets and large phones (700-1023px) — tighten the overlay layout. Capped
   at 1023px (not 1200px) so it doesn't fight the >=1024px desktop block above,
   which uses its own compact, avatar-clearing trick. Narrower phone rules
   (<=960 / <=720 / …) still override this for small viewports. */
@media (max-width: 1023px) {
  :root { --seat-avatar-size: 70px; }
  .trick { max-width: 380px; height: 300px; }
  .trick .played-card { width: 82px; height: 118px; }
  .trick .played-card .pip { font-size: 46px; }
  .trick .played-card .corner .rank { font-size: 18px; }
  .trick .played-card .corner .suit { font-size: 14px; }
  .seat .avatar { font-size: 20px; }
  .seat .name { font-size: 13px; }
  /* Tablet/large-phone overlay layout rings the seats tightly around the
     play-zone, so the side (east/west) avatars sit right at its edge. When
     such a seat is the bidder its tricks+contract chips, side by side, are
     wide enough to graze the zone's edge. Stack them vertically (matching the
     ≤720px phone rule) so the chip column stays narrow and clears the zone —
     east grows away to the right, west to the left. Desktop (≥1024px) has a
     wide felt with the avatars well clear of the zone, so it keeps the row. */
  .seat.seat-pos-east .team-stats { flex-direction: column; align-items: flex-end; gap: 2px; }
  .seat.seat-pos-west .team-stats { flex-direction: column; align-items: flex-start; gap: 2px; }
}
@media (max-width: 960px) {
  :root { --seat-avatar-size: 62px; }
  .game-area { gap: 0; }
  .felt { min-height: 440px; }
  .trick { max-width: 320px; height: 270px; }
  .trick .played-card { width: 74px; height: 106px; }
  .trick .played-card .pip { font-size: 42px; }
  .trick .played-card .corner .rank { font-size: 16px; }
  .trick .played-card .corner .suit { font-size: 13px; }
  .seat .avatar { font-size: 18px; }
  .seat .name { font-size: 13px; }
}

/* =======================================================================
   Mobile (≤720px) — size adjustments for smaller viewports.
   The overlay layout is already the baseline; this just shrinks elements.
   ======================================================================= */
html, body { overflow: hidden; }

@media (max-width: 720px) {
  body { font-size: 16px; text-align: center; }
  .top-bar { padding: 6px 10px; text-align: center; }
  .top-bar .brand-sub { display: none; }
  .top-bar .brand-name { font-size: 18px; }
  .top-bar .brand-mark { font-size: 16px; }
  .top-bar .user-chip { font-size: 13px; padding: 4px 8px; }
  .top-bar #btn-logout { padding: 4px 10px; font-size: 13px; }
  /* Phones: the guest who-chip + Logout + Log in + Register together overflow
     the bar (the right cluster ran off-screen at 344px). The guest name is
     already shown in-game (seat + scoreboard), so drop the redundant who-chip
     on phones. Registered users (who use the avatar me-link, with who/logout
     already hidden by the hydrate script) are unaffected. */
  .top-bar-right { gap: 6px; flex-wrap: nowrap; min-width: 0; }
  .top-bar #who { display: none !important; }
  .top-bar .brand-name { font-size: 17px; }
  /* Phones: the History/Store text links + wallet + avatar/name overflow the
     bar and clip the wallet off-screen. Drop the two text links (Store is still
     reachable via the wallet/points chip; History via the profile), hide the
     "PTS" unit, and clamp the display name so the right cluster always fits. */
  .top-bar #link-history, .top-bar #link-store { display: none; }
  .top-bar .top-bar-inner { gap: 6px; }
  .top-bar .wallet-chip-top { padding: 3px 8px; flex: 0 0 auto; }
  .top-bar .wallet-chip-top .wc-unit { display: none; }
  .top-bar .user-chip #me-name { max-width: 86px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  .game-area {
    /* The bands are tokens.json > devices, at EVERY width — that is the whole
       point of a declared budget. This block used to re-declare
       `auto auto minmax(0,1fr) auto` for the old four-area shape, which silently
       beat the token rows further up the file and put the HUD at 125px and the
       table at 51.4%. A gap here steals from the bands too, so it is 0. */
    grid-template-rows: var(--hudH) minmax(0, 1fr) var(--handH) var(--railH);
    gap: 0;
    padding: 0;
    max-width: 100%;
  }
  .ga-north { padding: 2px 0 0; }
  .ga-west, .ga-east { padding: 0; }
  .ga-south { padding: 0 0 2px; }

  /* ---- Slim top bar (phones) ---- */
  .ga-top { gap: 3px 6px; padding: 4px 8px; min-height: 0; height: var(--hudH); box-sizing: border-box; }
  .gt-scores { gap: 5px; }
  .gt-score-rows .ms-row { gap: 4px; }
  .gt-score-rows .ms-label { font-size: 10px; max-width: 96px; letter-spacing: 0.2px; }
  .gt-score-rows .ms-score { font-size: 13.5px; }
  .gt-score-rows .ms-tricks { font-size: 9.5px; }
  /* Short "/"-joined names so the row never clips. */
  .ms-label-full { display: none; }
  .ms-label-short { display: inline; }
  .gt-contract { padding: 2px 9px; gap: 4px; }
  .gt-contract .gt-bid { font-size: 16px; }
  .gt-contract .gt-trump { font-size: 18px; }
  .gt-right { gap: 4px; }
  .gt-right #room-phase { font-size: 9px; padding: 2px 6px; max-width: 88px; letter-spacing: 0.5px; }
  .gt-right #btn-leave { padding: 3px 8px; font-size: 11px; }
  .gt-room-strip h2 { font-size: 11px; }
  .gt-room-strip .invite-banner { font-size: 11px; gap: 6px; }
  .gt-room-strip .invite-url { display: none; }
  .scores-details-btn { padding: 2px 6px; font-size: 12px; }
  .scores-details-panel {
    position: fixed;
    top: 96px; left: 8px; right: 8px;
    max-width: none; min-width: 0;
    z-index: 200;
  }

  /* Continuous (ratio) seat size instead of a fixed 48px step — scales with
     viewport width so seats keep proportion across phones; ~48px on the S25
     (411px * 11.7%), floored/capped so it never gets tiny or huge. */
  :root { --seat-avatar-size: clamp(40px, 11.7vw, 60px); }
  .seat .avatar { font-size: clamp(14px, 4.4vw, 22px); }
  .seat .name { font-size: 15px; font-weight: 700; max-width: 100px; }
  .seat .info { display: none; }
  .seat .seat-pills { display: none; }

  /* ---- Per-position seat layout (mobile) ----
     North/South: avatar + stats row to the RIGHT
     West/East:   avatar on edge, stats row BELOW */

  .seat.seat-horizontal,
  .seat.seat-vertical {
    display: flex !important;
    gap: 3px;
    align-items: center;
    padding: 0 !important;
  }
  .seat.seat-horizontal .seat-main,
  .seat.seat-vertical .seat-main {
    display: flex !important;
    flex-direction: column !important;
    align-items: center;
    gap: 0;
    flex-shrink: 0;
  }
  .seat.seat-horizontal .seat-identity,
  .seat.seat-vertical .seat-identity { display: none !important; }

  /* Stats row: count + bid inline, always in a row */
  .seat.seat-horizontal .team-stats,
  .seat.seat-vertical .team-stats {
    display: flex !important;
    flex-direction: row !important;
    align-items: center;
    gap: 3px;
    margin: 0 !important;
    padding: 0;
    background: none !important;
    border: none !important;
  }

  /* ---- Chip base style (mobile) ---- */
  .seat .team-stats .ts-chip {
    font-size: 13px;
    font-weight: 700;
    border-radius: var(--radius);
    padding: 2px 6px;
    display: inline-flex;
    align-items: center;
    gap: 3px;
    white-space: nowrap;
    line-height: 1.1;
  }
  /* Bid chip: white background with real suit colors */
  .seat .team-stats .ts-tarneeb {
    background: #fff;
    border: 1.5px solid rgba(0,0,0,0.12);
    box-shadow: 0 1px 4px rgba(0,0,0,0.15);
    color: #000;
    text-shadow: none;
  }
  .seat .team-stats .ts-tarneeb.ts-suit-red { color: #E8192C; }
  .seat .team-stats .ts-tarneeb.ts-suit-black { color: #000; }
  .seat .team-stats .ts-tarneeb .ts-trump { font-size: 16px; }
  .seat .team-stats .ts-tarneeb .ts-bid {
    font-size: 14px;
    font-weight: 800;
    color: inherit;
  }
  /* Count chip: dark translucent */
  .seat .team-stats .ts-wins {
    background: rgba(0,0,0,0.6);
    border: 1px solid rgba(255,255,255,0.2);
    color: #fff;
    text-shadow: 0 1px 3px rgba(0,0,0,0.9);
  }
  .seat .team-stats .ts-wins .ts-value {
    font-size: 14px;
    font-weight: 700;
    color: #fff;
  }

  /* =========== NORTH & SOUTH: stats to the RIGHT of avatar =========== */
  .seat.seat-pos-north,
  .seat.seat-pos-south {
    flex-direction: row !important;
  }

  /* =========== WEST: avatar left-edge, stats BELOW =========== */
  .seat.seat-pos-west {
    flex-direction: column !important;
    align-items: flex-start !important;
  }

  /* =========== EAST: avatar right-edge, stats BELOW =========== */
  .seat.seat-pos-east {
    flex-direction: column !important;
    align-items: flex-end !important;
  }

  /* On the side seats the avatar sits at the felt edge, just outside the
     bounded play-zone. When that seat is the bidder it carries TWO chips
     (tricks + contract); side-by-side they are wide enough to spill back
     over the zone's edge and clip the played card at 344px. Stack the
     chips vertically here so the chip column stays as narrow as one chip
     and never reaches into the zone. East stays right-aligned (grows away
     from the zone), west stays left-aligned. North/South keep the row
     layout — they have clear space above/below the zone. */
  .seat.seat-pos-east .team-stats { flex-direction: column !important; align-items: flex-end !important; gap: 2px; }
  .seat.seat-pos-west .team-stats { flex-direction: column !important; align-items: flex-start !important; gap: 2px; }

  .felt { min-height: 420px; }
  .felt-logo { font-size: 42px; letter-spacing: 12px; }
  .trick { max-width: 260px; height: 230px;
    grid-template-columns: 30px 1fr 30px; grid-template-rows: 40px 1fr 40px; }
  /* Jawaker-measured proportions (S25 @1080px: their table card ~165px =
     15.3vw, their held card ~233px = 21.6vw, so table = 0.71 x hand). */
  .trick .played-card { width: 15.3vw; height: calc(15.3vw * 1.4); }
  .trick .played-card .pip { font-size: calc(15.3vw * 0.62); }
  .trick .played-card .corner .rank { font-size: calc(15.3vw * 0.28); }
  .trick .played-card .corner .suit { font-size: calc(15.3vw * 0.22); }
  .trick .played-card .corner.tl { top: 3px; left: 4px; }
  .trick .played-card .corner.br { bottom: 3px; right: 4px; }

  /* ---- Hand: fix centering & margins on mobile — use full screen width ---- */
  .ga-hand {
    /* The LANE is a declared band (--handH, 158px on a phone) and the CARD is a
       declared size (--cw x --ch, 84x118). Neither is derived from the other.
       This block used to run it backwards — card from the viewport, lane from
       the card — which computed 173.39px and pinned min/max-height to it, so it
       beat the token row further up the file and ate 15px of the table zone
       every time. That is why the table measured 57.9% against a design that
       allocates 62.1%. */
    --card-w: var(--cw);
    padding: 4px 6px 6px;
    height: var(--handH);
    min-height: var(--handH);
    max-height: var(--handH);
    /* visible so a raised card is never clipped mid-press */
    overflow: visible;
  }
  .hand {
    --card-w: clamp(40px, 21.5vw, 110px);
    /* Deep overlap so 13 big cards still fit the width (Jawaker shows ~30px of
       each card's index strip and hides the rest under the next card). */
    --card-overlap: calc(var(--card-w) * -0.72);
    --card-natural-gap: calc((100% - var(--card-count, 13) * var(--card-w)) / (var(--card-count, 13) - 1));
    --card-gap: clamp(var(--card-overlap), var(--card-natural-gap), 4px);
    padding: 16px 4px 4px;
    justify-content: center;
    overflow: visible;
    width: auto;
    max-width: 100%;
    margin: 0 auto;
  }
  .hand .card {
    height: calc(var(--card-w) * 1.4);
  }
  .hand .card .corner .rank { font-size: calc(var(--card-w) * 0.30); }
  .hand .card .corner .suit { font-size: calc(var(--card-w) * 0.24); }
  .hand .card .pip {
    font-size: calc(var(--card-w) * 0.58);
    inset: auto; bottom: 3px; right: 4px;
    align-items: flex-end; justify-content: flex-end;
  }
  .ga-hand .hand-hint-row { font-size: 15px; gap: 6px; height: 28px; min-height: 28px; max-height: 28px; text-align: center; justify-content: center; }
  .ga-hand .hand-hint { font-size: 15px; font-weight: 600; }

  .round-status { flex-direction: column; align-items: stretch; gap: 4px; padding: 5px 10px; text-align: center; }
  .round-status-left, .round-status-right { justify-content: center; }
  .round-status-right .goal-pill { font-size: 11px; padding: 2px 7px; }

  .action-modal { padding: 4px; }
  .action-modal-card { min-width: 0; max-width: 100%; width: 100%; padding: 14px 6px 16px; text-align: center; }
  .action-modal-header h3 { font-size: 22px; }
  .action-modal-header .action-modal-kicker { font-size: 14px; }
  .action-modal-sub { font-size: 15px; }

  /* Modal hand preview — reuse main hand sizing so bidding/trump views match */
  .modal-hand-preview { overflow: visible; padding: 6px 0; margin-left: -6px; margin-right: -6px; width: calc(100% + 12px); }
  .modal-hand-preview .hand.hand-preview {
    --card-w: clamp(40px, calc((100vw - 14px) / 9), 96px);
    min-height: calc(var(--card-w) * 1.4 + 10px);
    padding: 4px 4px; justify-content: center; overflow: visible; width: 100%;
  }
  .modal-hand-preview .hand.hand-preview .card {
    width: var(--card-w); height: calc(var(--card-w) * 1.4);
    margin-left: max(calc((100% - 13 * var(--card-w)) / 12), calc(var(--card-w) * -0.58)); border-radius: var(--radius-card); flex: 0 0 auto;
  }
  .modal-hand-preview .hand.hand-preview .card:first-child { margin-left: 0; }
  .modal-hand-preview .hand.hand-preview .card .corner .rank { font-size: calc(var(--card-w) * 0.30); }
  .modal-hand-preview .hand.hand-preview .card .corner .suit { font-size: calc(var(--card-w) * 0.24); }
  .modal-hand-preview .hand.hand-preview .card .corner.br { display: none; }
  .modal-hand-preview .hand.hand-preview .card .pip {
    font-size: calc(var(--card-w) * 0.58);
    inset: auto; bottom: 3px; right: 4px;
    align-items: flex-end; justify-content: flex-end;
  }

  .bid-grid:not(.bid-grid-tiered) { grid-template-columns: repeat(4, 1fr); gap: 6px; }
  .bid-grid-tiered { gap: 6px; }
  .bid-grid-tiered .bid-row { gap: 6px; }

  .log-wrap summary { padding: 3px 8px; font-size: 12px; }
  #log { font-size: 12px; padding: 8px; max-height: 120px; }

  /* Center align lobby content on mobile */
  .lobby { text-align: center; }
  .lobby h2, .lobby h3 { text-align: center; }
  .room-list .room-row { text-align: center; }

  /* ---- Quick Join & Create Room (mobile) ---- */
  .quick-join-btn { padding: 14px 16px; }
  .quick-join-btn .qj-icon { font-size: 22px; }
  .create-room-fields { flex-direction: column; gap: 8px; }
  .create-room-fields input, .create-room-fields select { min-width: 0; width: 100%; }
  .create-room-actions { flex-direction: column; gap: 8px; }
  .create-room-actions button { min-width: 0; width: 100%; }

  /* ---- Waiting room (mobile) ---- */
  .waiting-room-content {
    width: 96%; max-width: 100%;
    padding: 14px 10px 14px;
    max-height: 100vh;
    max-height: 100dvh;
    overflow-y: auto;
  }
  .wr-header { margin-bottom: 10px; }
  .wr-header .wr-kicker { font-size: 10px; padding: 2px 10px; margin-bottom: 4px; }
  .wr-header .wr-title { font-size: 20px; margin: 4px 0 2px; }
  .wr-header .wr-sub { font-size: 12px; }
  .wr-actions { margin-bottom: 10px; gap: 8px; }
  .wr-actions button.big { padding: 10px 20px; font-size: 14px; }
  .wr-actions button { min-width: 120px; }
  .seat-picker {
    max-width: 300px;
    aspect-ratio: 1.1;
    margin-bottom: 10px;
  }
  .seat-picker .sp-table-felt { border-width: 2px; border-radius: var(--radius); }
  .sp-seat { padding: 5px 8px; gap: 2px; border-radius: var(--radius); border-width: 1.5px; }
  .sp-seat .sp-avatar { width: 32px; height: 32px; font-size: 14px; border-width: 1.5px; }
  .sp-seat .sp-label { font-size: 9px; }
  .sp-seat .sp-team-tag { font-size: 8px; padding: 0 5px; }
  .sp-felt-logo { font-size: 14px; letter-spacing: 4px; }
  .wr-invite { padding: 10px 12px; margin-bottom: 0; }
  .wr-invite-code { font-size: 20px; letter-spacing: 3px; }
  .wr-invite-qr { margin: 8px auto 4px; }
  .wr-invite-qr canvas, .wr-invite-qr img { width: 80px !important; height: 80px !important; }
  .wr-invite-url { font-size: 11px; }

  /* ---- Ceremony modals (mobile) ---- */
  .ceremony-btn-row:not(.ceremony-cut-row) { grid-template-columns: 1fr 1fr; gap: 8px; }
  .ceremony-btn-row.ceremony-cut-row { grid-template-columns: 1fr; gap: 8px; }
  .ceremony-shuffle-btn { font-size: 0.9rem !important; padding: 12px 0 !important; }
  .ceremony-cut-btn { padding: 12px 8px !important; }
  .ceremony-cut-btn .cut-icon { display: inline; margin-right: 6px; }
  .ceremony-deck-visual { padding: 12px 0 8px; }
  .deck-stack { width: 60px; height: 85px; }
  .deck-card { width: 54px; height: 76px; border-radius: var(--radius-card-lg); }
  .dc-top::after { font-size: 11px; }
}

/* Phones (<=420px) — squeeze a bit for narrower viewports. The hand row
   sizes itself via clamp() in the parent media query, so we don't need to
   hard-code card dimensions here any more; only non-hand tweaks remain. */
@media (max-width: 420px) {
  /* Very narrow phones: even after dropping the who-chip, Log in + Register +
     Logout still crowd the bar. For guests the account links are the priority
     path, so drop the redundant Logout here too (guests reset via the lobby).
     Registered users have it hidden already. */
  .top-bar #btn-logout { display: none !important; }
  .game-area { gap: 0; padding: 0; }
  .felt { min-height: 390px; }
  /* Ultra-narrow top bar: tighten every group so one row still fits. */
  .ga-top { gap: 2px 5px; padding: 3px 6px; min-height: 0; height: var(--hudH); box-sizing: border-box; }
  .gt-score-rows .ms-label { font-size: 9px; max-width: 74px; }
  .gt-score-rows .ms-score { font-size: 12.5px; }
  .gt-score-rows .ms-tricks { font-size: 9px; }
  .gt-contract { padding: 2px 7px; gap: 3px; }
  .gt-contract .gt-bid { font-size: 15px; }
  .gt-contract .gt-trump { font-size: 16px; }
  .gt-right #room-phase { max-width: 64px; padding: 2px 4px; }
  .gt-right #btn-leave { padding: 3px 6px; font-size: 10px; }
  .scores-details-btn { padding: 1px 5px; font-size: 11px; }
  .trick {
    max-width: 240px;
    height: 210px;
    grid-template-columns: 28px 1fr 28px;
    grid-template-rows: 36px 1fr 36px;
  }
  /* Keep the Jawaker table:hand ratio (0.71x) at this width too — inherit the
     vw sizing from the ≤720px block instead of hard-coding px. */
  .trick .played-card { width: 15.3vw; height: calc(15.3vw * 1.4); }
  .trick .played-card .pip { font-size: calc(15.3vw * 0.62); }
  /* modal-hand-preview card size is handled by clamp() in the parent
     ≤720px media query — no hard-coded overrides here. */
  .seat.seat-horizontal .avatar,
  .seat.seat-vertical .avatar { width: 38px; height: 38px; font-size: 15px; }
  .seat.seat-horizontal .name,
  .seat.seat-vertical .name { font-size: 13px; font-weight: 700; }
  .seat.seat-horizontal .seat-identity,
  .seat.seat-vertical .seat-identity { display: none; }
  .seat.seat-horizontal .team-stats .ts-chip,
  .seat.seat-vertical .team-stats .ts-chip { font-size: 13px; padding: 2px 5px; }

  /* ---- Waiting room (phones) ---- */
  .waiting-room-content { padding: 10px 6px 10px; }
  .wr-header { margin-bottom: 6px; }
  .wr-header .wr-title { font-size: 18px; }
  .wr-header .wr-sub { font-size: 11px; }
  .wr-actions { margin-bottom: 6px; gap: 6px; }
  .wr-actions button.big { padding: 8px 16px; font-size: 13px; }
  .wr-actions button { min-width: 100px; font-size: 12px; }
  .seat-picker {
    max-width: 260px;
    aspect-ratio: 1.05;
    margin-bottom: 6px;
  }
  .seat-picker .sp-table-felt { border-radius: var(--radius); }
  .sp-seat { padding: 3px 6px; gap: 1px; border-radius: var(--radius); }
  .sp-seat .sp-avatar { width: 28px; height: 28px; font-size: 12px; }
  .sp-seat .sp-label { font-size: 8px; }
  .sp-seat .sp-team-tag { font-size: 7px; padding: 0 4px; }
  .sp-felt-logo { font-size: 11px; letter-spacing: 2px; }
  .wr-invite { padding: 6px 8px; }
  .wr-invite-code { font-size: 16px; letter-spacing: 2px; }
  .wr-invite-qr { display: none; }
  .wr-invite-url { font-size: 10px; }

  /* ---- Quick join (phones) ---- */
  .quick-join-btn { padding: 12px; }
  .quick-join-btn .qj-text strong { font-size: 14px; }

  /* ---- Ceremony (phones) ---- */
  .ceremony-shuffle-btn { font-size: 0.85rem !important; padding: 10px 0 !important; }

  /* ---- Table panel (phones) ----
     Side seat avatars are 38px at ≤420px (see .seat-horizontal .avatar
     above) but .table-panel still computes its width assuming the 48px
     value inherited from ≤720px. Re-declaring the var locally to match
     the actual rendered avatar reclaims ~20px of panel width at the
     Galaxy Fold / Android Small / iPhone SE widths — which is what
     keeps the 3-column bid grid and the trump button row from feeling
     squeezed at 344-375px. */
  :root { --seat-avatar-size: 38px; }
  .table-panel { padding: 10px 10px; gap: 6px; }
  .bid-grid-tiered .bid-btn .bid-amt  { font-size: 18px; }
  .bid-grid-tiered .bid-btn .bid-icon { font-size: 16px; }
  .pass-btn { padding: 12px 0; font-size: 14px; }
  .bid-panel-status { font-size: 11px; padding: 0 2px 2px; }
  .table-panel--trump .trump-grid-vertical .trump-glyph { font-size: 22px; }
  .table-panel--trump .trump-grid-vertical .trump-name { font-size: 12px; }
}

/* ---------- Bot personality selector ---------- */
.bot-personality-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin: 12px 0 16px;
  max-width: 480px;
}
.bot-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 12px 8px 10px;
  background: rgba(255,255,255,0.06);
  border: 2px solid rgba(255,255,255,0.12);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color .2s, background .2s, transform .15s;
  text-align: center;
}
.bot-card:hover {
  background: rgba(255,255,255,0.10);
  border-color: rgba(255,255,255,0.25);
  transform: translateY(-2px);
}
.bot-card-selected {
  border-color: var(--sage-accent, #456882);
  background: rgba(200,150,62,0.18);
  box-shadow: 0 0 12px rgba(200,150,62,0.25);
}
.bot-card-selected:hover {
  border-color: var(--sage-accent, #456882);
  background: rgba(200,150,62,0.22);
}
.bot-icon {
  font-size: 28px;
  line-height: 1;
}
.bot-name {
  font-size: 13px;
  font-weight: 700;
  color: #fff;
  line-height: 1.2;
}
.bot-desc {
  font-size: 10px;
  color: rgba(255,255,255,0.55);
  line-height: 1.3;
}

@media (max-width: 520px) {
  .bot-personality-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 8px;
  }
  .bot-card { padding: 10px 6px 8px; }
  .bot-icon { font-size: 24px; }
  .bot-name { font-size: 12px; }
}

/* ---------- Bot personality accordion (vertical stacking) ---------- */
.bot-personality-accordion {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 12px 0 16px;
  max-width: 480px;
}
.bot-card-accordion {
  border: 2px solid rgba(255,255,255,0.12);
  border-radius: var(--radius);
  background: rgba(255,255,255,0.06);
  overflow: hidden;
  transition: all 0.2s;
}
.bot-card-accordion:hover {
  border-color: rgba(255,255,255,0.25);
  background: rgba(255,255,255,0.10);
}
.bot-card-header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  cursor: pointer;
  transition: background 0.15s;
  user-select: none;
}
.bot-card-header:hover {
  background: rgba(255,255,255,0.05);
}
.bot-card-header .bot-icon {
  font-size: 24px;
  flex-shrink: 0;
}
.bot-card-header .bot-name {
  font-size: 14px;
  font-weight: 700;
  color: #fff;
  flex: 1;
}
.bot-card-header .bot-checkmark {
  font-size: 18px;
  color: var(--sage-deep);
  font-weight: bold;
}
.bot-card-body {
  display: none;
  padding: 0 14px 12px;
  background: rgba(0,0,0,0.1);
}
.bot-card-accordion.bot-card-selected {
  border-color: var(--sage-accent, #456882);
  background: rgba(200,150,62,0.18);
  box-shadow: 0 0 12px rgba(200,150,62,0.25);
}
.bot-card-accordion.bot-card-selected .bot-card-header {
  background: rgba(200,150,62,0.1);
}
.bot-card-accordion.bot-card-selected .bot-card-body {
  display: block;
}
.bot-card-body .bot-desc {
  font-size: 12px;
  color: rgba(255,255,255,0.75);
  line-height: 1.4;
}

/* ---------- Game start options ---------- */
.game-start-options {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 12px 0;
}
.game-start-options button {
  width: 100%;
}

/* ---------- Wait timer display ---------- */
.wait-timer-display {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  margin: 20px 0;
}
.wtd-countdown {
  font-size: 48px;
  font-weight: 800;
  color: var(--sage-deep);
  font-variant-numeric: tabular-nums;
  min-width: 120px;
  text-align: center;
}
.wtd-bar {
  width: 100%;
  height: 12px;
  background: rgba(0,0,0,0.15);
  border-radius: var(--radius);
  overflow: hidden;
  max-width: 300px;
}
.wtd-hint {
  font-size: 12px;
  color: var(--ink-mute);
  text-align: center;
  margin-top: -8px;
}
.wtd-fill {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--yellow), var(--lime), var(--sage-deep));
  transition: width 0.15s linear;
}

/* ── Join-by-code section ── */
.join-code-section {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}
.join-code-section h3 { margin-bottom: 8px; }
.lobby-panel > h3 {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}
.room-list { margin-top: 6px; }
.join-code-section .row {
  display: flex; gap: 8px; align-items: center;
}
.invite-code-input {
  flex: 1;
  min-width: 0;
  max-width: 200px;
  text-transform: uppercase;
  letter-spacing: 2px;
  font-weight: 700;
  text-align: center;
}
.invite-code-input::placeholder {
  letter-spacing: normal;
  text-transform: none;
  font-weight: 500;
  opacity: 0.55;
}
@media (max-width: 480px) {
  .invite-code-input { max-width: none; }
}

/* ── Invite banner (in-room) ── */
.invite-banner {
  display: flex;
  align-items: center;
  gap: 10px;
  background: rgba(200, 150, 62, 0.12);
  border: 1px solid rgba(200, 150, 62, 0.25);
  border-radius: var(--radius);
  padding: 6px 14px;
  margin-top: 6px;
  font-size: 13px;
}
.invite-label {
  color: var(--ink-soft);
  font-weight: 500;
}
.invite-code {
  font-family: 'Courier New', monospace;
  font-weight: 800;
  font-size: 16px;
  letter-spacing: 3px;
  color: var(--sage-deep);
  background: rgba(255,255,255,0.35);
  padding: 2px 10px;
  border-radius: var(--radius);
  user-select: all;
}
.invite-url {
  font-size: 11px;
  color: var(--ink-mute);
  opacity: 0.8;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 280px;
  flex-shrink: 1;
}
.invite-banner .ghost.small {
  font-size: 12px;
  padding: 3px 8px;
  margin-left: auto;
  flex-shrink: 0;
}

/* ── Seat map in lobby modal ── */
.seat-map-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-bottom: 8px;
}
.seat-map-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 10px 8px;
  border-radius: var(--radius);
  border: 2px solid rgba(255,255,255,0.1);
  background: rgba(0,0,0,0.2);
  transition: all 0.15s;
}
.seat-map-card[data-team="1"] { border-color: rgba(200,150,62,0.3); }
.seat-map-card[data-team="2"] { border-color: rgba(224,173,78,0.3); }
.seat-map-card.seat-map-me {
  border-color: var(--sage);
  background: rgba(200,150,62,0.2);
  box-shadow: 0 0 8px rgba(200,150,62,0.3);
}
.seat-map-card.seat-map-empty {
  border-style: dashed;
  opacity: 0.7;
}
.seat-map-card.seat-map-empty:hover {
  opacity: 1;
  border-color: var(--yellow);
  background: rgba(224,173,78,0.1);
}
.smc-name { font-weight: 700; font-size: 13px; }
.smc-info { font-size: 11px; color: var(--ink-mute); }
.seat-map-hint {
  font-size: 11px;
  color: var(--ink-mute);
  text-align: center;
  margin-bottom: 12px;
  line-height: 1.4;
}
.modal-section-label {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--ink-mute);
  margin-bottom: 6px;
}

/* ── Clickable empty seat on table ── */
.seat-clickable {
  cursor: pointer;
  border: 2px dashed rgba(224,173,78,0.5) !important;
}
.seat-clickable:hover {
  border-color: var(--yellow) !important;
  box-shadow: 0 0 10px rgba(224,173,78,0.4);
}
.seat-move-hint {
  font-size: 10px;
  color: var(--yellow);
  opacity: 0.8;
  margin-top: 2px;
}

/* =========================================================
   ANIMATIONS & EFFECTS — Bold & Exciting
   ========================================================= */

/* ---- Animated gradient shimmer on the top bar ---- */
.top-bar::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(224,173,78,0) 20%,
    rgba(224,173,78,0.8) 50%,
    rgba(224,173,78,0) 80%,
    transparent 100%);
  background-size: 200% 100%;
  animation: shimmerBar 3s ease-in-out infinite;
}
@keyframes shimmerBar {
  0%   { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

/* ---- Pulsing gold glow on brand name ---- */
.brand-name {
  animation: brandPulse 3s ease-in-out infinite;
}
@keyframes brandPulse {
  0%, 100% { text-shadow: 0 1px 4px rgba(200,150,62,0.3), 0 0 12px rgba(224,173,78,0.15); }
  50%      { text-shadow: 0 1px 6px rgba(200,150,62,0.5), 0 0 24px rgba(224,173,78,0.35); }
}

/* ---- Animated gradient border on the felt ---- */
.felt {
  border: 2px solid transparent;
  background-clip: padding-box;
  position: relative;
}
.felt::before {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: var(--radius);
  background: linear-gradient(135deg,
    rgba(224,173,78,0.35),
    rgba(224,173,78,0.05),
    rgba(224,173,78,0.25),
    rgba(224,173,78,0.05),
    rgba(224,173,78,0.35));
  background-size: 300% 300%;
  animation: borderGlow 6s ease-in-out infinite;
  z-index: -1;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask-composite: xor;
  -webkit-mask-composite: xor;
  padding: 2px;
}
@keyframes borderGlow {
  0%   { background-position: 0% 0%; }
  50%  { background-position: 100% 100%; }
  100% { background-position: 0% 0%; }
}

/* ---- Floating particle effect on the felt (CSS-only) ---- */
.felt::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--radius);
  background:
    radial-gradient(2px 2px at 20% 30%, rgba(224,173,78,0.3) 0%, transparent 100%),
    radial-gradient(2px 2px at 80% 20%, rgba(224,173,78,0.25) 0%, transparent 100%),
    radial-gradient(2px 2px at 60% 70%, rgba(224,173,78,0.2) 0%, transparent 100%),
    radial-gradient(2px 2px at 30% 80%, rgba(224,173,78,0.25) 0%, transparent 100%),
    radial-gradient(2px 2px at 70% 50%, rgba(224,173,78,0.15) 0%, transparent 100%),
    radial-gradient(1.5px 1.5px at 10% 60%, rgba(255,255,255,0.12) 0%, transparent 100%),
    radial-gradient(1.5px 1.5px at 50% 10%, rgba(255,255,255,0.1) 0%, transparent 100%),
    radial-gradient(1.5px 1.5px at 90% 90%, rgba(255,255,255,0.08) 0%, transparent 100%);
  background-size: 200% 200%;
  animation: floatParticles 12s ease-in-out infinite;
  pointer-events: none;
  z-index: 0;
  opacity: 0.7;
}
@keyframes floatParticles {
  0%   { background-position: 0% 0%; }
  25%  { background-position: 50% 25%; }
  50%  { background-position: 100% 50%; }
  75%  { background-position: 50% 75%; }
  100% { background-position: 0% 0%; }
}

/* ---- Turn avatar: pulsing ring animation ---- */
.seat.turn .avatar {
  animation: turnPulse 1.8s ease-in-out infinite;
}
@keyframes turnPulse {
  0%, 100% {
    box-shadow: 0 0 0 3px rgba(224, 173, 78, 0.85),
                0 0 18px rgba(224, 173, 78, 0.4);
  }
  50% {
    box-shadow: 0 0 0 5px rgba(224, 173, 78, 0.95),
                0 0 30px rgba(224, 173, 78, 0.6),
                0 0 50px rgba(224, 173, 78, 0.2);
  }
}

/* ---- Card hover sparkle effect ---- */
.hand .card.playable:hover {
  animation: cardSparkle 0.6s ease-out;
}
@keyframes cardSparkle {
  0%   { box-shadow: 0 10px 22px rgba(200, 150, 62, 0.35); }
  30%  { box-shadow: 0 10px 22px rgba(200, 150, 62, 0.35),
                     0 0 20px rgba(224, 173, 78, 0.5),
                     0 0 40px rgba(224, 173, 78, 0.2); }
  100% { box-shadow: 0 10px 22px rgba(200, 150, 62, 0.35); }
}

/* ---- Score value animated count feel ---- */
.gt-score-rows .ms-score {
  transition: transform 0.3s cubic-bezier(0.2, 0.9, 0.3, 1.3), color 0.3s;
}

/* ---- Shimmer on the primary buttons ---- */
button.primary {
  position: relative;
  overflow: hidden;
}
button.primary::after {
  content: "";
  position: absolute;
  top: 0;
  left: -100%;
  width: 60%;
  height: 100%;
  background: linear-gradient(90deg,
    transparent,
    rgba(255,255,255,0.25),
    transparent);
  animation: btnShimmer 3s ease-in-out infinite;
}
@keyframes btnShimmer {
  0%   { left: -100%; }
  50%  { left: 150%; }
  100% { left: 150%; }
}

/* ---- Drag-and-drop: highlight felt when dragging a card over it ---- */
.felt.drag-over {
  outline: 3px dashed rgba(224, 173, 78, 0.7);
  outline-offset: -3px;
  background: rgba(224, 173, 78, 0.08);
}
.card.dragging {
  opacity: 0.5;
  transform: scale(0.9) !important;
}

/* ---- Subtle gold glow line on hand panel top ---- */
.ga-hand {
  border-top: 1px solid rgba(224,173,78,0.3);
  box-shadow: 0 -2px 12px rgba(224,173,78,0.08), var(--shadow-sm);
}

/* ---- Winning trick celebration flash ---- */
@keyframes trickWin {
  0%   { opacity: 0; transform: scale(0.8); }
  20%  { opacity: 1; transform: scale(1.05); }
  40%  { transform: scale(0.98); }
  60%  { transform: scale(1.02); }
  100% { opacity: 1; transform: scale(1); }
}

/* ---- Bid announce entrance animation (enhanced) ---- */
.bid-announce.bid-announce-in .bid-announce-value {
  animation: bidPop 0.4s cubic-bezier(0.2, 0.9, 0.3, 1.3);
}
@keyframes bidPop {
  0%   { transform: scale(0.5); opacity: 0; }
  60%  { transform: scale(1.1); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}

/* ---- Score panel shimmer on score change ---- */
.gt-score-rows .ms-row {
  transition: background 0.4s;
}
.gt-score-rows .ms-row.bidding {
  animation: scorePulse 2s ease-in-out infinite;
}
@keyframes scorePulse {
  0%, 100% { background: rgba(224, 173, 78, 0.05); }
  50%      { background: rgba(224, 173, 78, 0.15); }
}

/* ---- Animated background gradient on card panel (lobby) ---- */
.card-panel {
  position: relative;
  overflow: hidden;
}
.card-panel::before {
  content: "";
  position: absolute;
  top: -50%;
  left: -50%;
  width: 200%;
  height: 200%;
  background: radial-gradient(circle at center,
    rgba(224,173,78,0.05) 0%,
    transparent 50%);
  animation: panelGlow 8s ease-in-out infinite;
  pointer-events: none;
}
@keyframes panelGlow {
  0%   { transform: translate(0, 0); }
  25%  { transform: translate(10%, 5%); }
  50%  { transform: translate(-5%, 10%); }
  75%  { transform: translate(-10%, -5%); }
  100% { transform: translate(0, 0); }
}

/* ---- Gold sparkle dots on seats that won the last trick ---- */
.seat .avatar {
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

/* ---- Smooth transitions for all interactive elements ---- */
.seat, .gt-score-rows .ms-row, .ga-top, .ga-hand {
  transition: border-color 0.3s, box-shadow 0.3s;
}

/* ---- Subtle breathing animation on the felt logo ---- */
.felt-logo {
  animation: feltBreathe 5s ease-in-out infinite;
}
@keyframes feltBreathe {
  0%, 100% { opacity: 0.08; transform: scale(1); }
  50%      { opacity: 0.14; transform: scale(1.02); }
}

/* ---- Glowing border on scores when a trick is won ---- */
.gt-score-rows .ms-row.just-scored {
  animation: scoreFlash 0.8s ease-out;
}
@keyframes scoreFlash {
  0%   { box-shadow: inset 0 0 20px rgba(224,173,78,0.5); }
  100% { box-shadow: none; }
}

/* ---- Bot personality card hover effect ---- */
.bot-card:hover, .bot-card-header:hover {
  box-shadow: 0 0 20px rgba(224,173,78,0.15);
}

/* ---- Room list item hover glow ---- */
.room-list li:hover {
  box-shadow: 0 4px 20px rgba(224,173,78,0.12), var(--shadow-md);
}

/* ---- Played card on felt — subtle entrance glow ---- */
.trick .played-card {
  animation: cardLand 0.3s ease-out;
}
@keyframes cardLand {
  0%   { box-shadow: 0 0 0 0 rgba(224,173,78,0.6); }
  50%  { box-shadow: 0 0 16px 4px rgba(224,173,78,0.3); }
  100% { box-shadow: none; }
}

/* ================================================================
   Shuffle / Cut Ceremony
   ================================================================ */

/* -- Shuffle modal: deck visual -- */
.ceremony-shuffle-wrap { text-align: center; margin-bottom: 16px; }

.ceremony-deck-visual {
  display: flex; justify-content: center; align-items: center;
  padding: 18px 0 12px;
}
.deck-stack { position: relative; width: 80px; height: 110px; }
.deck-card {
  position: absolute; width: 72px; height: 100px;
  border-radius: var(--radius-card-lg); border: 2px solid var(--gold-dim, #8b7843);
  background: linear-gradient(135deg, #2a3a2a 0%, #1a2e1a 100%);
  box-shadow: 1px 1px 4px rgba(0,0,0,.4);
}
.deck-card::after {
  content: "♠"; position: absolute; top: 50%; left: 50%;
  transform: translate(-50%,-50%); font-size: 24px; color: #e0ad4e55;
}
.dc-1 { top: 0; left: 0; }
.dc-2 { top: 2px; left: 2px; }
.dc-3 { top: 4px; left: 4px; }
.dc-4 { top: 6px; left: 6px; }
.dc-top { top: 8px; left: 8px; background: linear-gradient(135deg, #35472e 0%, #1e3018 100%);
  border-color: var(--gold, #e0ad4e); }
.dc-top::after { content: "♠♥♣♦"; font-size: 14px; letter-spacing: 2px; color: #e0ad4eaa; }

/* -- Ceremony button rows -- */
.ceremony-btn-row {
  display: grid; gap: 10px; margin-bottom: 12px;
}
.ceremony-btn-row.ceremony-cut-row {
  grid-template-columns: 1fr 1fr 1fr;
}
.ceremony-shuffle-btn {
  font-size: 1rem !important;
  padding: 12px 10px !important;
  display: flex !important;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  line-height: 1.15;
  text-align: center;
}
.ceremony-shuffle-btn .shuffle-level-label {
  font-weight: 800;
  font-size: 0.98rem;
}
.ceremony-shuffle-btn .shuffle-level-sub {
  font-size: 0.72rem;
  font-weight: 500;
  opacity: 0.78;
  letter-spacing: 0.3px;
}
/* 2×2 grid for shuffle buttons */
.ceremony-shuffle-row,
.ceremony-btn-row:not(.ceremony-cut-row) {
  grid-template-columns: 1fr 1fr;
}
.ceremony-cut-btn { font-size: .9rem !important; padding: 14px 8px !important; }
.ceremony-cut-btn .cut-icon { display: block; font-size: 1.2rem; margin-bottom: 2px; }
.ceremony-pass-btn { width: 100%; margin-top: 4px; }

/* -- Shuffle animation on felt -- */
.shuffle-anim {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%,-50%); z-index: 20;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  pointer-events: none;
}
.shuffle-deck-wrap {
  position: relative; width: 120px; height: 80px;
}
.shuffle-half {
  position: absolute; top: 0; width: 54px; height: 76px;
  border-radius: var(--radius); border: 2px solid var(--gold-dim, #8b7843);
  background: linear-gradient(135deg, #2a3a2a, #1a2e1a);
  transition: transform 0.25s ease-in-out;
}
.shuffle-half-left  { left: 4px; }
.shuffle-half-right { right: 4px; }
.shuffling .shuffle-half-left  { transform: translateX(-18px) rotate(-8deg); }
.shuffling .shuffle-half-right { transform: translateX(18px) rotate(8deg); }

/* Per-level animation intensity — the halves spread further and tilt more
   as the chosen shuffle gets more aggressive. Matches the server-side
   ShuffleMode so the visual and the actual randomness line up. */
.shuffle-level-light.shuffling   .shuffle-half-left  { transform: translateX(-10px) rotate(-4deg); }
.shuffle-level-light.shuffling   .shuffle-half-right { transform: translateX(10px)  rotate(4deg);  }
.shuffle-level-average.shuffling .shuffle-half-left  { transform: translateX(-22px) rotate(-10deg); }
.shuffle-level-average.shuffling .shuffle-half-right { transform: translateX(22px)  rotate(10deg);  }
.shuffle-level-extreme.shuffling .shuffle-half-left  { transform: translateX(-34px) rotate(-16deg) translateY(-4px); }
.shuffle-level-extreme.shuffling .shuffle-half-right { transform: translateX(34px)  rotate(16deg)  translateY(-4px); }

/* The "none" variant doesn't animate — just the deck and label briefly. */
.shuffle-level-none .shuffle-half { transition: none; opacity: 0.85; }

/* Transition speed scales with intensity so extreme feels snappier. */
.shuffle-level-light   .shuffle-half { transition: transform 0.30s ease-in-out; }
.shuffle-level-average .shuffle-half { transition: transform 0.24s ease-in-out; }
.shuffle-level-extreme .shuffle-half { transition: transform 0.18s ease-in-out; }

.shuffle-label {
  font-size: 14px; color: var(--gold, #e0ad4e); font-weight: 700;
  text-shadow: 0 1px 4px #000; letter-spacing: 1px;
}

/* -- Cut animation on felt -- */
.cut-anim {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%,-50%); z-index: 20;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  pointer-events: none;
}
.cut-deck-wrap {
  position: relative; width: 72px; height: 100px;
}
.cut-top, .cut-bottom {
  position: absolute; left: 0; width: 100%; height: 46px;
  border-radius: var(--radius); border: 2px solid var(--gold-dim, #8b7843);
  background: linear-gradient(135deg, #2a3a2a, #1a2e1a);
  transition: transform 0.4s ease;
}
.cut-top    { top: 0; }
.cut-bottom { bottom: 0; }
.cutting .cut-top    { transform: translateY(-12px); }
.cutting .cut-bottom { transform: translateY(12px); }
.cut-label {
  font-size: 14px; color: var(--gold, #e0ad4e); font-weight: 700;
  text-shadow: 0 1px 4px #000; letter-spacing: 1px;
}

/* ═══════════ Kufi Noir — shuffle & cut ceremonies ═══════════ */
/* Sharp saffron kicker + uppercase Kufi title (scoped to the ceremonies) */
.table-panel--shuffle .table-panel__kicker,
.table-panel--cut .table-panel__kicker {
  background: var(--accent);
  color: #111;
  border-radius: 0;
  font-family: var(--font-ui);
  letter-spacing: 1.6px;
}
.table-panel--shuffle .table-panel__title,
.table-panel--cut .table-panel__title {
  font-family: var(--font-ui);
  text-transform: uppercase;
  letter-spacing: 1px;
}
/* Deck visual → classic cream cards with a saffron-edged top card */
.table-panel--shuffle .deck-card {
  border-radius: var(--radius-card-lg);
  border: 1.5px solid #1A1A1A;
  background: #F5F1E8;
  box-shadow: 2px 2px 0 rgba(0,0,0,.35);
}
.table-panel--shuffle .deck-card::after { color: rgba(200,150,62,.4); }
.table-panel--shuffle .dc-top {
  background: #F5F1E8;
  border-color: var(--accent);
  box-shadow: 3px 3px 0 rgba(0,0,0,.4);
}
.table-panel--shuffle .dc-top::after {
  content: "♠";
  color: var(--accent);
  font-size: 20px;
  letter-spacing: 0;
}
/* Option tiles → dark sharp Kufi tiles (override the global .primary fill) */
.table-panel--shuffle .ceremony-shuffle-btn.primary,
.table-panel--cut .ceremony-cut-btn.primary {
  background: #1D1D1D !important;
  color: var(--ink) !important;
  border: 2px solid #2A2A2A !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  transition: border-color .12s ease, box-shadow .12s ease, transform .12s ease;
}
.table-panel--shuffle .ceremony-shuffle-btn.primary:hover,
.table-panel--cut .ceremony-cut-btn.primary:hover {
  border-color: var(--accent) !important;
  box-shadow: 4px 4px 0 #000 !important;
  transform: translate(-1px, -1px);
}
.table-panel--shuffle .shuffle-level-label {
  font-family: var(--font-ui);
  text-transform: uppercase;
  letter-spacing: .5px;
  color: var(--ink);
}
.table-panel--shuffle .shuffle-level-sub { color: var(--ink-mute); }
/* Intensity pips — saffron bars showing shuffle strength */
.shuffle-pips { display: flex; gap: 4px; margin-top: 6px; justify-content: center; }
.shuffle-pips i { width: 14px; height: 4px; background: #3A3A3A; display: block; }
.shuffle-pips i.on { background: var(--accent); }
/* Cut tiles — saffron directional icon, uppercase Kufi label */
.table-panel--cut .ceremony-cut-btn {
  font-family: var(--font-ui);
  text-transform: uppercase;
  letter-spacing: .5px;
}
.table-panel--cut .ceremony-cut-btn .cut-icon { color: var(--accent); }
/* Pass — hollow outline; accent stays reserved for the cut choices */
.table-panel--cut .ceremony-pass-btn {
  background: transparent !important;
  color: var(--ink-mute) !important;
  border: 1px solid #2A2A2A !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  text-transform: uppercase;
  letter-spacing: 1px;
}
.table-panel--cut .ceremony-pass-btn small { color: var(--ink-mute); opacity: .7; }
.table-panel--cut .ceremony-pass-btn:hover {
  border-color: var(--ink-mute) !important;
  color: var(--ink) !important;
}
/* On-felt ceremony motion → cream cards + saffron labels (Kufi retint) */
.shuffle-half, .cut-top, .cut-bottom {
  border-color: #1A1A1A !important;
  background: #F5F1E8 !important;
}
.shuffle-label, .cut-label { color: var(--accent) !important; }

/* ═══════════ Kufi Noir — bot-partner picker ═══════════ */
.table-panel--bot-setup .table-panel__kicker {
  background: var(--accent);
  color: #111;
  border-radius: 0;
  font-family: var(--font-ui);
  letter-spacing: 1.6px;
}
.table-panel--bot-setup .table-panel__title {
  font-family: var(--font-ui);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-size: 15px;
}
/* Compact header so the 6-bot list keeps room on short felts. */
.table-panel--bot-setup .table-panel__header { padding: 0 0 4px; }
.table-panel--bot-setup .table-panel__kicker { margin-bottom: 4px; }
.table-panel--bot-setup .table-panel__sub {
  font-size: 11px;
  line-height: 1.3;
  margin-top: 3px;
}
/* Character tiles → flat dark, sharp, all descriptions visible for comparison */
.table-panel--bot-setup .bot-card-accordion {
  border: 2px solid #2A2A2A;
  border-radius: 0;
  background: #1D1D1D;
  transition: border-color .12s ease, box-shadow .12s ease, transform .12s ease;
  cursor: pointer;
}
.table-panel--bot-setup .bot-card-accordion:hover {
  border-color: #3A3A3A;
  background: #1D1D1D;
}
.table-panel--bot-setup .bot-card-header,
.table-panel--bot-setup .bot-card-header:hover {
  background: transparent;
  padding: 8px 10px;
  gap: 10px;
}
/* Signature Kufi avatar tile: cream square, character mark, colored
   bottom-border. The underline color is set inline per personality
   (green → red by aggression). */
.table-panel--bot-setup .bot-card-header .bot-icon {
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #F5F1E8;
  border: none;
  border-bottom: 4px solid #888;
  border-radius: 0;
  font-size: 19px;
  flex-shrink: 0;
}
.table-panel--bot-setup .bot-name {
  font-family: var(--font-ui);
  text-transform: uppercase;
  letter-spacing: .5px;
  font-size: 13px;
  color: var(--ink);
  font-weight: 700;
  flex: 1;
}
.table-panel--bot-setup .bot-checkmark {
  margin-left: auto;
  color: var(--accent);
  font-weight: 800;
  display: none;
}
/* Accordion: collapsed rows stay one line; only the selected card
   expands to reveal its description. Keeps all 6 bots visible on a
   short felt without the scroll/crowd the always-open list caused. */
.table-panel--bot-setup .bot-card-body {
  display: none;
  background: transparent;
  padding: 0 10px 8px 48px;    /* align under the name, past the 30px glyph */
}
.table-panel--bot-setup .bot-card-accordion.bot-card-selected .bot-card-body {
  display: block;
}
.table-panel--bot-setup .bot-desc { color: var(--ink-mute); font-size: 11px; line-height: 1.35; }
/* Compact aggression bar shown inline in every header for at-a-glance
   comparison, so collapsing the description costs no information. */
.table-panel--bot-setup .bot-aggr-mini {
  position: relative;
  flex: 0 0 46px;
  height: 4px;
  background: #2A2A2A;
  display: block;
  margin-left: 4px;
}
.table-panel--bot-setup .bot-aggr-fill {
  position: absolute;
  left: 0; top: 0;
  height: 100%;
  background: var(--accent);
  display: block;
}
/* Selected → saffron border + block shadow + saffron glyph frame + checkmark */
.table-panel--bot-setup .bot-card-accordion.bot-card-selected {
  border-color: var(--accent);
  background: #1D1D1D;
  box-shadow: 4px 4px 0 #000;
}
.table-panel--bot-setup .bot-card-accordion.bot-card-selected .bot-card-header { background: transparent; }
.table-panel--bot-setup .bot-card-accordion.bot-card-selected .bot-icon { box-shadow: 0 0 0 2px var(--accent); }
.table-panel--bot-setup .bot-card-accordion.bot-card-selected .bot-checkmark { display: inline; }
.table-panel--bot-setup .table-panel__actions .primary {
  text-transform: uppercase;
  letter-spacing: 1px;
}

/* ═══════════ Kufi Noir — seat-picker (waiting room) ═══════════ */
.table-panel--waiting .table-panel__kicker {
  background: var(--accent);
  color: #111;
  border-radius: 0;
  font-family: var(--font-ui);
  letter-spacing: 1.6px;
}
.table-panel--waiting .table-panel__title {
  font-family: var(--font-ui);
  text-transform: uppercase;
  letter-spacing: 1px;
}
.table-panel--waiting .table-panel__invite {
  background: #111;
  border: 1px solid #2A2A2A;
  border-radius: 0;
}
.table-panel--waiting .tp-invite-label { color: var(--ink-mute); }
.table-panel--waiting .tp-invite-code { color: var(--accent); }
.table-panel--waiting .table-panel__actions .primary {
  text-transform: uppercase;
  letter-spacing: 1px;
}
/* Legacy seat-picker felt + seats → dark Kufi, square, saffron states */
.seat-picker .sp-table-felt {
  background: #1D1D1D;
  border: 1px solid #2A2A2A;
  border-radius: 0;
  box-shadow: inset 0 2px 16px rgba(0,0,0,.5);
}
.sp-felt-logo { color: var(--accent); opacity: .22; }
.sp-seat { border-radius: 0; }
.sp-seat:hover, .sp-seat.sp-you {
  border-color: var(--accent);
  background: rgba(245,176,39,.12);
}
.sp-seat .sp-avatar, .sp-seat .sp-avatar img { border-radius: 0; }
.sp-seat.sp-you .sp-avatar {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(245,176,39,.3);
}
.sp-seat.sp-you .sp-label { color: var(--accent); }
.sp-seat .sp-team-tag { border-radius: 0; }
.sp-team-0 { background: rgba(74,125,214,.22); color: var(--team-blue); }
.sp-team-1 { background: rgba(63,174,110,.22); color: var(--team-green); }

/* ═══════════ Kufi Noir — auto-play picker ═══════════ */
.table-panel--autoplay .table-panel__kicker {
  background: var(--accent);
  color: #111;
  border-radius: 0;
  font-family: var(--font-ui);
  letter-spacing: 1.6px;
}
.table-panel--autoplay .table-panel__title {
  font-family: var(--font-ui);
  text-transform: uppercase;
  letter-spacing: 1px;
}
.table-panel--autoplay .ap-picker-btn {
  background: #1D1D1D !important;
  border: 2px solid #2A2A2A !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  color: var(--ink) !important;
  transition: border-color .12s ease, box-shadow .12s ease, transform .12s ease;
}
.table-panel--autoplay .ap-picker-btn:hover {
  border-color: var(--accent) !important;
  box-shadow: 4px 4px 0 #000 !important;
  transform: translate(-1px, -1px);
}
.table-panel--autoplay .ap-picker-icon {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #F5F1E8;               /* cream Kufi avatar tile */
  border: none;
  border-bottom: 4px solid #888;    /* overridden inline per personality */
  border-radius: 0;
  font-size: 22px;
  margin-bottom: 3px;
}
.table-panel--autoplay .ap-picker-name {
  font-family: var(--font-ui);
  text-transform: uppercase;
  letter-spacing: .5px;
  color: var(--ink);
}
.table-panel--autoplay .ap-picker-desc { color: var(--ink-mute); }
.table-panel--autoplay .table-panel__actions .ghost {
  background: transparent !important;
  color: var(--ink-mute) !important;
  border: 1px solid #2A2A2A !important;
  border-radius: 0 !important;
  text-transform: uppercase;
  letter-spacing: 1px;
}
.table-panel--autoplay .table-panel__actions .ghost:hover {
  border-color: var(--ink-mute) !important;
  color: var(--ink) !important;
}

/* ═══════════ Kufi Noir — auth pages (login / register / reset) ═══════════
   Scoped under .auth-shell so these beat each page's inline <style> block
   without !important (higher specificity wins regardless of source order). */
.auth-shell { background: #111; }
.auth-shell .auth-card.card-panel {
  background: #1D1D1D;
  border: 1px solid #2A2A2A;
  border-radius: 0;
  box-shadow: 8px 8px 0 #000;
  padding: 28px 26px;
  position: relative;
}
/* Arabic wordmark crowns every auth card — brand presence at the front door.
   Real element (not ::before) because .card-panel already owns ::before. */
.auth-shell .auth-card .auth-brand {
  display: block;
  font-family: var(--font-ui);
  font-size: 34px;
  font-weight: 800;
  color: var(--accent);
  letter-spacing: 2px;
  line-height: 1;
  margin-bottom: 6px;
  position: relative;
  z-index: 1;
}
.auth-shell .auth-card h2 {
  font-family: var(--font-ui);
  text-transform: uppercase;
  letter-spacing: .5px;
  font-size: 20px;
  margin: 6px 0 2px;
  color: var(--ink);
}
.auth-shell .auth-card .muted { color: var(--ink-mute); font-size: 13px; }
.auth-shell .auth-card label {
  color: var(--ink-mute);
  text-transform: uppercase;
  letter-spacing: .5px;
  font-size: 11px;
  font-weight: 700;
  opacity: 1;
}
.auth-shell .auth-card label.remember,
.auth-shell .auth-card label.tos {
  text-transform: none;
  letter-spacing: 0;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-soft);
}
.auth-shell .auth-card input[type=text],
.auth-shell .auth-card input[type=email],
.auth-shell .auth-card input[type=password] {
  border-radius: 0;
  border: 1.5px solid #2A2A2A;
  background: #111;
  color: var(--ink);
  padding: 11px 12px;
}
.auth-shell .auth-card input[type=text]:focus,
.auth-shell .auth-card input[type=email]:focus,
.auth-shell .auth-card input[type=password]:focus {
  border-color: var(--accent);
  outline: none;
}
.auth-shell .auth-card .primary {
  text-transform: uppercase;
  letter-spacing: 1px;
}
.auth-shell .auth-card .muted-link { color: var(--ink-mute); }
.auth-shell .auth-card .muted-link:hover { color: var(--accent); }
.auth-shell .auth-card a { color: var(--accent); }
/* Flash → sharp, team-colored left rule */
.auth-shell .flash { border-radius: 0; }
.auth-shell .flash-ok {
  background: rgba(63,174,110,.14);
  color: var(--green);
  border-left: 3px solid var(--green);
}
.auth-shell .flash-err {
  background: rgba(224,71,47,.14);
  color: var(--red);
  border-left: 3px solid var(--red);
}

/* ═══════════ Kufi Noir — profile ═══════════ */
.profile-shell { color: var(--ink); }
.profile-shell .card-panel.profile-card {
  background: #1D1D1D;
  border: 1px solid #2A2A2A;
  border-radius: 0;
  box-shadow: 6px 6px 0 #000;
  padding: 18px;
}
.profile-shell h2,
.profile-shell h3 {
  font-family: var(--font-ui);
  text-transform: uppercase;
  letter-spacing: .5px;
  color: var(--ink);
}
.profile-shell .avatar {
  border-radius: 0;
  border: 2px solid var(--accent);
  background: #111;
}
.profile-shell input:not([type=file]) {
  border-radius: 0;
  border: 1.5px solid #2A2A2A;
  background: #111;
  color: var(--ink);
  padding: 11px 12px;
}
.profile-shell input:not([type=file]):focus {
  border-color: var(--accent);
  outline: none;
}
.profile-shell .stat {
  background: #111;
  border: 1.5px solid #2A2A2A;
  border-radius: 0;
  padding: 12px;
}
.profile-shell .stat .v {
  color: var(--accent);
  font-family: var(--font-ui);
  font-weight: 800;
}
.profile-shell .stat .l {
  color: var(--ink-mute);
  text-transform: uppercase;
  letter-spacing: .5px;
  font-size: 10px;
}
.profile-shell .primary { text-transform: uppercase; letter-spacing: 1px; }
.profile-shell .muted { color: var(--ink-mute); }
.profile-shell .flash { border-radius: 0; }
.profile-shell .flash-ok  { background: rgba(63,174,110,.14); color: var(--green); border-left: 3px solid var(--green); }
.profile-shell .flash-err { background: rgba(224,71,47,.14); color: var(--red);   border-left: 3px solid var(--red); }

/* ═══════════ Kufi Noir — match history ═══════════ */
body .history-wrap { color: var(--ink); font-family: var(--font-ui); }
.history-wrap h1 {
  font-family: var(--font-ui);
  text-transform: uppercase;
  letter-spacing: 1px;
}
.history-wrap .history-item {
  background: #1D1D1D;
  border: 1px solid #2A2A2A;
  border-radius: 0;
  box-shadow: 4px 4px 0 #000;
}
.history-wrap .history-item header strong {
  color: var(--accent);
  font-family: var(--font-ui);
}
.history-wrap .badge {
  border-radius: 0;
  text-transform: uppercase;
  letter-spacing: .5px;
  font-weight: 700;
}
.history-wrap .badge-win  { background: rgba(63,174,110,.16); color: var(--green); border-left: 3px solid var(--green); }
.history-wrap .badge-loss { background: rgba(224,71,47,.16); color: var(--red);   border-left: 3px solid var(--red); }
.history-wrap .badge-prog { background: #2A2A2A; color: var(--ink-mute); }
.history-wrap .round { border-bottom-color: #2A2A2A; }
.history-wrap .play { border-radius: 0; background: #111; color: var(--ink-soft); }
.history-wrap .play.winner { background: rgba(63,174,110,.2); color: var(--green); }
.history-wrap summary { color: var(--ink-mute); }
.history-wrap .empty { color: var(--ink-mute); }
.history-wrap .pager button {
  background: #1D1D1D;
  border: 1.5px solid #2A2A2A;
  border-radius: 0;
  color: var(--ink);
  text-transform: uppercase;
  letter-spacing: .5px;
}
.history-wrap .pager button:hover:not([disabled]) { border-color: var(--accent); }
/* Top-action links (Back to lobby / game) — kill the default purple */
.profile-shell .top-actions a,
.history-wrap .top-actions a {
  color: var(--ink-mute);
  text-decoration: none;
  text-transform: uppercase;
  letter-spacing: .5px;
  font-size: 12px;
  font-weight: 700;
}
.profile-shell .top-actions a:hover,
.history-wrap .top-actions a:hover { color: var(--accent); }

/* ═══════════ Kufi Noir — top-bar wallet chip ═══════════ */
.wallet-chip-top {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid #2A2A2A;
  background: #1D1D1D;
  border-radius: 0;
  text-decoration: none;
  font-size: 12px;
  color: var(--ink-mute);
  font-weight: 700;
}
.wallet-chip-top:hover { border-color: var(--accent); }
.wallet-chip-top .coin {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #F5D07A, var(--accent) 60%, #8B6B2A);
  border: 1px solid #8B6B2A;
  flex: 0 0 auto;
}
.wallet-chip-top b { color: var(--accent); font-weight: 800; }
.wallet-chip-top .wc-unit { font-size: 9px; letter-spacing: 1px; color: var(--ink-mute); }

/* ═══════════ Kufi Noir — store polish (tokens already carry most of it) ═══════════ */
.store-shell h1,
.store-shell .store-card h3 {
  font-family: var(--font-ui);
  text-transform: uppercase;
  letter-spacing: .5px;
}
.store-shell .cat-head { font-family: var(--font-ui); }
.store-shell .shop-card { border-radius: 0; }
.store-shell .shop-card:hover { border-color: var(--accent); }
.store-shell .pkg-points,
.store-shell .item-price { color: var(--accent); }
.store-shell .pkg-bonus {
  border-radius: 0;
  background: rgba(63,174,110,.16);
  color: var(--green);
  border-color: rgba(63,174,110,.4);
}
.store-shell .owned-badge {
  border-radius: 0;
  background: var(--accent);
  color: #111;
  box-shadow: 3px 3px 0 #000;
}
.store-shell .wallet-chip {
  border-radius: 0;
  background: #1D1D1D;
  border-color: #2A2A2A;
}
.store-shell .wallet-chip b { color: var(--accent); }
.store-shell .top-actions a.ghost { border-radius: 0; }
.store-shell .top-actions a.ghost:hover { border-color: var(--accent); background: rgba(245,176,39,.12); }
.store-shell .store-toast { border-radius: 0; }

/* ═══════════ Bot play narration — "why" cue over a seat ═══════════ */
.reason-bubble {
  position: fixed;
  transform: translate(-50%, -100%);
  background: #111;
  color: var(--accent);
  border: 1px solid #2A2A2A;
  border-left: 2px solid var(--accent);
  font-family: var(--font-ui);
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .5px;
  padding: 3px 8px;
  white-space: nowrap;
  z-index: 900;
  pointer-events: none;
  box-shadow: 2px 2px 0 #000;
  animation: reasonPop .18s ease-out;
}
@keyframes reasonPop {
  from { opacity: 0; transform: translate(-50%, -84%); }
  to   { opacity: 1; transform: translate(-50%, -100%); }
}

/* ========== Auto-Play Toggle Button ========== */
.ap-toggle {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 2px 8px;
  border-radius: var(--radius);
  font-size: 11px;
  font-weight: 700;
  border: 1.5px solid;
  cursor: pointer;
  transition: all 0.2s ease;
  white-space: nowrap;
  line-height: 1.2;
  z-index: 2;
}
.ap-toggle .ap-icon { font-size: 13px; }
.ap-toggle .ap-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; }
.ap-toggle.ap-off {
  background: rgba(255,255,255,0.08);
  border-color: rgba(255,255,255,0.2);
  color: rgba(255,255,255,0.6);
}
.ap-toggle.ap-off:hover {
  background: rgba(255,255,255,0.15);
  border-color: rgba(255,255,255,0.4);
  color: #fff;
}
.ap-toggle.ap-on {
  background: rgba(224, 173, 78, 0.2);
  border-color: var(--yellow);
  color: var(--yellow);
  animation: apPulse 2s ease-in-out infinite;
}
.ap-toggle.ap-on:hover {
  background: rgba(224, 173, 78, 0.35);
}
@keyframes apPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(224, 173, 78, 0.3); }
  50%      { box-shadow: 0 0 0 4px rgba(224, 173, 78, 0); }
}

/* While auto-play controls YOUR seat: purple-tinted ring on the avatar and
   a purple AUTO chip, so a glance says "a bot is driving this seat". Both
   disappear the moment auto-play is switched OFF (renderSeats drops the
   class). Declared after .seat.turn so the purple wins if both apply. */
.seat.seat-auto-on .avatar {
  border-color: #A78BFA !important;
  box-shadow: 0 0 0 3px #8B5CF6, 0 0 16px rgba(139, 92, 246, 0.55) !important;
}
.seat.seat-auto-on .ap-toggle.ap-on {
  border-color: #A78BFA;
  color: #DDD3FE;
  background: rgba(139, 92, 246, 0.22);
}

/* ========== Auto-Play Picker Grid (in action modal) ========== */
.ap-picker-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  padding: 8px 0;
}
.ap-picker-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 12px 8px;
  background: rgba(255,255,255,0.05);
  border: 2px solid var(--border-soft);
  border-radius: var(--radius);
  cursor: pointer;
  transition: all 0.2s;
  color: var(--ink);
}
.ap-picker-btn:hover {
  border-color: var(--yellow);
  background: rgba(224, 173, 78, 0.1);
  transform: translateY(-2px);
}
.ap-picker-icon { font-size: 28px; }
.ap-picker-name { font-size: 13px; font-weight: 700; color: var(--ink); }
.ap-picker-desc { font-size: 10px; color: var(--ink-mute); }

/* ========== Auto-Play Toast ==========
   Anchored bottom-center but using left/right insets (not translateX)
   so the toast can wrap naturally inside the viewport instead of
   clipping on narrow screens (Galaxy Fold 344, Android Small 360).
   Lifted above the .round-status pill via --status-bar-height. */
.ap-toast {
  position: fixed;
  left: max(12px, env(safe-area-inset-left));
  right: max(12px, env(safe-area-inset-right));
  bottom: calc(var(--status-bar-height, 48px) + 12px + env(safe-area-inset-bottom));
  margin-inline: auto;
  max-width: 520px;
  transform: translateY(120%);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 20px;
  background: rgba(224, 173, 78, 0.95);
  color: #1C1C22;
  border-radius: var(--radius);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.35;
  white-space: normal;
  text-wrap: balance;
  box-shadow: 0 8px 30px rgba(0,0,0,0.4);
  z-index: var(--z-toast, 1100);
  transition: transform 0.3s ease;
}
.ap-toast-show { transform: translateY(0); }
.ap-toast-icon { font-size: 22px; flex: 0 0 auto; }
.ap-toast-msg  { flex: 1 1 auto; min-width: 0; }

/* Compact pill sitting in the gap BELOW the play zone (not over the cards):
   it just confirms which card auto-play threw, so it must never obscure the
   table. Anchored low-centre, small, and out of the trick square. */
.ap-card-toast {
  position: fixed;
  top: 62%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.9);
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  background: rgba(255, 255, 255, 0.96);
  border: 1.5px solid #e0ad4e;
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0,0,0,0.45);
  z-index: 10000;
  opacity: 0;
  transition: opacity 200ms ease, transform 200ms ease;
  pointer-events: none;
}
.ap-card-toast-show { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.ap-card-toast-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1px;
  color: #7a5a1d;
}
.ap-card-toast-card {
  font-size: 20px;
  font-weight: 800;
  font-family: var(--font-ui);
}

/* Mobile adjustments for auto-play */
@media (max-width: 720px) {
  .ap-toggle { padding: 1px 5px; font-size: 10px; }
  .ap-toggle .ap-icon { font-size: 11px; }
  .ap-toggle .ap-label { font-size: 8px; }
  .ap-picker-grid { grid-template-columns: repeat(2, 1fr); }
  .ap-toast { font-size: 13px; padding: 10px 14px; gap: 8px; }
  .ap-toast-icon { font-size: 20px; }
}

/* ============================================================
   Trick resolution (client-owned timeline). Applied to the
   just-completed trick: the 4 cards REST, then HOLD (winner card
   gets a gold outline, the winner's seat avatar pulses, a "+1"
   pops near their tricks-won chip), then all 4 cards SWEEP to the
   winner's side and HOLD there ~1.5s, then the felt clears.
   Durations are JS constants (TRICK_HOLD_FULL_MS / TRICK_SWEEP_MS
   / TRICK_WINNER_HOLD_MS) near showTrickResolution in app.js.
   ============================================================ */

/* Winning card highlight — gold outline + lift */
.played-card.trick-card--winner {
  outline: 2px solid var(--gold-accent, #E0AD4E);
  outline-offset: 2px;
  box-shadow: 0 0 12px rgba(224, 173, 78, 0.6);
  transition: outline-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;
  z-index: 2;
}
/* Lift is applied as a CSS variable so it composes with the felt-position
   transforms set inline by animateCardFlyIn without overwriting them. */
.played-card.trick-card--winner { transform: translateY(-2px); }

/* Winner-side "last trick": the winning card is LEFT on the winner's seat
   grid-cell after the losers clear (see showTrickResolution), as the single
   last-trick signal (the neutral centred strip is retired). Steady gold ring
   + small "LAST" tag so it reads as settled history that belongs to whoever
   sits on that side — not an active play. */
.played-card.trick-card--last {
  outline: 2px solid var(--gold-accent, #E0AD4E);
  outline-offset: 2px;
  box-shadow: 0 0 10px rgba(224, 173, 78, 0.5);
  transform: translateY(-1px) scale(0.94);
  opacity: 0.96;
  z-index: 2;
}
.played-card.trick-card--last::after {
  content: "LAST";
  position: absolute;
  top: -9px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 8px;
  font-weight: 800;
  letter-spacing: 0.6px;
  line-height: 1;
  padding: 2px 5px;
  border-radius: var(--radius);
  background: var(--gold-accent, #E0AD4E);
  color: #2a2206;
  box-shadow: 0 1px 3px rgba(0,0,0,0.5);
  pointer-events: none;
}

/* Neutral centred "Last trick" strip is retired in favour of the winner-side
   held card above. Force it hidden at every viewport (JS also keeps it empty). */
.last-trick-tray { display: none !important; }

/* Small winner-side "last trick" review (non-blocking; see renderLastTrickMini).
   A tiny 4-card snapshot of the just-finished trick, parked just inside the felt
   edge on the WINNER's side while the next trick plays in the centre. N/S winners
   get a horizontal row (wide top/bottom margin); W/E get a vertical column
   (narrow side margin) so it never overlaps the active centre trick. */
#last-trick-mini.ltm {
  position: fixed;
  z-index: 10000;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 3px 4px;
  border-radius: var(--radius);
  background: rgba(0, 0, 0, 0.30);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
  pointer-events: none;
}
.ltm-label {
  font-size: 7px; font-weight: 800; letter-spacing: 0.8px;
  text-transform: uppercase; color: rgba(255, 255, 255, 0.70);
}
.ltm-cards { display: flex; gap: 2px; }
/* One orientation now: the tray lives in a fixed corner (decision 6), so it no
   longer rotates to suit whichever seat won. */
.ltm-corner .ltm-cards { flex-direction: row; }
.ltm-north .ltm-cards, .ltm-south .ltm-cards { flex-direction: row; }
.ltm-west  .ltm-cards, .ltm-east  .ltm-cards { flex-direction: column; }
.ltm-card {
  width: 16px; height: 23px; border-radius: var(--radius-card-mini);
  background: #f7f5ef; color: #1a1a1a;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  line-height: 1; font-weight: 800;
  border: 0.5px solid rgba(0, 0, 0, 0.25);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}
.ltm-card.red { color: #c8281e; }
.ltm-card .ltm-r { font-size: 9px; }
.ltm-card .ltm-s { font-size: 8px; }
/* The winning card is now the ONLY thing carrying who-won, since the tray no
   longer moves to the winner's side. The prototype fills it gold rather than
   outlining it — bg var(--gold), on-gold ink, a gold-hi border and scale 1.1 —
   which reads at tray size where a 1.5px outline did not. */
.ltm-card.ltm-win {
  background: var(--gold, #C8963E) !important;
  color: var(--on-gold, #14110C) !important;
  border: 2px solid var(--gold-hi, #E3C06A) !important;
  transform: scale(1.1);
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.45);
  z-index: 3;
}
/* Position (fixed, parked on the winner's side) is set inline by renderLastTrickMini. */
.ltm-pop { animation: ltm-pop 200ms ease-out 1; }
@keyframes ltm-pop { 0% { opacity: 0.25; } 100% { opacity: 1; } }

/* Non-winning cards fade and drop slightly during the clear phase */
.played-card.trick-card--loser-clearing {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 250ms ease 0ms, transform 250ms ease 0ms;
}

/* Swept pile: while the 4 cards travel to (and rest on) the winner's side,
   give them a gold glow so the gathered trick reads as "won by this side".
   The transform is set inline by showTrickResolution (it must beat the CSS
   cascade — see the inline-styles regression trap), so this rule only styles
   the glow/elevation, never the transform. */
.played-card.trick-card--swept {
  box-shadow: 0 0 0 1.5px rgba(224, 173, 78, 0.7), 0 12px 26px rgba(0, 0, 0, 0.45);
  z-index: 3;
}

/* Parked "last trick" review: after the sweep the gathered pile is LEFT on the
   winner's side (showTrickResolution parks instead of deleting) until the next
   trick's first card lands. Non-winning cards recede so the gold-outlined
   winner card clearly reads as who took it; its off-centre, dimmed position
   distinguishes the settled review from an active, centred trick. NB: never set
   `transform` here — the inline sweep transform must survive so the pile stays
   on the winner's side. (No text tag: .card is overflow:hidden, which would
   clip a badge pinned above the card.) */
.played-card.trick-card--parked { transition: opacity 200ms ease; }
.played-card.trick-card--parked:not(.trick-card--winner) {
  opacity: 0.78;
  filter: saturate(0.9);
}

/* Seat avatar pulse (1 cycle, ~500ms). The avatar element here uses
   class .avatar (not .seat-avatar) — see renderSeats(). */
.seat.seat--won-trick .avatar {
  animation: tarneeb-seat-pulse 500ms ease-out 1;
}

@keyframes tarneeb-seat-pulse {
  0%   { transform: scale(1.0); box-shadow: 0 0 0 0 rgba(224, 173, 78, 0.6); }
  40%  { transform: scale(1.08); box-shadow: 0 0 0 8px rgba(224, 173, 78, 0.3); }
  100% { transform: scale(1.0); box-shadow: 0 0 0 12px rgba(224, 173, 78, 0); }
}

/* "+1" badge that pops near the winner's tricks-won chip. The chip is
   given position:relative inline at render time so this absolute pin
   anchors against it. */
.trick-plus-one {
  position: absolute;
  left: 100%;
  top: 50%;
  margin-left: 4px;
  transform: translateY(-50%);
  font-weight: 700;
  font-size: 14px;
  color: var(--gold-accent, #E0AD4E);
  text-shadow: 0 1px 2px rgba(0,0,0,0.6);
  pointer-events: none;
  animation: tarneeb-plus-one 750ms ease-out 1 forwards;
}

@keyframes tarneeb-plus-one {
  0%   { opacity: 0; transform: translate(0, calc(-50% + 0px))   scale(0.85); }
  25%  { opacity: 1; transform: translate(0, calc(-50% - 2px))   scale(1.0); }
  75%  { opacity: 1; transform: translate(0, calc(-50% - 6px))   scale(1.0); }
  100% { opacity: 0; transform: translate(0, calc(-50% - 10px))  scale(0.9); }
}

/* Reduced-motion: skip animations, keep static cues */
@media (prefers-reduced-motion: reduce) {
  .played-card.trick-card--winner          { transition: none; }
  .played-card.trick-card--loser-clearing  { transition: opacity 0ms; }
  .seat.seat--won-trick .avatar            { animation: none; }
  .trick-plus-one                          { animation: none; opacity: 1; }
}

/* ---------- Narrow-mobile (≤360px) readability fixes ----------
   Galaxy Fold Cover (344px) and similar slivers were truncating
   "The Companion" in the bot picker and wrapping "Game Lobby" in
   the lobby header because the Refresh label competed for space.
   Single-purpose overrides — no layout change above 360px. */
@media (max-width: 360px) {
  /* Bot picker — keep one-line names, clamp descriptions, tighten
     padding so all 6 cards plus the pinned Start Game button fit. */
  .table-panel--bot-setup .bot-personality-accordion { gap: 6px; }
  .table-panel--bot-setup .bot-card-header {
    padding: 8px 10px;
    gap: 8px;
    min-height: 40px;
  }
  .table-panel--bot-setup .bot-card-header .bot-icon {
    font-size: 17px;
    width: 30px;
    height: 30px;
    flex: 0 0 30px;
  }
  .table-panel--bot-setup .bot-card-header .bot-name {
    font-size: 13px;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .table-panel--bot-setup .bot-card-body { padding: 0 10px 8px; }
  .table-panel--bot-setup .bot-card-body .bot-desc {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    font-size: 12px;
    line-height: 1.3;
  }

  /* Lobby header — Refresh collapses to a 40px icon-only tap target,
     freeing horizontal space so "Game Lobby" stays on one line. */
  .lobby-head h2 {
    font-size: 22px;
    white-space: nowrap;
  }
  .lobby-head #btn-refresh {
    width: 40px;
    height: 40px;
    padding: 0;
    font-size: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
  }
  .lobby-head #btn-refresh::before {
    content: "↻";
    font-size: 20px;
    line-height: 1;
  }
}

/* ---- Bid panel action row at ultra-narrow widths (Galaxy Fold Cover,
   Android Small) ----
   The .pass-row holds Strong Pass + Pass side-by-side via a 1fr 1fr grid.
   In a 344px panel that row overflows — only Strong Pass and a sliver of
   "P" of Pass render. Stack vertically at ≤360px so both buttons fit;
   side-by-side stays intact at ≥361px (iPhone 14 and wider). */
@media (max-width: 360px) {
  .pass-row {
    grid-template-columns: 1fr;
  }
  .pass-row > button {
    min-width: 0;
    width: 100%;
  }
}

/* ---- Status text containment at ultra-narrow widths ----
   Status messages ("Waiting for cutter's decision…", "Waiting for [Seat]
   to bid…") render via msg() into #actions em inside .ga-hand .hand-hint-row.
   That row locks to height:28px with white-space:nowrap + overflow:hidden,
   so on Fold Cover (344px) "Waiting for cutter" was clipping to "Waiting
   for cutte". Relax the row to wrap and grow; shrink the font so two lines
   stay readable without pushing the hand down too far. */
@media (max-width: 360px) {
  .round-status,
  .round-status-left,
  .round-status-right {
    font-size: 0.75rem;
    line-height: 1.15;
  }
  .ga-hand .hand-hint-row {
    height: auto;
    min-height: 28px;
    max-height: none;
    white-space: normal;
    overflow: visible;
    flex-wrap: wrap;
  }
  .ga-hand .actions-row {
    flex-wrap: wrap;
    flex: 1 1 auto;
  }
  .ga-hand .actions-row em {
    white-space: normal;
    text-wrap: balance;
    font-size: 0.75rem;
    line-height: 1.15;
    min-width: 0;
  }
}

/* ---------- Narrowest phones (≤380px): tighten the trick so all 4 played
   cards stay clear of the 4 edge avatars ----------
   At Galaxy Fold Cover (344), Android Small (360) and iPhone SE (375) the
   ≤420px trick (max-width:240, 62×94 cards) let the WEST/EAST cards overlap
   the side avatars (~15px) and the NORTH card overlap the top avatar (the
   tall card overflows its short grid row up into the avatar). Pull the
   diamond in (smaller max-width), give the cards taller rows so they overflow
   less into the avatar zones, widen the side columns, and shrink the cards a
   touch. Measured clear (no card↔card and no card↔avatar overlap) at 344/360/
   375; 390/412 keep the larger ≤420px sizing (already clear there).
   This block is intentionally last so it wins the cascade for ≤380px. */
@media (max-width: 380px) {
  .trick {
    max-width: 200px;
    height: 196px;
    grid-template-columns: 44px 1fr 44px;
    grid-template-rows: 52px 1fr 52px;
  }
  .trick .played-card { width: 52px; height: 74px; border-radius: var(--radius-card-lg); }
  .trick .played-card .pip { font-size: 28px; }
  .trick .played-card .corner .rank { font-size: 14px; }
  .trick .played-card .corner .suit { font-size: 11px; }
  .trick .played-card .corner.tl { top: 3px; left: 4px; }
  .trick .played-card .corner.br { bottom: 3px; right: 4px; }
}

/* ==========================================================================
   THE ACTION RAIL — tokens.json > devices.*.railH
   ==========================================================================
   Four labelled targets in a band of their own, replacing a floating emote
   bubble and a vertical signal tray that were both overlays looking for a safe
   place to sit. The design gives them 58px so they stop competing with the
   cards; every target clears the 44px minimum without arithmetic.

   Labels, not glyphs. The prototype spells SIGNAL / EMOTE / AUTO / LAST — an
   unlabelled icon rail is the same "copy that describes an intention rather
   than a behaviour" problem the redesign has been unpicking. */
.ga-rail { grid-area: rail; }

.rail {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-9);
  width: 100%;
  padding: 0 var(--sp-12);
  box-sizing: border-box;
}

.rail-btn {
  flex: 1 1 0;
  min-width: 0;
  /* a11y.controlPattern, verbatim: "nowrap is required: controls are
     shrink-to-fit and wrap without it, especially in Arabic and under font
     fallback" */
  min-height: var(--tap);
  white-space: nowrap;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--sp-12);

  background: var(--surf2);
  border: 1px solid var(--line2);
  border-radius: var(--radius);
  color: var(--ink2);
  font: 700 11px/1 var(--font-ui);
  letter-spacing: .1em;
  text-transform: uppercase;
  cursor: pointer;
}

.rail-btn:disabled { opacity: .45; cursor: default; }

/* Active/armed reads as gold ink on a gold wash, never as a filled pill —
   the same rule the tab bar follows. */
.rail-btn.is-on {
  background: var(--goldwash);
  border-color: var(--line4);
  color: var(--gold-hi);
}

/* The rail belongs to the table. Outside a live room there is nothing for it
   to act on, so it goes rather than sitting there disabled. */
body:not(.in-game) .ga-rail { display: none; }

/* ==========================================================================
   THE HAND BAND IS CARDS, AND ONLY CARDS
   ==========================================================================
   158px is the card (118) plus its lift corridor and padding — nothing else.
   The hint / deadline / actions row was living inside that band and pushing it
   19px past its own bottom edge, straight onto the action rail.

   In the prototype this text is not under the hand at all: phase and prompt sit
   on the FELT, in the middle ("HIGH BID — NO BID", "YOUR TURN TO BID"). So the
   row moves onto the felt's bottom edge, just above the hand, where it reads as
   part of the table rather than as a caption to your cards.

   Anchored off the band tokens rather than a magic offset — trap 24: "a magic
   pixel offset that clears a dynamic element is a bug on a timer". */
.game-area { position: relative; }

.ga-hand .hand-hint-row {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(var(--handH) + var(--railH));
  z-index: 25;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-9);
  padding: 0 var(--sp-12);
  min-height: 28px;
  pointer-events: none;
  text-align: center;
}
/* The controls inside it are still real targets. */
.ga-hand .hand-hint-row > * { pointer-events: auto; }

/* ==========================================================================
   THE HUD — ported from the gameplay prototype, value for value
   ==========================================================================
   Its markup, its numbers: 34px chips inside a 54px band, hudGap 6 / hudPadX 8
   at phone width (10 / 12 above 500), score numeral 19px on a phone (22 on
   tablet and desktop), and a card-faced trick chip so the two numbers that move
   during a trick read as objects on the table rather than as body text.

   Radius here is 2px on the trump glyph and 3px on the trick chips — both carry
   background:var(--card), so they take the card exception, not the chrome zero. */
.ga-top {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 8px;
  height: var(--hudH);
  box-sizing: border-box;
  border-bottom: 1px solid var(--line);
  background: var(--surf0);
  position: relative;
  z-index: 40;
}
@media (min-width: 500px) {
  .ga-top { gap: 10px; padding: 0 12px; }
}

.hud-contract {
  display: flex;
  align-items: center;
  gap: 6px;
  padding-inline: 8px;
  min-height: 34px;
  box-sizing: border-box;
  border: 1px solid var(--line3);
  background: var(--surf);
  flex-shrink: 1;
  min-width: 0;
  overflow: hidden;
}
@media (min-width: 500px) { .hud-contract { gap: 8px; padding-inline: 10px; } }

.hud-trump {
  display: grid;
  place-items: center;
  min-width: 21px;
  height: 21px;
  padding: 0 3px;
  box-sizing: border-box;
  background: var(--card);
  border: 1px solid var(--card-line);
  border-radius: var(--radius-card-mini);
  font: 800 14px/1 var(--font-ui);
  color: var(--card-ink);
}
.hud-trump.is-red { color: var(--card-red); }

.hud-div { width: 1px; height: 16px; background: var(--line2); flex: none; }

.hud-contract-val {
  flex: none;
  font: 700 14px/1 var(--font-ui);
  color: var(--ink);
  letter-spacing: .01em;
  white-space: nowrap;
}
.hud-progress {
  font: 500 11px/1 var(--font-ui);
  color: var(--ink2);
  letter-spacing: .08em;
  white-space: nowrap;
}

.hud-tricks { display: flex; align-items: center; gap: 6px; flex: none; }

.hud-chip {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  min-height: 34px;
  box-sizing: border-box;
  background: var(--card);
  border: 1px solid var(--card-line);
  border-radius: var(--radius-card-lg);
}
.hud-chip-label {
  font: 700 9px/1 var(--font-ui);
  color: var(--card-gold);
  letter-spacing: .1em;
}
.hud-chip-num {
  font: 900 19px/1 var(--font-ui);
  letter-spacing: -.03em;
  color: var(--card-ink);
  font-variant-numeric: tabular-nums;
}
@media (min-width: 500px) { .hud-chip-num { font-size: 22px; } }

/* Once a contract can no longer be stopped — or has already failed — the chips
   say so with a 2px border. The prototype computes this as `verdict`: green on
   the side that has secured it, red on the other. It is the one moment where a
   trick count stops being a running total and becomes a result. */
.hud-chip.is-won  { border: 2px solid var(--win); }
.hud-chip.is-lost { border: 2px solid var(--loss); }

.hud-spacer { flex: 1; }

.hud-round {
  flex: none;
  white-space: nowrap;
  font: 500 10px/1 var(--font-ui);
  color: var(--ink4);
  letter-spacing: .1em;
}

.hud-more {
  width: 34px;
  height: 34px;
  flex: none;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: none;
  color: var(--ink2);
  font: 700 15px/1 var(--font-ui);
  cursor: pointer;
}
.hud-more.is-on { color: var(--gold-hi); border-color: var(--line4); background: var(--goldwash); }

/* The overflow menu the HUD's ⋯ opens. Anchored to the game area, under the
   HUD's right edge — not position:fixed, for the same reason the seats and the
   toast lane are not (trap 24). */
.overflow-menu {
  position: absolute;
  top: var(--hudH);
  inset-inline-end: 8px;
  z-index: 60;
  min-width: 168px;
  display: flex;
  flex-direction: column;
  background: var(--surf2);
  border: 1px solid var(--line3);
  border-radius: var(--radius);
  box-shadow: 0 14px 34px rgba(0,0,0,.55);
  overflow: hidden;
}
.ovf-item {
  min-height: var(--tap);
  white-space: nowrap;
  display: flex;
  align-items: center;
  padding: 0 13px;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  font: 600 13px/1 var(--font-ui);
  text-align: start;
  cursor: pointer;
}
.ovf-item:last-child { border-bottom: 0; }
.ovf-item:hover { background: var(--surf3); }

/* The pre-game room strip (room name + invite code + Copy link) is GONE from
   the HUD. It was a second line grafted onto a band the design fixes at 54px,
   so it escaped the bar entirely and floated over the felt.

   The invite code has a home in the design: the waiting room's bottom rail,
   next to SHARE and PICK A SEAT. Until that rail is built the code is still
   reachable from the seat panel, which shows ROOM CODE with its own Copy. */
.gt-room-strip { display: none !important; }

/* The HUD is one row and nothing may make it taller. Anything still writing a
   second line into it gets clipped rather than allowed to push the table down —
   the band is the budget. */
.ga-top > * { min-width: 0; }
