/* ========================================================================== *
 *  Cribbage — newsprint.
 *
 *  Styled after a newspaper, because a deck of cards already is one: cream
 *  stock, black and red ink, a serif numeral in the corner, and no decoration
 *  that is not information. The cards set the rules and everything else
 *  follows them.
 *
 *  Six palettes. Day is the bare :root; the rest restate tokens only —
 *  nothing below this section names a colour, a radius or a face directly,
 *  which is what makes a new ground a short list rather than a restyle.
 *
 *  Two rules the whole file obeys:
 *    - Separation is a hairline, never a panel. There are no fills.
 *    - State is a rule, never a wash. A shaded block is invisible at night;
 *      a rule reads the same against either ground.
 *
 *  See design.md for the argument, and design/*.html for the mockups.
 * ========================================================================== */

:root {
  /* ---- day: the default ------------------------------------------------ */
  --paper:      #fbfbf9;
  --sheet:      #ffffff;   /* a sheet lifted off the page */
  --ink:        #121212;   /* near-black, never #000 */
  --ink-muted:  #5a5a5a;
  --ink-faint:  #6b6b6b;   /* measured 5.14:1 — #8b8b8b was 3.1 and failed */
  --rule:       #e2e2de;
  --rule-soft:  #efefec;   /* the faintest divider, between log entries */
  --wash:       rgba(18,18,18,.04);   /* hover only; never a resting state */
  --scrim:      rgba(18,18,18,.55);

  /* The two players are the two colours a deck comes in. */
  --p1: #b03030;   /* red deck  — seat 0 */
  --p2: #2f5f8f;   /* blue deck — seat 1 */
  --good: #1c7a46;
  --bad:  #a8332c;

  --p1-edge: color-mix(in srgb, var(--p1) 45%, transparent);
  --p2-edge: color-mix(in srgb, var(--p2) 45%, transparent);

  /* A playing card is a light object in BOTH palettes, so its ink cannot ride
     on --ink: that flips to near-white at night and would put white text on
     cream. The card is the one thing in the app with a ground of its own. */
  --card:       #f6f4ef;
  --card-ink:   #14181f;
  --card-red:   #b03030;
  --card-edge:  #dcdcd6;
  --card-hatch: #d9d9d1;   /* face down, hatched — see design.md */

  --board-dot:  #cbcbc3;

  /* ---- type ------------------------------------------------------------ */
  --font-display: "Iowan Old Style", Charter, Georgia, "Times New Roman", serif;
  --font-text: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue",
               Inter, system-ui, sans-serif;

  /* Six steps, replacing the fourteen ad-hoc sizes this file used to carry.
     Serif names things; sans operates things; figures are always tabular. */
  --t-masthead: clamp(2.2rem, 7vw, 3.1rem);
  --t-title:    1.5rem;
  --t-lede:     1.05rem;
  --t-body:     .95rem;
  --t-small:    .82rem;
  --t-kicker:   .7rem;

  --card-w: 64px;
  --card-h: 92px;

  --radius: 2px;        /* everything */
  --radius-card: 6px;   /* except a card, which has real corners */

  --ease: cubic-bezier(.22, .68, .32, 1);

  /* Room the hardware takes for itself. Installed to an iOS home screen the
     page is drawn under the status bar, so anything pinned to a frame edge
     needs the inset or the clock lands on top of it. Zero where it does not
     apply, which is why this is not a fixed margin. */
  --safe-top:    env(safe-area-inset-top, 0px);
  --safe-right:  env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left:   env(safe-area-inset-left, 0px);
}

/* ---- night: tokens only, nothing structural -------------------------- */
:root[data-theme="night"] {
  --paper:      #100f0e;   /* near-black, faintly warm — not blue-black */
  --sheet:      #171615;
  --ink:        #f2f0ea;
  --ink-muted:  #a8a49c;
  --ink-faint:  #85817a;   /* measured 4.94:1 */
  --rule:       #2e2c29;
  --rule-soft:  #1f1e1c;
  --wash:       rgba(242,240,234,.05);
  --scrim:      rgba(0,0,0,.66);

  --p1: #d9575c;   /* the same two decks, lifted off a dark ground */
  --p2: #7aa7d9;
  --good: #6ecb8d;
  --bad:  #f4594b;   /* hotter and brighter than --p1, so the two stay apart */

  /* Stock toned down from #f6f4ef. Cards are the largest light mass on the
     screen, so they set how bright the whole thing feels, and six full-cream
     cards on an OLED panel at night is a glare. */
  --card:       #e8e5dc;
  --card-ink:   #161513;
  --card-red:   #a8272c;
  --card-edge:  #c9c5bb;
  --card-hatch: #c4c0b5;

  --board-dot:  #3a3835;
}

/* ---- dark brown: a wood-panelled room --------------------------------- */
:root[data-theme="brown"] {
  --paper:      #1a130f;
  --sheet:      #221913;
  --ink:        #f2ece2;
  --ink-muted:  #b3a595;
  --ink-faint:  #8e8072;
  --rule:       #332822;
  --rule-soft:  #241b16;
  --wash:       rgba(242,236,226,.05);
  --scrim:      rgba(0,0,0,.68);

  --p1: #e2635c;
  --p2: #7fb0d8;
  --good: #6ecb8d;
  --bad:  #ff6b52;   /* hotter than --p1, which is warm on this ground */

  --card:       #ece7db;
  --card-ink:   #1a1613;
  --card-red:   #a8272c;
  --card-edge:  #c9c2b2;
  --card-hatch: #c5bcaa;

  --board-dot:  #3d3028;
}

/* ---- navy ------------------------------------------------------------- */
/* The weakest of the set and worth knowing why: this ground sits about 7° of
   hue from the blue deck, so the blue player is the table one step lighter.
   The lightness gap carries it, but the blue peg is harder to find here than
   on any other palette. */
:root[data-theme="navy"] {
  --paper:      #101827;
  --sheet:      #162032;
  --ink:        #e9edf5;
  --ink-muted:  #9ba7be;
  --ink-faint:  #7d899f;
  --rule:       #243049;
  --rule-soft:  #1a2338;
  --wash:       rgba(233,237,245,.05);
  --scrim:      rgba(0,0,0,.66);

  --p1: #e2626a;
  --p2: #7fb0e8;
  --good: #6ecb8d;
  --bad:  #ff6f61;

  --card:       #e9e8e2;
  --card-ink:   #14161b;
  --card-red:   #a8272c;
  --card-edge:  #c6c5bd;
  --card-hatch: #c2c1b8;

  --board-dot:  #2a3654;
}

/* ---- mauve ------------------------------------------------------------ */
/* Structurally the most comfortable of the coloured grounds: purple sits
   between red and blue on the wheel, so it is roughly equidistant from both
   decks rather than crowding either. */
:root[data-theme="mauve"] {
  --paper:      #1c1622;
  --sheet:      #241d2c;
  --ink:        #f0eaf2;
  --ink-muted:  #afa4b6;
  --ink-faint:  #8a8093;
  --rule:       #332a41;
  --rule-soft:  #251e2e;
  --wash:       rgba(240,234,242,.05);
  --scrim:      rgba(0,0,0,.66);

  --p1: #e2606f;
  --p2: #8fb0e0;
  --good: #6ecb8d;
  --bad:  #ff7060;

  --card:       #eae5e6;
  --card-ink:   #181419;
  --card-red:   #a8272c;
  --card-edge:  #c8c1c4;
  --card-hatch: #c4bdc0;

  --board-dot:  #392f48;
}

/* ---- salmon: the pink paper ------------------------------------------ */
/* Two things here are not like the other palettes, and both were found by
   looking rather than by reasoning:
   - The cards go LIGHTER than the ground rather than cream. On salmon a cream
     card reads as a dirty smudge; near-white reads as printed stock laid on
     the page, which is what a card is.
   - The decks deepen to claret and teal. A mid red sits 38° from salmon and
     goes muddy on it. The cost is honest: these are no longer quite the two
     colours a deck of cards comes in. */
/* Powdery mint. The one coloured ground where the decks do not have to move:
   at 136° from the red and 74° from the blue it collides with neither, so this
   keeps the two colours a deck of cards actually comes in — which salmon had
   to give up. Chroma 4.3%, well inside the 10% ceiling.

   Green, having deliberately deleted the green felt, is a fair thing to
   question. The felt was a second background with its own ink sitting on top
   of the page; this is the page. Nothing floats on it and nothing is carved
   out of it, which is the whole distinction. */
:root[data-theme="mint"] {
  --paper:      #e4efe7;
  --sheet:      #f2f8f4;
  --ink:        #101613;   /* near-black, cooled toward the ground */
  --ink-muted:  #4c574f;
  --ink-faint:  #5b665e;   /* measured 5.08:1 */
  --rule:       #c8d9cd;
  --rule-soft:  #d8e6dc;
  --wash:       rgba(16,22,19,.045);
  --scrim:      rgba(16,22,19,.55);

  /* Unmoved. See above. */
  --p1: #b03030;
  --p2: #2f5f8f;
  --good: #1c7a46;
  --bad:  #a8332c;

  /* Near-white rather than cream: on a green ground cream reads as a stain,
     the same lesson salmon taught from the other side of the wheel. */
  --card:       #fcfffd;
  --card-ink:   #14181f;
  --card-red:   #b03030;
  --card-edge:  #d4e2d9;
  --card-hatch: #cfdfd5;

  --board-dot:  #c2d5c9;
}

:root[data-theme="salmon"] {
  --paper:      #fff1e5;
  --sheet:      #fffaf3;
  --ink:        #16130f;
  --ink-muted:  #5c534a;
  --ink-faint:  #6b6157;
  --rule:       #e6d6c4;
  --rule-soft:  #f0e5d8;
  --wash:       rgba(22,19,15,.045);
  --scrim:      rgba(22,19,15,.55);

  --p1: #94152b;   /* claret */
  --p2: #0f5c7a;   /* deep teal */
  --good: #1c7a46;
  --bad:  #a8332c;

  --card:       #fffdf9;
  --card-ink:   #14181f;
  --card-red:   #a8272c;
  --card-edge:  #e6d9ca;
  --card-hatch: #e2d3c2;

  --board-dot:  #e3d0bc;
}

* { box-sizing: border-box; }

/* Swapping a palette repaints every colour at once. Backgrounds and borders
   are transitioned all over this file; `color` is not — so a live swap would
   animate half the page and snap the other half, and for those 180ms the
   in-between is a smear of one palette's text on another's panels. Held only
   for the single frame the swap takes. */
.theme-swapping, .theme-swapping *,
.theme-swapping *::before, .theme-swapping *::after {
  transition: none !important;
}

