/* ---------------------------------------------------------------------------
   RASE BRIDGE — web preview
   Palette and components mirror the native SwiftUI app (Theme.swift, dark).
--------------------------------------------------------------------------- */

:root {
  --bg: #0B1D15;
  --bg-deep: #06110C;
  --surface: #102A1F;
  --surface-raised: #163527;
  --stroke: #2C4A3B;
  --gold: #C6A353;
  --gold-soft: #E6D5A8;
  --text: #F2EFE6;
  --text-2: #9DB2A6;
  --felt-top: #14382A;
  --felt-bottom: #0A2018;
  --positive: #2E7D4F;
  --negative: #B03A34;
  --info: #2F6FBF;
  --ink-on-gold: #14261C;
  --ivory: #FFFEF6;
  /* per-suit shades: distinguishable within each colour family */
  --spade: #000000;      /* jet black */
  --club: #47525A;       /* soft slate charcoal */
  --heart: #EE1C25;      /* vivid scarlet */
  --diamond: #EF6A13;    /* warm orange-red */
  --card-blue: #1F5FBF;
  --card-green: #1E7A3C;
  --card-black: #000000;
  --card-red: #EE1C25;
  --radius: 15px;
}

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

html, body { height: 100%; overscroll-behavior: none; }

body {
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Rounded", "Segoe UI", Roboto, sans-serif;
  background: var(--bg-deep);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}

/* The setting increases reading and decision cues rather than scaling the whole
   interface, which would make a short landscape table overflow. It still has to
   be VISIBLY different — a setting whose effect you cannot see is one you cannot
   choose — so every cue the Text Size sample shows moves with it. */
body.text-large .status-bar { font-size: 14px; }
body.text-large .status-context { font-size: 14px; }
body.text-large .status-turn { font-size: 21px; }
body.text-large .conv-row .ct, body.text-large .system-option .ot { font-size: 17px; }
body.text-large .conv-row .cb, body.text-large .system-option .ob,
body.text-large .section-caption { font-size: 14px; }
body.text-large .rules-text { font-size: 16px; }
body.text-large .rs-input { font-size: 17px; }
body.text-large .rs-snip, body.text-large .rs-status { font-size: 13px; }
body.text-large .seg { font-size: 15px; }
body.text-large .result-title { font-size: 26px; }
body.text-large .result-sub, body.text-large .result-debrief { font-size: 15px; }
body.text-large .tc-pill .l { font-size: 13px; }
body.text-large .tc-pill .v { font-size: 22px; }

/* A live sample of the two lines the table leads with, so choosing a size shows
   what it does instead of describing it. */
.text-sample {
  margin-top: 10px; padding: 10px 12px;
  background: rgba(0,0,0,.25); border: 1px solid rgba(44,74,59,.6); border-radius: 11px;
}
.ts-strip { display: flex; flex-direction: column; gap: 1px; }
.ts-context { font-size: 12px; font-weight: 600; color: rgba(230,213,168,.72); }
.ts-turn { font-size: 17px; font-weight: 800; color: var(--gold); }
.ts-note { margin-top: 7px; font-size: 12px; color: var(--text-2); line-height: 1.4; }
body.text-large .ts-context { font-size: 14px; }
body.text-large .ts-turn { font-size: 21px; }
body.text-large .ts-note { font-size: 14px; }
body.text-large .seat-badge { font-size: 12px; }
body.text-large .bp-profile { font-size: 17px; }
body.text-large .bp-badge, body.text-large .bp-hcp { font-size: 14px; }
body.text-large .ag-head { font-size: 14px; }
body.text-large .ag-cell { font-size: 20px; }
body.text-large .meaning-text, body.text-large .meaning-hint, body.text-large .bp-waiting { font-size: 14px; }
body.text-large .hint-pill { font-size: 13px; }
body.text-large .user-hand .pcard .idx { font-size: calc(var(--uw) * 0.38); }
body.text-large .dummy-cols .pcard .idx { font-size: calc(var(--dw) * 0.46); }

#app { height: 100dvh; display: flex; flex-direction: column; }
.screen { flex: 1; display: flex; flex-direction: column; min-height: 0; }

button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }
button:disabled { cursor: default; }

.brand-font { font-weight: 800; letter-spacing: .3px; }

/* ---------------------------------------------------------------- brand mark

   js/brand.js draws the logo as inline SVG without any fills of its own, so the
   mark is coloured from the palette above and stays in step with the theme.   */

.rase-mark, .rase-word, .rase-club, .rase-tile { display: block; overflow: visible; }
.mk-club { fill: var(--gold); }
.mk-r, .mk-word { fill: var(--ivory); }

/* The gold-ruled tile from the app icon, reused wherever the app signs its name. */
.rase-tile .tile-face {
  fill: var(--surface);
  stroke: var(--gold);
}

/* A hairline with the club set into it — a divider that carries the brand. */
.club-rule { display: flex; align-items: center; gap: 10px; width: min(300px, 70%); }
.club-rule span { flex: 1; height: 1px; background: rgba(198,163,83,.45); }
.club-rule span:first-child { background: linear-gradient(90deg, transparent, rgba(198,163,83,.5)); }
.club-rule span:last-child { background: linear-gradient(90deg, rgba(198,163,83,.5), transparent); }
.club-rule .rase-club { opacity: .85; }

/* The mark standing in for the app's name in a screen title bar. */
.top-brand { width: 36px; display: flex; justify-content: center; opacity: .7; flex-shrink: 0; }

/* Faint mark for empty states and the felt. */
.mark-ghost .mk-r { fill: rgba(255,254,246,.62); }
.mark-ghost .mk-club { fill: rgba(198,163,83,.72); }

/* ---------------------------------------------------------------- top bar */

.top-bar {
  display: flex; align-items: center; gap: 8px;
  padding: max(10px, env(safe-area-inset-top)) 12px 8px;
  flex-shrink: 0;
}
.top-bar h2 { font-size: 17px; font-weight: 700; flex: 1; text-align: center; color: var(--text); }
.top-spacer { width: 36px; }

.icon-btn {
  width: 36px; height: 36px; border-radius: 10px;
  background: rgba(255,255,255,.08);
  color: var(--gold-soft);
  font-size: 17px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.icon-btn:active { background: rgba(255,255,255,.18); }

/* ---------------------------------------------------------------- home */

.home-screen {
  background: linear-gradient(180deg, var(--bg) 0%, var(--bg-deep) 100%);
  align-items: center;
  padding: 24px 20px;
  overflow-y: auto;
  justify-content: flex-start;
  gap: 26px;
}
.home-hero {
  text-align: center; padding-top: 30px;
  display: flex; flex-direction: column; align-items: center; gap: 15px;
}
.home-hero .rase-tile { filter: drop-shadow(0 10px 26px rgba(0,0,0,.5)); }
.home-hero h1 { line-height: 0; }
.tagline { font-size: 14px; font-weight: 600; color: var(--text-2); }
.home-sub { margin-top: 5px; font-size: 12px; color: var(--text-2); opacity: .8; }

.chips-row { display: flex; gap: 10px; width: min(430px, 100%); }
.chip {
  flex: 1; text-align: center; padding: 11px 4px;
  background: var(--surface); border: 1px solid var(--stroke); border-radius: 12px;
}
.chip .chip-value { font-size: 20px; font-weight: 800; color: var(--gold); }
.chip .chip-title { font-size: 10px; font-weight: 600; color: var(--text-2); margin-top: 3px; letter-spacing: .3px; }

.home-buttons { display: flex; flex-direction: column; gap: 12px; width: min(430px, 100%); }

.btn-primary {
  border-radius: 16px; padding: 15px 20px; text-align: center;
  background: linear-gradient(135deg, var(--gold-soft), var(--gold));
  color: var(--ink-on-gold);
  box-shadow: 0 5px 18px rgba(198,163,83,.28);
  transition: transform .06s ease;
}
.btn-primary:active { transform: scale(.98); }
.btn-primary .bt { font-size: 17px; font-weight: 800; }
.btn-primary .bs { font-size: 12px; font-weight: 600; opacity: .72; margin-top: 3px; }

.menu-row {
  display: flex; align-items: center; gap: 13px;
  background: var(--surface); border: 1px solid var(--stroke); border-radius: var(--radius);
  padding: 13px; text-align: left; width: 100%;
  transition: transform .06s ease;
}
.menu-row:active { transform: scale(.985); }
.menu-icon {
  width: 42px; height: 42px; border-radius: 11px; flex-shrink: 0;
  background: rgba(198,163,83,.12); color: var(--gold);
  display: flex; align-items: center; justify-content: center; font-size: 19px;
}
.menu-row .mt { font-size: 15px; font-weight: 700; color: var(--text); }
.menu-row .ms { font-size: 11.5px; font-weight: 500; color: var(--text-2); margin-top: 2px; }
.menu-chevron { margin-left: auto; color: var(--text-2); opacity: .6; font-size: 15px; font-weight: 700; }

.home-footer { font-size: 12px; color: var(--text-2); opacity: .7; }
.home-version { font-size: 11px; color: var(--text-2); opacity: .5; padding-bottom: 18px; margin-top: 4px; letter-spacing: .3px; }

.about-card {
  padding: 22px 14px 10px;
  display: flex; flex-direction: column; align-items: center; gap: 9px; text-align: center;
}
.about-card .about-tag { font-size: 12.5px; color: var(--text-2); font-style: italic; }
.about-card .about-meta { font-size: 11.5px; color: var(--text-2); opacity: .6; }
/* third-party attribution: quieter than the app's own metadata, and allowed to wrap */
.about-card .credit { max-width: 300px; line-height: 1.45; opacity: .45; }
.about-card .club-rule { margin: 3px 0; }

/* ---------------------------------------------------------------- shared cards/panels */

.section-card {
  background: var(--surface); border: 1px solid var(--stroke); border-radius: var(--radius);
  padding: 14px; width: 100%;
}
.section-card h3 { font-size: 15px; font-weight: 800; color: var(--gold); margin-bottom: 8px; }

/* An empty state signs itself with the mark instead of leaving a bare sentence. */
.section-card.brand-empty {
  padding: 24px 16px 20px;
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: 9px;
}
.section-card.brand-empty h3, .section-card.brand-empty .section-caption { margin: 0; }
.section-caption { font-size: 11.5px; color: var(--text-2); margin-bottom: 12px; line-height: 1.5; }

.scroll-body {
  flex: 1;
  min-height: 0;               /* allow the flex child to shrink so overflow scrolls */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 12px 16px calc(28px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: 14px;
  background: var(--bg);
}
/* Keep cards at their natural height so tall content overflows (and scrolls)
   instead of being squished by the flex column. */
.scroll-body > * { flex-shrink: 0; }

/* ---------------------------------------------------------------- settings */

.system-option {
  display: flex; align-items: center; gap: 12px;
  background: rgba(255,255,255,.03);
  border: 1px solid var(--stroke); border-radius: 11px;
  padding: 11px; margin-bottom: 8px; width: 100%; text-align: left;
}
.system-option.selected { background: rgba(198,163,83,.12); border-color: rgba(198,163,83,.6); }
.system-option .ot { font-size: 14px; font-weight: 700; color: var(--text); }
.system-option .ob { font-size: 11.5px; color: var(--text-2); margin-top: 2px; line-height: 1.45; }
.radio-dot {
  margin-left: auto; width: 20px; height: 20px; border-radius: 50%;
  border: 2px solid var(--gold); flex-shrink: 0; position: relative;
}
.system-option.selected .radio-dot::after {
  content: ""; position: absolute; inset: 3px; border-radius: 50%; background: var(--gold);
}

.conv-row {
  display: flex; align-items: center; gap: 12px; padding: 10px 0;
  border-bottom: 1px solid rgba(44,74,59,.5);
}
.conv-row:last-child { border-bottom: none; }
.conv-row .ct { font-size: 13px; font-weight: 600; color: var(--text); }
.conv-row .cb { font-size: 11px; color: var(--text-2); margin-top: 2px; line-height: 1.4; }
.conv-text { flex: 1; }

.segmented {
  display: flex; gap: 4px; margin-top: 9px;
  background: rgba(255,255,255,.05); border-radius: 9px; padding: 3px;
  width: fit-content;
}
.seg {
  padding: 6px 13px; border-radius: 7px;
  font-size: 12px; font-weight: 700; color: var(--text-2);
}
.seg.sel { background: var(--gold); color: var(--ink-on-gold); }

.toggle {
  width: 46px; height: 28px; border-radius: 14px;
  background: #2a4335; position: relative; flex-shrink: 0;
  transition: background .15s ease; cursor: pointer; border: none;
}
.toggle::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 24px; height: 24px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.35);
  transition: left .15s ease;
}
.toggle.on { background: var(--gold); }
.toggle.on::after { left: 20px; }

/* ---------------------------------------------------------------- rules

   Two levels of native <details>: a chapter is the card, and its sections are
   hairline-separated rows inside it. The whole library therefore fits one
   scroll of chapter headings, with any single answer two taps away.          */

.rules-chapter {
  background: var(--surface); border: 1px solid var(--stroke);
  border-radius: var(--radius); overflow: hidden;
}
.rules-chapter > summary {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 34px 14px 16px; cursor: pointer; list-style: none; position: relative;
}
.rules-chapter > summary::-webkit-details-marker { display: none; }
.rules-chapter > summary::after {
  content: "›"; position: absolute; right: 14px; top: 50%;
  font-size: 20px; line-height: 0; color: var(--text-2);
  transform: translateY(-50%) rotate(90deg); transition: transform .15s;
}
.rules-chapter[open] > summary::after { transform: translateY(-50%) rotate(-90deg); }
.rules-chapter[open] > summary { border-bottom: 1px solid var(--stroke); }
.rc-head { flex: 1; min-width: 0; }
.rc-title { display: block; font-size: 15px; font-weight: 800; color: var(--gold); }
.rc-caption { display: block; font-size: 11.5px; line-height: 1.45; color: var(--text-2); margin-top: 2px; }
.rc-count {
  flex: none; min-width: 22px; text-align: center; padding: 2px 6px;
  border-radius: 9px; background: var(--surface-raised); border: 1px solid var(--stroke);
  font-size: 11px; font-weight: 700; color: var(--text-2);
}

