/* ========== GAME TABLE ==========
   One felt table for the whole hand. Seats sit around it (you at the
   bottom); .center-zone holds the phase panel (bid / trump / meld / trick).
   Bottom-rail clearance comes from public/clearance.js (--hand-h / --chrome-h);
   read docs/MODE_SELECTOR_REBASE.md before touching hand/chat layout. */

.game-table {
  position: relative;
  display: grid;
  grid-template-areas:
    ".    top    ."
    "left center right"
    ".    bottom .";
  /* The middle gets its room first: side seats grow to 250px only from what
     is left (a grid grows fixed tracks before 1fr ones, so a table narrowed
     by the Teach Me or chat panel squeezed the bid panel to one word wide). */
  grid-template-columns: minmax(120px, 250px) minmax(min(280px, 40%), 1fr) minmax(120px, 250px);
  grid-template-rows: auto minmax(0, 1fr) auto;
  gap: 10px 14px;
  /* Exactly the space between the top bar and the hand rail, so your own
     seat never slips under the hand; only the center scrolls if needed. */
  height: calc(100dvh - var(--chrome-h) - var(--hand-h) - 12px);
  min-height: 400px;
  margin: 12px auto 0;
  padding: 16px 18px 4px;
  width: min(1180px, calc(100% - 24px));
}

/* The felt cloth sits behind the whole grid. */
.table-cloth {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: 36px;
  background:
    radial-gradient(ellipse at 50% 40%, rgba(255, 255, 255, 0.07), transparent 65%),
    radial-gradient(ellipse at 50% 50%, var(--felt) 0%, var(--table) 55%, var(--felt-edge) 100%);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.06),
    inset 0 0 60px rgba(0, 0, 0, 0.45),
    0 20px 50px rgba(0, 0, 0, 0.35);
  pointer-events: none;
}

.player-top { grid-area: top; justify-self: center; }
.player-left { grid-area: left; align-self: center; }
.player-right { grid-area: right; align-self: center; }
.player-bottom { grid-area: bottom; justify-self: center; }

/* Belt-and-braces fallback for spectator/undefined seats (F-P1). */
.player-undefined { grid-area: bottom; }

/* ---- Seats ---- */
.player-zone {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 0;
  max-width: 100%;
}

.seat-plate {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px 8px 8px;
  border-radius: 16px;
  background: rgba(8, 22, 14, 0.78);
  border: 1px solid var(--panel-border);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.3);
  min-width: 0;
  max-width: 100%;
  transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
}

.seat-plate.is-empty {
  padding: 10px 16px;
  color: var(--text-dim);
  font-size: 0.85rem;
}

.seat-avatar {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  font-weight: 800;
  font-size: 1rem;
  color: #10241a;
  background: var(--team-ew);
}

.team-ns .seat-avatar { background: var(--team-ns); }