/* Class-level `display` beats the attribute's UA rule, so restate it loudly:
   several panels below are toggled purely with [hidden]. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-text);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overscroll-behavior: none;
}

#app { position: relative; z-index: 1; min-height: 100%; }

h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -.02em; }
button, input { font: inherit; color: inherit; }

/* ------------------------------------------------------ accidental zooming */
/* Two different zooms, both the browser's rather than ours.

   The first is double-tap to zoom, which fires on the second of two quick
   taps: laying two cards away, or tapping the same card twice, zoomed the
   board instead of playing it. `touch-action: manipulation` turns off the
   double-tap gesture on the things you tap, and nothing else — pinch zoom and
   the browser's own page zoom still work. That matters: `user-scalable=no` in
   the viewport tag would also stop this, by taking zooming away from people
   who need it, and iOS ignores it anyway. */
button, input, select, textarea, label, a, summary,
.card, .card-back, .rematch-row, .game-row, .roster-row, #log-list li {
  touch-action: manipulation;
}

/* The second is iOS Safari zooming the page whenever a field smaller than
   16px takes focus — and it does not zoom back out afterwards. `font: inherit`
   above hands every field the body's 15px, so the name box, the table code and
   the count field all did it. The root stays at 15px deliberately, since the
   whole type scale is built on it; only the fields are lifted. */
input, select, textarea { font-size: 16px; }
ul, ol { margin: 0; padding: 0; list-style: none; }
.num { font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------ buttons */
.btn {
  appearance: none;
  border: 1px solid var(--rule);
  background: var(--wash);
  color: var(--ink);
  border-radius: var(--radius);
  padding: 11px 20px;
  font-weight: 550;
  letter-spacing: -.01em;
  cursor: pointer;
  transition: background .18s var(--ease), border-color .18s var(--ease),
              transform .12s var(--ease), opacity .18s var(--ease);
}
.btn:hover:not(:disabled) { background: var(--wash); border-color: var(--rule); }
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: .38; cursor: default; }

.btn-primary {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--sheet);
}
.btn-primary:hover:not(:disabled) {
  background: var(--ink-muted);
  border-color: var(--ink-muted);
}
.btn-ghost { background: transparent; }

.icon-btn {
  display: grid; place-items: center;
  width: 36px; height: 36px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink-muted);
  cursor: pointer;
  transition: color .18s var(--ease), background .18s var(--ease);
  flex: none;
}
.icon-btn:hover { color: var(--ink); background: var(--wash); }

/* The bell reads as struck-through when this table is muted. */
#bell-btn .bell-slash { opacity: 0; transition: opacity .16s var(--ease); }
#bell-btn:not(.is-off) { color: var(--p1); }
#bell-btn.is-off .bell-slash { opacity: 1; }
#bell-btn.is-off .bell-body { opacity: .55; }

.linkish {
  background: none; border: 0; color: var(--ink-muted);
  cursor: pointer; padding: 6px; text-decoration: underline;
  text-underline-offset: 3px; text-decoration-color: var(--rule);
}
.linkish:hover { color: var(--ink); }

/* -------------------------------------------------------------- going back */
/* One back mark everywhere, drawn rather than typed. A literal "←" was in use
   on some of these and nothing at all on the rest, so the same act looked like
   two different controls; and a glyph is at the mercy of whichever font has
   it, where this inherits the size and the colour of the text it belongs to.

   Two borders on a rotated square, which is a chevron. It sits on the same
   diagonal as the one in the game's top bar, so the two read as the same
   gesture. */
.back { display: inline-flex; align-items: center; gap: .5em; }
.back::before {
  content: ""; flex: none;
  width: .4em; height: .4em;
  border-left: 1.6px solid currentColor;
  border-bottom: 1.6px solid currentColor;
  transform: rotate(45deg);
  /* The rotation leaves it sitting high against the text; this drops it back
     onto the same optical line. */
  margin-top: -.05em;
  transition: transform .16s var(--ease);
}
.back:hover::before { transform: rotate(45deg) translate(1.5px, -1.5px); }
/* A button already reads as a control, so the mark is all it needs. */
.btn.back { display: inline-flex; justify-content: center; }
.btn.back span { text-decoration: none; }

/* Underline the words, not the space the chevron sits in. */
.linkish.back { text-decoration: none; }
.linkish.back span { text-decoration: underline; text-underline-offset: 3px;
                     text-decoration-color: var(--rule); }

.error { color: var(--bad); font-size: var(--t-body); margin: 4px 0 0; }

/* ==========================================================================
 *  Lobby
 * ========================================================================== */

.lobby {
  max-width: 560px;
  margin: 0 auto;
  padding: calc(clamp(40px, 9vh, 88px) + var(--safe-top))
           calc(22px + var(--safe-right)) 60px calc(22px + var(--safe-left));
  display: flex;
  flex-direction: column;
  gap: 26px;
}

.lobby-head { text-align: center; }
.lobby-head h1 {
  font-family: var(--font-display); font-weight: 400;
  font-size: var(--t-masthead);
  letter-spacing: -.02em;
  margin-bottom: 8px;
}
/* the rule under a masthead is the heavy one; everything else is a hairline */
.lobby-head::after {
  content: ''; display: block; height: 2px; background: var(--ink);
  margin: 16px auto 0; max-width: 100%;
}
.tagline {
  color: var(--ink-muted); margin: 0 auto; max-width: 42ch; text-wrap: balance;
  font-family: var(--font-display); font-style: italic; font-size: var(--t-lede);
}

.mark {
  display: flex; justify-content: center; align-items: center; gap: 12px;
  margin: 0 auto 14px;
}
.mark .peg { width: 11px; height: 11px; border-radius: 50%; flex: none; }
.mark .peg-a { background: var(--p1); }
.mark .peg-b { background: var(--p2); }

/* Not a box. A section of the page, separated from the next by a hairline —
   the single most identifiable thing about the source material, and the
   change that does most of the work in here. */
.card-panel {
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
/* Separators go on the sections that need them rather than on every panel:
   the gate and the play panel are mutually exclusive and one of them is always
   [hidden], which a sibling selector cannot see past. */
/* Separators go on the sections that need them rather than on every panel:
   the gate and the play panel are mutually exclusive and one of them is always
   [hidden], which a sibling selector cannot see past. */
#settings-body > section + section {
  border-top: 1px solid var(--rule); padding-top: 22px;
}
/* A footnote about where tables are stored, not a section of its own. */
.guest-note {
  color: var(--ink-faint); font-size: var(--t-small);
  text-align: center; max-width: 46ch; margin: 0 auto;
}
.guest-note p { margin: 0; }

.field { display: flex; flex-direction: column; gap: 7px; }
.field > span {
  font-size: var(--t-kicker); text-transform: uppercase; letter-spacing: .12em;
  color: var(--ink-faint); font-weight: 700;
}
input[type="text"] {
  background: none;
  /* a ruled underline, matching the count field — the line is the field */
  border: 0; border-bottom: 1.5px solid var(--rule);
  padding: 8px 2px;
  width: 100%;
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease);
}
input[type="text"]::placeholder { color: var(--ink-faint); }
input[type="text"]:focus {
  outline: none;
  border-bottom-color: var(--ink); border-bottom-width: 2px;
}

.actions { display: flex; flex-direction: column; gap: 12px; }
.actions .btn-primary { width: 100%; padding: 13px; }
.join-row { display: flex; gap: 10px; }
.join-row input { text-transform: uppercase; letter-spacing: .14em; font-weight: 600; }
.join-row .btn { flex: none; }

/* ------------------------------------------------------------ muggins toggle */
.toggle {
  display: flex; align-items: flex-start; gap: 11px;
  cursor: pointer; user-select: none;
  padding: 2px;
}
.toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.toggle .box {
  flex: none; width: 18px; height: 18px; margin-top: 1px;
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--paper);
  position: relative;
  transition: background .16s var(--ease), border-color .16s var(--ease);
}
.toggle .box::after {
  content: ""; position: absolute; inset: 0;
  background: no-repeat center/10px 8px
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 10'%3E%3Cpath d='M1 5l3.2 3.2L11 1.4' fill='none' stroke='%232b1e05' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  opacity: 0; transition: opacity .16s var(--ease);
}
.toggle input:checked + .box { background: var(--p1); border-color: var(--p1); }
.toggle input:checked + .box::after { opacity: 1; }
.toggle input:focus-visible + .box { box-shadow: 0 0 0 3px color-mix(in srgb, var(--ink) 25%, transparent); }
.toggle .lbl { display: flex; flex-direction: column; gap: 1px; line-height: 1.35; }
.toggle .lbl b { font-weight: 600; font-size: var(--t-body); }
.toggle .lbl small { color: var(--ink-faint); font-size: var(--t-small); }

/* How muggins settles, shown only while it is on. A rule down the left rather
   than a box, so it reads as a qualification of the line above it and not as
   a second, unrelated setting. */
.muggins-how {
  margin: 8px 0 0 11px;
  padding: 8px 0 2px 13px;
  border: 0; border-left: 1px solid var(--rule);
  display: flex; flex-direction: column; gap: 9px;
}
.muggins-how legend {
  padding: 0; margin-bottom: 2px;
  font-size: var(--t-kicker); text-transform: uppercase;
  letter-spacing: .12em; color: var(--ink-faint);
}
/* A radio is a choice among alternatives; a checkbox is a thing switched on.
   Same control, different shape, which is the only thing that says so. */
.toggle .box.round { border-radius: 50%; }
.toggle .box.round::after {
  /* The base rule pins this to `inset: 0` and paints a tick into it. A dot is
     sized and centred instead, so the inset has to be given up first. */
  inset: auto;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--paper);
  top: 50%; left: 50%; margin: -4px 0 0 -4px;
}
.toggle:hover .lbl small { color: var(--ink-muted); }

/* -------------------------------------------------- play-the-computer picker */
.rule {
  display: flex; align-items: center; gap: 12px;
  color: var(--ink-faint); font-size: var(--t-small);
  text-transform: uppercase; letter-spacing: .11em; font-weight: 600;
}
.rule::before, .rule::after {
  content: ""; flex: 1; height: 1px; background: var(--rule);
}

.levels { display: flex; flex-direction: column; }
/* Three ruled rows rather than three boxes — a list of choices set the way a
   newspaper sets a list of choices. */
.level {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  background: none; border: 0; border-top: 1px solid var(--rule);
  padding: 12px 2px;
  cursor: pointer; text-align: left; width: 100%;
  transition: background .18s var(--ease);
}
.level:last-of-type { border-bottom: 1px solid var(--rule); }
.level b { font-weight: 600; letter-spacing: -.01em; }
.level small { color: var(--ink-faint); font-size: var(--t-small); line-height: 1.25; }
.level:hover:not(:disabled) { background: var(--wash); }
.level:hover:not(:disabled) small { color: var(--ink-muted); }