.rules-section {
  background: var(--surface); border: 1px solid var(--stroke);
  border-radius: var(--radius); overflow: hidden;
}
/* Inside a chapter the section is a row, not a card of its own. */
.rules-chapter .rules-section {
  border: none; border-radius: 0; background: none;
}
.rules-chapter .rules-section + .rules-section { border-top: 1px solid var(--stroke); }
.rules-section summary {
  padding: 14px 16px; font-weight: 700; font-size: 15px; color: var(--text);
  cursor: pointer; list-style: none; position: relative;
}
.rules-chapter .rules-section summary { font-size: 14px; padding: 12px 34px 12px 16px; }
.rules-section summary::-webkit-details-marker { display: none; }
.rules-section summary::after {
  content: "›"; position: absolute; right: 16px; top: 11px;
  font-size: 20px; color: var(--text-2); transform: rotate(90deg); transition: transform .15s;
}
.rules-chapter .rules-section summary::after { right: 14px; top: 9px; }
.rules-section[open] summary::after { transform: rotate(-90deg); }
.rules-section[open] > summary { color: var(--gold-soft); }
.rules-text { padding: 2px 16px 16px; font-size: 13.5px; line-height: 1.62; color: var(--text-2); }
.rules-text p { margin-bottom: 10px; }
.rules-text strong, .rules-text em { color: var(--text); }
.rules-text ul { margin: 0 0 10px 18px; }
.rules-text li { margin-bottom: 7px; }
.rules-table { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 6px 0 12px; }
.rules-text table { border-collapse: collapse; margin: 6px 0 12px; width: 100%; }
.rules-table table { margin: 0; min-width: 100%; }
.rules-text th, .rules-text td { border: 1px solid var(--stroke); padding: 6px 10px; font-size: 12.5px; text-align: left; }
.rules-text th { background: var(--surface-raised); color: var(--gold-soft); }

/* Search sits between the bar and the scroll, so it stays put while results
   move under it. Filtering is done in the DOM by ui.js rather than by a
   re-render, which would replace the field mid-word. */
.rules-search {
  display: flex; align-items: center; gap: 8px; flex-shrink: 0;
  margin: 0 16px; padding: 0 10px;
  background: var(--surface); border: 1px solid var(--stroke); border-radius: 11px;
}
.rules-search:focus-within { border-color: var(--gold); }
.rs-icon { flex: none; font-size: 13px; opacity: .55; }
.rs-input {
  flex: 1; min-width: 0; padding: 10px 0;
  background: none; border: none; outline: none; -webkit-appearance: none;
  font-family: inherit; font-size: 15px; color: var(--text);
  user-select: text; -webkit-user-select: text;
}
.rs-input::placeholder { color: var(--text-2); opacity: .8; }
.rs-input::-webkit-search-cancel-button { display: none; }   /* .rs-clear is ours */
.rs-clear {
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  background: var(--surface-raised); color: var(--text-2); font-size: 11px;
  display: inline-flex; align-items: center; justify-content: center;
}
.rs-clear:active { background: rgba(255,255,255,.18); }
.rs-status { flex-shrink: 0; margin: 8px 18px 0; font-size: 11.5px; color: var(--text-2); }

/* Filtered-out chapters and sections, and the excerpt under a matched heading. */
.rules-chapter[hidden], .rules-section[hidden], .rs-clear[hidden],
.rs-status[hidden], .rs-snip[hidden], .about-card[hidden], .rs-empty[hidden] { display: none; }
.rs-t { display: block; }
.rs-snip {
  display: block; margin-top: 4px; padding-right: 4px;
  font-size: 11.5px; font-weight: 400; line-height: 1.5; color: var(--text-2);
}
.rules-text mark, .rs-snip mark {
  background: rgba(198,163,83,.26); color: var(--gold-soft);
  border-radius: 3px; padding: 0 1px;
}

/* ---------------------------------------------------------------- game table */

.game-screen {
  /* The auction hand's card width. The panel below sits clear of the hand by
     deriving its offset from this, rather than a px guess that only holds on
     one screen size. Do NOT raise: 13 overlapped cards are 6.04 card-widths,
     so 16.5vw is exactly one screen wide. */
  --uw-bid: clamp(52px, 16.5vw, 84px);
  /* The player's card width in PLAY, hoisted to the screen so the dummy can be
     sized as a proportion of it rather than by an independent px guess. Those
     two drifted apart badly — a 120px player card next to a 58px dummy is a
     hand you can read beside one you cannot. Dummy now derives from this. */
  --card-player: clamp(52px, 15vw, 78px);
  --card-dummy: clamp(52px, calc(var(--card-player) * 0.84), 88px);
  background: radial-gradient(120% 90% at 50% 38%, var(--felt-top) 0%, var(--felt-bottom) 78%);
}

/* Two-line status strip. Line 1 is the standing context (deal, dealer,
   vulnerability); line 2 is the one thing the player needs right now — the
   contract and whose move it is — set larger and brighter so the current
   decision is readable without parsing the four seat badges. */
