* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; }
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: linear-gradient(180deg, #3f2d1a 0%, #78350f 55%, #3f2d1a 100%);
  color: #fffbeb; user-select: none; -webkit-user-select: none;
  touch-action: none; overflow: hidden;
}
#app { height: 100%; display: flex; flex-direction: column; max-width: min(100%, 860px); margin: 0 auto; }

#hdr { display: flex; align-items: center; gap: 8px; padding: 8px 12px; padding-top: max(8px, env(safe-area-inset-top)); }
#hdr .pill { background: rgba(0,0,0,0.22); border: 2px solid rgba(255,255,255,0.16); border-radius: 999px; padding: 6px 11px; font-weight: 800; font-size: 15px; min-width: 44px; min-height: 44px; display: flex; align-items: center; cursor: pointer; justify-content: center; color: inherit; font-family: inherit; }
#hdrHome { border: none; background: #fbbf24; color: #3f2d1a; font-size: 18px; }
#hdrStatus { flex: 1; min-width: 0; text-align: center; font-weight: 800; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #fde68a; }

.scr { flex: 1; display: none; flex-direction: column; overflow-y: auto; padding: 6px 14px 18px; }
/* The table gets the full width. 14px of padding either side costs the card
   size directly -- seven columns divide whatever is left, so 28px of margin is
   9% off every card on the narrowest phone. The menu and help screens keep
   their padding; only the board gives it up. */
#scr-play { padding: 6px 4px 6px; }
.scr.on { display: flex; }