.level:disabled { opacity: .45; cursor: default; }

.my-games h2 {
  font-size: var(--t-kicker); text-transform: uppercase; letter-spacing: .12em;
  color: var(--ink-faint); font-weight: 700; margin-bottom: 10px;
}
.my-games ul { display: flex; flex-direction: column; }

/* The row is a button; the dismiss sits beside it rather than inside, because
   a button within a button is not a thing browsers or screen readers accept. */
.game-item { display: flex; align-items: stretch; gap: 8px; }
.game-item .game-row { flex: 1; min-width: 0; }
.game-dismiss {
  flex: none; width: 34px;
  border: 0; border-top: 1px solid var(--rule);
  background: none; color: var(--ink-faint);
  font-size: var(--t-title); line-height: 1; cursor: pointer;
  transition: color .18s var(--ease), background .18s var(--ease),
              border-color .18s var(--ease);
}
.game-dismiss:hover {
  color: var(--bad); background: var(--sheet);
  border-color: color-mix(in srgb, var(--bad) 40%, transparent);
}

.game-row {
  display: flex; align-items: center; gap: 14px;
  width: 100%; text-align: left;
  background: none;
  border: 0; border-top: 1px solid var(--rule);
  padding: 13px 2px;
  cursor: pointer;
  transition: background .18s var(--ease);
}
.game-item:last-child .game-row,
.game-item:last-child .game-dismiss { border-bottom: 1px solid var(--rule); }
.game-row:hover { background: var(--wash); }

/* ------------------------------------------------------- playing again */
/* Deliberately the same row as a table in your list: it is the same act —
   open this and play — rather than a new kind of control to learn. */
.rematch-list { list-style: none; margin: 4px 0 0; padding: 0; }
.rematch-row {
  display: flex; align-items: center; gap: 14px;
  width: 100%; text-align: left;
  background: none;
  border: 0; border-top: 1px solid var(--rule);
  padding: 12px 2px;
  cursor: pointer;
  color: inherit;
  transition: background .18s var(--ease);
}
.rematch-list li:last-child .rematch-row { border-bottom: 1px solid var(--rule); }
.rematch-row:hover { background: var(--wash); }
.rematch-row:disabled { opacity: .5; cursor: default; }
.rematch-row .who { flex: 1; min-width: 0; }
.rematch-row .who b {
  font-family: var(--font-display); font-weight: 400;
  display: block; font-size: var(--t-lede);
}
.rematch-row .who small {
  color: var(--ink-faint); font-size: var(--t-small); display: block;
}
.rematch-go {
  font-size: var(--t-kicker); font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-muted); white-space: nowrap;
}
#rematch-note { margin: 0 0 2px; }
.game-row .who { flex: 1; min-width: 0; }
.game-row .who b { font-family: var(--font-display); font-weight: 400; display: block; font-size: var(--t-lede); }
.game-row .who small {
  color: var(--ink-faint); font-size: var(--t-small);
  display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.game-row .tally {
  font-variant-numeric: tabular-nums; font-weight: 600;
  letter-spacing: -.02em; color: var(--ink-muted); flex: none;
}
.game-row .tally b { color: var(--ink); }

.badge {
  flex: none;
  font-size: var(--t-kicker); font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--p1);
  padding-left: 9px; border-left: 2px solid var(--p1);
}
.badge.waiting { color: var(--ink-faint); border-left-color: var(--rule); }
.badge.won { color: var(--good); border-left-color: var(--good); }
.badge.lost { background: transparent; color: var(--ink-faint); border-color: var(--rule); }

/* --------------------------------------------------------------- sign-in gate */
.gate { text-align: center; gap: 14px; }
.gate-title { font-size: var(--t-title); letter-spacing: -.025em; }
.gate-why {
  margin: 0 auto; max-width: 34ch;
  color: var(--ink-muted); font-size: var(--t-body); text-wrap: balance;
}
#gate-actions { display: flex; justify-content: center; }
#gate-actions .btn-google { width: 100%; justify-content: center; padding: 13px; }
.gate-guest { margin: 0; color: var(--ink-faint); font-size: var(--t-body); }
.gate-guest .linkish { font-size: var(--t-body); }

.guest-note {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  text-align: center;
}
.guest-why {
  margin: 0 auto; max-width: 42ch;
  color: var(--ink-faint); font-size: var(--t-small); line-height: 1.5; text-wrap: balance;
}
.guest-actions {
  margin: 0; display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap; justify-content: center;
  font-size: var(--t-small);
}
.guest-actions a { color: var(--ink-muted); text-underline-offset: 3px; }
.guest-actions a:hover { color: var(--ink); }
.guest-actions .sep { color: var(--ink-faint); }
.guest-actions .linkish { font-size: var(--t-small); padding: 0; }

.join-signin {
  margin-top: 16px; padding-top: 14px;
  border-top: 1px solid var(--rule);
  color: var(--ink-faint); font-size: var(--t-body);
}
.join-signin a { color: var(--ink-muted); text-underline-offset: 3px; }
.join-signin a:hover { color: var(--ink); }
.join-signin small { display: block; margin-top: 4px; font-size: var(--t-small); }

/* ----------------------------------------------------------------- settings */
.set-title {
  font-size: var(--t-small); text-transform: uppercase; letter-spacing: .12em;
  color: var(--ink-faint); font-weight: 700; margin-bottom: 10px;
}
/* The admin page runs several of these down one column, and a section heading
   with no air above it reads as a label on the block it is actually ending. */
#admin-body .set-title { margin-top: 34px; }
#admin-body > .set-title:first-child { margin-top: 0; }
.field-note {
  margin: 8px 0 0; font-size: var(--t-small); color: var(--ink-faint); line-height: 1.45;
}
.palettes { display: grid; }
.palette {
  display: flex; align-items: center; gap: 13px; text-align: left;
  padding: 11px 13px;
  border: 0; border-top: 1px solid var(--rule);
  background: none; color: inherit; cursor: pointer;
  transition: border-color .16s var(--ease), background .16s var(--ease);
}
.palette:hover { background: var(--wash); }
/* selected: a rule in the margin, never a wash — see design.md */
.palette:last-of-type { border-bottom: 1px solid var(--rule); }
.palette.is-on { border-left: 2px solid var(--p1); padding-left: 11px; margin-left: -13px; }
/* The swatch is four stops of the palette it names — page, felt, and the two
   seat colours. It is the only honest preview at this size. */
.pal-swatch {
  display: flex; flex: none; border-radius: var(--radius); overflow: hidden;
  border: 1px solid var(--rule);
  /* Hairlines between the stops, in the rule colour, because the first stop
     IS the page: on the palette you are currently using it matches the ground
     exactly and would otherwise read as a gap in the swatch rather than as
     the ground it is naming. True of every palette, seen first on mint. */
  gap: 1px; background: var(--rule);
}
.pal-swatch i { display: block; width: 15px; height: 30px; }
.pal-text { display: flex; flex-direction: column; gap: 1px; }
.pal-text b { font-weight: 600; letter-spacing: -.01em; }
.pal-text small { font-size: var(--t-small); color: var(--ink-faint); }

.lobby-foot .sep { color: var(--ink-faint); margin: 0 8px; }

/* -------------------------------------------------------------------- admin */
.lobby-wide { max-width: 900px; }

.trend-grid {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}
.trend {
  border-top: 1px solid var(--rule); padding: 12px 2px 10px;
}
.trend-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.trend-label {
  font-size: var(--t-small); text-transform: uppercase; letter-spacing: .09em;
  color: var(--ink-faint); font-weight: 700;
}
.trend-now { font-size: var(--t-title); font-weight: 650; letter-spacing: -.02em; }
.spark { width: 100%; height: 44px; display: block; margin: 6px 0 2px; overflow: visible; }
/* Non-scaling-stroke, or `preserveAspectRatio: none` stretches the line into a
   different thickness on every card. */
.spark-line {
  fill: none; stroke: var(--p1); stroke-width: 1.6;
  stroke-linejoin: round; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.spark-dot, .spark-head { fill: var(--p1); }
.trend-foot {
  display: flex; justify-content: space-between; gap: 8px;
  font-size: var(--t-kicker); color: var(--ink-faint);
}

.split-row {
  display: grid; align-items: center; gap: 10px;
  grid-template-columns: minmax(96px, auto) 1fr auto;
  padding: 6px 0;
}
.split-label { font-size: var(--t-body); display: flex; align-items: center; gap: 7px; }
.split-emoji { font-size: var(--t-lede); }
.split-track {
  height: 8px; border-radius: var(--radius); background: var(--wash); overflow: hidden;
  display: flex;   /* segments sit end to end rather than stacking */
}
.split-fill { display: block; height: 100%; background: var(--p1); border-radius: var(--radius); }
/* The solo half of a split bar: same hue, drained, so the total still reads as
   one bar while the two parts stay tellable apart. */
.split-fill.is-solo { background: var(--p1); opacity: .34; }
.split-fill.is-good { background: var(--good); }
.split-fill.is-live { background: var(--p2); }
.split-fill.is-bad  { background: var(--bad); }
.split-value { font-weight: 650; font-size: var(--t-body); min-width: 3ch; text-align: right; }
.split-extra {
  grid-column: 2 / -1; font-size: var(--t-small); color: var(--ink-faint); margin-top: -4px;
}

.admin-foot { margin-top: 18px; text-align: center; }
.admin-foot .btn { display: inline-block; text-decoration: none; }
.admin-foot .field-note { max-width: 44ch; margin: 10px auto 0; }

/* ------------------------------------------------------- starting a table */
/* The steps stack their contents the way the panel does, so a slot that has
   claimed the muggins row or the join box sits in the same rhythm as the
   buttons above it. */
#step-friend, #step-computer, #step-share {
  display: flex; flex-direction: column; gap: 18px;
}
#lobby-parts { display: none; }
.step-head {
  display: flex; align-items: baseline; gap: 12px;
  padding-bottom: 10px; border-bottom: 1px solid var(--rule); margin-bottom: 2px;
}
.step-title {
  font-size: var(--t-kicker); text-transform: uppercase; letter-spacing: .12em;
  color: var(--ink-faint); font-weight: 700;
}
.step-back { font-size: var(--t-small); }

.share { display: flex; flex-direction: column; gap: 12px; padding-top: 14px; }
/* The code is the thing being handed over, so it is set like a headline. */
.share code {
  font-family: var(--font-display);
  font-size: 2.4rem; letter-spacing: .12em; line-height: 1;
  text-align: center; display: block;
}
.share-link {
  margin: 0; text-align: center; font-size: var(--t-small);
  color: var(--ink-faint); word-break: break-all;
}
.share-actions { display: flex; gap: 9px; }
.share-actions .btn { flex: 1; }
.share .field-note { text-align: center; }
.share #share-open { align-self: center; font-size: var(--t-small); }