.seat-info {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

/* Your own plate: "You" plus the trump glyph never shrink under the chips
   beside them (they used to overlap the tricks count on phones). */
.player-bottom .seat-info { flex-shrink: 0; }
.player-bottom .player-name { min-width: 0; overflow: visible; }

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

.player-zone .player-name {
  font-weight: 700;
  font-size: 0.95rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 2.6em;
}

/* Fixes file item 5: large trump glyph beside the bid winner's name.
   In normal flow (never absolutely positioned) so it cannot cover cards. */
.player-zone .trump-symbol {
  font-size: 1.5rem;
  line-height: 1;
  color: #fff;
  text-shadow: 0 0 10px rgba(255, 255, 255, 0.35);
  flex-shrink: 0;
}

.player-zone .trump-symbol.suit-H,
.player-zone .trump-symbol.suit-D { color: #ff8a80; }

.seat-tags {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
}

.seat-chip {
  font-size: var(--text-micro, 0.6875rem);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-dim);
}

/* Fixes file item 5: the Dealer label */
.seat-chip.dealer-caption {
  background: rgba(227, 181, 74, 0.18);
  color: var(--gold-strong);
}

.seat-chip.presence-idle-dot {
  background: rgba(255, 179, 0, 0.2);
  color: #ffcc66;
}

.seat-status {
  margin-left: 4px;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 0.82rem;
  font-weight: 700;
  white-space: nowrap;
  background: rgba(255, 255, 255, 0.08);
  font-variant-numeric: tabular-nums;
}

.seat-status.is-high {
  background: var(--gold);
  color: #1b1405;
}

.seat-status.is-pass {
  color: var(--text-dim);
  background: transparent;
  border: 1px dashed rgba(255, 255, 255, 0.18);
}

.seat-status.is-thinking {
  color: var(--gold-strong);
  background: rgba(227, 181, 74, 0.12);
}

.seat-status.is-ready::after {
  content: " \2713";
  color: var(--good);
}

/* Side seats on wide screens: status under the name, so names fit. */
.player-left .seat-plate,
.player-right .seat-plate {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  column-gap: 10px;
  row-gap: 4px;
}

.player-left .seat-avatar,
.player-right .seat-avatar { grid-row: 1 / span 2; }

.player-left .seat-status,
.player-right .seat-status {
  grid-column: 2;
  justify-self: start;
  margin-left: 0;
  padding: 2px 8px;
  font-size: 0.76rem;
}

/* Partner seat */
.player-zone.teammate .seat-plate {
  border-color: rgba(255, 255, 255, 0.2);
}

/* Whose turn. The glow breathes by fading a ring drawn once (opacity runs
   on the compositor); animating box-shadow itself repainted the plate on
   every frame for as long as anyone was thinking. */
.player-zone.current-player .seat-plate {
  position: relative;
  border-color: var(--gold);
  box-shadow: 0 0 0 2px rgba(227, 181, 74, 0.3), 0 8px 24px rgba(0, 0, 0, 0.35);
}
.player-zone.current-player .seat-plate::after {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  box-shadow: 0 0 0 5px rgba(227, 181, 74, 0.16);
  pointer-events: none;
  animation: seat-turn 1.6s ease-in-out infinite alternate;
}

@keyframes seat-turn {
  from { opacity: 0.15; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .player-zone.current-player .seat-plate::after { animation: none; opacity: 0.6; }
}

.player-zone.presence-dropped .seat-plate,
.player-zone.presence-away .seat-plate {
  opacity: 0.6;
  border-style: dashed;
}

/* Your-turn play button on your own plate */
.player-play-btn {
  margin-left: 4px;
  margin-block: -2px; /* fits the plate's height: no hop when it comes and goes */
  padding: 6px 14px;
  min-height: 40px;
  border-radius: 999px;
  border: 0;
  background: var(--gold);
  color: #1b1405;
  font-weight: 800;
  font-size: 0.85rem;
  cursor: pointer;
  white-space: nowrap;
}

.player-play-btn:disabled {
  background: rgba(227, 181, 74, 0.16);
  color: var(--gold-strong);
  cursor: default;
}

/* ---- Center ---- */
.center-zone {
  grid-area: center;
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* safe: a panel taller than the felt starts at the top. It never
     scrolls: fitCenter (render/table.js) makes it compact or scales it. */
  justify-content: safe center;
  gap: 12px;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
  padding: 4px;
}

/* Larger Text: the center scrolls at full size instead of shrinking
   (fitCenter's largeText, as in the app). */
.center-zone.is-large-text { overflow-y: auto; justify-content: flex-start; overscroll-behavior: contain; }

/* A phone on its side with Larger Text: the primary action stays pinned at
   the bottom of the scrolling center (render/table.js fitCenter). */
@media (max-height: 500px) and (orientation: landscape) {
  .center-zone.is-large-text .bid-actions,
  .center-zone.is-large-text .trump-actions,
  .center-zone.is-large-text .meld-actions { position: sticky; bottom: 0; z-index: 2; background: var(--panel); box-shadow: 0 -8px 12px var(--panel); }
}

/* Past the 44 px / 11 px floor the panel keeps its size and the felt
   scrolls (fitCenter, build 24: never a shrunken button). docs/TABLE_LAYOUT.md
   rule 2: a fade and a chevron at the bottom edge while more is below, and
   the primary action pinned at the bottom of what scrolls. */
.center-zone.is-scroll { overflow-y: auto; justify-content: flex-start; overscroll-behavior: contain; }
.center-zone.is-scroll.has-more-below { --fade-end: calc(100% - var(--pin-h, 0px)); -webkit-mask-image: linear-gradient(to bottom, #000 calc(var(--fade-end) - 28px), rgba(0, 0, 0, 0.35) var(--fade-end), #000 var(--fade-end)); mask-image: linear-gradient(to bottom, #000 calc(var(--fade-end) - 28px), rgba(0, 0, 0, 0.35) var(--fade-end), #000 var(--fade-end)); }
/* The fade and its chevron end above the pinned action (N-004): the action
   itself is never under the fade. */
.center-zone.is-scroll.has-more-below::after { content: "\2304"; position: sticky; bottom: var(--pin-h, 0px); align-self: center; flex: none; margin-top: -18px; height: 18px; line-height: 12px; color: var(--gold-strong); font-size: 1.1rem; pointer-events: none; z-index: 3; }
.center-zone.is-scroll .bid-actions,
.center-zone.is-scroll .trump-actions,
.center-zone.is-scroll .meld-actions { position: sticky; bottom: 0; z-index: 2; background: var(--panel); box-shadow: 0 -8px 12px var(--panel); }

/* fitCenter's steps for a tall panel (the meld board: css/meld.css). */
.center-panel.fit-compact { gap: 4px; }
.bid-panel.fit-compact { padding: 10px 14px; }
.bid-panel.fit-compact .bid-amount-display { font-size: 2.2rem; }
.bid-panel.fit-compact .bid-stepper { margin-top: 0; }
/* Compact: the high bid on one line ("HIGH BID 51 Tommy"); the step rule
   goes (the app has none). */
.bid-panel.fit-compact .bid-group-info { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: center; column-gap: 8px; }
.bid-panel.fit-compact .bid-group-info .bid-amount-display { font-size: 1.6rem; }
.bid-panel.fit-compact .panel-hint { display: none; }
.bid-panel.fit-split .bid-group-info { flex-direction: column; }
.bid-panel.fit-split .bid-group-info .bid-amount-display { font-size: 2.2rem; }

/* Your bid panel's three groups: invisible in the column; side by side on
   a short, wide felt (fit-split: a phone on its side, as the app's
   centerIsShort). */
.bid-group { display: contents; }
.bid-panel.fit-split {
  width: min(600px, 100%);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr);
  grid-template-areas: "info controls" "more controls";
  align-items: center;
  column-gap: 16px;
  row-gap: 6px;
}
.bid-panel.fit-split .bid-group { display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 0; }
.bid-panel.fit-split .bid-group-info { grid-area: info; align-self: end; }
.bid-panel.fit-split .bid-group-controls { grid-area: controls; gap: 6px; }
.bid-panel.fit-split .bid-group-more { grid-area: more; align-self: start; }

/* "(P)": a keyboard hint, only where there is a keyboard and pointer. */
.kbd-hint { display: none; }
@media (hover: hover) and (pointer: fine) { .kbd-hint { display: inline; } }

.center-zone > * { flex-shrink: 0; }

.table-message {
  color: var(--text-dim);
  font-size: 1.05rem;
}

.center-panel {
  width: min(460px, 100%);
  padding: 20px 22px;
  border-radius: 20px;
  background: rgba(8, 22, 14, 0.82);
  border: 1px solid var(--panel-border);
  box-shadow: var(--shadow-lg);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.panel-eyebrow {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
}

.panel-title {
  font-size: 1.15rem;
  font-weight: 700;
}

.panel-sub {
  color: var(--text-dim);
  font-size: 0.92rem;
}

.panel-wait {
  color: var(--text-dim);
  font-style: italic;
}

.panel-hint {
  font-size: 0.8rem;
  color: var(--text-dim);
  max-width: 38ch;
}

.panel-hint.is-warning { color: #ffc4b8; }

/* Bidding */
.bid-amount-display {
  font-size: 3rem;
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.bid-stepper {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 4px;
}

.stepper-btn {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1px solid var(--panel-border);
  background: rgba(255, 255, 255, 0.06);
  color: var(--text);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}

.stepper-btn:hover:not(:disabled) { background: rgba(255, 255, 255, 0.12); }
.stepper-btn:disabled { opacity: 0.3; cursor: default; }

.stepper-value {
  min-width: 3ch;
  font-size: 1.8rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--gold-strong);
}

.bid-actions {
  display: flex;
  gap: 10px;
  width: 100%;
  justify-content: center;
}

.bid-actions .btn { flex: 1; max-width: 170px; white-space: nowrap; padding-left: 10px; padding-right: 10px; }

/* ---- Trick ---- */
.trick-area {
  position: relative;
  width: calc(var(--card-width) * 3 + 36px);
  height: calc(var(--card-height) * 2 + 60px);
  flex-shrink: 0;
  z-index: 2;
}

.trick-card { position: absolute; }
/* The high card's lift eases when another card takes the trick. */
.trick-card .card { transition: transform 0.2s var(--ease-out), box-shadow 0.2s ease; }
.trick-card-0 { bottom: 0; left: 50%; transform: translateX(-50%); }
.trick-card-1 { left: 0; top: 50%; transform: translateY(-50%); }
.trick-card-2 { top: 0; left: 50%; transform: translateX(-50%); }
.trick-card-3 { right: 0; top: 50%; transform: translateY(-50%); }

/* Belt-and-braces fallback for spectator/undefined positions (F-P1). */
.trick-card-undefined { bottom: 0; left: 50%; transform: translateX(-50%); }

.trick-slot {
  width: var(--card-width);
  height: var(--card-height);
  border-radius: 8px;
  border: 1.5px dashed rgba(255, 255, 255, 0.14);
}

.trick-lead-badge {
  position: absolute;
  top: -9px;
  left: 50%;
  transform: translateX(-50%);
  padding: 1px 6px;
  border-radius: 6px;
  background: #0d2016;
  color: var(--team-ew);
  font-size: var(--text-micro, 0.6875rem);
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  z-index: 3;
}

.trick-status {
  min-height: 1.3em;
  font-size: 0.9rem;
  color: var(--text-dim);
  text-align: center;
}

.trick-status.is-you {
  color: var(--gold-strong);
  font-weight: 700;
}

.running-score {
  display: flex;
  gap: 1.5rem;
  justify-content: center;
  font-size: 0.85rem;
  font-weight: 700;
}

/* Trick history: a small panel on the felt (wide screens) */
.trick-history {
  position: absolute;
  right: 18px;
  bottom: 18px;
  z-index: 2;
  width: 214px;
  max-height: 42%;
  display: flex;
  flex-direction: column;
  border-radius: 14px;
  background: rgba(8, 22, 14, 0.8);
  border: 1px solid var(--panel-border);
  overflow: hidden;
}

.trick-history-title {
  padding: 8px 12px;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: left;
  cursor: pointer;
}

.trick-history-title::after { content: " \25BE"; }
.trick-history-title[aria-expanded="false"]::after { content: " \25B8"; }

.trick-history-entries {
  overflow-y: auto;
  padding: 0 8px 8px;
}

.trick-history-entry {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 4px;
  border-top: 1px solid rgba(255, 255, 255, 0.05);
  font-size: 0.75rem;
}

.trick-history-entry .trick-number {
  min-width: 18px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

.trick-history-entry .trick-cards {
  display: flex;
  gap: 3px;
}

.trick-history-winner {
  margin-left: auto;
  font-size: 0.7rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 64px;
}

.trick-history-entry.team-ns .trick-history-winner { color: var(--team-ns); }
.trick-history-entry.team-ew .trick-history-winner { color: var(--team-ew); }

/* Compact text chips for played cards (history + meld) — no card faces. */
.trick-hist-card,
.mini-card {
  display: inline-block;
  min-width: 24px;
  padding: 1px 4px;
  border-radius: 4px;
  background: #fbfbf8;
  color: #151515;
  font-size: 0.72rem;
  font-weight: 800;
  line-height: 1.45;
  text-align: center;
  white-space: nowrap;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}

.trick-hist-card.red,
.mini-card.red { color: #c62828; }

.trick-hist-card.winner { box-shadow: 0 0 0 2px var(--gold); }

body.four-color-deck .mini-card.suit-C,
body.four-color-deck .trick-hist-card.suit-C { color: #1b7a2b; }
body.four-color-deck .mini-card.suit-D,
body.four-color-deck .trick-hist-card.suit-D { color: #1565c0; }

/* ---- Meld board ---- */
.meld-board {
  width: min(760px, 100%);
  align-items: stretch;
  text-align: left;
  gap: 12px;
  padding: 16px 18px;
}

.meld-board-head { text-align: center; }

.meld-board-trump {
  margin: 4px 0 2px;
  font-size: 1.35rem;
  font-weight: 800;
}

.meld-board-trump .trump-glyph { font-size: 1.3em; }

.meld-teams {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.meld-team {
  border-radius: 14px;
  padding: 10px 12px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--panel-border);
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}

.meld-team.is-bidding { border-color: rgba(227, 181, 74, 0.45); }

.meld-team-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.meld-team.team-ns .meld-team-head { color: var(--team-ns); }
.meld-team.team-ew .meld-team-head { color: var(--team-ew); }

.meld-team-total {
  font-size: 1.2rem;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
  text-transform: none;
}

.meld-actions {
  display: flex;
  gap: 8px;
  justify-content: center;
  flex-wrap: wrap;
}

.meld-waiting { align-self: center; margin-top: 2px; line-height: 1.3; }

/* ---- Trump picker ---- */
.trump-panel { width: min(520px, 100%); }

/* ---- Hand rail ---- */
.hand-area {
  padding: 4px 12px 10px;
  /* Keep the bar clear of the home indicator. */
  padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
  background: linear-gradient(to top, rgba(6, 18, 12, 0.96), rgba(6, 18, 12, 0.82));
  backdrop-filter: blur(8px);
  border-top: 1px solid var(--panel-border);
  box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.35);
  /* The raised card and its Play button rise above the rail's top edge,
     over the felt (build 24: no empty band kept for them). The hand always
     fits the width (its slots shrink), so nothing needs to scroll. */
  overflow: visible;
}

/* Meld review (build 25, render/hand-dock.js): the hand slides down out of
   the way behind a grab handle, so the meld list gets the room. Down shows
   the card tops (a 32 px peek), up shows the hand as always. The rail's
   height changes and clearance.js re-measures --hand-h. */
.hand-area.is-docked .hand-handle {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  width: 100%;
  min-height: 36px;
  padding: 0;
  margin: 0;
  background: none;
  border: 0;
  color: var(--text-dim, rgba(255, 255, 255, 0.7));
  font: inherit;
  font-size: 0.7rem;
  font-weight: 600;
  cursor: pointer;
  touch-action: none;
  position: relative;
}
/* A 44 px target: 4 px beyond the row, above and below. */
.hand-area.is-docked .hand-handle::before { content: ""; position: absolute; inset: -4px 0; }
.hand-handle-pill { width: 40px; height: 5px; border-radius: 3px; background: rgba(255, 255, 255, 0.55); }
.hand-area.is-docked .hand-handle:focus-visible { outline: 2px solid var(--gold-strong); outline-offset: -2px; border-radius: 10px; }
.hand-area.is-docked .hand { max-height: 400px; }
.hand-area.is-docked.is-down .hand { max-height: 32px; overflow: hidden; pointer-events: none; }
@media (prefers-reduced-motion: no-preference) {
  .hand-area.is-docked .hand { transition: max-height 0.28s ease; }
}

/* The Play button on the raised card (render/hand.js syncHandPlayButton). */
.hand-play-btn {
  position: absolute;
  left: calc(var(--card-width) / 2);
  bottom: calc(100% + 30px);
  transform: translateX(-50%);
  z-index: 60;
  min-width: 72px;
  min-height: 40px;
  padding: 6px 18px;
  border-radius: 999px;
  border: 0;
  background: var(--gold);
  color: #1b1405;
  font-weight: 800;
  font-size: 0.95rem;
  white-space: nowrap;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
  cursor: pointer;
}
.card-slot:first-child .hand-play-btn { left: 0; transform: none; }
.card-slot:last-child .hand-play-btn { left: auto; right: 0; transform: none; }
@media (max-width: 340px) { .hand-area { overflow-x: auto; } }

/* ---- Hand summary / game over ---- */
.scores-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: safe center;
  flex: 1;
  padding: 24px 16px;
  /* A summary taller than the room left (a lesson card above it, F-001)
     scrolls here, inside the felt, never the page. */
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.scores-container > .summary-card { flex: none; }
/* The window is the page while a summary is up: #app keeps the window's
   height so the summary scrolls in its own box (F-001, F-002). */
body:has(#phase-root .scores-container) #app { flex: none; height: 100vh; height: 100dvh; min-height: 0; }
body:has(#phase-root .scores-container) #phase-root { overflow: hidden; }
/* Deal Next Hand stays in view while the summary scrolls (rule 2). */
.scores-container .summary-card > .summary-actions,
.scores-container .summary-side > .summary-actions { position: sticky; bottom: 0; z-index: 2; padding-block: 8px; margin-block: -8px; background: var(--panel); box-shadow: 0 -8px 12px var(--panel); }

.summary-card {
  width: min(520px, 100%);
  padding: 24px;
  border-radius: 22px;
  background: var(--panel);
  border: 1px solid var(--panel-border);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  gap: 14px;
  text-align: center;
}

.summary-card h2 {
  font-size: 1.4rem;
}

.result-banner {
  padding: 10px 14px;
  border-radius: 12px;
  font-weight: 700;
}

.result-banner.is-made {
  background: rgba(111, 207, 142, 0.14);
  color: var(--good);
}

.result-banner.is-set {
  background: rgba(239, 125, 111, 0.14);
  color: var(--bad);
}

.summary-table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.summary-table th,
.summary-table td {
  padding: 8px 10px;
  text-align: right;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.summary-table th:first-child,
.summary-table td:first-child {
  text-align: left;
  color: var(--text-dim);
  font-weight: 500;
}

.summary-table th { font-size: 0.85rem; }
.summary-table tr.is-strong td { font-size: 1.05rem; }
.summary-table tr.is-total td { border-bottom: 0; font-size: 1.1rem; }
.summary-table .neg { color: var(--bad); }

.waiting-note {
  color: var(--text-dim);
  font-size: 0.85rem;
}

.continue-btn { align-self: center; min-width: 220px; }

/* Deal Next Hand with Review Hand under it (render/summary.js); side by
   side on a phone on its side (N-014). The host's spectators and seats are
   one folded row (N-014, N-015). */
.summary-actions { display: flex; flex-direction: column; align-items: center; gap: 14px; }
.summary-actions > .continue-btn { align-self: center; }
.summary-actions .review-links { margin-top: 0; }
.summary-manage { text-align: left; border-top: 1px solid rgba(255, 255, 255, 0.08); }
.summary-manage > summary { font-weight: 700; color: var(--text); }
@media (max-height: 500px) and (orientation: landscape) {
  .summary-actions { flex-direction: row; flex-wrap: wrap; justify-content: center; gap: 8px 16px; }
  .summary-actions > .continue-btn { min-width: 0; flex: 1 1 200px; }
  .summary-actions .review-links { flex: 0 1 auto; margin: 0; }
  /* The players' meld lines two by two (each a 44 px row). */
  .summary-card .meld-summary { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); column-gap: 14px; margin-top: 0; }
  .summary-card .meld-summary > .meld-summary-title { grid-column: 1 / -1; }
}
/* A phone held upright: the summary fits its screen (N-015). */
@media (max-width: 600px) and (min-height: 501px) {
  .scores-container { padding: 12px 12px; }
  .summary-card { padding: 16px 16px; gap: 10px; }
  .summary-table th, .summary-table td { padding: 5px 8px; }
}

/* R-073: gold is for a win; a loss is told in the plain text colour (as in the app). */
.celebration-screen h2.is-win { color: var(--gold); }
.celebration-screen h2.is-loss { color: var(--text); }
.celebration-screen .trophy { font-size: 3rem; }
/* The other side sees the four suits, not the winners' cup (as in the app). */
.celebration-screen .trophy-suits { font-size: 2rem; color: var(--gold); letter-spacing: 0.2em; }

.final-scores {
  display: flex;
  justify-content: center;
  gap: 12px;
}

.final-score {
  flex: 1;
  max-width: 200px;
  padding: 12px;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.04);
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.final-score span { font-size: 0.8rem; }
.final-score b { font-size: 1.8rem; color: var(--text); }

.taunt {
  color: var(--text-dim);
  font-style: italic;
}

/* ---- Trick animation (fly to the winner) ---- */
@keyframes fly-to-winner {
  0% { transform: translate(0, 0) scale(1); opacity: 1; }
  50% { opacity: 1; }
  100% { transform: translate(var(--fly-x), var(--fly-y)) scale(0.6); opacity: 0.8; }
}

.trick-animation-overlay {
  position: fixed;
  inset: 0;
  z-index: 300;
  pointer-events: none;
}

/* Cards on their way to the felt (render/motion.js). */
.card-flight-layer {
  position: fixed;
  inset: 0;
  z-index: 300;
  pointer-events: none;
}

.trick-animation-card {
  position: absolute;
  width: var(--card-width);
  height: var(--card-height);
  border-radius: 8px;
  background: #fff;
  box-shadow: 2px 3px 8px rgba(0,0,0,0.4);
  z-index: 301;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  backface-visibility: hidden;
}

.trick-animation-card .rank {
  position: absolute;
  top: 3px;
  left: 5px;
  font-size: 14px;
  font-weight: bold;
  line-height: 1;
}

.trick-animation-card .suit {
  font-size: 40px;
  line-height: 1;
}

.trick-animation-card.red { color: var(--red); }
.trick-animation-card.black { color: var(--black); }

/* ---- Misc ---- */
.waiting-message {
  text-align: center;
  opacity: 0.7;
  font-style: italic;
  padding: 1rem;
}

.set-indicator {
  color: var(--bad);
  font-weight: bold;
}

.btn-sub-bot {
  font-size: 0.72rem;
  padding: 2px 10px;
  border-radius: 8px;
  border: 1px solid #ffb74d;
  background: rgba(255, 183, 77, 0.16);
  color: #ffcc80;
  cursor: pointer;
  white-space: nowrap;
  min-height: 44px;
  transition: background 0.2s;
}

.btn-sub-bot:hover { background: rgba(255, 183, 77, 0.32); }
/* A side seat's plate is narrow (66 px on a phone): the button wraps to
   the plate's width under it, so it never runs off the screen (N-005). */
.player-left .btn-sub-bot,
.player-right .btn-sub-bot {
  max-width: 100%;
  white-space: normal;
  line-height: 1.15;
  padding: 4px 4px;
  text-align: center;
}

/* A computer is playing for you at a public table (R-008, the app's
   StandInBanner): one big button under your plate. */
.standin-banner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-height: 44px;
  padding: 8px 22px;
  border-radius: 999px;
  border: 1.5px solid var(--gold);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  cursor: pointer;
  box-shadow: var(--shadow-lg);
}
.standin-banner .standin-sub { font-size: 0.75rem; font-weight: 600; color: var(--text-dim); }
.standin-banner .standin-main { font-size: 1rem; font-weight: 700; }
@media (max-height: 500px) and (orientation: landscape) {
  .standin-banner { flex-direction: row; gap: 8px; padding: 4px 16px; }
}

/* Spectator all-hands containers inside the seats */
.spec-hand { max-width: 100%; }
/* A spectator's suit rows on the dark plate: the light reds (and the
   four-colour deck's light green and blue), as the trump glyph (F-003). */
.spec-glyph-suit.suit-H, .spec-glyph-suit.suit-D { color: #ff8a80; }
body.four-color-deck .spec-glyph-suit.suit-C { color: #7ee08f; }
body.four-color-deck .spec-glyph-suit.suit-D { color: #82b8ff; }

/* ---- Medium screens: history drops below the table ---- */
@media (max-width: 1100px) {
  .trick-history {
    position: static;
    grid-column: 1 / -1;
    width: auto;
    max-height: 160px;
    margin-top: 4px;
  }
}

/* ---- Phones / touch tablets ---- */
@media (max-width: 600px), (max-height: 500px) {
  .game-table {
    /* The side columns grow with the longer side name's longest word, up
       to 84px (render/table.js fitSideNames, N-017). */
    grid-template-columns: clamp(66px, var(--side-name-w, 0px), 84px) minmax(0, 1fr) clamp(66px, var(--side-name-w, 0px), 84px);
    gap: 6px;
    padding: 10px 6px 2px;
    margin-top: 6px;
    width: calc(100% - 8px);
    height: calc(100dvh - var(--chrome-h) - var(--hand-h) - 6px);
    min-height: 340px;
  }

  .table-cloth { border-radius: 24px; }

  /* Side seats: a compact vertical card */
  .player-left .seat-plate,
  .player-right .seat-plate {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 6px 4px;
    /* 64px at the standard size; wider with Larger Text, up to 104px as in
       the app, so a name and its status stay inside the plate. Both side
       plates grow to the longer name's longest word (render/table.js
       fitSideNames), up to 84px at the standard size (N-017). */
    width: clamp(64px, max(4em, var(--side-name-w, 0px)), clamp(84px, 4em, 104px));
    text-align: center;
  }

  /* The bid winner's trump glyph goes under the name when the two don't fit
     the plate side by side (Larger Text): the row never runs off the screen. */
  .player-left .player-name-row,
  .player-right .player-name-row { flex-wrap: wrap; justify-content: center; row-gap: 0; max-width: 100%; }
  .player-left .player-name-row .player-name,
  .player-right .player-name-row .player-name { min-width: 0; }

  .player-left .seat-info,
  .player-right .seat-info { align-items: center; }

  .player-left .player-name,
  .player-right .player-name { font-size: 0.72rem; max-width: 100%; white-space: normal; overflow-wrap: anywhere; hyphens: none; line-height: 1.15; }

  .player-left .seat-status,
  .player-right .seat-status { margin: 0; padding: 2px 6px; font-size: var(--text-micro, 0.6875rem); max-width: 100%; white-space: normal; text-align: center; }

  .player-left .seat-tags,
  .player-right .seat-tags { justify-content: center; }

  .seat-plate { padding: 6px 10px 6px 6px; gap: 8px; }
  .seat-avatar { width: 28px; height: 28px; font-size: 0.85rem; }
  .player-zone .player-name { font-size: 0.85rem; }
  .player-zone .trump-symbol { font-size: 1.2rem; }
  .seat-chip { font-size: var(--text-micro, 0.6875rem); padding: 1px 4px; }
  .seat-status { font-size: 0.74rem; padding: 3px 8px; }
  .player-bottom .seat-plate { gap: 6px; }
  /* The Play button fits inside the plate's height (it made your plate
     taller as you picked a card, and the trick above it hopped). */
  .player-play-btn { padding: 4px 12px; min-height: 36px; margin: -3px 0; }

  .center-panel { padding: 14px 12px; gap: 8px; }
  .bid-amount-display { font-size: 2.3rem; }
  .meld-teams { grid-template-columns: 1fr; }

  .trick-area {
    width: calc(var(--card-width) * 3 + 12px);
    height: calc(var(--card-height) * 2 + 36px);
  }

  .trick-history { max-height: 120px; }

  .hand-area {
    padding: 2px 6px;
    padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px));
  }
}

/* ---- Short desktop screens (laptops at 720-800px tall) ----
   Smaller trick cards and tighter spacing so the trick, the status line and
   the progress bar all fit between the seats. */
@media (max-height: 820px) and (min-width: 601px) and (min-height: 501px) {
  .game-table { padding: 10px 14px; row-gap: 6px; margin-top: 8px; }
  .trick-area { --card-width: 62px; --card-height: 88px; }
  .center-zone { gap: 6px; }
  .seat-plate { padding: 6px 10px 6px 6px; }
  .bid-progress { padding: 6px 10px; gap: 3px; }
  .center-panel { padding: 14px 18px; gap: 8px; }
  .bid-amount-display { font-size: 2.4rem; }
}

/* ---- Short phones (SE and smaller) ---- */
@media (max-width: 600px) and (max-height: 700px) {
  .trick-area { --card-width: 46px; --card-height: 66px; }
  .game-table { padding: 6px 4px; row-gap: 4px; }
  .center-zone { gap: 4px; }
  .bid-progress { padding: 5px 8px; gap: 2px; }
  .bp-row { gap: 2px; }
  .bp-row + .bp-row { padding-top: 3px; }
}

@media (max-width: 420px) {
  /* Keep the top bar to two short rows: trump stays, the bid lives on the
     bid winner's seat anyway. */
  /* ("Hand 3 of 12" at a tournament table stays: there's no target bar.) */
  .hand-context .ctx-chip:not(.is-trump):not(.is-hands) { display: none; }
}

/* Phones: your glowing seat and lit-up cards already say it's your turn.
   The line keeps its room (hidden, not removed): taking it away moved the
   whole trick up and down on every turn, and refitting the felt each time
   rescaled the cards already on it. */
@media (max-width: 600px) {
  .trick-status.is-you { visibility: hidden; height: 1.3em; overflow: hidden; }
}

/* ---- Portrait tablets (iPad mini to iPad Air, 601-1023px wide) ----
   The full table, with narrower side seats standing upright so the trick
   and the progress panel keep their width. */
@media (min-width: 601px) and (max-width: 1023px) and (min-height: 501px) {
  .game-table {
    grid-template-columns: minmax(96px, 140px) minmax(0, 1fr) minmax(96px, 140px);
    gap: 10px 10px;
    padding: 14px 12px 4px;
  }

  .player-left .seat-plate,
  .player-right .seat-plate {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 10px 8px;
    width: 100%;
    max-width: 132px;
    text-align: center;
  }

  .player-left .seat-info,
  .player-right .seat-info { align-items: center; }
  .player-left .player-name,
  .player-right .player-name { max-width: 110px; }
  .player-left .seat-status,
  .player-right .seat-status { margin: 0; }
  .player-left .seat-tags,
  .player-right .seat-tags { justify-content: center; }

  .trick-area { --card-width: 70px; --card-height: 100px; }
  .center-panel { width: min(440px, 100%); }
}

/* ---- Phones on their side (build 24, as in the app's TableLayout
   "landscape"; docs/TABLE_LAYOUT.md "One layout") ----
   The felt is the whole width: the top bar is one row (scores, then the
   buttons), the partner's one-line plate on top, the opponents level with
   the trick and brought in to it at equal distances, your one-line plate
   just over one fanned row of cards. */
@media (max-height: 500px) and (orientation: landscape) {
  /* The top bar on one row: the scores in the leading corner, trump and
     the buttons in the trailing one. */
  .score-bar.top-bar { flex-wrap: nowrap; padding: 3px 8px; padding-top: calc(3px + env(safe-area-inset-top, 0px)); }
  .score-pills { flex: 0 1 auto; order: 1; }
  .score-pill { flex: 0 1 auto; min-width: 120px; }
  .hand-context { order: 2; flex: 1 1 auto; justify-content: flex-end; }
  .top-actions { order: 3; }
  /* Short on height: the trick and its line. The bid's progress is one compact
     line ("We need 18 more. They need 12 more to keep 30 meld"), not the two bars
     and not nothing (build 28, ISSUES row 84: the numbers never just vanish). */
  .center-zone .bid-progress { display: none; }
  .center-zone .bid-progress-line { display: block; }
  .game-table {
    grid-template-areas:
      ".    .      top    .     ."
      ".    left   center right ."
      "bottom bottom bottom bottom bottom";
    grid-template-columns: minmax(0, 1fr) auto minmax(0, max-content) auto minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr) auto;
    column-gap: 14px;
    row-gap: 2px;
    padding: 4px 6px 2px;
    margin-top: 2px;
    width: calc(100% - 8px);
    height: calc(100dvh - var(--chrome-h) - var(--hand-h) - 6px);
    min-height: 0;
  }
  .table-cloth { border-radius: 18px; }
  .player-left { justify-self: end; }
  .player-right { justify-self: start; }
  /* The side seats stand against the whole felt, not the middle row between
     the two one-line plates (build 26, found by CI on a wider font;
     docs/TABLE_LAYOUT.md "Wider fonts"). At Larger Text a side plate is
     taller than that row (the status wraps to two lines, the bid winner's
     trump glyph goes under the name, a Dealer chip is one more row: 140 to
     210 px of plate in a row of 60), and centered on the row it hung out of
     it equally above and below. The row's middle moves with the plates
     around it: when the partner dealt, the Dealer and Partner chips wrapped,
     the partner's plate grew 33 px and yours lost its Dealer row (30 px), the
     row's middle dropped 31 px, and a side seat ran into your cards (7 px in
     Verdana, 2 px in the CI machine's font). The seats share no column with
     either plate, so they take the felt's full height: centered on it and
     capped to it (--side-cap, the table's height less its padding, 4 + 2).
     The plate keeps its size up to the cap; what does not fit is cut inside
     it (the tags first, then the name; the status pill keeps its lines) and
     a peek hand under it gives way first, so no font can take a seat to the
     hand or the top bar. */
  .game-table { --side-cap: calc(100dvh - var(--chrome-h) - var(--hand-h) - 12px); }
  .player-left, .player-right { grid-row: 1 / -1; min-height: 0; max-height: var(--side-cap); }
  .player-left .seat-plate, .player-right .seat-plate { flex-shrink: 0; min-height: 0; max-height: var(--side-cap); }
  .player-left .seat-info, .player-right .seat-info { min-height: 0; overflow: hidden; }
  .player-left .seat-status, .player-right .seat-status { flex-shrink: 0; }
  .player-left .peek-hand, .player-right .peek-hand,
  .player-left .spec-hand, .player-right .spec-hand { min-height: 0; overflow: hidden; }
  /* A computer is playing your seat (a public table; render/table.js): under
     your plate stand the banner, a pill wider than the center column, and
     sometimes the take-back button. Your zone is then wider than the center
     and grows up into the felt by their height (50 px for the banner and its
     gap, 100 with the button; 90 px of zone with the plate at 100% text), so
     a side seat cannot stand beside it. The seats used to rise with the
     middle row as it shrank (half of that, about 23 px); a span down the
     whole felt does not, and they ran 13 to 23 px into your plate
     (iPhone 17 Pro on its side, 100% text; b26-cifix2). In this state they
     take only the rows above your zone, rows 1 and 2, centered on them and
     capped to their height (a percentage of that area, which ends where your
     zone starts, however tall that is) less 6 px: 5 px stay between a seat
     and your plate, and the plate gives way inside the cap (the avatar first,
     then the name and tags; the status pill keeps its lines). :where() keeps
     this under the meld's own placement of the seats (meld.css), which is
     not these rows. */
  .game-table:where(:has(.player-bottom .standin-banner, .player-bottom .btn-sub-bot)) :is(.player-left, .player-right) { grid-row: 1 / 3; max-height: calc(100% - 6px); }
  .game-table:where(:has(.player-bottom .standin-banner, .player-bottom .btn-sub-bot)) :is(.player-left, .player-right) .seat-plate { flex-shrink: 1; max-height: none; }
  .game-table:where(:has(.player-bottom .standin-banner, .player-bottom .btn-sub-bot)) :is(.player-left, .player-right) .seat-avatar { flex-shrink: 20; min-height: 0; overflow: hidden; }
  /* The partner's plate sits over the middle column and stops 6 px inside it
     (margin-inline), clear of the side seats: a long name ("Grandpappy") with
     tags at 200% text ran 5 px under both side seats on an iPhone SE (build
     26, G2). It cannot widen the column (contain), so the name shortens first.
     Its place in the row is the same as before.
     Build 28 (ISSUES row 86): the plate carries no "Partner" tag any more (the
     partner is always across the table; a screen reader still hears it), so the
     Dealer chip is the only chip a partner's plate has and it keeps its word.
     Builds 26 and 27 folded the Dealer chip to a "D" and let this plate reach 6 px
     past the column with 6 px gaps of its own so that the two chips shared one
     row; with one chip that room is not needed and both are gone. */
  .player-top { contain: inline-size; justify-self: stretch; margin-inline: 6px; align-items: center; min-width: 0; }
  .center-zone { min-width: 0; max-width: min(600px, calc(100vw - 200px)); }
  .center-panel { padding: 8px 12px; gap: 6px; }
  /* Your plate and the partner's on one line. */
  .player-top .seat-plate, .player-bottom .seat-plate { padding: 2px 8px 2px 2px; }
  /* Build 27: a computer is playing your seat. The banner is one button on one
     row, whatever the text size: its text follows Larger Text up to 18 px (the
     app caps a phone on its side at accessibility 1; at 200% the two lines of a
     pill 86 px tall ran 22 px into your cards on an SE), the explaining line
     gives way first, with an ellipsis, in a font too wide for the row, and the
     "Take Seat Back" button, which says the same as the banner, is not drawn
     beside it (it made the zone 50 px taller, 72 px into your cards at 200%).
     It stands 2 px under your plate, not 6, for a little room in a wide font. */
  .player-bottom:has(.standin-banner) { gap: 2px; }
  .standin-banner { max-width: 100%; flex-wrap: nowrap; }
  .standin-banner .standin-sub { font-size: min(0.75rem, 14px); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .standin-banner .standin-main { font-size: min(1rem, 18px); flex-shrink: 0; white-space: nowrap; }
  .player-bottom:has(.standin-banner) > .btn-sub-bot { display: none; }
  .player-top .seat-avatar, .player-bottom .seat-avatar { width: 24px; height: 24px; }
  .hand-area { padding-top: 2px; padding-bottom: calc(2px + env(safe-area-inset-bottom, 0px)); }
  .trick-history { max-height: 60px; }
  /* The trick as the app sizes it on its side (TableMetrics.trickCardWidth:
     12.5% of the height, 44 to 58), with its line beside the cross (R-056),
     so the cross keeps clear of the room a raised card and its Play button
     rise into (render/table.js liftClearance, as the app since bb08c0e). */
  .trick-area { --card-width: clamp(44px, 12.5vh, 58px); --card-height: calc(var(--card-width) * 108 / 76); }
  .center-zone:has(> .trick-area) { flex-direction: row; align-items: safe center; justify-content: center; gap: 10px; }
  .center-zone:has(> .trick-area) > .trick-status { max-width: 160px; text-align: start; }
  /* Build 28 (ISSUES row 84): the tricks-needed line stands in the row after the turn line, wraps there
     (it was one run, cut at the zone's edge: "They need 30 more. We need 20 more t"), and gives way to
     the cross first (`.center-zone > *` does not shrink, so this one says it may). */
  .center-zone:has(> .trick-area) > .bid-progress-line { flex: 0 1 230px; min-width: 110px; max-width: 240px; text-align: start; }
}

/* ---- Very short phones (iPhone SE 1st gen and similar, ≤600px tall) ---- */
@media (max-width: 600px) and (max-height: 600px) {
  .game-table { min-height: 0; padding: 4px; row-gap: 2px; }
  .trick-area { --card-width: 40px; --card-height: 57px; height: calc(var(--card-height) * 2 + 20px); }
  .seat-plate { padding: 4px 8px 4px 4px; }
  .player-left .seat-plate,
  .player-right .seat-plate { padding: 4px; }
  .seat-avatar { width: 24px; height: 24px; font-size: 0.75rem; }
  /* Tiny screens: the bars and headlines stay (both teams' 20). */
  .bid-progress-sub { display: none; }
  .trick-history { max-height: 44px; } /* its title is a 44 px target */
}

/* Luke: the whole auction so far, until the bid is won. */
.bid-history {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 4px;
  padding: 6px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 8px;
  max-width: 100%;
}
/* An entry wraps when the panel is narrow (Table Talk open beside it, N-006),
   never cut at the panel's edges. */
.bid-history-entry { font-size: 0.78rem; color: var(--text-dim); padding: 1px 6px; border-radius: 10px; max-width: 100%; overflow-wrap: break-word; text-align: center; }
.bid-history-entry.is-newest { color: var(--text); font-weight: 700; background: rgba(255, 255, 255, 0.08); }

/* Build 26: the auction held on screen after the bidding, until the host closes it
   (render/auction-review.js). The whole auction shows; a long one scrolls in its box. */
.auction-review .bid-history { max-height: 36vh; overflow-y: auto; margin: 8px 0; }
.auction-review-close { min-width: 160px; }

/* Luke's signals, said with the bid or pass: one small pill menu. */
.bid-signal-select { display: block; margin: 6px auto 2px; max-width: 100%; font: inherit; font-size: 0.78rem; font-weight: 600; padding: 4px 10px; border-radius: 99px; border: 1px solid var(--border, rgba(255,255,255,0.2)); background: transparent; color: var(--text-dim); cursor: pointer; }
.bid-signal-select option, .bid-signal-select optgroup { color: #1a1a1a; background: #fff; }
.bid-signal-select.is-set { background: var(--gold); border-color: var(--gold); color: #1a1a1a; }

/* Build 16: the suit tapped, waiting for Confirm (Luke). */
.trump-inspect-btn.is-picked {
  outline: 3px solid var(--gold);
  outline-offset: 3px;
  box-shadow: 0 0 18px rgba(227, 181, 74, 0.55);
  transform: translateY(-4px);
}
.trump-actions { width: 100%; }
.trump-confirm { margin-top: 12px; width: 100%; }

/* iPad-width screens and bigger (Luke, build 20): one step larger bid text. */
@media (min-width: 768px) and (min-height: 501px) {
  .bid-history-entry { font-size: 0.92rem; }
  .bid-signal-select { font-size: 0.92rem; }
  .bid-actions .btn { font-size: 1.05rem; }
  .seat-status { font-size: 0.95rem; }
}

/* iPad-size windows (R-082, as the app's regular width, R-101): the table
   one size up, so the panel and the plates fill the felt instead of
   floating in 200 px of empty green. zoom keeps every control and line of
   text in proportion (and fitCenter measures the zoomed panel). The meld
   board stays at 1: it already fills the felt, and zoomed, the folded
   lines beside each name had no room left (eleven combinations). */
@media (min-width: 768px) and (min-height: 1000px) {
  .center-zone > .center-panel:not(.meld-board) { zoom: 1.25; }
  .game-table .seat-plate { zoom: 1.2; }
  .trick-area { --card-width: 92px; --card-height: 131px; }
}
/* Big screens (N-010 to N-013, docs/TABLE_LAYOUT.md rule 3): an iPad upright
   and a wide computer window take the iPad landscape table's scale, s =
   clamp(play height / 776, 1, 1.3) (clearance.js bigScale: body.big-table,
   --big-scale, --big-step).
   As the iPad landscape table: hand card 80·s, trick card 92·s, and the
   iPad's type steps plus one from s ≥ 1.2 (× --big-step, 1.12) for the
   seats, the center, the top bar. */
body.big-table .center-zone > .center-panel:not(.meld-board) { zoom: calc(1.25 * var(--big-step, 1)); }
body.big-table .game-table .seat-plate { zoom: calc(1.2 * var(--big-step, 1)); }
body.big-table .score-bar.top-bar { zoom: var(--big-step, 1); }
/* Upright, the center panel keeps the iPad's two steps (1.25) without the
   third: at 1.4 the bid panel filled the felt and your own speech bubble
   had no slot over your plate (test/web-build21-ui.test.js). */
@media (orientation: portrait) {
  body.big-table .center-zone > .center-panel:not(.meld-board) { zoom: 1.25; }
}
body.big-table .trick-area { --card-width: calc(92px * var(--big-scale, 1)); --card-height: calc(92px * var(--big-scale, 1) * 108 / 76); }
body.big-table #hand-slot { --card-width: calc(80px * var(--big-scale, 1)); --card-height: calc(80px * var(--big-scale, 1) * 108 / 76); }
/* An iPad held upright (B24 pass 2 N-067, N-074, docs/TABLE_LAYOUT.md
   "Fit rules" 3): the table grows past that scale to use the tall felt,
   S = clamp(min(w / 512, (h - 44) / 710), s, 1.8), as the app's
   TableMetrics.tableScale; --upright-zoom is S / s (1.231 on an iPad Air 11,
   1.385 on an iPad Pro 13, clearance.js uprightZoom). The plates by that much,
   the hand's cards 80·S, the trick's 92·s (at most 9% of the play height)
   times it, and the center panel up to 1.25 times it, stepping down to fit
   the felt (render/table.js fitCenter). */
body.upright-big .game-table .seat-plate { zoom: calc(1.2 * var(--big-step, 1) * var(--upright-zoom, 1)); }
body.upright-big .trick-area {
  --card-width: calc(min(92px * var(--big-scale, 1), (100vh - 44px) * 0.09) * var(--upright-zoom, 1));
  --card-height: calc(var(--card-width) * 108 / 76);
}
body.upright-big #hand-slot {
  --card-width: calc(80px * var(--big-scale, 1) * var(--upright-zoom, 1));
  --card-height: calc(80px * var(--big-scale, 1) * var(--upright-zoom, 1) * 108 / 76);
}
/* A lesson card open over the felt takes its height: the hand keeps the
   landscape scale's 80·s then, so the center still fits beside the card at
   its full size (docs/TABLE_LAYOUT.md "Fit rules" 1) on an iPad Air 11. */
body.upright-big:has(#coach-slot .coach-lesson) #hand-slot {
  --card-width: calc(80px * var(--big-scale, 1));
  --card-height: calc(80px * var(--big-scale, 1) * 108 / 76);
}

/* A computer's window (N-010; F-006 down to 1200 px, a 1366x1024 window
   at s = 1.26): the hand summary and game over a 720 px card at the
   table's scale, the result beside the meld and the buttons, not a 520 px
   card in a big window. */
@media (min-width: 1200px) and (min-height: 700px) {
  .scores-container .summary-card { width: min(calc(720px / var(--big-scale, 1)), 100%); zoom: var(--big-scale, 1); }
  /* The hand summary's two columns need more: the score table on one line
     beside a meld column that isn't a 200 px strip (F-007). */
  .scores-container .summary-card.hand-summary.can-split { width: min(calc(880px / var(--big-scale, 1)), 100%); }
  .scores-container .summary-card.can-split {
    display: grid;
    /* The result column is as wide as its table on one line (F-007:
       "Trick points" and "Matt & Tommy" broke in two); the meld the rest. */
    grid-template-columns: minmax(min-content, 1.1fr) minmax(0, 1fr);
    column-gap: 24px;
    align-items: start;
  }
  .scores-container .summary-card.can-split .summary-table :is(th, td) { white-space: nowrap; }
  .scores-container .summary-card.can-split .summary-group { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
  .scores-container .summary-card.can-split .meld-summary { margin-top: 0; }
}

/* Hand summary and game over: the result and the rest, side by side on a
   phone on its side (R-083, as the app's landscape panel), so Deal Next
   Hand, Play Again, and Home are on screen without page scrolling. */
.summary-group { display: contents; }
@media (max-height: 500px) and (orientation: landscape) {
  .scores-container { padding: 8px 12px; justify-content: flex-start; }
  .summary-card.can-split {
    width: min(860px, 100%);
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: 20px;
    align-items: start;
    padding: 14px 18px;
  }
  .summary-card.can-split .summary-group { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
  .summary-card.can-split .summary-side { align-self: center; }
  .summary-card.can-split .trophy { font-size: 2rem; margin: 0; }
  .summary-card.can-split h2 { margin: 0; }
}

/* A wide computer window (N-013): the hand stays one overlapping fan,
   centred, its step 0.6 of a card, the gap between suits a steady 16 px
   (Luke's wider gap between two suits of a colour stays wider); it no
   longer spreads into a broken row across 1900 px. The iPad landscape
   table has its own hand (body.pad-landscape). */
@media (min-width: 1200px) {
  body:not(.pad-landscape) #hand-slot .hand:not(.hand-two-rows) .card-slot:not(:last-child) { flex-basis: calc(var(--card-width) * 0.6); }
  body:not(.pad-landscape) #hand-slot .hand:not(.hand-two-rows) .suit-gap:not(.same-color) { flex: 0 0 16px; min-width: 16px; }
}

/* ---- Swipe a card up to play it (render/swipe.js, docs/SWIPE_TO_PLAY.md) ---- */
/* The hand takes vertical strokes itself (a swipe up plays); on the
   narrowest phones, where the hand scrolls sideways, it keeps that pan. */
#hand-slot .hand { touch-action: none; }
@media (max-width: 340px) { #hand-slot .hand { touch-action: pan-x; } }
/* Under the finger: no easing behind it, above its neighbours. */
.hand .card.is-flicking,
.hand .card.is-launched { transition: none !important; z-index: 70; }
/* Past the play line: a gold glow and a little growth. */
.hand .card.is-armed {
  scale: 1.06;
  box-shadow: 0 0 0 2px var(--gold), 0 0 18px rgba(227, 181, 74, 0.9);
}
/* A dimmed card that was tried (Reduce Motion: instead of the shake). */
.hand .card.is-refused { outline: 3px solid var(--bad, #e05a4f); outline-offset: -1px; }
@media (prefers-reduced-motion: reduce) {
  .hand .card.is-armed { scale: none; }
}

/* ---- iPad (and big windows) on their side: docs/TABLE_LAYOUT.md "iPad
   landscape", as the app's TableLayout.padLandscape. clearance.js sets
   body.pad-landscape and the one scale, --table-scale (1 at an iPad Air
   11's play height, up to 1.3) and --table-step (one type step more from
   1.2). The seats keep their places; the cloth runs under your plate and
   hand to the bottom edge, the opponents sit 40·s from the trick, the last
   two tricks sit in the felt's top-leading corner, and a lesson card is a
   column of its own at the trailing edge. */
body.pad-landscape {
  --pad-trick-w: calc(92px * var(--table-scale, 1));
  --pad-seat-gap: calc(40px * var(--table-scale, 1));
  --lesson-w: min(32vw, calc(330px * var(--table-scale, 1)));
  --lesson-col: 0px;
  --pad-cross: calc(var(--pad-trick-w) * 3 + 36px);
  /* The column beside the cross for the turn line and the progress:
     260·s, less on a narrower table, never under 200 (the app's
     TableScreen.padTrickSideWidth). */
  --pad-side: max(200px, min(calc(260px * var(--table-scale, 1)), calc(100vw - var(--lesson-col) - 392px - var(--pad-cross) - 16px)));
}
body.pad-landscape:has(#coach-slot .coach-lesson) { --lesson-col: var(--lesson-w); }
body.pad-landscape .game-table {
  width: 100%;
  margin: 0;
  padding: 10px 16px 4px;
  height: calc(100dvh - var(--chrome-h) - var(--hand-h));
}
body.pad-landscape .table-cloth {
  bottom: calc(-1 * var(--hand-h));
  border-radius: 0;
}
body.pad-landscape #hand-slot .hand-area {
  background: transparent;
  border-top: 0;
  box-shadow: none;
  backdrop-filter: none;
}
body.pad-landscape #hand-slot {
  --card-w: calc(80px * var(--table-scale, 1));
  --card-h: calc(80px * var(--table-scale, 1) * 108 / 76);
  --card-width: var(--card-w);
  --card-height: var(--card-h);
}
body.pad-landscape .trick-area {
  --card-width: var(--pad-trick-w);
  --card-height: calc(var(--pad-trick-w) * 108 / 76);
}
/* The opponents a set distance from the trick (the cross with its turn
   line and progress beside it, plus twice the gap less the column gaps),
   mirrored. */
body.pad-landscape .game-table.phase-tricking {
  column-gap: 16px;
  grid-template-columns: minmax(120px, 1fr) calc(var(--pad-cross) + 16px + var(--pad-side) + 2 * (var(--pad-seat-gap) - 16px)) minmax(120px, 1fr);
}
body.pad-landscape .game-table.phase-tricking .center-zone:has(> .trick-area) {
  display: grid;
  grid-template-columns: var(--pad-cross) var(--pad-side);
  grid-template-rows: 1fr auto auto 1fr;
  column-gap: 16px;
  row-gap: 8px;
  align-content: center;
  justify-content: center;
}
body.pad-landscape .center-zone:has(> .trick-area) > .trick-area { grid-column: 1; grid-row: 1 / -1; align-self: center; }
body.pad-landscape .center-zone:has(> .trick-area) > .trick-status { grid-column: 2; grid-row: 2; text-align: start; max-width: none; }
body.pad-landscape .center-zone:has(> .trick-area) > .bid-progress { grid-column: 2; grid-row: 3; width: auto; max-width: none; }
body.pad-landscape .game-table.phase-tricking .player-left { justify-self: end; }
body.pad-landscape .game-table.phase-tricking .player-right { justify-self: start; }
body.pad-landscape .game-table .seat-plate { zoom: calc(1.2 * var(--table-step, 1)); }
body.pad-landscape .center-zone > .center-panel:not(.meld-board) { zoom: calc(1.25 * var(--table-step, 1)); }
body.pad-landscape .score-bar.top-bar { zoom: var(--table-step, 1); }
/* The last two tricks in the felt's top-leading corner, sized to them. */
body.pad-landscape .trick-history {
  top: 10px;
  left: 16px;
  right: auto;
  bottom: auto;
  width: max-content;
  max-width: calc(50% - 160px);
  zoom: var(--table-step, 1);
}
body.pad-landscape .trick-history-entry:nth-child(n + 3) { display: none; }
/* A lesson card: a column at the trailing edge, from under the top bar to
   the bottom; the table and your hand take the rest. */
body.pad-landscape #coach-slot:has(.coach-lesson) {
  position: fixed;
  top: var(--chrome-h);
  right: 0;
  bottom: 0;
  width: var(--lesson-w);
  z-index: var(--z-panel);
  display: flex;
  flex-direction: column;
}
body.pad-landscape #coach-slot:has(.coach-lesson) .coach {
  flex: 1;
  min-height: 0;
  padding: 12px;
  background: rgb(10, 26, 18);
  border-bottom: 0;
  border-left: 1px solid var(--panel-border);
}
body.pad-landscape #coach-slot .coach-lesson { max-width: none; max-height: 100%; }
body.pad-landscape:has(#coach-slot .coach-lesson) #phase-root { padding-right: var(--lesson-w); }
body.pad-landscape:has(#coach-slot .coach-lesson) #hand-slot { right: var(--lesson-w); }
/* In the column: Tell Me More and Stop Lessons on one line, Got It under them. */
body.pad-landscape #coach-slot .coach-lesson .coach-lesson-actions { flex-wrap: wrap; }
body.pad-landscape #coach-slot .coach-lesson .coach-lesson-gap { flex: 1; }
body.pad-landscape #coach-slot .coach-lesson .coach-lesson-actions .coach-gotit { order: 3; flex: 1 0 100%; }
/* With a lesson column there is no room beside the cross for the side
   seats as well (the website's plates are wider than the app's), so the
   turn line and the progress go under it: there is the height, as the
   lesson takes width, not height. */
body.pad-landscape:has(#coach-slot .coach-lesson) .game-table.phase-tricking {
  grid-template-columns: minmax(120px, 1fr) calc(var(--pad-cross) + 2 * (var(--pad-seat-gap) - 16px)) minmax(120px, 1fr);
}
body.pad-landscape:has(#coach-slot .coach-lesson) .game-table.phase-tricking .center-zone:has(> .trick-area) {
  display: flex;
  flex-direction: column;
}
