/* ============================================================
   SOLITAIRE FAMILY — shared stylesheet
   §69: SHARE THE DESIGN SYSTEM, NOT THE BOARD GEOMETRY.

   Every Solitaire game (Klondike, FreeCell, Spider, Pyramid,
   TriPeaks) links this file FIRST, then its own board file.
   This holds:
     - the OnlineCardGames.org product layer (cards, typography,
       gold action, buttons, modals, results, spacing, radii)
     - the Solitaire category tokens (midnight blue / slate)
     - the card face and card back
     - the empty-slot treatment
     - chrome: header, HUD, buttons, panels, hint, score delta

   What each game adds separately is ONLY its board arrangement:
   Klondike's 7 columns + stock/waste/4 foundations, FreeCell's
   8 columns + 4 free cells, Spider's 10 columns, and so on.

   CONTRAST FLOORS, all measured and enforced by the test suite:
     - card back vs felt ...... border >= 3.0 (it is the border,
       not the ground, that makes a face-down card read)
     - empty slot outline ..... >= 3.0 against the felt
     - suit cue in a slot ..... >= 3.0
   These exist because each one shipped broken once: the card back
   was built from the felt tokens (contrast 1.27) and the slot
   outline measured 1.45, so cards and slots were invisible.

   Renderer constraints: no `gap`/`row-gap`/`column-gap`, no
   `inset:` shorthand, no double-position gradient colour stops.
   ============================================================ */

:root{
  /* CATEGORY TOKENS (PART 13-17). Rummy keeps its green; the
     trick-taking family uses deep petrol / navy-teal. These are the
     only colours that differ between the two families. */
  /* CATEGORY TOKENS — Solitaire family (§4). Rummy keeps its green and
     the trick-taking family its petrol; Solitaire is midnight blue/slate.
     These are the only colours that differ from the other categories. */
  --table-bg:#26384A;
  --table-deep:#1B2938;
  --table-highlight:#31485E;

  --text-primary:#F6F7F4;
  --text-secondary:#CBD2D8;
  --text-muted:#B4BEC5;

  --action:#F0B84B;
  --action-text:#1A1F1D;

  --our-team:#62C4FF;
  --their-team:#D8B26A;

  --success:#62C58B;
  --danger:#E45B64;
  --frozen:#82C7E8;

  --card-bg:#FAFAF7;
  --card-black:#202322;
  /* #D84A52 on the card face is 4.0:1 — under the WCAG floor for normal
     text, in every game in the catalogue that shows a red card. Found by
     contrast-sweep.js on 2026-09-21, after Ronan reported the Cribbage
     captions from his phone. #CB343E is 4.9:1 and still plainly red. */
  --card-red:#CB343E;

  --line:rgba(255,255,255,.12);
  --line-soft:rgba(255,255,255,.07);
  --panel:rgba(0,0,0,.20);

  --space-1:4px;  --space-2:8px;  --space-3:12px;
  --space-4:16px; --space-5:24px; --space-6:32px;

  --radius:10px;
  /* Cards and small controls take a slightly tighter corner than panels.
     Six rules referenced --radius-sm before it existed, so every one of
     them resolved to 0 and rendered square. */
  --radius-sm:6px;
  /* Referenced by the ported layer and the panel chrome but never defined
     here, so both resolved to nothing. */
  --active:#62C4FF;
  --panel-bg:#16212C;

  /* Card geometry is driven by BOTH vw and vh so the composition is
     guaranteed to fit the viewport height, not just its width — the
     measured cause of the previous build's 300px desktop overflow. */
  /* PART 12: Oklahoma shows only 10-11 cards, so they carry more size than
     a Canasta hand — 75-85px wide, 108-122px high at full desktop. */
  --card-w:clamp(48px, min(6.9vw, 11.6vh), 85px);
  --card-h:calc(var(--card-w) / .70);
  --pile-w:clamp(50px, min(6.4vw, 11.1vh), 82px);
  --pile-h:calc(var(--pile-w) / .70);
  --meld-w:clamp(30px, min(3.6vw, 6.3vh), 52px);
  --meld-h:calc(var(--meld-w) / .70);
  --back-w:clamp(20px, min(2.6vw, 4.6vh), 40px);
  --back-h:calc(var(--back-w) / .70);

  --font-ui:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
  --font-mono:'IBM Plex Mono',ui-monospace,monospace;
  --font-display:'Playfair Display',Georgia,serif;
}

*{ box-sizing:border-box; }
html,body{ margin:0; padding:0; height:100%; }