/* ------------------------------------------------------------- lobby menu */
/* Account, record, admin and settings behind one control in the corner. They
   are about the person, not about playing, so they should not sit between the
   player and the table. */
.lobby-bar {
  position: relative;
  display: flex; justify-content: flex-end;
  margin-bottom: -18px;   /* sits over the masthead's air, not below it */
}
.menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 30;
  min-width: 190px;
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 6px 0;
  display: flex; flex-direction: column;
  animation: menu-in .12s var(--ease);
}
@keyframes menu-in { from { opacity: 0; transform: translateY(-4px); } }
.menu-who {
  padding: 7px 16px 9px; font-size: var(--t-kicker);
  text-transform: uppercase; letter-spacing: .12em;
  color: var(--ink-faint); font-weight: 700;
}
.menu-who b {
  display: block; margin-top: 3px;
  font-family: var(--font-display); font-weight: 400; font-size: var(--t-body);
  text-transform: none; letter-spacing: 0; color: var(--ink);
}
.menu-item {
  appearance: none; border: 0; background: none; color: inherit;
  font: inherit; font-size: var(--t-body); text-align: left; text-decoration: none;
  padding: 9px 16px; cursor: pointer;
  transition: background .14s var(--ease);
}
.menu-item:hover { background: var(--wash); }
.menu-rule { height: 1px; background: var(--rule); margin: 5px 0; }

/* --------------------------------------------------------------- statistics */
.stats-head { text-align: center; }
.stats-head h1 {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(1.8rem, 5vw, 2.2rem); letter-spacing: -.02em; margin: 14px 0 8px;
}
.stats-head::after {
  content: ''; display: block; height: 2px; background: var(--ink); margin-top: 14px;
}
.stats-head .linkish { font-size: var(--t-body); }

.stat-grid {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
}
.stat-tile {
  border-top: 1px solid var(--rule);
  padding: 14px 2px;
}
/* A figure, set as a newspaper sets one: large, tabular, and in ink rather
   than in a colour — the colour is reserved for the two players. */
.stat-value {
  font-size: 1.9rem; font-weight: 700; letter-spacing: -.03em;
  line-height: 1.05; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.stat-label {
  margin-top: 5px; font-size: var(--t-small); font-weight: 600;
  text-transform: uppercase; letter-spacing: .09em; color: var(--ink-muted);
}
.stat-note { margin-top: 3px; font-size: var(--t-small); color: var(--ink-faint); }

.stat-extras {
  margin: 14px 0 0; text-align: center;
  font-size: var(--t-small); color: var(--ink-faint);
}

.stats-subhead {
  margin: 30px 0 12px; padding-left: 4px;
  font-size: var(--t-small); text-transform: uppercase; letter-spacing: .1em;
  color: var(--ink-faint);
}

.history { display: flex; flex-direction: column; }
.history-row {
  display: flex; align-items: center; gap: 14px;
  border-top: 1px solid var(--rule);
  border-left: 2px solid transparent;
  padding: 12px 2px 12px 10px;
}
/* a win is marked by a rule in the margin, like everything else */
.history-row.won { border-left-color: var(--p1); }
.history-row .who { flex: 1; min-width: 0; }
.history-row .who b { display: block; font-family: var(--font-display); font-weight: 400; }
.history-row .who small {
  display: block; color: var(--ink-faint); font-size: var(--t-small);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.history-row .tally {
  flex: none; font-variant-numeric: tabular-nums;
  font-weight: 600; letter-spacing: -.02em; color: var(--ink-muted);
}
.history-row .tally b { color: var(--ink); }

/* ------------------------------------------------------------------ account */
.account {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  text-align: center;
}
.acct-who { color: var(--ink-muted); font-size: var(--t-body); }
.acct-who b { color: var(--ink); font-weight: 600; }
.acct-why { margin: 0; color: var(--ink-faint); font-size: var(--t-small); max-width: 34ch; }

.btn-google {
  display: inline-flex; align-items: center; gap: 10px;
  text-decoration: none;
  background: var(--sheet);
  border-color: var(--rule);
}
.btn-google:hover { background: var(--wash); }
.g-mark {
  display: grid; place-items: center;
  width: 20px; height: 20px; border-radius: 50%;
  background: #fff; color: #4285f4;
  font-weight: 800; font-size: var(--t-small); line-height: 1;
  flex: none;
}

.lobby-foot { text-align: center; }

/* ==========================================================================
 *  Game shell
 * ========================================================================== */

.game {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  max-width: 980px;
  margin: 0 auto;
  position: relative;
}

.topbar {
  display: flex; align-items: center; gap: 14px;
  /* The notifications bell, the match log and the dealer chip all live here,
     so this is the row the status bar was covering. */
  padding: calc(14px + var(--safe-top)) calc(16px + var(--safe-right))
           10px calc(16px + var(--safe-left));
}

/* `min-width: 0` because a flex item defaults to `min-width: auto`, which means
   it refuses to shrink below its own content. With a wide inset either side —
   landscape on a notched phone — the names would hold their width and shove the
   log button out through the padding and back under the hardware. The names
   already ellipsis; this is what lets them. */
.scoreline {
  flex: 1; min-width: 0;
  display: flex; align-items: center; justify-content: center; gap: 14px;
}
.side {
  display: flex; align-items: baseline; gap: 9px;
  min-width: 0;
}
.side .nm {
  font-family: var(--font-display); font-weight: 400; letter-spacing: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 12ch;
  color: var(--ink-muted);
}
.side.is-you .nm { color: var(--ink); }
.side .pts {
  font-variant-numeric: tabular-nums;
  font-size: var(--t-title); font-weight: 650; letter-spacing: -.035em; line-height: 1;
}
.side[data-seat="0"] .pts { color: var(--p1); }
.side[data-seat="1"] .pts { color: var(--p2); }
.side .dealer-chip {
  font-size: var(--t-kicker); font-weight: 800; letter-spacing: .08em;
  border: 1px solid var(--rule); color: var(--ink-faint);
  border-radius: var(--radius); padding: 1px 4px; align-self: center;
}
.side .lvl-chip {
  font-size: var(--t-kicker); font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; align-self: center;
  color: var(--ink-faint);
}
.scoreline .vs { color: var(--ink-faint); font-size: var(--t-body); }

/* ---------------------------------------------------------------- the board */
.board-wrap {
  padding: 8px 16px 12px;
  border-bottom: 1px solid var(--rule);
}
.board { width: 100%; max-width: 720px; height: auto; display: block; margin: 0 auto; }
.board .hole { fill: var(--board-dot); }
/* every fifth hole is darker, the way a real board marks its counting */
.board .hole.mark5 { fill: var(--ink-faint); }
.board .lane-bg { fill: none; stroke: var(--rule); }
.board .street-label { fill: var(--ink-faint); font-size: 8px; font-weight: 600; letter-spacing: .1em; }
.board .peg-back { opacity: .3; }
.board .peg-front { }
.board .peg-link { stroke-width: 2; opacity: .3; }

/* --------------------------------------------------------------- the table */
/* There is no felt. The page is the table, and the play area is divided into
   three ruled bands — theirs, the middle, yours — which is what stops the
   cards floating in an undifferentiated field. A newspaper is not a blank
   page; it is a page of ruled blocks. The bands also carry a meaning the old
   single green rectangle never did. */
.table-felt {
  position: relative;   /* anchors the floating point/go callouts */
  display: flex;
  flex-direction: column;
  flex: 1;              /* absorb the slack, or the prompt floats mid-page */
  min-height: 330px;
}

.seat, .middle {
  padding: 13px calc(14px + var(--safe-right)) 13px calc(14px + var(--safe-left));
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 11px;
}
.seat { flex: 1; min-height: calc(var(--card-h) + 24px); }
/* Their side holds a line of text here, not a row of cards, so it does not
   need to reserve a card's height — nor the padding that was leaving room for
   one. The board loses a little too: this arrangement exists to be read on a
   phone, and every band above the play is height the play does not get. */
.game:has(.play-raw) .seat-opp { min-height: 0; padding-top: 8px; padding-bottom: 8px; }
.game:has(.play-raw) .board-wrap { padding: 5px 16px 7px; }
/* Narrower, so it is shorter: the board keeps its proportions and stays
   perfectly readable at this size, and the height it gives up goes to the
   play. It is the largest picture on a screen whose whole purpose is the
   text beside it. */
.game:has(.play-raw) .board { width: 76%; }
.game:has(.play-raw) .middle { padding-top: 8px; padding-bottom: 8px; }
.middle { flex: 1.2; }   /* slightly more, since it holds two rows */
.table-felt > * + * { border-top: 1px solid var(--rule); }

.middle { position: relative; }   /* anchors the floating callouts */

/* -------------------------------------------------------------------- cards */
.hand { display: flex; justify-content: center; gap: 8px; flex-wrap: wrap; }

.card {
  width: var(--card-w); height: var(--card-h);
  border-radius: var(--radius-card);
  background: var(--card);
  color: var(--card-ink);
  position: relative;
  flex: none;
  user-select: none;
  transition: transform .18s var(--ease), box-shadow .18s var(--ease), opacity .18s var(--ease);
  /* The hairline is what makes a cream card read as an object on a cream
     page. Without it the whole hand dissolves into the paper. */
  border: 1px solid var(--card-edge);
  padding: 0;
  display: block;
}
.card.dealt, .play-run .pl.dealt { animation: deal .32s var(--ease) backwards; }
@keyframes deal {
  from { opacity: 0; transform: translateY(14px) scale(.94); }
}
.card .corner {
  position: absolute; top: 4px; left: 7px;
  line-height: 1;
  font-family: var(--font-display);
  font-size: var(--t-body); letter-spacing: -.02em;
}
.card .center {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-size: 1.85rem;
}
.card.red { color: var(--card-red); }

.card.is-button { cursor: pointer; }
.card.is-button:hover { transform: translateY(-6px); }
.card.selected {
  transform: translateY(-14px);
  box-shadow: 0 0 0 2px var(--p1);
}
.card.dim { opacity: .34; cursor: not-allowed; }
.card.dim:hover { transform: none; }
.card.small { --card-w: 44px; --card-h: 64px; }
.card.small .center { font-size: 1.2rem; }
.card.small .corner { font-size: var(--t-small); top: 3px; left: 5px; }
.card.highlight { box-shadow: 0 0 0 2px var(--ink); }

.card-back {
  width: var(--card-w); height: var(--card-h);
  border-radius: var(--radius-card);
  flex: none;
  /* Face down is hatched, the way a newspaper prints a hidden card. Kept
     faint on purpose: it is the least informative thing on the table, so it
     should carry the least weight. */
  background:
    repeating-linear-gradient(45deg, var(--card-hatch) 0 1px, transparent 1px 6px),
    var(--card);
  border: 1px solid var(--card-edge);
}

/* The deck you cut from: forty slivers, each one a card edge you can pick. */
/* The deck, seen edge-on, to cut anywhere in.

   It used to be forty fixed-width slivers laid at a 10px step: 403px of them
   on a 375px screen, so it ran off both edges with no beginning or end, and
   the separators were `--card-edge` on `--card` — 1.25:1, which is no line at
   all. The whole thing read as one hatched band, and "pick anywhere in the
   stack" was asking about something that did not look like a stack.

   So: the strip is bounded, the cards divide the space between them however
   many there are, and the lines that make it a stack are mixed from
   `--card-ink` rather than `--ink`. That last part is not a detail. A card is
   a light object in every palette but `--ink` is near-white in four of them,
   so an ink-mixed line measures 1.97 on day and 1.01 on night — it would
   vanish on exactly the palettes where it is most needed. On `--card-ink` it
   holds ~1.9 everywhere. */
.cut-strip {
  display: flex;
  width: min(340px, 88%);
  margin: 0 auto;
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--card-ink) 45%, transparent);
  border-radius: var(--radius);
  background: var(--card);
}
.cut-strip button {
  flex: 1 1 0; min-width: 0;     /* however many cards, they fit */
  height: 74px;
  border: 0; padding: 0; margin: 0;
  background:
    repeating-linear-gradient(45deg, var(--card-hatch) 0 1px, transparent 1px 6px),
    var(--card);
  border-left: 1px solid color-mix(in srgb, var(--card-ink) 30%, transparent);
  cursor: pointer;
  transition: transform .16s var(--ease), box-shadow .16s var(--ease);
}
.cut-strip button:first-child { border-left: 0; }
.cut-strip button:hover, .cut-strip button:focus-visible {
  transform: translateY(-13px);
  box-shadow: 0 0 0 1.5px var(--p1);
  outline: none;
  z-index: 2;
}