#scr-menu { align-items: center; text-align: center; gap: 12px; justify-content: center; }
#scr-menu h1 { font-size: clamp(32px, 9vw, 44px); color: #ffffff; }
#scr-menu .sub { color: #fde68a; font-weight: 600; margin-bottom: 4px; }
#tierRow { display: flex; gap: 8px; width: 100%; }
.tierCard { flex: 1; background: rgba(0,0,0,0.2); border: 3px solid rgba(255,255,255,0.14); border-radius: 16px; padding: 10px 4px; font-weight: 800; font-size: 14px; line-height: 1.35; cursor: pointer; color: #fffbeb; font-family: inherit; }
.tierCard small { display: block; color: #fde68a; font-weight: 700; font-size: 11.5px; }
.tierCard .te { font-size: 26px; display: block; }
.tierCard.sel { border-color: #fbbf24; background: rgba(251,191,36,0.14); }
/* Settings, behind a disclosure. Same shape as word-search and cross-math so
   a player who has met one has met them all. */
#custom { width: 100%; margin: 2px 0; text-align: left; background: rgba(0,0,0,0.2); border: 3px solid rgba(255,255,255,0.13); border-radius: 16px; padding: 6px 12px 10px; }
#custom > summary { list-style: none; cursor: pointer; font-weight: 800; font-size: 16px; padding: 10px 2px; min-height: 44px; display: flex; align-items: center; gap: 8px; }
#custom > summary::-webkit-details-marker { display: none; }
#custom > summary::before { content: "▸"; transition: transform .12s; }
#custom[open] > summary::before { transform: rotate(90deg); }
.field { display: flex; align-items: center; gap: 10px; margin: 7px 0; }
.field .lbl { flex: 0 0 104px; font-size: 13.5px; font-weight: 800; color: #fde68a; }
.seg { flex: 1; display: flex; gap: 6px; }
.seg button { flex: 1; min-height: 44px; border-radius: 11px; border: 2px solid rgba(255,255,255,0.16); background: rgba(0,0,0,0.24); color: #fffbeb; font-family: inherit; font-weight: 800; font-size: 13.5px; cursor: pointer; padding: 4px 2px; }
.seg button.on { background: #fbbf24; border-color: #d97706; color: #3f2d1a; min-height: 44px;}
#optNote { font-size: 12.5px; line-height: 1.45; color: #fde68a; font-weight: 600; margin: 8px 2px 2px; }

button.big { width: 100%; border-radius: 16px; min-height: 56px; font-size: 19px; font-weight: 800; background: rgba(0,0,0,0.2); color: #fffbeb; border: 3px solid rgba(255,255,255,0.14); cursor: pointer; font-family: inherit; }
button.big:active { transform: translateY(2px); }
button.big.primary { background: #fbbf24; border-color: #d97706; color: #3f2d1a; min-height: 44px;}

/* play */
#playTop { display: flex; align-items: center; justify-content: space-between; font-weight: 800; padding: 2px 2px 6px; font-size: 15px; min-height: 22px; }

#stageWrap { flex: 1; position: relative; min-height: 0; }
canvas#stage { display: block; touch-action: none; }

/* The "dealing" line. It exists because the deal SEARCHES for a winnable hand
   before it shows one, and a blank table for even 200ms reads as a dead tap.
   It sits over the table rather than replacing the screen, so nothing jumps. */

/* Centred, and it stays centred. It was moved to the bottom for one release,
   to keep it off the cards it was describing — but that was treating the
   symptom. The panel CARRIES the cards now (see the result rows in app.js), so
   there is nothing behind it that has to be read, and centre is where the most
   important message in the game belongs. A panel in a corner puts its buttons
   next to the home indicator and makes the eye travel for the one thing it
   most wants.
   overflow-y stays: a four-player summary is a tall panel on a short phone,
   and a message that runs off the top is worse than one that scrolls. */
.ovl { position: absolute; inset: 0; display: none; align-items: flex-start; justify-content: center; background: rgba(30,18,6,0.55); border-radius: 12px; padding: 16px; overflow-y: auto; }
.ovl.on { display: flex; }
.ovlCard { text-align: center; width: 100%; max-width: 320px; display: flex; flex-direction: column; gap: 10px; margin: auto;}
/* THE OVERLAY REPORTS ON THE TABLE, SO IT MUST NOT HIDE THE TABLE.
   It was a 90%-opaque curtain over the felt — and what it says is "Robin took
   three tricks", "the pot goes to Kit", "kings and fours". Every one of those
   sentences is about cards the player can no longer see. That is not a tuning
   nit; it is the overlay defeating its own purpose, the same shape as a word
   game refusing a word for being too hard.
   The fix is not just less alpha, which would leave white text on a busy felt.
   The scrim goes down to a dim, and the WORDS move onto a panel of their own,
   so the message is more legible than before and the table is still there
   around it. Overlays whose board is irrelevant once they appear — a game-over
   in an arcade game — are left opaque on purpose. */
.ovlCard { background: rgba(41,26,10,0.96); border: 2px solid rgba(255,255,255,0.16);
            border-radius: 18px; padding: 16px 14px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.45); margin: auto;}

#ovlTitle { font-size: 26px; font-weight: 800; color: #ffffff; }
#ovlSub { color: #fde68a; font-weight: 700; min-height: 22px; line-height: 1.45; }

#scr-how .card { background: rgba(0,0,0,0.2); border: 3px solid rgba(255,255,255,0.13); border-radius: 16px; padding: 14px; margin-bottom: 10px; font-size: 15px; line-height: 1.55; font-weight: 600; }
#scr-how h2 { margin-bottom: 8px; color: #ffffff; }

/* The legend (docs/offline-games-pwa.md R-L1). One column of swatches at a
   fixed width so the eye can run the column instead of the text. The suit
   swatches are built from cards.js and the ink colours come from render.js —
   nothing here restates a colour the game paints, which is how a legend ends
   up showing a shade the product no longer uses. */
.legend { display: flex; flex-direction: column; gap: 7px; }
.lgRow { display: flex; align-items: center; gap: 10px; }
.lgArt { flex: none; width: 46px; display: flex; align-items: center; justify-content: center; }

.lgTxt { display: flex; flex-direction: column; min-width: 0; }
.lgTxt b { font-size: 14px; font-weight: 800; }
.lgTxt small { font-size: 12.5px; color: #fde68a; line-height: 1.3; font-weight: 600; }

#toast { position: fixed; left: 50%; bottom: 84px; transform: translateX(-50%); background: #ffffff; color: #3f2d1a; font-weight: 800; padding: 9px 16px; border-radius: 999px; opacity: 0; transition: opacity 0.25s; pointer-events: none; z-index: 40; max-width: calc(100vw - 32px); text-align: center; }
#toast.on { opacity: 1; }

/* docs/offline-pwa.md R-C15 — the PAGE must never scroll. The product is a
   fixed frame: header, a named scroll region, footer. If the document scrolls
   too, the header scrolls away and a drag that reaches the end of the inner
   region chains out and drags the whole table under the player's thumb. */
html, body { overflow: hidden; overscroll-behavior: none; }
.scr { overscroll-behavior: contain; }

/* --- euchre only ---------------------------------------------------------- */

/* THE SCOREBOARD ROW (R-T5, R-C19).
   Fixed height, and nothing in it changes width by more than a digit. The
   running message used to live in the middle of this row, so every "Robin is
   thinking…" pushed the chips either side of it around; it has its own row
   now, below. Two rows that each hold still beat one row that does not. */
#playTop { font-size: 13px; gap: 5px; min-height: 30px; align-items: stretch;
           justify-content: flex-start; padding: 2px 2px 3px; }
#playTop .gap { flex: 1 1 auto; min-width: 0; }

/* Every number on the felt is a chip, not bare text (R-T5). Bare text reads as
   a caption; a chip reads as a readout, and the trump suit is the most
   consulted fact in the game. */
.chip { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px;
        padding: 0 8px; border-radius: 999px; background: rgba(0,0,0,0.26);
        border: 2px solid rgba(255,255,255,0.14); line-height: 1; }
/* No text-transform. The caption slot carries a NAME half the time — "Robin
   called" — and lowercasing it made the game address its own players in a way
   it does nowhere else. Captions that should be lower case are written that
   way at the point they are set. */
.chip i { font-style: normal; font-size: 10px; font-weight: 800; opacity: 0.72;
          letter-spacing: 0.02em; }
.chip b { font-size: 15px; font-weight: 800; font-variant-numeric: tabular-nums; }
/* The suit glyph sits on the chip's own dark ground, so red has to be a red
   that survives it — #dc2626 on brown is unreadable at 15px. */
.chip.red b { color: #fca5a5; }
.chip#trumpChip.set { border-color: #fbbf24; background: rgba(251,191,36,0.16); }
.chip.us    { border-color: rgba(253,230,138,0.55); color: #fde68a; }
.chip.them  { border-color: rgba(231,229,228,0.45); color: #e7e5e4; }

/* The message. Its own row, reserved whether or not there is anything to say
   — an empty line that holds its height cannot move the table. */
#msgLine { flex: none; min-height: 20px; display: flex; align-items: center;
           justify-content: center; padding: 0 4px 3px; }
#msgOut { color: #fde68a; font-weight: 800; font-size: 13px; white-space: nowrap;
          overflow: hidden; text-overflow: ellipsis; max-width: 100%; }

/* The bid row wraps rather than scrolls: a fourteen-button row that runs off
   the edge hides the very choices it exists to offer. Every button keeps the
   44px floor on its short side. */
/* THE ROW IS ALWAYS HERE, EVEN WHEN IT HAS NOTHING TO SAY.
   `visibility` hides it, not `display`, so the box keeps its place in the
   layout. Hiding it with display:none made #stageWrap taller, which re-fitted
   the canvas, which re-laid the whole table — so every card resized and slid
   the moment the row appeared, and slid back when it went. Reserving the
   height removes the cause; toggling a class only ever chased the symptom.
   min-height is the 44px touch floor plus this row's own padding. */
#bidRow { flex: none; display: flex; flex-wrap: wrap; gap: 6px; justify-content: center;
          align-items: center; min-height: 60px; visibility: hidden;
          padding: 8px 4px max(4px, env(safe-area-inset-bottom)); }
#bidRow.on { visibility: visible; }
.bidBtn { min-width: 44px; min-height: 44px; border-radius: 12px; font-family: inherit;
          font-weight: 800; font-size: 16px; cursor: pointer; color: #fffbeb;
          background: rgba(0,0,0,0.24); border: 2px solid rgba(255,255,255,0.16); }
.bidBtn:active { transform: translateY(2px); }
/* The bid buttons are WORDS here, not numbers, so they need room. */
.bidBtn { padding: 0 12px; font-size: 14px; }
.bidBtn.take { border-color: #22c55e; color: #bbf7d0; }
.bidBtn.alone { border-color: #fbbf24; color: #fde68a; }
.bidBtn.red { color: #fca5a5; }

/* THE LEGEND CARD IS ONE WIDTH (R-L1).
   It was `min-width`, so "10♦" came out four pixels wider than "A♠" and a row
   of example cards — a straight, a flush, a hand of five — stopped lining up
   at whichever card happened to be a ten. A legend whose job is comparison
   cannot have a column that wanders. Fixed width, and the glyph centres in
   it. */
/* There were TWO `.lgCard` rules in this file, one copied from hearts and one
   written for euchre, twenty-five lines apart. The second won on every property
   it mentioned and the first still supplied `flex-direction: column` — so the
   cards were laid out by a rule nobody meant to be reading. One rule now. */
.lgCard { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 40px;
           border-radius: 4px; background: #fff; color: #0f172a; font-weight: 800; font-size: 13px;
           border: 1px solid #94a3b8; }
.lgCard.red { color: #dc2626; }
.lgCard.up { box-shadow: 0 0 0 3px #fbbf24; }
.lgAlone { font-size: 20px; }
.lgDim { width: 26px; height: 37px; border-radius: 4px; background: #fff; box-shadow: inset 0 0 0 40px rgba(60,40,20,0.6); display: inline-block; }

/* The tricks record. Wide, because the content is four cards a row and a
   narrow column would wrap every one of them. */
#btnTricks { flex: none; min-width: 34px; min-height: 34px; border-radius: 10px; border: 2px solid rgba(255,255,255,0.16); background: rgba(0,0,0,0.24); color: inherit; font-size: 15px; cursor: pointer; font-family: inherit; padding: 0 6px; }
#btnTricks:active { transform: translateY(1px); }
.ovlCard.wide { max-width: 460px; margin: auto;}
#ovlTitle2 { font-size: 22px; font-weight: 800; color: #ffffff; }
#trickList { max-height: 46vh; max-height: 46dvh; overflow-y: auto; overscroll-behavior: contain; display: flex; flex-direction: column; gap: 4px; text-align: left; padding: 2px; }
#trickList:empty::after { content: "Nothing yet — the first trick will show up here."; color: #fde68a; font-weight: 700; font-size: 14px; }
.trRow { display: flex; align-items: center; gap: 8px; font-weight: 800; font-size: 13px; padding: 5px 6px; border-radius: 8px; background: rgba(255,255,255,0.05); }
.trNo { flex: none; width: 20px; color: #fde68a; font-variant-numeric: tabular-nums; }
/* The cards take exactly the room the cards need — `flex: 1` gave the column a
   whole row's width and pushed the winner's name to the far edge, leaving a
   band of dead space in the middle of every line. A column sized to its
   content puts the name right beside the trick it belongs to. */
.trCards { flex: 0 1 auto; display: flex; gap: 4px; flex-wrap: wrap; }
.trC { display: inline-flex; align-items: center; justify-content: center; min-width: 32px; height: 26px; border-radius: 4px; background: #fff; color: #0f172a; font-size: 12px; font-weight: 800; border: 1px solid #94a3b8; }
.trC.red { color: #dc2626; }
.trC.win { box-shadow: 0 0 0 3px #fbbf24; }
.trWho { flex: 1 1 auto; font-size: 12px; color: #fbbf24; text-align: left; padding-left: 2px; white-space: nowrap; }

/* THE RESULT ROWS (docs/offline-games-pwa.md, "a result must carry its
   evidence"). One row per player or per team: who, the thing itself, what it
   was worth. Same class names as Poker's showdown, because it is the same idea
   and a second vocabulary for it would drift.
   `#ovlSub` becomes a column here rather than a paragraph, so the rows stack
   and their columns line up. */
#ovlSub { display: flex; flex-direction: column; gap: 5px; min-height: 0; }
.sdRow { display: flex; align-items: center; gap: 7px; padding: 5px 7px;
         border-radius: 10px; background: rgba(255,255,255,0.05);
         border: 2px solid transparent; text-align: left; }
.sdRow.you  { background: rgba(255,255,255,0.10); }
.sdRow.win  { border-color: #4ade80; background: rgba(74,222,128,0.14); }
.sdRow.out  { opacity: 0.55; }
.sdWho   { flex: 0 0 62px; font-size: 13px; font-weight: 800; }
.sdArt   { flex: 1 1 auto; display: flex; gap: 3px; flex-wrap: wrap; min-width: 0; }
.sdHand  { flex: 1 1 auto; font-size: 12px; font-weight: 700; line-height: 1.25;
           color: #fde68a; }
.sdSwing { flex: 0 0 auto; font-size: 13px; font-weight: 800;
           font-variant-numeric: tabular-nums; color: #fca5a5; }
.sdSwing.up { color: #4ade80; }
/* A rank or a card inside a result row. Fixed width for the same reason the
   legend's is: rows only form columns if a ten is as wide as an ace. */
.sdCard { display: inline-flex; align-items: center; justify-content: center;
          width: 27px; height: 34px; border-radius: 4px; background: #fff;
          color: #0f172a; font-weight: 800; font-size: 12px;
          border: 1px solid #94a3b8; }
.sdCard.red { color: #dc2626; }

/* The Settings panel's help line. It was in the markup with no rule behind it,
   so it rendered at body size in body colour where every other product shows
   it small and muted -- `just check-settings` found it once the guard started
   deriving the panel's OWN class vocabulary instead of demanding `.field` and
   `.seg` by name. Markup with no rules behind it is not a control. */
.blurb { margin: 6px 0 2px; font-size: 12.5px; line-height: 1.45; color: #fde68a; }