.status-bar {
  display: flex; align-items: center; gap: 8px;
  margin: 0 10px; padding: 7px 13px;
  background: rgba(0,0,0,.25);
  border: 1px solid rgba(44,74,59,.6);
  border-radius: 11px;
  color: rgba(230,213,168,.9);
  width: calc(100% - 20px);
  text-align: left;
}
.status-lines { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.status-context { font-size: 12px; font-weight: 600; color: rgba(230,213,168,.72); }
.status-turn {
  font-size: 17px; font-weight: 800; color: var(--gold);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.status-turn:empty { display: none; }
.status-chev { color: rgba(230,213,168,.6); font-size: 11px; flex: none; }

.table-area {
  flex: 1; display: flex; flex-direction: column;
  justify-content: space-between;
  padding: 6px 8px 2px; min-height: 0; gap: 2px; position: relative;
  /* Contain the z-index:1 below. Without this the table's children are stacked
     against the SCREEN, not against the table, so .table-middle painted over
     the absolutely-positioned .bidding-panel (z-index auto) and every bid
     control became untappable on phone portrait. Isolating keeps that 1 local
     and costs nothing else. */
  isolation: isolate;
}

/* One decorative watermark belongs to the table, not to the trick.  Keeping it
   in this absolute layer means rotation, a completed trick, or the bidding
   panel can never move it or create a second overlapping mark.

   Its position is fixed to the TOP of the table rather than centred on it:
   .table-area changes height between bidding and play (the composer takes the
   lower half), so a centred mark slid up and down as the hand progressed. An
   anchor that does not move is the point of a watermark. The size is fixed for
   the same reason — a vw-derived width redrew it on every rotation. */
.table-watermark {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 240px; pointer-events: none; opacity: .04; z-index: 0;
}
/* The mark lives inside .table-middle, so "centred" means centred on the band
   that holds the trick — which is the same thing in every layout, however the
   table is arranged around it. Anchored to .table-area instead it drifted with
   the table's own height: 9px off the cross on a phone, 69px in landscape and
   157px on a tablet, where it read as a smudge below the cards. */
.table-middle { position: relative; }
.table-middle > :not(.table-watermark) { position: relative; z-index: 1; }
.table-watermark .rase-mark { width: 100%; height: auto; }
.table-area > :not(.table-watermark) { position: relative; z-index: 1; }
.table-middle { display: flex; flex: 1; align-items: center; min-height: 0; }
/* The bidding panel occupies the lower table. Keep the side-seat labels in the
   clear upper region instead of centring them into that panel. */
.game-screen.is-bidding .table-middle { align-items: flex-start; padding-top: 12px; }
/* --tw lives here, not on .trick-area, so the felt print resolves to the same
   width during the auction (no trick on the table) as it does in play. */
.table-center {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; min-width: 0; position: relative; --tw: 196px;
}

.seat-zone { display: flex; flex-direction: column; align-items: center; gap: 4px; min-height: 0; }
.seat-W, .seat-E { width: 74px; flex-shrink: 0; justify-content: center; }
.seat-zone.bare { gap: 0; }
.seat-W.bare, .seat-E.bare { width: auto; }

.seat-badge {
  font-size: 10.5px; font-weight: 700; color: var(--text-2);
  background: rgba(0,0,0,.28);
  padding: 4px 10px; border-radius: 20px;
  display: inline-flex; align-items: center; gap: 5px;
  white-space: nowrap;
}
.seat-badge .dot { width: 6px; height: 6px; border-radius: 50%; background: rgba(157,178,166,.4); }
/* whose turn: the active seat's badge flips to solid gold so the side to play
   is unmistakable at a glance */
.seat-badge.active { color: var(--ink-on-gold); background: var(--gold); font-weight: 800; }
/* side (E/W) zones are narrow: let the badge text wrap instead of clipping */
.seat-W .seat-badge, .seat-E .seat-badge {
  white-space: normal; max-width: 100%;
  text-align: center; line-height: 1.4; border-radius: 12px;
}
.seat-badge.active .dot { background: var(--ink-on-gold); box-shadow: none; animation: turnpulse 1.2s ease-in-out infinite; }
@keyframes turnpulse { 50% { opacity: .25; } }

/* playing cards */
.pcard {
  background: var(--ivory);
  border-radius: 8px;
  border: 1px solid rgba(0,0,0,.14);
  box-shadow: 0 1.5px 3px rgba(0,0,0,.3);
  position: relative;
  display: inline-block;
  color: var(--card-black);
  flex-shrink: 0;
}
.pcard .idx {
  position: absolute; top: 5%; left: 8%;
  display: flex; flex-direction: column; align-items: center; line-height: 1.02;
  font-weight: 800;
}
.pcard .pip {
  position: absolute; left: 55%; top: 58%; transform: translate(-50%, -50%);
  opacity: .85;
}
.pcard.hint-glow {
  border: 2.5px solid var(--gold);
  box-shadow: 0 0 12px rgba(198,163,83,.9);
}
.pcard.dimmed { opacity: .45; }
.pcard:disabled { cursor: default; }

.card-back {
  background: linear-gradient(135deg, #1C4534 0%, #0E2A1E 100%);
  border-radius: 6px;
  border: 1px solid rgba(0,0,0,.25);
  box-shadow: 0 1px 3px rgba(0,0,0,.3), inset 0 0 0 1.5px rgba(198,163,83,.5);
  flex-shrink: 0;
}

.hidden-hand { display: flex; }
.hidden-hand .card-back { width: 32px; height: 46px; margin-left: -21px; }
.hidden-hand .card-back:first-child { margin-left: 0; }
.hidden-hand.vertical { flex-direction: column; }
.hidden-hand.vertical .card-back { width: 44px; height: 30px; margin-left: 0; margin-top: -19px; }
.hidden-hand.vertical .card-back:first-child { margin-top: 0; }

/* user hand row (bidding: single compact row) */
.user-hand { --uw: clamp(52px, 14.5vw, 72px); display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 3px 2px 4px; }
.user-hand .hand-line-row { display: flex; justify-content: center; }
/* bidding: the 13-card row spans the full screen width (13 overlapped cards
   ≈ 6.04 card-widths, so 16.5vw fills 100vw once the side padding is cancelled) */
.user-hand:not(.play2) {
  flex-direction: row;
  --uw: var(--uw-bid);
  margin: 0 -8px;                 /* cancel .table-area side padding */
  padding: 4px 0 5px;
}
.user-hand .pcard {
  width: var(--uw);
  height: calc(var(--uw) * 1.45);
  margin-left: calc(var(--uw) * -0.58);
  transition: transform .15s ease;
}
.user-hand .pcard:first-child,
.user-hand .hand-line-row .pcard:first-child { margin-left: 0; }
.user-hand .pcard .idx { font-size: calc(var(--uw) * 0.34); }
/* "10" is two characters wide — shrink and nudge it so the overlapping next
   card never covers the value (visible strip in the bidding fan = 0.42 uw) */
.user-hand:not(.play2) .pcard.r10 .idx { font-size: calc(var(--uw) * 0.28); left: 5%; }
.user-hand .pcard .pip { font-size: calc(var(--uw) * 0.5); }
.user-hand .pcard.playable:active { transform: translateY(-10px); }
.user-hand .pcard.hint-glow { transform: translateY(-8px); z-index: 2; }

/* play mode: two rows → bigger cards than bidding, less overlap, suits grouped */
.user-hand.play2 { --uw: var(--card-player); gap: 4px; }
.user-hand.play2 .pcard { margin-left: calc(var(--uw) * -0.46); }
.user-hand.play2 .pcard.suit-gap { margin-left: calc(var(--uw) * -0.1); }

/* dummy columns (per suit, stacked downward)
   --dw is the dummy card width and carries the brief's floor: 52px on phone,
   70px on tablet (raised in the 700px query below). Dummy may be smaller than
   the player's hand, never illegibly so — a hand you have to squint at is one
   you misplay. */
.dummy-cols { --dw: var(--card-dummy); display: flex; align-items: flex-start; gap: 6px; }
.dummy-col { display: flex; flex-direction: column; }
/* overlap tuned so each card's rank+suit index stays fully visible (not clipped) */
.dummy-col .pcard { margin-top: calc(var(--dw, 42px) * -0.82); }
.dummy-col .pcard:first-child { margin-top: 0; }
.dummy-cols .pcard {
  width: var(--dw, 42px); height: calc(var(--dw, 42px) * 1.45);
}
/* horizontal index (rank beside suit) fits the visible strip without overlap */
.dummy-cols .pcard .idx {
  flex-direction: row; align-items: center; gap: calc(var(--dw, 42px) * 0.04);
  top: 8%; left: 12%; font-size: calc(var(--dw, 42px) * 0.42); font-weight: 800;
}
.dummy-cols .pcard .idx span:last-child { font-size: calc(var(--dw, 42px) * 0.46); }
/* the two-character "10" index shrinks so rank and suit both stay on the card */
.dummy-cols .pcard.r10 .idx { left: 6%; font-size: calc(var(--dw, 42px) * 0.34); gap: calc(var(--dw, 42px) * 0.02); }
.dummy-cols .pcard.r10 .idx span:last-child { font-size: calc(var(--dw, 42px) * 0.40); }
.dummy-cols .pcard .pip { display: none; }
/* side (E/W) dummy: wrap suits into a 2×2 grid so it fits the narrow seat zone.
   The width was a flat 33px, which is what a dummy sitting East or West still
   rendered at in portrait — two thirds of the 52px floor and barely legible.
   It now takes the same token as the top tray, and the seat zone widens to
   hold two columns of it. */
.dummy-cols.side {
  --dw: var(--card-dummy);
  flex-wrap: wrap;
  width: calc(var(--dw) * 2 + 6px);
  justify-content: center;
}
.seat-W:has(.dummy-cols.side), .seat-E:has(.dummy-cols.side) {
  width: calc(var(--card-dummy) * 2 + 14px);
}
/* A phone cannot afford a widened dummy tray AND a full-size column of card
   backs opposite it — between them they squeezed the trick under its own
   minimum. The backs stay, but as a SILHOUETTE: a narrow column the play
   layouts below size and place. Only short landscape, where the height is
   genuinely gone, drops them entirely. */
@media (max-width: 699px) {
  /* A narrow column, not `auto`: left to size itself the badge grew into the
     middle and sat on a played card. This reserves just enough for the name. */
  .game-screen:not(.is-bidding) .seat-zone.is-hidden.seat-W,
  .game-screen:not(.is-bidding) .seat-zone.is-hidden.seat-E { width: 58px; }
  .game-screen:not(.is-bidding) .seat-zone.is-hidden .seat-badge {
    font-size: 11px; padding: 4px 7px; text-align: center;
  }
}

/* trick area — every measurement derives from --tw so the whole cross scales
   with one variable (phones keep the original 196px geometry) */
.trick-area { position: relative; width: var(--tw); height: calc(var(--tw) * 0.878); }
.trick-card {
  position: absolute; width: calc(var(--tw) * 0.286); height: calc(var(--tw) * 0.408);
  background: var(--ivory); border-radius: 8px; border: 1px solid rgba(0,0,0,.15);
  box-shadow: 0 2.5px 7px rgba(0,0,0,.4);
}
.trick-card .tc-idx {
  position: absolute; top: 6%; left: 9%;
  font-weight: 800; font-size: calc(var(--tw) * 0.097); line-height: 1;
}
.trick-card .tc-pip {
  position: absolute; left: 56%; top: 60%; transform: translate(-50%, -50%);
  font-size: calc(var(--tw) * 0.173); line-height: 1;
}
/* Entry motion belongs to the card that has just ARRIVED, never to the trick.
   The whole view re-renders on every action, so an animation on .trick-card
   itself restarted on all four cards every time anything happened: the trick
   flickered as a unit and said nothing about who had just played. That is why
   it was cut back to a bare fade — the fade was not the fix, the re-render
   was. render() now diffs the trick against the previous frame and tags only
   the newly played card with `tc-new`, so the motion runs once, on one card.

   Each card slides in from the edge its own seat sits at, which is the cue the
   cross exists to give: the card comes FROM the hand that played it. Every
   keyframe carries its position's rotation, because a transform keyframe
   replaces the .pos-* transform outright — a plain translate would drop the
   tilt for the length of the animation and snap it back at the end. The
   travel is a percentage of the card, so it scales with --tw and stays inside
   the layout the way the old scale(1.18) did not. */
.trick-card.tc-new {
  animation-duration: .26s;
  animation-timing-function: cubic-bezier(.22,.85,.3,1);
}
.trick-card.tc-new.pos-N { animation-name: tcInN; }
.trick-card.tc-new.pos-S { animation-name: tcInS; }
.trick-card.tc-new.pos-W { animation-name: tcInW; }
.trick-card.tc-new.pos-E { animation-name: tcInE; }
@keyframes tcInN { from { opacity: 0; transform: translateY(-80%) rotate(-3deg); } }
@keyframes tcInS { from { opacity: 0; transform: translateY(80%) rotate(3deg); } }
@keyframes tcInW { from { opacity: 0; transform: translateX(-90%) rotate(-6deg); } }
@keyframes tcInE { from { opacity: 0; transform: translateX(90%) rotate(6deg); } }
/* Reduce Motion keeps the arrival — the card appears, it just does not travel. */
@keyframes trickIn { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .trick-card.tc-new.pos-N, .trick-card.tc-new.pos-S,
  .trick-card.tc-new.pos-W, .trick-card.tc-new.pos-E { animation-name: trickIn; }
}
.trick-card.pos-N { top: 0; left: calc(var(--tw) * 0.357); transform: rotate(-3deg); }
.trick-card.pos-S { bottom: 0; left: calc(var(--tw) * 0.357); transform: rotate(3deg); }
.trick-card.pos-W { top: calc(var(--tw) * 0.235); left: calc(var(--tw) * 0.026); transform: rotate(-6deg); }
.trick-card.pos-E { top: calc(var(--tw) * 0.235); right: calc(var(--tw) * 0.026); transform: rotate(6deg); }

.trick-counters { display: flex; gap: 10px; }
.tc-pill {
  display: flex; align-items: center; gap: 5px;
  padding: 4px 11px; border-radius: 20px; background: rgba(0,0,0,.3);
}
.tc-pill .l { font-size: 10px; font-weight: 700; color: var(--text-2); }
.tc-pill .v { font-size: 15px; font-weight: 800; color: var(--gold-soft); }

/* suit colours */
.s-S { color: var(--spade); }
.s-C { color: var(--club); }
.s-H { color: var(--heart); }
.s-D { color: var(--diamond); }
.fourcolor .s-D { color: var(--card-blue); }
.fourcolor .s-C { color: var(--card-green); }

/* suit symbols inline in text on dark surfaces (auction grid, review tables):
   the card-face shades disappear against the felt, so use brighter ones */
.cs { font-weight: 800; }
.cs.s-S { color: var(--text); }
.cs.s-C { color: #9FB0AE; }
.cs.s-H { color: #F1615A; }
.cs.s-D { color: #EFA15A; }
.fourcolor .cs.s-D { color: #6FA0F0; }
.fourcolor .cs.s-C { color: #62C287; }

/* hint pill */
.hint-pill {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 4px 11px; border-radius: 20px;
  background: rgba(198,163,83,.22);
  border: 1px solid rgba(198,163,83,.55);
  color: var(--gold-soft); font-size: 11px; font-weight: 700;
}
.undo-pill {
  background: rgba(176,58,52,.2);
  border-color: rgba(176,58,52,.55);
  color: #E8A9A4;
}
.undo-pill:active { background: rgba(176,58,52,.4); }
.claim-pill {
  background: rgba(47,111,191,.22);
  border-color: rgba(47,111,191,.6);
  color: #90BAEF;
  font-weight: 800;
}
.claim-pill:active { background: rgba(47,111,191,.4); }
.concede-pill {
  background: rgba(176,58,52,.2);
  border-color: rgba(176,58,52,.55);
  color: #E8A9A4;
  font-weight: 800;
}
.concede-pill:active { background: rgba(176,58,52,.4); }
/* toggled-on pill (e.g. Last trick peek is showing) */
.hint-pill.active {
  background: rgba(198,163,83,.5);
  color: var(--ivory);
}
.badge-row { display: flex; align-items: center; gap: 8px; justify-content: center; flex-wrap: wrap; }

/* last-trick peek */
.trick-area.peeking .trick-card, .trick-area.peeking .trick-card.tc-new { animation-name: none; opacity: .96; }
.trick-card.tc-win {
  box-shadow: 0 0 0 2px var(--gold-soft), 0 3px 9px rgba(0,0,0,.5);
  z-index: 2;
}
.trick-peek-label {
  font-size: 11px; font-weight: 700; color: var(--gold-soft);
  padding: 4px 11px; border-radius: 20px; background: rgba(0,0,0,.3);
  text-align: center;
}

/* ---------------------------------------------------------------- bidding panel */

.bidding-panel {
  /* phone: floats above the frozen hand fan (tablets dock it — see below) */
  position: absolute; left: 12px; right: 12px;
  bottom: calc(var(--uw-bid) * 1.45 + 39px);
  background: rgba(16,42,31,.97);
  border: 1px solid var(--stroke); border-radius: 14px;
  box-shadow: 0 8px 26px rgba(0,0,0,.45);
  padding: 7px;
  display: flex; flex-direction: column; gap: 5px;
  /* the hand stays frozen below the panel; the panel itself scrolls if its
     content outgrows the space between the status bar and the cards */
  min-height: calc(60dvh - (var(--uw-bid) * 1.45 + 14px));
  max-height: calc(100% - 210px);
  overflow-y: auto;
  /* No entry animation: the whole view re-renders on every action, which would
     replay the slide each time and make the buttons jump under a double-tap. */
}

.bp-header { display: flex; align-items: center; gap: 8px; }
.bp-profile { font-size: 14.5px; font-weight: 700; color: var(--gold-soft); }
.bp-badge {
  font-size: 12px; font-weight: 800; color: var(--gold);
  background: rgba(198,163,83,.18); border-radius: 20px; padding: 4px 9px;
}
.bp-hcp {
  margin-left: auto;
  font-size: 13px; font-weight: 800; color: var(--ink-on-gold);
  background: var(--gold-soft); border-radius: 20px; padding: 4px 11px;
  white-space: nowrap;
}

.auction-grid-wrap {
  background: rgba(22,53,39,.55); border-radius: 10px; padding: 9px 10px;
  /* Absorbs whatever the panel has spare, so it fills the space from the first
     call and the buttons below never shift down as the auction grows. */
  flex: 1 1 auto; min-height: 96px; overflow-y: auto;
}
.auction-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px 8px; }
/* 5-column variant adds a round-number column on the left */
.auction-grid.ag5 { grid-template-columns: 22px repeat(4, 1fr); gap: 6px 6px; }
.ag-rnd-head { }
.ag-rnd {
  font-size: 11px; font-weight: 700; color: rgba(157,178,166,.45);
  display: flex; align-items: center; justify-content: center;
}
.ag-head {
  font-size: 13px; font-weight: 700; color: var(--text-2);
  text-align: center; letter-spacing: .4px; padding-bottom: 6px;
  border-bottom: 1px solid rgba(157,178,166,.25);
}
.ag-head.dealer { color: var(--gold); }
.ag-cell {
  font-size: 18px; font-weight: 600; text-align: center; color: var(--gold-soft);
  min-height: 30px; line-height: 30px; border-radius: 6px;
}
.ag-cell.pass { color: var(--text-2); font-weight: 400; }
.ag-cell.sel { background: rgba(198,163,83,.2); }
.ag-explain {
  display: flex; gap: 6px; align-items: flex-start;
  margin-top: 7px; padding: 8px;
  background: rgba(198,163,83,.1); border-radius: 8px;
  font-size: 11.5px; color: var(--text-2); line-height: 1.5; text-align: left;
}
.ag-explain b { color: var(--gold-soft); }
.ag-explain { cursor: pointer; }                       /* tap anywhere to dismiss */
.ag-close { margin-left: auto; padding-left: 8px; color: var(--text-2); font-size: 12px; flex-shrink: 0; }

.level-row { display: flex; gap: 6px; }
/* Sized for a thumb, not for the space that was left over: the auction hides the
   other seats' card backs, and that room goes here. */
.level-chip {
  /* 48px is the brief's minimum touch target; the row is the primary control
     of the whole screen, so it takes the floor rather than the leftovers. */
  flex: 1; height: 48px; border-radius: 10px;
  background: var(--surface-raised); color: var(--text);
  font-size: 18px; font-weight: 700;
}
.level-chip.sel { background: var(--gold); color: var(--ink-on-gold); }
.level-chip:disabled { opacity: .3; }

.strain-row { display: flex; gap: 6px; }
.strain-btn {
  flex: 1; height: 48px; border-radius: 10px;
  background: var(--ivory); font-size: 27px; font-weight: 700;
  line-height: 1; display: flex; align-items: center; justify-content: center;
}
.strain-btn.nt { font-size: 19px; color: var(--card-black); }
.strain-btn.sel { background: var(--gold); color: var(--ink-on-gold) !important; }
.strain-btn:disabled { opacity: .4; }

.special-row { display: flex; gap: 6px; flex-wrap: nowrap; }
.special-btn {
  flex: 1 1 0; min-width: 0; height: 48px; border-radius: 10px;
  color: #fff; font-size: 15px; font-weight: 700;
}
.special-btn.pass { background: var(--positive); }
.special-btn.dbl { background: var(--negative); }
.special-btn.rdbl { background: var(--info); }
.special-btn.sel { outline: 2px solid var(--gold-soft); outline-offset: -2px; }
.special-btn:disabled { opacity: .32; }
/* The lightbulb stays — it is what the tutorials and the play-screen Hint pill
   both point at — but it carries its name as well, so the action is
   discoverable without decoding an icon. */
.suggest-btn {
  min-width: 48px; height: 48px; border-radius: 10px; flex: none;
  padding: 0 12px; display: flex; align-items: center; gap: 6px;
  background: rgba(198,163,83,.16);
  border: 1px solid rgba(198,163,83,.5);
  color: var(--gold-soft); font-size: 20px; font-weight: 800;
  white-space: nowrap;
}
.suggest-btn .sb-label { font-size: 15px; font-weight: 700; }
.bid-utility-row { min-height: 0; display: flex; justify-content: flex-end; }
.undo-bid-btn {
  min-height: 44px; padding: 0 14px; border-radius: 10px;
  color: #E8A9A4; background: rgba(176,58,52,.2);
  border: 1px solid rgba(176,58,52,.55); font-size: 15px; font-weight: 800;
}
.undo-bid-btn:active { background: rgba(176,58,52,.4); }

/* natural height: the slot sits above the buttons, so growing it extends the
   bottom-anchored panel upward without moving the rows below */
.meaning-slot { display: flex; flex-direction: column; justify-content: center; }
.meaning-panel {
  background: rgba(22,53,39,.7); border-radius: 10px; padding: 7px 8px;
  display: flex; flex-direction: column; gap: 5px;
}
.meaning-head { display: flex; align-items: flex-start; gap: 8px; flex: 1; min-height: 0; overflow: hidden; }
.meaning-call { font-size: 14px; font-weight: 800; color: var(--gold); white-space: nowrap; }
.meaning-text {
  flex: 1; font-size: 12px; color: var(--text-2); line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.meaning-tag {
  font-size: 9.5px; font-weight: 800; color: var(--gold);
  background: rgba(198,163,83,.18); border-radius: 20px; padding: 3px 7px;
  white-space: nowrap;
}
.meaning-tag.suggested { background: rgba(198,163,83,.4); }
.place-btn {
  height: 34px; border-radius: 11px;
  background: var(--gold); color: var(--ink-on-gold);
  font-size: 14.5px; font-weight: 800;
}
/* Instructional text, not metadata — the brief's 14px floor applies. */
.meaning-hint { font-size: 14px; line-height: 1.35; color: var(--text-2); text-align: center; padding: 0; }

.bp-waiting {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  padding: 7px 0 4px; color: var(--text-2); font-size: 12.5px; font-weight: 500;
}
.spinner {
  width: 15px; height: 15px; border-radius: 50%;
  border: 2px solid rgba(198,163,83,.25); border-top-color: var(--gold);
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------------------------------------------------------------- overlays / sheets */

.overlay {
  position: fixed; inset: 0; z-index: 40;
  background: rgba(4,12,8,.6);
  display: flex; align-items: flex-end; justify-content: center;
}
.overlay.center { align-items: center; padding: 20px; }
.sheet {
  background: var(--bg);
  border-radius: 20px 20px 0 0;
  box-shadow: 0 -8px 40px rgba(0,0,0,.5);
  padding: 8px 18px calc(20px + env(safe-area-inset-bottom));
  width: min(560px, 100%);
  max-height: 88dvh;
  overflow-y: auto;
  animation: sheetUp .3s ease;
}
.overlay.center .sheet { border-radius: 20px; animation: none; }
@keyframes sheetUp { from { transform: translateY(60px); opacity: .4; } }
.grab { width: 42px; height: 5px; border-radius: 3px; background: var(--stroke); margin: 6px auto 14px; }

.sheet h3 { font-size: 17px; font-weight: 800; color: var(--text); display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.sheet h3 .h3-brand { display: inline-flex; align-items: center; gap: 9px; }

/* table menu (≡) — reuses the home screen's menu rows */
.game-menu { display: flex; flex-direction: column; gap: 10px; padding-bottom: 6px; }
.game-menu .menu-row { background: rgba(255,255,255,.03); }

/* result sheet */
.result-title { font-size: 25px; font-weight: 800; color: var(--text); text-align: center; }
.result-sub { font-size: 13.5px; color: var(--text-2); text-align: center; margin: 8px 0 14px; line-height: 1.5; }
.result-entries {
  background: rgba(22,53,39,.6); border-radius: 12px; padding: 10px 12px;
  display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px;
}
.result-entry { display: flex; align-items: baseline; gap: 8px; font-size: 13px; }
.result-entry .lab { flex: 1; color: var(--text-2); }
.result-entry .amt { font-weight: 800; }
.result-entry .amt.we { color: var(--gold); }
.result-entry .amt.they { color: var(--text); }
.result-entry .line-tag { font-size: 10px; color: var(--text-2); opacity: .75; }
.result-event { font-size: 14px; font-weight: 700; color: var(--gold); text-align: center; margin: 8px 0; }
.result-buttons { display: flex; flex-direction: column; gap: 9px; margin-top: 14px; }
.result-buttons .row2 { display: flex; gap: 9px; }
.action-btn {
  height: 48px; box-sizing: border-box; border-radius: 13px;   /* border-box → all buttons exactly 48px */
  background: var(--surface-raised); border: 1px solid var(--stroke);
  color: var(--text); font-size: 14.5px; font-weight: 700;
}
.result-buttons .row2 .action-btn { flex: 1; }                  /* split the row evenly */
.action-btn.prominent { width: 100%; background: var(--gold); border: none; color: var(--ink-on-gold); }
.action-btn.subtle { height: 40px; background: none; border: none; color: var(--text-2); font-weight: 600; font-size: 13.5px; }

/* scorepad */
.pad-stats { display: flex; gap: 10px; margin-bottom: 14px; }
.pad-table { background: var(--surface); border: 1px solid var(--stroke); border-radius: 16px; padding: 13px; }
.pad-cols {
  display: grid; grid-template-columns: 1fr 84px 84px; gap: 8px;
  font-size: 11px; font-weight: 800; color: var(--gold);
  padding-bottom: 8px;
}
.pad-cols span:nth-child(n+2) { text-align: right; }
.pad-section { font-size: 12px; font-weight: 800; color: var(--text); padding: 7px 0 4px; }
.pad-section small { font-weight: 500; color: var(--text-2); margin-left: 5px; }
.pad-row {
  display: grid; grid-template-columns: 1fr 84px 84px; gap: 8px;
  font-size: 12.5px; padding: 4px 0; align-items: baseline;
  font-variant-numeric: tabular-nums;
}
.pad-row .lab { color: var(--text-2); font-weight: 500; }
.pad-row.bold .lab { color: var(--text); font-weight: 700; }
.pad-row .num { text-align: right; font-weight: 600; color: var(--gold-soft); }
.pad-row.bold .num { font-weight: 800; }
.pad-empty { font-size: 11.5px; color: rgba(157,178,166,.55); padding: 4px 0; }
.gold-line { border: none; height: 2px; background: rgba(198,163,83,.85); margin: 7px 0; }
.gold-line.double { box-shadow: 0 4px 0 -2px rgba(198,163,83,.85); margin-bottom: 11px; }
.gold-line.thin { height: 1px; background: rgba(198,163,83,.5); }
.pad-status {
  margin-top: 12px; font-size: 12.5px; color: var(--text-2); line-height: 1.55;
  background: var(--surface-raised); padding: 10px 12px; border-radius: 10px;
}
.danger-btn { color: #E07A74; font-size: 13px; font-weight: 600; padding: 14px; width: 100%; text-align: center; }

/* contract card in recap */
.contract-card {
  background: var(--surface); border: 1px solid var(--stroke); border-radius: var(--radius);
  padding: 13px 14px; margin-bottom: 12px;
}
.contract-line { display: flex; align-items: center; gap: 8px; }
.contract-big { font-size: 22px; font-weight: 800; color: var(--gold); }
.contract-detail { font-size: 12px; color: var(--text-2); margin-top: 6px; }
.your-contract {
  margin-left: auto; font-size: 9px; font-weight: 800; color: var(--gold);
  background: rgba(198,163,83,.2); border-radius: 20px; padding: 3px 8px;
}

/* ---------------------------------------------------------------- review */

.review-screen { background: var(--bg); }
.review-loading { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; color: var(--text-2); font-size: 13px; }
.review-loading .rase-mark { opacity: .75; margin-bottom: 4px; }
.progress { width: min(280px, 70%); height: 6px; background: var(--surface-raised); border-radius: 3px; overflow: hidden; }
.progress div { height: 100%; background: var(--gold); border-radius: 3px; transition: width .2s; }

.deal-diagram { display: flex; flex-direction: column; gap: 12px; }
.dd-mid { display: flex; justify-content: space-between; gap: 12px; }
.hand-block .hb-title { font-size: 11px; font-weight: 800; color: var(--gold); margin-bottom: 4px; }
.hand-line { display: flex; gap: 6px; font-size: 12px; align-items: baseline; padding: 1px 0; }
.hand-line .hs { width: 14px; font-weight: 700; }
.hand-line .hr { font-family: ui-monospace, "SF Mono", Consolas, monospace; font-weight: 600; color: var(--text-2); letter-spacing: .5px; }
.hand-line .hs.s-S { color: var(--text); }
.hand-line .hs.s-C { color: #9FB0AE; }
.hand-line .hs.s-H { color: #F1615A; }
.hand-line .hs.s-D { color: #EFA15A; }


.ra-table { display: flex; flex-direction: column; }
.ra-row {
  display: grid; grid-template-columns: 58px 52px 52px 1fr; gap: 8px;
  padding: 7px 4px; font-size: 12px; align-items: baseline;
  border-bottom: 1px solid rgba(44,74,59,.5);
}
.ra-row:last-child { border-bottom: none; }
.ra-row.head { font-size: 10px; font-weight: 800; color: var(--text-2); text-transform: uppercase; letter-spacing: .5px; }
.ra-row.user-row { background: rgba(198,163,83,.06); }
.ra-row.deviation { background: rgba(217,142,50,.1); }
/* A defensible alternative sits between the two: marked, but not flagged. */
.ra-row.sound { background: rgba(198,163,83,.09); }
.ra-sound {
  display: inline-block; margin-left: 6px; padding: 1px 5px; border-radius: 5px;
  background: rgba(198,163,83,.16); color: var(--text-2);
  font-size: 9.5px; font-weight: 800; letter-spacing: .4px; text-transform: uppercase;
  vertical-align: 1px;
}
.ra-row .mono { font-weight: 700; color: var(--gold-soft); font-variant-numeric: tabular-nums; }
.ra-row .exp { color: var(--text-2); font-size: 11px; line-height: 1.45; }

.rp-trick {
  background: rgba(22,53,39,.5); border-radius: 10px; padding: 8px 11px;
  display: flex; flex-wrap: wrap; gap: 4px 11px; align-items: baseline; font-size: 12.5px;
  margin-bottom: 6px;
}
.rp-no { font-weight: 800; color: var(--text-2); min-width: 17px; }
.rp-card { font-weight: 600; color: var(--text); }
.rp-card.winner { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--gold); }
.rp-note { font-size: 11.5px; color: var(--text-2); width: 100%; padding-left: 28px; }
.rp-note.good { color: #6FBF8B; }
.rp-note.bad { color: #E07A74; font-weight: 600; }
.rp-reason { font-size: 11.5px; color: var(--gold-soft); width: 100%; padding-left: 28px; font-style: italic; line-height: 1.4; }
/* tricks the double-dummy tracker says changed hands in this trick — the same
   losses the grade is built from, so the list and the percentage agree */
.rp-loss { font-size: 11.5px; width: 100%; padding-left: 28px; line-height: 1.4; font-weight: 600; }
.rp-loss.mine { color: #E07A74; }
.rp-loss.theirs { color: var(--text-2); font-weight: 500; }

/* play grade ring — the closed face of the breakdown disclosure */
.grade-card { display: flex; align-items: center; gap: 16px; }
.grade-details > summary.grade-card { cursor: pointer; list-style: none; }
.grade-details > summary.grade-card::-webkit-details-marker { display: none; }
.grade-details > summary::after {
  content: '▾'; margin-left: auto; padding-left: 8px; flex-shrink: 0;
  font-size: 12px; color: var(--text-2); transition: transform .15s;
}
.grade-details[open] > summary::after { transform: rotate(180deg); }
.grade-details[open] > summary.grade-card { margin-bottom: 12px; }
/* the disclosure hint doubles as the closed-state summary, so keep it legible */
.grade-details .grade-basis { font-weight: 600; }
.grade-ring {
  width: 92px; height: 92px; border-radius: 50%; flex-shrink: 0;
  background: conic-gradient(var(--gold) calc(var(--pct) * 1%), rgba(255,255,255,.09) 0);
  display: flex; align-items: center; justify-content: center;
}
.grade-pct {
  width: 72px; height: 72px; border-radius: 50%; background: var(--surface);
  /* grid: content centred in the disc, number and % aligned on their baseline */
  display: grid; grid-auto-flow: column; align-content: center; justify-content: center;
  align-items: baseline; line-height: 1;
  font-size: 26px; font-weight: 800; color: var(--gold-soft); font-variant-numeric: tabular-nums;
}
.grade-pct span { font-size: 13px; margin-left: 1px; }
.grade-meta { flex: 1; min-width: 0; }
.grade-label { font-size: 19px; font-weight: 800; color: var(--text); }
.grade-basis { font-size: 12px; color: var(--text-2); margin-top: 4px; line-height: 1.45; }

/* the two halves behind the ring: bidding and play, each with what it cost */
.scorecard .sc-row { margin-top: 12px; }
.scorecard .sc-row:first-of-type { margin-top: 4px; }
.sc-head { display: flex; justify-content: space-between; align-items: baseline; }
.sc-name { font-size: 13px; font-weight: 700; color: var(--text); letter-spacing: .02em; }
.sc-pct { font-size: 13px; font-weight: 800; color: var(--gold-soft); font-variant-numeric: tabular-nums; }
.sc-bar {
  height: 6px; border-radius: 3px; margin: 5px 0 6px;
  background: rgba(255,255,255,.09); overflow: hidden;
}
.sc-bar > div { height: 100%; border-radius: 3px; background: var(--gold); }
.sc-detail { font-size: 12px; color: var(--text-2); line-height: 1.5; }
.sc-detail b { color: var(--gold-soft); font-weight: 700; }

/* one-line debrief on the result sheet when our contract goes down */
.result-debrief {
  margin: 8px auto 0; max-width: 340px;
  padding: 9px 12px; border-radius: 9px;
  background: rgba(198,163,83,.1);
  font-size: 12.5px; line-height: 1.5; color: var(--gold-soft);
  text-align: center;
}

/* the verdict that opens "What to work on" — the one sentence a player reads
   if they read nothing else, so it leads the card rather than sitting alone */
.improve-lead {
  font-size: 13.5px; line-height: 1.5; color: var(--gold-soft);
  padding-bottom: 9px; margin-bottom: 2px;
}
.improve-lead:not(:last-child) { border-bottom: 1px solid rgba(44,74,59,.5); }

/* analysis: the deal stays pinned at the top while the review scrolls under
   it, so any note can be compared with the layout; collapsible when not needed */
.deal-pin {
  /* lives between the top bar and the scroll area — structurally always
     visible, with the review scrolling in its own region below */
  flex-shrink: 0; z-index: 5;
  border-radius: 0; border-left: none; border-right: none;
  box-shadow: 0 10px 22px rgba(0,0,0,.5);
  padding: 10px 16px;
}
.deal-pin summary {
  font-size: 15px; font-weight: 800; color: var(--gold);
  cursor: pointer; list-style: none;
  display: flex; align-items: center;
}
.deal-pin summary::-webkit-details-marker { display: none; }
/* Title and result share a line where there is room and stack where there is
   not — the chevron sits outside the wrapping group so it stays put either way. */
.deal-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px; flex: 1; min-width: 0; }
.deal-title { flex-shrink: 0; }
.deal-pin .deal-meta { font-size: 11.5px; font-weight: 600; color: var(--text-2); }
.deal-pin .deal-meta b { color: var(--text); font-weight: 700; }
.deal-pin .deal-meta b.deal-score { color: var(--gold-soft); }
/* dealer and vulnerability: context you check occasionally, so it gives up the
   title to the contract and its score and sits at the head of the body instead */
.deal-context { font-size: 11px; color: var(--text-2); margin-bottom: 8px; }
/* dealer chip beside the hand's title in the diagram */
/* high-card points, sat at the end of the hand's title line. Deliberately not
   gold: the dealer badge next to it is, and the two must not read as one mark. */
.hb-hcp { margin-left: auto; padding-left: 8px; font-weight: 700; color: var(--text-2); white-space: nowrap; }
.hand-block .hb-title { display: flex; align-items: baseline; }

.hb-dealer {
  display: inline-block; min-width: 13px; text-align: center;
  font-size: 9px; font-weight: 800; line-height: 13px;
  color: var(--ink-on-gold); background: var(--gold); border-radius: 4px;
  vertical-align: 1px;
}
.deal-pin summary::after { content: '▾'; margin-left: auto; font-size: 11px; color: var(--text-2); transition: transform .15s; }
.deal-pin:not([open]) summary::after { transform: rotate(-90deg); }
.deal-pin[open] summary { margin-bottom: 8px; }
/* compact rendering inside the pinned card so it doesn't dominate the screen */
.deal-pin .deal-diagram { gap: 6px; }
.deal-pin .hand-line { font-size: 11px; padding: 0; }
.deal-pin .hand-block .hb-title { font-size: 10px; margin-bottom: 2px; }
.deal-pin .dd-tricks { margin-top: 8px; padding-top: 8px; }
.deal-pin .ddo-note { display: none; }
.deal-pin .ddt-cell { font-size: 12px; }

/* tricks-available grid — every hand as declarer, every strain */
.dd-tricks { margin-top: 12px; padding-top: 10px; border-top: 1px solid rgba(44,74,59,.6); }
.ddt-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 5px; }
.ddt-head .ddo-title { margin-bottom: 0; }
/* the grid folds away by default — it is reference material, and on a phone it
   is most of the panel's height when the hands are what you came to see */
.dd-tricks > summary.ddt-head { cursor: pointer; list-style: none; }
.dd-tricks > summary::-webkit-details-marker { display: none; }
.dd-tricks > summary::after {
  content: '▾'; margin-left: auto; font-size: 11px; color: var(--text-2); transition: transform .15s;
}
.dd-tricks:not([open]) > summary::after { transform: rotate(-90deg); }
.dd-tricks[open] > summary { margin-bottom: 7px; }
.ddt-grid { overflow-x: auto; }         /* a narrow phone scrolls the grid, not the page */
/* The grid reads as a table, so the columns stay close enough to scan across.
   Left free to stretch it would sprawl over the full width of the panel on a
   tablet and the rows would stop lining up to the eye. */
.ddt-row { display: flex; align-items: baseline; min-width: 220px; max-width: 340px; }
.ddt-seat { width: 34px; flex-shrink: 0; font-size: 12px; font-weight: 800; color: var(--text-2); }
.ddt-cell {
  flex: 1; text-align: center; padding: 2px 0; font-size: 13px; font-weight: 600;
  color: var(--text); font-variant-numeric: tabular-nums;
}
/* points sit past a gap, off the end of the trick columns — they are a
   different quantity and must not read as a sixth strain */
.ddt-hcp-cell {
  width: 34px; flex-shrink: 0; margin-left: 10px; text-align: right;
  font-size: 12px; font-weight: 700; color: var(--text-2); font-variant-numeric: tabular-nums;
  border-left: 1px solid rgba(44,74,59,.6); padding-left: 8px;
}
/* the hand that has to declare, where it makes a difference to the count */
.ddt-by {
  font-size: .7em; font-weight: 700; color: var(--text-2);
  margin-left: 2px; vertical-align: 2px; letter-spacing: 0;
}
.ddt-cell.played .ddt-by { color: var(--gold-soft); }
.ddt-row.head .ddt-cell { font-size: 12.5px; font-weight: 800; padding-bottom: 3px; }
.ddt-row.head .ddt-cell.nt { color: var(--text-2); }
.ddt-row.head .ddt-hcp-cell { font-size: 10px; letter-spacing: .04em; }
.ddt-cell.none { color: var(--text-2); opacity: .45; font-weight: 500; }
/* The best contract each side had, ringed in place. An inset shadow rather than
   a border: a border would add two pixels to one cell and pull the column out
   of line with the rows above and below it. */
.ddt-cell.best {
  box-shadow: inset 0 0 0 1px var(--gold); border-radius: 4px;
  color: var(--gold-soft); font-weight: 800;
}
.ddt-cell.played {
  color: var(--gold); font-weight: 800;
  background: rgba(198,163,83,.14); border-radius: 4px;
}
/* A marked cell must read even where the count is too low to bid — a contract
   played and gone down lands on exactly such a cell. */
.ddt-cell.none.played, .ddt-cell.none.best { opacity: 1; }

/* what the two marks mean. Unlike .ddo-note this survives on a phone: with the
   optimum list gone, the marks carry that meaning on their own. */
.ddt-key { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 7px; }
.ddt-key-item { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--text-2); }
.ddt-key-item i { width: 15px; height: 15px; border-radius: 4px; flex-shrink: 0; }
.ddt-key-item i.k-best { box-shadow: inset 0 0 0 1px var(--gold); }
.ddt-key-item i.k-played { background: rgba(198,163,83,.14); }

.ddo-title { font-size: 12px; font-weight: 800; color: var(--gold-soft); text-transform: uppercase; letter-spacing: .5px; margin-bottom: 5px; }
.ddo-note { font-size: 11px; color: var(--text-2); line-height: 1.4; margin-top: 6px; }

/* live scorepad entry form */
.lp-row { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; }
.lp-label { width: 56px; flex-shrink: 0; font-size: 11px; font-weight: 700; color: var(--text-2); text-transform: uppercase; letter-spacing: .3px; }
.lp-row .segmented { margin-top: 0; width: 100%; flex-wrap: wrap; }
.lp-row .seg { flex: 1; padding: 6px 2px; text-align: center; min-width: 30px; white-space: nowrap; }
.lp-preview {
  margin: 10px 0; padding: 8px 12px; border-radius: 9px;
  background: rgba(198,163,83,.1);
  font-size: 12.5px; font-weight: 600; color: var(--gold-soft); text-align: center;
}
.lp-events { padding: 10px 14px; }
.lp-note { font-size: 10.5px; color: var(--text-2); line-height: 1.45; margin: 2px 0 4px; }
.lp-notice { font-size: 13px; font-weight: 700; color: #6FBF8B; }
/* two segmented switches sharing one row (honours amount + side) */
.lp-duo { display: flex; gap: 6px; flex: 1; min-width: 0; }
.lp-duo .segmented { width: auto; flex: 1; }
.lp-duo .lp-dim { opacity: .4; }
.lp-timer-card { display: flex; align-items: center; gap: 10px; padding: 10px 14px; }
.lp-timer { flex: 1; font-size: 19px; font-weight: 800; color: var(--text-2); font-variant-numeric: tabular-nums; }
.lp-timer.run { color: var(--gold); }
.lp-timer-btn { height: 36px; padding: 0 14px; font-size: 12.5px; }
.lp-void .archive-title, .lp-void .archive-sub { opacity: .45; text-decoration: line-through; }
.lp-editing { background: rgba(198,163,83,.08); border-radius: 8px; }

/* hand archive */
.archive-row { display: flex; align-items: center; gap: 8px; border-bottom: 1px solid rgba(44,74,59,.5); }
.archive-row:last-child { border-bottom: none; }
.archive-main {
  flex: 1; min-width: 0; padding: 10px 0;
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px; text-align: left;
}
.archive-main:active { opacity: .7; }
.archive-title { font-size: 14.5px; font-weight: 700; color: var(--text); }
.archive-sub { font-size: 11.5px; color: var(--text-2); }
.archive-grade {
  flex-shrink: 0; font-size: 12px; font-weight: 800; color: var(--gold-soft);
  background: rgba(0,0,0,.28); border-radius: 12px; padding: 4px 8px;
  font-variant-numeric: tabular-nums;
}
.archive-del {
  flex-shrink: 0; width: 30px; height: 30px; border-radius: 50%;
  color: #E07A74; background: rgba(224,122,116,.12); font-size: 13px;
}
.archive-del:active { background: rgba(224,122,116,.28); }

/* key improvements */
.improve-row { display: flex; gap: 10px; padding: 9px 0; border-bottom: 1px solid rgba(44,74,59,.5); align-items: baseline; }
.improve-row:last-child { border-bottom: none; }
.improve-label {
  font-size: 10.5px; font-weight: 800; color: var(--gold);
  background: rgba(198,163,83,.15); border-radius: 6px; padding: 3px 8px;
  white-space: nowrap; flex-shrink: 0;
}
.improve-text { font-size: 13px; color: var(--text-2); line-height: 1.45; }

.wide-btn {
  width: 100%; height: 46px; border-radius: 13px; margin-top: 16px;
  background: var(--gold); color: var(--ink-on-gold); font-size: 15px; font-weight: 800;
}

/* confirm dialog */
.confirm-box { text-align: center; padding: 6px 2px; }
.confirm-box .cq { font-size: 16px; font-weight: 800; color: var(--text); margin-bottom: 6px; }
.confirm-box .cm { font-size: 12.5px; color: var(--text-2); line-height: 1.5; margin-bottom: 16px; }
.confirm-box .action-btn.destructive { background: var(--negative); border: none; color: #fff; }

/* ---------------------------------------------------------------- tablet (iPad)

   Breakpoints key off the *window* width, not the device, so Split View, Slide
   Over and Stage Manager windows fall back to the phone layout automatically
   once they get narrow. Three tiers:

     < 700px            phone (everything above)
     >= 700px           tablet: full-bleed screens, bigger cards, the bidding
                        panel docked as an in-flow band under the table
     >= 920px landscape tablet landscape: bidding panel becomes a right-hand
                        rail beside the table; analysis becomes two columns

   Card sizes use vmin so a card is the same physical size in portrait and
   landscape, with a vh cap so a short landscape window can't overflow.
------------------------------------------------------------------------------ */

@media (min-width: 700px) {
  #app { background: var(--bg-deep); }
  .screen { width: 100%; }

  /* Tablet card scale. The dummy keeps the same 80% relationship to the hand as
     on a phone — the extra screen goes to BOTH, not to the player's cards
     alone, which is how the two ended up at 120px against 58px. */
  .game-screen {
    --card-player: min(clamp(70px, 13.5vmin, 120px), 13.5vh);
    --card-dummy: clamp(70px, calc(var(--card-player) * 0.84), 96px);
  }

  /* --- chrome ------------------------------------------------------------ */
  .top-bar { padding: max(14px, env(safe-area-inset-top)) 20px 10px; gap: 12px; }
  .top-bar h2 { font-size: 20px; }
  .icon-btn { width: 42px; height: 42px; border-radius: 12px; font-size: 19px; }
  .top-brand { width: 42px; }
  .top-brand .rase-mark { width: 25px; height: auto; }

  /* --- home -------------------------------------------------------------- */
  .home-screen { padding: 30px 40px; gap: 30px; }
  .home-hero { padding-top: 46px; gap: 20px; }
  /* The lockup is SVG, so it scales by height and keeps its own proportions. */
  .home-hero .rase-tile { width: 116px; height: auto; }
  .home-hero .rase-word { height: 46px; width: auto; }
  .tagline { font-size: 17px; }
  .home-sub { font-size: 14px; }
  .chips-row { width: min(720px, 100%); gap: 14px; }
  .chip { padding: 15px 6px; }
  .chip .chip-value { font-size: 26px; }
  .chip .chip-title { font-size: 12px; }
  /* the four secondary destinations pair up under a full-width play button */
  .home-buttons {
    width: min(720px, 100%);
    display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
  }
  .home-buttons .btn-primary { grid-column: 1 / -1; padding: 19px 24px; }
  .btn-primary .bt { font-size: 20px; }
  .btn-primary .bs { font-size: 14px; }
  .menu-row { padding: 16px; gap: 15px; height: 100%; }
  .menu-icon { width: 48px; height: 48px; font-size: 22px; }
  .menu-row .mt { font-size: 17px; }
  .menu-row .ms { font-size: 13px; }
  .home-footer { font-size: 13.5px; }

  /* --- reading screens: one generous, centred measure --------------------- */
  .scroll-body { padding: 20px 24px calc(40px + env(safe-area-inset-bottom)); gap: 18px; }
  .scroll-body > * { width: 100%; max-width: 860px; margin-left: auto; margin-right: auto; }
  .section-card { padding: 18px 20px; }
  .section-card h3 { font-size: 17px; margin-bottom: 10px; }
  .section-caption { font-size: 13px; }
  .system-option { padding: 14px; }
  .system-option .ot { font-size: 16px; }
  .system-option .ob { font-size: 13px; }
  .conv-row { padding: 13px 0; }
  .conv-row .ct { font-size: 15px; }
  .conv-row .cb { font-size: 12.5px; }
  .seg { font-size: 13px; padding: 7px 15px; }
  .rules-section summary { font-size: 17px; padding: 16px 20px; }
  .rules-chapter > summary { padding: 16px 38px 16px 20px; }
  .rules-chapter .rules-section summary { font-size: 16px; padding: 14px 38px 14px 20px; }
  .rc-title { font-size: 17px; }
  .rc-caption { font-size: 13px; }
  .rules-text { font-size: 15px; padding: 2px 20px 20px; }
  /* Match the centred measure the scroll body uses on a tablet. */
  .rules-search { width: calc(100% - 48px); max-width: 860px; margin: 0 auto; }
  .rs-input { font-size: 16px; padding: 12px 0; }
  .rs-status { width: calc(100% - 48px); max-width: 860px; margin: 10px auto 0; font-size: 13px; }
  .rs-snip { font-size: 13px; }
  .rules-text th, .rules-text td { font-size: 14px; padding: 8px 12px; }
  .archive-title { font-size: 16px; }
  .archive-sub { font-size: 13px; }
  .improve-text { font-size: 14.5px; }

  /* --- sheets become centred dialogs ------------------------------------- */
  .overlay { align-items: center; padding: 32px; }
  .sheet {
    width: min(680px, 100%);
    border-radius: 20px;
    max-height: 84dvh;
    padding: 14px 26px calc(26px + env(safe-area-inset-bottom));
    animation: none;
  }
  .grab { display: none; }
  .sheet h3 { font-size: 19px; }
  .result-title { font-size: 30px; }
  .result-sub { font-size: 15px; }
  .result-entry { font-size: 14.5px; }
  .action-btn { height: 52px; font-size: 16px; }
  .pad-cols, .pad-row { grid-template-columns: 1fr 110px 110px; }
  .pad-cols { font-size: 12.5px; }
  .pad-row { font-size: 14px; }
  .pad-status { font-size: 14px; }

  /* --- game table -------------------------------------------------------- */
  /* grid so the bidding panel can be placed as a band (or, wide, a rail)
     without the markup changing */
  .game-screen {
    display: grid; min-height: 0;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto minmax(0, 1fr) auto;
    grid-template-areas: "top" "status" "table" "rail";
  }
  .game-screen > .top-bar { grid-area: top; }
  .game-screen > .status-bar { grid-area: status; }
  .game-screen > .table-area { grid-area: table; }
  .game-screen > .bidding-panel { grid-area: rail; }

  .status-bar { width: auto; margin: 0 20px; padding: 11px 18px; font-size: 14px; }
  .table-area { padding: 10px 20px calc(8px + env(safe-area-inset-bottom)); gap: 6px; }
  /* the top seat and the hand keep their natural height; only the middle
     (trick / felt mark) gives way when the window is short */
  .table-area > .seat-zone { flex-shrink: 0; }
  .table-center { gap: 14px; }

  .seat-badge { font-size: 15px; padding: 8px 17px; gap: 8px; border-radius: 22px; }
  .seat-badge .dot { width: 9px; height: 9px; }
  .seat-W, .seat-E { width: 116px; }
  /* An opponent's dummy turns the table into a compass, and the cross only
     lands on the centre line if BOTH side columns reserve the same width. The
     tray's side was sized by :has(.dummy-cols.side) and the card-backs' side
     was not, so the trick sat 36px right of centre at 768x1024. Capping the
     height matters as much: four vertical suit columns run 560px against the
     ~530px the band has, and the overflow reached down into the player's hand. */
  .game-screen.dummy-at-W .seat-W, .game-screen.dummy-at-W .seat-E,
  .game-screen.dummy-at-E .seat-W, .game-screen.dummy-at-E .seat-E {
    /* Sized to FIT, not capped and scrolled — a scrollbar here just hides the
       ♠Q and the ♥A above the fold, which is worse than a tight fit.
       A 2x2 block of VERTICAL columns runs 7.31 card-widths tall (1.45 for the
       first card in a column, 0.63 for each one under it, over two rows), so
       even at 64px it wanted ~474px against the 395px this band actually has.
       Horizontal fans cost one card-height per suit whatever its length —
       4 x 1.45 = 5.8 card-widths for the whole hand — which fits with room. */
    --dw-side: 58px;
    width: calc(var(--dw-side) * 3.6);
    max-height: 100%;
  }
  .game-screen.dummy-at-W .dummy-cols.side,
  .game-screen.dummy-at-E .dummy-cols.side {
    --dw: var(--dw-side, 58px);
    display: flex; flex-direction: column; flex-wrap: nowrap;
    width: auto; gap: 4px;
  }
  .game-screen.dummy-at-E .dummy-cols.side { align-items: flex-end; }
  .game-screen.dummy-at-W .dummy-cols.side .dummy-col,
  .game-screen.dummy-at-E .dummy-cols.side .dummy-col { flex-direction: row; }
  .game-screen.dummy-at-W .dummy-cols.side .dummy-col .pcard,
  .game-screen.dummy-at-E .dummy-cols.side .dummy-col .pcard {
    margin-top: 0; margin-left: calc(var(--dw) * -0.42);
  }
  .game-screen.dummy-at-W .dummy-cols.side .dummy-col .pcard:first-child,
  .game-screen.dummy-at-E .dummy-cols.side .dummy-col .pcard:first-child { margin-left: 0; }
  /* the cross gives back what the two side columns now take */
  .game-screen.dummy-at-W .table-center,
  .game-screen.dummy-at-E .table-center { --tw: min(44vmin, 290px); }
  .seat-zone { gap: 8px; }

  .hidden-hand .card-back { width: 46px; height: 66px; margin-left: -30px; }
  .hidden-hand.vertical .card-back { width: 62px; height: 43px; margin-top: -26px; }

  /* Dummy at the top lays each suit out horizontally — like the player's own
     fan — instead of in tall vertical columns. A long suit in a column costs
     ~5x its card height, which is more than any tablet can spare; a row costs
     one card height whatever the length. */
  .dummy-cols:not(.side) { --dw: var(--card-dummy); gap: 12px; }
  .dummy-cols:not(.side) .dummy-col { flex-direction: row; }
  .dummy-cols:not(.side) .dummy-col .pcard {
    margin-top: 0; margin-left: calc(var(--dw) * -0.58);
  }
  .dummy-cols:not(.side) .dummy-col .pcard:first-child { margin-left: 0; }
  .dummy-cols:not(.side) .pcard .idx {
    flex-direction: column; align-items: center; gap: 0;
    top: 5%; left: 8%; font-size: calc(var(--dw) * 0.34);
  }
  .dummy-cols:not(.side) .pcard .idx span:last-child { font-size: calc(var(--dw) * 0.34); }
  .dummy-cols:not(.side) .pcard.r10 .idx,
  .dummy-cols:not(.side) .pcard.r10 .idx span:last-child {
    left: 5%; font-size: calc(var(--dw) * 0.28);
  }
  .dummy-cols:not(.side) .pcard .pip { display: block; font-size: calc(var(--dw) * 0.5); }

  /* A side (E/W) dummy keeps vertical columns — that zone is a tall, narrow
     strip — wrapped 2x2 as on the phone. */
  /* A tablet has the room, so a side dummy takes the same token as the top
     tray rather than a flat 46px against a 120px hand. */
  .dummy-cols.side { --dw: calc(var(--card-dummy) * 0.8); width: calc(var(--dw) * 2 + 9px); }

  .table-center { --tw: min(44vmin, 340px); }
  .tc-pill { padding: 8px 18px; gap: 7px; }
  .tc-pill .l { font-size: 13.5px; }
  .tc-pill .v { font-size: 22px; }
  .trick-peek-label { font-size: 14px; padding: 6px 15px; }

  .hint-pill { font-size: 15.5px; padding: 10px 19px; gap: 6px; }
  .badge-row { gap: 11px; }

  .user-hand:not(.play2) {
    --uw: min(clamp(72px, 14vmin, 124px), 15vh);
    margin: 0; padding: 6px 0 8px;
  }
  .user-hand.play2 { --uw: var(--card-player); gap: 8px; }

  /* --- bidding panel: docked band under the table ------------------------ */
  .bidding-panel {
    position: static;
    max-height: none; overflow: visible;
    width: min(760px, calc(100% - 40px));
    margin: 0 auto calc(12px + env(safe-area-inset-bottom));
    padding: 12px; gap: 8px;
    box-shadow: 0 -6px 26px rgba(0,0,0,.35);
  }
  .bp-profile { font-size: 16px; }
  .bp-badge { font-size: 13.5px; padding: 5px 11px; }
  .bp-hcp { font-size: 15px; padding: 5px 13px; }
  .auction-grid-wrap { max-height: 230px; padding: 9px 13px; }
  .ag-head { font-size: 13.5px; padding-bottom: 5px; }
  .ag-cell { font-size: 17.5px; min-height: 28px; }
  .ag-rnd { font-size: 11px; }
  .ag-explain { font-size: 14.5px; padding: 11px; }
  .level-row, .strain-row, .special-row { gap: 6px; }
  .level-chip { height: 46px; font-size: 19px; }
  .strain-btn { height: 54px; font-size: 26px; }
  .strain-btn.nt { font-size: 19px; }
  .special-btn { height: 54px; font-size: 17px; }
  .suggest-btn { width: 64px; height: 54px; font-size: 20px; }
  .place-btn { height: 50px; font-size: 19px; }
  .meaning-call { font-size: 18px; }
  .meaning-text { font-size: 15.5px; -webkit-line-clamp: 3; line-clamp: 3; }
  .meaning-tag { font-size: 11.5px; padding: 4px 9px; }
  .meaning-hint { font-size: 14.5px; }
  .bp-waiting { font-size: 15.5px; }

  /* --- analysis ---------------------------------------------------------- */
  .deal-pin { padding: 12px 24px; }
  .deal-pin summary { font-size: 17px; }
  .deal-pin .hand-line { font-size: 13.5px; }
  .deal-pin .hand-block .hb-title { font-size: 12px; }
  .deal-pin .ddt-cell { font-size: 14px; }
  .deal-pin .ddt-seat { font-size: 13px; width: 38px; }
  .deal-pin .ddt-hcp-cell { font-size: 13px; }
  .ra-row { grid-template-columns: 72px 64px 64px 1fr; font-size: 13.5px; padding: 9px 6px; }
  .ra-row .exp { font-size: 12.5px; }
  .rp-trick { font-size: 14px; padding: 10px 14px; }
  .rp-note, .rp-reason { font-size: 12.5px; }
  .wide-btn { height: 52px; font-size: 16.5px; }
}

/* ------------------------------------------------- tablet, landscape/wide */

@media (min-width: 920px) and (orientation: landscape) {
  /* Bidding docks to a right-hand rail: the auction grid and the call buttons
     stay visible alongside the full 13-card fan instead of covering it. */
  .game-screen {
    /* Landscape is short, so both cards are height-bound — but they shrink
       TOGETHER. The brief's floor for phone landscape is the strictest in the
       document (80% of the player card) and it is enforced here. */
    --card-player: min(clamp(66px, 9.5vw, 118px), 17vh);
    --card-dummy: clamp(52px, calc(var(--card-player) * 0.84), 96px);
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-rows: auto auto minmax(0, 1fr);
    grid-template-areas:
      "top    top"
      "status status"
      "table  rail";
  }
  .bidding-panel {
    width: clamp(320px, 30vw, 400px);
    /* In the rail the panel is a GRID ITEM, not the floating overlay the phone
       uses. It kept the base rule's absolute positioning, so `align-self: end`
       did nothing and the panel was placed from the bottom of the screen by a
       phone-shaped offset — which is how its lowest controls ended up past the
       fold. Returning it to flow lets the grid area bound it. */
    position: relative; left: auto; right: auto; bottom: auto;
    /* hugs its content at the foot of the rail and grows upward as the
       auction fills, so the call buttons never move under a double-tap */
    align-self: end;
    /* The PANEL must not scroll — only its auction history may. It used to
       carry overflow-y:auto itself, so on a short landscape phone the
       Pass/Dbl/Rdbl row and Suggest sat 77px below the fold and four controls
       were unreachable. Clamping the panel and letting the grid absorb the
       squeeze keeps every control on screen at any height. */
    min-height: 0; max-height: 100%; overflow: hidden;
    margin: 0 20px calc(16px + env(safe-area-inset-bottom)) 0;
    box-shadow: 0 8px 26px rgba(0,0,0,.45);
  }
  /* A short landscape rail cannot hold the header on two lines, a full-height
     meaning slot AND three rows of 48px+ controls. The controls are the
     priority (they are the decision), so the header stays on one line and the
     two explanatory blocks become the flexible ones. */
  .bp-header { flex-wrap: nowrap; min-width: 0; }
  .bp-header .bp-profile { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .bidding-panel {
    gap: 4px; padding: 6px;
  }
  .bidding-panel .auction-grid-wrap {
    flex: 1 1 auto; min-height: 0; max-height: none; overflow-y: auto;
  }
  .bidding-panel .meaning-slot {
    flex: 0 1 auto; min-height: 0; overflow-y: auto;
  }

  /* Landscape is short (an 11" iPad is only 834pt tall), so the top dummy's
     rows shrink with the window, and a side dummy unwraps into a single row of
     four columns — the 2x2 wrap would be twice as tall as there is room for. */
  /* Never derive dummy width solely from the short edge: that reduced cards
     to 28px wide on a landscape iPhone.  A readable minimum wins; suit fans
     are compact enough to fit the available horizontal table space. */
  .dummy-cols:not(.side) { --dw: var(--card-dummy); }
  /* The rule above this used to be `min(38px, 4.6vh)`, which is precisely the
     "derive from the short edge" the comment forbids: on a 430pt-tall phone
     4.6vh is 20px, so the side dummy rendered at 20x29 — worse than the 28x40
     the brief called critical. The floor now wins, and the seat widens to hold
     four columns of it. Only the dummy's own side reserves the width; the
     opposite seat is .bare and stays auto. */
  .dummy-cols.side { --dw: max(52px, calc(var(--card-dummy) * 0.75)); flex-wrap: nowrap; width: auto; }
  /* The tablet block (min-width: 700px) also matches here, and its stacked
     horizontal fans are written with the dummy-at-* class, which outranks the
     one-class rule above — so a side dummy came out 348px tall against the
     307px this band has, and scrolled four of its cards out of sight.
     Landscape has the width for all four columns side by side; restore that
     at matching specificity. */
  .game-screen.dummy-at-W .dummy-cols.side,
  .game-screen.dummy-at-E .dummy-cols.side {
    --dw: max(52px, calc(var(--card-dummy) * 0.75));
    display: flex; flex-direction: row; flex-wrap: nowrap;
    width: auto; gap: 6px; align-items: flex-start;
  }
  .game-screen.dummy-at-E .dummy-cols.side { justify-content: flex-end; }
  .game-screen.dummy-at-W .dummy-cols.side .dummy-col,
  .game-screen.dummy-at-E .dummy-cols.side .dummy-col { flex-direction: column; }
  .game-screen.dummy-at-W .dummy-cols.side .dummy-col .pcard,
  .game-screen.dummy-at-E .dummy-cols.side .dummy-col .pcard {
    margin-left: 0; margin-top: calc(var(--dw) * -0.82);
  }
  .game-screen.dummy-at-W .dummy-cols.side .dummy-col .pcard:first-child,
  .game-screen.dummy-at-E .dummy-cols.side .dummy-col .pcard:first-child { margin-top: 0; }
  /* likewise the tablet's narrowed cross: this layout sizes its own. */
  .game-screen.dummy-at-W .table-center,
  .game-screen.dummy-at-E .table-center { --tw: min(44vmin, 340px); }
  /* Thirteen legible cards in four vertical columns are taller than a landscape
     phone has to give (a 4-card suit alone is 172px against ~127px of room), so
     the zone is bounded and scrolls its overflow. Scrolling is the lesser evil:
     the previous behaviour shrank the cards to 20px wide, and simply letting
     them grow spilled them over the Undo pill and the player's own hand.
     The brief's proper remedy is a top-centre dummy tray in this orientation —
     a layout change rather than a sizing one. */
  .seat-W, .seat-E {
    width: calc(var(--card-dummy) * 0.75 * 4 + 24px); min-width: 226px;
    max-height: 100%; overflow-y: auto; overscroll-behavior: contain;
  }
  /* Same equalisation as the tablet: the dummy's column was pinned narrower by
     the :has() rule while the card-backs opposite kept the full width, which
     put the cross 22px off centre — and off centre the other way when the
     dummy changed sides. */
  .game-screen.dummy-at-W .seat-W, .game-screen.dummy-at-W .seat-E,
  .game-screen.dummy-at-E .seat-W, .game-screen.dummy-at-E .seat-E {
    width: calc(var(--card-dummy) * 0.75 * 4 + 24px);
  }
  .table-middle { align-items: stretch; }

  /* The trick is a cross, so its box is ~0.88 of its width TALL. On a landscape
     phone the middle row only gets ~140px, and a 40vh cross wanted 151 — so it
     spilled onto the seat badges, the WE/THEY counters and the utility pills,
     printing them over a played card. Deriving the width from the height the
     row actually has keeps the cross inside its own space. */
  .table-center { --tw: min(44vmin, 360px, 30vh); gap: 6px; min-height: 0; }
  .trick-area { flex: 0 0 auto; }
  .user-hand:not(.play2) { --uw: min(clamp(72px, 13vmin, 124px), 17vh); }

  /* Width to spare: the hand plays as ONE row of 13 — the two rendered rows
     are joined end to end. Bigger cards, and the height of a whole row handed
     back to the table. */
  .user-hand.play2 {
    --uw: var(--card-player);
    flex-direction: row; gap: 0;
  }
  .user-hand.play2 .hand-line-row + .hand-line-row .pcard:first-child {
    margin-left: calc(var(--uw) * -0.1);   /* the join is always a suit change */
  }

  /* Analysis: the deal diagram becomes a permanent left rail so any note can
     be read against the layout without scrolling. */
  .review-screen {
    display: grid; min-height: 0;
    grid-template-columns: minmax(320px, 380px) minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
    grid-template-areas: "top top" "pin body";
  }
  .review-screen > .top-bar { grid-area: top; }
  .review-screen > .deal-pin {
    grid-area: pin; overflow-y: auto;
    box-shadow: none; border-right: 1px solid var(--stroke);
  }
  .review-screen > .scroll-body { grid-area: body; }
  .deal-pin .ddo-note { display: block; }
  .deal-pin .deal-diagram { gap: 10px; }
}

/* ------------------------------------------------- short landscape (phone) */

/* A landscape phone gives the middle row about 140px. The trick's cross is
   ~0.88 of its width tall, so a trick whose cards meet the brief's 64x92
   minimum would need a 197px-tall box — it cannot be done, and shrinking the
   cross until it fits produced 42x56 cards, smaller than the dummy beside them,
   which the brief explicitly forbids. Laying the four cards in a ROW costs one
   card height instead of one and a half, so they keep full size and stop
   colliding with the seat badges and counters. */
@media (orientation: landscape) and (max-height: 520px) {
  /* The row still needs ~16px that the middle row does not have, so the hand
     gives it up: 15vh keeps the player's cards at the brief's 64px floor while
     freeing the height the trick and the badges were fighting over. */
  .game-screen {
    --card-player: min(clamp(64px, 9.5vw, 118px), 15vh);
    --card-dummy: clamp(52px, calc(var(--card-player) * 0.84), 96px);
  }
  /* --- the brief's landscape play arrangement -------------------------------
     dummy tray left · trick and score centre · utilities right · hand across
     the bottom. Previously all three of those competed for one horizontal row
     between the status bar and the hand, which is why a legible dummy, a
     full-size trick and the utility pills could not coexist: the dummy zone
     (226px), the trick row (274px) and the opposite seat all wanted the same
     band. Naming the regions and laying them out as a grid gives each its own
     space instead of trading pixels between them.

     `display: contents` dissolves the two wrapper divs so their children can be
     placed directly — the markup keeps its nesting for every other layout. */
  .game-screen:not(.is-bidding) .table-area {
    /* The opponents get a column each, flanking the cross. 38px is what a
       card back needs to read as a card back and nothing more — see the
       silhouette note below. */
    --side: 38px;
    display: grid;
    grid-template-columns: minmax(0, auto) var(--side) 132px var(--side) minmax(0, auto);
    grid-template-rows: minmax(0, 1fr) auto;
    grid-template-areas:
      "dummy left trick right util"
      "hand  hand hand  hand  hand";
    align-items: center;
    column-gap: 6px; row-gap: 2px;
  }
  .game-screen:not(.is-bidding) .table-middle,
  .game-screen:not(.is-bidding) .seat-zone.seat-S { display: contents; }
  .game-screen:not(.is-bidding) .table-watermark { grid-area: trick; }

  .game-screen:not(.is-bidding) .seat-zone.is-dummy {
    grid-area: dummy; align-self: center; justify-self: start;
    max-height: 100%; overflow-y: auto; overscroll-behavior: contain;
  }
  /* The centre column is the cross plus the two counters — 166px of content
     against a 141px band. Centred, it overflowed EQUALLY in both directions:
     the North card was clipped by the status strip above and the counters sat
     on the player's own hand below. Anchoring it to the top of the band, and
     giving the counters the phone's proportions instead of the tablet's
     (this width matches the tablet query too), lands the whole column inside
     the space between the strip and the hand. */
  .game-screen:not(.is-bidding) .table-center {
    grid-area: trick; align-self: stretch; justify-content: flex-start; gap: 3px;
  }
  .game-screen:not(.is-bidding) .tc-pill { padding: 3px 9px; gap: 4px; }
  .game-screen:not(.is-bidding) .tc-pill .l { font-size: 10px; }
  .game-screen:not(.is-bidding) .tc-pill .v { font-size: 14px; }
  .game-screen:not(.is-bidding) .badge-row {
    grid-area: util; flex-direction: column; align-items: stretch;
    gap: 6px; justify-self: end; max-height: 100%; overflow-y: auto;
  }
  /* The rail is sized by its widest item, and that was "South · You ·
     Declarer" at 150px — half again the width of every button beside it, and
     the single largest claim on a band that now has to seat two more columns.
     It is also the one thing on this screen said twice: the status strip
     directly above reads "3NT by S — your play", and the hand it names is the
     one spread across the bottom of the screen. Dropping it here holds the
     rail to the width of its buttons (~100px) and leaves the dummy tray its
     full row. Every other layout keeps the badge. */
  .game-screen:not(.is-bidding):not(.dummy-at-W):not(.dummy-at-E)
    .badge-row > .seat-badge { display: none; }
  .game-screen:not(.is-bidding) .user-hand { grid-area: hand; }
  /* The opponents, as a silhouette down each side of the cross — the same cue
     portrait gets, on the budget this orientation has. What goes instead is
     their NAME: the badge costs 34px out of a 141px band, and in a 38px column
     "West · Robot" wraps to three lines taller than the stack it labels. The
     seat is still unambiguous — the stack sits on the side of the cross the
     seat plays into, and the status strip names the seat to play in words. */
  .game-screen:not(.is-bidding) .seat-zone.seat-W { grid-area: left; }
  .game-screen:not(.is-bidding) .seat-zone.seat-E { grid-area: right; }
  .game-screen:not(.is-bidding) .seat-zone.seat-W,
  .game-screen:not(.is-bidding) .seat-zone.seat-E {
    display: flex; max-height: 100%; gap: 0;
  }
  /* `.is-hidden` is carried only to match the phone block's 58px reservation,
     which is a wider selector than this one and otherwise wins — a 58px zone
     in a 38px column overhangs into the cross on one side and the utility
     rail on the other. */
  .game-screen:not(.is-bidding) .seat-zone.is-hidden.seat-W,
  .game-screen:not(.is-bidding) .seat-zone.is-hidden.seat-E { width: var(--side); }
  .game-screen:not(.is-bidding):not(.dummy-at-W):not(.dummy-at-E)
    .seat-zone.is-hidden .seat-badge { display: none; }
  .game-screen:not(.is-bidding) .seat-zone.is-hidden .hidden-hand { display: flex; }
  /* 6px of exposed edge — half what portrait affords. Thirteen of these stand
     96px tall, which is what clears the 120px cross beside them. */
  .game-screen:not(.is-bidding) .seat-zone.seat-W .hidden-hand.vertical .card-back,
  .game-screen:not(.is-bidding) .seat-zone.seat-E .hidden-hand.vertical .card-back {
    width: 34px; height: 24px; margin-top: -18px;
  }
  .game-screen:not(.is-bidding) .seat-zone.seat-W .hidden-hand.vertical .card-back:first-child,
  .game-screen:not(.is-bidding) .seat-zone.seat-E .hidden-hand.vertical .card-back:first-child {
    margin-top: 0;
  }
  /* The dummy tray now shares the band with two more columns. Sized `auto` it
     would win the argument and push the cross off the screen, so it wraps
     instead: a second row of fans inside a zone that already scrolls. Only the
     widest trays ever reach it. */
  .game-screen:not(.is-bidding) .seat-zone.is-dummy .dummy-cols:not(.side) { flex-wrap: wrap; }

  /* Each suit fans HORIZONTALLY. This is the tray the tablet query already
     builds, but that query starts at 700px — so a 667px landscape phone fell
     back to tall vertical columns, and a six-card suit stood 239px high in a
     171px band: the dummy's own long suit scrolled out of sight. A row costs
     one card height whatever the suit's length, which is the only shape this
     band can hold. Repeating it here binds it to the ORIENTATION rather than
     to a width, and is a no-op on the wide landscape phones the tablet query
     was already covering. */
  /* Row-gap is tighter than column-gap: the second row only exists on the
     narrowest landscape phones, where every pixel of it is height the band
     does not have. */
  .dummy-cols:not(.side) { --dw: var(--card-dummy); gap: 4px 10px; flex-wrap: wrap; }
  .dummy-cols:not(.side) .dummy-col { flex-direction: row; }
  .dummy-cols:not(.side) .dummy-col .pcard {
    margin-top: 0; margin-left: calc(var(--dw) * -0.58);
  }
  .dummy-cols:not(.side) .dummy-col .pcard:first-child { margin-left: 0; }
  .dummy-cols:not(.side) .pcard .idx {
    flex-direction: column; align-items: center; gap: 0;
    top: 5%; left: 8%; font-size: calc(var(--dw) * 0.34);
  }
  .dummy-cols:not(.side) .pcard .idx span:last-child { font-size: calc(var(--dw) * 0.34); }
  .dummy-cols:not(.side) .pcard.r10 .idx,
  .dummy-cols:not(.side) .pcard.r10 .idx span:last-child {
    left: 5%; font-size: calc(var(--dw) * 0.28);
  }
  .dummy-cols:not(.side) .pcard .pip { display: block; font-size: calc(var(--dw) * 0.5); }

  .table-center { gap: 4px; }
  /* The trick is a CROSS here whoever holds the dummy, not a left-to-right
     row. A row is perfectly readable and says nothing about WHO played each
     card, which is the one thing a trick has to say. 52x74 is under the
     brief's floor for a trick card and is what buys the separation inside the
     120px of height this layout has: at 92px tall the North and South cards
     cannot be told apart. */
  .trick-area {
    display: block; position: relative;
    width: 132px; height: 120px;
  }
  .trick-area .felt-print { display: none; }
  /* `relative`, not `static`: the rank and pip inside are absolutely positioned
     against the card, so removing the card as a containing block sent them to
     the nearest positioned ancestor — a stray K in one corner of the table and
     a pip floating over a blank card. Relative keeps it a flex item AND keeps
     its own contents anchored to it. */
  .trick-card {
    position: absolute; inset: auto;
    width: 52px; height: 74px;
  }
  .trick-card.pos-N { top: 0; left: 40px; bottom: auto; right: auto; transform: rotate(-3deg); }
  .trick-card.pos-S { bottom: 0; left: 40px; top: auto; right: auto; transform: rotate(3deg); }
  .trick-card.pos-W { top: 23px; left: 0; bottom: auto; right: auto; transform: rotate(-6deg); }
  .trick-card.pos-E { top: 23px; right: 0; bottom: auto; left: auto; transform: rotate(6deg); }
  .trick-card .tc-idx { font-size: 17px; top: 5%; left: 8%; }
  .trick-card .tc-pip { font-size: 26px; }

  /* ---- an OPPONENT's dummy: the compass table ------------------------------
     The flat row above is readable but says nothing about WHO played each
     card, which is the one thing a trick has to say. Here the four seats take
     their compass positions and the trick goes back to a cross, so every card
     sits toward the hand that played it.

     The budget is exact and worth recording. 250px of table height, less
     padding, leaves 236: 24 (partner strip) + 124 (cross) + 88 (hand). 124px
     cannot separate two 92px cards, so the trick card drops to 52x74 here —
     under the brief's floor, and the price of a cross you can read a seat off.
     The hand goes to ONE row for the same reason; two rows cost 90px that the
     cross needs more. */
  .game-screen.dummy-at-W:not(.is-bidding),
  .game-screen.dummy-at-E:not(.is-bidding) { --card-player: 46px; }

  .game-screen.dummy-at-W:not(.is-bidding) .table-area,
  .game-screen.dummy-at-E:not(.is-bidding) .table-area {
    padding: 8px 14px calc(6px + env(safe-area-inset-bottom));
    grid-template-rows: 24px minmax(0, 1fr) auto;
    column-gap: 6px; row-gap: 0;
    align-items: center; justify-items: center;
  }
  /* Area names are SCREEN positions, never seat roles — "left" is always the
     left-hand column. Only the widths swap, and the utility rail swaps with
     them: parked on a fixed edge it stacked against the wide dummy column and
     shoved the cross 150px off centre. Mirrored, the cross lands within 9px of
     the screen's centre line either way. */
  .game-screen.dummy-at-W:not(.is-bidding) .table-area {
    grid-template-columns: 312px 182px 156px minmax(0, 1fr);
    grid-template-areas:
      "top  top   top   util"
      "left trick right util"
      "hand hand  hand  util";
  }
  .game-screen.dummy-at-E:not(.is-bidding) .table-area {
    grid-template-columns: minmax(0, 1fr) 156px 182px 312px;
    grid-template-areas:
      "util top  top   top"
      "util left trick right"
      "util hand hand  hand";
  }
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.seat-W,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-W { grid-area: left; display: flex; }
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.seat-E,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-E { grid-area: right; display: flex; }
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.seat-N,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-N {
    grid-area: top; display: flex; flex-direction: row; align-items: center; gap: 8px;
  }
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.is-hidden .hidden-hand,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.is-hidden .hidden-hand { display: flex; }

  /* The tricks-won counters are a scoreboard, not table furniture: moving them
     out of the centre frees the whole middle row for the cross. display:
     contents dissolves .table-center so both children place directly, the
     same trick .table-middle already uses. */
  .game-screen.dummy-at-W:not(.is-bidding) .table-center,
  .game-screen.dummy-at-E:not(.is-bidding) .table-center { display: contents; }
  .game-screen.dummy-at-W:not(.is-bidding) .trick-counters,
  .game-screen.dummy-at-E:not(.is-bidding) .trick-counters {
    grid-area: top; justify-self: end; align-self: center; display: flex; gap: 6px;
  }
  .game-screen.dummy-at-W:not(.is-bidding) .trick-counters .tc-pill,
  .game-screen.dummy-at-E:not(.is-bidding) .trick-counters .tc-pill,
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone .seat-badge,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone .seat-badge {
    font-size: 10px; padding: 2px 8px;
  }

  /* the cross restored, with the spread the flat row was hiding */
  .game-screen.dummy-at-W:not(.is-bidding) .trick-area,
  .game-screen.dummy-at-E:not(.is-bidding) .trick-area {
    grid-area: trick; display: block; position: relative;
    width: 174px; height: 124px;
  }
  .game-screen.dummy-at-W:not(.is-bidding) .trick-card.pos-N,
  .game-screen.dummy-at-E:not(.is-bidding) .trick-card.pos-N { top: 0; left: 61px; transform: rotate(-3deg); }
  .game-screen.dummy-at-W:not(.is-bidding) .trick-card.pos-S,
  .game-screen.dummy-at-E:not(.is-bidding) .trick-card.pos-S { bottom: 0; left: 61px; transform: rotate(3deg); }
  .game-screen.dummy-at-W:not(.is-bidding) .trick-card.pos-W,
  .game-screen.dummy-at-E:not(.is-bidding) .trick-card.pos-W { top: 25px; left: 0; transform: rotate(-6deg); }
  .game-screen.dummy-at-W:not(.is-bidding) .trick-card.pos-E,
  .game-screen.dummy-at-E:not(.is-bidding) .trick-card.pos-E { top: 25px; right: 0; transform: rotate(6deg); }

  /* ONE row, for every dummy position. Two rows cost ~90px of a 232px table,
     which is the whole reason a North dummy was squeezed into a 52px scroll
     box against the 117px its four fans need — eleven of its cards were off
     screen. 812px of width has room for thirteen cards in a line. */
  .game-screen:not(.is-bidding) .user-hand.play2 {
    flex-direction: row; align-items: flex-end; gap: 10px;
  }

  /* Dummy: 2 x 2 grid of HORIZONTAL fans — one card-height per suit whatever
     its length, which is what fits 13 cards in the 124px middle row. `auto`
     columns, not 1fr: a singleton suit beside a five-card fan left a 120px
     hole. Card sizes across the table are hand 46 / dummy 40 / trick 52; they
     were 56 / 36 / 52, and that 20px spread is what read as compressed. */
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.seat-W.is-dummy,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-E.is-dummy {
    width: auto; max-height: none; overflow: visible;
    align-self: center; justify-self: start; align-items: flex-start;
  }
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-E.is-dummy {
    justify-self: end; align-items: flex-end;
  }
  .game-screen.dummy-at-W:not(.is-bidding) .dummy-cols.side,
  .game-screen.dummy-at-E:not(.is-bidding) .dummy-cols.side {
    --dw: 40px;
    display: grid; grid-template-columns: repeat(2, auto);
    width: auto; column-gap: 14px; row-gap: 2px;
  }
  .game-screen.dummy-at-W:not(.is-bidding) .dummy-cols.side .dummy-col,
  .game-screen.dummy-at-E:not(.is-bidding) .dummy-cols.side .dummy-col { flex-direction: row; }
  .game-screen.dummy-at-W:not(.is-bidding) .dummy-cols.side .dummy-col .pcard,
  .game-screen.dummy-at-E:not(.is-bidding) .dummy-cols.side .dummy-col .pcard {
    margin-top: 0; margin-left: calc(var(--dw) * -0.34);
  }
  .game-screen.dummy-at-W:not(.is-bidding) .dummy-cols.side .dummy-col .pcard:first-child,
  .game-screen.dummy-at-E:not(.is-bidding) .dummy-cols.side .dummy-col .pcard:first-child { margin-left: 0; }

  /* the declarer's stack opposite, and the partner's strip across the top */
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.seat-E,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-W { width: 156px; }
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-W { justify-self: end; }
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-W .hidden-hand { justify-content: flex-end; }
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.seat-E .hidden-hand,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-W .hidden-hand { flex-direction: row; }
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.seat-E .hidden-hand .card-back,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-W .hidden-hand .card-back {
    width: 38px; height: 55px; margin-top: 0; margin-left: -28px;
  }
  .game-screen.dummy-at-W:not(.is-bidding) .seat-N .hidden-hand .card-back,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-N .hidden-hand .card-back {
    width: 20px; height: 22px; margin-left: -12px;
  }

  /* The tray is centred in its row and runs a few px taller than it, so its
     lowest card finished flush against the top of the player's hand — touching
     exactly, gap 0. A margin lifts it clear without touching the card sizes. */
  .game-screen.dummy-at-W .seat-zone.seat-W.is-dummy,
  .game-screen.dummy-at-E .seat-zone.seat-E.is-dummy { margin-bottom: 12px; }

  /* the rail gives back ~38px, which goes into the cross'+String.fromCharCode(39)+'s spread */
  .game-screen.dummy-at-W:not(.is-bidding) .badge-row .hint-pill,
  .game-screen.dummy-at-E:not(.is-bidding) .badge-row .hint-pill {
    font-size: 11px; padding: 6px 10px; gap: 4px;
  }
}

/* ----------------------------------------------------- phone portrait play */

/* The brief's portrait arrangement: dummy tray at the top, trick in the centre,
   a utility row, then the two-row hand. Previously the dummy sat wherever the
   compass put it — a tall 2x2 block at the side when East or West declared —
   which squeezed the centre and let the trick counters collide with the cards.
   Naming the regions and laying them out in rows gives each its own band, the
   same fix the landscape rail got. */
@media (max-width: 699px) and (orientation: portrait) {
  .game-screen:not(.is-bidding) .table-area {
    /* 58px matches the column width the phone block reserves for a concealed
       seat, so the zone fills its grid cell exactly instead of overhanging it. */
    --side: 58px;
    display: grid;
    grid-template-columns: var(--side) minmax(0, 1fr) var(--side);
    grid-template-rows: auto minmax(0, 1fr) auto auto;
    grid-template-areas:
      "dummy dummy dummy"
      "left  trick right"
      "util  util  util"
      "hand  hand  hand";
    row-gap: 4px; column-gap: 4px; align-items: center; justify-items: center;
  }
  .game-screen:not(.is-bidding) .table-middle,
  .game-screen:not(.is-bidding) .seat-zone.seat-S { display: contents; }
  /* display:contents dissolves the band, so the mark becomes a grid item and
     has to name its cell — the trick's — to stay centred on the cross. */
  .game-screen:not(.is-bidding) .table-watermark { grid-area: trick; }

  .game-screen:not(.is-bidding) .seat-zone.is-dummy {
    grid-area: dummy; width: 100%; max-height: 42vh;
    overflow-y: auto; overscroll-behavior: contain;
  }
  /* With the dummy in a top tray and the empty side seats gone, the centre has
     the whole width — enough for the trick to reach the brief's 64x92 floor
     instead of the 60x83 it was squeezed to. */
  .game-screen:not(.is-bidding) .table-center { grid-area: trick; --tw: min(228px, 53vw); }
  .game-screen:not(.is-bidding) .badge-row { grid-area: util; }
  .game-screen:not(.is-bidding) .user-hand { grid-area: hand; width: 100%; }

  /* The opponents, as a stack of backs down each edge. They carry no
     information — every concealed hand holds the same count — and that is why
     they used to be dropped here. What went with them was the table itself:
     with the dummy hoisted into the top band, nothing on screen said that the
     cards arriving from the left and right belong to two players sitting
     there, and a trick played into empty margins reads as a solitaire layout
     rather than as two sides. The column is deliberately thin: a silhouette to
     place the seat by, not a hand anyone is meant to read. */
  .game-screen:not(.is-bidding) .seat-zone.seat-W { grid-area: left; }
  .game-screen:not(.is-bidding) .seat-zone.seat-E { grid-area: right; }
  .game-screen:not(.is-bidding) .seat-zone.seat-W,
  .game-screen:not(.is-bidding) .seat-zone.seat-E {
    display: flex; width: var(--side); max-height: 100%; gap: 5px;
  }
  .game-screen:not(.is-bidding) .seat-zone.is-hidden .hidden-hand { display: flex; }
  /* Overlapped to ~9px of exposed edge: enough that each card reads as its own
     outline, short enough that thirteen of them clear the trick's height. */
  .game-screen:not(.is-bidding) .seat-zone.seat-W .hidden-hand.vertical .card-back,
  .game-screen:not(.is-bidding) .seat-zone.seat-E .hidden-hand.vertical .card-back {
    width: 46px; height: 32px; margin-top: -23px;
  }
  .game-screen:not(.is-bidding) .seat-zone.seat-W .hidden-hand.vertical .card-back:first-child,
  .game-screen:not(.is-bidding) .seat-zone.seat-E .hidden-hand.vertical .card-back:first-child {
    margin-top: 0;
  }
  .game-screen:not(.is-bidding) .seat-zone.seat-W .seat-badge,
  .game-screen:not(.is-bidding) .seat-zone.seat-E .seat-badge {
    font-size: 10px; padding: 3px 5px; letter-spacing: -.1px;
  }

  /* In a wide, short tray each suit fans HORIZONTALLY — a vertical column costs
     about five card-heights for a long suit, a row costs one whatever its
     length. Four fans do not fit across 430px, so they wrap two-by-two. */
  .game-screen:not(.is-bidding) .dummy-cols,
  .game-screen:not(.is-bidding) .dummy-cols.side {
    --dw: var(--card-dummy);
    display: flex; flex-wrap: wrap; width: auto; max-width: 100%;
    justify-content: center; align-items: flex-start; gap: 6px 14px;
  }
  .game-screen:not(.is-bidding) .dummy-cols .dummy-col { flex-direction: row; }
  .game-screen:not(.is-bidding) .dummy-cols .dummy-col .pcard {
    margin-top: 0; margin-left: calc(var(--dw) * -0.58);
  }
  .game-screen:not(.is-bidding) .dummy-cols .dummy-col .pcard:first-child { margin-left: 0; }
  /* vertical index (rank above suit), as the tablet tray uses */
  .game-screen:not(.is-bidding) .dummy-cols .pcard .idx {
    flex-direction: column; align-items: center; gap: 0;
    top: 5%; left: 8%; font-size: calc(var(--dw) * 0.34);
  }
  .game-screen:not(.is-bidding) .dummy-cols .pcard .idx span:last-child { font-size: calc(var(--dw) * 0.34); }
  .game-screen:not(.is-bidding) .dummy-cols .pcard.r10 .idx,
  .game-screen:not(.is-bidding) .dummy-cols .pcard.r10 .idx span:last-child {
    left: 5%; font-size: calc(var(--dw) * 0.28);
  }
  .game-screen:not(.is-bidding) .dummy-cols .pcard .pip { display: block; font-size: calc(var(--dw) * 0.5); }

  /* ---- an OPPONENT's dummy: the compass table ------------------------------
     The band arrangement above hoists the dummy to the top row whichever seat
     holds it, which throws away the one cue that says WHICH opponent you are
     looking at. When the dummy belongs to the opposition it goes to its own
     edge instead, the top band is free for the partner's concealed hand, and
     the declarer takes the column opposite the tray — the compass, as at a
     table. `dummy-at-W`/`dummy-at-E` are set in gameScreen().

     The side columns are deliberately EQUAL. Sized to their contents the trick
     landed 26px off the screen's centre line; at 92px each it sits on it. */
  .game-screen.dummy-at-W:not(.is-bidding) .table-area,
  .game-screen.dummy-at-E:not(.is-bidding) .table-area {
    --side: 92px;
    grid-template-columns: var(--side) minmax(0, 1fr) var(--side);
    grid-template-rows: auto minmax(0, 1fr) auto auto;
    grid-template-areas:
      "top   top   top"
      "left  trick right"
      "util  util  util"
      "hand  hand  hand";
    column-gap: 6px; align-items: center; justify-items: center;
  }
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.seat-W,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-W { grid-area: left; }
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.seat-E,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-E { grid-area: right; }
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.seat-W,
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.seat-E,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-W,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-E {
    display: flex; width: var(--side); max-height: 100%;
  }
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.seat-N,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-N {
    grid-area: top; display: flex; flex-direction: row;
    align-items: center; justify-content: center; gap: 10px; width: 100%;
  }
  .game-screen.dummy-at-W:not(.is-bidding) .table-center,
  .game-screen.dummy-at-E:not(.is-bidding) .table-center {
    grid-area: trick; --tw: min(159px, 43vw);
  }
  /* Every seat present. The count is identical in each concealed hand — one
     card leaves every hand per trick — so the backs carry no information the
     status strip lacks. They earn their place by making the table read as the
     game: two empty chairs opposite does not. */
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.is-hidden .hidden-hand,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.is-hidden .hidden-hand { display: flex; }

  /* The dummy's four suits as a 2 x 2 grid of VERTICAL columns, in ♠♥ / ♣♦
     reading order. A grid rather than flex-wrap: a singleton suit beside a
     five-card fan left a ragged hole. Two columns is what keeps the tray
     inside 92px and the trick on the centre line. */
  .game-screen.dummy-at-W:not(.is-bidding) .dummy-cols.side,
  .game-screen.dummy-at-E:not(.is-bidding) .dummy-cols.side {
    --dw: 42px;
    display: grid; grid-template-columns: repeat(2, var(--dw));
    justify-content: center; align-items: start;
    column-gap: 6px; row-gap: 4px; width: auto;
  }
  .game-screen.dummy-at-W:not(.is-bidding) .dummy-cols.side .dummy-col,
  .game-screen.dummy-at-E:not(.is-bidding) .dummy-cols.side .dummy-col { flex-direction: column; }
  .game-screen.dummy-at-W:not(.is-bidding) .dummy-cols.side .dummy-col .pcard,
  .game-screen.dummy-at-E:not(.is-bidding) .dummy-cols.side .dummy-col .pcard {
    margin-left: 0; margin-top: calc(var(--dw) * -0.72);
  }
  .game-screen.dummy-at-W:not(.is-bidding) .dummy-cols.side .dummy-col .pcard:first-child,
  .game-screen.dummy-at-E:not(.is-bidding) .dummy-cols.side .dummy-col .pcard:first-child { margin-top: 0; }
  /* the horizontal index (rank beside suit) reads in the ~30px strip a
     vertical column leaves visible; the stacked one clipped "10" to "1(" */
  .game-screen.dummy-at-W:not(.is-bidding) .dummy-cols.side .pcard .idx,
  .game-screen.dummy-at-E:not(.is-bidding) .dummy-cols.side .pcard .idx {
    flex-direction: row; align-items: center; gap: 1px;
    top: 5%; left: 10%; font-size: calc(var(--dw) * 0.44);
  }
  .game-screen.dummy-at-W:not(.is-bidding) .dummy-cols.side .pcard .idx span:last-child,
  .game-screen.dummy-at-E:not(.is-bidding) .dummy-cols.side .pcard .idx span:last-child {
    font-size: calc(var(--dw) * 0.46);
  }
  .game-screen.dummy-at-W:not(.is-bidding) .dummy-cols.side .pcard.r10 .idx,
  .game-screen.dummy-at-E:not(.is-bidding) .dummy-cols.side .pcard.r10 .idx,
  .game-screen.dummy-at-W:not(.is-bidding) .dummy-cols.side .pcard.r10 .idx span:last-child,
  .game-screen.dummy-at-E:not(.is-bidding) .dummy-cols.side .pcard.r10 .idx span:last-child {
    left: 6%; font-size: calc(var(--dw) * 0.34);
  }
  .game-screen.dummy-at-W:not(.is-bidding) .dummy-cols.side .pcard .pip,
  .game-screen.dummy-at-E:not(.is-bidding) .dummy-cols.side .pcard .pip { display: none; }

  /* The declarer's stack opposite. Tightened so it does not out-run the dummy
     beside it — but the FIRST card keeps its margin, or the whole stack rides
     up over its own seat badge. */
  .game-screen.dummy-at-W:not(.is-bidding) .hidden-hand.vertical .card-back,
  .game-screen.dummy-at-E:not(.is-bidding) .hidden-hand.vertical .card-back {
    width: 52px; height: 34px; margin-top: -25px;
  }
  .game-screen.dummy-at-W:not(.is-bidding) .hidden-hand.vertical .card-back:first-child,
  .game-screen.dummy-at-E:not(.is-bidding) .hidden-hand.vertical .card-back:first-child { margin-top: 0; }

  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.seat-W .seat-badge,
  .game-screen.dummy-at-W:not(.is-bidding) .seat-zone.seat-E .seat-badge,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-W .seat-badge,
  .game-screen.dummy-at-E:not(.is-bidding) .seat-zone.seat-E .seat-badge {
    font-size: 11px; padding: 3px 7px; text-align: center; gap: 4px;
  }
}