/* --------------------------------------------------------- middle furniture */
/* flex-start, not center: the slots hold items of different heights (a card
   is 66px, the count dial 74px) and centring them puts the labels on three
   different baselines. Aligning the tops aligns the labels, which is the row
   a reader actually scans. */
.mid-row {
  display: flex; align-items: flex-start; justify-content: center;
  gap: 18px; flex-wrap: wrap;
}

.slot { display: flex; flex-direction: column; align-items: center; gap: 11px; }
.slot .cap {
  font-size: var(--t-kicker); text-transform: uppercase; letter-spacing: .12em;
  color: var(--ink-faint); font-weight: 700;
}
.slot .placeholder {
  width: var(--card-w); height: var(--card-h);
  border-radius: var(--radius);
  border: 1px dashed var(--rule);
}
.crib-stack { position: relative; width: var(--card-w); height: var(--card-h); }
.crib-stack .card-back { position: absolute; inset: 0; }
.crib-stack .card-back:nth-child(2) { transform: translateY(-2px) rotate(2.5deg); }
.crib-stack .card-back:nth-child(3) { transform: translateY(-4px) rotate(5deg); }
.crib-stack .card-back:nth-child(4) { transform: translateY(-6px) rotate(7.5deg); }

/* The count is a figure, not an object.

   It was a bordered circle the width of a card, ringed in the turn colour,
   sitting in a row between the crib and the starter. Two things went wrong
   with that. It read as a third card in a row of two — the row says "here are
   the objects on the table", and a number is not one of them. And a coloured
   ring is the loudest mark available on a page where colour is rationed to
   the two players, so the quietest piece of information on screen was drawn
   as the loudest, while the prompt bar underneath was already saying it in a
   sentence.

   Set as a figure instead: no enclosure, no fill, no colour. Typography
   carries it, which is the rule the rest of this file follows. */
.count-figure {
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  /* A hairline, and sized to what it holds rather than to a card. Both halves
     matter: the rule gives the figure an edge to sit against, and letting it
     take its own width keeps it from being a card-shaped rectangle in a row
     of cards — which is what the old circle got wrong. No fill, and no turn
     colour; those were the loud parts. */
  padding: 7px 13px 8px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}
/* Set large. What was wrong with the circle was that it was an object in a
   row of objects, not that the count was easy to find — during the play it is
   the number you are reading on every turn, so it should be the biggest thing
   in the band. A figure can be loud without being furniture. */
.count-figure .n {
  font-size: 2.1rem; font-weight: 700; letter-spacing: -.045em;
  font-variant-numeric: tabular-nums; line-height: .95;
}
.count-figure .of {
  font-size: var(--t-kicker); letter-spacing: .12em;
  color: var(--ink-faint); font-weight: 700;
}
/* Near thirty-one. The kicker stops being faint — enough to notice while you
   are reading the number anyway, and nothing at all from across the room.
   It is not a warning: the engine never offers a card that would bust. */
.count-figure.hot .of { color: var(--ink); }

/* The play keeps a card's real size, on a phone as well.

   The root token drops to 46px under 430px wide, and that exists for one
   reason: six cards of your own hand have to sit in one row or the hand reads
   as two hands. That is your hand's constraint and the table should not have
   inherited it — these were rendering at 46 beside a starter at 64, so the
   reference card outweighed the cards the game is actually being played with.

   It wraps to a second row once a round is wide, and that wrap falls between
   rounds rather than inside one, so it reads as the rounds it already is. The
   height comes out of the opponent's face-down band, which is flexible and is
   the least informative thing on the table. */
.play-run { --card-w: 64px; --card-h: 92px; }
.play-run {
  display: flex; align-items: center; justify-content: center;
  /* Row gap smaller than the column gap. Rounds sit apart from each other
     across the row, but a wrap is the same play continuing on the next line
     and does not want the same air — and at three rounds those two row gaps
     are exactly the margin between fitting the screen and scrolling it. */
  gap: 8px 16px;
  min-height: calc(var(--card-h) + 8px); flex-wrap: wrap;
}
.play-run .round { display: flex; gap: 5px; }
/* A round that has been counted out is history: it is already faded, and it
   steps back in size too. That is not only taste. Rounds wrap as whole units,
   so three of them at full size need three rows, which is 68px more than the
   screen has — and the opponent's band has no more to give by then. Shrinking
   what is finished keeps the live round at its full size, which is the part
   that was too small to begin with. */
.play-run .round.past { opacity: .34; }
.play-run .round.past .card { --card-w: 46px; --card-h: 66px; }
.play-run .round + .round { position: relative; }
.play-run .round + .round::before {
  content: ""; position: absolute; left: -9px; top: 12%; bottom: 12%;
  border-left: 1px dashed var(--rule);
}
.play-run .pl { position: relative; }
.play-run .pl::after {   /* whose card it was */
  content: ""; position: absolute; left: 6px; right: 6px; bottom: -5px;
  height: 2px; border-radius: var(--radius);
}
.play-run .pl[data-seat="0"]::after { background: var(--p1); }
.play-run .pl[data-seat="1"]::after { background: var(--p2); }

.float-note {
  font-size: var(--t-small); color: var(--ink-faint);
  letter-spacing: .02em;
}

/* What the opponent is holding, said rather than shown — see paintOpponent.
   Its own margins matter: as a bare <p> it carried the browser's default
   `margin: 1em 0`, thirty pixels of nothing on the arrangement with the least
   height to spare. */
.held-count {
  margin: 0; color: var(--ink-muted); font-size: var(--t-body);
}
.held-count b { color: var(--ink); font-weight: 650; font-variant-numeric: tabular-nums; }

/* How many they are holding, set as the kicker the other furniture uses so it
   reads as a label on the band rather than a second line of prose. Spelled out
   rather than reusing `.cap`, which is scoped to `.slot` and so does nothing
   here — it looked like body text in the band. */