body{
  font-family:var(--font-ui);
  color:var(--text-primary);
  background:radial-gradient(circle at 50% 38%, #31485E 0%, #26384A 58%, #1B2938 100%);
  height:100dvh;
  display:flex;
  flex-direction:column;
  overflow:hidden;
  padding-top:env(safe-area-inset-top);
  padding-bottom:env(safe-area-inset-bottom);
}
.mono{ font-family:var(--font-mono); }

/* ============================================================
   Five-zone frame (§8). Rows are sized so the middle table zone
   absorbs slack and the hand zone is protected last (§98).
   ============================================================ */
.game{
  flex:1 1 auto;
  min-height:0;
  /* A flex item defaults to min-width:auto and will refuse to shrink below
     its min-content width — measured as a 355px frame inside a 320px
     viewport, overflowing every zone by 25px. */
  min-width:0;
  width:min(1200px,100%);
  margin-left:auto; margin-right:auto;
  position:relative;
  display:grid;
  grid-template-rows:auto auto auto auto auto auto;
  /* Anchor the table to the TOP. Centring the block meant any phase with
     shorter content (the draft, where the hand is still being built)
     pushed everything down and left dead space above the header — up to
     55px at 1200x675. Slack now falls below the hand, where it reads as
     table rather than as a gap. */
  align-content:start;
  padding:0 clamp(8px,1.6vw,22px);
}
.zone-header, .zone-seats, .zone-condition, .zone-table, .zone-status, .zone-hand{
  min-width:0; min-height:0; overflow:hidden;
}

/* ---------------- ZONE 1 — header (45–55px) ---------------- */
.zone-header{
  min-height:46px;
  display:flex; align-items:center; justify-content:space-between;
  padding:var(--space-2) 0 var(--space-1);
  border-bottom:1px solid var(--line-soft);
}
.wordmark .title{
  font-family:var(--font-display); font-weight:800; font-style:italic;
  font-size:clamp(17px,2vw,24px); color:var(--action); white-space:nowrap;
}

.scoreline{ display:flex; align-items:baseline; justify-content:center; flex:1 1 auto; min-width:0; }
.scoreline > *{ margin-left:6px; }
.scoreline > *:first-child{ margin-left:0; }
.team-name{ font-size:12px; font-weight:600; letter-spacing:.04em; text-transform:uppercase; white-space:nowrap; }
.team-name.ours{ color:var(--our-team); }
.team-name.theirs{ color:var(--their-team); }
.team-total{ font-size:17px; font-weight:700; }
.score-dash{ color:var(--text-muted); font-size:13px; }

.header-controls{ display:flex; align-items:center; }
.header-controls > *{ margin-left:var(--space-2); }
.hand-pill{
  font-size:11px; color:var(--text-secondary);
  border:1px solid var(--line); border-radius:999px; padding:3px 9px; white-space:nowrap;
}
/* Icon-only utility buttons always carry a text label beneath the glyph. */
.icon-btn{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  min-width:44px; min-height:40px; padding:2px 6px;
  background:rgba(255,255,255,.06); border:1px solid var(--line);
  border-radius:var(--radius); color:var(--text-secondary); cursor:pointer;
  font-family:var(--font-ui);
}
.icon-btn:hover{ border-color:var(--action); color:var(--text-primary); }
.icon-btn .icon{ font-size:15px; line-height:1.1; }
.icon-btn .icon-label{ font-size:9px; letter-spacing:.06em; text-transform:uppercase; margin-top:1px; }

/* ---------------- ZONE 2 — partner + opponents (75–100px) ---------------- */
/* All three AI seats share ONE row: West | Partner | East, which also
   mirrors the physical table. Stacking partner above the opponents made
   this zone 173px against the brief's 75-100px budget, and the overspend
   was what forced the meld panels into internal scrolling. */
.zone-seats{
  display:grid;
  grid-template-columns:1fr auto 1fr;
  grid-template-areas:"west partner east";
  align-items:center;
  padding:var(--space-1) 0;
}
.seat-partner{ grid-area:partner; align-items:center; }
.seat-west{ grid-area:west; align-items:flex-start; }
.seat-east{ grid-area:east; align-items:flex-end; }

.seat{ display:flex; flex-direction:column; min-width:0; max-width:100%; overflow:hidden; }
.seat-head{ display:flex; align-items:center; }
.seat-head > *{ margin-left:5px; }
.seat-head > *:first-child{ margin-left:0; }
.seat-dot{
  width:7px; height:7px; border-radius:50%;
  background:transparent; border:1px solid var(--line);
}
.seat.is-acting .seat-dot{ background:var(--action); border-color:var(--action); }
.seat-name{ font-size:13px; font-weight:600; color:var(--text-secondary); white-space:nowrap; }
.seat.is-acting .seat-name{ color:var(--text-primary); font-weight:700; }
.seat-partner .seat-name{ color:var(--our-team); }
.seat-west .seat-name, .seat-east .seat-name{ color:var(--their-team); }
.seat-west .seat-backs .card-back, .seat-east .seat-backs .card-back{
  width:calc(var(--back-w) * .78); height:calc(var(--back-h) * .78);
}
.seat-count{
  font-size:11px; color:var(--text-muted);
  border:1px solid var(--line-soft); border-radius:999px; padding:1px 6px;
}
.seat-backs{ display:flex; margin-top:3px; min-height:2px; }
.seat-backs .card-back{ margin-left:calc(var(--back-w) * -.62); }
.seat-backs .card-back:first-child{ margin-left:0; }
.seat-action{
  font-size:11px; color:var(--text-muted); margin-top:2px;
  max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.seat.is-acting{ }

/* ---------------- ZONE 3 — main table (220–250px) ---------------- */
.zone-table{
  min-height:0;
  align-items:start;
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,.78fr) minmax(0,1.2fr);
  align-items:stretch;
}
.zone-table > * + *{ margin-left:clamp(8px,1.5vw,22px); }

/* --- meld panels --- */
.meldpanel{ min-width:0; min-height:0; display:flex; flex-direction:column; }
.meldpanel-head{
  display:flex; align-items:baseline; width:100%;
  background:transparent; border:none; padding:0 0 4px;
  border-bottom:1px solid var(--line-soft);
  color:inherit; font-family:var(--font-ui); cursor:default; text-align:left;
}
.meldpanel-head > *{ margin-left:6px; }
.meldpanel-head > *:first-child{ margin-left:0; }
.panel-title{ font-size:13px; font-weight:600; letter-spacing:.05em; text-transform:uppercase; white-space:nowrap; }
.meldpanel.ours .panel-title{ color:var(--our-team); }
.meldpanel.theirs .panel-title{ color:var(--their-team); }
.panel-sub{ font-size:11px; color:var(--text-muted); white-space:nowrap; }
.panel-count{
  font-size:11px; color:var(--text-secondary); margin-left:auto;
  border:1px solid var(--line-soft); border-radius:999px; padding:0 6px;
}
.panel-chevron{ font-size:10px; color:var(--text-muted); display:none; }

.red3-row{ display:flex; align-items:center; flex-wrap:wrap; min-height:0; }
.red3-row:not(:empty){ margin-top:4px; }
.red3-chip{
  display:flex; align-items:center; font-size:11px; font-weight:600;
  border:1px solid var(--line); border-radius:999px; padding:1px 8px; margin-right:6px;
}
.red3-chip.bonus{ color:var(--success); border-color:rgba(98,197,139,.45); }
.red3-chip.penalty{ color:var(--danger); border-color:rgba(228,91,100,.45); }

.meldlist{
  margin-top:6px; min-height:0; flex:0 1 auto; max-height:100%;
  display:flex; flex-wrap:wrap; align-content:flex-start;
  overflow-y:auto; overflow-x:hidden;
}
.meldlist::-webkit-scrollbar{ width:4px; }
.meldlist::-webkit-scrollbar-thumb{ background:var(--line); border-radius:2px; }
.meldlist-empty{ font-size:11px; color:var(--text-muted); font-style:italic; }

.meld{ margin-right:10px; margin-bottom:8px; max-width:100%; }
.meld-header{ display:flex; align-items:baseline; margin-bottom:2px; }
.meld-header > *{ margin-left:5px; }
.meld-header > *:first-child{ margin-left:0; }
.meld-rank{ font-size:12px; font-weight:700; color:var(--text-primary); }
.meld-note{ font-size:10px; color:var(--text-muted); letter-spacing:.04em; text-transform:uppercase; white-space:nowrap; }
.meld.is-canasta .meld-note{ color:var(--success); font-weight:600; }
.meld.is-canasta-mixed .meld-note{ color:var(--their-team); }
.meld-cards{ display:flex; }
.meld-cards > * + *{ margin-left:calc(var(--meld-w) * -.52); }
.meld.is-target .meld-cards{ box-shadow:0 0 0 2px var(--action); border-radius:6px; }
.meld.is-clickable{ cursor:pointer; }

/* --- stock / discard --- */
.zone-piles{
  min-width:0; display:flex; flex-direction:column;
  align-items:center; justify-content:flex-start;
}
.piles{ display:flex; align-items:flex-start; justify-content:center; }
.piles > * + *{ margin-left:clamp(10px,1.6vw,22px); }
.pile{ display:flex; flex-direction:column; align-items:center; }
.pile-card{
  position:relative; width:var(--pile-w); height:var(--pile-h);
  border-radius:8px;
}
.pile-card.stock{ border:2px dashed var(--line); }
.pile-card.stock.has-cards{ border:none; }
.pile-card:focus-visible{ outline:3px solid var(--action); outline-offset:2px; }
.pile-card.is-actionable{ cursor:pointer; box-shadow:0 0 0 2px var(--action), 0 6px 16px rgba(0,0,0,.35); }
.pile-card .card-back{ position:absolute; top:0; left:0; width:var(--pile-w); height:var(--pile-h); }
/* Depth must fall BEHIND the top card. Offsetting by nth-child pushed the
   topmost back right-and-up, leaving the cards beneath exposed along the
   left edge — their white borders read as a clipped card. Counting from the
   end keeps the top card flush at the container origin for any pile depth. */
.pile-card .card-back:last-child{ top:0; left:0; }
.pile-card .card-back:nth-last-child(2){ top:2px; left:2px; }
.pile-card .card-back:nth-last-child(3){ top:4px; left:4px; }
.pile-card .card{ width:var(--pile-w); height:var(--pile-h); }
.pile-empty-note{
  position:absolute; top:50%; left:0; right:0; transform:translateY(-50%);
  text-align:center; font-size:10px; color:var(--text-muted);
}
.pile-meta{ display:flex; align-items:baseline; margin-top:5px; }
.pile-meta > * + *{ margin-left:5px; }
.pile-name{ font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:var(--text-muted); }
.pile-meta .mono{ font-size:12px; color:var(--text-secondary); font-weight:600; }
.pile-state{ margin-top:8px; text-align:center; min-height:16px; }
.pile-flag{
  display:inline-flex; align-items:center; font-size:12px; font-weight:600;
  border-radius:999px; padding:2px 10px;
}
.pile-flag.frozen{ color:var(--frozen); border:1px solid rgba(130,199,232,.45); background:rgba(130,199,232,.08); }
.pile-flag.blocked{ color:var(--danger); border:1px solid rgba(228,91,100,.45); }
.pile-flag.takeable{ color:var(--success); border:1px solid rgba(98,197,139,.45); }

/* ---------------- ZONE 4 — status / current action (55–75px) ---------------- */
.zone-status{
  padding:var(--space-2) 0 var(--space-1);
  border-top:1px solid var(--line-soft);
  text-align:center;
}
.status-strip{
  font-size:12px; color:var(--text-secondary);
  display:flex; align-items:center; justify-content:center; flex-wrap:wrap; min-height:16px;
}
.status-strip .chip{ white-space:nowrap; }
.status-strip .chip + .chip::before{
  content:'·'; color:var(--text-muted); margin-left:8px; margin-right:8px;
}
.status-strip .chip.met{ color:var(--success); font-weight:600; }
.status-strip .chip.unmet{ color:var(--action); font-weight:600; }

.action-line{ display:flex; align-items:center; justify-content:center; flex-wrap:wrap; margin-top:3px; }
.current-action{
  font-size:clamp(15px,1.6vw,20px); font-weight:700; letter-spacing:.01em;
  color:var(--text-primary);
}
.turn-steps{
  display:flex; list-style:none; margin:0; padding:0; margin-left:var(--space-4);
  font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:var(--text-muted);
}
.turn-steps li{ display:flex; align-items:center; margin-left:10px; }
.turn-steps li:first-child{ margin-left:0; }
.turn-steps li + li::before{ content:'\2192'; margin-right:10px; color:var(--line); }
.step-mark{
  width:8px; height:8px; border-radius:50%; border:1px solid var(--line); margin-right:5px;
}
.turn-steps li.is-current{ color:var(--text-primary); font-weight:600; }
.turn-steps li.is-current .step-mark{ background:var(--action); border-color:var(--action); }
.turn-steps li.is-done{ color:var(--success); }
.turn-steps li.is-done .step-mark{ background:var(--success); border-color:var(--success); }

.explain{
  font-size:12px; color:var(--action); min-height:15px; margin-top:2px;
  padding-left:var(--space-3); padding-right:var(--space-3);
}
.explain.is-error{ color:var(--danger); }

/* ---------------- ZONE 5 — human hand (210–250px) ---------------- */
.zone-hand{
  display:flex; flex-direction:column; justify-content:flex-end;
  padding-bottom:var(--space-2);
}
.hand{
  display:flex; flex-direction:column; align-items:center; justify-content:flex-end;
  width:min(940px,100%); margin-left:auto; margin-right:auto;
  padding-top:16px; /* room for the lift on selected cards */
}
.hand-row{ display:flex; justify-content:center; }
.hand-row + .hand-row{ margin-top:6px; }
.hand-row .card{ cursor:pointer; }
.hand .card{ width:var(--card-w); height:var(--card-h); }
.hand .card.selected{
  transform:translateY(-14px); z-index:20;
  box-shadow:0 0 0 3px var(--action), 0 8px 18px rgba(0,0,0,.28);
}
.hand .card:focus-visible{ outline:3px solid var(--action); outline-offset:2px; }

.actionbar{
  display:flex; align-items:center; justify-content:space-between;
  width:min(940px,100%); margin-left:auto; margin-right:auto; margin-top:var(--space-2);
}
.selection-info{ font-size:12px; color:var(--text-secondary); white-space:nowrap; margin-right:var(--space-3); }
.selection-info strong{ color:var(--text-primary); }
.action-buttons{ display:flex; align-items:center; }
.action-buttons > * + *{ margin-left:var(--space-2); }

/* ---------------- buttons ---------------- */
.btn{
  font-family:var(--font-ui); font-weight:700; font-size:15px;
  min-height:44px; padding:10px 18px;
  border-radius:var(--radius); border:1px solid var(--line);
  background:rgba(255,255,255,.06); color:var(--text-primary);
  cursor:pointer; white-space:nowrap;
  transition:transform .08s ease, box-shadow .15s ease, background .15s ease;
}
.btn:hover{ background:rgba(255,255,255,.11); }
.btn:active{ transform:translateY(1px); }
.btn:focus-visible{ outline:3px solid var(--action); outline-offset:2px; }
.btn.secondary{
  background:rgba(255,255,255,.06); border:1px solid var(--line-soft); color:var(--text-secondary);
  font-weight:600; font-size:13px; padding:10px 14px;
}
/* Exactly one gold action at a time (§87) — applied by the controller. */
.btn.is-primary-action{
  background:var(--action); color:var(--action-text); border-color:var(--action);
  box-shadow:0 6px 16px rgba(240,184,75,.25);
}
.btn.is-primary-action:hover{ background:#F5C86B; }
.btn[hidden]{ display:none; }
.btn:disabled{ opacity:.38; cursor:not-allowed; transform:none; box-shadow:none; }
.btn.wide{ width:100%; margin-top:var(--space-3); }

/* ============================================================
   Cards
   ============================================================ */
.card{
  position:relative; width:var(--card-w); height:var(--card-h);
  border-radius:6px; background:var(--card-bg);
  box-shadow:0 3px 8px rgba(0,0,0,.35), inset 0 0 0 1px rgba(0,0,0,.08);
  display:inline-flex; align-items:center; justify-content:center;
  user-select:none; overflow:hidden; flex:0 0 auto;
  transition:transform .12s ease, box-shadow .12s ease;
}
.card.red{ color:var(--card-red); }
.card:not(.red){ color:var(--card-black); }
.card-corner{
  position:absolute; font-family:var(--font-mono); font-weight:700;
  font-size:calc(var(--card-w) * .21); line-height:1.02; text-align:center;
}
.card-corner-top{ top:3px; left:4px; }
.card-corner-bottom{ bottom:3px; right:4px; transform:rotate(180deg); }
.card-centre-suit{ font-size:calc(var(--card-w) * .36); opacity:.85; }
.court-figure{ width:72%; height:62%; display:block; }
.card .wild-tag{
  position:absolute; bottom:2px; left:0; right:0; text-align:center;
  font-family:var(--font-mono); font-size:8px; letter-spacing:.08em;
  color:var(--text-muted);
}

.meld-cards .card{ width:var(--meld-w); height:var(--meld-h); }
.meld-cards .card .card-corner{ font-size:calc(var(--meld-w) * .27); }
.meld-cards .card .card-centre-suit{ font-size:calc(var(--meld-w) * .40); }
.meld-cards .card:not(:last-child) .card-corner-bottom{ display:none; }

.card-back{
  width:var(--back-w); height:var(--back-h); border-radius:6px;
  background:#8E2C3A;
  background-image:
    repeating-linear-gradient(45deg, rgba(250,250,247,.75) 0px, rgba(250,250,247,.75) 1px, transparent 1px, transparent 8px),
    repeating-linear-gradient(-45deg, rgba(250,250,247,.75) 0px, rgba(250,250,247,.75) 1px, transparent 1px, transparent 8px);
  border:2px solid var(--card-bg);
  box-shadow:0 2px 6px rgba(0,0,0,.4);
  flex:0 0 auto;
}

/* ============================================================
   Overlays — scoped to .game, never the whole page
   ============================================================ */
.overlay{
  position:absolute; top:0; left:0; right:0; bottom:0; z-index:40;
  display:none; align-items:center; justify-content:center;
  background:rgba(6,18,15,.82);
  padding:var(--space-4);
}
.overlay.open{ display:flex; }
.overlay-panel{
  background:linear-gradient(165deg,#174A3F 0%,#0D3029 100%);
  border:1px solid var(--line); border-radius:16px;
  box-shadow:0 24px 60px rgba(0,0,0,.55);
  padding:var(--space-5); width:min(440px,100%); max-height:100%;
  overflow-y:auto; text-align:center;
}
.overlay-panel.wide-panel{ width:min(560px,100%); }
.overlay-panel h2{
  font-family:var(--font-display); font-style:italic; color:var(--action);
  margin:0 0 6px; font-size:23px;
}
.overlay-panel p.sub{ font-size:12px; color:var(--text-secondary); margin:0 0 var(--space-4); }

/* end-of-hand breakdown (§130) */
.summary-grid{ text-align:left; }
.summary-team{ margin-bottom:var(--space-4); }
.summary-team h3{
  font-size:12px; letter-spacing:.06em; text-transform:uppercase;
  margin:0 0 6px; padding-bottom:4px; border-bottom:1px solid var(--line-soft);
}
.summary-team.ours h3{ color:var(--our-team); }
.summary-team.theirs h3{ color:var(--their-team); }
.summary-row{
  display:flex; justify-content:space-between; align-items:baseline;
  font-size:13px; padding:3px 0; color:var(--text-secondary);
}
.summary-row .val{ font-family:var(--font-mono); font-weight:600; color:var(--text-primary); }
.summary-row .val.pos{ color:var(--success); }
.summary-row .val.neg{ color:var(--danger); }
.summary-row.total{
  margin-top:5px; padding-top:6px; border-top:1px solid var(--line-soft);
  font-weight:700; color:var(--text-primary); font-size:14px;
}
.running-total{
  display:flex; justify-content:space-between; align-items:baseline;
  border-top:1px solid var(--line); padding-top:var(--space-3); margin-top:var(--space-2);
  font-size:14px;
}
.running-total .rt-pair{ display:flex; flex-direction:column; align-items:flex-start; }
.running-total .rt-label{ font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:var(--text-muted); }
.running-total .rt-val{ font-family:var(--font-mono); font-weight:700; font-size:20px; }
.win-stats{ font-size:13px; color:var(--text-secondary); margin-bottom:var(--space-2); }

.rules-panel{ text-align:left; }
.rules-panel h2, .rules-panel p.sub{ text-align:center; }
.rules-list{ list-style:none; margin:0; padding:0; }
.rules-list li{
  font-size:12.5px; line-height:1.5; color:var(--text-secondary);
  padding:7px 0; border-bottom:1px solid var(--line-soft);
}
.rules-list li:last-child{ border-bottom:none; }

footer{
  flex:0 0 auto;
  text-align:center; font-size:10px; color:#B3BFBA;
  line-height:1.4; padding:5px 12px;
  max-width:820px; margin-left:auto; margin-right:auto;
}

/* ============================================================
   COMPACT LAYOUT — mobile portrait recomposition (§102–§120).
   Driven by the game container's measured width, so it behaves
   correctly inside an iframe rather than tracking device width.
   Order: action → piles → our melds → hand, with Their Melds
   collapsible because it is the least directly actionable.
   ============================================================ */
.game[data-layout="compact"]{
  --card-w:clamp(52px, 15.5vw, 68px); /* PART 61 */
  --pile-w:clamp(52px, 15.5vw, 68px);
  --meld-w:clamp(26px, 8.2vw, 38px);
  --back-w:clamp(16px, 4.6vw, 24px);
  /* The height tokens MUST be restated here. A custom property is
     substituted where it is declared, so --card-h declared on :root
     resolves against the :root --card-w and would ignore the overrides
     above — which measured as 63x66 cards (nearly square) on mobile. */
  --card-h:calc(var(--card-w) / .70);
  --pile-h:calc(var(--pile-w) / .70);
  --meld-h:calc(var(--meld-w) / .70);
  --back-h:calc(var(--back-w) / .70);
  padding:0 10px;
  /* Rows size to content; the block centres. Six zones: header, condition,
     opponent, piles, status, hand. */
  grid-template-rows:auto auto auto auto auto auto;
}
/* Status sits directly above the hand on mobile, so the current action is
   next to the cards the player is touching (§115) — not stranded at the
   top of the screen. Explicit order on every zone; relying on order:0 for
   the rest had pushed the status zone above the header. */
.game[data-layout="compact"] .zone-header{ order:1; min-height:42px; }
/* PART 59/60 — the Oklahoma condition sits directly under the score. */
.game[data-layout="compact"] .zone-condition{ order:2; padding:4px 0 2px; }
.game[data-layout="compact"] .zone-seats{ order:3; }
.game[data-layout="compact"] .zone-table{ order:4; }
.game[data-layout="compact"] .zone-status{
  order:5; border-top:1px solid var(--line-soft); padding:5px 0 4px;
}
.game[data-layout="compact"] .zone-hand{ order:6; padding-bottom:6px; }
.game[data-layout="compact"] .wordmark{ display:none; }
.game[data-layout="compact"] .scoreline{ justify-content:flex-start; }
.game[data-layout="compact"] .team-name{ font-size:10px; }
.game[data-layout="compact"] .team-total{ font-size:15px; }
.game[data-layout="compact"] .hand-pill{ display:none; }
.game[data-layout="compact"] .icon-btn{ min-width:40px; min-height:38px; }

/* Partner + opponents collapse to one compact row (§117). */
.game[data-layout="compact"] .zone-seats{
  display:flex; align-items:center; justify-content:space-between;
  padding:5px 0;
}
.game[data-layout="compact"] .seat{ align-items:center; }
.game[data-layout="compact"] .seat-head > *{ margin-left:4px; }
.game[data-layout="compact"] .seat-name{ font-size:11px; }
.game[data-layout="compact"] .seat-count{ font-size:10px; padding:0 5px; }
.game[data-layout="compact"] .seat-backs{ display:none; }
.game[data-layout="compact"] .seat-action{ display:none; }

.game[data-layout="compact"] .action-line{ flex-direction:column; }
.game[data-layout="compact"] .turn-steps{ display:none; }
.game[data-layout="compact"] .explain{
  font-size:11px; line-height:1.25; max-height:28px; overflow:hidden;
  padding-left:4px; padding-right:4px;
}
.game[data-layout="compact"] .status-strip{ min-height:14px; }
.game[data-layout="compact"] .turn-steps{ margin-left:0; margin-top:4px; }
.game[data-layout="compact"] .current-action{ font-size:clamp(15px,4.4vw,19px); }
.game[data-layout="compact"] .status-strip{ font-size:11px; }

.game[data-layout="compact"] .zone-table{
  display:flex; flex-direction:column; min-height:0;
}
.game[data-layout="compact"] .zone-table > * + *{ margin-left:0; margin-top:8px; }
.game[data-layout="compact"] .zone-piles{ order:1; }
/* Our Melds must win the space fight against Their Melds: measured at
   320px it had collapsed to 0px height while theirs kept 89px. */
.game[data-layout="compact"] .zone-piles{ flex:0 0 auto; }
.game[data-layout="compact"] .meldpanel.ours{ order:2; flex:0 1 auto; min-height:64px; }
.game[data-layout="compact"] .meldpanel.theirs{ order:3; flex:0 0 auto; max-height:34%; }

.game[data-layout="compact"] .meldpanel-head{ cursor:pointer; padding-bottom:5px; }
.game[data-layout="compact"] .panel-chevron{ display:inline; margin-left:6px; transition:transform .15s ease; }
.game[data-layout="compact"] .panel-sub{ display:none; }
.game[data-layout="compact"] .meldpanel.is-collapsed .meldlist{ display:none; }
.game[data-layout="compact"] .meldpanel.is-collapsed .red3-row{ display:none; }
.game[data-layout="compact"] .meldpanel.is-collapsed .panel-chevron{ transform:rotate(-90deg); }
.game[data-layout="compact"] .piles > * + *{ margin-left:22px; }

.game[data-layout="compact"] .actionbar{ flex-direction:row; align-items:center; }
.game[data-layout="compact"] .selection-info{ font-size:11px; margin-right:8px; }
.game[data-layout="compact"] .action-buttons{ justify-content:center; }
.game[data-layout="compact"] .action-buttons .btn{ flex:1 1 auto; min-height:48px; }
.game[data-layout="compact"] .hand .card.selected{ transform:translateY(-16px); }
body[data-layout="compact"] footer{ font-size:9px; padding:3px 10px; line-height:1.3; }

/* ============================================================
   Accessibility
   ============================================================ */
@media (prefers-reduced-motion: reduce){
  *{ transition-duration:.01ms !important; animation-duration:.01ms !important; }
  .hand .card.selected{ transform:none; }
}

/* Compressed meld tail on mobile (§71, §107) — a completed canasta shows a
   short stack plus a count rather than seven overlapping cards. */
.meld-more{
  display:inline-flex; align-items:center; justify-content:center;
  margin-left:6px; padding:0 6px; height:var(--meld-h);
  font-family:var(--font-mono); font-size:11px; font-weight:700;
  color:var(--text-secondary); background:rgba(255,255,255,.08);
  border:1px solid var(--line); border-radius:6px;
}

/* Final compact trims: reclaim the last vertical slack for Our Melds. */
.game[data-layout="compact"] .pile-state{ margin-top:4px; min-height:14px; }
.game[data-layout="compact"] .pile-flag{ font-size:11px; padding:1px 8px; }
.game[data-layout="compact"] .pile-meta{ margin-top:3px; }
.game[data-layout="compact"] .hand{ padding-top:12px; }
.game[data-layout="compact"] .meldlist{ margin-top:4px; }

/* Their Melds sheet (mobile). Scrolls inside the panel, never the table. */
.sheet-meldlist{ max-height:46vh; overflow-y:auto; margin-bottom:var(--space-3); }
.game[data-layout="compact"] .meldpanel.theirs .meldlist{ display:none; }
.game[data-layout="compact"] .meldpanel.theirs .red3-row{ display:none; }
.game[data-layout="compact"] .meldpanel.theirs .panel-chevron{ transform:rotate(-90deg); }

/* §144 — meld the current selection would extend. Softer than an explicit
   tap target, because the player has not chosen it yet. */
.meld.is-suggested .meld-cards{
  box-shadow:0 0 0 2px rgba(240,184,75,.45); border-radius:6px;
}

/* §67 recent-turn log */
.turnlog{ list-style:none; margin:0 0 var(--space-3); padding:0; text-align:left; max-height:44vh; overflow-y:auto; }
.turnlog li{
  font-size:12.5px; line-height:1.45; color:var(--text-secondary);
  padding:7px 0; border-bottom:1px solid var(--line-soft);
}
.turnlog li:last-child{ border-bottom:none; }
.turnlog li.is-you{ color:var(--text-primary); font-weight:600; }
.turnlog-empty{ font-size:12px; color:var(--text-muted); font-style:italic; }

/* §148 expanded meld */
.meld-detail-cards{
  display:flex; flex-wrap:wrap; justify-content:center; margin-bottom:var(--space-3);
}
.meld-detail-cards .card{
  width:clamp(40px,11vw,58px); height:auto; aspect-ratio:.70;
  margin:3px;
}

/* §21 — manual card arrangement */
.hand .card.is-dragging{
  z-index:60; opacity:.92; cursor:grabbing;
  box-shadow:0 0 0 3px var(--action), 0 14px 26px rgba(0,0,0,.42);
}
.hand .card.drop-before{ box-shadow:-3px 0 0 0 var(--action), 0 3px 8px rgba(0,0,0,.35); }
.hand .card.drop-after{ box-shadow:3px 0 0 0 var(--action), 0 3px 8px rgba(0,0,0,.35); }

/* §70 — compact the melds before ever resorting to an internal scrollbar.
   Density is stepped up by the controller only while the panel overflows. */
.meld{ margin-right:8px; margin-bottom:6px; }
.meldlist[data-density="1"]{ --meld-w:clamp(26px, min(3.1vw, 5.4vh), 44px); --meld-h:calc(var(--meld-w) / .70); }
.meldlist[data-density="1"] .meld{ margin-right:6px; margin-bottom:4px; }
.meldlist[data-density="1"] .meld-note{ font-size:9px; }
.meldlist[data-density="2"]{ --meld-w:clamp(22px, min(2.7vw, 4.7vh), 38px); --meld-h:calc(var(--meld-w) / .70); }
.meldlist[data-density="2"] .meld{ margin-right:5px; margin-bottom:3px; }
.meldlist[data-density="2"] .meld-header{ margin-bottom:0; }
.meldlist[data-density="2"] .meld-note{ font-size:9px; }
.meldlist[data-density="1"] .meld-card, .meldlist[data-density="2"] .meld-card{ }

/* Density 3 — text-only meld summaries for the tightest panels. Rank,
   natural/mixed status and card count are all still shown in words. */
.meldlist[data-density="3"] .meld-cards{ display:none; }
.meldlist[data-density="3"] .meld{
  margin-right:8px; margin-bottom:2px;
  border:1px solid var(--line-soft); border-radius:6px; padding:1px 6px;
}
.meldlist[data-density="3"] .meld-header{ margin-bottom:0; }
.meldlist[data-density="3"] .meld-rank{ font-size:11px; }
.meldlist[data-density="3"] .meld-note{ font-size:9px; }

/* Action bar: utilities separate from the contextual primary actions. */
.bar-utility{ display:flex; align-items:center; }
.utility-buttons{ display:flex; align-items:center; margin-left:var(--space-2); }
.utility-buttons > * + *{ margin-left:var(--space-2); }
.game[data-layout="compact"] .actionbar{ flex-direction:column; align-items:stretch; }
.game[data-layout="compact"] .bar-utility{ justify-content:space-between; margin-bottom:6px; }
.game[data-layout="compact"] .utility-buttons .btn{ min-height:38px; padding:7px 12px; font-size:12px; }
.game[data-layout="compact"] .action-buttons{ width:100%; }
.game[data-layout="compact"] .action-buttons .btn{ flex:1 1 0; min-width:0; padding:10px 8px; }
/* Long contextual labels must shrink rather than push the row off-screen. */
.game[data-layout="compact"] .action-buttons .btn{
  overflow:hidden; text-overflow:ellipsis;
}

/* ============================================================
   OKLAHOMA GIN — game-specific components.
   Should read as the cleanest game in the suite (PART 8).
   ============================================================ */

/* PART 11/77 — two-player content stays concentrated centrally. */
.zone-seats, .zone-table, .zone-status, .hand, .actionbar, .zone-condition{
  width:min(900px,100%); margin-left:auto; margin-right:auto;
}
.game[data-layout="compact"] .zone-seats,
.game[data-layout="compact"] .zone-table,
.game[data-layout="compact"] .zone-status,
.game[data-layout="compact"] .zone-condition{ width:100%; }

/* PART 32/33/65 — one compact opponent. */
.zone-seats{ display:flex; flex-direction:column; align-items:center; padding:var(--space-2) 0; }
.opp-backs{ display:flex; justify-content:center; margin-top:4px; }
.opp-backs .card-back{ width:clamp(30px,3.4vw,46px); height:calc(clamp(30px,3.4vw,46px) / .70); }
.opp-backs .card-back + .card-back{ margin-left:-26px; }
.game[data-layout="compact"] .opp-backs .card-back{ width:24px; height:34px; }
.game[data-layout="compact"] .opp-backs .card-back + .card-back{ margin-left:-15px; }
.last-action{
  font-size:12px; color:var(--text-secondary); text-align:center; margin-top:3px;
  max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* PART 2/20-24 — the Oklahoma condition is persistent state, never
   something the player has to remember from the opening up-card. */
.zone-condition{ display:flex; align-items:center; justify-content:center; padding:2px 0; }
.condition-pill{
  display:inline-flex; align-items:center; white-space:nowrap;
  font-size:clamp(13px,1.4vw,17px); font-weight:700; letter-spacing:.02em;
  border-radius:999px; padding:4px 14px;
  color:var(--action); border:1px solid rgba(240,184,75,.45); background:rgba(240,184,75,.08);
}
.condition-pill .sep{ color:var(--text-muted); margin-left:8px; margin-right:8px; font-weight:400; }
.condition-pill.gin-only{ color:var(--success); border-color:rgba(98,197,139,.5); background:rgba(98,197,139,.09); }
.condition-pill .dbl{ color:var(--their-team); }
/* PART 24 — the up-card is shown once, connected to the condition it set. */
.upcard-reveal{ display:flex; flex-direction:column; align-items:center; }
.upcard-reveal .card{ width:clamp(54px,6vw,78px); height:auto; aspect-ratio:.70; margin-bottom:var(--space-3); }
.upcard-reveal .cond-line{ font-size:16px; font-weight:700; color:var(--action); }
.upcard-reveal .cond-note{ font-size:12.5px; color:var(--text-secondary); margin-top:4px; }

/* PART 15/25/78 — stock and discard are one decision. */
.zone-table{ display:flex; align-items:center; justify-content:center; min-height:0; }
.piles > * + *{ margin-left:clamp(32px,6vw,80px); }

/* PART 3/18/64 — deadwood sits with the hand and is tied to the threshold. */
.deadwood-line{ display:flex; align-items:baseline; justify-content:center; flex-wrap:wrap; margin-top:2px; }
.deadwood-line .dw-btn{
  background:none; border:none; padding:0; cursor:pointer;
  font-family:var(--font-ui); font-size:clamp(15px,1.5vw,19px); font-weight:700;
  color:var(--text-primary); text-decoration:underline dotted rgba(255,255,255,.3);
}
.deadwood-line .dw-thresh{ font-size:12.5px; color:var(--text-muted); margin-left:8px; }
.deadwood-line .dw-flag{
  font-size:12px; font-weight:700; letter-spacing:.04em; text-transform:uppercase;
  border-radius:999px; padding:2px 10px; margin-left:10px;
}
.dw-flag.knock{ color:var(--action); border:1px solid rgba(240,184,75,.5); background:rgba(240,184,75,.08); }
.dw-flag.gin{ color:var(--success); border:1px solid rgba(98,197,139,.5); background:rgba(98,197,139,.09); }
.deadwood-breakdown{
  font-family:var(--font-mono); font-size:12px; color:var(--text-secondary);
  text-align:center; margin-top:3px; min-height:15px;
}

/* PART 16/17 — melds and deadwood distinguished by spacing plus a thin
   indicator, never by tinting whole cards. */
.hand-group{ display:flex; align-items:flex-end; }
.hand-group + .hand-group{ margin-left:clamp(18px,2.2vw,26px); }
.hand-group.is-deadwood{ margin-left:clamp(24px,3vw,32px); }
.hand-group-wrap{ display:flex; flex-direction:column; }
.hand-group-cards{ display:flex; align-items:flex-end; padding-bottom:3px; }
.hand-group.is-run .hand-group-cards,
.hand-group.is-set .hand-group-cards{ box-shadow:0 2px 0 0 var(--success); border-radius:2px; }
.hand-group.is-deadwood .hand-group-cards{ box-shadow:0 2px 0 0 var(--their-team); border-radius:2px; }
.hand-group-label{
  font-size:9.5px; font-weight:700; letter-spacing:.09em; text-transform:uppercase;
  text-align:center; margin-top:4px; color:var(--text-muted);
  white-space:nowrap; overflow:visible;
}
.hand-group.is-run .hand-group-label,
.hand-group.is-set .hand-group-label{ color:var(--success); }
.hand-group.is-deadwood .hand-group-label{ color:var(--their-team); }

/* PART 43-48 — transparent end-of-hand arithmetic, including the double. */
.reveal-sides{ text-align:left; }
.reveal-side{ margin-bottom:var(--space-3); }
.reveal-side h3{
  font-size:12px; letter-spacing:.06em; text-transform:uppercase;
  margin:0 0 5px; padding-bottom:4px; border-bottom:1px solid var(--line-soft);
}
.reveal-side.ours h3{ color:var(--our-team); }
.reveal-side.theirs h3{ color:var(--their-team); }
.reveal-melds{ display:flex; flex-wrap:wrap; margin-bottom:5px; }
.reveal-meld{ display:flex; margin-right:10px; margin-bottom:5px; }
.reveal-meld .card{ width:clamp(26px,3.2vw,38px); height:auto; aspect-ratio:.70; }
.reveal-meld .card + .card{ margin-left:calc(clamp(26px,3.2vw,38px) * -.45); }
.reveal-meld.is-deadwood .card{ opacity:.72; }
.reveal-tag{
  font-size:9.5px; letter-spacing:.06em; text-transform:uppercase;
  color:var(--text-muted); align-self:center; margin-left:5px;
}
.laid-off-note{ font-size:12px; color:var(--success); margin-bottom:5px; }
.summary-row.doubled .val{ color:var(--their-team); }
.summary-row.doubled{ border-top:1px solid var(--line-soft); margin-top:4px; padding-top:5px; }

/* ============================================================
   2-PLAYER SPADES — game-specific components.
   Four phases (PART 4): DRAFT / BID / PLAY / SCORE, each
   recomposing around the player's current task (PART 70).
   ============================================================ */

/* PART 92 — a two-player game stays concentrated centrally.
   The brief specifies 920px, but Gin and Oklahoma — the other two-player
   games in the suite — both use 900px, and a 20px difference nobody can
   perceive is not worth breaking visual consistency across the family for.
   Matched to 900px deliberately. */
.zone-seats, .zone-table, .zone-status, .hand, .actionbar, .zone-phase{
  width:min(900px,100%); margin-left:auto; margin-right:auto;
}
.game[data-layout="compact"] .zone-seats,
.game[data-layout="compact"] .zone-table,
.game[data-layout="compact"] .zone-status,
.game[data-layout="compact"] .zone-phase{ width:100%; }

/* PART 30/44 — one compact opponent, count always shown. */
.zone-seats{ display:flex; flex-direction:column; align-items:center; padding:var(--space-2) 0; }
.opp-backs{ display:flex; justify-content:center; margin-top:4px; }
.opp-backs .card-back{ width:clamp(30px,3.4vw,45px); height:calc(clamp(30px,3.4vw,45px) / .70); }
.opp-backs .card-back + .card-back{ margin-left:-26px; }
.game[data-layout="compact"] .opp-backs .card-back{ width:24px; height:34px; }
.game[data-layout="compact"] .opp-backs .card-back + .card-back{ margin-left:-15px; }
.seat-state{ font-size:12.5px; color:var(--text-secondary); margin-top:3px; white-space:nowrap; }
.seat-state .sep{ color:var(--text-muted); margin-left:6px; margin-right:6px; }

/* ---- PHASE 1: DRAFT (PART 23-29) ---- */
.zone-phase{ display:flex; flex-direction:column; align-items:center; justify-content:center; min-height:0; }
.draft-head{ text-align:center; margin-bottom:var(--space-2); }
.draft-title{
  font-size:clamp(15px,1.7vw,20px); font-weight:700; letter-spacing:.02em; color:var(--text-primary);
}
.draft-progress{ font-size:13.5px; color:var(--text-secondary); margin-top:2px; }
/* PART 24 — the draft card is deliberately larger than hand cards. */
.draft-card-slot{ display:flex; align-items:center; justify-content:center; min-height:0; }
.draft-card-slot .card{
  width:clamp(74px,9.5vw,118px); height:auto; aspect-ratio:.70;
  box-shadow:0 10px 26px rgba(0,0,0,.42), inset 0 0 0 1px rgba(0,0,0,.08);
}
.draft-card-slot .card.is-arriving{ animation:draftIn .22s ease-out; }
@keyframes draftIn{ from{ transform:translateY(-14px) scale(.96); opacity:.4; } to{ transform:none; opacity:1; } }
.draft-buttons{ display:flex; justify-content:center; margin-top:var(--space-3); }
.draft-buttons .btn{ min-width:clamp(120px,14vw,180px); min-height:50px; }
.draft-buttons > * + *{ margin-left:var(--space-3); }
/* PART 26 — the forced consequence, stated before the player commits. */
.draft-consequence{
  font-size:13px; color:var(--action); text-align:center; margin-top:var(--space-2);
  min-height:18px; padding-left:var(--space-3); padding-right:var(--space-3);
}
/* PART 27 — the second card is an outcome, not a decision. */
.auto-tag{
  display:inline-flex; align-items:center; font-size:11px; font-weight:800;
  letter-spacing:.08em; text-transform:uppercase; border-radius:999px; padding:2px 10px;
}
.auto-tag.keep{ color:var(--success); border:1px solid rgba(98,197,139,.5); background:rgba(98,197,139,.10); }
.auto-tag.discard{ color:var(--danger); border:1px solid rgba(228,91,100,.45); background:rgba(228,91,100,.08); }
.second-card-label{ font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:var(--text-muted); margin-bottom:4px; }

/* ---- PHASE 2: BID (PART 35-39) ---- */
.bid-panel{ display:flex; flex-direction:column; align-items:center; }
.bid-stepper{ display:flex; align-items:center; margin-top:var(--space-2); }
.bid-stepper > * + *{ margin-left:var(--space-4); }
.bid-step-btn{
  width:52px; height:52px; border-radius:50%;
  background:rgba(255,255,255,.06); border:1px solid var(--line);
  color:var(--text-primary); font-size:24px; font-weight:700; cursor:pointer;
}
.bid-step-btn:hover{ border-color:var(--action); }
.bid-step-btn:disabled{ opacity:.35; cursor:not-allowed; }
.bid-value{
  font-family:var(--font-mono); font-size:clamp(26px,3.4vw,40px); font-weight:700;
  min-width:3ch; text-align:center; color:var(--text-primary);
}
.bid-value.is-nil{ font-family:var(--font-ui); font-size:clamp(20px,2.6vw,30px); color:var(--success); }
.bid-note{ font-size:12.5px; color:var(--text-secondary); margin-top:4px; min-height:16px; }
.bid-actions{ display:flex; align-items:center; margin-top:var(--space-3); }
.bid-actions > * + *{ margin-left:var(--space-2); }
.blind-nil-flag{
  font-size:11px; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  color:var(--action); border:1px solid rgba(240,184,75,.45); background:rgba(240,184,75,.08);
  border-radius:999px; padding:2px 10px; margin-bottom:var(--space-2);
}

/* ---- PHASE 3: PLAY (PART 45-53) ---- */
.trick-area{ display:flex; align-items:center; justify-content:center; min-height:0; }
.trick-slot{ display:flex; flex-direction:column; align-items:center; }
.trick-slot + .trick-slot{ margin-left:clamp(20px,3.6vw,48px); }
.trick-slot .card{ width:clamp(62px,7.8vw,100px); height:auto; aspect-ratio:.70; }
.trick-slot-label{
  font-size:10.5px; letter-spacing:.07em; text-transform:uppercase;
  color:var(--text-muted); margin-bottom:4px;
}
.trick-slot.is-winner .card{ box-shadow:0 0 0 3px var(--action), 0 10px 22px rgba(0,0,0,.34); transform:translateY(-6px); }
.trick-empty{
  width:clamp(62px,7.8vw,100px); aspect-ratio:.70;
  border:2px dashed var(--line); border-radius:8px;
}
.trick-verdict{
  font-size:13.5px; font-weight:700; color:var(--action);
  text-align:center; margin-top:var(--space-2); min-height:18px;
}
/* PART 47 — illegal cards stay visible but muted and inert. */
.hand .card.is-illegal{ opacity:.48; filter:saturate(.65); cursor:not-allowed; }
/* PART 50 — spades state in words, never colour alone. */
.status-strip .chip.spades-broken{ color:var(--action); font-weight:600; }
.status-strip .chip.spades-intact{ color:var(--text-muted); }
.lead-suit{ font-size:12px; color:var(--text-secondary); }

/* ---- PHASE 4: SCORE (PART 59-64) ---- */
.score-sides{ text-align:left; }
.score-side{ margin-bottom:var(--space-4); }
.score-side h3{
  font-size:12px; letter-spacing:.06em; text-transform:uppercase;
  margin:0 0 5px; padding-bottom:4px; border-bottom:1px solid var(--line-soft);
}
.score-side.ours h3{ color:var(--our-team); }
.score-side.theirs h3{ color:var(--their-team); }
.summary-row.penalty .val{ color:var(--danger); }
.nil-banner{
  font-size:13px; font-weight:700; letter-spacing:.04em; text-transform:uppercase;
  text-align:center; border-radius:999px; padding:3px 12px; margin-bottom:var(--space-2);
}
.nil-banner.success{ color:var(--success); border:1px solid rgba(98,197,139,.5); }
.nil-banner.failed{ color:var(--danger); border:1px solid rgba(228,91,100,.45); }

/* The `hidden` attribute is only `display:none` from the UA stylesheet, so
   any author `display` rule beats it — which left the bid panel painted on
   top of the draft card. Phase panels each set their own display, so make
   `hidden` authoritative. */
[hidden]{ display:none !important; }


/* Short containers: the vh term above is tuned so a full-height desktop gets
   the 75-88px cards the brief asks for (PART 43). In a genuinely short
   container that same term still demanded ~89px-tall cards, which overflowed
   the hand zone and clipped all 13. Shrink the cards only once height is the
   binding constraint, leaving the desktop size untouched. */
@media (max-height: 590px){
  :root{ --card-w:clamp(44px, min(6.9vw, 9.9vh), 85px); }
}
@media (max-height: 470px){
  :root{ --card-w:clamp(40px, min(6.5vw, 9.2vh), 85px); }
}

/* ============================================================
   CUTTHROAT SPADES — game-specific components.
   §10/§22: every player is an individual. No grouping, no team
   language, one compact status per seat.
   ============================================================ */

/* §11/§12 — mode selection: two clear choices, existing button language. */
.mode-choices{ display:flex; flex-direction:column; }
.mode-choices > * + *{ margin-top:var(--space-3); }
.mode-choice-btn{
  display:block; width:100%; text-align:left; cursor:pointer;
  background:rgba(255,255,255,.06); border:1px solid var(--line);
  border-radius:var(--radius); padding:var(--space-3) var(--space-4);
  color:var(--text-primary); font-family:var(--font-ui);
}
.mode-choice-btn:hover{ border-color:var(--action); background:rgba(255,255,255,.10); }
.mode-choice-btn:focus-visible{ outline:3px solid var(--action); outline-offset:2px; }
.mode-choice-title{ display:block; font-size:16px; font-weight:700; }
.mode-choice-sub{ display:block; font-size:12.5px; color:var(--text-secondary); margin-top:2px; }

/* §19/§20/§44 — four seats around a compact table, not a giant oval. */
.zone-table[data-seats="4"]{
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1.5fr) minmax(0,1fr);
  grid-template-areas:". north ." "west trick east";
  align-items:center; justify-items:center; min-height:0;
}
.seat-north{ grid-area:north; }
.seat-west{ grid-area:west; }
.seat-east{ grid-area:east; }
.trick-zone{ grid-area:trick; min-width:0; }
.zone-table[data-seats="2"]{ display:flex; align-items:center; justify-content:center; }

/* §22/§74 — concise per-player status, typography not containers. */
.player{ display:flex; flex-direction:column; align-items:center; min-width:0; max-width:100%; }
.player-name{
  font-size:13.5px; font-weight:600; color:var(--text-secondary);
  letter-spacing:.02em; white-space:nowrap;
}
.player.is-acting .player-name{ color:var(--text-primary); font-weight:700; }
.player-dot{ width:7px; height:7px; border-radius:50%; border:1px solid var(--line); margin-right:5px; }
.player.is-acting .player-dot{ background:var(--active); border-color:var(--active); }
.player-head{ display:flex; align-items:center; }
.player-state{ font-size:12px; color:var(--text-muted); margin-top:2px; white-space:nowrap; }
.player-state .nil{ color:var(--success); font-weight:700; }
.player-state .nil-broken{ color:var(--danger); font-weight:700; }
.player-state .bags-warn{ color:var(--action); font-weight:600; }
.player-backs{ display:flex; justify-content:center; margin-top:3px; }
.player-backs .card-back{ width:clamp(26px,3vw,42px); height:calc(clamp(26px,3vw,42px) / .70); }
.player-backs .card-back + .card-back{ margin-left:-22px; }
/* §23 — active player never by colour alone: marker + name + outline. */
.player.is-acting{
  border:1px solid var(--active); border-radius:10px;
  background:rgba(98,196,255,.07); padding:3px 9px; margin:-4px -10px;
}

/* §47/§48/§49 — the trick, with cards placed to match seat positions. */
.trick-grid{
  display:grid; justify-items:center; align-items:center;
  grid-template-columns:repeat(3,minmax(0,1fr));
  grid-template-areas:". tnorth ." "twest . teast" ". tsouth .";
  min-width:0;
}
.trick-grid[data-seats="2"]{ grid-template-areas:"tnorth" "tsouth"; grid-template-columns:minmax(0,1fr); }
.trick-cell-north{ grid-area:tnorth; } .trick-cell-west{ grid-area:twest; }
.trick-cell-east{ grid-area:teast; }  .trick-cell-south{ grid-area:tsouth; }
.trick-cell{ display:flex; flex-direction:column; align-items:center; padding:3px; }
.trick-cell .card{ width:clamp(46px,6.4vw,92px); height:auto; aspect-ratio:.70; }
.trick-cell-label{
  font-size:9.5px; letter-spacing:.07em; text-transform:uppercase;
  color:var(--text-muted); margin-bottom:3px;
}
/* §60 — winning card highlighted, not merely announced. */
.trick-cell.is-winner .card{
  box-shadow:0 0 0 3px var(--action), 0 10px 22px rgba(0,0,0,.34);
  transform:translateY(-6px);
}
.trick-empty{ width:clamp(46px,6.4vw,92px); aspect-ratio:.70; border:2px dashed var(--line); border-radius:8px; }
.trick-verdict{
  font-size:13.5px; font-weight:700; color:var(--action);
  text-align:center; margin-top:var(--space-2); min-height:18px;
}

/* §62 — compact individual leaderboard, never a team score. */
.leaderboard{ display:flex; align-items:baseline; justify-content:center; flex-wrap:wrap; }
.lb-entry{ display:flex; align-items:baseline; white-space:nowrap; margin-left:12px; }
.lb-entry:first-child{ margin-left:0; }
.lb-name{
  font-size:11px; font-weight:600; letter-spacing:.04em; text-transform:uppercase;
  color:var(--text-muted); margin-right:4px;
}
.lb-entry.is-you .lb-name{ color:var(--our-team); }
.lb-entry.is-leading .lb-score{ color:var(--action); }
.lb-score{ font-family:var(--font-mono); font-size:15px; font-weight:700; }

/* §65/§66 — result as a clean list, human row emphasised. */
.result-rows{ text-align:left; }
.result-player{ margin-bottom:var(--space-3); }
.result-player h3{
  font-size:12px; letter-spacing:.06em; text-transform:uppercase;
  margin:0 0 4px; padding-bottom:3px; border-bottom:1px solid var(--line-soft);
  color:var(--text-secondary);
}
.result-player.is-you h3{ color:var(--our-team); }
.result-player.is-you{ background:rgba(98,196,255,.05); border-radius:8px; padding:6px 8px; margin-left:-8px; margin-right:-8px; }
.nil-banner{
  font-size:12px; font-weight:700; letter-spacing:.04em; text-transform:uppercase;
  border-radius:999px; padding:2px 10px; display:inline-block; margin-bottom:4px;
}
.nil-banner.success{ color:var(--success); border:1px solid rgba(98,197,139,.5); }
.nil-banner.failed{ color:var(--danger); border:1px solid rgba(228,91,100,.45); }
.summary-row.penalty .val{ color:var(--danger); }

/* §51 — illegal cards visible but inert. */
.hand .card.is-illegal{ opacity:.48; filter:saturate(.65); cursor:not-allowed; }

/* Draft components (two-player mode) reuse the 2-Player Spades language. */
.draft-card-slot{ display:flex; align-items:center; justify-content:center; min-height:0; }
.draft-card-slot .card{
  width:clamp(74px,9.5vw,118px); height:auto; aspect-ratio:.70;
  box-shadow:0 10px 26px rgba(0,0,0,.42), inset 0 0 0 1px rgba(0,0,0,.08);
}
.draft-head{ text-align:center; margin-bottom:var(--space-2); }
.draft-title{ font-size:clamp(15px,1.7vw,20px); font-weight:700; }
.draft-progress{ font-size:13.5px; color:var(--text-secondary); margin-top:2px; }
.draft-consequence{
  font-size:13px; color:var(--action); text-align:center; margin-top:var(--space-2); min-height:18px;
}
.auto-tag{
  display:inline-flex; align-items:center; font-size:11px; font-weight:800;
  letter-spacing:.08em; text-transform:uppercase; border-radius:999px; padding:2px 10px;
}
.auto-tag.keep{ color:var(--success); border:1px solid rgba(98,197,139,.5); background:rgba(98,197,139,.10); }
.auto-tag.discard{ color:var(--danger); border:1px solid rgba(228,91,100,.45); background:rgba(228,91,100,.08); }
.second-card-label{ font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:var(--text-muted); margin-bottom:4px; }

/* Bid controls (both modes) */
.bid-panel{ display:flex; flex-direction:column; align-items:center; }
.bid-stepper{ display:flex; align-items:center; margin-top:var(--space-2); }
.bid-stepper > * + *{ margin-left:var(--space-4); }
.bid-step-btn{
  width:52px; height:52px; border-radius:50%;
  background:rgba(255,255,255,.06); border:1px solid var(--line);
  color:var(--text-primary); font-size:24px; font-weight:700; cursor:pointer;
}
.bid-step-btn:disabled{ opacity:.35; cursor:not-allowed; }
.bid-value{ font-family:var(--font-mono); font-size:clamp(26px,3.4vw,40px); font-weight:700; min-width:3ch; text-align:center; }
.bid-value.is-nil{ font-family:var(--font-ui); font-size:clamp(20px,2.6vw,30px); color:var(--success); }
.bid-note{ font-size:12.5px; color:var(--text-secondary); margin-top:4px; min-height:16px; }
.bid-actions{ display:flex; align-items:center; margin-top:var(--space-3); }
.bid-actions > * + *{ margin-left:var(--space-2); }
.blind-nil-flag{
  font-size:11px; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  color:var(--action); border:1px solid rgba(240,184,75,.45); background:rgba(240,184,75,.08);
  border-radius:999px; padding:2px 10px; margin-bottom:var(--space-2);
}
/* §40 — bids so far, compact, not a scoreboard. */
.bids-so-far{ font-size:12.5px; color:var(--text-secondary); text-align:center; margin-top:var(--space-2); }
.bids-so-far .bid-chip + .bid-chip::before{ content:'\00b7'; color:var(--text-muted); margin-left:8px; margin-right:8px; }

/* The `hidden` attribute must beat author display rules on phase panels. */
[hidden]{ display:none !important; }

/* Compact adjustments */
.game[data-layout="compact"] .zone-table[data-seats="4"]{
  grid-template-columns:minmax(0,1fr) minmax(0,1.3fr) minmax(0,1fr);
}
.game[data-layout="compact"] .player-backs{ display:none; }
.game[data-layout="compact"] .player-name{ font-size:11.5px; }
.game[data-layout="compact"] .player-state{ font-size:11px; }
.game[data-layout="compact"] .trick-cell .card{ width:clamp(44px,15vw,70px); }
.game[data-layout="compact"] .trick-empty{ width:clamp(44px,15vw,70px); }
.game[data-layout="compact"] .lb-score{ font-size:13px; }
.game[data-layout="compact"] .lb-entry{ margin-left:9px; }

/* At short desktop heights the bid panel overflowed .zone-table by a few px,
   clipping the Nil and Confirm buttons. Trim its vertical footprint rather
   than letting the zone stop clipping, which would risk zone overlap. */
@media (max-height: 560px){
  .bid-step-btn{ width:44px; height:44px; font-size:20px; }
  .bid-value{ font-size:clamp(22px,3vw,32px); }
  .bid-stepper{ margin-top:2px; }
  .bid-actions{ margin-top:var(--space-2); }
  .bid-actions .btn{ min-height:40px; padding:8px 14px; }
  .bid-note{ min-height:0; margin-top:2px; }
  .bids-so-far{ margin-top:4px; }
}

/* §47 — the four-card trick must live inside roughly 200-240px of height.
   Three stacked rows of full-height cards measured 389px against a 221px
   zone, which clipped the West/East cards, their card backs and their whole
   status line. The diamond now overlaps vertically: each row rises into the
   one above, so four cards occupy a little under two card-heights while
   every rank and suit index stays visible. */
.trick-grid[data-seats="4"]{ align-items:start; }
.trick-grid[data-seats="4"] .trick-cell-west,
.trick-grid[data-seats="4"] .trick-cell-east{ margin-top:calc(var(--trick-overlap) * -1); }
.trick-grid[data-seats="4"] .trick-cell-south{ margin-top:calc(var(--trick-overlap) * -1); }
:root{ --trick-overlap:clamp(40px, 8vh, 74px); }
.trick-grid[data-seats="4"] .trick-cell .card{ width:clamp(44px,5.6vw,80px); }
.trick-grid[data-seats="4"] .trick-empty{ width:clamp(44px,5.6vw,80px); }
/* §49 — in four-player mode the card's POSITION already identifies its
   player (north/west/east/south), so the per-card label is redundant and
   its height is better spent on the cards themselves. Two-player mode keeps
   its labels, where top/bottom alone is less obvious. */
.trick-grid[data-seats="4"] .trick-cell-label{ display:none; }
.trick-grid[data-seats="4"] .trick-cell{ padding:2px; }

/* §78/§79 — mobile prioritises information over literal table geometry.
   Stacking the desktop three-column grid put West above the trick and East
   below it, with a dead band between, and broke the seat association the
   columns were there to provide. Instead: both side opponents share one
   compact row directly under North, then the trick sits beneath them. */
.game[data-layout="compact"] .zone-table[data-seats="4"]{
  /* display must be re-asserted: the inherited compact rule sets this zone
     to flex-column, which silently discarded the grid areas below. */
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  grid-template-areas:"west east" "trick trick";
  align-items:start; justify-items:center;
}
.game[data-layout="compact"] .zone-table[data-seats="4"] .trick-zone{
  width:100%; margin-top:var(--space-2);
}
.game[data-layout="compact"] .player{ max-width:100%; }
/* §80 — the trick stays a diamond on mobile so each card still reads as
   its player's, just tighter. */
.game[data-layout="compact"] .trick-grid[data-seats="4"]{
  --trick-overlap:clamp(30px, 6vh, 56px);
}
.game[data-layout="compact"] .trick-grid[data-seats="4"] .trick-cell .card,
.game[data-layout="compact"] .trick-grid[data-seats="4"] .trick-empty{
  width:clamp(44px,15vw,68px);
}

/* Found by the layout-contract check, not by eye.
   (a) At short desktop heights the trick diamond and the draft card still
       overflowed .zone-table by 3-14px, clipping their bottom edges.
   (b) On small phones the two-row hand rose out of .zone-hand by up to
       62px, clipping the top row. */
@media (max-height: 560px){
  .trick-grid[data-seats="4"] .trick-cell .card,
  .trick-grid[data-seats="4"] .trick-empty{ width:clamp(38px,4.6vw,62px); }
  :root{ --trick-overlap:clamp(34px, 9vh, 60px); }
  .draft-card-slot .card{ width:clamp(58px,7vw,88px); }
  .trick-verdict{ min-height:0; font-size:12px; }
  .trick-cell{ padding:1px; }
}
@media (max-width: 400px){
  .game[data-layout="compact"]{ --card-w:clamp(42px, 13.2vw, 58px); }
  .game[data-layout="compact"] .hand{ padding-top:10px; }
  .game[data-layout="compact"] .hand-row + .hand-row{ margin-top:4px; }
}

/* On small phones the two-row hand still needed more height than .zone-hand
   had. The hand is the primary interactive object, so the trick yields to
   it rather than the cards shrinking further: a tighter diamond and smaller
   trick cards free the vertical space the hand needs. */
@media (max-width: 400px){
  .game[data-layout="compact"] .trick-grid[data-seats="4"]{
    --trick-overlap:clamp(38px, 8vh, 62px);
  }
  .game[data-layout="compact"] .trick-grid[data-seats="4"] .trick-cell .card,
  .game[data-layout="compact"] .trick-grid[data-seats="4"] .trick-empty{
    width:clamp(38px,12vw,54px);
  }
  .game[data-layout="compact"] .player-state{ font-size:10.5px; }
  .game[data-layout="compact"] .zone-table[data-seats="4"] .trick-zone{ margin-top:2px; }
  .game[data-layout="compact"] .trick-verdict{ min-height:0; }
}


/* Constrained heights: the hand zone's content (deadwood/status line, cards
   and the action bar) was taller than the zone itself, so the excess was
   clipped — off the top with flex-end, off the bottom with `safe flex-end`.
   Choosing an end to clip is the wrong fix; the content has to fit. Trim the
   non-card furniture at these heights and leave the cards alone. */
@media (max-height: 560px){
  .zone-hand{ padding-bottom:2px; }
  .hand{ padding-top:6px; padding-bottom:2px; }
  .hand-row + .hand-row{ margin-top:3px; }
  .actionbar{ padding-top:4px; }
  .bar-utility{ margin-bottom:4px; }
  .action-buttons .btn, .bar-utility .btn{ min-height:38px; padding-top:7px; padding-bottom:7px; }
  .selection-info{ font-size:12px; }
}

/* ============================================================
   PARTNERSHIP SPADES — the partnership layer.
   §2: the screen must say YOU + PARTNER vs WEST + EAST.
   §8/§64: restrained accents and compact language, never large
   coloured panels or a dashboard.
   ============================================================ */

/* §19/§56 — one partnership score, never four individual totals. */
.team-score{ display:flex; align-items:baseline; justify-content:center; white-space:nowrap; }
.team-score .ts-label{
  font-size:11px; font-weight:600; letter-spacing:.06em; text-transform:uppercase;
  color:var(--text-muted);
}
.team-score .ts-label.ours{ color:var(--our-team); }
.team-score .ts-label.theirs{ color:var(--their-team); }
.team-score .ts-val{ font-family:var(--font-mono); font-size:clamp(15px,1.6vw,18px); font-weight:700; margin-left:5px; margin-right:5px; }
.team-score .ts-dash{ color:var(--text-muted); margin-left:6px; margin-right:6px; }

/* §17/§48 — Partner is never just another seat. The label is explicit
   (§89: not colour alone) and carries the team accent. */
.player-name.is-partner{ color:var(--our-team); }
.player-name.is-opponent{ color:var(--their-team); }
.player.is-partner .player-dot{ border-color:var(--our-team); }
.player.is-opponent .player-dot{ border-color:var(--their-team); }
.partner-tag{
  font-size:9.5px; font-weight:800; letter-spacing:.08em; text-transform:uppercase;
  color:var(--our-team); border:1px solid rgba(98,196,255,.45);
  border-radius:999px; padding:1px 7px; margin-left:6px;
}

/* §43/§64 — team contract progress: the single most useful persistent line. */
.team-contract{
  display:flex; align-items:baseline; justify-content:center; flex-wrap:wrap;
  font-size:clamp(13px,1.4vw,16px);
}
.team-contract .tc-name{ font-weight:700; letter-spacing:.02em; }
.team-contract .tc-name.ours{ color:var(--our-team); }
.team-contract .tc-name.theirs{ color:var(--their-team); }
.team-contract .tc-body{ color:var(--text-secondary); margin-left:8px; }
.team-contract .sep{ color:var(--text-muted); margin-left:7px; margin-right:7px; }
/* §45/§46 — contract made: a small success state, not a green table. */
.tc-made{
  font-size:10.5px; font-weight:800; letter-spacing:.07em; text-transform:uppercase;
  color:var(--success); border:1px solid rgba(98,197,139,.45);
  border-radius:999px; padding:1px 8px; margin-left:8px;
}
/* §51/§52 — bags are a partnership state; emphasis only near the penalty. */
.bags-warn{ color:var(--action); font-weight:700; }

/* §47/§48/§49/§50 — Nil is individual and must stay visible. */
.nil-flag{
  font-size:10.5px; font-weight:800; letter-spacing:.06em; text-transform:uppercase;
  border-radius:999px; padding:1px 8px; margin-left:6px;
}
.nil-flag.intact{ color:var(--success); border:1px solid rgba(98,197,139,.5); }
.nil-flag.broken{ color:var(--danger); border:1px solid rgba(228,91,100,.45); }

/* §57-§62 — result rows grouped by partnership. */
.result-team{ margin-bottom:var(--space-4); }
.result-team h3{
  font-size:12px; letter-spacing:.06em; text-transform:uppercase;
  margin:0 0 5px; padding-bottom:4px; border-bottom:1px solid var(--line-soft);
}
.result-team.ours h3{ color:var(--our-team); }
.result-team.theirs h3{ color:var(--their-team); }
.result-team.ours{ background:rgba(98,196,255,.05); border-radius:8px; padding:6px 8px; margin-left:-8px; margin-right:-8px; }

/* ============================================================
   CHROME COMPONENTS
   Replacing the stylesheet ported the product layer and the board
   geometry but dropped the chrome in between: 17 `.k-*` classes the
   markup and controller use were left undefined. That is why the
   rules panel had no background, Undo rendered as a bare grey pill,
   and Hint appeared to do nothing — it applies .k-hint-glow, which
   did not exist. All are rebuilt here on the shared tokens.
   ============================================================ */

.k-brand{ display:flex; flex-direction:column; justify-content:center; }
.k-title{
  font-family:var(--font-display); font-size:clamp(16px,2vw,22px);
  font-weight:700; color:var(--text-primary); line-height:1.1;
}
.k-action-row{ display:flex; align-items:center; flex-wrap:wrap; }
.k-action-row > * + *{ margin-left:var(--space-2); }

/* Buttons follow the suite's control language (§2/§41). */
.k-btn{
  font-family:var(--font-ui); font-size:14px; font-weight:600;
  color:var(--text-primary); background:rgba(255,255,255,.07);
  border:1px solid var(--line); border-radius:var(--radius-sm);
  padding:9px 16px; min-height:40px; cursor:pointer;
}
.k-btn:hover{ border-color:var(--action); background:rgba(255,255,255,.12); }
.k-btn:focus-visible{ outline:3px solid var(--action); outline-offset:2px; }
.k-btn:disabled{ opacity:.38; cursor:not-allowed; }
/* §9 — gold still means primary action. */
.k-btn-accent{
  background:var(--action); color:var(--action-text);
  border-color:var(--action); font-weight:700;
}
.k-btn-accent:hover{ filter:brightness(1.06); background:var(--action); }

.k-icon-btn{
  display:inline-flex; align-items:center; justify-content:center;
  min-width:44px; min-height:40px; padding:6px 10px;
  color:var(--text-primary); background:rgba(255,255,255,.07);
  border:1px solid var(--line); border-radius:var(--radius-sm); cursor:pointer;
}
.k-icon-btn:hover{ border-color:var(--action); }
.k-icon-btn:focus-visible{ outline:3px solid var(--action); outline-offset:2px; }
.k-icon-btn:disabled{ opacity:.38; cursor:not-allowed; }
.k-icon-btn-labeled{ min-width:auto; padding-left:12px; padding-right:12px; font-size:13px; font-weight:600; }
.k-ico{ font-size:15px; line-height:1; }
.k-icon-btn-labeled .k-ico{ margin-right:6px; }

/* Panels use the suite's modal treatment (§2), not a bare text overlay. */
.k-panel{
  width:min(560px, calc(100% - 32px)); max-height:86vh; overflow-y:auto;
  background:var(--panel-bg, #16212C);
  border:1px solid var(--line); border-radius:var(--radius);
  box-shadow:0 22px 60px rgba(0,0,0,.55);
  padding:var(--space-5); color:var(--text-primary);
}
.k-panel h2{ font-family:var(--font-display); font-size:22px; margin:0 0 var(--space-3); }
.k-rules-list{ margin:0 0 var(--space-4); padding-left:var(--space-5); }
.k-rules-list li{ font-size:14px; line-height:1.55; color:var(--text-secondary); margin-bottom:8px; }
.k-win-panel{ text-align:center; }
.k-win-stats{
  display:flex; justify-content:center; flex-wrap:wrap;
  margin-top:var(--space-3); margin-bottom:var(--space-4);
  font-family:var(--font-mono); font-size:15px; color:var(--text-secondary);
}
.k-win-stats > * + *{ margin-left:var(--space-4); }

.k-toggle-row{
  display:flex; align-items:center; justify-content:space-between;
  padding-top:8px; padding-bottom:8px; font-size:14px; color:var(--text-secondary);
}
.k-switch{ position:relative; width:44px; height:24px; }
.k-slider{
  position:absolute; left:0; top:0; width:44px; height:24px;
  background:rgba(255,255,255,.14); border-radius:999px; cursor:pointer;
  transition:background .15s ease;
}
.k-slider::before{
  content:''; position:absolute; left:3px; top:3px; width:18px; height:18px;
  background:var(--text-primary); border-radius:50%; transition:transform .15s ease;
}
input:checked + .k-slider{ background:var(--action); }
input:checked + .k-slider::before{ transform:translateX(20px); }

/* §43 — Hint marks the relevant card/column. Restrained, and shape as well
   as colour (§71) so it does not depend on hue alone. */
@keyframes kHintGlow{
  0%,100%{ box-shadow:0 0 0 0 rgba(240,184,75,0); }
  35%{ box-shadow:0 0 0 3px var(--action), 0 0 14px rgba(240,184,75,.55); }
}
.k-hint-glow{
  animation:kHintGlow 1.25s ease-in-out 2;
  border-radius:var(--radius-sm); z-index:60;
}
.k-cascade-card{ position:absolute; }

@media (prefers-reduced-motion: reduce){
  .k-hint-glow{ animation:none; box-shadow:0 0 0 3px var(--action); }
}

/* Without touch-action the browser treats a drag on a card as a scroll
   gesture, claims the pointer and fires pointercancel — which is why
   dragging misbehaved on a phone but not on desktop. Cards own their
   gesture; the page itself does not scroll (the board is viewport-fitted). */
.k-card, .k-slot, .k-column{ touch-action:none; }

/* §45/§10 — the header must stay quiet: the board is the hero. It measured
   64px against the brief's 40-50, taking height the tableau should have.
   §48: spare vertical room goes to card exposure, not to chrome. */
header{ flex:0 0 auto; }
.header-inner{
  display:flex; align-items:center; justify-content:space-between;
  min-height:0; padding-top:4px; padding-bottom:4px;
  padding-left:clamp(6px,1.4vw,20px); padding-right:clamp(6px,1.4vw,20px);
}
.k-title{ font-size:clamp(15px,1.7vw,19px); }
.k-stat label{ font-size:9px; letter-spacing:.07em; }
.k-stat .val{ font-size:14px; }
.k-hud > * + *{ margin-left:var(--space-3); }
.k-action-row{ padding-top:2px; padding-bottom:2px; }
.k-action-row .k-btn{ min-height:34px; padding-top:6px; padding-bottom:6px; font-size:13px; }
.k-icon-btn{ min-height:34px; }

/* The HUD is a non-wrapping flex row, so on a 320px screen it measured 347px
   and forced horizontal page scroll — the one thing §62 forbids outright.
   Allow it to wrap, let the stats shrink, and keep the header quiet. */
.k-hud{ flex-wrap:wrap; justify-content:flex-end; min-width:0; }
.header-inner{ flex-wrap:wrap; }
.k-brand{ min-width:0; flex:1 1 auto; }
@media (max-width: 480px){
  .k-hud > * + *{ margin-left:var(--space-2); }
  .k-stat label{ font-size:8.5px; }
  .k-stat .val{ font-size:13px; }
  .k-icon-btn{ min-width:40px; padding-left:8px; padding-right:8px; }
  .k-icon-btn-labeled{ padding-left:9px; padding-right:9px; font-size:12px; }
  .k-icon-btn-labeled .k-ico{ margin-right:4px; }
  .k-action-row .k-btn{ font-size:12px; padding-left:11px; padding-right:11px; }
  .k-title{ font-size:15px; }
}

/* Trimming the header to §45's 40-50px pushed icon controls down to 34px,
   below §59's 44x44 touch target — trading a real usability requirement for
   chrome height. §45 allows up to 50px, which fits a proper target, so the
   header takes the upper end of its range rather than the controls shrinking. */
.k-icon-btn{ min-height:44px; min-width:44px; }
.k-action-row .k-btn{ min-height:44px; }
/* The icon buttons now live inside .k-action-row, whose flex rules reset
   their min-width to 0; restate it so the touch target survives. */
.k-action-row .k-icon-btn{ min-width:44px; min-height:44px; }
@media (max-width: 480px){
  /* Touch targets take priority on the devices that need them most. */
  .k-icon-btn{ min-height:44px; min-width:44px; }
  .k-action-row .k-btn{ min-height:44px; }
}

/* ============================================================
   MOBILE CHROME — one control row.
   §10/§52: the board is the hero and mobile must prioritise the
   tableau. The controls were wrapping across three ragged rows
   (Settings orphaned on its own), taking 161px — 28% of a 568px
   screen — before a single card was drawn.
   All five controls now sit on ONE row, each with its icon above
   a text label (utility buttons keep their label), sized to the
   44px touch target rather than to their text.
   ============================================================ */
@media (max-width: 560px){
  .header-inner{
    flex-direction:column; align-items:stretch;
    padding-top:3px; padding-bottom:3px;
  }
  .k-brand{ flex:0 0 auto; }
  .k-title{ font-size:15px; line-height:1.15; }

  /* Score / time / moves: one compact row, left aligned under the title. */
  .k-hud{
    flex-wrap:nowrap; justify-content:flex-start; align-items:baseline;
    margin-top:1px;
  }
  .k-hud > * + *{ margin-left:var(--space-4); }
  .k-stat{ text-align:left; display:flex; align-items:baseline; }
  .k-stat label{ font-size:9px; margin-right:5px; }
  .k-stat .val{ font-size:13px; }

  /* Every control on one row: icon over label, evenly divided. */
  .k-action-row{
    display:flex; flex-wrap:nowrap; align-items:stretch;
    width:100%; margin-top:3px;
  }
  .k-action-row > * + *{ margin-left:5px; }
  .k-action-row .k-btn,
  .k-action-row .k-icon-btn{
    flex:1 1 0; min-width:0; min-height:44px;
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    padding:4px 2px; font-size:10.5px; font-weight:600; line-height:1.1;
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  }
  .k-action-row .k-ico{ font-size:14px; margin-right:0; margin-bottom:1px; }
  .k-icon-btn-labeled{ padding-left:2px; padding-right:2px; }
}

/* §45 — the header has a floor as well as a ceiling. Moving the icon
   controls down into the action row left it at 37px, under the brief's
   40-50 band, so the title and score no longer read as a header at all. */
@media (min-width: 561px){
  header{ min-height:44px; display:flex; align-items:center; }
  .header-inner{ width:100%; }
}


/* A disabled control should look deliberately inactive, not broken. At .38
   opacity, beside four full-strength buttons, Undo read as a fault rather
   than as "nothing to undo yet". Keep it clearly muted but legible, and
   lose the border emphasis instead of the text. */
.k-btn:disabled, .k-icon-btn:disabled{
  opacity:.62; color:var(--text-muted);
  border-color:rgba(255,255,255,.10);
  background:rgba(255,255,255,.03);
}

/* Two controls are text-only and three carry icons, so their labels sat on
   different baselines across the row. Give every control the same internal
   structure so the row reads as one set. */
@media (max-width: 560px){
  .k-action-row .k-btn::before{
    content:''; display:block; height:14px; margin-bottom:1px;
  }
  .k-action-row .k-btn:has(.k-ico)::before{ display:none; }
}




/* ============================================================
   LAYOUT CONTRACT FIX — 2026-09-18
   .zone-table declared grid-template-columns but no rows, so its implicit
   row sized to CONTENT while the zone box stayed height-constrained by the
   outer flex column. overflow:hidden then cut the difference silently
   (measured 39px at 1200x675 on Whiz, hiding the bid-chip row).
   Constraining the track stops the silent cut. Measured, not guessed.
   ============================================================ */
.zone-table{ min-height:0; }
.zone-table > *{ min-height:0; }
.zone-table[data-seats="4"]{ grid-template-rows:auto minmax(0,1fr); }


/* ============================================================
   TOUCH + DEAD-SPACE FIX — 2026-09-18
   Measured, not guessed:
   (1) Compact icon/utility buttons were min-height:38px, giving a 38px
       effective target against the 44px requirement. Hearts already uses
       44px and passes with zero touch failures — this brings the rest of
       the kit to the same value rather than inventing a new one.
   (2) Panel toggles rendered 370x22. Their hit area is expanded with a
       pseudo-element rather than their height, so the touch requirement is
       met WITHOUT consuming vertical space the composition needs.
   (3) No zone had flex-grow, so spare height sat unused at the bottom of
       .game while the table and hand zones measured under their brief
       bands (Canasta: 133px unused, table 154 vs 220-250, hand 178 vs
       210-250). Letting those two zones grow spends the dead space on the
       zones the brief says should have it.
   ============================================================ */

.game[data-layout="compact"] .icon-btn{ min-width:44px; min-height:44px; }
.game[data-layout="compact"] .utility-buttons .btn{ min-height:44px; }

/* OPEN ITEM — panel toggles measure 370x22 against the 44px requirement
   (Canasta §112). Two attempts were reverted because both manufactured the
   fault they measured: an absolutely positioned ::after overflowed the 22px
   parent, and padding offset by negative margin pushed the box above its
   panel — each added ~11px of "clipped content" on every mobile width.
   Giving these toggles real height needs the 173px currently sitting unused
   inside .game on mobile (align-content:start again), which is a composition
   change across the shared kit. Left measured and flagged, not guessed at. */

.zone-table{ flex:1 1 auto; }
.zone-hand{ flex:1 1 auto; }


/* ============================================================
   TYPOGRAPHY FLOOR — 2026-09-18
   The briefs put SECONDARY information at 12-14px (Canasta §75/§76,
   Spades §68, Oklahoma §62/§63). Measured: 19 elements rendered below
   12px, worst .icon-label at 9px and .meld-note at 10px.

   SCOPED to the non-compact (desktop) layout. Applying it to the
   compact layout too is equally required by the briefs, but measured
   +70px of content that the table zone then CLIPPED on every mobile
   width — and the same briefs forbid clipping. Gaps were trimmed first
   per the §18/§98 reduction order and did not recover enough. The
   mobile half is therefore a composition decision, not a CSS tweak:
   flagged rather than chosen silently.
   ============================================================ */
.game:not([data-layout="compact"]) .icon-label{ font-size:12px; }
.game:not([data-layout="compact"]) :is(.seat-action,.panel-sub,.pile-name,
  .hand-pill,.red3-chip,.meldlist-empty,.meld-note,.turnstep){ font-size:12px; }