.held-cap {
  font-size: var(--t-kicker); text-transform: uppercase; letter-spacing: .12em;
  color: var(--ink-faint); font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------- the played cards, by side */
/* Each player's cards on their own side of the table, as an alternative to
   the shared run. The order of play is gone here by design — that is what the
   arrangement trades away for looking like a table — so what is left has to
   carry its weight: the name says whose row it is, the rule under each card
   still says who played it, and a faded card is one from a count that has
   already reset. */
.play-sides { display: flex; flex-direction: column; gap: 13px; width: 100%; }
.play-side { display: flex; align-items: center; gap: 12px; min-width: 0; }
.play-side .cap {
  flex: none; width: 5.5em; text-align: right;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.play-side[data-seat="0"] .cap { color: var(--p1); }
.play-side[data-seat="1"] .cap { color: var(--p2); }
.play-cards { display: flex; gap: 6px; flex-wrap: wrap; min-height: 64px; align-items: center; }
/* The two rows read as two sides of one table, not two unrelated lists. */
.play-side + .play-side { border-top: 1px solid var(--rule); padding-top: 13px; }
.pl.past { opacity: .34; }

/* ------------------------------------------------ the play, written out */
/* No picture at all. Monospace and column-aligned, because the point of it is
   that the count and the score line up down the page and can be read as a
   sequence rather than looked at as an arrangement. */
.play-raw {
  list-style: none; margin: 0; padding: 0; width: 100%;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  /* Much larger than the rest of the app on purpose. In this arrangement the
     list *is* the play — there is no picture of the cards to look at instead —
     so it is the thing being read, not an annotation beside something else.

     Sized against the viewport rather than fixed, so it is as large as will
     fit on whatever it is being read on and cannot overflow a narrow phone.
     The floor keeps it legible on the very smallest screens and the ceiling
     stops it becoming a headline on a desktop. */
  font-size: clamp(1rem, 4.3vw, 1.2rem); line-height: 1.42;
}
.raw-line {
  display: flex; align-items: baseline; gap: 8px; padding: 1px 0;
  /* So the reason a play scored can fall to a second line at this size rather
     than pushing the count off the edge. */
  flex-wrap: wrap;
}
.raw-line.past { opacity: .42; }
.raw-who {
  /* Wide enough for most names at this size. They are capped at 24 characters
     so a long one still truncates, but nine was tighter than it needed to be —
     there was room for this without pushing the count off the edge. */
  flex: none; width: 5.6em; color: var(--ink-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.raw-line[data-seat="0"] .raw-who { color: var(--p1); }
.raw-line[data-seat="1"] .raw-who { color: var(--p2); }
.raw-card { flex: none; width: 2.8em; font-weight: 600; }
.raw-card.red { color: var(--p1); }
.raw-count {
  flex: none; width: 2.2em; text-align: right; color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* Always on its own line, indented to the card column: at this size it does
   not reliably fit beside the count, and a note that sometimes wraps and
   sometimes does not is worse than one that always sits in the same place. */
.raw-why {
  flex-basis: 100%; margin-left: 5.6em; margin-top: -3px;
  color: var(--ink-faint); font-family: var(--font-text);
  font-size: var(--t-small); line-height: 1.3;
}
.raw-why b { color: var(--good); font-weight: 700; }

/* Two columns rather than two stacked blocks: the play on the left, where it
   is read, and the table's furniture on the right, where it is glanced at.
   Stacked, the list sat under a centred row and the middle of the screen
   looked like two different screens; side by side they are one thing.

   `row-reverse` rather than reordering the markup, because the crib and the
   starter belong before the play in the document — that is the order they
   happen in, and it is what a screen reader should hear. */
.middle[data-layout="raw"] {
  flex-direction: row-reverse;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 14px;
}
/* Two across rather than three down. Stacked, this column was taller than the
   play beside it and set the height of the whole middle — on a phone with any
   browser chrome that is the difference between seeing the game and scrolling
   for it. The crib and the starter are one card each and sit happily side by
   side; the count goes underneath, spanning both. */
.middle[data-layout="raw"] .mid-row {
  flex: none;
  display: grid;
  grid-template-columns: auto auto;
  justify-items: center;
  /* Bottom-aligned, so the two cards line up even though "Their crib" wraps
     to two lines and "Starter" does not. Reserving two lines for both would
     have aligned them too, and cost 28px of height that a short phone does
     not have — the captions sitting at different heights is the cheaper of
     the two mismatches, and much the less noticeable. */
  align-items: end;
  gap: 8px 10px;
}
.middle[data-layout="raw"] .mid-row > .slot:nth-child(3) { grid-column: 1 / -1; }
.middle[data-layout="raw"] .play-raw {
  flex: 1; min-width: 0;
  /* The list is the one part of this screen that grows without limit — a long
     hand is more plays, more resets and more scoring notes — so it is the one
     part that scrolls. Everything else stays put: the board, what they hold,
     the crib, the cut, the count, your cards and the prompt are the game
     state, and needing to scroll for those was the complaint. Only the history
     moves, and it keeps its newest line in view. */
  max-height: min(31vh, 210px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* The captions, not the cards, were setting this column's width — "Their crib"
   is far wider than the card under it — and every pixel it took was a pixel
   the play needed, until the lines began wrapping and the column cost more
   height than it saved. So each slot is the width of its card and the caption
   wraps inside that. */
/* The crib is a fanned stack whose rotated cards reach about eight pixels
   above the box they sit in, so a three-pixel gap put the top card through the
   caption. This column is shorter than the play beside it, so the clearance
   costs no height overall. */
.middle[data-layout="raw"] .slot { gap: 9px; width: 44px; }
.middle[data-layout="raw"] .cap {
  white-space: normal; text-align: center; line-height: 1.15;
}
.middle[data-layout="raw"] .mid-row > .slot:nth-child(3) { width: auto; }
/* Everything in the column shrinks: the point of this arrangement is the text
   beside it, and these are here to be glanced at rather than studied. */
.middle[data-layout="raw"] .count-figure { padding: 5px 9px 6px; }
.middle[data-layout="raw"] .count-figure .n { font-size: var(--t-title); }
.middle[data-layout="raw"] .count-figure .of { display: none; }
.middle[data-layout="raw"] .card,
.middle[data-layout="raw"] .card-back { --card-w: 40px; --card-h: 58px; }
.middle[data-layout="raw"] .cap { font-size: .62rem; letter-spacing: .08em; }
/* Where somebody said go and the count went back to nothing. */
.raw-reset { height: 1px; background: var(--rule); margin: 5px 0 5px 5.6em; }

/* The sketch on the settings button: four cards, arranged the way the option
   arranges them. */
.lay-sketch {
  flex: none; display: flex; flex-wrap: wrap; gap: 3px;
  width: 42px; align-content: center;
}
.lay-sketch i {
  width: 8px; height: 12px; border-radius: 2px; display: block;
}
.lay-sketch i.a { background: var(--p1); }
.lay-sketch i.b { background: var(--p2); }
.lay-sketch i.wrap { margin-left: 20px; }
/* The written-out option has no cards to draw, so its sketch is lines of text. */
.lay-sketch.text { flex-direction: column; gap: 3px; align-items: flex-start; }
.lay-sketch.text i { width: 26px; height: 2px; border-radius: 1px; background: var(--ink-faint); }
.lay-sketch.text i.short { width: 16px; }

/* ------------------------------------------------- points and gos, in passing */
.floater {
  /* High on the felt, over the opponent's face-down cards. Anywhere lower
     covers the starter or the run of played cards, which are exactly what you
     are looking at while pegging; the backs carry no information. */
  position: absolute; left: 50%; top: 18%;
  transform: translate(-50%, 0);
  pointer-events: none;
  z-index: 5;

  /* A token in the player's colour rather than bare text on the page: the
     score lands over the cards, and loose type across a row of card backs was
     hard to read and looked like a mistake. */
  display: grid; place-items: center;
  width: 54px; height: 54px;
  border-radius: 50%;
  border: 2px solid currentColor;
  /* The theme's own sheet rather than a literal white: on the day palette
     --sheet is pure white, which is exactly what this wants, and on the five
     darker palettes a white disc would be a hole burnt in the page. */
  background: color-mix(in srgb, var(--sheet) 86%, transparent);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);

  font-weight: 700; letter-spacing: -.02em;
  white-space: nowrap;
  /* `both`, not `backwards`. Backwards alone covers the wait before a queued
     floater starts, but says nothing about after it finishes — so the moment
     the animation ended the element reverted to its own style, which is fully
     opaque at the starting position. It faded out at the top and then popped
     back at the bottom for the quarter-second before it was removed, which is
     the score appearing to flash a second time. */
  animation: float-up 1.3s var(--ease) both;
}
.floater[data-seat="0"] { color: var(--p1); }
.floater[data-seat="1"] { color: var(--p2); }

.floater-score {
  font-size: 1.3rem;
  font-variant-numeric: tabular-nums;
}
.floater-go {
  font-size: var(--t-small); font-weight: 800;
  text-transform: uppercase; letter-spacing: .14em;
  text-indent: .14em;   /* the tracking adds a gap after the last letter only */
}

@keyframes float-up {
  0%   { opacity: 0; transform: translate(-50%, 14px) scale(.85); }
  18%  { opacity: 1; transform: translate(-50%, 0) scale(1); }
  68%  { opacity: 1; transform: translate(-50%, -14px) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -52px) scale(1); }
}

/* -------------------------------------------------------------- prompt bar */
.prompt-bar {
  display: flex; align-items: center; justify-content: center; gap: 14px;
  padding: 12px calc(18px + var(--safe-right)) calc(16px + var(--safe-bottom))
           calc(18px + var(--safe-left));
  min-height: 68px;
  flex-wrap: wrap;
  /* Whatever else overflows, the thing you have to do stays on screen. */
  position: sticky; bottom: 0; z-index: 20;
  background: var(--paper);
  border-top: 1px solid var(--rule);
}
/* The bar reserves room for two lines because most prompts have a second one
   under them. The ones that appear during the play do not, and this is the
   arrangement with the least height to spare. */
.game:has(.play-raw) .prompt-bar { min-height: 54px; }
.prompt-text { font-weight: 550; letter-spacing: -.01em; }
.prompt-text small { display: block; color: var(--ink-muted); font-weight: 400; letter-spacing: 0; }
.prompt-bar .waiting { color: var(--ink-muted); display: flex; align-items: center; gap: 9px; }

.pulse {
  width: 7px; height: 7px; border-radius: 50%; background: var(--ink-muted);
  animation: pulse 1.8s var(--ease) infinite;
}
@keyframes pulse { 0%,100% { opacity: .25; transform: scale(.8); } 50% { opacity: 1; transform: scale(1); } }

/* ------------------------------------------------------------- whose turn */
/* During the play the turn changes every few seconds, and the only signal
   used to be one line of text in a bar at the bottom of a very tall screen —
   nowhere near where the eye actually is, which is your hand and the count.
   So the turn is carried where you are already looking. State is a rule and
   never a wash, so these are two strokes and one opacity, no fills.

   Which colour is "yours" depends on the chair you took, so the root carries
   the seat and everything else reads --you and --them. */
.game[data-seat="0"] { --you: var(--p1); --them: var(--p2); }
.game[data-seat="1"] { --you: var(--p2); --them: var(--p1); }

/* An inset stroke rather than a border: a border would take up space, and
   the felt would then sit three pixels right of the board above it. This
   costs no layout at all, so nothing moves when the turn changes. */
.game[data-turn="you"]  .seat-you { box-shadow: inset 3px 0 0 var(--you); }
.game[data-turn="them"] .seat-opp { box-shadow: inset 3px 0 0 var(--them); }

/* The count used to carry the turn as well, as a ring round its circle. It
   was the third thing saying it and the loudest, and it is gone — the count
   says the count now. What is left is the seat edge above, your hand going
   quiet below, and the prompt bar in words. */


/* The prompt bar deliberately carries no turn colour. It did, as a rule across
   its top, and with the seat edge and the count dial already saying whose go it
   is that was a third line of the same colour stacked under the second — the
   screen ended up striped. The bar still says it in words, which is the part
   that was never redundant. */

.hand.idle .card { opacity: .5; }

.share-line {
  display: flex; align-items: center; gap: 10px;
  background: var(--sheet); border: 1px solid var(--rule);
  border-radius: var(--radius); padding: 8px 8px 8px 14px;
}
.share-line code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .22em; font-size: var(--t-lede); font-weight: 600; color: var(--p1);
}

/* -------------------------------------------------------------- log drawer */
.log-panel {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(390px, 88vw);
  background: var(--sheet);
  border-left: 1px solid var(--rule);
  /* Above .overlay (50), because the finished-game sheet opens the drawer and
     a drawer that slides out behind the thing that opened it is just gone.
     Still below .toast (60), which has to be readable over everything. */
  z-index: 55;
  display: flex; flex-direction: column;
  animation: slide-in .26s var(--ease);
}
@keyframes slide-in { from { transform: translateX(100%); } }
.log-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: calc(16px + var(--safe-top)) calc(14px + var(--safe-right)) 12px 20px;
  border-bottom: 1px solid var(--rule);
}
.log-head h2 { font-size: var(--t-small); text-transform: uppercase; letter-spacing: .1em; color: var(--ink-faint); }
#log-list {
  /* `overflow-y: auto` alone is not enough inside a flex column: without
     `flex: 1; min-height: 0` the list sizes to its content, so a long match
     grows past the drawer and shoves the footer — and the resign control in
     it — off the bottom of the screen. */
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 12px 20px 24px;
  display: flex; flex-direction: column; gap: 2px;
}
#log-list li {
  padding: 7px 0; font-size: var(--t-body); color: var(--ink-muted);
  border-bottom: 1px solid var(--rule-soft);
  display: flex; flex-direction: column; gap: 4px;
}
#log-list li .line { display: flex; gap: 10px; align-items: baseline; }
#log-list li .pts {
  margin-left: auto; font-variant-numeric: tabular-nums; font-weight: 650; flex: none;
}
#log-list li[data-seat="0"] .pts { color: var(--p1); }
#log-list li[data-seat="1"] .pts { color: var(--p2); }
#log-list li.k-deal, #log-list li.k-phase { color: var(--ink-faint); font-size: var(--t-small); }
#log-list li.k-win { color: var(--good); font-weight: 600; }
/* The log carries the same news as the note on the counting sheet, so it is
   marked the same way rather than sitting in the run of ordinary entries. */
#log-list li.k-miscount { color: var(--bad); }
/* Table talk. The one log line that is somebody speaking rather than the game
   reporting, so it is set as a newspaper sets a quotation: serif, italic, and
   marked in the margin rather than boxed. */
#log-list li.k-chat {
  font-family: var(--font-display); font-style: italic;
  color: var(--ink-muted);
  padding-left: 11px; border-left: 2px solid var(--p2);
}
.log-foot {
  margin-top: auto;
  padding: 14px calc(20px + var(--safe-right)) calc(18px + var(--safe-bottom)) 20px;
  border-top: 1px solid var(--rule);
}
.resign-link { font-size: var(--t-small); padding: 0; }
.resign-confirm p {
  margin: 0 0 10px; font-size: var(--t-body); color: var(--ink-muted); line-height: 1.45;
}
.resign-buttons { display: flex; gap: 8px; flex-wrap: wrap; }
.resign-buttons .btn { padding: 8px 14px; font-size: var(--t-body); }
.btn-danger {
  border-color: color-mix(in srgb, var(--bad) 45%, transparent);
  color: var(--bad);
}
.btn-danger:hover:not(:disabled) {
  background: var(--wash);
  border-color: var(--bad);
}

#log-list li.hand-rule {
  border-bottom: 0; margin-top: 12px; padding-bottom: 2px;
  font-size: var(--t-kicker); text-transform: uppercase; letter-spacing: .12em;
  color: var(--ink-faint); font-weight: 700;
}

/* ------------------------------------------------------------- reactions */
/* A line you can react to is a control, and a control should not also be a
   selection target: on a touch screen the two gestures are the same one, so
   leaving the text selectable meant a tap aimed at the picker highlighted the
   log instead. Lines that do nothing stay selectable. */
#log-list li.can-react {
  cursor: pointer;
  user-select: none; -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}
#log-list li.can-react:hover { background: var(--wash); }

.rx { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.rx-chip {
  border: 1px solid var(--rule); background: var(--wash);
  border-radius: var(--radius); padding: 1px 7px; font-size: var(--t-small); line-height: 1.5;
  cursor: pointer; transition: border-color .15s var(--ease), background .15s var(--ease);
}
.rx-chip[data-seat="0"] { border-color: var(--p1-edge); }
.rx-chip[data-seat="1"] { border-color: var(--p2-edge); }
.rx-chip:disabled { cursor: default; }
.rx-chip:not(:disabled):hover { background: var(--wash); }

.rx-add {
  border: 1px dashed var(--rule); background: none; color: var(--ink-faint);
  border-radius: var(--radius); width: 22px; height: 20px; padding: 0;
  font-size: var(--t-body); line-height: 1; cursor: pointer;
  transition: color .15s var(--ease), border-color .15s var(--ease), opacity .15s var(--ease);
}
.rx-add:hover { color: var(--p1); border-color: var(--p1-edge); }

/* On a pointer device the plus is noise until you go looking for it; on a
   touch screen there is no hover, so hiding it would hide the whole feature. */
@media (hover: hover) {
  #log-list li .rx-add { opacity: 0; }
  #log-list li:hover .rx-add,
  #log-list li:focus-within .rx-add { opacity: 1; }
}

.rx-pick {
  display: flex; gap: 2px; padding: 3px;
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--wash);
  animation: rx-in .14s var(--ease);
}
@keyframes rx-in { from { opacity: 0; transform: scale(.9); } }
.rx-opt {
  border: 0; background: none; cursor: pointer;
  border-radius: var(--radius); padding: 2px 5px; font-size: var(--t-lede); line-height: 1.3;
  transition: transform .12s var(--ease), background .12s var(--ease);
}
.rx-opt:hover { background: var(--wash); transform: scale(1.15); }

/* Unread mark: a reaction that arrives while the drawer is shut is invisible
   otherwise, and in a game played over days the drawer is shut most of the time. */
#log-btn { position: relative; }

/* An opponent's emoji is the one thing that happens in the log which is aimed
   at you, and the drawer is shut most of the time — in a game played over
   days, almost always. A 7px dot in the corner of a 36px button was too easy
   to never notice, so the whole control takes their colour and keeps the dot
   on top of it. Their colour, not yours: it is their reaction. */
#log-btn.has-news {
  color: var(--them, var(--p1));
  border-color: currentColor;
}
#log-btn.has-news::after {
  content: ''; position: absolute; top: 4px; right: 4px;
  width: 9px; height: 9px; border-radius: 50%;
  background: currentColor;
  /* Matches the topbar it sits on, so the dot reads as detached from the
     icon rather than as a blob welded to it. */
  box-shadow: 0 0 0 2px var(--paper);
  animation: news-in .28s var(--ease);
}
/* Only fires when the class is first added: toggling it on an element that
   already carries it is a no-op, so this never loops. */
@keyframes news-in { from { transform: scale(0); } }

/* Hover must not wipe the signal out. */
#log-btn.has-news:hover { color: var(--them, var(--p1)); }

/* -------------------------------------------------------------------- prose */
/* The privacy and terms pages: running text, which nothing else here is. */
.prose h2 {
  font-size: var(--t-body); font-weight: 650; letter-spacing: -.01em;
  margin: 22px 0 6px;
}
.prose h2:first-of-type { margin-top: 6px; }
.prose p { margin: 0 0 10px; color: var(--ink-muted); line-height: 1.6; }
.prose ul { margin: 0 0 12px; padding: 0; list-style: none; }
.prose li {
  color: var(--ink-muted); line-height: 1.6;
  margin: 0 0 7px; padding-left: 14px; position: relative;
}
.prose li::before {
  content: ""; position: absolute; left: 0; top: .72em;
  width: 6px; height: 1px; background: var(--ink-faint);
}
.prose b { color: var(--ink); font-weight: 600; }
.prose a { color: var(--ink); }
.prose code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .9em;
}
.prose .linkish { font-size: inherit; }

/* ---------------------------------------------------------------- analysis */
.analysis { margin: 18px 0 4px; border-top: 1px solid var(--rule); padding-top: 14px; }
.analysis-body .cap { display: block; margin: 0 0 8px; }

/* The written reading. Serif and a shade larger than the rows beneath it: it
   is somebody talking about the figures, not another figure, and the change
   of voice is what says so. */
.an-reading {
  font-family: var(--font-display);
  font-size: var(--t-lede); line-height: 1.5;
  color: var(--ink); margin: 0 0 14px;
}
.an-row {
  display: flex; align-items: baseline; gap: 10px;
  padding: 7px 0; border-bottom: 1px solid var(--rule);
}
.an-row .an-label { flex: 1; min-width: 0; }
.an-row b { font-variant-numeric: tabular-nums; font-weight: 650; }
.an-row small { color: var(--ink-faint); flex: none; }
.an-worst { margin: 12px 0 4px; padding-left: 10px; border-left: 2px solid var(--rule); }
.an-remarks { list-style: none; margin: 14px 0 0; padding: 0; }
.an-remarks li {
  margin: 0 0 10px; padding-left: 14px; position: relative;
  color: var(--ink-muted); font-size: var(--t-small); line-height: 1.5;
}
/* A rule rather than a bullet glyph, matching everything else that marks a
   line here. */
.an-remarks li::before {
  content: ""; position: absolute; left: 0; top: .55em;
  width: 6px; height: 1px; background: var(--ink-faint);
}
/* Praise is marked by the colour of its rule, not by the colour of its text —
   a green sentence among grey ones reads as a warning label. The words say
   which it is; this only helps the eye find them. */
.an-remarks li.is-good::before { background: var(--good); width: 8px; height: 2px; }

.reliability { margin: 10px 0 2px; }
.reliability .cap { display: block; margin: 0 0 8px; }
.rel-lede {
  margin: 0 0 12px; color: var(--ink-muted);
  font-size: var(--t-small); line-height: 1.5;
}
.rel-row { padding: 9px 0; border-top: 1px solid var(--rule); }
.rel-head { display: flex; align-items: baseline; gap: 10px; }
.rel-head b { flex: 1; min-width: 0; font-weight: 600; }
/* A plain word, not a colour-coded badge: "not judged" is not a warning and
   "exact" is not a reward, and colouring them would imply a ranking. */
.rel-verdict {
  flex: none; font-size: var(--t-kicker); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-faint);
}
.rel-row p {
  margin: 4px 0 0; color: var(--ink-muted);
  font-size: var(--t-small); line-height: 1.45;
}
/* The two takeaways read as a pair, so they are set as one: a label in small
   caps and the sentence run in after it. No colour telling them apart — "you
   cannot" is a caveat, not a warning, and red would make it look like an
   error message. */
.rel-can, .rel-cannot { margin-top: 6px !important; padding-left: 12px; position: relative; }
.rel-can span, .rel-cannot span {
  font-size: var(--t-kicker); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-faint);
}
.rel-can span::after, .rel-cannot span::after { content: " \2014 "; letter-spacing: 0; }
.rel-can::before, .rel-cannot::before {
  content: ""; position: absolute; left: 0; top: .5em; bottom: .3em;
  width: 1px; background: var(--rule);
}
.rel-foot {
  margin: 14px 0 0; padding-top: 10px; border-top: 1px solid var(--rule);
  color: var(--ink-faint); font-size: var(--t-kicker); line-height: 1.5;
}

/* ------------------------------------------------------------------- drill */
.drill .hand { align-items: center; gap: 7px; margin: 14px 0 4px; }
.drill-kind {
  display: flex; align-items: baseline; justify-content: space-between;
  margin: 0; gap: 12px;
}
/* Present but quiet: it is there to be glanced at between hands, not to
   hurry anybody through the one they are counting. */
.drill-clock {
  font-weight: 600; letter-spacing: 0;
  color: var(--ink-faint); font-variant-numeric: tabular-nums;
}
.drill-plus { color: var(--ink-faint); font-weight: 600; padding: 0 3px; }

/* The number first and large, because it is the answer; what you said second,
   because it is the correction. Right and wrong are told apart by a rule in
   the margin and by the words, never by colour alone. */
.drill-verdict {
  display: flex; align-items: baseline; gap: 14px;
  margin: 18px 0 2px; padding-left: 12px;
  border-left: 2px solid var(--bad);
}
.drill-verdict.is-right { border-left-color: var(--good); }
.drill-total {
  font-size: var(--t-title); font-weight: 650; letter-spacing: -.035em;
  line-height: 1; flex: none;
}
.drill-said { color: var(--bad); font-weight: 600; }
.drill-verdict.is-right .drill-said { color: var(--ink-muted); }

/* ------------------------------------------------------------------ roster */
.roster { list-style: none; margin: 10px 0 0; padding: 0; }
.roster-row {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 2px;
  border-top: 1px solid var(--rule);
}
.roster:last-child .roster-row:last-child { border-bottom: 1px solid var(--rule); }
.roster-who { flex: 1; min-width: 0; }
.roster-who b {
  font-family: var(--font-display); font-weight: 400;
  display: block; font-size: var(--t-lede);
}
.roster-who small { color: var(--ink-faint); font-size: var(--t-small); display: block; }
/* A block is a state, and state is a rule rather than a wash. */
.roster-row.is-blocked { box-shadow: inset 3px 0 0 var(--bad); padding-left: 12px; }
.roster-blocked { color: var(--bad) !important; font-weight: 600; }
.roster-acts { display: flex; gap: 14px; flex: none; }
/* The one action here that cannot be undone. Coloured, but only the word — a
   filled red button would sit heavier on the page than Rename deserves to be
   read against. */
.is-destructive { color: var(--bad); }

/* ---------------------------------------------------------------- overlay */
.overlay {
  position: fixed; inset: 0; z-index: 50;
  background: var(--scrim);
  backdrop-filter: blur(7px); -webkit-backdrop-filter: blur(7px);
  display: grid; place-items: center;
  padding: calc(20px + var(--safe-top)) calc(20px + var(--safe-right))
           calc(20px + var(--safe-bottom)) calc(20px + var(--safe-left));
  animation: fade .22s var(--ease);
}
@keyframes fade { from { opacity: 0; } }

.sheet {
  background: var(--sheet);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  width: min(560px, 100%);
  max-height: min(86dvh, 760px);
  overflow-y: auto;
  padding: 26px;
  animation: rise .3s var(--ease);
}
@keyframes rise { from { opacity: 0; transform: translateY(16px) scale(.98); } }
/* Already on screen: a rebuild must not replay its arrival. */
.sheet.settled { animation: none; }
/* A sheet heading names something, so it takes the serif; the line under it
   is a standfirst and is set as one. */
.sheet h2 {
  font-family: var(--font-display); font-weight: 400;
  font-size: var(--t-title); letter-spacing: -.01em;
}
.sheet .sub {
  color: var(--ink-muted); font-size: var(--t-small); margin: 4px 0 0;
  font-family: var(--font-display); font-style: italic;
}
/* the heavy rule under a heading, as under a masthead */
.sheet h2 + .sub { padding-bottom: 14px; border-bottom: 2px solid var(--ink); }
.sheet .foot { display: flex; gap: 10px; justify-content: flex-end; margin-top: 22px; flex-wrap: wrap; }
/* Sits with "How did I play?" rather than in the foot: both are for looking
   back at the game just played, while the foot is for leaving it. */
.sheet-log { display: block; margin: 14px 0 0; }

.count-block { border-top: 1px solid var(--rule); padding: 16px 0 4px; }
.count-block:first-of-type { border-top: 0; }
.count-block .cb-head {
  display: flex; align-items: baseline; gap: 10px; margin-bottom: 10px;
}
.count-block .cb-head .who {
  font-family: var(--font-display); font-weight: 400; font-size: var(--t-lede);
}
.count-block .cb-head .what {
  color: var(--ink-faint); font-size: var(--t-kicker); font-weight: 700;
  text-transform: uppercase; letter-spacing: .12em;
}
.count-block .cb-head .tot {
  margin-left: auto; font-variant-numeric: tabular-nums;
  font-size: 1.6rem; font-weight: 700; letter-spacing: -.03em; line-height: 1;
}
.count-block[data-seat="0"] .tot { color: var(--p1); }
.count-block[data-seat="1"] .tot { color: var(--p2); }
.count-block .cards { display: flex; gap: 5px; margin-bottom: 10px; flex-wrap: wrap; align-items: center; }
.count-block .cards .plus { color: var(--ink-faint); margin: 0 3px; }
/* The itemised breakdown, set the way a newspaper sets a short tabulation
   inside body text rather than as a row of pills. */
.items {
  margin: 11px 0 0; font-size: var(--t-small); line-height: 1.75;
  color: var(--ink-muted);
}
.items .term { white-space: nowrap; }
.items .term b {
  color: var(--ink); font-variant-numeric: tabular-nums; margin-left: 3px;
}
.items .sep { color: var(--ink-faint); margin: 0 7px; }
.items .none { font-family: var(--font-display); font-style: italic; }

/* ------------------------------------------------------- counting your hand */
/* The block you owe a count on, marked by a rule in the margin and nothing
   else. A fill would be invisible at night — a dark-on-dark step of two or
   three percent luminance is not a signal — and a rule reads the same against
   either ground. The content stays aligned with its neighbours; the rule
   hangs outside the column, the way a sidebar rule does. */
.count-block.is-current {
  margin-left: -14px; padding-left: 12px;
  border-left: 2px solid var(--ink);
}
.count-block.is-current[data-seat="0"] { border-left-color: var(--p1); }
.count-block.is-current[data-seat="1"] { border-left-color: var(--p2); }

/* Deliberate, not inherited: the block you owe keeps the seat colour, because
   the "?" is the one thing asking you to act. A block not yet reached is
   asking nothing, so it recedes. */
.count-block .tot.pending { font-weight: 400; }
.count-block:not(.is-current) .tot.pending { color: var(--ink-faint); }

.count-entry { margin-top: 12px; }
.count-entry label {
  display: block; margin-bottom: 8px;
  font-size: var(--t-body); color: var(--ink-muted);
}
.count-row { display: flex; gap: 10px; align-items: stretch; }
.count-row input {
  width: 84px; flex: none;
  background: none;
  /* a ruled underline — the field is the line it sits on */
  border: 0; border-bottom: 1.5px solid var(--ink);
  padding: 2px 4px 6px;
  font-size: var(--t-title); font-weight: 700;
  font-variant-numeric: tabular-nums; letter-spacing: -.02em;
  text-align: center;
  transition: border-color .18s var(--ease), box-shadow .18s var(--ease);
  /* The spinners are useless at this size and crowd the digits. */
  -moz-appearance: textfield;
}
.count-row input::-webkit-outer-spin-button,
.count-row input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.count-row input::placeholder { color: var(--ink-faint); font-weight: 400; }
.count-row input:focus {
  outline: none;
  border-bottom-color: var(--p1); border-bottom-width: 2px;
}

/* Being told the count was wrong is the whole point of counting by hand, so
   this is the one line on the sheet that has to be seen. It was set as a
   newspaper correction — muted, serif, italic — which read as an aside rather
   than as feedback. Sans, red, and a rule in the margin: the same way every
   other state in this design is marked. */
.miscount {
  margin: 12px 0 2px;
  padding-left: 10px;
  border-left: 2px solid var(--bad);
  color: var(--bad);
  font-size: var(--t-body); font-weight: 600; line-height: 1.45;
}

.result-hero { text-align: center; padding: 8px 0 4px; }
.result-hero .big {
  font-size: var(--t-title); font-weight: 650; letter-spacing: -.04em;
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
.result-hero .who { font-size: var(--t-lede); margin-top: 6px; }
.result-hero .skunk {
  display: inline-block; margin-top: 10px;
  font-size: var(--t-small); font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--p1); border-left: 2px solid var(--p1);
  padding: 2px 0 2px 10px;
}

.rules-body h3 { font-size: var(--t-small); text-transform: uppercase; letter-spacing: .1em; color: var(--ink-faint); margin: 18px 0 6px; }
.rules-body p { margin: 0 0 8px; color: var(--ink-muted); font-size: var(--t-body); }
.rules-body b { color: var(--ink); font-weight: 600; }

.toast {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%);
  background: var(--wash); color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 10px 18px; z-index: 60;
  animation: toast-in .24s var(--ease);
  max-width: min(420px, 90vw); text-align: center; font-size: var(--t-body);
}
.toast.bad { border-color: color-mix(in srgb, var(--bad) 45%, transparent); }
@keyframes toast-in { from { opacity: 0; transform: translate(-50%, 12px); } }

/* ------------------------------------------------------------- responsive */
@media (max-width: 620px) {
  :root { --card-w: 52px; --card-h: 75px; }
  .table-felt { margin: 0 8px 8px; padding: 12px 10px; min-height: 320px; }
  .board-wrap { padding: 2px 10px 6px; }
  .hand { gap: 6px; }
  .side .pts { font-size: var(--t-title); }
  .side .nm { max-width: 8ch; }
  .sheet { padding: 20px; border-radius: var(--radius); }
}

/* Six cards have to sit in one row on a phone, or the hand reads as two hands. */
@media (max-width: 430px) {
  :root { --card-w: 46px; --card-h: 66px; }
  /* Three columns leaves the descriptions wrapping mid-phrase; stack instead. */
  .levels { grid-template-columns: 1fr; gap: 8px; }
  .level { flex-direction: row; justify-content: space-between; padding: 11px 14px; }
  .level small { text-align: right; }
  .hand { gap: 5px; }
  .card .center { font-size: var(--t-title); }
  .card .corner { font-size: var(--t-small); }
  .table-felt { padding: 12px 8px; min-height: 300px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
  /* The blanket rule above would collapse these to nothing, which does not
     mean "no motion" — it means the points and gos never appear at all. They
     carry information, so they still show; they just hold still and fade. */
  .floater {
    animation: float-hold 1.3s linear backwards !important;
    animation-duration: 1.3s !important;
  }
}

@keyframes float-hold {
  0%, 75% { opacity: 1; transform: translate(-50%, 0); }
  100%    { opacity: 0; transform: translate(-50%, 0); }
}
